Give wikilinks a relative href in static/public HTML

The content-cache renderer (docToHtml) emitted wikilinks without an
href, so on the read-only public page view they rendered as styled but
unclickable text. Emit href="<slug>" — relative to the current page URL,
it resolves to the sibling page under both /public/<pond>/… and the
in-app /p/<pond>/… version-history preview, without the renderer needing
pond context. Slugs are [a-z0-9-], safe as a bare path segment.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1
This commit is contained in:
Claude Fable 5 2026-07-12 23:50:16 +02:00
parent 6b17fa289b
commit 79376bcdd1
2 changed files with 17 additions and 4 deletions

View File

@ -63,4 +63,13 @@ describe('docToHtml (issue #24)', () => {
expect(html).toContain('data-checked="true"'); expect(html).toContain('data-checked="true"');
expect(html).toContain('<input type="checkbox" disabled checked>'); expect(html).toContain('<input type="checkbox" disabled checked>');
}); });
it('gives wikilinks a relative href so public/static HTML is clickable', () => {
const html = docToHtml(markdownToDoc('See [[api-guide|the API guide]].'));
// Relative slug href resolves to the sibling page under both
// /public/<pond>/… and /p/<pond>/… without pond context in the renderer.
expect(html).toContain(
'<a class="wikilink" href="api-guide" data-wikilink="api-guide" data-display="the API guide">the API guide</a>',
);
});
}); });

View File

@ -57,14 +57,18 @@ function renderInline(node: Node): string {
const widthAttr = width ? ` width="${width}"` : ''; const widthAttr = width ? ` width="${width}"` : '';
out += `<img data-file-id="${escapeHtml(child.attrs.fileId as string)}" alt="${alt}"${widthAttr}>`; out += `<img data-file-id="${escapeHtml(child.attrs.fileId as string)}" alt="${alt}"${widthAttr}>`;
} else if (child.type.name === 'wikilink') { } else if (child.type.name === 'wikilink') {
// The slug and shown text; live title resolution and navigation happen // The slug and shown text; live title resolution happens in the editor
// where the pond's pages are known (issue #46) — this is the static // where the pond's pages are known (issue #46). The href is the target
// representation used for the content cache and version history. // slug *relative* to the current page URL, so this static HTML links
// correctly from both the public view (`/public/<pond>/<slug>`) and the
// in-app version-history preview (`/p/<pond>/<slug>`) without the
// renderer needing the pond context. Slugs are `[a-z0-9-]`, safe as a
// bare path segment.
const slug = escapeHtml(child.attrs.targetSlug as string); const slug = escapeHtml(child.attrs.targetSlug as string);
const display = child.attrs.displayText as string | null; const display = child.attrs.displayText as string | null;
const text = escapeHtml(display ?? (child.attrs.targetSlug as string)); const text = escapeHtml(display ?? (child.attrs.targetSlug as string));
const displayAttr = display ? ` data-display="${escapeHtml(display)}"` : ''; const displayAttr = display ? ` data-display="${escapeHtml(display)}"` : '';
out += `<a class="wikilink" data-wikilink="${slug}"${displayAttr}>${text}</a>`; out += `<a class="wikilink" href="${slug}" data-wikilink="${slug}"${displayAttr}>${text}</a>`;
} }
}); });
return out; return out;