Skip to content

Commit

Permalink
Merge pull request #8 from HiDeoo/hd-copy-starlight-frontmatter
Browse files Browse the repository at this point in the history
  • Loading branch information
HiDeoo committed Mar 9, 2024
2 parents 9ea4b00 + 513e03e commit 038628e
Show file tree
Hide file tree
Showing 11 changed files with 134 additions and 16 deletions.
10 changes: 10 additions & 0 deletions docs/src/content/docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 2 additions & 1 deletion docs/src/content/docs/guides/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
| :------------------------------------------------------------------------------------------------ | :-------: |
Expand Down
10 changes: 10 additions & 0 deletions fixtures/basics/.obsidian/types.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"types": {
"aliases": "aliases",
"cssclasses": "multitext",
"tags": "tags",
"tableOfContents": "checkbox",
"sidebar": "multitext",
"pagefind": "checkbox"
}
}
9 changes: 9 additions & 0 deletions fixtures/basics/Starlight properties.md
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions fixtures/basics/Unsupported properties.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
unknown: this is a custom property
cssclasses:
- custom
pagefind: false
---

Test
11 changes: 11 additions & 0 deletions packages/starlight-obsidian/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
7 changes: 5 additions & 2 deletions packages/starlight-obsidian/libs/obsidian.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -222,4 +223,6 @@ export interface VaultFile extends BaseVaultFile {
isEqualStem(otherStem: string): boolean
}

export type ObsidianFrontmatter = z.output<typeof obsidianFrontmatterSchema>
export type ObsidianFrontmatter = z.output<typeof obsidianFrontmatterSchema> & {
raw: Record<string | number, unknown>
}
28 changes: 18 additions & 10 deletions packages/starlight-obsidian/libs/remark.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
33 changes: 32 additions & 1 deletion packages/starlight-obsidian/libs/starlight.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -47,6 +47,24 @@ const obsidianToStarlightCalloutTypeMap: Record<string, string> = {
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: [],
Expand Down Expand Up @@ -135,6 +153,18 @@ export function isAssetFile(filePath: string): boolean {
return getExtension(filePath) !== '.bmp' && isObsidianFile(filePath, 'image')
}

export function getStarlightLikeFrontmatter(rawFrontmatter: ObsidianFrontmatter['raw']): Record<string, unknown> {
const frontmatter: Record<string, unknown> = {}

for (const key of starlightFrontmatterKeys) {
if (key in rawFrontmatter) {
frontmatter[key] = rawFrontmatter[key]
}
}

return frontmatter
}

async function addContent(
config: StarlightObsidianConfig,
vault: Vault,
Expand All @@ -151,6 +181,7 @@ async function addContent(
type,
} = await transformMarkdownToString(vaultFile.fsPath, obsidianContent, {
files: vaultFiles,
copyStarlightFrontmatter: config.copyStarlightFrontmatter,
output: config.output,
vault,
})
Expand Down
33 changes: 31 additions & 2 deletions packages/starlight-obsidian/tests/properties.test.ts
Original file line number Diff line number Diff line change
@@ -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 () => {
Expand Down Expand Up @@ -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
"
`)
})
5 changes: 5 additions & 0 deletions packages/starlight-obsidian/tests/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,12 @@ export function getFixtureConfig(
config: Partial<StarlightObsidianConfig> = {},
): StarlightObsidianConfig {
return {
autoLinkHeadings: false,
configFolder: '.obsidian',
copyStarlightFrontmatter: false,
ignore: [],
skipGeneration: false,
tableOfContentsOverview: 'default',
output: 'notes',
sidebar: {
collapsed: false,
Expand Down Expand Up @@ -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,
Expand Down

0 comments on commit 038628e

Please sign in to comment.