From 513e03e9f06936586999b742da9d3c00c8c57ece Mon Sep 17 00:00:00 2001 From: HiDeoo <494699+HiDeoo@users.noreply.github.com> Date: Sat, 9 Mar 2024 19:24:03 +0100 Subject: [PATCH] feat: adds new `copyStarlightFrontmatter` option, defaulting to `false`, to copy known Starlight frontmatter fields from an Obsidian note to the associated generated page. --- docs/src/content/docs/configuration.md | 10 ++++++ docs/src/content/docs/guides/features.md | 3 +- fixtures/basics/.obsidian/types.json | 10 ++++++ fixtures/basics/Starlight properties.md | 9 +++++ fixtures/basics/Unsupported properties.md | 1 + packages/starlight-obsidian/index.ts | 11 +++++++ packages/starlight-obsidian/libs/obsidian.ts | 7 ++-- packages/starlight-obsidian/libs/remark.ts | 28 ++++++++++------ packages/starlight-obsidian/libs/starlight.ts | 33 ++++++++++++++++++- .../tests/properties.test.ts | 33 +++++++++++++++++-- packages/starlight-obsidian/tests/utils.ts | 5 +++ 11 files changed, 134 insertions(+), 16 deletions(-) create mode 100644 fixtures/basics/.obsidian/types.json create mode 100644 fixtures/basics/Starlight properties.md diff --git a/docs/src/content/docs/configuration.md b/docs/src/content/docs/configuration.md index a8ccab3..8071ddc 100644 --- a/docs/src/content/docs/configuration.md +++ b/docs/src/content/docs/configuration.md @@ -87,6 +87,16 @@ Add links to Starlight headings to make it easier to share a link to a specific By default, Starlight will include an “Overview” heading at the top of each page’s table of contents. If your Obsidian vault pages already include a top-level heading named “Overview”, you can set this option to `'title'` to instead use the page title as the top-level heading in the table of contents. +### `copyStarlightFrontmatter` + +**Type:** `boolean` +**Default:** `false` + +By default, all unsupported [properties](/guides/features/#properties) are ignored and not exported. Set this option to `true` to copy all known [Starlight frontmatter fields](https://starlight.astro.build/reference/frontmatter/) from an Obsidian note to the associated generated page. + +This is useful if you want to customize the generated Starlight pages from Obsidian. +Note that the values are not validated and are copied as-is so it's up to you to ensure they are compatible with Starlight. + ## Sidebar configuration The sidebar configuration is an object used to configure the generated vault pages sidebar group. diff --git a/docs/src/content/docs/guides/features.md b/docs/src/content/docs/guides/features.md index 27dfd21..0bba068 100644 --- a/docs/src/content/docs/guides/features.md +++ b/docs/src/content/docs/guides/features.md @@ -81,7 +81,8 @@ Some features may never be supported due to various reasons, e.g. accessibility ## Properties -All unsupported properties are ignored and not exported. +By deault, all unsupported properties are ignored and not exported. +This behavior can be changed by setting the [`copyStarlightFrontmatter`](/configuration/#copystarlightfrontmatter) option to `true` to include all known [Starlight frontmatter fields](https://starlight.astro.build/reference/frontmatter/). | Name | Supported | | :------------------------------------------------------------------------------------------------ | :-------: | diff --git a/fixtures/basics/.obsidian/types.json b/fixtures/basics/.obsidian/types.json new file mode 100644 index 0000000..b928377 --- /dev/null +++ b/fixtures/basics/.obsidian/types.json @@ -0,0 +1,10 @@ +{ + "types": { + "aliases": "aliases", + "cssclasses": "multitext", + "tags": "tags", + "tableOfContents": "checkbox", + "sidebar": "multitext", + "pagefind": "checkbox" + } +} \ No newline at end of file diff --git a/fixtures/basics/Starlight properties.md b/fixtures/basics/Starlight properties.md new file mode 100644 index 0000000..d9db045 --- /dev/null +++ b/fixtures/basics/Starlight properties.md @@ -0,0 +1,9 @@ +--- +description: This is a custom description +slug: custom-starlight-slug +unknown: this is a custom property +title: Custom Starlight Title +tableOfContents: false +--- + +Test diff --git a/fixtures/basics/Unsupported properties.md b/fixtures/basics/Unsupported properties.md index d25f898..d5c2015 100644 --- a/fixtures/basics/Unsupported properties.md +++ b/fixtures/basics/Unsupported properties.md @@ -2,6 +2,7 @@ unknown: this is a custom property cssclasses: - custom +pagefind: false --- Test diff --git a/packages/starlight-obsidian/index.ts b/packages/starlight-obsidian/index.ts index 7df79e3..e965c4e 100644 --- a/packages/starlight-obsidian/index.ts +++ b/packages/starlight-obsidian/index.ts @@ -21,6 +21,17 @@ const starlightObsidianConfigSchema = z.object({ * @see https://help.obsidian.md/Files+and+folders/Configuration+folder */ configFolder: z.string().startsWith('.').default('.obsidian'), + /** + * Whether the Starlight Obsidian plugin should copy known Starlight frontmatter fields from Obsidian notes to the + * generated pages. + * + * This is useful if you want to customize the generated Starlight pages from Obsidian. Note that the values are not + * validated and are copied as-is so it's up to you to ensure they are compatible with Starlight. + * + * @default false + * @see https://starlight.astro.build/reference/frontmatter/ + */ + copyStarlightFrontmatter: z.boolean().default(false), /** * A list of glob patterns to ignore when generating the Obsidian vault pages. * This option can be used to ignore files or folders. diff --git a/packages/starlight-obsidian/libs/obsidian.ts b/packages/starlight-obsidian/libs/obsidian.ts index dd87918..1df5463 100644 --- a/packages/starlight-obsidian/libs/obsidian.ts +++ b/packages/starlight-obsidian/libs/obsidian.ts @@ -155,7 +155,8 @@ export function isObsidianFile(filePath: string, type?: 'image' | 'audio' | 'vid export function parseObsidianFrontmatter(content: string): ObsidianFrontmatter | undefined { try { - return obsidianFrontmatterSchema.parse(yaml.parse(content)) + const raw: unknown = yaml.parse(content) + return { ...obsidianFrontmatterSchema.parse(raw), raw: raw as ObsidianFrontmatter['raw'] } } catch { return } @@ -222,4 +223,6 @@ export interface VaultFile extends BaseVaultFile { isEqualStem(otherStem: string): boolean } -export type ObsidianFrontmatter = z.output +export type ObsidianFrontmatter = z.output & { + raw: Record +} diff --git a/packages/starlight-obsidian/libs/remark.ts b/packages/starlight-obsidian/libs/remark.ts index 2433721..14e7f39 100644 --- a/packages/starlight-obsidian/libs/remark.ts +++ b/packages/starlight-obsidian/libs/remark.ts @@ -29,7 +29,7 @@ import { type VaultFile, } from './obsidian' import { extractPathAndAnchor, getExtension, isAnchor } from './path' -import { getStarlightCalloutType, isAssetFile } from './starlight' +import { getStarlightCalloutType, getStarlightLikeFrontmatter, isAssetFile } from './starlight' const generateAssetImportId = customAlphabet('abcdefghijklmnopqrstuvwxyz', 6) @@ -420,21 +420,28 @@ async function handleMermaid(tree: Root, file: VFile) { } function getFrontmatterNodeValue(file: VFile, obsidianFrontmatter?: ObsidianFrontmatter) { - const frontmatter: Frontmatter = { + let frontmatter: Frontmatter = { title: file.stem, editUrl: false, } + if (obsidianFrontmatter && file.data.copyStarlightFrontmatter) { + const starlightLikeFrontmatter = getStarlightLikeFrontmatter(obsidianFrontmatter.raw) + frontmatter = { ...frontmatter, ...starlightLikeFrontmatter } + } + if (file.data.includeKatexStyles) { - frontmatter.head = [ - { - tag: 'link', - attrs: { - rel: 'stylesheet', - href: 'https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css', - }, + if (!frontmatter.head) { + frontmatter.head = [] + } + + frontmatter.head.push({ + tag: 'link', + attrs: { + rel: 'stylesheet', + href: 'https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css', }, - ] + }) } const ogImage = obsidianFrontmatter?.cover ?? obsidianFrontmatter?.image @@ -634,6 +641,7 @@ function ensureTransformContext(file: VFile): asserts file is VFile & { data: Tr export interface TransformContext { aliases?: string[] assetImports?: [id: string, path: string][] + copyStarlightFrontmatter?: boolean files: VaultFile[] includeKatexStyles?: boolean includeTwitterComponent?: boolean diff --git a/packages/starlight-obsidian/libs/starlight.ts b/packages/starlight-obsidian/libs/starlight.ts index c419e9d..4307272 100644 --- a/packages/starlight-obsidian/libs/starlight.ts +++ b/packages/starlight-obsidian/libs/starlight.ts @@ -8,7 +8,7 @@ import type { StarlightObsidianConfig } from '..' import { copyFile, ensureDirectory, removeDirectory } from './fs' import { transformMarkdownToString } from './markdown' -import { getObsidianVaultFiles, isObsidianFile, type Vault, type VaultFile } from './obsidian' +import { getObsidianVaultFiles, isObsidianFile, type ObsidianFrontmatter, type Vault, type VaultFile } from './obsidian' import { getExtension } from './path' const assetsPath = 'src/assets' @@ -47,6 +47,24 @@ const obsidianToStarlightCalloutTypeMap: Record = { cite: 'note', } +// https://github.com/withastro/starlight/blob/main/packages/starlight/schema.ts +const starlightFrontmatterKeys = [ + 'title', + // The `description` property is ignored as it's part of the Obsidian frontmatter too. + 'slug', + 'editUrl', + 'head', + 'tableOfContents', + 'template', + 'hero', + 'banner', + 'lastUpdated', + 'prev', + 'next', + 'pagefind', + 'sidebar', +] + export function getSidebarGroupPlaceholder(): SidebarManualGroup { return { items: [], @@ -135,6 +153,18 @@ export function isAssetFile(filePath: string): boolean { return getExtension(filePath) !== '.bmp' && isObsidianFile(filePath, 'image') } +export function getStarlightLikeFrontmatter(rawFrontmatter: ObsidianFrontmatter['raw']): Record { + const frontmatter: Record = {} + + for (const key of starlightFrontmatterKeys) { + if (key in rawFrontmatter) { + frontmatter[key] = rawFrontmatter[key] + } + } + + return frontmatter +} + async function addContent( config: StarlightObsidianConfig, vault: Vault, @@ -151,6 +181,7 @@ async function addContent( type, } = await transformMarkdownToString(vaultFile.fsPath, obsidianContent, { files: vaultFiles, + copyStarlightFrontmatter: config.copyStarlightFrontmatter, output: config.output, vault, }) diff --git a/packages/starlight-obsidian/tests/properties.test.ts b/packages/starlight-obsidian/tests/properties.test.ts index 0f59bc0..47e0a4b 100644 --- a/packages/starlight-obsidian/tests/properties.test.ts +++ b/packages/starlight-obsidian/tests/properties.test.ts @@ -1,12 +1,15 @@ import { expect, test } from 'vitest' -import { transformFixtureMdFile } from './utils' +import { getObsidianPaths, getObsidianVaultFiles, getVault } from '../libs/obsidian' -test('strips unsupported known and unknown properties', async () => { +import { getFixtureConfig, transformFixtureMdFile } from './utils' + +test('strips unsupported known, unknown properties, and known Starlight frontmatter fields', async () => { const result = await transformFixtureMdFile('basics', 'Unsupported properties.md', { includeFrontmatter: true }) expect(result.content).not.toMatch(/unknown/) expect(result.content).not.toMatch(/cssclasses/) + expect(result.content).not.toMatch(/pagefind/) }) test('includes supported properties', async () => { @@ -45,3 +48,29 @@ head: Test `) }) + +test('includes known Starlight frontmatter fields if the option is enabled', async () => { + const fixtureName = 'basics' + const vault = await getVault(getFixtureConfig(fixtureName)) + const paths = await getObsidianPaths(vault) + const files = getObsidianVaultFiles(vault, paths) + const options = { + context: { copyStarlightFrontmatter: true, files, output: 'notes', vault }, + includeFrontmatter: true, + } + + const result = await transformFixtureMdFile(fixtureName, 'Starlight properties.md', options) + + expect(result.content).toMatchInlineSnapshot(` + "--- + title: Custom Starlight Title + editUrl: false + slug: custom-starlight-slug + tableOfContents: false + description: This is a custom description + --- + + Test + " + `) +}) diff --git a/packages/starlight-obsidian/tests/utils.ts b/packages/starlight-obsidian/tests/utils.ts index d1b6e58..23f2486 100644 --- a/packages/starlight-obsidian/tests/utils.ts +++ b/packages/starlight-obsidian/tests/utils.ts @@ -25,8 +25,12 @@ export function getFixtureConfig( config: Partial = {}, ): StarlightObsidianConfig { return { + autoLinkHeadings: false, configFolder: '.obsidian', + copyStarlightFrontmatter: false, ignore: [], + skipGeneration: false, + tableOfContentsOverview: 'default', output: 'notes', sidebar: { collapsed: false, @@ -57,6 +61,7 @@ export async function transformFixtureMdFile( const md = await getFixtureFile(fixtureName, filePath) const fileName = path.basename(filePath) const result = await transformMarkdownToString(fixtureFilePath, md, { + copyStarlightFrontmatter: options.context?.copyStarlightFrontmatter ?? false, files: options.context?.files ?? [ createVaultFile({ fileName,