@life-and-dev/mdsite 0.0.3
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/README.md +228 -0
- package/bin/mdsite.js +3 -0
- package/dist/commands/commands.test.d.ts +1 -0
- package/dist/commands/commands.test.js +302 -0
- package/dist/commands/commands.test.js.map +1 -0
- package/dist/commands/generate.d.ts +1 -0
- package/dist/commands/generate.js +20 -0
- package/dist/commands/generate.js.map +1 -0
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +16 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/prepare.d.ts +1 -0
- package/dist/commands/prepare.js +111 -0
- package/dist/commands/prepare.js.map +1 -0
- package/dist/commands/prepare.test.d.ts +1 -0
- package/dist/commands/prepare.test.js +131 -0
- package/dist/commands/prepare.test.js.map +1 -0
- package/dist/commands/preview.d.ts +5 -0
- package/dist/commands/preview.js +62 -0
- package/dist/commands/preview.js.map +1 -0
- package/dist/commands/start.d.ts +5 -0
- package/dist/commands/start.js +50 -0
- package/dist/commands/start.js.map +1 -0
- package/dist/commands/stop.d.ts +1 -0
- package/dist/commands/stop.js +26 -0
- package/dist/commands/stop.js.map +1 -0
- package/dist/commands/workflows.test.d.ts +1 -0
- package/dist/commands/workflows.test.js +183 -0
- package/dist/commands/workflows.test.js.map +1 -0
- package/dist/config/default-mdsite-config.d.ts +2 -0
- package/dist/config/default-mdsite-config.js +82 -0
- package/dist/config/default-mdsite-config.js.map +1 -0
- package/dist/config/default-mdsite-config.test.d.ts +1 -0
- package/dist/config/default-mdsite-config.test.js +42 -0
- package/dist/config/default-mdsite-config.test.js.map +1 -0
- package/dist/config/mdsite-config.d.ts +42 -0
- package/dist/config/mdsite-config.js +74 -0
- package/dist/config/mdsite-config.js.map +1 -0
- package/dist/config/mdsite-config.test.d.ts +1 -0
- package/dist/config/mdsite-config.test.js +115 -0
- package/dist/config/mdsite-config.test.js.map +1 -0
- package/dist/config/menu.d.ts +3 -0
- package/dist/config/menu.js +58 -0
- package/dist/config/menu.js.map +1 -0
- package/dist/config/menu.test.d.ts +1 -0
- package/dist/config/menu.test.js +53 -0
- package/dist/config/menu.test.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +99 -0
- package/dist/index.js.map +1 -0
- package/dist/index.test.d.ts +1 -0
- package/dist/index.test.js +133 -0
- package/dist/index.test.js.map +1 -0
- package/dist/process/child-process.d.ts +9 -0
- package/dist/process/child-process.js +142 -0
- package/dist/process/child-process.js.map +1 -0
- package/dist/process/child-process.test.d.ts +1 -0
- package/dist/process/child-process.test.js +131 -0
- package/dist/process/child-process.test.js.map +1 -0
- package/dist/process/runtime-state.d.ts +16 -0
- package/dist/process/runtime-state.js +42 -0
- package/dist/process/runtime-state.js.map +1 -0
- package/dist/process/runtime-state.test.d.ts +1 -0
- package/dist/process/runtime-state.test.js +57 -0
- package/dist/process/runtime-state.test.js.map +1 -0
- package/dist/renderer/mdsite-nuxt.d.ts +22 -0
- package/dist/renderer/mdsite-nuxt.js +194 -0
- package/dist/renderer/mdsite-nuxt.js.map +1 -0
- package/dist/renderer/mdsite-nuxt.test.d.ts +1 -0
- package/dist/renderer/mdsite-nuxt.test.js +226 -0
- package/dist/renderer/mdsite-nuxt.test.js.map +1 -0
- package/mdsite-nuxt/app/app.vue +7 -0
- package/mdsite-nuxt/app/assets/css/bible-tooltips.css +87 -0
- package/mdsite-nuxt/app/assets/css/markdown.css +195 -0
- package/mdsite-nuxt/app/assets/css/print.css +133 -0
- package/mdsite-nuxt/app/components/AppBar.vue +138 -0
- package/mdsite-nuxt/app/components/AppFooter.vue +88 -0
- package/mdsite-nuxt/app/components/AppNavigation.vue +162 -0
- package/mdsite-nuxt/app/components/AppTableOfContents.vue +149 -0
- package/mdsite-nuxt/app/components/BreadcrumbNav.vue +139 -0
- package/mdsite-nuxt/app/components/NavigationTree.vue +169 -0
- package/mdsite-nuxt/app/components/SearchBox.vue +212 -0
- package/mdsite-nuxt/app/components/TocItem.vue +75 -0
- package/mdsite-nuxt/app/components/TreeNode.vue +264 -0
- package/mdsite-nuxt/app/components/content/MarkdownAlert.vue +42 -0
- package/mdsite-nuxt/app/components/content/Mermaid.vue +103 -0
- package/mdsite-nuxt/app/components/content/ProseA.vue +57 -0
- package/mdsite-nuxt/app/components/content/ProseBlockquote.vue +14 -0
- package/mdsite-nuxt/app/components/content/ProsePre.vue +49 -0
- package/mdsite-nuxt/app/components/content/ProseTable.vue +143 -0
- package/mdsite-nuxt/app/composables/useAppTheme.ts +89 -0
- package/mdsite-nuxt/app/composables/useBibleTooltips.ts +21 -0
- package/mdsite-nuxt/app/composables/useBreadcrumbs.ts +70 -0
- package/mdsite-nuxt/app/composables/useContentPostProcessing.ts +89 -0
- package/mdsite-nuxt/app/composables/useNavigationTree.ts +174 -0
- package/mdsite-nuxt/app/composables/useSearchIndex.ts +47 -0
- package/mdsite-nuxt/app/composables/useSearchRelevance.test.ts +255 -0
- package/mdsite-nuxt/app/composables/useSearchRelevance.ts +207 -0
- package/mdsite-nuxt/app/composables/useSidebarState.ts +62 -0
- package/mdsite-nuxt/app/composables/useSiteConfig.ts +37 -0
- package/mdsite-nuxt/app/composables/useSourceEdit.ts +69 -0
- package/mdsite-nuxt/app/composables/useTableOfContents.ts +141 -0
- package/mdsite-nuxt/app/composables/useTableParser.ts +106 -0
- package/mdsite-nuxt/app/composables/useTooltipConfig.ts +20 -0
- package/mdsite-nuxt/app/config/themes.ts +17 -0
- package/mdsite-nuxt/app/layouts/default.vue +376 -0
- package/mdsite-nuxt/app/pages/[...slug].vue +56 -0
- package/mdsite-nuxt/app/pages/index.vue +54 -0
- package/mdsite-nuxt/app/plugins/bible-tooltips.client.ts +401 -0
- package/mdsite-nuxt/app/plugins/bible-tooltips.test.ts +284 -0
- package/mdsite-nuxt/app/types/content.d.ts +10 -0
- package/mdsite-nuxt/app/types/table.ts +57 -0
- package/mdsite-nuxt/app/utils/bible-book-names.ts +37 -0
- package/mdsite-nuxt/app/utils/bible-verse-utils.test.ts +148 -0
- package/mdsite-nuxt/app/utils/bible-verse-utils.ts +236 -0
- package/mdsite-nuxt/content.config.ts +18 -0
- package/mdsite-nuxt/example.config.yml +67 -0
- package/mdsite-nuxt/nuxt.config.ts +247 -0
- package/mdsite-nuxt/package-lock.json +15922 -0
- package/mdsite-nuxt/package.json +50 -0
- package/mdsite-nuxt/playwright.config.js +47 -0
- package/mdsite-nuxt/public/robots.txt +2 -0
- package/mdsite-nuxt/scripts/generate-favicons.ts +192 -0
- package/mdsite-nuxt/scripts/generate-indices.ts +508 -0
- package/mdsite-nuxt/scripts/renderer-hooks.test.ts +277 -0
- package/mdsite-nuxt/scripts/renderer-hooks.ts +141 -0
- package/mdsite-nuxt/scripts/start.test.ts +164 -0
- package/mdsite-nuxt/scripts/start.ts +55 -0
- package/mdsite-nuxt/scripts/sync-content.ts +504 -0
- package/mdsite-nuxt/tsconfig.json +18 -0
- package/mdsite-nuxt/utils/mdsite-config.ts +240 -0
- package/mdsite-nuxt/vitest.config.ts +7 -0
- package/package.json +36 -0
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import chokidar from 'chokidar'
|
|
4
|
+
import fs from 'fs-extra'
|
|
5
|
+
import path from 'path'
|
|
6
|
+
import { fileURLToPath } from 'url'
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
9
|
+
const __dirname = path.dirname(__filename)
|
|
10
|
+
|
|
11
|
+
const IMAGE_EXTS = ['jpg', 'jpeg', 'png', 'webp', 'gif', 'svg']
|
|
12
|
+
const STATIC_FILES = [
|
|
13
|
+
'favicon.ico',
|
|
14
|
+
'favicon.svg',
|
|
15
|
+
'apple-touch-icon.png',
|
|
16
|
+
'icon-192.png',
|
|
17
|
+
'icon-512.png',
|
|
18
|
+
'site.webmanifest',
|
|
19
|
+
'robots.txt'
|
|
20
|
+
]
|
|
21
|
+
const MENU_FILES = ['_menu.yml', '_menu.yaml']
|
|
22
|
+
const LOGO_FILE = 'logo.svg'
|
|
23
|
+
const FAVICON_DIR = 'favicon'
|
|
24
|
+
const FAVICON_FILES = [
|
|
25
|
+
'favicon.svg',
|
|
26
|
+
'favicon.ico',
|
|
27
|
+
'apple-touch-icon.png',
|
|
28
|
+
'icon-192.png',
|
|
29
|
+
'icon-512.png'
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
// Debounce timers for JSON regeneration (5 second delay)
|
|
33
|
+
let navigationDebounceTimer: NodeJS.Timeout | null = null
|
|
34
|
+
let searchDebounceTimer: NodeJS.Timeout | null = null
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Get content domain from environment variable (read at runtime, not import time)
|
|
38
|
+
*/
|
|
39
|
+
function getContentDomain(): string {
|
|
40
|
+
return process.env.CONTENT || 'cms'
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get source directory for content domain
|
|
45
|
+
*/
|
|
46
|
+
function getSourceDir(): string {
|
|
47
|
+
if (process.env.CONTENT_DIR) {
|
|
48
|
+
return process.env.CONTENT_DIR
|
|
49
|
+
}
|
|
50
|
+
return path.resolve(__dirname, '..', '../../md-content', getContentDomain())
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Get target public directory
|
|
55
|
+
*/
|
|
56
|
+
function getTargetDir(): string {
|
|
57
|
+
return path.resolve(__dirname, '..', 'public')
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Regenerate navigation JSON with debouncing (5 second delay)
|
|
62
|
+
*/
|
|
63
|
+
function regenerateNavigation() {
|
|
64
|
+
if (navigationDebounceTimer) {
|
|
65
|
+
clearTimeout(navigationDebounceTimer)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
navigationDebounceTimer = setTimeout(async () => {
|
|
69
|
+
console.log('🔄 Regenerating navigation tree...')
|
|
70
|
+
try {
|
|
71
|
+
const { generateNavigationJson } = await import('./generate-indices.js')
|
|
72
|
+
await generateNavigationJson()
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.error('❌ Failed to regenerate navigation:', error)
|
|
75
|
+
}
|
|
76
|
+
navigationDebounceTimer = null
|
|
77
|
+
}, 5000)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Regenerate search index JSON with debouncing (5 second delay)
|
|
82
|
+
*/
|
|
83
|
+
function regenerateSearchIndex() {
|
|
84
|
+
if (searchDebounceTimer) {
|
|
85
|
+
clearTimeout(searchDebounceTimer)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
searchDebounceTimer = setTimeout(async () => {
|
|
89
|
+
console.log('🔄 Regenerating search index...')
|
|
90
|
+
try {
|
|
91
|
+
const { generateSearchIndexJson } = await import('./generate-indices.js')
|
|
92
|
+
await generateSearchIndexJson()
|
|
93
|
+
} catch (error) {
|
|
94
|
+
console.error('❌ Failed to regenerate search index:', error)
|
|
95
|
+
}
|
|
96
|
+
searchDebounceTimer = null
|
|
97
|
+
}, 5000)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Generate navigation and search JSON files (one-time on startup)
|
|
102
|
+
*/
|
|
103
|
+
export async function generateJsonFiles() {
|
|
104
|
+
console.log('🔨 Generating JSON files...')
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const { generateNavigationJson } = await import('./generate-indices.js')
|
|
108
|
+
await generateNavigationJson()
|
|
109
|
+
} catch (error) {
|
|
110
|
+
console.error('❌ Failed to generate navigation:', error)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
const { generateSearchIndexJson } = await import('./generate-indices.js')
|
|
115
|
+
await generateSearchIndexJson()
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.error('❌ Failed to generate search index:', error)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Copy all images, menus, and favicons from content to public directory (one-time)
|
|
123
|
+
*/
|
|
124
|
+
export async function copyAllImages() {
|
|
125
|
+
const sourceDir = getSourceDir()
|
|
126
|
+
const targetDir = getTargetDir()
|
|
127
|
+
|
|
128
|
+
console.log(`📦 Copying images, menus, and favicons from: ${sourceDir}`)
|
|
129
|
+
console.log(`📦 Target directory: ${targetDir}`)
|
|
130
|
+
|
|
131
|
+
let copiedCount = 0
|
|
132
|
+
|
|
133
|
+
// Copy images (excluding logo.svg and favicon directory - handled separately)
|
|
134
|
+
for (const ext of IMAGE_EXTS) {
|
|
135
|
+
const files = await getAllFiles(sourceDir, ext)
|
|
136
|
+
|
|
137
|
+
for (const sourcePath of files) {
|
|
138
|
+
|
|
139
|
+
// Skip files in favicon directory - handled separately
|
|
140
|
+
if (sourcePath.includes(`${path.sep}${FAVICON_DIR}${path.sep}`)) {
|
|
141
|
+
continue
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
await copyImage(sourcePath, false)
|
|
145
|
+
copiedCount++
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Copy root menu file (check all supported names)
|
|
150
|
+
for (const menuFile of MENU_FILES) {
|
|
151
|
+
const rootMenuPath = path.join(sourceDir, menuFile)
|
|
152
|
+
if (await fs.pathExists(rootMenuPath)) {
|
|
153
|
+
await copyMenuFile(rootMenuPath, false)
|
|
154
|
+
copiedCount++
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Copy favicon files
|
|
159
|
+
const faviconDir = path.join(sourceDir, FAVICON_DIR)
|
|
160
|
+
if (await fs.pathExists(faviconDir)) {
|
|
161
|
+
for (const faviconFile of FAVICON_FILES) {
|
|
162
|
+
const sourcePath = path.join(faviconDir, faviconFile)
|
|
163
|
+
if (await fs.pathExists(sourcePath)) {
|
|
164
|
+
await copyFaviconFile(sourcePath, false)
|
|
165
|
+
copiedCount++
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
console.log(`✓ Copied ${copiedCount} file(s)\n`)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Clean public directory except static files
|
|
175
|
+
*/
|
|
176
|
+
export async function cleanPublicDirectory() {
|
|
177
|
+
const targetDir = getTargetDir()
|
|
178
|
+
|
|
179
|
+
console.log(`🧹 Cleaning public directory...`)
|
|
180
|
+
|
|
181
|
+
if (!await fs.pathExists(targetDir)) {
|
|
182
|
+
return
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const items = await fs.readdir(targetDir)
|
|
186
|
+
|
|
187
|
+
for (const item of items) {
|
|
188
|
+
if (!STATIC_FILES.includes(item)) {
|
|
189
|
+
const itemPath = path.join(targetDir, item)
|
|
190
|
+
await fs.remove(itemPath)
|
|
191
|
+
console.log(` ✗ Removed: ${item}`)
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
console.log(`✓ Public directory cleaned\n`)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Watch images, markdown files, and menu files in content directory
|
|
200
|
+
*/
|
|
201
|
+
export async function startWatcher() {
|
|
202
|
+
const sourceDir = getSourceDir()
|
|
203
|
+
const targetDir = getTargetDir()
|
|
204
|
+
|
|
205
|
+
// Clean public directory first (handles domain switching)
|
|
206
|
+
await cleanPublicDirectory()
|
|
207
|
+
|
|
208
|
+
// Copy all existing files BEFORE starting watcher (ensures files are ready)
|
|
209
|
+
await copyAllImages()
|
|
210
|
+
|
|
211
|
+
// Generate navigation and search JSON files
|
|
212
|
+
await generateJsonFiles()
|
|
213
|
+
|
|
214
|
+
console.log(`👀 Watching images, markdown, and menus in: ${sourceDir}`)
|
|
215
|
+
console.log(`👀 Target directory: ${targetDir}\n`)
|
|
216
|
+
|
|
217
|
+
const patterns = [
|
|
218
|
+
...IMAGE_EXTS.map(ext => `${sourceDir}/**/*.${ext}`),
|
|
219
|
+
`${sourceDir}/**/*.md`, // Watch all markdown files
|
|
220
|
+
...MENU_FILES.map(f => `${sourceDir}/${f}`) // Watch root menu files
|
|
221
|
+
]
|
|
222
|
+
|
|
223
|
+
const watcher = chokidar.watch(patterns, {
|
|
224
|
+
persistent: true,
|
|
225
|
+
ignoreInitial: true, // Files already copied above
|
|
226
|
+
awaitWriteFinish: {
|
|
227
|
+
stabilityThreshold: 500,
|
|
228
|
+
pollInterval: 100
|
|
229
|
+
}
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
watcher
|
|
233
|
+
.on('add', (filePath) => {
|
|
234
|
+
const fileName = path.basename(filePath)
|
|
235
|
+
if (MENU_FILES.includes(fileName)) {
|
|
236
|
+
copyMenuFile(filePath, true, 'added')
|
|
237
|
+
regenerateNavigation() // Menu change affects navigation
|
|
238
|
+
} else if (fileName === LOGO_FILE) {
|
|
239
|
+
handleLogoChange(filePath, 'added')
|
|
240
|
+
} else if (fileName.endsWith('.md')) {
|
|
241
|
+
// Markdown file added - regenerate both navigation and search
|
|
242
|
+
console.log(`📝 Markdown added: ${fileName}`)
|
|
243
|
+
regenerateNavigation()
|
|
244
|
+
regenerateSearchIndex()
|
|
245
|
+
} else {
|
|
246
|
+
copyImage(filePath, true, 'added')
|
|
247
|
+
}
|
|
248
|
+
})
|
|
249
|
+
.on('change', (filePath) => {
|
|
250
|
+
const fileName = path.basename(filePath)
|
|
251
|
+
if (MENU_FILES.includes(fileName)) {
|
|
252
|
+
copyMenuFile(filePath, true, 'updated')
|
|
253
|
+
regenerateNavigation() // Menu change affects navigation
|
|
254
|
+
} else if (fileName === LOGO_FILE) {
|
|
255
|
+
handleLogoChange(filePath, 'updated')
|
|
256
|
+
} else if (fileName.endsWith('.md')) {
|
|
257
|
+
// Markdown file changed - regenerate both navigation and search
|
|
258
|
+
console.log(`📝 Markdown updated: ${fileName}`)
|
|
259
|
+
regenerateNavigation()
|
|
260
|
+
regenerateSearchIndex()
|
|
261
|
+
} else {
|
|
262
|
+
copyImage(filePath, true, 'updated')
|
|
263
|
+
}
|
|
264
|
+
})
|
|
265
|
+
.on('unlink', (filePath) => {
|
|
266
|
+
const fileName = path.basename(filePath)
|
|
267
|
+
if (MENU_FILES.includes(fileName)) {
|
|
268
|
+
deleteMenuFile(filePath)
|
|
269
|
+
regenerateNavigation() // Menu deletion affects navigation
|
|
270
|
+
} else if (fileName === LOGO_FILE) {
|
|
271
|
+
console.log(`🗑️ Logo removed: ${fileName}`)
|
|
272
|
+
} else if (fileName.endsWith('.md')) {
|
|
273
|
+
// Markdown file deleted - regenerate both navigation and search
|
|
274
|
+
console.log(`📝 Markdown deleted: ${fileName}`)
|
|
275
|
+
regenerateNavigation()
|
|
276
|
+
regenerateSearchIndex()
|
|
277
|
+
} else {
|
|
278
|
+
deleteImage(filePath)
|
|
279
|
+
}
|
|
280
|
+
})
|
|
281
|
+
.on('error', (error) => console.error(`❌ Watcher error: ${error}`))
|
|
282
|
+
|
|
283
|
+
return watcher
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* One-time sync for production builds
|
|
288
|
+
*/
|
|
289
|
+
export async function syncContent() {
|
|
290
|
+
await cleanPublicDirectory()
|
|
291
|
+
await copyAllImages()
|
|
292
|
+
await generateJsonFiles()
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Transform content path to public path (strips domain prefix)
|
|
297
|
+
* Example: /content/kingdom/church/image.jpg → /public/church/image.jpg
|
|
298
|
+
*/
|
|
299
|
+
function getPublicPath(sourcePath: string): { relativePath: string, targetPath: string } {
|
|
300
|
+
const sourceDir = getSourceDir()
|
|
301
|
+
const targetDir = getTargetDir()
|
|
302
|
+
// With generic sourceDir, we just calculate relative path
|
|
303
|
+
const relativeFromSource = path.relative(sourceDir, sourcePath)
|
|
304
|
+
const targetPath = path.join(targetDir, relativeFromSource)
|
|
305
|
+
|
|
306
|
+
return { relativePath: relativeFromSource, targetPath }
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Copy a single file from content to public (images or menus)
|
|
311
|
+
*/
|
|
312
|
+
async function copyFile(
|
|
313
|
+
sourcePath: string,
|
|
314
|
+
fileType: 'Image' | 'Menu',
|
|
315
|
+
log: boolean = true,
|
|
316
|
+
action: string = 'copied'
|
|
317
|
+
) {
|
|
318
|
+
try {
|
|
319
|
+
// Check if this is a draft-only image (skip menus, they don't have draft checks)
|
|
320
|
+
if (fileType === 'Image' && await isDraftOnlyImage(sourcePath)) {
|
|
321
|
+
if (log) {
|
|
322
|
+
const fileName = path.basename(sourcePath)
|
|
323
|
+
console.log(`⊗ Skipped draft image: ${fileName}`)
|
|
324
|
+
}
|
|
325
|
+
return
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const { relativePath, targetPath } = getPublicPath(sourcePath)
|
|
329
|
+
|
|
330
|
+
await fs.ensureDir(path.dirname(targetPath))
|
|
331
|
+
await fs.copy(sourcePath, targetPath)
|
|
332
|
+
|
|
333
|
+
if (log) {
|
|
334
|
+
console.log(`✓ ${fileType} ${action}: ${relativePath}`)
|
|
335
|
+
}
|
|
336
|
+
} catch (error) {
|
|
337
|
+
console.error(`❌ Failed to copy ${sourcePath}:`, error)
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Delete a single file from public directory (images or menus)
|
|
343
|
+
*/
|
|
344
|
+
async function deleteFile(sourcePath: string, fileType: 'Image' | 'Menu') {
|
|
345
|
+
try {
|
|
346
|
+
// Check if this was a draft-only image (skip menus)
|
|
347
|
+
if (fileType === 'Image' && await isDraftOnlyImage(sourcePath)) {
|
|
348
|
+
const fileName = path.basename(sourcePath)
|
|
349
|
+
console.log(`⊗ Draft image removed from content: ${fileName}`)
|
|
350
|
+
return
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const { relativePath, targetPath } = getPublicPath(sourcePath)
|
|
354
|
+
|
|
355
|
+
if (await fs.pathExists(targetPath)) {
|
|
356
|
+
await fs.remove(targetPath)
|
|
357
|
+
console.log(`✗ ${fileType} deleted: ${relativePath}`)
|
|
358
|
+
}
|
|
359
|
+
} catch (error) {
|
|
360
|
+
console.error(`❌ Failed to delete ${sourcePath}:`, error)
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Copy a single image from content to public
|
|
366
|
+
*/
|
|
367
|
+
async function copyImage(sourcePath: string, log: boolean = true, action: string = 'copied') {
|
|
368
|
+
return copyFile(sourcePath, 'Image', log, action)
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Delete image from public directory
|
|
373
|
+
*/
|
|
374
|
+
async function deleteImage(sourcePath: string) {
|
|
375
|
+
return deleteFile(sourcePath, 'Image')
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Copy a single _menu.yml file from content to public
|
|
380
|
+
*/
|
|
381
|
+
async function copyMenuFile(sourcePath: string, log: boolean = true, action: string = 'copied') {
|
|
382
|
+
return copyFile(sourcePath, 'Menu', log, action)
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Delete _menu.yml file from public directory
|
|
387
|
+
*/
|
|
388
|
+
async function deleteMenuFile(sourcePath: string) {
|
|
389
|
+
return deleteFile(sourcePath, 'Menu')
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Check if an image belongs to a draft-only page (no published .md exists)
|
|
394
|
+
*/
|
|
395
|
+
async function isDraftOnlyImage(imagePath: string): Promise<boolean> {
|
|
396
|
+
const ext = path.extname(imagePath)
|
|
397
|
+
const fileName = path.basename(imagePath, ext)
|
|
398
|
+
|
|
399
|
+
// Extract page prefix from image name (e.g., "something.pic.jpg" → "something")
|
|
400
|
+
const parts = fileName.split('.')
|
|
401
|
+
if (parts.length < 2) {
|
|
402
|
+
// Single-part filename (no prefix) - assume it's a shared image, copy it
|
|
403
|
+
return false
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const pagePrefix = parts[0]
|
|
407
|
+
const imageDir = path.dirname(imagePath)
|
|
408
|
+
|
|
409
|
+
// Check if corresponding published .md file exists
|
|
410
|
+
const publishedMdPath = path.join(imageDir, `${pagePrefix}.md`)
|
|
411
|
+
const hasPublishedVersion = await fs.pathExists(publishedMdPath)
|
|
412
|
+
|
|
413
|
+
// Check if only draft version exists
|
|
414
|
+
const draftMdPath = path.join(imageDir, `${pagePrefix}.draft.md`)
|
|
415
|
+
const hasDraftVersion = await fs.pathExists(draftMdPath)
|
|
416
|
+
|
|
417
|
+
// Skip copying if only draft exists (no published version)
|
|
418
|
+
return !hasPublishedVersion && hasDraftVersion
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Copy a single favicon file from content to public
|
|
423
|
+
*/
|
|
424
|
+
async function copyFaviconFile(sourcePath: string, log: boolean = true, action: string = 'copied') {
|
|
425
|
+
try {
|
|
426
|
+
const targetDir = getTargetDir()
|
|
427
|
+
const fileName = path.basename(sourcePath)
|
|
428
|
+
const targetPath = path.join(targetDir, fileName)
|
|
429
|
+
|
|
430
|
+
await fs.ensureDir(targetDir)
|
|
431
|
+
await fs.copy(sourcePath, targetPath)
|
|
432
|
+
|
|
433
|
+
if (log) {
|
|
434
|
+
console.log(`✓ Favicon ${action}: ${fileName}`)
|
|
435
|
+
}
|
|
436
|
+
} catch (error) {
|
|
437
|
+
console.error(`❌ Failed to copy favicon ${sourcePath}:`, error)
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Handle logo.svg changes - regenerate favicons
|
|
443
|
+
*/
|
|
444
|
+
async function handleLogoChange(logoPath: string, action: string) {
|
|
445
|
+
const domain = getContentDomain()
|
|
446
|
+
console.log(`🎨 Logo ${action}, regenerating favicons for ${domain}...`)
|
|
447
|
+
|
|
448
|
+
try {
|
|
449
|
+
const { generateFavicons, copyFaviconsToPublic } = await import('./generate-favicons.js')
|
|
450
|
+
const success = await generateFavicons(domain)
|
|
451
|
+
|
|
452
|
+
if (success) {
|
|
453
|
+
await copyFaviconsToPublic(domain)
|
|
454
|
+
console.log(`✓ Favicons regenerated for ${domain}\n`)
|
|
455
|
+
}
|
|
456
|
+
} catch (error) {
|
|
457
|
+
console.error(`❌ Failed to regenerate favicons:`, error)
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Get all files with specific extension recursively
|
|
463
|
+
*/
|
|
464
|
+
async function getAllFiles(dir: string, ext: string): Promise<string[]> {
|
|
465
|
+
const files: string[] = []
|
|
466
|
+
|
|
467
|
+
if (!await fs.pathExists(dir)) {
|
|
468
|
+
return files
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const items = await fs.readdir(dir)
|
|
472
|
+
|
|
473
|
+
for (const item of items) {
|
|
474
|
+
const itemPath = path.join(dir, item)
|
|
475
|
+
const stat = await fs.stat(itemPath)
|
|
476
|
+
|
|
477
|
+
if (stat.isDirectory()) {
|
|
478
|
+
const subFiles = await getAllFiles(itemPath, ext)
|
|
479
|
+
files.push(...subFiles)
|
|
480
|
+
} else if (item.toLowerCase().endsWith(`.${ext}`)) {
|
|
481
|
+
files.push(itemPath)
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
return files
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// CLI Entry Point
|
|
489
|
+
if (process.argv[1] === __filename) {
|
|
490
|
+
const args = process.argv.slice(2)
|
|
491
|
+
|
|
492
|
+
; (async () => {
|
|
493
|
+
if (args.includes('--copy')) {
|
|
494
|
+
await copyAllImages()
|
|
495
|
+
} else if (args.includes('--sync')) {
|
|
496
|
+
await syncContent()
|
|
497
|
+
} else if (args.includes('--watch')) {
|
|
498
|
+
await startWatcher()
|
|
499
|
+
}
|
|
500
|
+
})().catch((error) => {
|
|
501
|
+
console.error(error)
|
|
502
|
+
process.exit(1)
|
|
503
|
+
})
|
|
504
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
// https://nuxt.com/docs/guide/concepts/typescript
|
|
3
|
+
"files": [],
|
|
4
|
+
"references": [
|
|
5
|
+
{
|
|
6
|
+
"path": "./.nuxt/tsconfig.app.json"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"path": "./.nuxt/tsconfig.server.json"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"path": "./.nuxt/tsconfig.shared.json"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"path": "./.nuxt/tsconfig.node.json"
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|