@docsector/docsector-reader 1.7.1 → 2.0.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/.eslintrc.cjs +2 -0
- package/README.md +91 -9
- package/bin/docsector.js +27 -13
- package/package.json +1 -1
- package/src/components/DH1.vue +2 -1
- package/src/components/DMenu.vue +70 -30
- package/src/components/DMenuItem.vue +12 -6
- package/src/components/DPage.vue +124 -31
- package/src/components/DPageAnchor.vue +13 -1
- package/src/components/DPageBar.vue +2 -1
- package/src/components/DPageMeta.vue +9 -4
- package/src/components/DPageSection.vue +2 -1
- package/src/composables/useWebMcp.js +2 -1
- package/src/i18n/helpers.js +36 -9
- package/src/i18n/index.js +2 -2
- package/src/i18n/path.js +101 -0
- package/src/index.js +25 -2
- package/src/layouts/DefaultLayout.vue +181 -2
- package/src/pages/guide/getting-started.overview.en-US.md +3 -2
- package/src/pages/guide/getting-started.overview.pt-BR.md +3 -2
- package/src/pages/guide/pages-and-routing.overview.en-US.md +6 -6
- package/src/pages/guide/pages-and-routing.overview.pt-BR.md +6 -6
- package/src/pages/guide.book.js +12 -0
- package/src/pages/guide.index.js +184 -0
- package/src/pages/manual.book.js +12 -0
- package/src/pages/{index.js → manual.index.js} +13 -216
- package/src/quasar.factory.js +398 -56
- package/src/router/routes.js +132 -46
package/src/quasar.factory.js
CHANGED
|
@@ -61,33 +61,370 @@ function getPackageRoot (projectRoot) {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
/**
|
|
64
|
-
*
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
* Normalize paths for cross-platform file matching.
|
|
65
|
+
*/
|
|
66
|
+
function normalizePathForMatch (path) {
|
|
67
|
+
return String(path || '').replace(/\\/g, '/')
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* List top-level page registry definition files.
|
|
72
|
+
*
|
|
73
|
+
* Includes:
|
|
74
|
+
* - src/pages/index.js (legacy)
|
|
75
|
+
* - src/pages/*.book.js
|
|
76
|
+
* - src/pages/*.index.js
|
|
77
|
+
*/
|
|
78
|
+
function getPagesRegistryFiles (projectRoot) {
|
|
79
|
+
const pagesDir = resolve(projectRoot, 'src', 'pages')
|
|
80
|
+
if (!existsSync(pagesDir)) return []
|
|
81
|
+
|
|
82
|
+
const names = readdirSync(pagesDir, { withFileTypes: true })
|
|
83
|
+
.filter(entry => entry.isFile())
|
|
84
|
+
.map(entry => entry.name)
|
|
85
|
+
|
|
86
|
+
return names
|
|
87
|
+
.filter(name => {
|
|
88
|
+
if (name === 'index.js') return true
|
|
89
|
+
return /^[^/]+\.book\.js$/.test(name) || /^[^/]+\.index\.js$/.test(name)
|
|
90
|
+
})
|
|
91
|
+
.sort()
|
|
92
|
+
.map(name => resolve(pagesDir, name))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Discover configured books from src/pages/*.book.js paired with *.index.js.
|
|
97
|
+
*/
|
|
98
|
+
function getBookRegistryEntries (projectRoot) {
|
|
99
|
+
const pagesDir = resolve(projectRoot, 'src', 'pages')
|
|
100
|
+
if (!existsSync(pagesDir)) return []
|
|
101
|
+
|
|
102
|
+
const names = readdirSync(pagesDir, { withFileTypes: true })
|
|
103
|
+
.filter(entry => entry.isFile())
|
|
104
|
+
.map(entry => entry.name)
|
|
105
|
+
|
|
106
|
+
const books = names
|
|
107
|
+
.filter(name => /^[^/]+\.book\.js$/.test(name))
|
|
108
|
+
.sort()
|
|
109
|
+
|
|
110
|
+
const entries = []
|
|
111
|
+
for (const bookFile of books) {
|
|
112
|
+
const baseName = bookFile.slice(0, -'.book.js'.length)
|
|
113
|
+
const indexFile = `${baseName}.index.js`
|
|
114
|
+
if (!names.includes(indexFile)) continue
|
|
115
|
+
|
|
116
|
+
entries.push({
|
|
117
|
+
id: baseName,
|
|
118
|
+
bookFile,
|
|
119
|
+
indexFile,
|
|
120
|
+
bookPath: resolve(pagesDir, bookFile),
|
|
121
|
+
indexPath: resolve(pagesDir, indexFile)
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return entries
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Resolve book identifier from page config with legacy fallback support.
|
|
130
|
+
*/
|
|
131
|
+
function resolvePageBook (config, fallbackBook = 'manual') {
|
|
132
|
+
if (!config || typeof config !== 'object') return fallbackBook
|
|
133
|
+
return config.book ?? config.type ?? fallbackBook
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const DEFAULT_BOOK_COLORS = Object.freeze({
|
|
137
|
+
active: 'white',
|
|
138
|
+
inactive: 'rgba(255, 255, 255, 0.72)'
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
function normalizeBookColorConfig (rawColor) {
|
|
142
|
+
if (typeof rawColor === 'object' && rawColor !== null && !Array.isArray(rawColor)) {
|
|
143
|
+
const active = typeof rawColor.active === 'string' && rawColor.active.trim().length > 0
|
|
144
|
+
? rawColor.active.trim()
|
|
145
|
+
: DEFAULT_BOOK_COLORS.active
|
|
146
|
+
|
|
147
|
+
const inactive = typeof rawColor.inactive === 'string' && rawColor.inactive.trim().length > 0
|
|
148
|
+
? rawColor.inactive.trim()
|
|
149
|
+
: active
|
|
150
|
+
|
|
151
|
+
return { active, inactive }
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (typeof rawColor === 'string' && rawColor.trim().length > 0) {
|
|
155
|
+
const normalized = rawColor.trim()
|
|
156
|
+
return {
|
|
157
|
+
active: normalized,
|
|
158
|
+
inactive: normalized
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return { ...DEFAULT_BOOK_COLORS }
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Build source code for the virtual module `virtual:docsector-books`.
|
|
167
|
+
*/
|
|
168
|
+
function buildVirtualBooksModule (projectRoot) {
|
|
169
|
+
const bookEntries = getBookRegistryEntries(projectRoot)
|
|
170
|
+
|
|
171
|
+
// Legacy fallback: support projects that still define src/pages/index.js only.
|
|
172
|
+
if (bookEntries.length === 0) {
|
|
173
|
+
return `import legacyPages from 'pages'
|
|
174
|
+
|
|
175
|
+
const defaultBook = {
|
|
176
|
+
id: 'manual',
|
|
177
|
+
label: 'Manual',
|
|
178
|
+
icon: 'menu_book',
|
|
179
|
+
order: 1,
|
|
180
|
+
color: {
|
|
181
|
+
active: 'white',
|
|
182
|
+
inactive: 'rgba(255, 255, 255, 0.72)'
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const normalizedPages = legacyPages || {}
|
|
187
|
+
|
|
188
|
+
export const books = {
|
|
189
|
+
manual: {
|
|
190
|
+
config: defaultBook,
|
|
191
|
+
routes: normalizedPages
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export const allBooks = [defaultBook]
|
|
196
|
+
export const allPages = normalizedPages
|
|
197
|
+
|
|
198
|
+
export default books
|
|
199
|
+
`
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const imports = []
|
|
203
|
+
const rows = []
|
|
204
|
+
|
|
205
|
+
for (const [index, entry] of bookEntries.entries()) {
|
|
206
|
+
imports.push(`import __book_${index} from 'pages/${entry.bookFile}'`)
|
|
207
|
+
imports.push(`import __routes_${index} from 'pages/${entry.indexFile}'`)
|
|
208
|
+
rows.push(` { fallbackId: ${JSON.stringify(entry.id)}, config: __book_${index}, routes: __routes_${index} }`)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return `${imports.join('\n')}
|
|
212
|
+
|
|
213
|
+
const entries = [
|
|
214
|
+
${rows.join(',\n')}
|
|
215
|
+
]
|
|
216
|
+
|
|
217
|
+
const DEFAULT_BOOK_COLORS = Object.freeze({
|
|
218
|
+
active: 'white',
|
|
219
|
+
inactive: 'rgba(255, 255, 255, 0.72)'
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
const normalizeBookColor = (rawColor) => {
|
|
223
|
+
if (typeof rawColor === 'object' && rawColor !== null && !Array.isArray(rawColor)) {
|
|
224
|
+
const active = typeof rawColor.active === 'string' && rawColor.active.trim().length > 0
|
|
225
|
+
? rawColor.active.trim()
|
|
226
|
+
: DEFAULT_BOOK_COLORS.active
|
|
227
|
+
|
|
228
|
+
const inactive = typeof rawColor.inactive === 'string' && rawColor.inactive.trim().length > 0
|
|
229
|
+
? rawColor.inactive.trim()
|
|
230
|
+
: active
|
|
231
|
+
|
|
232
|
+
return { active, inactive }
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (typeof rawColor === 'string' && rawColor.trim().length > 0) {
|
|
236
|
+
const normalized = rawColor.trim()
|
|
237
|
+
return {
|
|
238
|
+
active: normalized,
|
|
239
|
+
inactive: normalized
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return { ...DEFAULT_BOOK_COLORS }
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export const books = entries.reduce((accumulator, entry, index) => {
|
|
247
|
+
const config = entry.config || {}
|
|
248
|
+
const resolvedId = config.id || entry.fallbackId || ('book-' + (index + 1))
|
|
249
|
+
const label = config.label || (resolvedId.charAt(0).toUpperCase() + resolvedId.slice(1))
|
|
250
|
+
const normalizedConfig = {
|
|
251
|
+
...config,
|
|
252
|
+
id: resolvedId,
|
|
253
|
+
label,
|
|
254
|
+
icon: config.icon || 'menu_book',
|
|
255
|
+
order: config.order ?? (index + 1),
|
|
256
|
+
color: normalizeBookColor(config.color)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
accumulator[resolvedId] = {
|
|
260
|
+
config: normalizedConfig,
|
|
261
|
+
routes: entry.routes || {}
|
|
262
|
+
}
|
|
263
|
+
return accumulator
|
|
264
|
+
}, {})
|
|
265
|
+
|
|
266
|
+
export const allBooks = Object.values(books).map(book => book.config)
|
|
267
|
+
export const allPages = Object.values(books).reduce((accumulator, book) => {
|
|
268
|
+
return {
|
|
269
|
+
...accumulator,
|
|
270
|
+
...(book.routes || {})
|
|
271
|
+
}
|
|
272
|
+
}, {})
|
|
273
|
+
|
|
274
|
+
export default books
|
|
275
|
+
`
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Load books and merged pages for build-time plugins (Node context).
|
|
280
|
+
*/
|
|
281
|
+
async function loadBooksRegistry (projectRoot) {
|
|
282
|
+
const entries = getBookRegistryEntries(projectRoot)
|
|
283
|
+
|
|
284
|
+
// Legacy fallback
|
|
285
|
+
if (entries.length === 0) {
|
|
286
|
+
const legacyPath = resolve(projectRoot, 'src', 'pages', 'index.js')
|
|
287
|
+
const pages = existsSync(legacyPath)
|
|
288
|
+
? ((await import(pathToFileURL(legacyPath).href)).default || {})
|
|
289
|
+
: {}
|
|
290
|
+
|
|
291
|
+
const defaultBook = {
|
|
292
|
+
id: 'manual',
|
|
293
|
+
label: 'Manual',
|
|
294
|
+
icon: 'menu_book',
|
|
295
|
+
order: 1,
|
|
296
|
+
color: {
|
|
297
|
+
active: 'white',
|
|
298
|
+
inactive: 'rgba(255, 255, 255, 0.72)'
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return {
|
|
303
|
+
books: {
|
|
304
|
+
manual: {
|
|
305
|
+
config: defaultBook,
|
|
306
|
+
routes: pages
|
|
307
|
+
}
|
|
308
|
+
},
|
|
309
|
+
allBooks: [defaultBook],
|
|
310
|
+
allPages: pages
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const books = {}
|
|
315
|
+
const allPages = {}
|
|
316
|
+
|
|
317
|
+
for (const [index, entry] of entries.entries()) {
|
|
318
|
+
const { default: rawConfig = {} } = await import(pathToFileURL(entry.bookPath).href)
|
|
319
|
+
const { default: routes = {} } = await import(pathToFileURL(entry.indexPath).href)
|
|
320
|
+
|
|
321
|
+
const resolvedId = rawConfig.id || entry.id || `book-${index + 1}`
|
|
322
|
+
const label = rawConfig.label || (resolvedId.charAt(0).toUpperCase() + resolvedId.slice(1))
|
|
323
|
+
|
|
324
|
+
const config = {
|
|
325
|
+
...rawConfig,
|
|
326
|
+
id: resolvedId,
|
|
327
|
+
label,
|
|
328
|
+
icon: rawConfig.icon || 'menu_book',
|
|
329
|
+
order: rawConfig.order ?? (index + 1),
|
|
330
|
+
color: normalizeBookColorConfig(rawConfig.color)
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
books[resolvedId] = {
|
|
334
|
+
config,
|
|
335
|
+
routes
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
Object.assign(allPages, routes || {})
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const allBooks = Object.values(books).map(book => book.config)
|
|
342
|
+
|
|
343
|
+
return {
|
|
344
|
+
books,
|
|
345
|
+
allBooks,
|
|
346
|
+
allPages
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Return page entries while preserving the book that contributed each route.
|
|
352
|
+
*
|
|
353
|
+
* Why: `allPages` is a legacy flattened registry and cannot represent two
|
|
354
|
+
* books that reuse the same route key, such as `/getting-started` in both
|
|
355
|
+
* `guide.index.js` and `manual.index.js`. Build artifacts that need concrete
|
|
356
|
+
* URLs must iterate per book instead.
|
|
357
|
+
*/
|
|
358
|
+
function getBookPageEntries (books = {}) {
|
|
359
|
+
const pageEntries = []
|
|
360
|
+
|
|
361
|
+
for (const [bookId, book] of Object.entries(books || {})) {
|
|
362
|
+
const fallbackBook = book?.config?.id || bookId || 'manual'
|
|
363
|
+
|
|
364
|
+
for (const [pagePath, page] of Object.entries(book?.routes || {})) {
|
|
365
|
+
pageEntries.push({
|
|
366
|
+
book: resolvePageBook(page?.config, fallbackBook),
|
|
367
|
+
pagePath,
|
|
368
|
+
page
|
|
369
|
+
})
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return pageEntries
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Check if a file path is a book registry definition file.
|
|
378
|
+
*/
|
|
379
|
+
function isPagesRegistryFile (projectRoot, changedPath) {
|
|
380
|
+
const pagesDir = normalizePathForMatch(resolve(projectRoot, 'src', 'pages'))
|
|
381
|
+
const normalizedPath = normalizePathForMatch(changedPath)
|
|
382
|
+
const prefix = `${pagesDir}/`
|
|
383
|
+
|
|
384
|
+
if (!normalizedPath.startsWith(prefix)) return false
|
|
385
|
+
const relativePath = normalizedPath.slice(prefix.length)
|
|
386
|
+
|
|
387
|
+
if (relativePath === 'index.js') return true
|
|
388
|
+
return /^[^/]+\.book\.js$/.test(relativePath) || /^[^/]+\.index\.js$/.test(relativePath)
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Create a Vite plugin that exposes discovered books through
|
|
393
|
+
* `virtual:docsector-books` and restarts dev server when definitions change.
|
|
67
394
|
*
|
|
68
395
|
* Why: The router module (`routes.js`) imports consumer content via the
|
|
69
396
|
* `pages` alias. Vite's dep optimizer pre-bundles the router with the
|
|
70
397
|
* consumer content inlined, but the optimizer cache hash is based on config
|
|
71
|
-
* and lockfile only — NOT on consumer source files. So when
|
|
72
|
-
*
|
|
73
|
-
* until the cache is manually cleared.
|
|
74
|
-
*
|
|
75
|
-
* Fix: When pages/index.js changes, the watcher plugin clears the dep cache,
|
|
76
|
-
* sets an env flag, and restarts the server. On restart, the config reads the
|
|
77
|
-
* flag and sets `optimizeDeps.force = true`, which makes Vite generate a new
|
|
78
|
-
* browserHash — effectively busting the browser module cache.
|
|
398
|
+
* and lockfile only — NOT on consumer source files. So when page registries
|
|
399
|
+
* change during development, the optimizer can serve stale pre-bundled code.
|
|
79
400
|
*/
|
|
80
|
-
function
|
|
81
|
-
const
|
|
401
|
+
function createBooksPlugin (projectRoot) {
|
|
402
|
+
const virtualId = 'virtual:docsector-books'
|
|
403
|
+
const resolvedId = '\0' + virtualId
|
|
404
|
+
|
|
82
405
|
return {
|
|
83
|
-
name: 'docsector-
|
|
406
|
+
name: 'docsector-books',
|
|
407
|
+
resolveId (id) {
|
|
408
|
+
if (id === virtualId) return resolvedId
|
|
409
|
+
},
|
|
410
|
+
load (id) {
|
|
411
|
+
if (id !== resolvedId) return null
|
|
412
|
+
return buildVirtualBooksModule(projectRoot)
|
|
413
|
+
},
|
|
84
414
|
configureServer (server) {
|
|
85
|
-
|
|
86
|
-
if (changedPath
|
|
415
|
+
const onPagesRegistryChange = (changedPath) => {
|
|
416
|
+
if (isPagesRegistryFile(projectRoot, changedPath)) {
|
|
87
417
|
server.config.logger.info(
|
|
88
|
-
|
|
418
|
+
`\\x1b[36m[docsector]\\x1b[0m pages registry changed (${changedPath}) — clearing dep cache and restarting...`,
|
|
89
419
|
{ timestamp: true }
|
|
90
420
|
)
|
|
421
|
+
|
|
422
|
+
// Invalidate virtual module before restart
|
|
423
|
+
const module = server.moduleGraph.getModuleById(resolvedId)
|
|
424
|
+
if (module) {
|
|
425
|
+
server.moduleGraph.invalidateModule(module)
|
|
426
|
+
}
|
|
427
|
+
|
|
91
428
|
// Signal the restarted config to force a new optimizer hash
|
|
92
429
|
process.env.__DOCSECTOR_FORCE_OPTIMIZE = '1'
|
|
93
430
|
// Delete the stale optimizer cache
|
|
@@ -95,7 +432,11 @@ function createPagesWatchPlugin (projectRoot) {
|
|
|
95
432
|
rmSync(cacheDir, { recursive: true, force: true })
|
|
96
433
|
server.restart()
|
|
97
434
|
}
|
|
98
|
-
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
server.watcher.on('add', onPagesRegistryChange)
|
|
438
|
+
server.watcher.on('change', onPagesRegistryChange)
|
|
439
|
+
server.watcher.on('unlink', onPagesRegistryChange)
|
|
99
440
|
}
|
|
100
441
|
}
|
|
101
442
|
}
|
|
@@ -145,11 +486,11 @@ function createPrerenderMetaPlugin (projectRoot) {
|
|
|
145
486
|
|
|
146
487
|
const baseHtml = readFileSync(baseHtmlPath, 'utf-8')
|
|
147
488
|
|
|
148
|
-
// Dynamic import
|
|
149
|
-
const pagesUrl = pathToFileURL(resolve(projectRoot, 'src', 'pages', 'index.js')).href
|
|
489
|
+
// Dynamic import books registry and docsector config
|
|
150
490
|
const configUrl = pathToFileURL(resolve(projectRoot, 'docsector.config.js')).href
|
|
151
491
|
|
|
152
|
-
const {
|
|
492
|
+
const { books } = await loadBooksRegistry(projectRoot)
|
|
493
|
+
const pageEntries = getBookPageEntries(books)
|
|
153
494
|
const { default: config } = await import(configUrl)
|
|
154
495
|
|
|
155
496
|
const brandingName = config.branding?.name || ''
|
|
@@ -167,10 +508,9 @@ function createPrerenderMetaPlugin (projectRoot) {
|
|
|
167
508
|
|
|
168
509
|
let count = 0
|
|
169
510
|
|
|
170
|
-
for (const
|
|
511
|
+
for (const { book, pagePath, page } of pageEntries) {
|
|
171
512
|
if (page.config === null) continue
|
|
172
513
|
|
|
173
|
-
const type = page.config.type ?? 'manual'
|
|
174
514
|
const titleData = page.data?.[defaultLang] || page.data?.['*'] || page.data?.['en-US'] || Object.values(page.data || {})[0]
|
|
175
515
|
const title = titleData?.title || ''
|
|
176
516
|
const fullTitle = title
|
|
@@ -186,7 +526,7 @@ function createPrerenderMetaPlugin (projectRoot) {
|
|
|
186
526
|
if (page.config.subpages?.vs) subpages.push('vs')
|
|
187
527
|
|
|
188
528
|
for (const subpage of subpages) {
|
|
189
|
-
const routePath = `${
|
|
529
|
+
const routePath = `${book}${pagePath}/${subpage}`
|
|
190
530
|
|
|
191
531
|
const html = baseHtml
|
|
192
532
|
.replace(/<title>[^<]*<\/title>/, () => `<title>${fullTitle}</title>`)
|
|
@@ -759,29 +1099,27 @@ function createMarkdownBuildPlugin (projectRoot) {
|
|
|
759
1099
|
|
|
760
1100
|
const pagesDir = resolve(projectRoot, 'src', 'pages')
|
|
761
1101
|
const configUrl = pathToFileURL(resolve(projectRoot, 'docsector.config.js')).href
|
|
762
|
-
const pagesUrl = pathToFileURL(resolve(projectRoot, 'src', 'pages', 'index.js')).href
|
|
763
1102
|
|
|
764
1103
|
const { default: config } = await import(configUrl)
|
|
765
|
-
const {
|
|
1104
|
+
const { books } = await loadBooksRegistry(projectRoot)
|
|
1105
|
+
const pageEntries = getBookPageEntries(books)
|
|
766
1106
|
|
|
767
1107
|
const defaultLang = config.defaultLanguage || config.languages?.[0]?.value || 'en-US'
|
|
768
1108
|
let count = 0
|
|
769
1109
|
|
|
770
|
-
for (const
|
|
1110
|
+
for (const { book, pagePath, page } of pageEntries) {
|
|
771
1111
|
if (page.config === null) continue
|
|
772
1112
|
if (page.config.status === 'empty') continue
|
|
773
1113
|
|
|
774
|
-
const type = page.config.type ?? 'manual'
|
|
775
|
-
|
|
776
1114
|
const subpages = ['overview']
|
|
777
1115
|
if (page.config.subpages?.showcase) subpages.push('showcase')
|
|
778
1116
|
if (page.config.subpages?.vs) subpages.push('vs')
|
|
779
1117
|
|
|
780
1118
|
for (const subpage of subpages) {
|
|
781
|
-
const srcFile = resolve(pagesDir, `${
|
|
1119
|
+
const srcFile = resolve(pagesDir, `${book}${pagePath}.${subpage}.${defaultLang}.md`)
|
|
782
1120
|
if (!existsSync(srcFile)) continue
|
|
783
1121
|
|
|
784
|
-
const routePath = `${
|
|
1122
|
+
const routePath = `${book}${pagePath}/${subpage}`
|
|
785
1123
|
const destFile = resolve(distDir, `${routePath}.md`)
|
|
786
1124
|
const destDir = resolve(destFile, '..')
|
|
787
1125
|
|
|
@@ -816,21 +1154,19 @@ function createMarkdownBuildPlugin (projectRoot) {
|
|
|
816
1154
|
const today = new Date().toISOString().split('T')[0]
|
|
817
1155
|
let urls = ''
|
|
818
1156
|
|
|
819
|
-
for (const
|
|
1157
|
+
for (const { book, pagePath, page } of pageEntries) {
|
|
820
1158
|
if (page.config === null) continue
|
|
821
1159
|
if (page.config.status === 'empty') continue
|
|
822
1160
|
|
|
823
|
-
const type = page.config.type ?? 'manual'
|
|
824
|
-
|
|
825
1161
|
const subpages = ['overview']
|
|
826
1162
|
if (page.config.subpages?.showcase) subpages.push('showcase')
|
|
827
1163
|
if (page.config.subpages?.vs) subpages.push('vs')
|
|
828
1164
|
|
|
829
1165
|
for (const subpage of subpages) {
|
|
830
|
-
const srcFile = resolve(pagesDir, `${
|
|
1166
|
+
const srcFile = resolve(pagesDir, `${book}${pagePath}.${subpage}.${defaultLang}.md`)
|
|
831
1167
|
if (!existsSync(srcFile)) continue
|
|
832
1168
|
|
|
833
|
-
const routePath = `/${
|
|
1169
|
+
const routePath = `/${book}${pagePath}/${subpage}`
|
|
834
1170
|
urls += ` <url>\n <loc>${siteUrl}${routePath}</loc>\n <lastmod>${today}</lastmod>\n </url>\n`
|
|
835
1171
|
}
|
|
836
1172
|
}
|
|
@@ -849,11 +1185,10 @@ function createMarkdownBuildPlugin (projectRoot) {
|
|
|
849
1185
|
|
|
850
1186
|
const llmsSections = {}
|
|
851
1187
|
|
|
852
|
-
for (const
|
|
1188
|
+
for (const { book, pagePath, page } of pageEntries) {
|
|
853
1189
|
if (page.config === null) continue
|
|
854
1190
|
if (page.config.status === 'empty') continue
|
|
855
1191
|
|
|
856
|
-
const type = page.config.type ?? 'manual'
|
|
857
1192
|
const title = page.data?.['*']?.title
|
|
858
1193
|
|| page.data?.[defaultLang]?.title
|
|
859
1194
|
|| page.data?.['en-US']?.title
|
|
@@ -865,18 +1200,18 @@ function createMarkdownBuildPlugin (projectRoot) {
|
|
|
865
1200
|
if (page.config.subpages?.vs) subpages.push('vs')
|
|
866
1201
|
|
|
867
1202
|
for (const subpage of subpages) {
|
|
868
|
-
const srcFile = resolve(pagesDir, `${
|
|
1203
|
+
const srcFile = resolve(pagesDir, `${book}${pagePath}.${subpage}.${defaultLang}.md`)
|
|
869
1204
|
if (!existsSync(srcFile)) continue
|
|
870
1205
|
|
|
871
|
-
const routePath = `${
|
|
1206
|
+
const routePath = `${book}${pagePath}/${subpage}`
|
|
872
1207
|
const mdUrl = `${siteUrl}/${routePath}.md`
|
|
873
1208
|
const pageUrl = `${siteUrl}/${routePath}`
|
|
874
1209
|
|
|
875
1210
|
const desc = page.config.meta?.description
|
|
876
1211
|
const descText = typeof desc === 'object' ? (desc[defaultLang] || desc['en-US'] || '') : (desc || '')
|
|
877
1212
|
|
|
878
|
-
if (!llmsSections[
|
|
879
|
-
llmsSections[
|
|
1213
|
+
if (!llmsSections[book]) llmsSections[book] = []
|
|
1214
|
+
llmsSections[book].push(
|
|
880
1215
|
descText
|
|
881
1216
|
? `- [${title}](${mdUrl}): ${descText}`
|
|
882
1217
|
: `- [${title}](${mdUrl})`
|
|
@@ -1470,11 +1805,10 @@ export async function onRequest (context) {
|
|
|
1470
1805
|
|
|
1471
1806
|
// Collect page index for MCP
|
|
1472
1807
|
const mcpPages = []
|
|
1473
|
-
for (const
|
|
1808
|
+
for (const { book, pagePath, page } of pageEntries) {
|
|
1474
1809
|
if (page.config === null) continue
|
|
1475
1810
|
if (page.config.status === 'empty') continue
|
|
1476
1811
|
|
|
1477
|
-
const type = page.config.type ?? 'manual'
|
|
1478
1812
|
const defaultTitle = page.data?.['*']?.title
|
|
1479
1813
|
|| page.data?.[defaultLang]?.title
|
|
1480
1814
|
|| page.data?.['en-US']?.title
|
|
@@ -1486,13 +1820,14 @@ export async function onRequest (context) {
|
|
|
1486
1820
|
if (page.config.subpages?.vs) subpageList.push('vs')
|
|
1487
1821
|
|
|
1488
1822
|
for (const subpage of subpageList) {
|
|
1489
|
-
const srcFile = resolve(pagesDir, `${
|
|
1823
|
+
const srcFile = resolve(pagesDir, `${book}${pagePath}.${subpage}.${defaultLang}.md`)
|
|
1490
1824
|
if (!existsSync(srcFile)) continue
|
|
1491
1825
|
|
|
1492
1826
|
mcpPages.push({
|
|
1493
|
-
path: `${
|
|
1827
|
+
path: `${book}${pagePath}/${subpage}`,
|
|
1494
1828
|
title: defaultTitle,
|
|
1495
|
-
|
|
1829
|
+
book,
|
|
1830
|
+
type: book,
|
|
1496
1831
|
subpage
|
|
1497
1832
|
})
|
|
1498
1833
|
}
|
|
@@ -1874,12 +2209,12 @@ export function createQuasarConfig (options = {}) {
|
|
|
1874
2209
|
vueRouterMode: 'history',
|
|
1875
2210
|
|
|
1876
2211
|
vitePlugins: [
|
|
2212
|
+
createBooksPlugin(projectRoot),
|
|
1877
2213
|
createHjsonPlugin(),
|
|
1878
2214
|
createHomePageOverridePlugin(projectRoot),
|
|
1879
2215
|
createGitDatesPlugin(projectRoot),
|
|
1880
2216
|
createMarkdownEndpointPlugin(projectRoot),
|
|
1881
2217
|
createMarkdownBuildPlugin(projectRoot),
|
|
1882
|
-
createPagesWatchPlugin(projectRoot),
|
|
1883
2218
|
createPrerenderMetaPlugin(projectRoot),
|
|
1884
2219
|
...vitePlugins
|
|
1885
2220
|
],
|
|
@@ -1897,16 +2232,23 @@ export function createQuasarConfig (options = {}) {
|
|
|
1897
2232
|
viteConf.optimizeDeps.force = true
|
|
1898
2233
|
}
|
|
1899
2234
|
|
|
1900
|
-
// Include a hash of
|
|
1901
|
-
//
|
|
1902
|
-
//
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
2235
|
+
// Include a hash of page registry definition files (legacy index.js,
|
|
2236
|
+
// plus *.book.js and *.index.js) in optimizer config so Vite's
|
|
2237
|
+
// configHash/browserHash changes whenever routes/books are edited.
|
|
2238
|
+
const pagesRegistryFiles = getPagesRegistryFiles(projectRoot)
|
|
2239
|
+
if (pagesRegistryFiles.length > 0) {
|
|
2240
|
+
const pagesHashBuilder = createHash('sha256')
|
|
2241
|
+
for (const file of pagesRegistryFiles) {
|
|
2242
|
+
pagesHashBuilder
|
|
2243
|
+
.update(file)
|
|
2244
|
+
.update(readFileSync(file))
|
|
2245
|
+
}
|
|
2246
|
+
|
|
1906
2247
|
const pagesHash = createHash('sha256')
|
|
1907
|
-
.update(
|
|
2248
|
+
.update(pagesHashBuilder.digest('hex'))
|
|
1908
2249
|
.digest('hex')
|
|
1909
2250
|
.slice(0, 8)
|
|
2251
|
+
|
|
1910
2252
|
viteConf.optimizeDeps.esbuildOptions = viteConf.optimizeDeps.esbuildOptions || {}
|
|
1911
2253
|
viteConf.optimizeDeps.esbuildOptions.define = {
|
|
1912
2254
|
...(viteConf.optimizeDeps.esbuildOptions.define || {}),
|
|
@@ -1950,7 +2292,7 @@ export function createQuasarConfig (options = {}) {
|
|
|
1950
2292
|
// The router is excluded because routes.js imports consumer content via
|
|
1951
2293
|
// the `pages` alias. If pre-bundled, consumer content gets embedded in
|
|
1952
2294
|
// the optimizer cache whose hash doesn't track source file changes,
|
|
1953
|
-
// causing stale routes after editing
|
|
2295
|
+
// causing stale routes after editing page registry files.
|
|
1954
2296
|
viteConf.optimizeDeps.exclude = [
|
|
1955
2297
|
...(viteConf.optimizeDeps.exclude || []),
|
|
1956
2298
|
'boot/i18n', 'boot/store', 'boot/QZoom', 'boot/axios'
|