starlight-obsidian 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.ts CHANGED
@@ -21,6 +21,17 @@ const starlightObsidianConfigSchema = z.object({
21
21
  * @see https://help.obsidian.md/Files+and+folders/Configuration+folder
22
22
  */
23
23
  configFolder: z.string().startsWith('.').default('.obsidian'),
24
+ /**
25
+ * Whether the Starlight Obsidian plugin should copy known Starlight frontmatter fields from Obsidian notes to the
26
+ * generated pages.
27
+ *
28
+ * This is useful if you want to customize the generated Starlight pages from Obsidian. Note that the values are not
29
+ * validated and are copied as-is so it's up to you to ensure they are compatible with Starlight.
30
+ *
31
+ * @default false
32
+ * @see https://starlight.astro.build/reference/frontmatter/
33
+ */
34
+ copyStarlightFrontmatter: z.boolean().default(false),
24
35
  /**
25
36
  * A list of glob patterns to ignore when generating the Obsidian vault pages.
26
37
  * This option can be used to ignore files or folders.
package/libs/obsidian.ts CHANGED
@@ -9,7 +9,7 @@ import yaml from 'yaml'
9
9
  import type { StarlightObsidianConfig } from '..'
10
10
 
11
11
  import { isDirectory, isFile } from './fs'
12
- import { getExtension, isAnchor, slugifyPath, stripExtension } from './path'
12
+ import { getExtension, isAnchor, slashify, slugifyPath, stripExtension } from './path'
13
13
  import { throwUserError } from './plugin'
14
14
  import { isAssetFile } from './starlight'
15
15
 
@@ -56,7 +56,7 @@ export async function getVault(config: StarlightObsidianConfig): Promise<Vault>
56
56
 
57
57
  return {
58
58
  options,
59
- path: vaultPath,
59
+ path: slashify(vaultPath),
60
60
  }
61
61
  }
62
62
 
@@ -155,7 +155,8 @@ export function isObsidianFile(filePath: string, type?: 'image' | 'audio' | 'vid
155
155
 
156
156
  export function parseObsidianFrontmatter(content: string): ObsidianFrontmatter | undefined {
157
157
  try {
158
- return obsidianFrontmatterSchema.parse(yaml.parse(content))
158
+ const raw: unknown = yaml.parse(content)
159
+ return { ...obsidianFrontmatterSchema.parse(raw), raw: raw as ObsidianFrontmatter['raw'] }
159
160
  } catch {
160
161
  return
161
162
  }
@@ -222,4 +223,6 @@ export interface VaultFile extends BaseVaultFile {
222
223
  isEqualStem(otherStem: string): boolean
223
224
  }
224
225
 
225
- export type ObsidianFrontmatter = z.output<typeof obsidianFrontmatterSchema>
226
+ export type ObsidianFrontmatter = z.output<typeof obsidianFrontmatterSchema> & {
227
+ raw: Record<string | number, unknown>
228
+ }
package/libs/path.ts CHANGED
@@ -37,3 +37,18 @@ export function slugifyPath(filePath: string) {
37
37
  })
38
38
  .join('/')
39
39
  }
40
+
41
+ // https://github.com/sindresorhus/slash
42
+ export function slashify(filePath: string) {
43
+ const isExtendedLengthPath = filePath.startsWith('\\\\?\\')
44
+
45
+ if (isExtendedLengthPath) {
46
+ return filePath
47
+ }
48
+
49
+ return filePath.replaceAll('\\', '/')
50
+ }
51
+
52
+ export function osPath(filePath: string) {
53
+ return filePath.replaceAll('/', path.sep)
54
+ }
package/libs/remark.ts CHANGED
@@ -29,7 +29,7 @@ import {
29
29
  type VaultFile,
30
30
  } from './obsidian'
31
31
  import { extractPathAndAnchor, getExtension, isAnchor } from './path'
32
- import { getStarlightCalloutType, isAssetFile } from './starlight'
32
+ import { getStarlightCalloutType, getStarlightLikeFrontmatter, isAssetFile } from './starlight'
33
33
 
34
34
  const generateAssetImportId = customAlphabet('abcdefghijklmnopqrstuvwxyz', 6)
35
35
 
@@ -38,7 +38,7 @@ const commentReplacementRegex = /%%(?<comment>(?:(?!%%).)+)%%/gs
38
38
  const wikilinkReplacementRegex = /!?\[\[(?<url>(?:(?![[\]|]).)+)(?:\|(?<maybeText>(?:(?![[\]]).)+))?]]/g
39
39
  const tagReplacementRegex = /(?:^|\s)#(?<tag>[\w/-]+)/g
40
40
  const calloutRegex = /^\[!(?<type>\w+)][+-]? ?(?<title>.*)$/
41
- const imageSizeRegex = /^(?<altText>.*)\|(?:(?<widthOnly>\d+)|(?:(?<width>\d+)x(?<height>\d+)))$/
41
+ const imageSizeRegex = /^(?:(?<altText>.*)\|)?(?:(?<widthOnly>\d+)|(?:(?<width>\d+)x(?<height>\d+)))$/
42
42
 
43
43
  const asideDelimiter = ':::'
44
44
 
@@ -358,7 +358,7 @@ function handleBlockquotes(node: Blockquote, context: VisitorContext) {
358
358
  return SKIP
359
359
  }
360
360
 
361
- const [firstLine, ...otherLines] = firstGrandChild.value.split('\n')
361
+ const [firstLine, ...otherLines] = firstGrandChild.value.split(/\r?\n/)
362
362
 
363
363
  if (!firstLine) {
364
364
  return SKIP
@@ -420,21 +420,28 @@ async function handleMermaid(tree: Root, file: VFile) {
420
420
  }
421
421
 
422
422
  function getFrontmatterNodeValue(file: VFile, obsidianFrontmatter?: ObsidianFrontmatter) {
423
- const frontmatter: Frontmatter = {
423
+ let frontmatter: Frontmatter = {
424
424
  title: file.stem,
425
425
  editUrl: false,
426
426
  }
427
427
 
428
+ if (obsidianFrontmatter && file.data.copyStarlightFrontmatter) {
429
+ const starlightLikeFrontmatter = getStarlightLikeFrontmatter(obsidianFrontmatter.raw)
430
+ frontmatter = { ...frontmatter, ...starlightLikeFrontmatter }
431
+ }
432
+
428
433
  if (file.data.includeKatexStyles) {
429
- frontmatter.head = [
430
- {
431
- tag: 'link',
432
- attrs: {
433
- rel: 'stylesheet',
434
- href: 'https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css',
435
- },
434
+ if (!frontmatter.head) {
435
+ frontmatter.head = []
436
+ }
437
+
438
+ frontmatter.head.push({
439
+ tag: 'link',
440
+ attrs: {
441
+ rel: 'stylesheet',
442
+ href: 'https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css',
436
443
  },
437
- ]
444
+ })
438
445
  }
439
446
 
440
447
  const ogImage = obsidianFrontmatter?.cover ?? obsidianFrontmatter?.image
@@ -478,7 +485,7 @@ function getRelativeFilePath(file: VFile, relativePath: string) {
478
485
  function getAssetPath(file: VFile, relativePath: string) {
479
486
  ensureTransformContext(file)
480
487
 
481
- return path.posix.join('../../..', path.relative(file.dirname, file.data.vault.path), 'assets', relativePath)
488
+ return path.posix.join('../../..', path.posix.relative(file.dirname, file.data.vault.path), 'assets', relativePath)
482
489
  }
483
490
 
484
491
  function getFilePathFromVaultFile(vaultFile: VaultFile, url: string) {
@@ -527,6 +534,7 @@ function handleImagesWithSize(node: Image, context: VisitorContext, type: 'asset
527
534
  return
528
535
  }
529
536
 
537
+ const imgAltText = altText ?? ''
530
538
  const imgWidth = widthOnly ?? width
531
539
  const imgHeight = height ?? 'auto'
532
540
  // Workaround Starlight `auto` height default style.
@@ -535,7 +543,7 @@ function handleImagesWithSize(node: Image, context: VisitorContext, type: 'asset
535
543
  if (type === 'external') {
536
544
  replaceNode(context, {
537
545
  type: 'html',
538
- value: `<img src="${node.url}" alt="${altText}" width="${imgWidth}" height="${imgHeight}"${imgStyle} />`,
546
+ value: `<img src="${node.url}" alt="${imgAltText}" width="${imgWidth}" height="${imgHeight}"${imgStyle} />`,
539
547
  })
540
548
  } else {
541
549
  const importId = generateAssetImportId()
@@ -549,7 +557,7 @@ function handleImagesWithSize(node: Image, context: VisitorContext, type: 'asset
549
557
  replaceNode(
550
558
  context,
551
559
  createMdxNode(
552
- `<Image src={${importId}} alt="${altText}" width="${imgWidth}" height="${imgHeight}"${imgStyle} />`,
560
+ `<Image src={${importId}} alt="${imgAltText}" width="${imgWidth}" height="${imgHeight}"${imgStyle} />`,
553
561
  ),
554
562
  )
555
563
  }
@@ -634,6 +642,7 @@ function ensureTransformContext(file: VFile): asserts file is VFile & { data: Tr
634
642
  export interface TransformContext {
635
643
  aliases?: string[]
636
644
  assetImports?: [id: string, path: string][]
645
+ copyStarlightFrontmatter?: boolean
637
646
  files: VaultFile[]
638
647
  includeKatexStyles?: boolean
639
648
  includeTwitterComponent?: boolean
package/libs/starlight.ts CHANGED
@@ -8,7 +8,7 @@ import type { StarlightObsidianConfig } from '..'
8
8
 
9
9
  import { copyFile, ensureDirectory, removeDirectory } from './fs'
10
10
  import { transformMarkdownToString } from './markdown'
11
- import { getObsidianVaultFiles, isObsidianFile, type Vault, type VaultFile } from './obsidian'
11
+ import { getObsidianVaultFiles, isObsidianFile, type ObsidianFrontmatter, type Vault, type VaultFile } from './obsidian'
12
12
  import { getExtension } from './path'
13
13
 
14
14
  const assetsPath = 'src/assets'
@@ -47,6 +47,24 @@ const obsidianToStarlightCalloutTypeMap: Record<string, string> = {
47
47
  cite: 'note',
48
48
  }
49
49
 
50
+ // https://github.com/withastro/starlight/blob/main/packages/starlight/schema.ts
51
+ const starlightFrontmatterKeys = [
52
+ 'title',
53
+ // The `description` property is ignored as it's part of the Obsidian frontmatter too.
54
+ 'slug',
55
+ 'editUrl',
56
+ 'head',
57
+ 'tableOfContents',
58
+ 'template',
59
+ 'hero',
60
+ 'banner',
61
+ 'lastUpdated',
62
+ 'prev',
63
+ 'next',
64
+ 'pagefind',
65
+ 'sidebar',
66
+ ]
67
+
50
68
  export function getSidebarGroupPlaceholder(): SidebarManualGroup {
51
69
  return {
52
70
  items: [],
@@ -135,6 +153,18 @@ export function isAssetFile(filePath: string): boolean {
135
153
  return getExtension(filePath) !== '.bmp' && isObsidianFile(filePath, 'image')
136
154
  }
137
155
 
156
+ export function getStarlightLikeFrontmatter(rawFrontmatter: ObsidianFrontmatter['raw']): Record<string, unknown> {
157
+ const frontmatter: Record<string, unknown> = {}
158
+
159
+ for (const key of starlightFrontmatterKeys) {
160
+ if (key in rawFrontmatter) {
161
+ frontmatter[key] = rawFrontmatter[key]
162
+ }
163
+ }
164
+
165
+ return frontmatter
166
+ }
167
+
138
168
  async function addContent(
139
169
  config: StarlightObsidianConfig,
140
170
  vault: Vault,
@@ -151,6 +181,7 @@ async function addContent(
151
181
  type,
152
182
  } = await transformMarkdownToString(vaultFile.fsPath, obsidianContent, {
153
183
  files: vaultFiles,
184
+ copyStarlightFrontmatter: config.copyStarlightFrontmatter,
154
185
  output: config.output,
155
186
  vault,
156
187
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "starlight-obsidian",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "license": "MIT",
5
5
  "description": "Starlight plugin to publish Obsidian vaults.",
6
6
  "author": "HiDeoo <github@hideoo.dev> (https://hideoo.dev)",