mirror of
https://github.com/aljazceru/rabbit.git
synced 2025-12-18 06:24:25 +01:00
update
This commit is contained in:
@@ -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">
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user