Compare commits

...

3 Commits

Author SHA1 Message Date
Bojan Mojsilovic
8a7140e109 Set vanity users accsess articles by identifier 2024-06-20 12:10:14 +02:00
Bojan Mojsilovic
3bed6dad60 Better scroll position meintain 2024-06-20 11:37:05 +02:00
Bojan Mojsilovic
94c91bdc42 Hold reads scroll position 2024-06-20 11:09:25 +02:00
7 changed files with 135 additions and 26 deletions

View File

@ -139,7 +139,10 @@ const Router: Component = () => {
<Route path="/mutelist/:npub" component={Mutelist} />
<Route path="/new" component={CreateAccount} />
<Route path="/404" component={NotFound} />
<Route path="/:vanityName" component={Profile} data={getKnownProfiles} />
<Route path="/:vanityName">
<Route path="/" component={Profile} data={getKnownProfiles} />
<Route path="/:identifier" component={Thread} data={getKnownProfiles} />
</Route>
</Route>
</Routes>
</>

View File

@ -1,5 +1,5 @@
import { A } from '@solidjs/router';
import { batch, Component, createEffect, For, JSXElement, Show } from 'solid-js';
import { batch, Component, createEffect, For, JSXElement, onMount, Show } from 'solid-js';
import { createStore } from 'solid-js/store';
import { Portal } from 'solid-js/web';
import { useAccountContext } from '../../contexts/AccountContext';
@ -24,6 +24,8 @@ import styles from './ArticlePreview.module.scss';
const ArticlePreview: Component<{
id?: string,
article: PrimalArticle,
height?: number,
onRender?: (article: PrimalArticle, el: HTMLAnchorElement | undefined) => void,
}> = (props) => {
const app = useAppContext();
@ -204,8 +206,19 @@ const ArticlePreview: Component<{
);
}
let articlePreview: HTMLAnchorElement | undefined;
const onImageLoaded = () => {
props.onRender && props.onRender(props.article, articlePreview);
};
return (
<A class={styles.article} href={`/e/${props.article.naddr}`}>
<A
ref={articlePreview}
class={styles.article}
href={`/e/${props.article.naddr}`}
style={props.height ? `height: ${props.height}px` : ''}
>
<div class={styles.upRightFloater}>
<NoteContextTrigger
ref={articleContextMenu}
@ -258,7 +271,7 @@ const ArticlePreview: Component<{
when={props.article.image}
fallback={<div class={styles.placeholderImage}></div>}
>
<img src={props.article.image} />
<img src={props.article.image} onload={onImageLoaded} />
</Show>
</div>
</div>

View File

@ -42,6 +42,10 @@ const ReedSelect: Component<{ isPhone?: boolean, id?: string, big?: boolean}> =
name: option.label,
};
const selected = reeds?.selectedFeed;
if (selected && selected.hex === feed.hex) return;
reeds?.actions.clearNotes();
reeds?.actions.selectFeed(feed);
};

View File

@ -16,6 +16,7 @@ import LoginModal from '../LoginModal/LoginModal';
import { userName } from '../../stores/profile';
import { PrimalUser } from '../../types/primal';
import ReedSelect from '../FeedSelect/ReedSelect';
import { useReadsContext } from '../../contexts/ReadsContext';
const ReadsHeader: Component< {
id?: string,
@ -25,6 +26,48 @@ const ReadsHeader: Component< {
newPostAuthors: PrimalUser[],
} > = (props) => {
const reads = useReadsContext();
let lastScrollTop = document.body.scrollTop || document.documentElement.scrollTop;
const onScroll = () => {
const scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
// const smallHeader = document.getElementById('small_header');
const border = document.getElementById('small_bottom_border');
reads?.actions.updateScrollTop(scrollTop);
const isScrollingDown = scrollTop > lastScrollTop;
lastScrollTop = scrollTop;
if (scrollTop < 2) {
if (border) {
border.style.display = 'none';
}
return;
}
if (lastScrollTop < 2) {
return;
}
if (border) {
border.style.display = 'flex';
}
if (!isScrollingDown) {
return;
}
}
onMount(() => {
window.addEventListener('scroll', onScroll);
});
onCleanup(() => {
window.removeEventListener('scroll', onScroll);
});
return (
<div id={props.id}>
<div class={`${styles.bigFeedSelect} ${styles.readsFeed}`}>

View File

@ -56,6 +56,7 @@ type ReadsContextStore = {
isFetching: boolean,
query: SelectionOption | undefined,
},
articleHeights: Record<string, number>,
actions: {
saveNotes: (newNotes: PrimalArticle[]) => void,
clearNotes: () => void,
@ -70,7 +71,8 @@ type ReadsContextStore = {
doSidebarSearch: (query: string) => void,
updateSidebarQuery: (selection: SelectionOption) => void,
getFirstPage: () => void,
resetSelectedFeed: () => void;
resetSelectedFeed: () => void,
setArticleHeight: (id: string, height: number) => void,
}
}
@ -122,6 +124,7 @@ const initialHomeData = {
query: undefined,
},
recomendedReads: [],
articleHeights: {},
};
export const ReadsContext = createContext<ReadsContextStore>();
@ -652,6 +655,10 @@ export const ReadsProvider = (props: { children: ContextChildren }) => {
saveNotes(newPosts, scope);
};
const setArticleHeight = (id: string, height: number) => {
updateStore('articleHeights', id, () => height);
}
// SOCKET HANDLERS ------------------------------
const onMessage = (event: MessageEvent) => {
@ -831,6 +838,7 @@ export const ReadsProvider = (props: { children: ContextChildren }) => {
doSidebarSearch,
updateSidebarQuery,
getFirstPage,
setArticleHeight,
},
});

View File

@ -23,7 +23,7 @@ import StickySidebar from '../components/StickySidebar/StickySidebar';
import { useHomeContext } from '../contexts/HomeContext';
import { useIntl } from '@cookbook/solid-intl';
import { createStore } from 'solid-js/store';
import { PrimalUser } from '../types/primal';
import { PrimalArticle, PrimalUser } from '../types/primal';
import Avatar from '../components/Avatar/Avatar';
import { userName } from '../stores/profile';
import { useAccountContext } from '../contexts/AccountContext';
@ -64,7 +64,7 @@ const Home: Component = () => {
onMount(() => {
setIsHome(true);
// setIsHome(true);
scrollWindowTo(context?.scrollTop);
});
@ -138,18 +138,24 @@ const Home: Component = () => {
createEffect(() => {
if (account?.isKeyLookupDone && account.publicKey) {
context?.actions.clearNotes();
if (params.topic) {
context?.actions.clearNotes();
context?.actions.fetchNotes(`filter;${decodeURIComponent(params.topic)}`, APP_ID);
return;
}
context?.actions.resetSelectedFeed();
context?.actions.selectFeed({ hex: account.publicKey, name: 'My Reads'});
const selected = context?.selectedFeed || { hex: account.publicKey, name: 'My Reads'};
// context?.actions.resetSelectedFeed();
context?.actions.selectFeed({ ...selected });
}
});
const onArticleRendered = (article: PrimalArticle, el: HTMLAnchorElement | undefined) => {
context?.actions.setArticleHeight(article.naddr, el?.getBoundingClientRect().height || 0);
};
return (
<div class={styles.homeContent}>
<PageTitle title={intl.formatMessage(branding)} />
@ -175,6 +181,7 @@ const Home: Component = () => {
<Link
class={styles.backToReads}
href={'/reads'}
onClick={() => context?.actions.resetSelectedFeed()}
>
Reads:
</Link>
@ -195,7 +202,13 @@ const Home: Component = () => {
>
<div class={styles.feed}>
<For each={context?.notes} >
{note => <ArticlePreview article={note} />}
{note => (
<ArticlePreview
article={note}
height={context?.articleHeights[note.naddr]}
onRender={onArticleRendered}
/>
)}
</For>
</div>
</Show>

View File

@ -1,4 +1,4 @@
import { Component, onMount } from 'solid-js';
import { Component, onMount, Resource } from 'solid-js';
import Branding from '../components/Branding/Branding';
import Wormhole from '../components/Wormhole/Wormhole';
import Search from '../components/Search/Search';
@ -15,43 +15,68 @@ import styles from './Downloads.module.scss';
import { downloads as t } from '../translations';
import { useIntl } from '@cookbook/solid-intl';
import StickySidebar from '../components/StickySidebar/StickySidebar';
import { appStoreLink, playstoreLink, apkLink } from '../constants';
import { appStoreLink, playstoreLink, apkLink, Kind } from '../constants';
import ExternalLink from '../components/ExternalLink/ExternalLink';
import PageCaption from '../components/PageCaption/PageCaption';
import PageTitle from '../components/PageTitle/PageTitle';
import { useSettingsContext } from '../contexts/SettingsContext';
import { useParams } from '@solidjs/router';
import { RouteDataFuncArgs, useParams, useRouteData } from '@solidjs/router';
import NotFound from './NotFound';
import NoteThread from './NoteThread';
import { nip19 } from 'nostr-tools';
import Longform from './Longform';
import { VanityProfiles } from '../types/primal';
import { logError } from '../lib/logger';
const EventPage: Component = () => {
const params = useParams();
const routeData = useRouteData<(opts: RouteDataFuncArgs) => Resource<VanityProfiles>>();
const render = () => {
const { id } = params;
const { id, identifier } = params;
if (!id) return <NotFound />;
if (!id && !identifier) return <NotFound />;
if (id.startsWith('naddr1')) {
return <Longform naddr={id} />
}
if (id) {
if (id.startsWith('naddr1')) {
return <Longform naddr={id} />
}
if (id.startsWith('note1')) {
return <NoteThread noteId={id} />
}
if (id.startsWith('note1')) {
return <NoteThread noteId={id} />
}
if (id.startsWith('nevent1')) {
const noteId = nip19.noteEncode(nip19.decode(id).data.id);
if (id.startsWith('nevent1')) {
const noteId = nip19.noteEncode(nip19.decode(id).data.id);
return <NoteThread noteId={noteId} />
}
const noteId = nip19.noteEncode(id);
return <NoteThread noteId={noteId} />
}
const noteId = nip19.noteEncode(id);
if (identifier) {
const name = params.vanityName.toLowerCase();
if (!name) return <NotFound />;
const pubkey = routeData()?.names[name];
const kind = Kind.LongForm;
try {
const naddr = nip19.naddrEncode({ pubkey, kind, identifier });
return <Longform naddr={naddr} />
} catch (e) {
logError('Error encoding naddr: ', e);
return <NotFound />;
}
}
return <NoteThread noteId={noteId} />
};
return <>{render()}</>;