@docsector/docsector-reader 1.7.1 → 2.0.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/.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 +370 -49
- package/src/router/routes.js +132 -46
package/.eslintrc.cjs
CHANGED
package/README.md
CHANGED
|
@@ -50,6 +50,9 @@ Transform Markdown content into beautiful, navigable documentation sites — wit
|
|
|
50
50
|
- 🔎 **Search** — Menu search across all documentation content and tags
|
|
51
51
|
- 🌐 **WebMCP Browser Tools** — Registers in-page tools for browser agents with `registerTool` and optional `provideContext` fallback
|
|
52
52
|
- 📱 **Responsive** — Mobile-friendly with collapsible sidebar and drawers
|
|
53
|
+
- 📚 **Book Tabs with Per-State Colors** — Define `*.book.js` tabs with icons, order, and `color.active` / `color.inactive`
|
|
54
|
+
- 🔀 **Internal Shortcut Pages** — Route entries can redirect with `config.link.to`, keeping localized titles while inheriting icon/status from the destination page
|
|
55
|
+
- 📐 **Responsive Subpage Toolbar** — Subpage actions align with the content column on desktop and dock to the bottom on mobile
|
|
53
56
|
- 🏷️ **Status Badges** — Mark pages as `done`, `draft`, or `empty` with visual indicators
|
|
54
57
|
- ✏️ **Edit on GitHub** — Direct links to edit pages on your repository
|
|
55
58
|
- 🧭 **Robust Edit Link Mapping** — Normalizes route paths (including trailing slashes) into `page.subpage.locale.md` source files for reliable GitHub edit URLs
|
|
@@ -94,7 +97,7 @@ When `mcp` is configured, `docsector build` generates:
|
|
|
94
97
|
|
|
95
98
|
| File | Purpose |
|
|
96
99
|
|---|---|
|
|
97
|
-
| `dist/spa/mcp-pages.json` | Page index (title, path,
|
|
100
|
+
| `dist/spa/mcp-pages.json` | Page index (title, path, book) for search |
|
|
98
101
|
| `functions/mcp.js` | Cloudflare Pages Function implementing MCP |
|
|
99
102
|
| `dist/spa/_routes.json` | Routes `/mcp` to the function |
|
|
100
103
|
| `dist/spa/_headers` | CORS headers for MCP endpoint |
|
|
@@ -775,11 +778,13 @@ const langModules = import.meta.glob('./languages/*.hjson', { eager: true })
|
|
|
775
778
|
const mdModules = import.meta.glob('../pages/**/*.md', { eager: true, query: '?raw', import: 'default' })
|
|
776
779
|
|
|
777
780
|
import boot from 'pages/boot'
|
|
778
|
-
import
|
|
781
|
+
import { books } from 'virtual:docsector-books'
|
|
779
782
|
|
|
780
|
-
export default buildMessages({ langModules, mdModules,
|
|
783
|
+
export default buildMessages({ langModules, mdModules, books, boot, homePageOverride })
|
|
781
784
|
```
|
|
782
785
|
|
|
786
|
+
> `books` is the preferred source because it preserves per-book registries and avoids path collisions when two books reuse the same route key.
|
|
787
|
+
|
|
783
788
|
### Language files
|
|
784
789
|
|
|
785
790
|
Place HJSON locale files in `src/i18n/languages/`:
|
|
@@ -815,7 +820,10 @@ my-docs/
|
|
|
815
820
|
├── package.json
|
|
816
821
|
├── src/
|
|
817
822
|
│ ├── pages/
|
|
818
|
-
│ │ ├──
|
|
823
|
+
│ │ ├── manual.book.js # Manual tab metadata (icon, order, active/inactive colors)
|
|
824
|
+
│ │ ├── manual.index.js # Manual page registry (routes + metadata)
|
|
825
|
+
│ │ ├── guide.book.js # Guide tab metadata (icon, order, active/inactive colors)
|
|
826
|
+
│ │ ├── guide.index.js # Guide page registry (routes + metadata)
|
|
819
827
|
│ │ ├── boot.js # Boot page data
|
|
820
828
|
│ │ ├── guide/ # Guide pages (.md files)
|
|
821
829
|
│ │ └── manual/ # Manual pages (.md files)
|
|
@@ -835,17 +843,54 @@ my-docs/
|
|
|
835
843
|
|
|
836
844
|
---
|
|
837
845
|
|
|
846
|
+
## ⚠️ Migrating from 1.x to 2.0
|
|
847
|
+
|
|
848
|
+
- Split the legacy `src/pages/index.js` registry into per-book files such as `src/pages/manual.book.js`, `src/pages/manual.index.js`, `src/pages/guide.book.js`, and `src/pages/guide.index.js`.
|
|
849
|
+
- Update i18n wiring to import `books` from `virtual:docsector-books` and pass it to `buildMessages({ ... })`.
|
|
850
|
+
- Rename `config.type` to `config.book` in page definitions. Legacy fallback still works, but `config.book` is the supported API moving forward.
|
|
851
|
+
|
|
852
|
+
---
|
|
853
|
+
|
|
854
|
+
## 📚 Defining Books (Tabs)
|
|
855
|
+
|
|
856
|
+
Each documentation tab is defined by a `*.book.js` file paired with a matching `*.index.js` registry.
|
|
857
|
+
|
|
858
|
+
```javascript
|
|
859
|
+
import { defineBook } from '@docsector/docsector-reader'
|
|
860
|
+
|
|
861
|
+
export default defineBook({
|
|
862
|
+
id: 'guide',
|
|
863
|
+
label: 'Guide',
|
|
864
|
+
icon: 'school',
|
|
865
|
+
order: 2,
|
|
866
|
+
color: {
|
|
867
|
+
active: 'white',
|
|
868
|
+
inactive: 'secondary'
|
|
869
|
+
}
|
|
870
|
+
})
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
Notes:
|
|
874
|
+
|
|
875
|
+
- `color.active` and `color.inactive` control the tab text color for each state.
|
|
876
|
+
- Color values accept Quasar tokens (`secondary`, `red-6`), CSS variables (`--brand-color` or `var(--brand-color)`), and plain CSS colors (`white`, `#fff`, `rgb(...)`).
|
|
877
|
+
- Legacy `color: 'secondary'` still works, but the object form is the recommended API.
|
|
878
|
+
- Tabs are ordered by `order`.
|
|
879
|
+
|
|
880
|
+
---
|
|
881
|
+
|
|
838
882
|
## 📄 Adding Pages
|
|
839
883
|
|
|
840
|
-
1️⃣ Register in `src/pages/index.js
|
|
884
|
+
1️⃣ Register in `src/pages/manual.index.js` (or `src/pages/guide.index.js`):
|
|
841
885
|
|
|
842
886
|
```javascript
|
|
887
|
+
import { definePage } from '@docsector/docsector-reader'
|
|
888
|
+
|
|
843
889
|
export default {
|
|
844
|
-
'/
|
|
890
|
+
'/my-section/my-page': definePage({
|
|
845
891
|
config: {
|
|
846
892
|
icon: 'description',
|
|
847
893
|
status: 'done', // 'done' | 'draft' | 'empty'
|
|
848
|
-
type: 'manual', // 'guide' | 'manual'
|
|
849
894
|
menu: {
|
|
850
895
|
header: { label: '.my-section', icon: 'category' }
|
|
851
896
|
},
|
|
@@ -855,10 +900,16 @@ export default {
|
|
|
855
900
|
'en-US': { title: 'My Page' },
|
|
856
901
|
'pt-BR': { title: 'Minha Página' }
|
|
857
902
|
}
|
|
858
|
-
}
|
|
903
|
+
})
|
|
859
904
|
}
|
|
860
905
|
```
|
|
861
906
|
|
|
907
|
+
Notes:
|
|
908
|
+
|
|
909
|
+
- In `manual.index.js`, route keys are relative to the `manual` book (for example `'/my-section/my-page'` becomes `/manual/my-section/my-page/...`).
|
|
910
|
+
- You only need to set `config.book` when overriding the inferred book from the registry file.
|
|
911
|
+
- When `showcase` or `vs` are enabled, the subpage toolbar aligns with the content width on desktop and becomes a bottom action bar on mobile.
|
|
912
|
+
|
|
862
913
|
2️⃣ Create Markdown files:
|
|
863
914
|
|
|
864
915
|
```
|
|
@@ -866,6 +917,34 @@ src/pages/manual/my-section/my-page.overview.en-US.md
|
|
|
866
917
|
src/pages/manual/my-section/my-page.overview.pt-BR.md
|
|
867
918
|
```
|
|
868
919
|
|
|
920
|
+
### Internal Links / Menu Shortcuts
|
|
921
|
+
|
|
922
|
+
Use `config.link.to` when an entry should appear in menus but redirect immediately to another internal page.
|
|
923
|
+
|
|
924
|
+
```javascript
|
|
925
|
+
import { definePage } from '@docsector/docsector-reader'
|
|
926
|
+
|
|
927
|
+
export default {
|
|
928
|
+
'/getting-started': definePage({
|
|
929
|
+
config: {
|
|
930
|
+
link: {
|
|
931
|
+
to: '/guide/getting-started/overview/'
|
|
932
|
+
}
|
|
933
|
+
},
|
|
934
|
+
data: {
|
|
935
|
+
'en-US': { title: 'Getting started' },
|
|
936
|
+
'pt-BR': { title: 'Começando' }
|
|
937
|
+
}
|
|
938
|
+
})
|
|
939
|
+
}
|
|
940
|
+
```
|
|
941
|
+
|
|
942
|
+
Notes:
|
|
943
|
+
|
|
944
|
+
- For shortcut pages, `link.to` and `data` are enough.
|
|
945
|
+
- `icon` and `status` automatically fall back to the destination page when omitted.
|
|
946
|
+
- Internal links redirect directly to the target route instead of rendering `overview` / `showcase` / `vs` locally.
|
|
947
|
+
|
|
869
948
|
### GitHub-Style Alert Example
|
|
870
949
|
|
|
871
950
|
```markdown
|
|
@@ -899,9 +978,12 @@ docsector help # Show help
|
|
|
899
978
|
|
|
900
979
|
| Import path | Export | Description |
|
|
901
980
|
|---|---|---|
|
|
981
|
+
| `@docsector/docsector-reader` | `createDocsector()` | Main helper for `docsector.config.js` objects |
|
|
982
|
+
| `@docsector/docsector-reader` | `defineBook()` | Define `*.book.js` tab metadata with active/inactive colors |
|
|
983
|
+
| `@docsector/docsector-reader` | `definePage()` | Define page registry entries, including internal shortcut pages |
|
|
902
984
|
| `@docsector/docsector-reader/quasar-factory` | `createQuasarConfig()` | Config factory for consumer projects |
|
|
903
985
|
| `@docsector/docsector-reader/quasar-factory` | `configure()` | No-op wrapper (avoids needing `quasar` dep) |
|
|
904
|
-
| `@docsector/docsector-reader/i18n` | `buildMessages()` | Build i18n messages from globs +
|
|
986
|
+
| `@docsector/docsector-reader/i18n` | `buildMessages()` | Build i18n messages from globs + book/page registries |
|
|
905
987
|
| `@docsector/docsector-reader/i18n` | `filter()` | Filter i18n messages by locale |
|
|
906
988
|
|
|
907
989
|
---
|
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 = '
|
|
26
|
+
const VERSION = '2.0.0'
|
|
27
27
|
|
|
28
28
|
const HELP = `
|
|
29
29
|
Docsector Reader v${VERSION}
|
|
@@ -71,7 +71,7 @@ function getTemplatePackageJson (name) {
|
|
|
71
71
|
serve: 'docsector serve'
|
|
72
72
|
},
|
|
73
73
|
dependencies: {
|
|
74
|
-
'@docsector/docsector-reader':
|
|
74
|
+
'@docsector/docsector-reader': `^${VERSION}`,
|
|
75
75
|
'@quasar/extras': '^1.16.12',
|
|
76
76
|
'quasar': '^2.16.6',
|
|
77
77
|
'vue': '^3.5.13',
|
|
@@ -273,7 +273,7 @@ const mdModules = import.meta.glob('../pages/**/*.md', { eager: true, query: '?r
|
|
|
273
273
|
|
|
274
274
|
// @ Import pages
|
|
275
275
|
import boot from 'pages/boot'
|
|
276
|
-
import pages from '
|
|
276
|
+
import { allPages as pages } from 'virtual:docsector-books'
|
|
277
277
|
|
|
278
278
|
export default buildMessages({ langModules, mdModules, pages, boot, homePageOverride })
|
|
279
279
|
`
|
|
@@ -383,14 +383,24 @@ const TEMPLATE_I18N_HJSON = `\
|
|
|
383
383
|
}
|
|
384
384
|
`
|
|
385
385
|
|
|
386
|
+
const TEMPLATE_PAGES_GUIDE_BOOK = `\
|
|
387
|
+
export default {
|
|
388
|
+
id: 'guide',
|
|
389
|
+
label: 'Guide',
|
|
390
|
+
icon: 'school',
|
|
391
|
+
order: 1,
|
|
392
|
+
color: 'secondary'
|
|
393
|
+
}
|
|
394
|
+
`
|
|
395
|
+
|
|
386
396
|
const TEMPLATE_PAGES_INDEX = `\
|
|
387
397
|
/**
|
|
388
|
-
* Pages Registry
|
|
398
|
+
* Guide Pages Registry
|
|
389
399
|
*
|
|
390
|
-
* Define
|
|
391
|
-
* and each value configures the page's
|
|
400
|
+
* Define guide pages here. Each key is a URL path,
|
|
401
|
+
* and each value configures the page's book, icon, status, and titles.
|
|
392
402
|
*
|
|
393
|
-
* config.
|
|
403
|
+
* config.book: top-level route prefix — 'guide', 'manual', etc.
|
|
394
404
|
* config.status: 'done' | 'draft' | 'empty'
|
|
395
405
|
* config.meta.description: string or localized object for SEO/social description
|
|
396
406
|
* config.icon: Material Design icon name
|
|
@@ -410,7 +420,7 @@ export default {
|
|
|
410
420
|
'en-US': 'Get started quickly with setup and project structure.'
|
|
411
421
|
}
|
|
412
422
|
},
|
|
413
|
-
|
|
423
|
+
book: 'guide',
|
|
414
424
|
menu: {
|
|
415
425
|
header: {
|
|
416
426
|
icon: 'school',
|
|
@@ -952,7 +962,8 @@ Here's an overview of the project files:
|
|
|
952
962
|
| \`docsector.config.js\` | Branding, links, languages, and GitHub config |
|
|
953
963
|
| \`quasar.config.js\` | Quasar/Vite build configuration (via factory) |
|
|
954
964
|
| \`.markdownlint.json\` | Markdown lint rules (allows Docsector custom tags) |
|
|
955
|
-
| \`src/pages/
|
|
965
|
+
| \`src/pages/guide.book.js\` | Guide book definition (tab metadata) |
|
|
966
|
+
| \`src/pages/guide.index.js\` | Guide page registry (routes + metadata) |
|
|
956
967
|
| \`src/pages/boot.js\` | Boot metadata for the home page |
|
|
957
968
|
| \`src/pages/Homepage.en-US.md\` | Home page content in Markdown |
|
|
958
969
|
| \`src/pages/404Page.vue\` | Not found page |
|
|
@@ -963,8 +974,9 @@ Here's an overview of the project files:
|
|
|
963
974
|
|
|
964
975
|
## Adding a Page
|
|
965
976
|
|
|
966
|
-
1. Register the page in \`src/pages/index.js\`
|
|
967
|
-
2.
|
|
977
|
+
1. Register the page in \`src/pages/guide.index.js\`
|
|
978
|
+
2. Set \`config.book\` (for example: \`'guide'\`)
|
|
979
|
+
3. Create the Markdown file at \`src/pages/{book}/{path}.overview.{lang}.md\`
|
|
968
980
|
3. The page will automatically appear in the sidebar navigation
|
|
969
981
|
|
|
970
982
|
## Customization
|
|
@@ -1082,7 +1094,8 @@ function initProject (name) {
|
|
|
1082
1094
|
['src/css/app.sass', TEMPLATE_CSS_STUB],
|
|
1083
1095
|
['src/i18n/index.js', TEMPLATE_I18N_INDEX],
|
|
1084
1096
|
['src/i18n/languages/en-US.hjson', TEMPLATE_I18N_HJSON],
|
|
1085
|
-
['src/pages/
|
|
1097
|
+
['src/pages/guide.book.js', TEMPLATE_PAGES_GUIDE_BOOK],
|
|
1098
|
+
['src/pages/guide.index.js', TEMPLATE_PAGES_INDEX],
|
|
1086
1099
|
['src/pages/boot.js', TEMPLATE_PAGES_BOOT],
|
|
1087
1100
|
['src/pages/Homepage.en-US.md', TEMPLATE_HOMEPAGE_MD],
|
|
1088
1101
|
['src/pages/404Page.vue', TEMPLATE_404_PAGE],
|
|
@@ -1120,7 +1133,8 @@ function initProject (name) {
|
|
|
1120
1133
|
console.log(' │ └── languages/')
|
|
1121
1134
|
console.log(' │ └── en-US.hjson')
|
|
1122
1135
|
console.log(' └── pages/')
|
|
1123
|
-
console.log(' ├──
|
|
1136
|
+
console.log(' ├── guide.book.js')
|
|
1137
|
+
console.log(' ├── guide.index.js')
|
|
1124
1138
|
console.log(' ├── boot.js')
|
|
1125
1139
|
console.log(' ├── Homepage.en-US.md')
|
|
1126
1140
|
console.log(' ├── 404Page.vue')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@docsector/docsector-reader",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.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/DH1.vue
CHANGED
|
@@ -4,6 +4,7 @@ import { useStore } from 'vuex'
|
|
|
4
4
|
import { useI18n } from "vue-i18n";
|
|
5
5
|
|
|
6
6
|
import useNavigator from '../composables/useNavigator'
|
|
7
|
+
import { pageTitleI18nPath } from '../i18n/path'
|
|
7
8
|
|
|
8
9
|
const props = defineProps({
|
|
9
10
|
id: {
|
|
@@ -22,7 +23,7 @@ const heading = computed(() => {
|
|
|
22
23
|
|
|
23
24
|
let h = ''
|
|
24
25
|
if (base && absolute) {
|
|
25
|
-
h = t(
|
|
26
|
+
h = t(pageTitleI18nPath(base))
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
return h
|
package/src/components/DMenu.vue
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
|
|
2
|
+
import { ref, computed, onMounted, onBeforeUnmount, watch } from 'vue'
|
|
3
3
|
import { useRoute, useRouter } from 'vue-router'
|
|
4
4
|
import { useQuasar, scroll, openURL } from 'quasar'
|
|
5
5
|
import { useI18n } from 'vue-i18n'
|
|
@@ -7,6 +7,8 @@ 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'
|
|
11
|
+
import { namespacedLabelI18nPath, routeSubpageSourceI18nPath } from '../i18n/path'
|
|
10
12
|
|
|
11
13
|
const $q = useQuasar()
|
|
12
14
|
const $route = useRoute()
|
|
@@ -29,6 +31,27 @@ const subpage = computed(() => {
|
|
|
29
31
|
return child.substring(parent.length)
|
|
30
32
|
})
|
|
31
33
|
|
|
34
|
+
const defaultBookId = computed(() => {
|
|
35
|
+
const sortedBooks = [...(allBooks || [])]
|
|
36
|
+
.filter(book => book && typeof book.id === 'string' && book.id.length > 0)
|
|
37
|
+
.sort((a, b) => {
|
|
38
|
+
const orderA = Number.isFinite(a.order) ? a.order : Number.MAX_SAFE_INTEGER
|
|
39
|
+
const orderB = Number.isFinite(b.order) ? b.order : Number.MAX_SAFE_INTEGER
|
|
40
|
+
return orderA - orderB
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
return sortedBooks[0]?.id || null
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
const currentBookId = computed(() => {
|
|
47
|
+
const routeBook = $route.matched?.[0]?.meta?.book ?? $route.meta?.book ?? null
|
|
48
|
+
if (routeBook && routeBook !== 'home') {
|
|
49
|
+
return routeBook
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return defaultBookId.value
|
|
53
|
+
})
|
|
54
|
+
|
|
32
55
|
const searchTerm = (term) => {
|
|
33
56
|
if (term.length > 1) {
|
|
34
57
|
term = term.toLowerCase()
|
|
@@ -76,7 +99,7 @@ const searchTermInI18nTexts = (route, term, locale) => {
|
|
|
76
99
|
let source = null
|
|
77
100
|
let found = false
|
|
78
101
|
for (const subpage of subpages) {
|
|
79
|
-
const path =
|
|
102
|
+
const path = routeSubpageSourceI18nPath(route, subpage)
|
|
80
103
|
const msgExists = te(path, locale)
|
|
81
104
|
if (msgExists) {
|
|
82
105
|
source = tm(path, locale)
|
|
@@ -99,7 +122,8 @@ const clearSearchTerm = () => {
|
|
|
99
122
|
const getMenuItemHeaderLabel = (meta) => {
|
|
100
123
|
const label = meta.menu.header.label
|
|
101
124
|
if (label[0] === '.') { // Node path
|
|
102
|
-
const
|
|
125
|
+
const book = meta.book ?? meta.type ?? 'manual'
|
|
126
|
+
const path = namespacedLabelI18nPath(book, label)
|
|
103
127
|
return t(path)
|
|
104
128
|
}
|
|
105
129
|
return label // String raw
|
|
@@ -156,38 +180,54 @@ onBeforeUnmount(() => {
|
|
|
156
180
|
}
|
|
157
181
|
})
|
|
158
182
|
|
|
159
|
-
|
|
160
|
-
//
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
const item = Object.freeze({
|
|
168
|
-
path: route.path,
|
|
169
|
-
meta: route.meta
|
|
183
|
+
const buildMenuItems = () => {
|
|
184
|
+
const routes = ($router.options.routes || []).slice(0, -2) // Delete last 2 routes
|
|
185
|
+
const activeBook = currentBookId.value
|
|
186
|
+
|
|
187
|
+
const filteredRoutes = routes.filter(route => {
|
|
188
|
+
const routeBook = route?.meta?.book ?? route?.meta?.type
|
|
189
|
+
if (!activeBook) return true
|
|
190
|
+
return routeBook === activeBook
|
|
170
191
|
})
|
|
171
|
-
// # Route
|
|
172
|
-
const basepath = route.path.split('/')[2]
|
|
173
|
-
const header = route.meta.menu.header
|
|
174
|
-
|
|
175
|
-
if (header !== undefined && basepath !== nodeBasepath) {
|
|
176
|
-
nodeBasepath = basepath
|
|
177
|
-
nodeIndex = index
|
|
178
|
-
itemsArray[index] = []
|
|
179
|
-
} else if (header === undefined && basepath !== nodeBasepath) {
|
|
180
|
-
nodeBasepath = ''
|
|
181
|
-
}
|
|
182
192
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
193
|
+
const itemsArray = []
|
|
194
|
+
|
|
195
|
+
let nodeBasepath = ''
|
|
196
|
+
let nodeIndex = 0
|
|
197
|
+
for (const [index, route] of filteredRoutes.entries()) {
|
|
198
|
+
const item = Object.freeze({
|
|
199
|
+
path: route.path,
|
|
200
|
+
meta: route.meta
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
// # Route
|
|
204
|
+
const basepath = route.path.split('/')[2]
|
|
205
|
+
const header = route.meta?.menu?.header
|
|
206
|
+
|
|
207
|
+
if (header !== undefined && basepath !== nodeBasepath) {
|
|
208
|
+
nodeBasepath = basepath
|
|
209
|
+
nodeIndex = index
|
|
210
|
+
itemsArray[index] = []
|
|
211
|
+
} else if (header === undefined && basepath !== nodeBasepath) {
|
|
212
|
+
nodeBasepath = ''
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (nodeBasepath !== '') {
|
|
216
|
+
itemsArray[nodeIndex].push(item)
|
|
217
|
+
} else {
|
|
218
|
+
itemsArray.push(item)
|
|
219
|
+
}
|
|
187
220
|
}
|
|
221
|
+
|
|
222
|
+
return Object.freeze(itemsArray.filter(item => item !== undefined))
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const rebuildItems = () => {
|
|
226
|
+
items.value = buildMenuItems()
|
|
188
227
|
}
|
|
189
228
|
|
|
190
|
-
|
|
229
|
+
rebuildItems()
|
|
230
|
+
watch(currentBookId, rebuildItems)
|
|
191
231
|
</script>
|
|
192
232
|
|
|
193
233
|
<template>
|
|
@@ -4,6 +4,8 @@ import { useRoute } from 'vue-router'
|
|
|
4
4
|
import { useQuasar } from 'quasar'
|
|
5
5
|
import { useI18n } from 'vue-i18n'
|
|
6
6
|
|
|
7
|
+
import { namespacedLabelI18nPath, routeTitleI18nPath } from '../i18n/path'
|
|
8
|
+
|
|
7
9
|
const props = defineProps({
|
|
8
10
|
items: {
|
|
9
11
|
type: Number,
|
|
@@ -36,13 +38,17 @@ const getMenuItemHeaderBackground = () => {
|
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
const getMenuItemLabel = (item, index) => {
|
|
39
|
-
|
|
40
|
-
return t(path)
|
|
41
|
+
return t(routeTitleI18nPath(item.path))
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
const getMenuItemSubheader = (meta) => {
|
|
44
|
-
const subheader = meta.menu
|
|
45
|
-
|
|
45
|
+
const subheader = meta.menu?.subheader
|
|
46
|
+
if (!subheader) {
|
|
47
|
+
return ''
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const book = meta.book ?? meta.type ?? 'manual'
|
|
51
|
+
const path = namespacedLabelI18nPath(book, subheader)
|
|
46
52
|
|
|
47
53
|
return t(path)
|
|
48
54
|
}
|
|
@@ -95,7 +101,7 @@ const isMenuItemActive = (path) => {
|
|
|
95
101
|
|
|
96
102
|
<template>
|
|
97
103
|
<!-- Menu Separator - Subheader -->
|
|
98
|
-
<q-item-section v-if="subitem.meta.menu
|
|
104
|
+
<q-item-section v-if="subitem.meta.menu?.subheader">
|
|
99
105
|
<q-item-label class="label subheader" header>
|
|
100
106
|
{{ getMenuItemSubheader(subitem.meta) }}
|
|
101
107
|
</q-item-label>
|
|
@@ -123,7 +129,7 @@ const isMenuItemActive = (path) => {
|
|
|
123
129
|
</q-item>
|
|
124
130
|
|
|
125
131
|
<!-- Menu Separator -->
|
|
126
|
-
<li v-if="subitem.meta.menu
|
|
132
|
+
<li v-if="subitem.meta.menu?.separator" role="listitem">
|
|
127
133
|
<q-separator
|
|
128
134
|
:class="'separator' + (subitem.meta.menu.separator === true ? '' : subitem.meta.menu.separator)"
|
|
129
135
|
role="separator"
|