@docsector/docsector-reader 2.0.7 → 2.1.0
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 +16 -5
- package/bin/docsector.js +10 -5
- package/docsector.config.js +13 -1
- package/package.json +1 -1
- package/src/components/DMenu.vue +224 -12
- package/src/components/DPageBar.vue +5 -0
- package/src/components/DPageMeta.vue +37 -23
- package/src/i18n/helpers.js +34 -18
- package/src/i18n/index.js +5 -3
- package/src/i18n/path.js +15 -2
- package/src/index.js +2 -2
- package/src/layouts/DefaultLayout.vue +16 -2
- package/src/pages/.old/v0.x/guide/getting-started.overview.en-US.md +7 -0
- package/src/pages/.old/v0.x/guide/getting-started.overview.pt-BR.md +7 -0
- package/src/pages/.old/v0.x/guide.book.js +12 -0
- package/src/pages/.old/v0.x/guide.index.js +28 -0
- package/src/pages/guide/configuration.overview.en-US.md +13 -2
- package/src/pages/guide/configuration.overview.pt-BR.md +13 -2
- package/src/pages/guide/pages-and-routing.overview.en-US.md +2 -0
- package/src/pages/guide/pages-and-routing.overview.pt-BR.md +2 -0
- package/src/pages/manual/components/d-menu.overview.en-US.md +6 -2
- package/src/pages/manual/components/d-menu.overview.pt-BR.md +6 -2
- package/src/quasar.factory.js +648 -91
- package/src/router/routes.js +127 -95
- package/src/store/App.js +15 -5
package/README.md
CHANGED
|
@@ -68,6 +68,8 @@ Transform Markdown content into beautiful, navigable documentation sites — wit
|
|
|
68
68
|
- 🧬 **Scaffolded Homepage Override Wiring** — New consumer projects automatically wire `virtual:docsector-homepage-override` into i18n message building
|
|
69
69
|
- 🧭 **Quick Links Custom Element** — Use `<d-quick-links>` and `<d-quick-link>` in Markdown to render rich home navigation cards
|
|
70
70
|
- 🗂️ **API Catalog Well-Known** — Auto-generates `/.well-known/api-catalog` as Linkset JSON for machine-readable API discovery
|
|
71
|
+
- 🗃️ **Multi-Version History** — Archive older major versions under `src/pages/.old/<version>/` and expose them at prefixed routes (e.g. `/v0.x/guide/...`) while keeping the current docs at unprefixed routes
|
|
72
|
+
- 🏷️ **Version Selector Badges** — Every version in the sidebar selector displays a color-coded badge: green for released, orange for draft, red for deprecated; fully customizable via `badge: { label, color, textColor }`
|
|
71
73
|
- ⚙️ **Single Config File** — Customize branding, links, and languages via `docsector.config.js`
|
|
72
74
|
|
|
73
75
|
---
|
|
@@ -675,7 +677,10 @@ export default {
|
|
|
675
677
|
logo: '/images/logo/my-logo.png',
|
|
676
678
|
name: 'My Project',
|
|
677
679
|
version: 'v1.0.0',
|
|
678
|
-
versions: [
|
|
680
|
+
versions: [
|
|
681
|
+
{ id: 'v1.0.0', current: true, released: false },
|
|
682
|
+
{ id: 'v0.9.0', released: true, status: 'deprecated' }
|
|
683
|
+
]
|
|
679
684
|
},
|
|
680
685
|
|
|
681
686
|
links: {
|
|
@@ -754,6 +759,10 @@ export default {
|
|
|
754
759
|
}
|
|
755
760
|
```
|
|
756
761
|
|
|
762
|
+
The current version keeps the normal unprefixed routes such as `/guide/getting-started/overview/`. Archived major versions can be placed under `src/pages/.old/<version>/` with the same book/index/Markdown layout, and are exposed with a URL prefix such as `/v0.x/guide/getting-started/overview/`.
|
|
763
|
+
|
|
764
|
+
Every version shows a release badge in the selector. Released versions default to `released`; versions with `released: false` or `status: 'draft'` default to `draft`; versions with `status: 'deprecated'` or `deprecated: true` default to `deprecated` in red. Use `badge: { label, color, textColor }` when you need custom badge copy or colors.
|
|
765
|
+
|
|
757
766
|
### MCP (optional)
|
|
758
767
|
|
|
759
768
|
```javascript
|
|
@@ -777,15 +786,17 @@ import { buildMessages } from '@docsector/docsector-reader/i18n'
|
|
|
777
786
|
import homePageOverride from 'virtual:docsector-homepage-override'
|
|
778
787
|
|
|
779
788
|
const langModules = import.meta.glob('./languages/*.hjson', { eager: true })
|
|
780
|
-
const
|
|
789
|
+
const currentMdModules = import.meta.glob('../pages/**/*.md', { eager: true, query: '?raw', import: 'default' })
|
|
790
|
+
const oldMdModules = import.meta.glob('../pages/.old/**/*.md', { eager: true, query: '?raw', import: 'default' })
|
|
791
|
+
const mdModules = { ...currentMdModules, ...oldMdModules }
|
|
781
792
|
|
|
782
793
|
import boot from 'pages/boot'
|
|
783
|
-
import { books } from 'virtual:docsector-books'
|
|
794
|
+
import { books, pageEntries } from 'virtual:docsector-books'
|
|
784
795
|
|
|
785
|
-
export default buildMessages({ langModules, mdModules, books, boot, homePageOverride })
|
|
796
|
+
export default buildMessages({ langModules, mdModules, books, pageEntries, boot, homePageOverride })
|
|
786
797
|
```
|
|
787
798
|
|
|
788
|
-
> `
|
|
799
|
+
> `pageEntries` is the preferred source because it preserves per-book and per-version registries and avoids path collisions when books or archived versions reuse the same route key.
|
|
789
800
|
|
|
790
801
|
### Language files
|
|
791
802
|
|
package/bin/docsector.js
CHANGED
|
@@ -23,7 +23,7 @@ const packageRoot = resolve(__dirname, '..')
|
|
|
23
23
|
const args = process.argv.slice(2)
|
|
24
24
|
const command = args[0]
|
|
25
25
|
|
|
26
|
-
const VERSION = '2.0
|
|
26
|
+
const VERSION = '2.1.0'
|
|
27
27
|
|
|
28
28
|
const HELP = `
|
|
29
29
|
Docsector Reader v${VERSION}
|
|
@@ -105,7 +105,10 @@ export default {
|
|
|
105
105
|
branding: {
|
|
106
106
|
logo: '/images/logo.png',
|
|
107
107
|
name: 'My Documentation',
|
|
108
|
-
version: 'v0.1.0'
|
|
108
|
+
version: 'v0.1.0',
|
|
109
|
+
versions: [
|
|
110
|
+
{ id: 'v0.1.0', current: true, released: false }
|
|
111
|
+
]
|
|
109
112
|
},
|
|
110
113
|
|
|
111
114
|
// @ Links
|
|
@@ -269,13 +272,15 @@ import homePageOverride from 'virtual:docsector-homepage-override'
|
|
|
269
272
|
// @ Import language HJSON files (Vite-compatible eager import)
|
|
270
273
|
const langModules = import.meta.glob('./languages/*.hjson', { eager: true })
|
|
271
274
|
// @ Import markdown files (Vite-compatible eager import as raw strings)
|
|
272
|
-
const
|
|
275
|
+
const currentMdModules = import.meta.glob('../pages/**/*.md', { eager: true, query: '?raw', import: 'default' })
|
|
276
|
+
const oldMdModules = import.meta.glob('../pages/.old/**/*.md', { eager: true, query: '?raw', import: 'default' })
|
|
277
|
+
const mdModules = { ...currentMdModules, ...oldMdModules }
|
|
273
278
|
|
|
274
279
|
// @ Import pages
|
|
275
280
|
import boot from 'pages/boot'
|
|
276
|
-
import {
|
|
281
|
+
import { books, pageEntries } from 'virtual:docsector-books'
|
|
277
282
|
|
|
278
|
-
export default buildMessages({ langModules, mdModules,
|
|
283
|
+
export default buildMessages({ langModules, mdModules, books, pageEntries, boot, homePageOverride })
|
|
279
284
|
`
|
|
280
285
|
|
|
281
286
|
const TEMPLATE_I18N_HJSON = `\
|
package/docsector.config.js
CHANGED
|
@@ -15,7 +15,19 @@ export default {
|
|
|
15
15
|
// Project name displayed in the sidebar
|
|
16
16
|
name: 'Docsector Reader',
|
|
17
17
|
// Version label displayed next to the name
|
|
18
|
-
version: 'v' + pkg.version
|
|
18
|
+
version: 'v' + pkg.version,
|
|
19
|
+
versions: [
|
|
20
|
+
{
|
|
21
|
+
id: 'v' + pkg.version,
|
|
22
|
+
current: true,
|
|
23
|
+
released: false
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: 'v0.x',
|
|
27
|
+
released: true,
|
|
28
|
+
status: 'deprecated'
|
|
29
|
+
}
|
|
30
|
+
]
|
|
19
31
|
},
|
|
20
32
|
|
|
21
33
|
// @ Links
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@docsector/docsector-reader",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
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",
|
package/src/components/DMenu.vue
CHANGED
|
@@ -7,7 +7,7 @@ import { useI18n } from 'vue-i18n'
|
|
|
7
7
|
import tags from '@docsector/tags'
|
|
8
8
|
import DMenuItem from './DMenuItem.vue'
|
|
9
9
|
import docsectorConfig from 'docsector.config.js'
|
|
10
|
-
import { allBooks } from 'virtual:docsector-books'
|
|
10
|
+
import { allBooks, booksByVersion, versions } from 'virtual:docsector-books'
|
|
11
11
|
import { namespacedLabelI18nPath, routeSubpageSourceI18nPath } from '../i18n/path'
|
|
12
12
|
|
|
13
13
|
const $q = useQuasar()
|
|
@@ -20,19 +20,93 @@ const links = docsectorConfig.links || {}
|
|
|
20
20
|
|
|
21
21
|
const term = ref(null)
|
|
22
22
|
const founds = ref(false)
|
|
23
|
-
const version = ref(branding.version || 'v0.x')
|
|
24
|
-
const versions = ref(branding.versions || ['v0.x'])
|
|
25
23
|
const items = ref([])
|
|
26
24
|
const scrolling = ref(null)
|
|
27
25
|
|
|
28
26
|
const subpage = computed(() => {
|
|
29
27
|
const parent = $route.matched[0]?.path
|
|
30
28
|
const child = $route.matched[1]?.path
|
|
29
|
+
if (!parent || !child) return '/overview'
|
|
31
30
|
return child.substring(parent.length)
|
|
32
31
|
})
|
|
33
32
|
|
|
33
|
+
const activeVersionId = computed(() => {
|
|
34
|
+
return $route.matched?.[0]?.meta?.version ?? versions?.[0]?.id ?? ''
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const activeBooks = computed(() => {
|
|
38
|
+
if (activeVersionId.value && booksByVersion?.[activeVersionId.value]?.allBooks) {
|
|
39
|
+
return booksByVersion[activeVersionId.value].allBooks
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return allBooks || []
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
const draftReleaseStatuses = new Set(['draft', 'unreleased', 'preview', 'next'])
|
|
46
|
+
|
|
47
|
+
const normalizeVersionBadge = (item) => {
|
|
48
|
+
const configuredStatus = item.deprecated === true
|
|
49
|
+
? 'deprecated'
|
|
50
|
+
: (item.releaseStatus || item.status)
|
|
51
|
+
const explicitlyReleased = item.released !== undefined ? item.released !== false : null
|
|
52
|
+
const released = configuredStatus === 'deprecated'
|
|
53
|
+
? true
|
|
54
|
+
: (explicitlyReleased ?? !draftReleaseStatuses.has(String(configuredStatus || '').toLowerCase()))
|
|
55
|
+
const releaseStatus = configuredStatus || (released ? 'released' : 'draft')
|
|
56
|
+
const rawBadge = item.badge ?? item.releaseBadge
|
|
57
|
+
const deprecated = releaseStatus === 'deprecated'
|
|
58
|
+
const defaultColor = deprecated ? 'negative' : (released ? 'positive' : 'warning')
|
|
59
|
+
const defaultTextColor = (deprecated || released) ? 'white' : 'dark'
|
|
60
|
+
|
|
61
|
+
if (rawBadge === false || rawBadge === null) {
|
|
62
|
+
return { label: releaseStatus, color: defaultColor, textColor: defaultTextColor }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (typeof rawBadge === 'string') {
|
|
66
|
+
return { label: rawBadge, color: defaultColor, textColor: defaultTextColor }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (typeof rawBadge === 'object' && rawBadge !== null) {
|
|
70
|
+
const label = rawBadge.label || rawBadge.text || releaseStatus
|
|
71
|
+
if (!label) {
|
|
72
|
+
return null
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
...rawBadge,
|
|
77
|
+
label,
|
|
78
|
+
color: rawBadge.color || defaultColor,
|
|
79
|
+
textColor: rawBadge.textColor || defaultTextColor
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return { label: releaseStatus, color: defaultColor, textColor: defaultTextColor }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const versionOptions = computed(() => {
|
|
87
|
+
return (versions || [])
|
|
88
|
+
.filter(item => item && item.id)
|
|
89
|
+
.map(item => ({
|
|
90
|
+
label: item.label || item.id,
|
|
91
|
+
value: item.id,
|
|
92
|
+
badge: normalizeVersionBadge(item),
|
|
93
|
+
released: item.released !== false,
|
|
94
|
+
deprecated: item.deprecated === true || item.releaseStatus === 'deprecated' || item.status === 'deprecated',
|
|
95
|
+
releaseStatus: item.releaseStatus || item.status || (item.released === false ? 'draft' : 'released')
|
|
96
|
+
}))
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
const activeVersionOption = computed(() => {
|
|
100
|
+
return versionOptions.value.find(item => item.value === activeVersionId.value) || null
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
const version = computed({
|
|
104
|
+
get: () => activeVersionId.value,
|
|
105
|
+
set: (versionId) => onVersionChange(versionId)
|
|
106
|
+
})
|
|
107
|
+
|
|
34
108
|
const defaultBookId = computed(() => {
|
|
35
|
-
const sortedBooks = [...
|
|
109
|
+
const sortedBooks = [...activeBooks.value]
|
|
36
110
|
.filter(book => book && typeof book.id === 'string' && book.id.length > 0)
|
|
37
111
|
.sort((a, b) => {
|
|
38
112
|
const orderA = Number.isFinite(a.order) ? a.order : Number.MAX_SAFE_INTEGER
|
|
@@ -52,13 +126,97 @@ const currentBookId = computed(() => {
|
|
|
52
126
|
return defaultBookId.value
|
|
53
127
|
})
|
|
54
128
|
|
|
129
|
+
const normalizeRoutePath = (path) => {
|
|
130
|
+
const normalized = String(path || '').trim()
|
|
131
|
+
if (normalized === '' || normalized === '/') {
|
|
132
|
+
return '/'
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const sanitized = normalized.replace(/\/+$/, '')
|
|
136
|
+
return sanitized === '' ? '/' : sanitized
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const getTopRoutes = () => {
|
|
140
|
+
return ($router.options.routes || []).slice(0, -2)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const routeHasSubpage = (route, subpageName) => {
|
|
144
|
+
return (route.children || []).some(child => child.path === subpageName)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const routeToSubpagePath = (route, subpageName) => {
|
|
148
|
+
return `${route.path.replace(/\/$/, '')}/${subpageName}/`
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const getCurrentSubpageName = () => {
|
|
152
|
+
return String(subpage.value || '/overview').replace(/^\/+|\/+$/g, '') || 'overview'
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const getFirstRoutePathByVersion = (versionId, preferredBook = null) => {
|
|
156
|
+
const routes = getTopRoutes()
|
|
157
|
+
|
|
158
|
+
for (const preferBook of [preferredBook, null]) {
|
|
159
|
+
for (const route of routes) {
|
|
160
|
+
if (route?.meta?.version !== versionId) continue
|
|
161
|
+
if (preferBook && (route.meta?.book ?? route.meta?.type) !== preferBook) continue
|
|
162
|
+
if (!routeHasSubpage(route, 'overview')) continue
|
|
163
|
+
|
|
164
|
+
const hasInternalLink = typeof route.meta?.link?.to === 'string' && route.meta.link.to.trim().length > 0
|
|
165
|
+
if (hasInternalLink) continue
|
|
166
|
+
|
|
167
|
+
return routeToSubpagePath(route, 'overview')
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return '/'
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const getEquivalentRoutePath = (versionId) => {
|
|
175
|
+
const routeMeta = $route.matched?.[0]?.meta || {}
|
|
176
|
+
const book = routeMeta.book ?? routeMeta.type ?? currentBookId.value
|
|
177
|
+
const pagePath = routeMeta.pagePath
|
|
178
|
+
const subpageName = getCurrentSubpageName()
|
|
179
|
+
|
|
180
|
+
if (book && typeof pagePath === 'string') {
|
|
181
|
+
const equivalentRoute = getTopRoutes().find(route => {
|
|
182
|
+
return route?.meta?.version === versionId &&
|
|
183
|
+
(route.meta?.book ?? route.meta?.type) === book &&
|
|
184
|
+
route.meta?.pagePath === pagePath &&
|
|
185
|
+
routeHasSubpage(route, subpageName)
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
if (equivalentRoute) {
|
|
189
|
+
return routeToSubpagePath(equivalentRoute, subpageName)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return getFirstRoutePathByVersion(versionId, book)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function onVersionChange (versionId) {
|
|
197
|
+
if (!versionId || versionId === activeVersionId.value) return
|
|
198
|
+
|
|
199
|
+
const targetVersion = (versions || []).find(item => item.id === versionId)
|
|
200
|
+
if (!targetVersion) return
|
|
201
|
+
|
|
202
|
+
if (typeof targetVersion.url === 'string' && targetVersion.url.trim().length > 0) {
|
|
203
|
+
openURL(targetVersion.url)
|
|
204
|
+
return
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const targetPath = getEquivalentRoutePath(versionId)
|
|
208
|
+
if (normalizeRoutePath($route.path) !== normalizeRoutePath(targetPath)) {
|
|
209
|
+
$router.push(targetPath)
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
55
213
|
const searchTerm = (term) => {
|
|
56
214
|
if (term.length > 1) {
|
|
57
215
|
term = term.toLowerCase()
|
|
58
216
|
const locale = $q.localStorage.getItem('setting.language')
|
|
59
217
|
founds.value = []
|
|
60
218
|
|
|
61
|
-
for (const
|
|
219
|
+
for (const group of items.value) {
|
|
62
220
|
searchTermIterate(group, term, locale)
|
|
63
221
|
}
|
|
64
222
|
} else {
|
|
@@ -74,13 +232,14 @@ const searchTermIterate = (items, term, locale) => {
|
|
|
74
232
|
} else if (typeof items === 'object') {
|
|
75
233
|
const item = items
|
|
76
234
|
const path = item.path
|
|
235
|
+
const tagPath = item.meta?.unversionedPath || path
|
|
77
236
|
founds.value[path] = false
|
|
78
237
|
|
|
79
238
|
// @ search in i18n/tags.hjson
|
|
80
239
|
if (tags[locale] && Object.keys(tags[locale]).length > 0) {
|
|
81
|
-
founds.value[path] = tags[locale][
|
|
240
|
+
founds.value[path] = tags[locale][tagPath]?.indexOf(term) !== -1
|
|
82
241
|
if (founds.value[path] === false && locale !== 'en-US') {
|
|
83
|
-
founds.value[path] = tags['en-US'][
|
|
242
|
+
founds.value[path] = tags['en-US'][tagPath]?.indexOf(term) !== -1
|
|
84
243
|
}
|
|
85
244
|
}
|
|
86
245
|
|
|
@@ -181,11 +340,13 @@ onBeforeUnmount(() => {
|
|
|
181
340
|
})
|
|
182
341
|
|
|
183
342
|
const buildMenuItems = () => {
|
|
184
|
-
const routes = (
|
|
343
|
+
const routes = getTopRoutes()
|
|
185
344
|
const activeBook = currentBookId.value
|
|
345
|
+
const activeVersion = activeVersionId.value
|
|
186
346
|
|
|
187
347
|
const filteredRoutes = routes.filter(route => {
|
|
188
348
|
const routeBook = route?.meta?.book ?? route?.meta?.type
|
|
349
|
+
if (activeVersion && route?.meta?.version !== activeVersion) return false
|
|
189
350
|
if (!activeBook) return true
|
|
190
351
|
return routeBook === activeBook
|
|
191
352
|
})
|
|
@@ -201,7 +362,7 @@ const buildMenuItems = () => {
|
|
|
201
362
|
})
|
|
202
363
|
|
|
203
364
|
// # Route
|
|
204
|
-
|
|
365
|
+
const basepath = route.meta?.menuGroupPath || route.path.split('/')[2]
|
|
205
366
|
const header = route.meta?.menu?.header
|
|
206
367
|
|
|
207
368
|
if (header !== undefined && basepath !== nodeBasepath) {
|
|
@@ -227,7 +388,7 @@ const rebuildItems = () => {
|
|
|
227
388
|
}
|
|
228
389
|
|
|
229
390
|
rebuildItems()
|
|
230
|
-
watch(currentBookId, rebuildItems)
|
|
391
|
+
watch([currentBookId, activeVersionId], rebuildItems)
|
|
231
392
|
</script>
|
|
232
393
|
|
|
233
394
|
<template>
|
|
@@ -254,10 +415,44 @@ watch(currentBookId, rebuildItems)
|
|
|
254
415
|
<div class="text-weight-medium">{{ branding.name || 'Docsector' }}</div>
|
|
255
416
|
<div class="text-caption q-pt-xs">{{ t('system.documentation') }}</div>
|
|
256
417
|
<q-select class="q-mr-md"
|
|
257
|
-
v-model="version" :options="
|
|
418
|
+
v-model="version" :options="versionOptions"
|
|
419
|
+
emit-value map-options
|
|
258
420
|
dense options-dense
|
|
259
421
|
behavior="menu"
|
|
260
|
-
|
|
422
|
+
>
|
|
423
|
+
<template v-slot:selected>
|
|
424
|
+
<div v-if="activeVersionOption" class="version-select-option">
|
|
425
|
+
<span class="version-select-label">{{ activeVersionOption.label }}</span>
|
|
426
|
+
<q-badge
|
|
427
|
+
v-if="activeVersionOption.badge"
|
|
428
|
+
class="version-select-badge"
|
|
429
|
+
:color="activeVersionOption.badge.color || 'warning'"
|
|
430
|
+
:text-color="activeVersionOption.badge.textColor || 'dark'"
|
|
431
|
+
:outline="activeVersionOption.badge.outline === true"
|
|
432
|
+
>
|
|
433
|
+
{{ activeVersionOption.badge.label }}
|
|
434
|
+
</q-badge>
|
|
435
|
+
</div>
|
|
436
|
+
</template>
|
|
437
|
+
<template v-slot:option="scope">
|
|
438
|
+
<q-item v-bind="scope.itemProps">
|
|
439
|
+
<q-item-section>
|
|
440
|
+
<div class="version-select-option">
|
|
441
|
+
<span class="version-select-label">{{ scope.opt.label }}</span>
|
|
442
|
+
<q-badge
|
|
443
|
+
v-if="scope.opt.badge"
|
|
444
|
+
class="version-select-badge"
|
|
445
|
+
:color="scope.opt.badge.color || 'warning'"
|
|
446
|
+
:text-color="scope.opt.badge.textColor || 'dark'"
|
|
447
|
+
:outline="scope.opt.badge.outline === true"
|
|
448
|
+
>
|
|
449
|
+
{{ scope.opt.badge.label }}
|
|
450
|
+
</q-badge>
|
|
451
|
+
</div>
|
|
452
|
+
</q-item-section>
|
|
453
|
+
</q-item>
|
|
454
|
+
</template>
|
|
455
|
+
</q-select>
|
|
261
456
|
</div>
|
|
262
457
|
</div>
|
|
263
458
|
|
|
@@ -470,6 +665,23 @@ body.body--light
|
|
|
470
665
|
width: 30px
|
|
471
666
|
height: 3px
|
|
472
667
|
|
|
668
|
+
.version-select-option
|
|
669
|
+
display: flex
|
|
670
|
+
align-items: center
|
|
671
|
+
gap: 6px
|
|
672
|
+
min-width: 0
|
|
673
|
+
max-width: 100%
|
|
674
|
+
|
|
675
|
+
.version-select-label
|
|
676
|
+
overflow: hidden
|
|
677
|
+
text-overflow: ellipsis
|
|
678
|
+
white-space: nowrap
|
|
679
|
+
|
|
680
|
+
.version-select-badge
|
|
681
|
+
flex: 0 0 auto
|
|
682
|
+
font-size: 10px
|
|
683
|
+
line-height: 1
|
|
684
|
+
|
|
473
685
|
// Search
|
|
474
686
|
label[for="search"]
|
|
475
687
|
z-index: 2
|
|
@@ -37,6 +37,11 @@ const subpage = computed(() => {
|
|
|
37
37
|
})
|
|
38
38
|
|
|
39
39
|
const fileKey = computed(() => {
|
|
40
|
+
const sourcePathBase = route.matched?.[0]?.meta?.sourcePathBase
|
|
41
|
+
if (typeof sourcePathBase === 'string' && sourcePathBase.length > 0) {
|
|
42
|
+
return `${sourcePathBase}.${subpage.value}.${locale.value}.md`
|
|
43
|
+
}
|
|
44
|
+
|
|
40
45
|
const base = store.state.page.base
|
|
41
46
|
if (!base) return ''
|
|
42
47
|
return `${base}.${subpage.value}.${locale.value}.md`
|
|
@@ -20,7 +20,17 @@ function normalizeEditBaseUrl (url = '') {
|
|
|
20
20
|
|
|
21
21
|
const base = normalizeEditBaseUrl(docsectorConfig.github?.editBaseUrl || '')
|
|
22
22
|
|
|
23
|
+
function getCurrentSubpageName () {
|
|
24
|
+
const relative = store.state.page.relative || '/overview'
|
|
25
|
+
return String(relative).replace(/^\/+|\/+$/g, '') || 'overview'
|
|
26
|
+
}
|
|
27
|
+
|
|
23
28
|
function routePathToSourcePath (path = '') {
|
|
29
|
+
const sourcePathBase = route.matched?.[0]?.meta?.sourcePathBase
|
|
30
|
+
if (typeof sourcePathBase === 'string' && sourcePathBase.length > 0) {
|
|
31
|
+
return `/${sourcePathBase}.${getCurrentSubpageName()}`
|
|
32
|
+
}
|
|
33
|
+
|
|
24
34
|
const cleanPath = String(path)
|
|
25
35
|
.replace(/\/index\.html$/, '')
|
|
26
36
|
.replace(/\/+$/, '')
|
|
@@ -131,34 +141,38 @@ const languages = computed(() => {
|
|
|
131
141
|
return `${i18nLocalesAvailable}/${i18nLocales.length}`
|
|
132
142
|
})
|
|
133
143
|
|
|
134
|
-
const
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
for (let i = 0; i < routes.length; i++) {
|
|
139
|
-
if ('/' + base === routes[i].path) {
|
|
140
|
-
if (i > 0) {
|
|
141
|
-
return routes[i - 1].path
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
+
const normalizeRoutePath = (path) => {
|
|
145
|
+
const normalized = String(path || '').trim()
|
|
146
|
+
if (normalized === '' || normalized === '/') {
|
|
147
|
+
return '/'
|
|
144
148
|
}
|
|
145
149
|
|
|
146
|
-
|
|
150
|
+
const sanitized = normalized.replace(/\/+$/, '')
|
|
151
|
+
return sanitized === '' ? '/' : sanitized
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const getVersionSiblingPath = (offset) => {
|
|
155
|
+
const versionId = route.matched?.[0]?.meta?.version ?? null
|
|
156
|
+
const currentPath = normalizeRoutePath(route.matched?.[0]?.path || `/${store.state.page.base}`)
|
|
157
|
+
const routes = router.options.routes
|
|
158
|
+
.slice(0, -2)
|
|
159
|
+
.filter(item => !versionId || item?.meta?.version === versionId)
|
|
160
|
+
|
|
161
|
+
const index = routes.findIndex(item => normalizeRoutePath(item.path) === currentPath)
|
|
162
|
+
if (index < 0) return ''
|
|
163
|
+
|
|
164
|
+
const sibling = routes[index + offset]
|
|
165
|
+
if (!sibling) return ''
|
|
166
|
+
|
|
167
|
+
return sibling.path
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const prev = computed(() => {
|
|
171
|
+
return getVersionSiblingPath(-1)
|
|
147
172
|
})
|
|
148
173
|
|
|
149
174
|
const next = computed(() => {
|
|
150
|
-
|
|
151
|
-
const routes = router.options.routes.slice(0, -2)
|
|
152
|
-
|
|
153
|
-
for (let i = 0; i < routes.length; i++) {
|
|
154
|
-
if ('/' + base === routes[i].path) {
|
|
155
|
-
if (typeof routes[i + 1] !== 'undefined') {
|
|
156
|
-
return routes[i + 1].path
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
return ''
|
|
175
|
+
return getVersionSiblingPath(1)
|
|
162
176
|
})
|
|
163
177
|
|
|
164
178
|
const hideRemoteHomeFooterMeta = computed(() => {
|
package/src/i18n/helpers.js
CHANGED
|
@@ -116,12 +116,13 @@ export function filter (source) {
|
|
|
116
116
|
* @param {Object} options.mdModules - Result of recursively globbing markdown files under ../pages with eager raw imports
|
|
117
117
|
* @param {Object} [options.pages] - Legacy merged page registry from virtual:docsector-books (allPages)
|
|
118
118
|
* @param {Object} [options.books] - Book registry from virtual:docsector-books (preferred, avoids path collisions)
|
|
119
|
+
* @param {Array} [options.pageEntries] - Version-aware page entries from virtual:docsector-books
|
|
119
120
|
* @param {Object} options.boot - Boot meta from pages/boot.js
|
|
120
121
|
* @param {string[]} [options.langs] - Language codes to process (auto-detected from langModules if omitted)
|
|
121
122
|
* @param {Object<string,string>} [options.homePageOverride] - Optional per-language Home markdown override
|
|
122
123
|
* @returns {Object} Complete i18n messages object keyed by locale
|
|
123
124
|
*/
|
|
124
|
-
export function buildMessages ({ langModules, mdModules, pages, books, boot, langs, homePageOverride = {} }) {
|
|
125
|
+
export function buildMessages ({ langModules, mdModules, pages, books, pageEntries, boot, langs, homePageOverride = {} }) {
|
|
125
126
|
// Auto-detect languages from HJSON files if not provided
|
|
126
127
|
if (!langs) {
|
|
127
128
|
langs = Object.keys(langModules).map(key => {
|
|
@@ -133,8 +134,9 @@ export function buildMessages ({ langModules, mdModules, pages, books, boot, lan
|
|
|
133
134
|
|
|
134
135
|
const i18n = {}
|
|
135
136
|
|
|
136
|
-
function load (topPage, path, subpage, lang) {
|
|
137
|
-
const
|
|
137
|
+
function load (topPage, path, subpage, lang, sourceRoot = '') {
|
|
138
|
+
const normalizedSourceRoot = String(sourceRoot || '').replace(/^\/+|\/+$/g, '')
|
|
139
|
+
const key = `../pages/${normalizedSourceRoot ? normalizedSourceRoot + '/' : ''}${topPage}/${path}.${subpage}.${lang}.md`
|
|
138
140
|
const content = mdModules[key]
|
|
139
141
|
|
|
140
142
|
if (!content) {
|
|
@@ -200,20 +202,30 @@ export function buildMessages ({ langModules, mdModules, pages, books, boot, lan
|
|
|
200
202
|
return match[1].trim()
|
|
201
203
|
}
|
|
202
204
|
|
|
203
|
-
const
|
|
205
|
+
const resolvedPageEntries = []
|
|
204
206
|
|
|
205
|
-
if (
|
|
207
|
+
if (Array.isArray(pageEntries) && pageEntries.length > 0) {
|
|
208
|
+
for (const entry of pageEntries) {
|
|
209
|
+
resolvedPageEntries.push({
|
|
210
|
+
key: entry.pagePath,
|
|
211
|
+
page: entry.page,
|
|
212
|
+
fallbackBook: entry.book,
|
|
213
|
+
sourceRoot: entry.sourceRoot || '',
|
|
214
|
+
i18nSegments: entry.i18nSegments
|
|
215
|
+
})
|
|
216
|
+
}
|
|
217
|
+
} else if (books && typeof books === 'object' && Object.keys(books).length > 0) {
|
|
206
218
|
for (const [bookId, book] of Object.entries(books)) {
|
|
207
219
|
const routes = book?.routes || {}
|
|
208
220
|
const fallbackBook = book?.config?.id || bookId || 'manual'
|
|
209
221
|
|
|
210
222
|
for (const [key, page] of Object.entries(routes)) {
|
|
211
|
-
|
|
223
|
+
resolvedPageEntries.push({ key, page, fallbackBook })
|
|
212
224
|
}
|
|
213
225
|
}
|
|
214
226
|
} else {
|
|
215
227
|
for (const [key, page] of Object.entries(pages || {})) {
|
|
216
|
-
|
|
228
|
+
resolvedPageEntries.push({ key, page, fallbackBook: null })
|
|
217
229
|
}
|
|
218
230
|
}
|
|
219
231
|
|
|
@@ -249,8 +261,8 @@ export function buildMessages ({ langModules, mdModules, pages, books, boot, lan
|
|
|
249
261
|
i18n[lang]._.home.overview.source = loadHomepage(lang)
|
|
250
262
|
|
|
251
263
|
// @ Iterate pages
|
|
252
|
-
for (const entry of
|
|
253
|
-
const { key, page, fallbackBook } = entry
|
|
264
|
+
for (const entry of resolvedPageEntries) {
|
|
265
|
+
const { key, page, fallbackBook, sourceRoot = '', i18nSegments: entryI18nSegments } = entry
|
|
254
266
|
const path = key.startsWith('/') ? key.slice(1) : key
|
|
255
267
|
|
|
256
268
|
const config = page.config
|
|
@@ -258,13 +270,13 @@ export function buildMessages ({ langModules, mdModules, pages, books, boot, lan
|
|
|
258
270
|
const meta = page.meta || boot.meta
|
|
259
271
|
|
|
260
272
|
const topPage = config?.book ?? config?.type ?? fallbackBook ?? 'manual'
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
273
|
+
const i18nSegments = Array.isArray(entryI18nSegments) && entryI18nSegments.length > 0
|
|
274
|
+
? entryI18nSegments
|
|
275
|
+
: [topPage, ...path.split('/').filter(Boolean)]
|
|
264
276
|
|
|
265
277
|
// ---
|
|
266
278
|
|
|
267
|
-
const _ =
|
|
279
|
+
const _ = i18nSegments.reduce((accumulator, current, index) => {
|
|
268
280
|
let node = accumulator[current]
|
|
269
281
|
|
|
270
282
|
// Set object if not exists
|
|
@@ -275,7 +287,7 @@ export function buildMessages ({ langModules, mdModules, pages, books, boot, lan
|
|
|
275
287
|
|
|
276
288
|
// @ Set metadata
|
|
277
289
|
// title
|
|
278
|
-
if (node._ === undefined) {
|
|
290
|
+
if (index === i18nSegments.length - 1 && node._ === undefined) {
|
|
279
291
|
node._ = data?.[lang]?.title || data?.['*']?.title || data?.['en-US']?.title || ''
|
|
280
292
|
}
|
|
281
293
|
|
|
@@ -283,6 +295,10 @@ export function buildMessages ({ langModules, mdModules, pages, books, boot, lan
|
|
|
283
295
|
return node
|
|
284
296
|
}
|
|
285
297
|
|
|
298
|
+
if (index < i18nSegments.length - 1) {
|
|
299
|
+
return node
|
|
300
|
+
}
|
|
301
|
+
|
|
286
302
|
// Set subpages sources if not exists
|
|
287
303
|
if (node.overview === undefined) {
|
|
288
304
|
node.overview = {
|
|
@@ -307,7 +323,7 @@ export function buildMessages ({ langModules, mdModules, pages, books, boot, lan
|
|
|
307
323
|
}
|
|
308
324
|
|
|
309
325
|
return node
|
|
310
|
-
}, i18n[lang]._
|
|
326
|
+
}, i18n[lang]._)
|
|
311
327
|
|
|
312
328
|
// ---
|
|
313
329
|
|
|
@@ -322,14 +338,14 @@ export function buildMessages ({ langModules, mdModules, pages, books, boot, lan
|
|
|
322
338
|
|
|
323
339
|
// @ Subpages
|
|
324
340
|
// Overview
|
|
325
|
-
_.overview.source = load(topPage, path, 'overview', lang)
|
|
341
|
+
_.overview.source = load(topPage, path, 'overview', lang, sourceRoot)
|
|
326
342
|
// showcase
|
|
327
343
|
if (config.subpages?.showcase === true) {
|
|
328
|
-
_.showcase.source = load(topPage, path, 'showcase', lang)
|
|
344
|
+
_.showcase.source = load(topPage, path, 'showcase', lang, sourceRoot)
|
|
329
345
|
}
|
|
330
346
|
// Vs
|
|
331
347
|
if (config.subpages?.vs === true) {
|
|
332
|
-
_.vs.source = load(topPage, path, 'vs', lang)
|
|
348
|
+
_.vs.source = load(topPage, path, 'vs', lang, sourceRoot)
|
|
333
349
|
}
|
|
334
350
|
}
|
|
335
351
|
}
|