mirror of
https://github.com/quartz-community/note-properties.git
synced 2026-07-22 02:50:26 +00:00
Wikilinks appearing in frontmatter property strings (e.g. 'related: "[[My Note]]"') were rendered with their raw target passed directly to resolveRelative, leaving the space intact. The browser then percent-encoded it as %20 in the rendered href, producing broken links like './My%20Note' that never resolve. The root cause was in renderTextWithLinks (NoteProperties.tsx): the matched wikilink target went straight into resolveRelative without going through slugifyFilePath, so spaces, ampersands, and other characters that should be slugified survived into the href. extractLinksFromValue (transformer.ts) had the same issue: it stored raw target strings in file.data.frontmatterLinks instead of canonical slugs. Adds a shared slugifyWikilinkTarget helper in util/path.ts that mirrors the slug+anchor contract CrawlLinks uses for body-content links, so frontmatter wikilinks and body wikilinks now produce identical hrefs. Both NoteProperties.tsx and the transformer's extractLinksFromValue now go through this helper. Also replaces the plugin's local slugTag/slugifyFilePath/ getFileExtension helpers with the shared @quartz-community/utils versions. This eliminates slugification drift between plugins and ensures that note-properties honors the same case-insensitive matching that the rest of the Quartz ecosystem uses. BREAKING CHANGE: tag URLs and frontmatter-wikilink slugs are now lowercased (inherited from utils' case-insensitive matching). Tags #MyTag, #mytag, and #MYTAG collapse into a single tag page tags/mytag.
74 lines
2.6 KiB
TypeScript
74 lines
2.6 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import { VFile } from "vfile";
|
|
import { NoteProperties } from "../src/transformer";
|
|
import type { BuildCtx } from "@quartz-community/types";
|
|
|
|
function runWithFrontmatter(frontmatter: string): string[] {
|
|
const plugin = NoteProperties({});
|
|
const ctx = { allSlugs: [] } as unknown as BuildCtx;
|
|
const plugins = plugin.markdownPlugins!(ctx);
|
|
const transformerFactory = plugins[1] as () => (tree: unknown, file: VFile) => void;
|
|
const transformer = transformerFactory();
|
|
|
|
const markdown = `---\n${frontmatter}\n---\ncontent`;
|
|
const file = new VFile({
|
|
value: new TextEncoder().encode(markdown),
|
|
path: "note.md",
|
|
});
|
|
file.data = {};
|
|
transformer(null, file);
|
|
|
|
return (file.data.frontmatterLinks as string[] | undefined) ?? [];
|
|
}
|
|
|
|
describe("frontmatterLinks extraction", () => {
|
|
it("slugifies wikilinks with spaces", () => {
|
|
const links = runWithFrontmatter('related: "[[My Note]]"');
|
|
expect(links).toEqual(["my-note"]);
|
|
});
|
|
|
|
it("never leaves spaces or %20 in the extracted wikilink", () => {
|
|
const links = runWithFrontmatter('related: "[[My Note]]"');
|
|
for (const link of links) {
|
|
expect(link).not.toContain(" ");
|
|
expect(link).not.toContain("%20");
|
|
}
|
|
});
|
|
|
|
it("preserves and slugifies anchors on wikilinks", () => {
|
|
const links = runWithFrontmatter('related: "[[My Note#Some Section]]"');
|
|
expect(links).toEqual(["my-note#some-section"]);
|
|
});
|
|
|
|
it("slugifies nested-path wikilinks per segment", () => {
|
|
const links = runWithFrontmatter('related: "[[Folder Name/My Note]]"');
|
|
expect(links).toEqual(["folder-name/my-note"]);
|
|
});
|
|
|
|
it("strips the .md extension on wikilinks that include it", () => {
|
|
const links = runWithFrontmatter('related: "[[My Note.md]]"');
|
|
expect(links).toEqual(["my-note"]);
|
|
});
|
|
|
|
it("extracts wikilinks from array values", () => {
|
|
const links = runWithFrontmatter(
|
|
"related:\n" + ' - "[[First Note]]"\n' + ' - "[[Second Note]]"',
|
|
);
|
|
expect(links).toEqual(["first-note", "second-note"]);
|
|
});
|
|
|
|
it("leaves markdown-link targets untouched (user-provided hrefs)", () => {
|
|
const links = runWithFrontmatter('link: "[label](./some/path)"');
|
|
expect(links).toEqual(["./some/path"]);
|
|
});
|
|
|
|
it("drops the alias portion and only stores the target", () => {
|
|
const links = runWithFrontmatter('related: "[[My Note|Display Text]]"');
|
|
expect(links).toEqual(["my-note"]);
|
|
});
|
|
|
|
it("does not set frontmatterLinks when frontmatter has no links", () => {
|
|
const links = runWithFrontmatter('title: "Plain Title"');
|
|
expect(links).toEqual([]);
|
|
});
|
|
});
|