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 +11 -0
- package/libs/obsidian.ts +7 -4
- package/libs/path.ts +15 -0
- package/libs/remark.ts +24 -15
- package/libs/starlight.ts +32 -1
- package/package.json +1 -1
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
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="${
|
|
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="${
|
|
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
|
})
|