@docsector/docsector-reader 2.0.7 → 2.2.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 +19 -7
- package/bin/docsector.js +28 -7
- package/docsector.config.js +13 -1
- package/package.json +1 -1
- package/src/components/DMenu.vue +241 -12
- package/src/components/DMenuItem.vue +25 -2
- package/src/components/DPageBar.vue +35 -5
- package/src/components/DPageMeta.vue +40 -26
- package/src/i18n/helpers.js +84 -18
- package/src/i18n/index.js +5 -3
- package/src/i18n/languages/en-US.hjson +14 -0
- package/src/i18n/languages/pt-BR.hjson +14 -0
- 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 +6 -2
- package/src/pages/guide/pages-and-routing.overview.pt-BR.md +6 -2
- package/src/pages/guide.index.js +3 -1
- 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/pages/manual/components/d-page-meta.overview.en-US.md +1 -0
- package/src/pages/manual/components/d-page-meta.overview.pt-BR.md +1 -0
- package/src/pages/manual.index.js +2 -1
- package/src/quasar.factory.js +648 -91
- package/src/router/routes.js +129 -95
- package/src/store/App.js +15 -5
package/README.md
CHANGED
|
@@ -54,7 +54,7 @@ Transform Markdown content into beautiful, navigable documentation sites — wit
|
|
|
54
54
|
- 📚 **Book Tabs with Per-State Colors** — Define `*.book.js` tabs with icons, order, and `color.active` / `color.inactive`
|
|
55
55
|
- 🔀 **Internal Shortcut Pages** — Route entries can redirect with `config.link.to`, keeping localized titles while inheriting icon/status from the destination page
|
|
56
56
|
- 📐 **Responsive Subpage Toolbar** — Subpage actions align with the content column on desktop and dock to the bottom on mobile
|
|
57
|
-
- 🏷️ **Status Badges** — Mark pages as `done`, `draft`, or `
|
|
57
|
+
- 🏷️ **Status Badges** — Mark pages as `done`, `draft`, `empty`, or `new` with visual indicators
|
|
58
58
|
- ✏️ **Edit on GitHub** — Direct links to edit pages on your repository
|
|
59
59
|
- 🧭 **Robust Edit Link Mapping** — Normalizes route paths (including trailing slashes) into `page.subpage.locale.md` source files for reliable GitHub edit URLs
|
|
60
60
|
- 📅 **Last Updated Date** — Automatic per-page "last updated" date from git commit history, locale-formatted
|
|
@@ -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
|
|
|
@@ -892,7 +903,8 @@ export default {
|
|
|
892
903
|
'/my-section/my-page': definePage({
|
|
893
904
|
config: {
|
|
894
905
|
icon: 'description',
|
|
895
|
-
status: '
|
|
906
|
+
status: 'new', // 'done' | 'draft' | 'empty' | 'new'
|
|
907
|
+
version: 'v2.1.0', // Optional: shown as "New in" / "Novo em"
|
|
896
908
|
menu: {
|
|
897
909
|
header: { label: '.my-section', icon: 'category' }
|
|
898
910
|
},
|
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.2.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 = `\
|
|
@@ -310,6 +315,7 @@ const TEMPLATE_I18N_HJSON = `\
|
|
|
310
315
|
prev: 'Previous page'
|
|
311
316
|
next: 'Next page'
|
|
312
317
|
}
|
|
318
|
+
newVersion: 'New in'
|
|
313
319
|
}
|
|
314
320
|
|
|
315
321
|
// @ Menu
|
|
@@ -331,6 +337,19 @@ const TEMPLATE_I18N_HJSON = `\
|
|
|
331
337
|
_: 'draft'
|
|
332
338
|
tooltip: 'This page is under construction.'
|
|
333
339
|
}
|
|
340
|
+
new: {
|
|
341
|
+
_: 'new'
|
|
342
|
+
tooltip: 'This page is new.'
|
|
343
|
+
tooltipVersion: 'New in {version}'
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
version: {
|
|
348
|
+
status: {
|
|
349
|
+
released: 'released'
|
|
350
|
+
draft: 'draft'
|
|
351
|
+
deprecated: 'deprecated'
|
|
352
|
+
}
|
|
334
353
|
}
|
|
335
354
|
|
|
336
355
|
settings: 'Settings'
|
|
@@ -401,7 +420,8 @@ const TEMPLATE_PAGES_INDEX = `\
|
|
|
401
420
|
* and each value configures the page's book, icon, status, and titles.
|
|
402
421
|
*
|
|
403
422
|
* config.book: top-level route prefix — 'guide', 'manual', etc.
|
|
404
|
-
* config.status: 'done' | 'draft' | 'empty'
|
|
423
|
+
* config.status: 'done' | 'draft' | 'empty' | 'new'
|
|
424
|
+
* config.version: optional version where this page was introduced (e.g. 'v2.1.0')
|
|
405
425
|
* config.meta.description: string or localized object for SEO/social description
|
|
406
426
|
* config.icon: Material Design icon name
|
|
407
427
|
* config.menu: menu display options (header, subheader, separator)
|
|
@@ -414,7 +434,8 @@ export default {
|
|
|
414
434
|
'/getting-started': {
|
|
415
435
|
config: {
|
|
416
436
|
icon: 'flag',
|
|
417
|
-
status: '
|
|
437
|
+
status: 'new',
|
|
438
|
+
version: 'v0.1.0',
|
|
418
439
|
meta: {
|
|
419
440
|
description: {
|
|
420
441
|
'en-US': 'Get started quickly with setup and project structure.'
|
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.2.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,110 @@ 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 versionStatusLabel = (label, releaseStatus) => {
|
|
48
|
+
const normalizedStatus = String(releaseStatus || '').toLowerCase()
|
|
49
|
+
const normalizedLabel = String(label || normalizedStatus).toLowerCase()
|
|
50
|
+
const statusKey = normalizedStatus ? `menu.version.status.${normalizedStatus}` : null
|
|
51
|
+
const labelKey = normalizedLabel ? `menu.version.status.${normalizedLabel}` : null
|
|
52
|
+
|
|
53
|
+
if (statusKey && (!label || normalizedLabel === normalizedStatus) && te(statusKey)) {
|
|
54
|
+
return t(statusKey)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (labelKey && te(labelKey)) {
|
|
58
|
+
return t(labelKey)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return label || releaseStatus
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const normalizeVersionBadge = (item) => {
|
|
65
|
+
const configuredStatus = item.deprecated === true
|
|
66
|
+
? 'deprecated'
|
|
67
|
+
: (item.releaseStatus || item.status)
|
|
68
|
+
const explicitlyReleased = item.released !== undefined ? item.released !== false : null
|
|
69
|
+
const released = configuredStatus === 'deprecated'
|
|
70
|
+
? true
|
|
71
|
+
: (explicitlyReleased ?? !draftReleaseStatuses.has(String(configuredStatus || '').toLowerCase()))
|
|
72
|
+
const releaseStatus = configuredStatus || (released ? 'released' : 'draft')
|
|
73
|
+
const rawBadge = item.badge ?? item.releaseBadge
|
|
74
|
+
const deprecated = releaseStatus === 'deprecated'
|
|
75
|
+
const defaultColor = deprecated ? 'negative' : (released ? 'positive' : 'warning')
|
|
76
|
+
const defaultTextColor = (deprecated || released) ? 'white' : 'dark'
|
|
77
|
+
|
|
78
|
+
if (rawBadge === false || rawBadge === null) {
|
|
79
|
+
return { label: versionStatusLabel(releaseStatus, releaseStatus), color: defaultColor, textColor: defaultTextColor }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (typeof rawBadge === 'string') {
|
|
83
|
+
return { label: versionStatusLabel(rawBadge, releaseStatus), color: defaultColor, textColor: defaultTextColor }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (typeof rawBadge === 'object' && rawBadge !== null) {
|
|
87
|
+
const label = rawBadge.label || rawBadge.text || releaseStatus
|
|
88
|
+
if (!label) {
|
|
89
|
+
return null
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
...rawBadge,
|
|
94
|
+
label: versionStatusLabel(label, releaseStatus),
|
|
95
|
+
color: rawBadge.color || defaultColor,
|
|
96
|
+
textColor: rawBadge.textColor || defaultTextColor
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return { label: versionStatusLabel(releaseStatus, releaseStatus), color: defaultColor, textColor: defaultTextColor }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const versionOptions = computed(() => {
|
|
104
|
+
return (versions || [])
|
|
105
|
+
.filter(item => item && item.id)
|
|
106
|
+
.map(item => ({
|
|
107
|
+
label: item.label || item.id,
|
|
108
|
+
value: item.id,
|
|
109
|
+
badge: normalizeVersionBadge(item),
|
|
110
|
+
released: item.released !== false,
|
|
111
|
+
deprecated: item.deprecated === true || item.releaseStatus === 'deprecated' || item.status === 'deprecated',
|
|
112
|
+
releaseStatus: item.releaseStatus || item.status || (item.released === false ? 'draft' : 'released')
|
|
113
|
+
}))
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
const activeVersionOption = computed(() => {
|
|
117
|
+
return versionOptions.value.find(item => item.value === activeVersionId.value) || null
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
const version = computed({
|
|
121
|
+
get: () => activeVersionId.value,
|
|
122
|
+
set: (versionId) => onVersionChange(versionId)
|
|
123
|
+
})
|
|
124
|
+
|
|
34
125
|
const defaultBookId = computed(() => {
|
|
35
|
-
const sortedBooks = [...
|
|
126
|
+
const sortedBooks = [...activeBooks.value]
|
|
36
127
|
.filter(book => book && typeof book.id === 'string' && book.id.length > 0)
|
|
37
128
|
.sort((a, b) => {
|
|
38
129
|
const orderA = Number.isFinite(a.order) ? a.order : Number.MAX_SAFE_INTEGER
|
|
@@ -52,13 +143,97 @@ const currentBookId = computed(() => {
|
|
|
52
143
|
return defaultBookId.value
|
|
53
144
|
})
|
|
54
145
|
|
|
146
|
+
const normalizeRoutePath = (path) => {
|
|
147
|
+
const normalized = String(path || '').trim()
|
|
148
|
+
if (normalized === '' || normalized === '/') {
|
|
149
|
+
return '/'
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const sanitized = normalized.replace(/\/+$/, '')
|
|
153
|
+
return sanitized === '' ? '/' : sanitized
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const getTopRoutes = () => {
|
|
157
|
+
return ($router.options.routes || []).slice(0, -2)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const routeHasSubpage = (route, subpageName) => {
|
|
161
|
+
return (route.children || []).some(child => child.path === subpageName)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const routeToSubpagePath = (route, subpageName) => {
|
|
165
|
+
return `${route.path.replace(/\/$/, '')}/${subpageName}/`
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const getCurrentSubpageName = () => {
|
|
169
|
+
return String(subpage.value || '/overview').replace(/^\/+|\/+$/g, '') || 'overview'
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const getFirstRoutePathByVersion = (versionId, preferredBook = null) => {
|
|
173
|
+
const routes = getTopRoutes()
|
|
174
|
+
|
|
175
|
+
for (const preferBook of [preferredBook, null]) {
|
|
176
|
+
for (const route of routes) {
|
|
177
|
+
if (route?.meta?.version !== versionId) continue
|
|
178
|
+
if (preferBook && (route.meta?.book ?? route.meta?.type) !== preferBook) continue
|
|
179
|
+
if (!routeHasSubpage(route, 'overview')) continue
|
|
180
|
+
|
|
181
|
+
const hasInternalLink = typeof route.meta?.link?.to === 'string' && route.meta.link.to.trim().length > 0
|
|
182
|
+
if (hasInternalLink) continue
|
|
183
|
+
|
|
184
|
+
return routeToSubpagePath(route, 'overview')
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return '/'
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const getEquivalentRoutePath = (versionId) => {
|
|
192
|
+
const routeMeta = $route.matched?.[0]?.meta || {}
|
|
193
|
+
const book = routeMeta.book ?? routeMeta.type ?? currentBookId.value
|
|
194
|
+
const pagePath = routeMeta.pagePath
|
|
195
|
+
const subpageName = getCurrentSubpageName()
|
|
196
|
+
|
|
197
|
+
if (book && typeof pagePath === 'string') {
|
|
198
|
+
const equivalentRoute = getTopRoutes().find(route => {
|
|
199
|
+
return route?.meta?.version === versionId &&
|
|
200
|
+
(route.meta?.book ?? route.meta?.type) === book &&
|
|
201
|
+
route.meta?.pagePath === pagePath &&
|
|
202
|
+
routeHasSubpage(route, subpageName)
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
if (equivalentRoute) {
|
|
206
|
+
return routeToSubpagePath(equivalentRoute, subpageName)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return getFirstRoutePathByVersion(versionId, book)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function onVersionChange (versionId) {
|
|
214
|
+
if (!versionId || versionId === activeVersionId.value) return
|
|
215
|
+
|
|
216
|
+
const targetVersion = (versions || []).find(item => item.id === versionId)
|
|
217
|
+
if (!targetVersion) return
|
|
218
|
+
|
|
219
|
+
if (typeof targetVersion.url === 'string' && targetVersion.url.trim().length > 0) {
|
|
220
|
+
openURL(targetVersion.url)
|
|
221
|
+
return
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const targetPath = getEquivalentRoutePath(versionId)
|
|
225
|
+
if (normalizeRoutePath($route.path) !== normalizeRoutePath(targetPath)) {
|
|
226
|
+
$router.push(targetPath)
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
55
230
|
const searchTerm = (term) => {
|
|
56
231
|
if (term.length > 1) {
|
|
57
232
|
term = term.toLowerCase()
|
|
58
233
|
const locale = $q.localStorage.getItem('setting.language')
|
|
59
234
|
founds.value = []
|
|
60
235
|
|
|
61
|
-
for (const
|
|
236
|
+
for (const group of items.value) {
|
|
62
237
|
searchTermIterate(group, term, locale)
|
|
63
238
|
}
|
|
64
239
|
} else {
|
|
@@ -74,13 +249,14 @@ const searchTermIterate = (items, term, locale) => {
|
|
|
74
249
|
} else if (typeof items === 'object') {
|
|
75
250
|
const item = items
|
|
76
251
|
const path = item.path
|
|
252
|
+
const tagPath = item.meta?.unversionedPath || path
|
|
77
253
|
founds.value[path] = false
|
|
78
254
|
|
|
79
255
|
// @ search in i18n/tags.hjson
|
|
80
256
|
if (tags[locale] && Object.keys(tags[locale]).length > 0) {
|
|
81
|
-
founds.value[path] = tags[locale][
|
|
257
|
+
founds.value[path] = tags[locale][tagPath]?.indexOf(term) !== -1
|
|
82
258
|
if (founds.value[path] === false && locale !== 'en-US') {
|
|
83
|
-
founds.value[path] = tags['en-US'][
|
|
259
|
+
founds.value[path] = tags['en-US'][tagPath]?.indexOf(term) !== -1
|
|
84
260
|
}
|
|
85
261
|
}
|
|
86
262
|
|
|
@@ -181,11 +357,13 @@ onBeforeUnmount(() => {
|
|
|
181
357
|
})
|
|
182
358
|
|
|
183
359
|
const buildMenuItems = () => {
|
|
184
|
-
const routes = (
|
|
360
|
+
const routes = getTopRoutes()
|
|
185
361
|
const activeBook = currentBookId.value
|
|
362
|
+
const activeVersion = activeVersionId.value
|
|
186
363
|
|
|
187
364
|
const filteredRoutes = routes.filter(route => {
|
|
188
365
|
const routeBook = route?.meta?.book ?? route?.meta?.type
|
|
366
|
+
if (activeVersion && route?.meta?.version !== activeVersion) return false
|
|
189
367
|
if (!activeBook) return true
|
|
190
368
|
return routeBook === activeBook
|
|
191
369
|
})
|
|
@@ -201,7 +379,7 @@ const buildMenuItems = () => {
|
|
|
201
379
|
})
|
|
202
380
|
|
|
203
381
|
// # Route
|
|
204
|
-
|
|
382
|
+
const basepath = route.meta?.menuGroupPath || route.path.split('/')[2]
|
|
205
383
|
const header = route.meta?.menu?.header
|
|
206
384
|
|
|
207
385
|
if (header !== undefined && basepath !== nodeBasepath) {
|
|
@@ -227,7 +405,7 @@ const rebuildItems = () => {
|
|
|
227
405
|
}
|
|
228
406
|
|
|
229
407
|
rebuildItems()
|
|
230
|
-
watch(currentBookId, rebuildItems)
|
|
408
|
+
watch([currentBookId, activeVersionId], rebuildItems)
|
|
231
409
|
</script>
|
|
232
410
|
|
|
233
411
|
<template>
|
|
@@ -254,10 +432,44 @@ watch(currentBookId, rebuildItems)
|
|
|
254
432
|
<div class="text-weight-medium">{{ branding.name || 'Docsector' }}</div>
|
|
255
433
|
<div class="text-caption q-pt-xs">{{ t('system.documentation') }}</div>
|
|
256
434
|
<q-select class="q-mr-md"
|
|
257
|
-
v-model="version" :options="
|
|
435
|
+
v-model="version" :options="versionOptions"
|
|
436
|
+
emit-value map-options
|
|
258
437
|
dense options-dense
|
|
259
438
|
behavior="menu"
|
|
260
|
-
|
|
439
|
+
>
|
|
440
|
+
<template v-slot:selected>
|
|
441
|
+
<div v-if="activeVersionOption" class="version-select-option">
|
|
442
|
+
<span class="version-select-label">{{ activeVersionOption.label }}</span>
|
|
443
|
+
<q-badge
|
|
444
|
+
v-if="activeVersionOption.badge"
|
|
445
|
+
class="version-select-badge"
|
|
446
|
+
:color="activeVersionOption.badge.color || 'warning'"
|
|
447
|
+
:text-color="activeVersionOption.badge.textColor || 'dark'"
|
|
448
|
+
:outline="activeVersionOption.badge.outline === true"
|
|
449
|
+
>
|
|
450
|
+
{{ activeVersionOption.badge.label }}
|
|
451
|
+
</q-badge>
|
|
452
|
+
</div>
|
|
453
|
+
</template>
|
|
454
|
+
<template v-slot:option="scope">
|
|
455
|
+
<q-item v-bind="scope.itemProps">
|
|
456
|
+
<q-item-section>
|
|
457
|
+
<div class="version-select-option">
|
|
458
|
+
<span class="version-select-label">{{ scope.opt.label }}</span>
|
|
459
|
+
<q-badge
|
|
460
|
+
v-if="scope.opt.badge"
|
|
461
|
+
class="version-select-badge"
|
|
462
|
+
:color="scope.opt.badge.color || 'warning'"
|
|
463
|
+
:text-color="scope.opt.badge.textColor || 'dark'"
|
|
464
|
+
:outline="scope.opt.badge.outline === true"
|
|
465
|
+
>
|
|
466
|
+
{{ scope.opt.badge.label }}
|
|
467
|
+
</q-badge>
|
|
468
|
+
</div>
|
|
469
|
+
</q-item-section>
|
|
470
|
+
</q-item>
|
|
471
|
+
</template>
|
|
472
|
+
</q-select>
|
|
261
473
|
</div>
|
|
262
474
|
</div>
|
|
263
475
|
|
|
@@ -470,6 +682,23 @@ body.body--light
|
|
|
470
682
|
width: 30px
|
|
471
683
|
height: 3px
|
|
472
684
|
|
|
685
|
+
.version-select-option
|
|
686
|
+
display: flex
|
|
687
|
+
align-items: center
|
|
688
|
+
gap: 6px
|
|
689
|
+
min-width: 0
|
|
690
|
+
max-width: 100%
|
|
691
|
+
|
|
692
|
+
.version-select-label
|
|
693
|
+
overflow: hidden
|
|
694
|
+
text-overflow: ellipsis
|
|
695
|
+
white-space: nowrap
|
|
696
|
+
|
|
697
|
+
.version-select-badge
|
|
698
|
+
flex: 0 0 auto
|
|
699
|
+
font-size: 10px
|
|
700
|
+
line-height: 1
|
|
701
|
+
|
|
473
702
|
// Search
|
|
474
703
|
label[for="search"]
|
|
475
704
|
z-index: 2
|
|
@@ -53,6 +53,10 @@ const getMenuItemSubheader = (meta = {}) => {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
const getPageStatusText = (status) => {
|
|
56
|
+
if (status === 'new') {
|
|
57
|
+
return t('menu.status.new._')
|
|
58
|
+
}
|
|
59
|
+
|
|
56
60
|
if (status === 'draft') {
|
|
57
61
|
return t('menu.status.draft._')
|
|
58
62
|
} else {
|
|
@@ -61,6 +65,10 @@ const getPageStatusText = (status) => {
|
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
const getPageStatusTextColor = (status) => {
|
|
68
|
+
if (status === 'new') {
|
|
69
|
+
return null
|
|
70
|
+
}
|
|
71
|
+
|
|
64
72
|
if (status === 'draft') {
|
|
65
73
|
return 'dark'
|
|
66
74
|
} else {
|
|
@@ -68,7 +76,15 @@ const getPageStatusTextColor = (status) => {
|
|
|
68
76
|
}
|
|
69
77
|
}
|
|
70
78
|
|
|
79
|
+
const getPageStatusStyle = (status) => {
|
|
80
|
+
return status === 'new' ? { color: '#151515' } : null
|
|
81
|
+
}
|
|
82
|
+
|
|
71
83
|
const getPageStatusColor = (status) => {
|
|
84
|
+
if (status === 'new') {
|
|
85
|
+
return 'positive'
|
|
86
|
+
}
|
|
87
|
+
|
|
72
88
|
if (status === 'draft') {
|
|
73
89
|
return 'orange'
|
|
74
90
|
} else {
|
|
@@ -76,7 +92,13 @@ const getPageStatusColor = (status) => {
|
|
|
76
92
|
}
|
|
77
93
|
}
|
|
78
94
|
|
|
79
|
-
const getPageStatusTooltip = (status) => {
|
|
95
|
+
const getPageStatusTooltip = (status, pageVersion) => {
|
|
96
|
+
if (status === 'new') {
|
|
97
|
+
return pageVersion
|
|
98
|
+
? t('menu.status.new.tooltipVersion', { version: pageVersion })
|
|
99
|
+
: t('menu.status.new.tooltip')
|
|
100
|
+
}
|
|
101
|
+
|
|
80
102
|
if (status === 'draft') {
|
|
81
103
|
return t('menu.status.draft.tooltip')
|
|
82
104
|
} else {
|
|
@@ -148,8 +170,9 @@ const onMenuItemClick = (event, path, currentSubpage) => {
|
|
|
148
170
|
:text-color="getPageStatusTextColor(subitem.meta.status)"
|
|
149
171
|
:color="getPageStatusColor(subitem.meta.status)"
|
|
150
172
|
:label="getPageStatusText(subitem.meta.status)"
|
|
173
|
+
:style="getPageStatusStyle(subitem.meta.status)"
|
|
151
174
|
/>
|
|
152
|
-
<q-tooltip :hide-delay="3">{{ getPageStatusTooltip(subitem.meta.status) }}</q-tooltip>
|
|
175
|
+
<q-tooltip :hide-delay="3">{{ getPageStatusTooltip(subitem.meta.status, subitem.meta.pageVersion) }}</q-tooltip>
|
|
153
176
|
</q-item-section>
|
|
154
177
|
</q-item>
|
|
155
178
|
|
|
@@ -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`
|
|
@@ -56,6 +61,11 @@ const formattedDate = computed(() => {
|
|
|
56
61
|
}).format(date)
|
|
57
62
|
})
|
|
58
63
|
|
|
64
|
+
const pageVersion = computed(() => {
|
|
65
|
+
const value = route.meta?.pageVersion
|
|
66
|
+
return typeof value === 'string' ? value.trim() : ''
|
|
67
|
+
})
|
|
68
|
+
|
|
59
69
|
const rawMarkdown = computed(() => {
|
|
60
70
|
const absolute = store.state.i18n.absolute
|
|
61
71
|
if (!absolute) return ''
|
|
@@ -187,10 +197,14 @@ const copyPage = () => {
|
|
|
187
197
|
|
|
188
198
|
<template>
|
|
189
199
|
<div class="d-page-bar">
|
|
190
|
-
<
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
200
|
+
<div class="d-page-bar__meta">
|
|
201
|
+
<span v-if="formattedDate" class="d-page-bar__date">
|
|
202
|
+
{{ t('page.lastUpdated') }}: <br class="d-page-bar__date-break"> {{ formattedDate }}
|
|
203
|
+
</span>
|
|
204
|
+
<span v-if="pageVersion" class="d-page-bar__new-in">
|
|
205
|
+
{{ t('page.newVersion') }}: {{ pageVersion }}
|
|
206
|
+
</span>
|
|
207
|
+
</div>
|
|
194
208
|
|
|
195
209
|
<q-btn-dropdown
|
|
196
210
|
class="d-page-bar__actions"
|
|
@@ -338,6 +352,18 @@ const copyPage = () => {
|
|
|
338
352
|
font-size: 0.8rem
|
|
339
353
|
opacity: 0.6
|
|
340
354
|
|
|
355
|
+
&__meta
|
|
356
|
+
display: flex
|
|
357
|
+
flex-direction: column
|
|
358
|
+
min-height: 1.5rem
|
|
359
|
+
|
|
360
|
+
&__new-in
|
|
361
|
+
font-size: 0.8rem
|
|
362
|
+
opacity: 0.6
|
|
363
|
+
margin-top: 8px
|
|
364
|
+
padding-top: 8px
|
|
365
|
+
border-top: 1px solid rgba(0, 0, 0, 0.12)
|
|
366
|
+
|
|
341
367
|
&__date-break
|
|
342
368
|
display: none
|
|
343
369
|
|
|
@@ -345,9 +371,13 @@ const copyPage = () => {
|
|
|
345
371
|
font-size: 0.75rem
|
|
346
372
|
|
|
347
373
|
body.body--dark
|
|
348
|
-
.d-page-bar__date
|
|
374
|
+
.d-page-bar__date,
|
|
375
|
+
.d-page-bar__new-in
|
|
349
376
|
color: rgba(255, 255, 255, 0.7)
|
|
350
377
|
|
|
378
|
+
.d-page-bar__new-in
|
|
379
|
+
border-top-color: rgba(255, 255, 255, 0.18)
|
|
380
|
+
|
|
351
381
|
@media (max-width: 376px)
|
|
352
382
|
.d-page-bar__date-break
|
|
353
383
|
display: block
|