mirror of
https://github.com/PrimalHQ/primal-web-app.git
synced 2024-09-28 16:00:50 +00:00
Compare commits
3 Commits
16c7c1c650
...
8a7140e109
Author | SHA1 | Date | |
---|---|---|---|
|
8a7140e109 | ||
|
3bed6dad60 | ||
|
94c91bdc42 |
@ -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>
|
||||
</>
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -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}`}>
|
||||
|
@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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()}</>;
|
||||
|
Loading…
Reference in New Issue
Block a user