starlight-obsidian 0.1.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.
@@ -0,0 +1,263 @@
1
+ import fs from 'node:fs/promises'
2
+ import path from 'node:path'
3
+
4
+ import type { StarlightUserConfig } from '@astrojs/starlight/types'
5
+ import type { AstroIntegrationLogger } from 'astro'
6
+
7
+ import type { StarlightObsidianConfig } from '..'
8
+
9
+ import { copyFile, ensureDirectory, removeDirectory } from './fs'
10
+ import { transformMarkdownToString } from './markdown'
11
+ import { getObsidianVaultFiles, isObsidianFile, type Vault, type VaultFile } from './obsidian'
12
+ import { getExtension } from './path'
13
+
14
+ const assetsPath = 'src/assets'
15
+ const docsPath = 'src/content/docs'
16
+ const publicPath = 'public'
17
+
18
+ const starlightObsidianSidebarGroupLabel = Symbol('StarlightObsidianSidebarGroupLabel')
19
+
20
+ const obsidianToStarlightCalloutTypeMap: Record<string, string> = {
21
+ note: 'note',
22
+ abstract: 'tip',
23
+ summary: 'tip',
24
+ tldr: 'tip',
25
+ info: 'note',
26
+ todo: 'note',
27
+ tip: 'tip',
28
+ hint: 'tip',
29
+ important: 'tip',
30
+ success: 'note',
31
+ check: 'note',
32
+ done: 'note',
33
+ question: 'caution',
34
+ help: 'caution',
35
+ faq: 'caution',
36
+ warning: 'caution',
37
+ caution: 'caution',
38
+ attention: 'caution',
39
+ failure: 'danger',
40
+ fail: 'danger',
41
+ missing: 'danger',
42
+ danger: 'danger',
43
+ error: 'danger',
44
+ bug: 'danger',
45
+ example: 'tip',
46
+ quote: 'note',
47
+ cite: 'note',
48
+ }
49
+
50
+ export function getSidebarGroupPlaceholder(): SidebarManualGroup {
51
+ return {
52
+ items: [],
53
+ label: starlightObsidianSidebarGroupLabel.toString(),
54
+ }
55
+ }
56
+
57
+ export function getSidebarFromConfig(
58
+ config: StarlightObsidianConfig,
59
+ sidebar: StarlightUserConfig['sidebar'],
60
+ ): StarlightUserConfig['sidebar'] {
61
+ if (!sidebar || sidebar.length === 0) {
62
+ return sidebar
63
+ }
64
+
65
+ function replaceSidebarGroupPlaceholder(group: SidebarManualGroup): SidebarGroup {
66
+ if (group.label === starlightObsidianSidebarGroupLabel.toString()) {
67
+ return {
68
+ autogenerate: {
69
+ collapsed: config.sidebar.collapsedFolders ?? config.sidebar.collapsed,
70
+ directory: config.output,
71
+ },
72
+ collapsed: config.sidebar.collapsed,
73
+ label: config.sidebar.label,
74
+ }
75
+ }
76
+
77
+ if (isSidebarGroup(group)) {
78
+ return {
79
+ ...group,
80
+ items: group.items.map((item) => {
81
+ return isSidebarGroup(item) ? replaceSidebarGroupPlaceholder(item) : item
82
+ }),
83
+ }
84
+ }
85
+
86
+ return group
87
+ }
88
+
89
+ return sidebar.map((item) => {
90
+ return isSidebarGroup(item) ? replaceSidebarGroupPlaceholder(item) : item
91
+ })
92
+ }
93
+
94
+ export async function addObsidianFiles(
95
+ config: StarlightObsidianConfig,
96
+ vault: Vault,
97
+ obsidianPaths: string[],
98
+ logger: AstroIntegrationLogger,
99
+ ) {
100
+ const outputPaths = getOutputPaths(config)
101
+
102
+ await cleanOutputPaths(outputPaths)
103
+
104
+ const vaultFiles = getObsidianVaultFiles(vault, obsidianPaths)
105
+
106
+ const results = await Promise.allSettled(
107
+ vaultFiles.map(async (vaultFile) => {
108
+ await (vaultFile.type === 'asset'
109
+ ? addAsset(outputPaths, vaultFile)
110
+ : vaultFile.type === 'file'
111
+ ? addFile(outputPaths, vaultFile)
112
+ : addContent(config, vault, outputPaths, vaultFiles, vaultFile))
113
+ }),
114
+ )
115
+
116
+ let didFail = false
117
+
118
+ for (const result of results) {
119
+ if (result.status === 'rejected') {
120
+ didFail = true
121
+ logger.error(result.reason instanceof Error ? result.reason.message : String(result.reason))
122
+ }
123
+ }
124
+
125
+ if (didFail) {
126
+ throw new Error('Failed to generate some Starlight pages. See the error(s) above for more information.')
127
+ }
128
+ }
129
+
130
+ export function getStarlightCalloutType(obsidianCalloutType: string): string {
131
+ return obsidianToStarlightCalloutTypeMap[obsidianCalloutType] ?? 'note'
132
+ }
133
+
134
+ export function isAssetFile(filePath: string): boolean {
135
+ return getExtension(filePath) !== '.bmp' && isObsidianFile(filePath, 'image')
136
+ }
137
+
138
+ async function addContent(
139
+ config: StarlightObsidianConfig,
140
+ vault: Vault,
141
+ outputPaths: OutputPaths,
142
+ vaultFiles: VaultFile[],
143
+ vaultFile: VaultFile,
144
+ ) {
145
+ try {
146
+ const obsidianContent = await fs.readFile(vaultFile.fsPath, 'utf8')
147
+ const {
148
+ content: starlightContent,
149
+ aliases,
150
+ skip,
151
+ type,
152
+ } = await transformMarkdownToString(vaultFile.fsPath, obsidianContent, {
153
+ files: vaultFiles,
154
+ output: config.output,
155
+ vault,
156
+ })
157
+
158
+ if (skip) {
159
+ return
160
+ }
161
+
162
+ const starlightPath = path.join(
163
+ outputPaths.content,
164
+ type === 'markdown' ? vaultFile.path : vaultFile.path.replace(/\.md$/, '.mdx'),
165
+ )
166
+ const starlightDirPath = path.dirname(starlightPath)
167
+
168
+ await ensureDirectory(starlightDirPath)
169
+ await fs.writeFile(starlightPath, starlightContent)
170
+
171
+ if (aliases) {
172
+ for (const alias of aliases) {
173
+ await addAlias(config, outputPaths, vaultFile, alias)
174
+ }
175
+ }
176
+ } catch (error) {
177
+ throwVaultFileError(error, vaultFile)
178
+ }
179
+ }
180
+
181
+ async function addFile(outputPaths: OutputPaths, vaultFile: VaultFile) {
182
+ try {
183
+ await copyFile(vaultFile.fsPath, path.join(outputPaths.file, vaultFile.slug))
184
+ } catch (error) {
185
+ throwVaultFileError(error, vaultFile)
186
+ }
187
+ }
188
+
189
+ async function addAsset(outputPaths: OutputPaths, vaultFile: VaultFile) {
190
+ try {
191
+ await copyFile(vaultFile.fsPath, path.join(outputPaths.asset, vaultFile.slug))
192
+ } catch (error) {
193
+ throwVaultFileError(error, vaultFile)
194
+ }
195
+ }
196
+
197
+ async function addAlias(
198
+ config: StarlightObsidianConfig,
199
+ outputPaths: OutputPaths,
200
+ vaultFile: VaultFile,
201
+ alias: string,
202
+ ) {
203
+ const starlightPath = path.join(outputPaths.file, path.dirname(vaultFile.path), alias, 'index.html')
204
+ const starlightDirPath = path.dirname(starlightPath)
205
+
206
+ const to = path.posix.join(path.posix.sep, config.output, vaultFile.slug)
207
+ const from = path.posix.join(path.dirname(to), alias)
208
+
209
+ await ensureDirectory(starlightDirPath)
210
+
211
+ // Based on https://github.com/withastro/astro/blob/57ab578bc7bdac6c65c2315365c0e94bc98af2b3/packages/astro/src/core/build/generate.ts#L584-L591
212
+ // but tweaked to add an `<html>` element so that Pagefind does not emit a warning when ignoring the page.
213
+ await fs.writeFile(
214
+ starlightPath,
215
+ `<!doctype html>
216
+ <html>
217
+ <head>
218
+ <title>Redirecting to: ${to}</title>
219
+ <meta http-equiv="refresh" content="0;url=${to}">
220
+ <meta name="robots" content="noindex">
221
+ <link rel="canonical" href="${to}">
222
+ </head>
223
+ <body>
224
+ <a href="${to}">Redirecting from <code>${from}</code> to "<code>${to}</code>"</a>
225
+ </body>
226
+ </html>`,
227
+ )
228
+ }
229
+
230
+ function getOutputPaths(config: StarlightObsidianConfig): OutputPaths {
231
+ return {
232
+ asset: path.join(assetsPath, config.output),
233
+ content: path.join(docsPath, config.output),
234
+ file: path.join(publicPath, config.output),
235
+ }
236
+ }
237
+
238
+ async function cleanOutputPaths(outputPaths: OutputPaths) {
239
+ await removeDirectory(outputPaths.asset)
240
+ await removeDirectory(outputPaths.content)
241
+ await removeDirectory(outputPaths.file)
242
+ }
243
+
244
+ function throwVaultFileError(error: unknown, vaultFile: VaultFile): never {
245
+ throw new Error(`${vaultFile.path} — ${error instanceof Error ? error.message : String(error)}`, { cause: error })
246
+ }
247
+
248
+ function isSidebarGroup(item: SidebarGroup): item is SidebarManualGroup {
249
+ return 'items' in item
250
+ }
251
+
252
+ interface OutputPaths {
253
+ asset: string
254
+ content: string
255
+ file: string
256
+ }
257
+
258
+ interface SidebarManualGroup {
259
+ items: SidebarManualGroup[]
260
+ label: string
261
+ }
262
+
263
+ type SidebarGroup = NonNullable<StarlightUserConfig['sidebar']>[number]
@@ -0,0 +1,14 @@
1
+ ---
2
+ import Default from '@astrojs/starlight/components/PageTitle.astro'
3
+ import type { Props } from '@astrojs/starlight/props'
4
+
5
+ import Tags from '../components/Tags.astro'
6
+
7
+ const { entry } = Astro.props
8
+
9
+ const tags = entry['data']['tags']
10
+ const showTags = tags !== undefined && tags.length > 0
11
+ ---
12
+
13
+ <Default {...Astro.props}><slot /></Default>
14
+ {showTags && <Tags {tags} />}
package/package.json ADDED
@@ -0,0 +1,77 @@
1
+ {
2
+ "name": "starlight-obsidian",
3
+ "version": "0.1.1",
4
+ "license": "MIT",
5
+ "description": "Starlight plugin to publish Obsidian vaults.",
6
+ "author": "HiDeoo <github@hideoo.dev> (https://hideoo.dev)",
7
+ "type": "module",
8
+ "exports": {
9
+ ".": "./index.ts",
10
+ "./components/Twitter.astro": "./components/Twitter.astro",
11
+ "./components/Youtube.astro": "./components/Youtube.astro",
12
+ "./overrides/PageTitle.astro": "./overrides/PageTitle.astro",
13
+ "./schema": "./schema.ts",
14
+ "./styles": "./styles.css",
15
+ "./package.json": "./package.json"
16
+ },
17
+ "dependencies": {
18
+ "@astro-community/astro-embed-twitter": "0.5.3",
19
+ "@astro-community/astro-embed-youtube": "0.4.4",
20
+ "github-slugger": "2.0.0",
21
+ "globby": "14.0.0",
22
+ "hast-util-to-html": "9.0.0",
23
+ "is-absolute-url": "4.0.1",
24
+ "mdast-util-find-and-replace": "3.0.1",
25
+ "mdast-util-to-hast": "13.1.0",
26
+ "nanoid": "5.0.4",
27
+ "rehype": "13.0.1",
28
+ "rehype-katex": "7.0.0",
29
+ "rehype-mermaid": "2.1.0",
30
+ "remark": "15.0.1",
31
+ "remark-frontmatter": "5.0.0",
32
+ "remark-gfm": "4.0.0",
33
+ "remark-math": "6.0.0",
34
+ "unist-util-visit": "5.0.0",
35
+ "vfile": "6.0.1",
36
+ "yaml": "2.3.4"
37
+ },
38
+ "devDependencies": {
39
+ "@astrojs/starlight": "0.16.0",
40
+ "@types/hast": "3.0.3",
41
+ "@types/mdast": "4.0.3",
42
+ "@types/unist": "3.0.2",
43
+ "astro": "4.2.1",
44
+ "typescript": "5.3.3",
45
+ "vitest": "1.2.1"
46
+ },
47
+ "peerDependencies": {
48
+ "@astrojs/starlight": ">=0.15.0",
49
+ "astro": ">=4.0.0"
50
+ },
51
+ "engines": {
52
+ "node": ">=18.14.1"
53
+ },
54
+ "packageManager": "pnpm@8.14.1",
55
+ "publishConfig": {
56
+ "access": "public"
57
+ },
58
+ "sideEffects": false,
59
+ "keywords": [
60
+ "starlight",
61
+ "plugin",
62
+ "obsidian",
63
+ "vault",
64
+ "documentation",
65
+ "astro"
66
+ ],
67
+ "homepage": "https://github.com/HiDeoo/starlight-obsidian",
68
+ "repository": {
69
+ "type": "git",
70
+ "url": "https://github.com/HiDeoo/starlight-obsidian.git"
71
+ },
72
+ "bugs": "https://github.com/HiDeoo/starlight-obsidian/issues",
73
+ "scripts": {
74
+ "test": "vitest",
75
+ "lint": "prettier -c --cache . && eslint . --cache --max-warnings=0"
76
+ }
77
+ }
package/schema.ts ADDED
@@ -0,0 +1,8 @@
1
+ import { z } from 'astro/zod'
2
+
3
+ export const starlightObsidianSchema = () =>
4
+ z
5
+ .object({
6
+ tags: z.array(z.string()),
7
+ })
8
+ .partial()
package/styles.css ADDED
@@ -0,0 +1,42 @@
1
+ :root {
2
+ --sl--bs-hue-yellow: 51;
3
+
4
+ --sl-obs-highlight: hsl(var(--sl--bs-hue-yellow), 71%, 56%);
5
+ }
6
+
7
+ :root[data-theme='light'] {
8
+ --sl-obs-highlight: hsl(var(--sl--bs-hue-yellow), 80%, 56%);
9
+ }
10
+
11
+ .sl-obs-highlight {
12
+ background-color: var(--sl-obs-highlight);
13
+ }
14
+
15
+ .sl-markdown-content .sl-obs-embed-audio {
16
+ width: 100%;
17
+ }
18
+
19
+ .sl-markdown-content .sl-obs-embed-pdf {
20
+ border: none;
21
+ height: 600px;
22
+ width: 100%;
23
+ }
24
+
25
+ .sl-obs-tag {
26
+ background-color: var(--sl-color-accent-low);
27
+ border-radius: 0.25rem;
28
+ border: 1px solid var(--sl-color-accent);
29
+ color: #fff;
30
+ font-family: var(--sl-font-system-mono);
31
+ font-size: var(--sl-text-xs);
32
+ padding: 0.25rem 0.375rem;
33
+ }
34
+
35
+ [data-theme='light'] .sl-obs-tag {
36
+ background-color: var(--sl-color-accent-high);
37
+ }
38
+
39
+ blockquote.twitter-tweet:not(.twitter-tweet-rendered) {
40
+ border: 1px solid var(--sl-color-gray-5);
41
+ margin-block: 1rem;
42
+ }