feat: render note quotes
This commit is contained in:
parent
a6eefb1027
commit
13461cca80
|
@ -26,7 +26,7 @@ import NostrLink from "Element/NostrLink";
|
||||||
import RevealMedia from "Element/RevealMedia";
|
import RevealMedia from "Element/RevealMedia";
|
||||||
import MagnetLink from "Element/MagnetLink";
|
import MagnetLink from "Element/MagnetLink";
|
||||||
|
|
||||||
export default function HyperText({ link, creator }: { link: string; creator: string }) {
|
export default function HyperText({ link, creator, depth }: { link: string; creator: string; depth?: number }) {
|
||||||
const a = link;
|
const a = link;
|
||||||
try {
|
try {
|
||||||
const url = new URL(a);
|
const url = new URL(a);
|
||||||
|
@ -85,7 +85,7 @@ export default function HyperText({ link, creator }: { link: string; creator: st
|
||||||
} else if (isWavlakeLink) {
|
} else if (isWavlakeLink) {
|
||||||
return <WavlakeEmbed link={a} />;
|
return <WavlakeEmbed link={a} />;
|
||||||
} else if (url.protocol === "nostr:" || url.protocol === "web+nostr:") {
|
} else if (url.protocol === "nostr:" || url.protocol === "web+nostr:") {
|
||||||
return <NostrLink link={a} />;
|
return <NostrLink link={a} depth={depth} />;
|
||||||
} else if (url.protocol === "magnet:") {
|
} else if (url.protocol === "magnet:") {
|
||||||
const parsed = magnetURIDecode(a);
|
const parsed = magnetURIDecode(a);
|
||||||
if (parsed) {
|
if (parsed) {
|
||||||
|
|
|
@ -2,23 +2,23 @@ import useEventFeed from "Feed/EventFeed";
|
||||||
import { NostrLink } from "Util";
|
import { NostrLink } from "Util";
|
||||||
import HyperText from "Element/HyperText";
|
import HyperText from "Element/HyperText";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import Spinner from "Icons/Spinner";
|
import PageSpinner from "Element/PageSpinner";
|
||||||
|
|
||||||
export default function NostrFileHeader({ link }: { link: NostrLink }) {
|
export default function NostrFileHeader({ link }: { link: NostrLink }) {
|
||||||
const ev = useEventFeed(link);
|
const ev = useEventFeed(link);
|
||||||
|
|
||||||
if (!ev.data?.length) return <Spinner />;
|
if (!ev.data) return <PageSpinner />;
|
||||||
|
|
||||||
// assume image or embed which can be rendered by the hypertext kind
|
// assume image or embed which can be rendered by the hypertext kind
|
||||||
// todo: make use of hash
|
// todo: make use of hash
|
||||||
// todo: use magnet or other links if present
|
// todo: use magnet or other links if present
|
||||||
const u = ev.data?.[0]?.tags.find(a => a[0] === "u")?.[1] ?? "";
|
const u = ev.data?.tags.find(a => a[0] === "u")?.[1] ?? "";
|
||||||
if (u) {
|
if (u) {
|
||||||
return <HyperText link={u} creator={ev.data?.[0]?.pubkey ?? ""} />;
|
return <HyperText link={u} creator={ev.data?.pubkey ?? ""} />;
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<b className="error">
|
<b className="error">
|
||||||
<FormattedMessage defaultMessage="Unknown file header: {name}" values={{ name: ev.data?.[0]?.content }} />
|
<FormattedMessage defaultMessage="Unknown file header: {name}" values={{ name: ev.data?.content }} />
|
||||||
</b>
|
</b>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import { EventKind, NostrPrefix } from "@snort/nostr";
|
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
import { EventKind, NostrPrefix } from "@snort/nostr";
|
||||||
|
|
||||||
import Mention from "Element/Mention";
|
import Mention from "Element/Mention";
|
||||||
import NostrFileHeader from "Element/NostrFileHeader";
|
import NostrFileHeader from "Element/NostrFileHeader";
|
||||||
import { parseNostrLink } from "Util";
|
import { parseNostrLink } from "Util";
|
||||||
|
import NoteQuote from "Element/NoteQuote";
|
||||||
|
|
||||||
export default function NostrLink({ link }: { link: string }) {
|
export default function NostrLink({ link, depth }: { link: string; depth?: number }) {
|
||||||
const nav = parseNostrLink(link);
|
const nav = parseNostrLink(link);
|
||||||
|
|
||||||
if (nav?.type === NostrPrefix.PublicKey || nav?.type === NostrPrefix.Profile) {
|
if (nav?.type === NostrPrefix.PublicKey || nav?.type === NostrPrefix.Profile) {
|
||||||
|
@ -14,12 +15,16 @@ export default function NostrLink({ link }: { link: string }) {
|
||||||
if (nav.kind === EventKind.FileHeader) {
|
if (nav.kind === EventKind.FileHeader) {
|
||||||
return <NostrFileHeader link={nav} />;
|
return <NostrFileHeader link={nav} />;
|
||||||
}
|
}
|
||||||
const evLink = nav.encode();
|
if ((depth ?? 0) > 0) {
|
||||||
return (
|
const evLink = nav.encode();
|
||||||
<Link to={`/e/${evLink}`} onClick={e => e.stopPropagation()} state={{ from: location.pathname }}>
|
return (
|
||||||
#{evLink.substring(0, 12)}
|
<Link to={`/e/${evLink}`} onClick={e => e.stopPropagation()} state={{ from: location.pathname }}>
|
||||||
</Link>
|
#{evLink.substring(0, 12)}
|
||||||
);
|
</Link>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return <NoteQuote link={nav} depth={depth} />;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<a href={link} onClick={e => e.stopPropagation()} target="_blank" rel="noreferrer" className="ext">
|
<a href={link} onClick={e => e.stopPropagation()} target="_blank" rel="noreferrer" className="ext">
|
||||||
|
|
|
@ -57,6 +57,14 @@
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.note-quote {
|
||||||
|
border: 1px solid var(--gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-quote.note > .body {
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.note > .body {
|
.note > .body {
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
|
@ -98,9 +106,6 @@
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.light .note > .footer .ctx-menu {
|
|
||||||
}
|
|
||||||
|
|
||||||
.note > .footer .ctx-menu li {
|
.note > .footer .ctx-menu li {
|
||||||
background: #1e1e1e;
|
background: #1e1e1e;
|
||||||
padding-top: 8px;
|
padding-top: 8px;
|
||||||
|
|
|
@ -39,6 +39,7 @@ export interface NoteProps {
|
||||||
highlight?: boolean;
|
highlight?: boolean;
|
||||||
ignoreModeration?: boolean;
|
ignoreModeration?: boolean;
|
||||||
onClick?: (e: TaggedRawEvent) => void;
|
onClick?: (e: TaggedRawEvent) => void;
|
||||||
|
depth?: number;
|
||||||
options?: {
|
options?: {
|
||||||
showHeader?: boolean;
|
showHeader?: boolean;
|
||||||
showTime?: boolean;
|
showTime?: boolean;
|
||||||
|
@ -187,7 +188,7 @@ export default function Note(props: NoteProps) {
|
||||||
</Reveal>
|
</Reveal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return <Text content={body} tags={ev.tags} creator={ev.pubkey} />;
|
return <Text content={body} tags={ev.tags} creator={ev.pubkey} depth={props.depth} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
import useEventFeed from "Feed/EventFeed";
|
||||||
|
import { NostrLink } from "Util";
|
||||||
|
import Note from "Element/Note";
|
||||||
|
import PageSpinner from "Element/PageSpinner";
|
||||||
|
|
||||||
|
export default function NoteQuote({ link, depth }: { link: NostrLink; depth?: number }) {
|
||||||
|
const ev = useEventFeed(link);
|
||||||
|
if (!ev.data) return <PageSpinner />;
|
||||||
|
return (
|
||||||
|
<Note
|
||||||
|
data={ev.data}
|
||||||
|
related={[]}
|
||||||
|
className="note-quote"
|
||||||
|
depth={(depth ?? 0) + 1}
|
||||||
|
options={{
|
||||||
|
showFooter: false,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
|
@ -5,7 +5,7 @@
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text a {
|
.text > a {
|
||||||
color: var(--highlight);
|
color: var(--highlight);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,9 +25,10 @@ export interface TextProps {
|
||||||
creator: HexKey;
|
creator: HexKey;
|
||||||
tags: Array<Array<string>>;
|
tags: Array<Array<string>>;
|
||||||
disableMedia?: boolean;
|
disableMedia?: boolean;
|
||||||
|
depth?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Text({ content, tags, creator, disableMedia }: TextProps) {
|
export default function Text({ content, tags, creator, disableMedia, depth }: TextProps) {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
function extractLinks(fragments: Fragment[]) {
|
function extractLinks(fragments: Fragment[]) {
|
||||||
|
@ -43,7 +44,7 @@ export default function Text({ content, tags, creator, disableMedia }: TextProps
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return <HyperText link={a} creator={creator} />;
|
return <HyperText link={a} creator={creator} depth={depth} />;
|
||||||
}
|
}
|
||||||
return a;
|
return a;
|
||||||
});
|
});
|
||||||
|
|
|
@ -88,7 +88,9 @@ const Timeline = (props: TimelineProps) => {
|
||||||
if (eRef) {
|
if (eRef) {
|
||||||
return <NoteReaction data={e} key={e.id} root={findRelated(eRef)} />;
|
return <NoteReaction data={e} key={e.id} root={findRelated(eRef)} />;
|
||||||
}
|
}
|
||||||
return <Note key={e.id} data={e} related={relatedFeed(e.id)} ignoreModeration={props.ignoreModeration} />;
|
return (
|
||||||
|
<Note key={e.id} data={e} related={relatedFeed(e.id)} ignoreModeration={props.ignoreModeration} depth={0} />
|
||||||
|
);
|
||||||
}
|
}
|
||||||
case EventKind.ZapReceipt: {
|
case EventKind.ZapReceipt: {
|
||||||
const zap = parseZap(e);
|
const zap = parseZap(e);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
|
||||||
import useRequestBuilder from "Hooks/useRequestBuilder";
|
import useRequestBuilder from "Hooks/useRequestBuilder";
|
||||||
import { FlatNoteStore, RequestBuilder } from "System";
|
import { RequestBuilder, ReplaceableNoteStore } from "System";
|
||||||
import { NostrLink } from "Util";
|
import { NostrLink } from "Util";
|
||||||
|
|
||||||
export default function useEventFeed(link: NostrLink) {
|
export default function useEventFeed(link: NostrLink) {
|
||||||
|
@ -11,5 +11,5 @@ export default function useEventFeed(link: NostrLink) {
|
||||||
return b;
|
return b;
|
||||||
}, [link]);
|
}, [link]);
|
||||||
|
|
||||||
return useRequestBuilder<FlatNoteStore>(FlatNoteStore, sub);
|
return useRequestBuilder<ReplaceableNoteStore>(ReplaceableNoteStore, sub);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
NoteStore,
|
NoteStore,
|
||||||
PubkeyReplaceableNoteStore,
|
PubkeyReplaceableNoteStore,
|
||||||
ParameterizedReplaceableNoteStore,
|
ParameterizedReplaceableNoteStore,
|
||||||
|
ReplaceableNoteStore,
|
||||||
} from "./NoteCollection";
|
} from "./NoteCollection";
|
||||||
import { diffFilters } from "./RequestSplitter";
|
import { diffFilters } from "./RequestSplitter";
|
||||||
import { Query } from "./Query";
|
import { Query } from "./Query";
|
||||||
|
@ -18,6 +19,7 @@ export {
|
||||||
FlatNoteStore,
|
FlatNoteStore,
|
||||||
PubkeyReplaceableNoteStore,
|
PubkeyReplaceableNoteStore,
|
||||||
ParameterizedReplaceableNoteStore,
|
ParameterizedReplaceableNoteStore,
|
||||||
|
ReplaceableNoteStore,
|
||||||
Query,
|
Query,
|
||||||
EventBuilder,
|
EventBuilder,
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue