diff --git a/src/server/routes/note.ts b/src/server/routes/note.ts --- a/src/server/routes/note.ts +++ b/src/server/routes/note.ts @@ -280,5 +280,6 @@ noteSlug, noteTitle: data.note.title, content: data.current.content, sidebarNotes, + canonicalRecord: data.note, }); }); diff --git a/src/server/routes/render-note.ts b/src/server/routes/render-note.ts --- a/src/server/routes/render-note.ts +++ b/src/server/routes/render-note.ts @@ -7,7 +7,11 @@ import { noteUrl, resolvePublicUrl } from "../../lib/urls.ts"; import { resolveBookmarkHtml } from "../../views/bookmark.ts"; import { notePage } from "../../views/note.ts"; import { shareOnBlueskyButton } from "../../views/share.ts"; -import { wikiIdentity, wikiLayoutOptions } from "../../views/view-options.ts"; +import { + atCanonicalOption, + wikiIdentity, + wikiLayoutOptions, +} from "../../views/view-options.ts"; interface NoteRenderInput { ctx: WikiRequestContext; @@ -17,6 +21,8 @@ noteTitle: string; content: string; sidebarNotes: { slug: string; title: string }[]; ogTitle?: string; + // The record this URL is the page for — the note, or the wiki on its home page. + canonicalRecord: { at_uri: string; did: string }; } export function renderNoteResponse(input: NoteRenderInput): Response { @@ -56,6 +62,7 @@ ogTitle: input.ogTitle ?? `${noteTitle} - ${ctx.wiki.name}`, ogUrl: canonicalUrl, ogImage, ...ogDescription, + ...atCanonicalOption(ctx.wiki, input.canonicalRecord), }, ctx.wiki.language, ), diff --git a/src/server/routes/wiki.ts b/src/server/routes/wiki.ts --- a/src/server/routes/wiki.ts +++ b/src/server/routes/wiki.ts @@ -37,6 +37,7 @@ import { editSidebarPage } from "../../views/edit-sidebar.ts"; import { newWikiPage } from "../../views/new-wiki.ts"; import { settingsPage } from "../../views/settings.ts"; import { + atCanonicalOption, baseLayoutOptions, wikiIdentity, wikiLayoutOptions, @@ -402,6 +403,8 @@ noteTitle: homeData.note.title, content: homeData.current.content, sidebarNotes, ogTitle: ctx.wiki.name, + // This URL is the wiki's page; the home note has its own. + canonicalRecord: ctx.wiki, }); } @@ -418,6 +421,7 @@ ogTitle: ctx.wiki.name, ogUrl: `${publicUrl}${wikiUrl(handle, ctx.wiki.slug)}`, ogImage, ...ogDescription, + ...atCanonicalOption(ctx.wiki, ctx.wiki), }, ctx.wiki.language, ), diff --git a/src/views/layout.ts b/src/views/layout.ts --- a/src/views/layout.ts +++ b/src/views/layout.ts @@ -70,6 +70,8 @@ ogTitle?: string; ogUrl?: string; ogDescription?: string; ogImage?: string; + // AT Tags: the record this URL is the page for. Set only for public wikis. + atCanonical?: { uri: string; authorDid: string }; wikiThemeMode?: WikiThemeMode; wikiTheme?: ThemeName; enableSearchShortcut?: boolean; @@ -106,6 +108,17 @@ `; +} + +// https://tangled.org/chrisshank.com/at-tags/ — the inverse of /at/:did/:collection/:rkey: +// tells any atmosphere tool which record this page renders. +function renderAtTags(options: LayoutOptions | undefined): string { + const record = options?.atCanonical; + if (!record) return ""; + const uri = escapeHtml(record.uri); + return ` + + `; } function renderSearchButton(locale: Locale = "en"): string { @@ -328,6 +341,7 @@ ${options?.noindex ? `` : ""} ${options?.csrfToken ? `` : ""} ${renderOpenGraph(options, title)} + ${renderAtTags(options)}