diff --git a/README.md b/README.md index 6e607bd..c296cbf 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,11 @@ dark, desktop, and mobile support. Run `npm install`, `npm run build`, and `npm run check`. -For live preview, set `AERA_TEST_VAULT`, then run `npm run link:vault` and -`npm run dev`. Changes to `manifest.json` require an Obsidian restart, while -compiled CSS changes automatically reload in the linked vault. +For live preview, set `AERA_TEST_VAULT` to a dedicated test vault, then run +`npm run link:vault` and `npm run dev`. The linker refuses to replace unknown +notes named `Theme Playground.md` or `Embedded Note.md`. Changes to +`manifest.json` require an Obsidian restart, while +compiled CSS automatically reloads in the linked vault. ## Release assets diff --git a/fixtures/Embedded Note.md b/fixtures/Embedded Note.md index 49093c5..4fd6bd3 100644 --- a/fixtures/Embedded Note.md +++ b/fixtures/Embedded Note.md @@ -3,3 +3,5 @@ 这是一篇用于验证 `[[Embedded Note]]` 已解析内部链接与嵌入预览的中文笔记。 返回 [[Theme Playground]]。 + + diff --git a/fixtures/Theme Playground.md b/fixtures/Theme Playground.md index ba1f7ac..b01f6b1 100644 --- a/fixtures/Theme Playground.md +++ b/fixtures/Theme Playground.md @@ -66,3 +66,5 @@ published: 2026-07-16 演练场以脚注引用结束。[^aera] [^aera]: Aera 是用于检查 Obsidian 主题的演练 fixture。 + + diff --git a/scripts/link-vault.mjs b/scripts/link-vault.mjs index afb144a..084e33b 100644 --- a/scripts/link-vault.mjs +++ b/scripts/link-vault.mjs @@ -17,6 +17,8 @@ import { fileURLToPath } from "node:url"; const modulePath = fileURLToPath(import.meta.url); const defaultRepoPath = dirname(dirname(modulePath)); const fixtureNames = ["Theme Playground.md", "Embedded Note.md"]; +const fixtureOwnershipMarker = + ""; async function existingPathType(path) { try { @@ -55,6 +57,17 @@ async function assertSafeFixtureTarget(path) { throw new Error(`Refusing to replace fixture ${type} at ${path}`); } +async function assertManagedFixtureTarget(path) { + const existing = await existingPathType(path); + if (!existing) return; + + await assertSafeFixtureTarget(path); + const contents = await readFile(path, "utf8"); + if (!contents.trimEnd().endsWith(fixtureOwnershipMarker)) { + throw new Error(`Refusing to replace unknown fixture at ${path}`); + } +} + export async function copyFixtureSafely( sourcePath, destinationPath, @@ -140,7 +153,7 @@ export async function linkVault(vaultPath, repoPath = defaultRepoPath) { source: join(repo, "fixtures", name), })); for (const { destination } of fixtures) { - await assertSafeFixtureTarget(destination); + await assertManagedFixtureTarget(destination); } for (const { destination, source } of fixtures) { await copyFixtureSafely(source, destination); diff --git a/src/editor/_headings.scss b/src/editor/_headings.scss index a1b2c09..49ebda9 100644 --- a/src/editor/_headings.scss +++ b/src/editor/_headings.scss @@ -1,6 +1,6 @@ body { --inline-title-color: var(--text-normal); - --inline-title-font: var(--font-text-theme); + --inline-title-font: var(--font-text); --inline-title-line-height: 1.25; --inline-title-size: 2em; --inline-title-weight: 650; diff --git a/tests/editor-inline.test.mjs b/tests/editor-inline.test.mjs index 00f73c8..a74b3f4 100644 --- a/tests/editor-inline.test.mjs +++ b/tests/editor-inline.test.mjs @@ -16,7 +16,7 @@ function assertDeclarations(expected) { test("defines the inline title hierarchy", () => { assertDeclarations({ "--inline-title-color": "var(--text-normal)", - "--inline-title-font": "var(--font-text-theme)", + "--inline-title-font": "var(--font-text)", "--inline-title-line-height": "1.25", "--inline-title-size": "2em", "--inline-title-weight": "650", diff --git a/tests/link-vault.test.mjs b/tests/link-vault.test.mjs index 7d83808..1713356 100644 --- a/tests/link-vault.test.mjs +++ b/tests/link-vault.test.mjs @@ -28,6 +28,8 @@ import { const repoRoot = dirname(fileURLToPath(new URL("../package.json", import.meta.url))); const cliPath = join(repoRoot, "scripts/link-vault.mjs"); const fixtureNames = ["Theme Playground.md", "Embedded Note.md"]; +const fixtureOwnershipMarker = + ""; async function createVault(t) { const vault = await mkdtemp(join(tmpdir(), "aera-vault-")); @@ -71,6 +73,58 @@ test("linkVault links Aera and copies the playground fixtures", async (t) => { } }); +test("linkVault updates fixtures carrying the Aera ownership marker", async (t) => { + const vault = await createVault(t); + + for (const name of fixtureNames) { + await writeFile( + join(vault, name), + `stale fixture\n\n${fixtureOwnershipMarker}\n`, + ); + } + + await linkVault(vault, repoRoot); + + for (const name of fixtureNames) { + assert.equal( + await readFile(join(vault, name), "utf8"), + await readFile(join(repoRoot, "fixtures", name), "utf8"), + ); + } +}); + +for (const unknownFixture of fixtureNames) { + test(`linkVault rejects an unknown ${unknownFixture} before updating any fixture`, async (t) => { + const vault = await createVault(t); + const originalContents = new Map(); + + for (const name of fixtureNames) { + const contents = + name === unknownFixture + ? `personal ${name} content\n` + : `stale owned ${name}\n\n${fixtureOwnershipMarker}\n`; + originalContents.set(name, contents); + await writeFile(join(vault, name), contents); + } + + await assert.rejects( + linkVault(vault, repoRoot), + new RegExp(`Refusing to replace unknown fixture.*${unknownFixture}`, "i"), + ); + + for (const name of fixtureNames) { + assert.equal( + await readFile(join(vault, name), "utf8"), + originalContents.get(name), + ); + } + assert.equal( + (await readdir(vault)).some((name) => /aera.*tmp|\.tmp/i.test(name)), + false, + ); + }); +} + test("the playground fixture covers the supported Markdown elements", async () => { const playground = await readFile( join(repoRoot, "fixtures", "Theme Playground.md"), @@ -118,6 +172,7 @@ test("the playground fixture covers the supported Markdown elements", async () = assert.match(playground, /^!\[\[Embedded Note\]\]$/m); assert.match(playground, /^演练场以脚注引用结束。\[\^aera\]$/m); assert.match(playground, /^\[\^aera\]: Aera 是用于检查 Obsidian 主题的演练 fixture。$/m); + assert.equal(playground.endsWith(`${fixtureOwnershipMarker}\n`), true); const embedded = await readFile( join(repoRoot, "fixtures", "Embedded Note.md"), @@ -126,6 +181,7 @@ test("the playground fixture covers the supported Markdown elements", async () = assert.match(embedded, /^# 嵌入内容$/m); assert.match(embedded, /^这是一篇用于验证 `\[\[Embedded Note\]\]` 已解析内部链接与嵌入预览的中文笔记。$/m); assert.match(embedded, /^返回 \[\[Theme Playground\]\]。$/m); + assert.equal(embedded.endsWith(`${fixtureOwnershipMarker}\n`), true); }); for (const fixtureName of fixtureNames) { diff --git a/theme.css b/theme.css index f45cab6..cd1d95b 100644 --- a/theme.css +++ b/theme.css @@ -72,7 +72,7 @@ body { body { --inline-title-color: var(--text-normal); - --inline-title-font: var(--font-text-theme); + --inline-title-font: var(--font-text); --inline-title-line-height: 1.25; --inline-title-size: 2em; --inline-title-weight: 650;