feat(desktop): enrich link previews with page metadata

Co-authored-by: npub14vtk7pvazqrq9639qu7e560wnqtl0d53ca4gjuvq6jzf3k2el23qqlwa7f <ab176f059d100602ea25073d9a69ee9817f7b691c76a897180d48498d959faa2@buzz.block.builderlab.xyz>
Signed-off-by: npub14vtk7pvazqrq9639qu7e560wnqtl0d53ca4gjuvq6jzf3k2el23qqlwa7f <ab176f059d100602ea25073d9a69ee9817f7b691c76a897180d48498d959faa2@buzz.block.builderlab.xyz>
This commit is contained in:
npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w
2026-07-30 23:20:57 -07:00
co-authored by npub14vtk7pvazqrq9639qu7e560wnqtl0d53ca4gjuvq6jzf3k2el23qqlwa7f
parent d88313f369
commit cb6fdffbb7
7 changed files with 302 additions and 203 deletions
+38 -2
View File
@@ -198,7 +198,7 @@ test("extractSupportedLinkPreviews skips markdown image link URLs", () => {
);
});
test("extractSupportedLinkPreviews requires bare URL boundaries", () => {
test("extractSupportedLinkPreviews treats other absolute HTTPS URLs as generic", () => {
assert.deepEqual(
extractSupportedLinkPreviews(
[
@@ -207,7 +207,7 @@ test("extractSupportedLinkPreviews requires bare URL boundaries", () => {
"(https://github.com/block/sprout/pull/2)",
].join(" "),
).map((preview) => preview.title),
["block/sprout #2"],
["evil-github.com", "example.com", "block/sprout #2"],
);
});
@@ -252,3 +252,39 @@ test("isSupportedLinkAutolinkLabel matches normalized bare URL labels", () => {
);
assert.equal(isSupportedLinkAutolinkLabel("review this", preview), false);
});
test("parseSupportedLinkPreview parses generic HTTPS URLs", () => {
assert.deepEqual(
parseSupportedLinkPreview("https://example.com/articles/rich-previews"),
{
kind: "generic-link",
href: "https://example.com/articles/rich-previews",
provider: "example.com",
title: "example.com",
typeLabel: "link",
},
);
});
test("parseSupportedLinkPreview rejects generic HTTP URLs", () => {
assert.equal(
parseSupportedLinkPreview("http://example.com/articles/rich-previews"),
null,
);
});
test("extractSupportedLinkPreviews finds generic links and preserves exclusions", () => {
assert.deepEqual(
extractSupportedLinkPreviews(
[
"Read https://example.com/article first.",
"`https://hidden.example.com/secret`",
"then [the details](https://docs.example.org/details)",
].join(" "),
).map(({ kind, title }) => ({ kind, title })),
[
{ kind: "generic-link", title: "example.com" },
{ kind: "generic-link", title: "the details" },
],
);
});
+25 -14
View File
@@ -7,18 +7,13 @@ export type SupportedLinkPreviewKind =
| "google-drive-folder"
| "google-docs-document"
| "google-sheets-spreadsheet"
| "google-slides-presentation";
| "google-slides-presentation"
| "generic-link";
export type SupportedLinkPreview = {
kind: SupportedLinkPreviewKind;
href: string;
provider:
| "GitHub"
| "Linear"
| "Google Drive"
| "Google Docs"
| "Google Sheets"
| "Google Slides";
provider: string;
title: string;
typeLabel:
| "PR"
@@ -28,13 +23,14 @@ export type SupportedLinkPreview = {
| "folder"
| "document"
| "spreadsheet"
| "presentation";
| "presentation"
| "link";
};
const SUPPORTED_URL_RE =
/(^|[\s([{<>"'])((?:https?:\/\/)?(?:(?:www\.)?github\.com|(?:www\.)?linear\.app|drive\.google\.com|docs\.google\.com)\/[^\s<>"'\]]+)/gi;
/(^|[\s([{<>"'])(https:\/\/[^\s<>"'\]]+|(?:(?:www\.)?github\.com|(?:www\.)?linear\.app|drive\.google\.com|docs\.google\.com)\/[^\s<>"'\]]+)/gi;
const MARKDOWN_SUPPORTED_LINK_RE =
/!?\[([^\]\n]+)\]\(((?:https?:\/\/)?(?:(?:www\.)?github\.com|(?:www\.)?linear\.app|drive\.google\.com|docs\.google\.com)\/[^)\s<>"']+)\)/gi;
/!?\[([^\]\n]+)\]\((https:\/\/[^)\s<>"']+|(?:(?:www\.)?github\.com|(?:www\.)?linear\.app|drive\.google\.com|docs\.google\.com)\/[^)\s<>"']+)\)/gi;
const MAX_PREVIEWS = 8;
type HiddenRange = {
@@ -431,12 +427,27 @@ export function parseSupportedLinkPreview(
return null;
}
return (
const recognized =
parseGithubLink(parsed) ??
parseLinearIssue(parsed) ??
parseGoogleDriveLink(parsed) ??
parseGoogleDocsLink(parsed)
);
parseGoogleDocsLink(parsed);
if (recognized) return recognized;
const hostname = normalizeHostname(parsed);
if (
parsed.protocol !== "https:" ||
[
"github.com",
"linear.app",
"drive.google.com",
"docs.google.com",
].includes(hostname)
) {
return null;
}
const provider = hostname;
return createPreview("generic-link", parsed, provider, "link", provider);
}
export function isSupportedLinkAutolinkLabel(
@@ -4,61 +4,64 @@ import { invokeTauri } from "@/shared/api/tauri";
import type { SupportedLinkPreview } from "./linkPreview";
const GOOGLE_FALLBACK_TITLES = new Set([
"Drive file",
"Drive folder",
"Document",
"Spreadsheet",
"Presentation",
]);
type LinkPreviewMetadata = {
title: string;
siteName: string | null;
};
const titleCache = new Map<string, Promise<string | null> | string | null>();
const metadataCache = new Map<
string,
Promise<LinkPreviewMetadata | null> | LinkPreviewMetadata | null
>();
function fetchLinkPreviewTitle(href: string): Promise<string | null> {
return invokeTauri<string | null>("fetch_link_preview_title", { href });
/** Clear ephemeral metadata when the active relay/community changes. */
export function resetLinkPreviewMetadataCache(): void {
metadataCache.clear();
}
function shouldResolveTitle(preview: SupportedLinkPreview): boolean {
return (
preview.kind.startsWith("google-") &&
GOOGLE_FALLBACK_TITLES.has(preview.title)
function fetchLinkPreviewMetadata(
href: string,
): Promise<LinkPreviewMetadata | null> {
return invokeTauri<LinkPreviewMetadata | null>(
"fetch_link_preview_metadata",
{
href,
},
);
}
function cacheTitle(href: string): Promise<string | null> {
const cached = titleCache.get(href);
function cacheMetadata(href: string): Promise<LinkPreviewMetadata | null> {
const cached = metadataCache.get(href);
if (cached instanceof Promise) return cached;
if (cached !== undefined) return Promise.resolve(cached);
const promise = fetchLinkPreviewTitle(href)
.then((title) => {
titleCache.set(href, title);
return title;
const promise = fetchLinkPreviewMetadata(href)
.then((metadata) => {
metadataCache.set(href, metadata);
return metadata;
})
.catch(() => {
titleCache.set(href, null);
metadataCache.set(href, null);
return null;
});
titleCache.set(href, promise);
metadataCache.set(href, promise);
return promise;
}
export function useResolvedLinkPreviews(
previews: SupportedLinkPreview[],
): SupportedLinkPreview[] {
const [resolvedTitles, setResolvedTitles] = React.useState<
Record<string, string>
const [resolvedMetadata, setResolvedMetadata] = React.useState<
Record<string, LinkPreviewMetadata>
>({});
React.useEffect(() => {
let cancelled = false;
const pending = previews.filter(shouldResolveTitle);
if (pending.length === 0) return undefined;
for (const preview of pending) {
const cached = titleCache.get(preview.href);
if (typeof cached === "string" && cached) {
setResolvedTitles((current) =>
for (const preview of previews) {
const cached = metadataCache.get(preview.href);
if (cached && !(cached instanceof Promise)) {
setResolvedMetadata((current) =>
current[preview.href] === cached
? current
: { ...current, [preview.href]: cached },
@@ -66,12 +69,12 @@ export function useResolvedLinkPreviews(
continue;
}
void cacheTitle(preview.href).then((title) => {
if (cancelled || !title) return;
setResolvedTitles((current) =>
current[preview.href] === title
void cacheMetadata(preview.href).then((metadata) => {
if (cancelled || !metadata) return;
setResolvedMetadata((current) =>
current[preview.href] === metadata
? current
: { ...current, [preview.href]: title },
: { ...current, [preview.href]: metadata },
);
});
}
@@ -84,9 +87,17 @@ export function useResolvedLinkPreviews(
return React.useMemo(
() =>
previews.map((preview) => {
const title = resolvedTitles[preview.href];
return title ? { ...preview, title } : preview;
const metadata = resolvedMetadata[preview.href];
if (!metadata) return preview;
return {
...preview,
title: metadata.title,
provider:
preview.kind === "generic-link" && metadata.siteName
? metadata.siteName
: preview.provider,
};
}),
[previews, resolvedTitles],
[previews, resolvedMetadata],
);
}
@@ -106,6 +106,8 @@ function LinkPreviewLogo({ preview }: { preview: SupportedLinkPreview }) {
return <GoogleSheetsLogo className="h-4 w-4" />;
case "google-slides-presentation":
return <GoogleSlidesLogo className="h-4 w-4" />;
case "generic-link":
return <ExternalLink aria-hidden="true" className="h-4 w-4" />;
}
}