Cleanup embed content

This commit is contained in:
hzrd149 2023-07-16 15:32:19 -05:00
parent 7cc9c9a432
commit 52d567c59b
25 changed files with 61 additions and 158 deletions

View File

@ -0,0 +1,5 @@
---
"nostrudel": minor
---
Cleanup embed content (hopefully performance improvement)

View File

@ -0,0 +1,5 @@
---
"nostrudel": minor
---
Remove twitter tweet embeds

View File

@ -30,7 +30,5 @@
<body>
<div id="root"></div>
<script type="module" src="./src/index.tsx"></script>
<script async src="/lib/twitter-widgets.js" charset="utf-8"></script>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,5 @@
import React, { Suspense } from "react";
import { createHashRouter, Outlet, RouterProvider, ScrollRestoration } from "react-router-dom";
import { createHashRouter, Outlet, RouterProvider } from "react-router-dom";
import { Spinner } from "@chakra-ui/react";
import { ErrorBoundary } from "./components/error-boundary";
import Layout from "./components/layout";
@ -46,7 +46,6 @@ const RootPage = () => {
return (
<PageProviders>
<Layout>
<ScrollRestoration />
<Suspense fallback={<Spinner />}>
<Outlet />
</Suspense>

View File

@ -54,7 +54,14 @@ const videoExt = [".mp4", ".mkv", ".webm", ".mov"];
export function renderVideoUrl(match: URL) {
if (!videoExt.some((ext) => match.pathname.endsWith(ext))) return null;
return <video src={match.toString()} controls style={{ maxWidth: "30rem", maxHeight: "20rem", width: "100%" }} />;
return (
<video
key={match.href}
src={match.toString()}
controls
style={{ maxWidth: "30rem", maxHeight: "20rem", width: "100%" }}
/>
);
}
export function renderGenericUrl(match: URL) {

View File

@ -20,17 +20,13 @@ export function embedNostrLinks(content: EmbedableContent) {
switch (decoded.type) {
case "npub":
return <UserLink color="blue.500" pubkey={decoded.data as string} showAt />;
case "nprofile": {
const pointer = decoded.data as ProfilePointer;
return <UserLink color="blue.500" pubkey={pointer.pubkey} showAt />;
}
return <UserLink color="blue.500" pubkey={decoded.data} showAt />;
case "nprofile":
return <UserLink color="blue.500" pubkey={decoded.data.pubkey} showAt />;
case "note":
return <QuoteNote noteId={decoded.data as string} />;
case "nevent": {
const pointer = decoded.data as EventPointer;
return <QuoteNote noteId={pointer.id} relay={pointer.relays?.[0]} />;
}
return <QuoteNote noteId={decoded.data} />;
case "nevent":
return <QuoteNote noteId={decoded.data.id} relays={decoded.data.relays} />;
default:
return null;
}
@ -54,7 +50,7 @@ export function embedNostrMentions(content: EmbedableContent, event: NostrEvent
return <UserLink color="blue.500" pubkey={tag[1]} showAt />;
}
if (tag[0] === "e" && tag[1]) {
return <QuoteNote noteId={tag[1]} relay={tag[2]} />;
return <QuoteNote noteId={tag[1]} relays={tag[2] ? [tag[2]] : undefined} />;
}
}

View File

@ -1,6 +1,5 @@
import { replaceDomain } from "../../helpers/url";
import appSettings from "../../services/app-settings";
import { TweetEmbed } from "../tweet-embed";
import { renderOpenGraphUrl } from "./common";
// copied from https://github.com/SimonBrazell/privacy-redirect/blob/master/src/assets/javascripts/helpers/twitter.js
@ -11,5 +10,5 @@ export function renderTwitterUrl(match: URL) {
const { twitterRedirect } = appSettings.value;
if (twitterRedirect) return renderOpenGraphUrl(replaceDomain(match, twitterRedirect));
else return <TweetEmbed href={match.toString()} conversation={false} />;
else return renderOpenGraphUrl(match);
}

View File

@ -1,5 +1,7 @@
import { AspectRatio, list } from "@chakra-ui/react";
import appSettings from "../../services/app-settings";
import { renderOpenGraphUrl } from "./common";
import { replaceDomain } from "../../helpers/url";
// copied from https://github.com/SimonBrazell/privacy-redirect/blob/master/src/assets/javascripts/helpers/youtube.js
export const YOUTUBE_DOMAINS = [
@ -21,6 +23,9 @@ export function renderYoutubeUrl(match: URL) {
const { youtubeRedirect } = appSettings.value;
// render opengraph card for performance
// return renderOpenGraphUrl(youtubeRedirect ? replaceDomain(match, youtubeRedirect) : match);
if (match.pathname.startsWith("/playlist")) {
const listId = match.searchParams.get("list");
if (!listId) throw new Error("missing list id");
@ -34,8 +39,7 @@ export function renderYoutubeUrl(match: URL) {
src={embedUrl.toString()}
title="YouTube video player"
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowFullScreen
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen"
width="100%"
></iframe>
</AspectRatio>
@ -52,8 +56,7 @@ export function renderYoutubeUrl(match: URL) {
src={embedUrl.toString()}
title="YouTube video player"
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; fullscreen"
width="100%"
></iframe>
</AspectRatio>

View File

@ -1,18 +0,0 @@
import { EmbedableContent } from "../helpers/embeds";
import { Text } from "@chakra-ui/react";
export default function EmbeddedContent({ content }: { content: EmbedableContent }) {
return (
<>
{content.map((part, i) =>
typeof part === "string" ? (
<Text as="span" key={"part-" + i}>
{part}
</Text>
) : (
part
)
)}
</>
);
}

View File

@ -38,10 +38,9 @@ import { useRegisterIntersectionEntity } from "../../providers/intersection-obse
export type NoteProps = {
event: NostrEvent;
maxHeight?: number;
variant?: CardProps["variant"];
};
export const Note = React.memo(({ event, maxHeight, variant = "outline" }: NoteProps) => {
export const Note = React.memo(({ event, variant = "outline" }: NoteProps) => {
const isMobile = useIsMobile();
const { showReactions, showSignatureVerification } = useSubject(appSettings);
@ -69,7 +68,7 @@ export const Note = React.memo(({ event, maxHeight, variant = "outline" }: NoteP
</Flex>
</CardHeader>
<CardBody p="0">
<NoteContentWithWarning event={event} maxHeight={maxHeight} />
<NoteContentWithWarning event={event} />
</CardBody>
<CardFooter padding="2" display="flex" gap="2">
<ButtonGroup size="sm" variant="link">

View File

@ -5,16 +5,12 @@ import { useExpand } from "./expanded";
import SensitiveContentWarning from "../sensitive-content-warning";
import useAppSettings from "../../hooks/use-app-settings";
export default function NoteContentWithWarning({ event, maxHeight }: { event: NostrEvent; maxHeight?: number }) {
export default function NoteContentWithWarning({ event }: { event: NostrEvent }) {
const expand = useExpand();
const settings = useAppSettings();
const contentWarning = event.tags.find((t) => t[0] === "content-warning")?.[1];
const showContentWarning = settings.showContentWarning && contentWarning && !expand?.expanded;
return showContentWarning ? (
<SensitiveContentWarning description={contentWarning} />
) : (
<NoteContents event={event} maxHeight={maxHeight} />
);
return showContentWarning ? <SensitiveContentWarning description={contentWarning} /> : <NoteContents event={event} />;
}

View File

@ -1,8 +1,6 @@
import React, { PropsWithChildren, useCallback, useEffect, useRef, useState } from "react";
import React from "react";
import { Box } from "@chakra-ui/react";
import { DraftNostrEvent, NostrEvent } from "../../types/nostr-event";
import { css } from "@emotion/react";
import { useExpand } from "./expanded";
import { EmbedableContent, embedUrls } from "../../helpers/embeds";
import {
embedLightningInvoice,
@ -22,7 +20,6 @@ import {
} from "../embed-types";
import { ImageGalleryProvider } from "../image-gallery";
import { renderRedditUrl } from "../embed-types/reddit";
import EmbeddedContent from "../embeded-content";
function buildContents(event: NostrEvent | DraftNostrEvent) {
let content: EmbedableContent = [event.content.trim()];
@ -53,54 +50,17 @@ function buildContents(event: NostrEvent | DraftNostrEvent) {
return content;
}
const gradientOverlayStyles = css`
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
background: linear-gradient(180deg, rgb(255 255 255 / 0%) 0%, var(--chakra-colors-chakra-body-bg) 100%);
cursor: pointer;
`;
export type NoteContentsProps = {
event: NostrEvent | DraftNostrEvent;
maxHeight?: number;
};
export const NoteContents = React.memo(({ event, maxHeight }: NoteContentsProps) => {
export const NoteContents = React.memo(({ event }: NoteContentsProps) => {
const content = buildContents(event);
const expand = useExpand();
const [innerHeight, setInnerHeight] = useState(0);
const ref = useRef<HTMLDivElement | null>(null);
const testHeight = useCallback(() => {
if (ref.current && maxHeight) {
const rect = ref.current.getClientRects()[0];
setInnerHeight(rect.height);
}
}, [maxHeight]);
useEffect(() => {
testHeight();
}, [testHeight]);
const showOverlay = !!maxHeight && !expand?.expanded && innerHeight > maxHeight;
return (
<ImageGalleryProvider>
<Box
whiteSpace="pre-wrap"
maxHeight={!expand?.expanded ? maxHeight : undefined}
position="relative"
overflow={maxHeight && !expand?.expanded ? "hidden" : "initial"}
onLoad={() => testHeight()}
px="2"
>
<div ref={ref}>
<EmbeddedContent content={content} />
</div>
{showOverlay && <Box css={gradientOverlayStyles} onClick={expand?.onExpand} />}
<Box whiteSpace="pre-wrap" px="2">
{content}
</Box>
</ImageGalleryProvider>
);

View File

@ -3,9 +3,9 @@ import useSingleEvent from "../../hooks/use-single-event";
import EmbeddedNote from "./embedded-note";
import { NoteLink } from "../note-link";
const QuoteNote = ({ noteId, relay }: { noteId: string; relay?: string }) => {
const relays = useReadRelayUrls(relay ? [relay] : []);
const { event, loading } = useSingleEvent(noteId, relays);
const QuoteNote = ({ noteId, relays }: { noteId: string; relays?: string[] }) => {
const readRelays = useReadRelayUrls(relays);
const { event, loading } = useSingleEvent(noteId, readRelays);
return event ? <EmbeddedNote note={event} /> : <NoteLink noteId={noteId} />;
};

View File

@ -14,7 +14,9 @@ export default function OpenGraphCard({ url, ...props }: { url: URL } & Omit<Car
return (
<LinkBox borderRadius="lg" borderWidth={1} overflow="hidden" {...props}>
{data.ogImage?.length === 1 && <Image key={data.ogImage[0].url} src={data.ogImage[0].url} mx="auto" />}
{data.ogImage?.length === 1 && (
<Image key={data.ogImage[0].url} src={new URL(data.ogImage[0].url, url).toString()} mx="auto" maxH="3in" />
)}
<Box m="2" mt="4">
<Heading size="sm" my="2">

View File

@ -12,9 +12,9 @@ import StreamNote from "./stream-note";
const RenderEvent = React.memo(({ event }: { event: NostrEvent }) => {
switch (event.kind) {
case Kind.Text:
return <Note event={event} maxHeight={1200} />;
return <Note event={event} />;
case Kind.Repost:
return <RepostNote event={event} maxHeight={1200} />;
return <RepostNote event={event} />;
case STREAM_KIND:
return <StreamNote event={event} />;
default:

View File

@ -23,7 +23,7 @@ function parseHardcodedNoteContent(event: NostrEvent) {
return (json as NostrEvent) ?? null;
}
export default function RepostNote({ event, maxHeight }: { event: NostrEvent; maxHeight?: number }) {
export default function RepostNote({ event }: { event: NostrEvent }) {
const ref = useRef<HTMLDivElement | null>(null);
useRegisterIntersectionEntity(ref, event.id);
@ -59,13 +59,7 @@ export default function RepostNote({ event, maxHeight }: { event: NostrEvent; ma
</Text>
<NoteMenu event={event} size="sm" variant="link" aria-label="note options" />
</Flex>
{loading ? (
<SkeletonText />
) : note ? (
<Note event={note} maxHeight={maxHeight} />
) : (
<ErrorFallback error={error} />
)}
{loading ? <SkeletonText /> : note ? <Note event={note} /> : <ErrorFallback error={error} />}
</Flex>
</TrustProvider>
);

View File

@ -1,30 +0,0 @@
import { useColorMode } from "@chakra-ui/react";
import { useEffect, useRef, useState } from "react";
export type TweetEmbedProps = {
href: string;
conversation?: boolean;
};
export const TweetEmbed = ({ href, conversation }: TweetEmbedProps) => {
const ref = useRef<HTMLQuoteElement | null>(null);
const { colorMode } = useColorMode();
useEffect(() => {
if (ref.current) {
// @ts-ignore
window.twttr?.widgets.load();
}
}, []);
return (
<blockquote
className="twitter-tweet"
ref={ref}
data-conversation={conversation ? undefined : "none"}
data-theme={colorMode}
>
<a href={href}></a>
</blockquote>
);
};

View File

@ -13,7 +13,7 @@ export const UserDnsIdentityIcon = ({ pubkey, onlyIcon }: { pubkey: string; only
const renderIcon = () => {
if (loading) {
return <Spinner size="xs" ml="1" />;
return <Spinner size="xs" ml="1" title={metadata.nip05} />;
} else if (error) {
return <VerificationFailed color="yellow.500" />;
} else if (!identity) {

View File

@ -31,7 +31,7 @@ export function embedJSX(content: EmbedableContent, embed: EmbedType): Embedable
if (render === null) return subContent;
if (typeof render !== "string" && !render.props.key) {
render = cloneElement(render, { key: embed.name + i });
render = cloneElement(render, { key: match[0] });
}
const newContent: EmbedableContent = [];

View File

@ -8,9 +8,9 @@ class SingleEventService {
pendingPromises = new Map<string, Deferred<NostrEvent>>();
async requestEvent(id: string, relays: string[]) {
if (this.eventCache.has(id)) {
return this.eventCache.get(id);
}
const event = this.eventCache.get(id);
if (event) return event;
this.pending.set(id, this.pending.get(id)?.concat(relays) ?? relays);
const deferred = createDefer<NostrEvent>();
this.pendingPromises.set(id, deferred);

View File

@ -51,7 +51,7 @@ export default function NoteView() {
pageContent = (
<>
{parentPosts.map((parent) => (
<Note key={parent.event.id + "-rely"} event={parent.event} maxHeight={200} />
<Note key={parent.event.id + "-rely"} event={parent.event} />
))}
<ThreadPost key={post.event.id} post={post} initShowReplies focusId={focusId} />
</>

View File

@ -10,7 +10,6 @@ import {
renderImageUrl,
} from "../../../components/embed-types";
import { Box, BoxProps } from "@chakra-ui/react";
import EmbeddedContent from "../../../components/embeded-content";
export default function StreamSummaryContent({ stream, ...props }: BoxProps & { stream: ParsedStream }) {
const content = useMemo(() => {
@ -32,7 +31,7 @@ export default function StreamSummaryContent({ stream, ...props }: BoxProps & {
return (
content && (
<Box whiteSpace="pre-wrap" {...props}>
<EmbeddedContent content={content} />
{content}
</Box>
)
);

View File

@ -8,7 +8,6 @@ import {
renderGenericUrl,
renderImageUrl,
} from "../../../../components/embed-types";
import EmbeddedContent from "../../../../components/embeded-content";
import { NostrEvent } from "../../../../types/nostr-event";
const ChatMessageContent = React.memo(({ event }: { event: NostrEvent }) => {
@ -26,7 +25,7 @@ const ChatMessageContent = React.memo(({ event }: { event: NostrEvent }) => {
return c;
}, [event.content]);
return <EmbeddedContent content={content} />;
return <>{content}</>;
});
export default ChatMessageContent;

View File

@ -43,7 +43,7 @@ const Like = ({ event }: { event: NostrEvent }) => {
<Spacer />
<NoteMenu event={event} aria-label="Note menu" variant="ghost" size="xs" />
</Flex>
<Note key={note.id} event={note} maxHeight={1200} />
<Note key={note.id} event={note} />
</>
);
} else content = <>Unknown note type {note.kind}</>;