This commit is contained in:
Shusui MOYATANI
2023-03-27 00:53:19 +09:00
parent 567e1f8df8
commit 3e52a24f87
7 changed files with 60 additions and 25 deletions

View File

@@ -1,4 +1,6 @@
import { Show, type JSX, type Component } from 'solid-js'; import { Show, type JSX, type Component } from 'solid-js';
import ArrowLeft from 'heroicons/24/outline/arrow-left.svg';
import { useHandleCommand } from '@/hooks/useCommandBus'; import { useHandleCommand } from '@/hooks/useCommandBus';
import { ColumnContext, useColumnState } from '@/components/ColumnContext'; import { ColumnContext, useColumnState } from '@/components/ColumnContext';
import ColumnContentDisplay from '@/components/ColumnContentDisplay'; import ColumnContentDisplay from '@/components/ColumnContentDisplay';
@@ -57,8 +59,14 @@ const Column: Component<ColumnProps> = (props) => {
{(columnContent) => ( {(columnContent) => (
<div class="absolute h-full w-full bg-white"> <div class="absolute h-full w-full bg-white">
<div class="flex h-8 shrink-0 items-center border-b bg-white px-2"> <div class="flex h-8 shrink-0 items-center border-b bg-white px-2">
<button class="w-full text-left" onClick={() => columnState?.clearColumnContext()}> <button
class="flex w-full items-center gap-1"
onClick={() => columnState?.clearColumnContext()}
>
<div class="inline-block h-4 w-4">
<ArrowLeft />
</div>
<div></div>
</button> </button>
</div> </div>
<ul class="flex h-full flex-col overflow-y-scroll scroll-smooth"> <ul class="flex h-full flex-col overflow-y-scroll scroll-smooth">

View File

@@ -7,8 +7,17 @@ type EventLinkProps = {
eventId: string; eventId: string;
}; };
const EventLink: Component<EventLinkProps> = (props) => ( const tryEncode = (eventId: string) => {
<span class="text-blue-500 underline">{noteEncode(props.eventId)}</span> try {
); return noteEncode(eventId);
} catch (err) {
console.error('failed to encode event id into Bech32 entity (NIP-19) but ignore', eventId, err);
return eventId;
}
};
const EventLink: Component<EventLinkProps> = (props) => {
return <button class="text-blue-500 underline">{tryEncode(props.eventId)}</button>;
};
export default EventLink; export default EventLink;

View File

@@ -3,18 +3,18 @@ import GeneralUserMentionDisplay from '@/components/textNote/GeneralUserMentionD
import useModalState from '@/hooks/useModalState'; import useModalState from '@/hooks/useModalState';
export type MentionedUserDisplayProps = { export type MentionedUserDisplayProps = {
mentionedUser: MentionedUser; pubkey: string;
}; };
const MentionedUserDisplay = (props: MentionedUserDisplayProps) => { const MentionedUserDisplay = (props: MentionedUserDisplayProps) => {
const { showProfile } = useModalState(); const { showProfile } = useModalState();
const handleClick = () => { const handleClick = () => {
showProfile(props.mentionedUser.pubkey); showProfile(props.pubkey);
}; };
return ( return (
<button class="inline text-blue-500 underline" onClick={handleClick}> <button class="inline text-blue-500 underline" onClick={handleClick}>
<GeneralUserMentionDisplay pubkey={props.mentionedUser.pubkey} /> <GeneralUserMentionDisplay pubkey={props.pubkey} />
</button> </button>
); );
}; };

View File

@@ -27,7 +27,7 @@ const TextNoteContentDisplay = (props: TextNoteContentDisplayProps) => {
return <PlainTextDisplay plainText={item} />; return <PlainTextDisplay plainText={item} />;
} }
if (item.type === 'MentionedUser') { if (item.type === 'MentionedUser') {
return <MentionedUserDisplay mentionedUser={item} />; return <MentionedUserDisplay pubkey={item.pubkey} />;
} }
if (item.type === 'MentionedEvent') { if (item.type === 'MentionedEvent') {
if (props.embedding) { if (props.embedding) {
@@ -43,6 +43,12 @@ const TextNoteContentDisplay = (props: TextNoteContentDisplayProps) => {
</div> </div>
); );
} }
if (item.data.type === 'npub' && props.embedding) {
return <MentionedUserDisplay pubkey={item.data.data} />;
}
if (item.data.type === 'nprofile' && props.embedding) {
return <MentionedUserDisplay pubkey={item.data.data.pubkey} />;
}
return <span class="text-blue-500 underline">{item.content}</span>; return <span class="text-blue-500 underline">{item.content}</span>;
} }
if (item.type === 'HashTag') { if (item.type === 'HashTag') {
@@ -61,6 +67,7 @@ const TextNoteContentDisplay = (props: TextNoteContentDisplayProps) => {
} }
return <SafeLink class="text-blue-500 underline" href={item.content} />; return <SafeLink class="text-blue-500 underline" href={item.content} />;
} }
console.error('Not all ParsedTextNoteNodes are covered', item);
return null; return null;
}} }}
</For> </For>

View File

@@ -165,16 +165,7 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
}); });
return ( return (
<div <div class="nostr-textnote flex flex-col">
class="nostr-textnote flex flex-col"
onClick={() => {
// FIXME
// columnContext?.setColumnContent({
// type: 'Replies',
// eventId: event().rootEvent()?.id ?? props.event.id,
// });
}}
>
<div class="flex w-full gap-1"> <div class="flex w-full gap-1">
<button <button
class="author-icon h-10 w-10 shrink-0 overflow-hidden" class="author-icon h-10 w-10 shrink-0 overflow-hidden"
@@ -213,7 +204,20 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
{/* TODO <Match when={author()?.nip05 != null}>@{author()?.nip05}</Match> */} {/* TODO <Match when={author()?.nip05 != null}>@{author()?.nip05}</Match> */}
</div> </div>
</button> </button>
<div class="created-at shrink-0">{createdAt()}</div> <div class="created-at shrink-0">
<button
type="button"
class="hover:underline"
onClick={() => {
columnContext?.setColumnContent({
type: 'Replies',
eventId: event().rootEvent()?.id ?? props.event.id,
});
}}
>
{createdAt()}
</button>
</div>
</div> </div>
<div <div
ref={contentRef} ref={contentRef}

View File

@@ -60,8 +60,11 @@ const parseTextNote = (event: NostrEvent): ParsedTextNote => {
const matches = [ const matches = [
...event.content.matchAll(/(?:#\[(?<idx>\d+)\])/g), ...event.content.matchAll(/(?:#\[(?<idx>\d+)\])/g),
...event.content.matchAll(/#(?<hashtag>[^[-^`:-@!-/{-~\d\s][^[-^`:-@!-/{-~\s]+)/g), ...event.content.matchAll(/#(?<hashtag>[^[-^`:-@!-/{-~\d\s][^[-^`:-@!-/{-~\s]+)/g),
// raw NIP-19 codes, NIP-21 links (NIP-27)
// nrelay and naddr is not supported by nostr-tools // nrelay and naddr is not supported by nostr-tools
...event.content.matchAll(/(?<nip19>(npub|note|nprofile|nevent)1[ac-hj-np-z02-9]+)/gi), ...event.content.matchAll(
/(?:nostr:)?(?<mention>(npub|note|nprofile|nevent)1[ac-hj-np-z02-9]+)/gi,
),
...event.content.matchAll( ...event.content.matchAll(
/(?<url>(?:https?|wss?):\/\/[-a-zA-Z0-9.]+(?:\/[-[\]~!$&'()*+.,:;@%\w]+|\/)*(?:\?[-[\]~!$&'()*+.,/:;%@\w&=]+)?(?:#[-[\]~!$&'()*+.,/:;%@\w?&=#]+)?)/g, /(?<url>(?:https?|wss?):\/\/[-a-zA-Z0-9.]+(?:\/[-[\]~!$&'()*+.,:;@%\w]+|\/)*(?:\?[-[\]~!$&'()*+.,/:;%@\w&=]+)?(?:#[-[\]~!$&'()*+.,/:;%@\w?&=#]+)?)/g,
), ),
@@ -117,10 +120,10 @@ const parseTextNote = (event: NostrEvent): ParsedTextNote => {
}; };
result.push(mentionedEvent); result.push(mentionedEvent);
} }
} else if (match.groups?.nip19) { } else if (match.groups?.mention) {
pushPlainText(index); pushPlainText(index);
try { try {
const decoded = decode(match[0]); const decoded = decode(match[1]);
const bech32Entity: Bech32Entity = { const bech32Entity: Bech32Entity = {
type: 'Bech32Entity', type: 'Bech32Entity',
content: match[0], content: match[0],
@@ -129,6 +132,8 @@ const parseTextNote = (event: NostrEvent): ParsedTextNote => {
result.push(bech32Entity); result.push(bech32Entity);
} catch (e) { } catch (e) {
console.error(`failed to parse Bech32 entity (NIP-19) but ignore this: ${match[0]}`); console.error(`failed to parse Bech32 entity (NIP-19) but ignore this: ${match[0]}`);
pushPlainText(index + match[0].length);
return;
} }
} else if (match.groups?.hashtag) { } else if (match.groups?.hashtag) {
pushPlainText(index); pushPlainText(index);

View File

@@ -286,6 +286,7 @@ export const useProfile = (propsProvider: () => UseProfileProps | null): UseProf
// cacheTime is long so that the user see profiles instantly. // cacheTime is long so that the user see profiles instantly.
staleTime: 5 * 60 * 1000, // 5 min staleTime: 5 * 60 * 1000, // 5 min
cacheTime: 24 * 60 * 60 * 1000, // 1 day cacheTime: 24 * 60 * 60 * 1000, // 1 day
refetchInterval: 5 * 60 * 1000, // 5 min
}, },
); );
@@ -331,7 +332,7 @@ export const useTextNote = (propsProvider: () => UseTextNoteProps | null): UseTe
}, },
); );
const event = () => query.data; const event = () => query.data ?? null;
return { event, query }; return { event, query };
}; };
@@ -362,6 +363,7 @@ export const useReactions = (propsProvider: () => UseReactionsProps | null): Use
{ {
staleTime: 1 * 60 * 1000, // 1 min staleTime: 1 * 60 * 1000, // 1 min
cacheTime: 4 * 60 * 60 * 1000, // 4 hour cacheTime: 4 * 60 * 60 * 1000, // 4 hour
refetchInterval: 1 * 60 * 1000, // 1 min
}, },
); );
@@ -412,6 +414,7 @@ export const useDeprecatedReposts = (
{ {
staleTime: 1 * 60 * 1000, // 1 min staleTime: 1 * 60 * 1000, // 1 min
cacheTime: 4 * 60 * 60 * 1000, // 4 hour cacheTime: 4 * 60 * 60 * 1000, // 4 hour
refetchInterval: 1 * 60 * 1000, // 1 min
}, },
); );
@@ -458,7 +461,6 @@ export const useFollowings = (propsProvider: () => UseFollowingsProps | null): U
staleTime: 5 * 60 * 1000, // 5 min staleTime: 5 * 60 * 1000, // 5 min
cacheTime: 24 * 60 * 60 * 1000, // 24 hour cacheTime: 24 * 60 * 60 * 1000, // 24 hour
refetchOnWindowFocus: false, refetchOnWindowFocus: false,
refetchInterval: 5 * 60 * 1000, // 5 min
}, },
); );