@docsector/docsector-reader 0.6.0 → 0.7.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/README.md +2 -0
- package/package.json +1 -1
- package/src/components/DPageBar.vue +135 -0
- package/src/components/DSubpage.vue +3 -0
- package/src/i18n/helpers.js +50 -0
- package/src/i18n/languages/en-US.hjson +7 -1
- package/src/i18n/languages/pt-BR.hjson +7 -1
- package/src/quasar.factory.js +192 -1
package/README.md
CHANGED
|
@@ -23,6 +23,8 @@ Transform Markdown content into beautiful, navigable documentation sites — wit
|
|
|
23
23
|
- 📝 **Markdown Rendering** — Write docs in Markdown, rendered with syntax highlighting (Prism.js)
|
|
24
24
|
- 🧩 **Mermaid Diagrams** — Native support for fenced ` ```mermaid ` blocks, with automatic dark/light theme switching
|
|
25
25
|
- 🚨 **GitHub-Style Alerts** — Native support for `[!NOTE]`, `[!TIP]`, `[!IMPORTANT]`, `[!WARNING]`, and `[!CAUTION]`
|
|
26
|
+
- 🤖 **AI-Friendly** — "Copy page" button copies raw Markdown for LLMs; "View as Markdown" opens any page as plain text via `.md` URL suffix
|
|
27
|
+
- 📅 **Last Updated Date** — Automatic per-page "last updated" date from git commit history, locale-formatted
|
|
26
28
|
- 🌍 **Internationalization (i18n)** — Multi-language support with HJSON locale files and per-page translations
|
|
27
29
|
- 🌗 **Dark/Light Mode** — Automatic theme switching with Quasar Dark Plugin
|
|
28
30
|
- 🔗 **Anchor Navigation** — Right-side Table of Contents tree with scroll tracking
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@docsector/docsector-reader",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "A documentation rendering engine built with Vue 3, Quasar v2 and Vite. Transform Markdown into beautiful, navigable documentation sites.",
|
|
5
5
|
"productName": "Docsector Reader",
|
|
6
6
|
"author": "Rodrigo de Araujo Vieira",
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { ref, computed } from 'vue'
|
|
3
|
+
import { useRoute } from 'vue-router'
|
|
4
|
+
import { useStore } from 'vuex'
|
|
5
|
+
import { useI18n } from 'vue-i18n'
|
|
6
|
+
import { copyToClipboard, openURL } from 'quasar'
|
|
7
|
+
|
|
8
|
+
import gitDates from 'virtual:docsector-git-dates'
|
|
9
|
+
|
|
10
|
+
const store = useStore()
|
|
11
|
+
const route = useRoute()
|
|
12
|
+
const { t, locale } = useI18n()
|
|
13
|
+
|
|
14
|
+
const copied = ref(false)
|
|
15
|
+
|
|
16
|
+
const subpage = computed(() => {
|
|
17
|
+
const rel = store.state.page.relative
|
|
18
|
+
return rel ? rel.replace(/^\//, '') : 'overview'
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const fileKey = computed(() => {
|
|
22
|
+
const base = store.state.page.base
|
|
23
|
+
if (!base) return ''
|
|
24
|
+
return `${base}.${subpage.value}.${locale.value}.md`
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const formattedDate = computed(() => {
|
|
28
|
+
const iso = gitDates[fileKey.value]
|
|
29
|
+
if (!iso) return ''
|
|
30
|
+
|
|
31
|
+
const date = new Date(iso)
|
|
32
|
+
if (isNaN(date.getTime())) return ''
|
|
33
|
+
|
|
34
|
+
return new Intl.DateTimeFormat(locale.value, {
|
|
35
|
+
year: 'numeric',
|
|
36
|
+
month: 'long',
|
|
37
|
+
day: 'numeric'
|
|
38
|
+
}).format(date)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
const rawMarkdown = computed(() => {
|
|
42
|
+
const absolute = store.state.i18n.absolute
|
|
43
|
+
if (!absolute) return ''
|
|
44
|
+
|
|
45
|
+
const source = t(`_.${absolute}.source`)
|
|
46
|
+
if (!source) return ''
|
|
47
|
+
|
|
48
|
+
return String(source)
|
|
49
|
+
.replace(/{/g, '{')
|
|
50
|
+
.replace(/}/g, '}')
|
|
51
|
+
.replace(/\{'([^']+)'\}/g, '$1')
|
|
52
|
+
.replace(/&/g, '&')
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
const markdownURL = computed(() => {
|
|
56
|
+
return `${route.path}.md`
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
const copyPage = () => {
|
|
60
|
+
if (!rawMarkdown.value) return
|
|
61
|
+
|
|
62
|
+
copyToClipboard(rawMarkdown.value).then(() => {
|
|
63
|
+
copied.value = true
|
|
64
|
+
setTimeout(() => { copied.value = false }, 2000)
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const viewAsMarkdown = () => {
|
|
69
|
+
openURL(markdownURL.value)
|
|
70
|
+
}
|
|
71
|
+
</script>
|
|
72
|
+
|
|
73
|
+
<template>
|
|
74
|
+
<div class="d-page-bar">
|
|
75
|
+
<span v-if="formattedDate" class="d-page-bar__date">
|
|
76
|
+
{{ t('page.lastUpdated') }}: {{ formattedDate }}
|
|
77
|
+
</span>
|
|
78
|
+
<span v-else class="d-page-bar__date"></span>
|
|
79
|
+
|
|
80
|
+
<q-btn-dropdown
|
|
81
|
+
class="d-page-bar__actions"
|
|
82
|
+
split
|
|
83
|
+
no-caps
|
|
84
|
+
:icon="copied ? 'check' : 'content_copy'"
|
|
85
|
+
:label="copied ? t('page.copied') : t('page.copyPage')"
|
|
86
|
+
:color="copied ? 'positive' : 'grey-7'"
|
|
87
|
+
size="sm"
|
|
88
|
+
@click="copyPage"
|
|
89
|
+
>
|
|
90
|
+
<q-list style="min-width: 240px">
|
|
91
|
+
<q-item clickable v-close-popup @click="copyPage" class="q-py-sm">
|
|
92
|
+
<q-item-section avatar>
|
|
93
|
+
<q-icon name="content_copy" />
|
|
94
|
+
</q-item-section>
|
|
95
|
+
<q-item-section>
|
|
96
|
+
<q-item-label>{{ t('page.copyPage') }}</q-item-label>
|
|
97
|
+
<q-item-label caption>{{ t('page.copyPageCaption') }}</q-item-label>
|
|
98
|
+
</q-item-section>
|
|
99
|
+
</q-item>
|
|
100
|
+
|
|
101
|
+
<q-item clickable v-close-popup @click="viewAsMarkdown" class="q-py-sm">
|
|
102
|
+
<q-item-section avatar>
|
|
103
|
+
<q-icon name="description" />
|
|
104
|
+
</q-item-section>
|
|
105
|
+
<q-item-section>
|
|
106
|
+
<q-item-label>{{ t('page.viewAsMarkdown') }}</q-item-label>
|
|
107
|
+
<q-item-label caption>{{ t('page.viewAsMarkdownCaption') }}</q-item-label>
|
|
108
|
+
</q-item-section>
|
|
109
|
+
<q-item-section side>
|
|
110
|
+
<q-icon name="open_in_new" size="xs" />
|
|
111
|
+
</q-item-section>
|
|
112
|
+
</q-item>
|
|
113
|
+
</q-list>
|
|
114
|
+
</q-btn-dropdown>
|
|
115
|
+
</div>
|
|
116
|
+
</template>
|
|
117
|
+
|
|
118
|
+
<style lang="sass">
|
|
119
|
+
.d-page-bar
|
|
120
|
+
display: flex
|
|
121
|
+
justify-content: space-between
|
|
122
|
+
align-items: center
|
|
123
|
+
margin-bottom: 4px
|
|
124
|
+
|
|
125
|
+
&__date
|
|
126
|
+
font-size: 0.8rem
|
|
127
|
+
opacity: 0.6
|
|
128
|
+
|
|
129
|
+
&__actions
|
|
130
|
+
font-size: 0.75rem
|
|
131
|
+
|
|
132
|
+
body.body--dark
|
|
133
|
+
.d-page-bar__date
|
|
134
|
+
color: rgba(255, 255, 255, 0.7)
|
|
135
|
+
</style>
|
|
@@ -3,6 +3,7 @@ import { computed } from 'vue'
|
|
|
3
3
|
import { useRoute } from 'vue-router'
|
|
4
4
|
// components
|
|
5
5
|
import DPage from "./DPage.vue";
|
|
6
|
+
import DPageBar from "./DPageBar.vue";
|
|
6
7
|
import DH1 from "./DH1.vue";
|
|
7
8
|
import DPageSection from "./DPageSection.vue";
|
|
8
9
|
|
|
@@ -23,6 +24,8 @@ const id = computed(() => {
|
|
|
23
24
|
<template>
|
|
24
25
|
<d-page>
|
|
25
26
|
<header>
|
|
27
|
+
<d-page-bar />
|
|
28
|
+
<hr />
|
|
26
29
|
<d-h1 :id="0" />
|
|
27
30
|
</header>
|
|
28
31
|
|
package/src/i18n/helpers.js
CHANGED
|
@@ -16,6 +16,51 @@
|
|
|
16
16
|
* export default buildMessages({ langModules, mdModules, pages, boot })
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Engine default i18n keys, keyed by locale.
|
|
21
|
+
* These are deep-merged into consumer messages so engine components
|
|
22
|
+
* always have their required translations available.
|
|
23
|
+
*/
|
|
24
|
+
const engineDefaults = {
|
|
25
|
+
'en-US': {
|
|
26
|
+
page: {
|
|
27
|
+
lastUpdated: 'Last updated',
|
|
28
|
+
copyPage: 'Copy page',
|
|
29
|
+
copyPageCaption: 'Copy page as Markdown for LLMs',
|
|
30
|
+
copied: 'Copied!',
|
|
31
|
+
viewAsMarkdown: 'View as Markdown',
|
|
32
|
+
viewAsMarkdownCaption: 'View this page as plain text'
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
'pt-BR': {
|
|
36
|
+
page: {
|
|
37
|
+
lastUpdated: 'Última atualização',
|
|
38
|
+
copyPage: 'Copiar página',
|
|
39
|
+
copyPageCaption: 'Copiar página como Markdown para LLMs',
|
|
40
|
+
copied: 'Copiado!',
|
|
41
|
+
viewAsMarkdown: 'Ver como Markdown',
|
|
42
|
+
viewAsMarkdownCaption: 'Ver esta página como texto simples'
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Deep-merge source into target (target values take precedence).
|
|
49
|
+
*/
|
|
50
|
+
function deepMerge (target, source) {
|
|
51
|
+
for (const key of Object.keys(source)) {
|
|
52
|
+
if (
|
|
53
|
+
source[key] && typeof source[key] === 'object' && !Array.isArray(source[key]) &&
|
|
54
|
+
target[key] && typeof target[key] === 'object' && !Array.isArray(target[key])
|
|
55
|
+
) {
|
|
56
|
+
deepMerge(target[key], source[key])
|
|
57
|
+
} else if (!(key in target)) {
|
|
58
|
+
target[key] = source[key]
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return target
|
|
62
|
+
}
|
|
63
|
+
|
|
19
64
|
/**
|
|
20
65
|
* Escape characters that conflict with vue-i18n message syntax.
|
|
21
66
|
*
|
|
@@ -77,6 +122,11 @@ export function buildMessages ({ langModules, mdModules, pages, boot, langs }) {
|
|
|
77
122
|
const langKey = `./languages/${lang}.hjson`
|
|
78
123
|
i18n[lang] = langModules[langKey]?.default || langModules[langKey] || {}
|
|
79
124
|
|
|
125
|
+
// Merge engine defaults (consumer values take precedence)
|
|
126
|
+
if (engineDefaults[lang]) {
|
|
127
|
+
deepMerge(i18n[lang], engineDefaults[lang])
|
|
128
|
+
}
|
|
129
|
+
|
|
80
130
|
// @ Iterate pages
|
|
81
131
|
for (const [key, page] of Object.entries(pages)) {
|
|
82
132
|
const path = key.slice(1)
|
|
@@ -30,7 +30,13 @@
|
|
|
30
30
|
nav: {
|
|
31
31
|
prev: 'Previous page',
|
|
32
32
|
next: 'Next page'
|
|
33
|
-
}
|
|
33
|
+
},
|
|
34
|
+
lastUpdated: 'Last updated',
|
|
35
|
+
copyPage: 'Copy page',
|
|
36
|
+
copyPageCaption: 'Copy page as Markdown for LLMs',
|
|
37
|
+
copied: 'Copied!',
|
|
38
|
+
viewAsMarkdown: 'View as Markdown',
|
|
39
|
+
viewAsMarkdownCaption: 'View this page as plain text'
|
|
34
40
|
},
|
|
35
41
|
|
|
36
42
|
menu: {
|
|
@@ -29,7 +29,13 @@
|
|
|
29
29
|
nav: {
|
|
30
30
|
prev: 'Página anterior',
|
|
31
31
|
next: 'Próxima página'
|
|
32
|
-
}
|
|
32
|
+
},
|
|
33
|
+
lastUpdated: 'Última atualização',
|
|
34
|
+
copyPage: 'Copiar página',
|
|
35
|
+
copyPageCaption: 'Copiar página como Markdown para LLMs',
|
|
36
|
+
copied: 'Copiado!',
|
|
37
|
+
viewAsMarkdown: 'Ver como Markdown',
|
|
38
|
+
viewAsMarkdownCaption: 'Ver esta página como texto simples'
|
|
33
39
|
},
|
|
34
40
|
|
|
35
41
|
menu: {
|
package/src/quasar.factory.js
CHANGED
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
* @param {Function} [options.extendViteConf] - Additional Vite config extension
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
-
import { readFileSync, existsSync, rmSync, mkdirSync, writeFileSync } from 'fs'
|
|
26
|
+
import { readFileSync, existsSync, rmSync, mkdirSync, writeFileSync, readdirSync } from 'fs'
|
|
27
|
+
import { execSync } from 'child_process'
|
|
27
28
|
import { createHash } from 'crypto'
|
|
28
29
|
import { resolve } from 'path'
|
|
29
30
|
import { pathToFileURL } from 'url'
|
|
@@ -231,6 +232,193 @@ function createPrerenderMetaPlugin (projectRoot) {
|
|
|
231
232
|
}
|
|
232
233
|
}
|
|
233
234
|
|
|
235
|
+
/**
|
|
236
|
+
* Create a Vite plugin that collects git last-commit dates for all Markdown
|
|
237
|
+
* files under src/pages/ and exposes them as a virtual module.
|
|
238
|
+
*
|
|
239
|
+
* Consuming components can `import gitDates from 'virtual:docsector-git-dates'`
|
|
240
|
+
* to get an object mapping relative page keys to ISO date strings.
|
|
241
|
+
*
|
|
242
|
+
* Keys use the pattern: `<type>/<path>.<subpage>.<locale>.md`
|
|
243
|
+
* e.g. `manual/Bootgly/about/what.overview.en-US.md`
|
|
244
|
+
*/
|
|
245
|
+
function createGitDatesPlugin (projectRoot) {
|
|
246
|
+
const virtualId = 'virtual:docsector-git-dates'
|
|
247
|
+
const resolvedId = '\0' + virtualId
|
|
248
|
+
let dates = {}
|
|
249
|
+
|
|
250
|
+
function collectDates () {
|
|
251
|
+
dates = {}
|
|
252
|
+
const pagesDir = resolve(projectRoot, 'src', 'pages')
|
|
253
|
+
if (!existsSync(pagesDir)) return
|
|
254
|
+
|
|
255
|
+
const walkDir = (dir) => {
|
|
256
|
+
const entries = readdirSync(dir, { withFileTypes: true })
|
|
257
|
+
for (const entry of entries) {
|
|
258
|
+
const fullPath = resolve(dir, entry.name)
|
|
259
|
+
if (entry.isDirectory()) {
|
|
260
|
+
walkDir(fullPath)
|
|
261
|
+
} else if (entry.name.endsWith('.md')) {
|
|
262
|
+
try {
|
|
263
|
+
const date = execSync(
|
|
264
|
+
`git log -1 --format=%cI -- "${fullPath}"`,
|
|
265
|
+
{ cwd: projectRoot, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }
|
|
266
|
+
).trim()
|
|
267
|
+
if (date) {
|
|
268
|
+
// Key relative to src/pages/, e.g. "manual/Bootgly/about/what.overview.en-US.md"
|
|
269
|
+
const relKey = fullPath.slice(pagesDir.length + 1)
|
|
270
|
+
dates[relKey] = date
|
|
271
|
+
}
|
|
272
|
+
} catch {
|
|
273
|
+
// git not available or file untracked — skip
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
walkDir(pagesDir)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return {
|
|
283
|
+
name: 'docsector-git-dates',
|
|
284
|
+
buildStart () {
|
|
285
|
+
collectDates()
|
|
286
|
+
},
|
|
287
|
+
resolveId (id) {
|
|
288
|
+
if (id === virtualId) return resolvedId
|
|
289
|
+
},
|
|
290
|
+
load (id) {
|
|
291
|
+
if (id === resolvedId) {
|
|
292
|
+
return `export default ${JSON.stringify(dates)}`
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Create a Vite plugin that serves raw Markdown content for `.md` suffixed URLs.
|
|
300
|
+
*
|
|
301
|
+
* In **dev mode**, intercepts requests like `/manual/Bootgly/about/what/overview.md`
|
|
302
|
+
* and serves the corresponding `src/pages/manual/Bootgly/about/what.overview.<lang>.md`
|
|
303
|
+
* file as `text/plain; charset=utf-8`.
|
|
304
|
+
*
|
|
305
|
+
* In **production build** (`closeBundle`), generates static `.md` files in `dist/spa/`
|
|
306
|
+
* for each page/subpage so that the `.md` URLs resolve to actual files on any static host.
|
|
307
|
+
*
|
|
308
|
+
* The language served is determined by the `?lang=` query parameter, falling back to the
|
|
309
|
+
* `defaultLanguage` from `docsector.config.js`.
|
|
310
|
+
*/
|
|
311
|
+
function createMarkdownEndpointPlugin (projectRoot) {
|
|
312
|
+
const pagesDir = resolve(projectRoot, 'src', 'pages')
|
|
313
|
+
|
|
314
|
+
function resolveMarkdownFile (urlPath, lang) {
|
|
315
|
+
// URL: /manual/Bootgly/about/what/overview.md
|
|
316
|
+
// Strip leading slash and trailing .md
|
|
317
|
+
const clean = urlPath.replace(/^\//, '').replace(/\.md$/, '')
|
|
318
|
+
// Split into segments: ['manual', 'Bootgly', 'about', 'what', 'overview']
|
|
319
|
+
const segments = clean.split('/')
|
|
320
|
+
if (segments.length < 2) return null
|
|
321
|
+
|
|
322
|
+
// Last segment is the subpage (overview, showcase, vs)
|
|
323
|
+
const subpage = segments.pop()
|
|
324
|
+
// Remaining segments form the type + path: 'manual/Bootgly/about/what'
|
|
325
|
+
const basePath = segments.join('/')
|
|
326
|
+
|
|
327
|
+
// File: src/pages/manual/Bootgly/about/what.overview.en-US.md
|
|
328
|
+
const filePath = resolve(pagesDir, `${basePath}.${subpage}.${lang}.md`)
|
|
329
|
+
if (existsSync(filePath)) return filePath
|
|
330
|
+
|
|
331
|
+
return null
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return {
|
|
335
|
+
name: 'docsector-markdown-endpoint',
|
|
336
|
+
|
|
337
|
+
configureServer (server) {
|
|
338
|
+
// Read default language from config
|
|
339
|
+
let defaultLang = 'en-US'
|
|
340
|
+
try {
|
|
341
|
+
const configPath = resolve(projectRoot, 'docsector.config.js')
|
|
342
|
+
if (existsSync(configPath)) {
|
|
343
|
+
// Dynamic import in dev — we read it synchronously via a simple approach
|
|
344
|
+
const configContent = readFileSync(configPath, 'utf-8')
|
|
345
|
+
const match = configContent.match(/defaultLanguage\s*:\s*['"]([^'"]+)['"]/)
|
|
346
|
+
if (match) defaultLang = match[1]
|
|
347
|
+
}
|
|
348
|
+
} catch { /* use fallback */ }
|
|
349
|
+
|
|
350
|
+
server.middlewares.use((req, res, next) => {
|
|
351
|
+
const url = new URL(req.url, 'http://localhost')
|
|
352
|
+
if (!url.pathname.endsWith('.md')) return next()
|
|
353
|
+
|
|
354
|
+
const lang = url.searchParams.get('lang') || defaultLang
|
|
355
|
+
const file = resolveMarkdownFile(url.pathname, lang)
|
|
356
|
+
if (!file) return next()
|
|
357
|
+
|
|
358
|
+
const content = readFileSync(file, 'utf-8')
|
|
359
|
+
res.setHeader('Content-Type', 'text/plain; charset=utf-8')
|
|
360
|
+
res.end(content)
|
|
361
|
+
})
|
|
362
|
+
},
|
|
363
|
+
|
|
364
|
+
apply: 'serve'
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Create a Vite plugin that generates static `.md` files at build time.
|
|
370
|
+
*
|
|
371
|
+
* Runs in the `closeBundle` hook alongside the prerender-meta plugin.
|
|
372
|
+
* For each page/subpage, copies the raw Markdown source into
|
|
373
|
+
* `dist/spa/<routePath>.md` so that `.md` URLs work on static hosts.
|
|
374
|
+
*/
|
|
375
|
+
function createMarkdownBuildPlugin (projectRoot) {
|
|
376
|
+
return {
|
|
377
|
+
name: 'docsector-markdown-build',
|
|
378
|
+
apply: 'build',
|
|
379
|
+
async closeBundle () {
|
|
380
|
+
const distDir = resolve(projectRoot, 'dist', 'spa')
|
|
381
|
+
if (!existsSync(distDir)) return
|
|
382
|
+
|
|
383
|
+
const pagesDir = resolve(projectRoot, 'src', 'pages')
|
|
384
|
+
const configUrl = pathToFileURL(resolve(projectRoot, 'docsector.config.js')).href
|
|
385
|
+
const pagesUrl = pathToFileURL(resolve(projectRoot, 'src', 'pages', 'index.js')).href
|
|
386
|
+
|
|
387
|
+
const { default: config } = await import(configUrl)
|
|
388
|
+
const { default: pages } = await import(pagesUrl)
|
|
389
|
+
|
|
390
|
+
const defaultLang = config.defaultLanguage || config.languages?.[0]?.value || 'en-US'
|
|
391
|
+
let count = 0
|
|
392
|
+
|
|
393
|
+
for (const [pagePath, page] of Object.entries(pages)) {
|
|
394
|
+
if (page.config === null) continue
|
|
395
|
+
if (page.config.status === 'empty') continue
|
|
396
|
+
|
|
397
|
+
const type = page.config.type ?? 'manual'
|
|
398
|
+
|
|
399
|
+
const subpages = ['overview']
|
|
400
|
+
if (page.config.subpages?.showcase) subpages.push('showcase')
|
|
401
|
+
if (page.config.subpages?.vs) subpages.push('vs')
|
|
402
|
+
|
|
403
|
+
for (const subpage of subpages) {
|
|
404
|
+
const srcFile = resolve(pagesDir, `${type}${pagePath}.${subpage}.${defaultLang}.md`)
|
|
405
|
+
if (!existsSync(srcFile)) continue
|
|
406
|
+
|
|
407
|
+
const routePath = `${type}${pagePath}/${subpage}`
|
|
408
|
+
const destFile = resolve(distDir, `${routePath}.md`)
|
|
409
|
+
const destDir = resolve(destFile, '..')
|
|
410
|
+
|
|
411
|
+
mkdirSync(destDir, { recursive: true })
|
|
412
|
+
writeFileSync(destFile, readFileSync(srcFile, 'utf-8'))
|
|
413
|
+
count++
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
console.log(`\x1b[36m[docsector]\x1b[0m Generated ${count} static .md files`)
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
234
422
|
/**
|
|
235
423
|
* Create a complete Quasar configuration for a docsector-reader consumer project.
|
|
236
424
|
*
|
|
@@ -301,6 +489,9 @@ export function createQuasarConfig (options = {}) {
|
|
|
301
489
|
|
|
302
490
|
vitePlugins: [
|
|
303
491
|
createHjsonPlugin(),
|
|
492
|
+
createGitDatesPlugin(projectRoot),
|
|
493
|
+
createMarkdownEndpointPlugin(projectRoot),
|
|
494
|
+
createMarkdownBuildPlugin(projectRoot),
|
|
304
495
|
createPagesWatchPlugin(projectRoot),
|
|
305
496
|
createPrerenderMetaPlugin(projectRoot),
|
|
306
497
|
...vitePlugins
|