@docsector/docsector-reader 1.7.0 → 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 CHANGED
@@ -1,6 +1,8 @@
1
1
  module.exports = {
2
2
  root: true,
3
3
 
4
+ ignorePatterns: ['docsector.config.js'],
5
+
4
6
  parserOptions: {
5
7
  ecmaVersion: 2022,
6
8
  sourceType: 'module'
package/README.md CHANGED
@@ -41,6 +41,7 @@ Transform Markdown content into beautiful, navigable documentation sites — wit
41
41
  ## ✨ Features
42
42
 
43
43
  - 📝 **Markdown Rendering** — Write docs in Markdown, rendered with syntax highlighting (Prism.js)
44
+ - 🧱 **Raw HTML in Markdown** — Renders inline and block HTML tags inside markdown sections (including homepage remote README content)
44
45
  - 🧩 **Mermaid Diagrams** — Native support for fenced ` ```mermaid ` blocks, with automatic dark/light theme switching
45
46
  - 🚨 **GitHub-Style Alerts** — Native support for `[!NOTE]`, `[!TIP]`, `[!IMPORTANT]`, `[!WARNING]`, and `[!CAUTION]`
46
47
  - 🌍 **Internationalization (i18n)** — Multi-language support with HJSON locale files and per-page translations
@@ -49,6 +50,9 @@ Transform Markdown content into beautiful, navigable documentation sites — wit
49
50
  - 🔎 **Search** — Menu search across all documentation content and tags
50
51
  - 🌐 **WebMCP Browser Tools** — Registers in-page tools for browser agents with `registerTool` and optional `provideContext` fallback
51
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
52
56
  - 🏷️ **Status Badges** — Mark pages as `done`, `draft`, or `empty` with visual indicators
53
57
  - ✏️ **Edit on GitHub** — Direct links to edit pages on your repository
54
58
  - 🧭 **Robust Edit Link Mapping** — Normalizes route paths (including trailing slashes) into `page.subpage.locale.md` source files for reliable GitHub edit URLs
@@ -59,6 +63,7 @@ Transform Markdown content into beautiful, navigable documentation sites — wit
59
63
  - 🧭 **Content Signals** — Injects `Content-Signal` policy in `robots.txt` with deterministic, idempotent build output
60
64
  - 🏠 **Markdown Home at Root** — Homepage is rendered from `src/pages/Homepage.{lang}.md` directly at `/`
61
65
  - 🌍 **Remote README as Home** — Optional build-time remote README source for homepage with automatic local fallback
66
+ - 🧬 **Scaffolded Homepage Override Wiring** — New consumer projects automatically wire `virtual:docsector-homepage-override` into i18n message building
62
67
  - 🧭 **Quick Links Custom Element** — Use `<d-quick-links>` and `<d-quick-link>` in Markdown to render rich home navigation cards
63
68
  - 🗂️ **API Catalog Well-Known** — Auto-generates `/.well-known/api-catalog` as Linkset JSON for machine-readable API discovery
64
69
  - ⚙️ **Single Config File** — Customize branding, links, and languages via `docsector.config.js`
@@ -92,7 +97,7 @@ When `mcp` is configured, `docsector build` generates:
92
97
 
93
98
  | File | Purpose |
94
99
  |---|---|
95
- | `dist/spa/mcp-pages.json` | Page index (title, path, type) for search |
100
+ | `dist/spa/mcp-pages.json` | Page index (title, path, book) for search |
96
101
  | `functions/mcp.js` | Cloudflare Pages Function implementing MCP |
97
102
  | `dist/spa/_routes.json` | Routes `/mcp` to the function |
98
103
  | `dist/spa/_headers` | CORS headers for MCP endpoint |
@@ -767,16 +772,19 @@ Consumer projects use the `buildMessages` helper from the engine:
767
772
 
768
773
  ```javascript
769
774
  import { buildMessages } from '@docsector/docsector-reader/i18n'
775
+ import homePageOverride from 'virtual:docsector-homepage-override'
770
776
 
771
777
  const langModules = import.meta.glob('./languages/*.hjson', { eager: true })
772
778
  const mdModules = import.meta.glob('../pages/**/*.md', { eager: true, query: '?raw', import: 'default' })
773
779
 
774
780
  import boot from 'pages/boot'
775
- import pages from 'pages'
781
+ import { books } from 'virtual:docsector-books'
776
782
 
777
- export default buildMessages({ langModules, mdModules, pages, boot })
783
+ export default buildMessages({ langModules, mdModules, books, boot, homePageOverride })
778
784
  ```
779
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
+
780
788
  ### Language files
781
789
 
782
790
  Place HJSON locale files in `src/i18n/languages/`:
@@ -812,7 +820,10 @@ my-docs/
812
820
  ├── package.json
813
821
  ├── src/
814
822
  │ ├── pages/
815
- │ │ ├── index.js # Page registry (routes + metadata)
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)
816
827
  │ │ ├── boot.js # Boot page data
817
828
  │ │ ├── guide/ # Guide pages (.md files)
818
829
  │ │ └── manual/ # Manual pages (.md files)
@@ -832,17 +843,54 @@ my-docs/
832
843
 
833
844
  ---
834
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
+
835
882
  ## 📄 Adding Pages
836
883
 
837
- 1️⃣ Register in `src/pages/index.js`:
884
+ 1️⃣ Register in `src/pages/manual.index.js` (or `src/pages/guide.index.js`):
838
885
 
839
886
  ```javascript
887
+ import { definePage } from '@docsector/docsector-reader'
888
+
840
889
  export default {
841
- '/manual/my-section/my-page': {
890
+ '/my-section/my-page': definePage({
842
891
  config: {
843
892
  icon: 'description',
844
893
  status: 'done', // 'done' | 'draft' | 'empty'
845
- type: 'manual', // 'guide' | 'manual'
846
894
  menu: {
847
895
  header: { label: '.my-section', icon: 'category' }
848
896
  },
@@ -852,10 +900,16 @@ export default {
852
900
  'en-US': { title: 'My Page' },
853
901
  'pt-BR': { title: 'Minha Página' }
854
902
  }
855
- }
903
+ })
856
904
  }
857
905
  ```
858
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
+
859
913
  2️⃣ Create Markdown files:
860
914
 
861
915
  ```
@@ -863,6 +917,34 @@ src/pages/manual/my-section/my-page.overview.en-US.md
863
917
  src/pages/manual/my-section/my-page.overview.pt-BR.md
864
918
  ```
865
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
+
866
948
  ### GitHub-Style Alert Example
867
949
 
868
950
  ```markdown
@@ -896,9 +978,12 @@ docsector help # Show help
896
978
 
897
979
  | Import path | Export | Description |
898
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 |
899
984
  | `@docsector/docsector-reader/quasar-factory` | `createQuasarConfig()` | Config factory for consumer projects |
900
985
  | `@docsector/docsector-reader/quasar-factory` | `configure()` | No-op wrapper (avoids needing `quasar` dep) |
901
- | `@docsector/docsector-reader/i18n` | `buildMessages()` | Build i18n messages from globs + pages |
986
+ | `@docsector/docsector-reader/i18n` | `buildMessages()` | Build i18n messages from globs + book/page registries |
902
987
  | `@docsector/docsector-reader/i18n` | `filter()` | Filter i18n messages by locale |
903
988
 
904
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 = '1.7.0'
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': '^0.6.0',
74
+ '@docsector/docsector-reader': `^${VERSION}`,
75
75
  '@quasar/extras': '^1.16.12',
76
76
  'quasar': '^2.16.6',
77
77
  'vue': '^3.5.13',
@@ -264,6 +264,7 @@ const TEMPLATE_CSS_STUB = `\
264
264
  const TEMPLATE_I18N_INDEX = `\
265
265
  // @ Import i18n message builder from Docsector Reader
266
266
  import { buildMessages } from '@docsector/docsector-reader/i18n'
267
+ import homePageOverride from 'virtual:docsector-homepage-override'
267
268
 
268
269
  // @ Import language HJSON files (Vite-compatible eager import)
269
270
  const langModules = import.meta.glob('./languages/*.hjson', { eager: true })
@@ -272,9 +273,9 @@ const mdModules = import.meta.glob('../pages/**/*.md', { eager: true, query: '?r
272
273
 
273
274
  // @ Import pages
274
275
  import boot from 'pages/boot'
275
- import pages from 'pages'
276
+ import { allPages as pages } from 'virtual:docsector-books'
276
277
 
277
- export default buildMessages({ langModules, mdModules, pages, boot })
278
+ export default buildMessages({ langModules, mdModules, pages, boot, homePageOverride })
278
279
  `
279
280
 
280
281
  const TEMPLATE_I18N_HJSON = `\
@@ -382,14 +383,24 @@ const TEMPLATE_I18N_HJSON = `\
382
383
  }
383
384
  `
384
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
+
385
396
  const TEMPLATE_PAGES_INDEX = `\
386
397
  /**
387
- * Pages Registry
398
+ * Guide Pages Registry
388
399
  *
389
- * Define your documentation pages here. Each key is a URL path,
390
- * and each value configures the page's type, icon, status, and titles.
400
+ * Define guide pages here. Each key is a URL path,
401
+ * and each value configures the page's book, icon, status, and titles.
391
402
  *
392
- * config.type: top-level route prefix — 'manual', 'guide', etc.
403
+ * config.book: top-level route prefix — 'guide', 'manual', etc.
393
404
  * config.status: 'done' | 'draft' | 'empty'
394
405
  * config.meta.description: string or localized object for SEO/social description
395
406
  * config.icon: Material Design icon name
@@ -409,7 +420,7 @@ export default {
409
420
  'en-US': 'Get started quickly with setup and project structure.'
410
421
  }
411
422
  },
412
- type: 'guide',
423
+ book: 'guide',
413
424
  menu: {
414
425
  header: {
415
426
  icon: 'school',
@@ -951,7 +962,8 @@ Here's an overview of the project files:
951
962
  | \`docsector.config.js\` | Branding, links, languages, and GitHub config |
952
963
  | \`quasar.config.js\` | Quasar/Vite build configuration (via factory) |
953
964
  | \`.markdownlint.json\` | Markdown lint rules (allows Docsector custom tags) |
954
- | \`src/pages/index.js\` | Page registry defines all documentation pages |
965
+ | \`src/pages/guide.book.js\` | Guide book definition (tab metadata) |
966
+ | \`src/pages/guide.index.js\` | Guide page registry (routes + metadata) |
955
967
  | \`src/pages/boot.js\` | Boot metadata for the home page |
956
968
  | \`src/pages/Homepage.en-US.md\` | Home page content in Markdown |
957
969
  | \`src/pages/404Page.vue\` | Not found page |
@@ -962,8 +974,9 @@ Here's an overview of the project files:
962
974
 
963
975
  ## Adding a Page
964
976
 
965
- 1. Register the page in \`src/pages/index.js\`
966
- 2. Create the Markdown file at \`src/pages/{type}/{path}.overview.{lang}.md\`
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\`
967
980
  3. The page will automatically appear in the sidebar navigation
968
981
 
969
982
  ## Customization
@@ -1081,7 +1094,8 @@ function initProject (name) {
1081
1094
  ['src/css/app.sass', TEMPLATE_CSS_STUB],
1082
1095
  ['src/i18n/index.js', TEMPLATE_I18N_INDEX],
1083
1096
  ['src/i18n/languages/en-US.hjson', TEMPLATE_I18N_HJSON],
1084
- ['src/pages/index.js', TEMPLATE_PAGES_INDEX],
1097
+ ['src/pages/guide.book.js', TEMPLATE_PAGES_GUIDE_BOOK],
1098
+ ['src/pages/guide.index.js', TEMPLATE_PAGES_INDEX],
1085
1099
  ['src/pages/boot.js', TEMPLATE_PAGES_BOOT],
1086
1100
  ['src/pages/Homepage.en-US.md', TEMPLATE_HOMEPAGE_MD],
1087
1101
  ['src/pages/404Page.vue', TEMPLATE_404_PAGE],
@@ -1119,7 +1133,8 @@ function initProject (name) {
1119
1133
  console.log(' │ └── languages/')
1120
1134
  console.log(' │ └── en-US.hjson')
1121
1135
  console.log(' └── pages/')
1122
- console.log(' ├── index.js')
1136
+ console.log(' ├── guide.book.js')
1137
+ console.log(' ├── guide.index.js')
1123
1138
  console.log(' ├── boot.js')
1124
1139
  console.log(' ├── Homepage.en-US.md')
1125
1140
  console.log(' ├── 404Page.vue')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@docsector/docsector-reader",
3
- "version": "1.7.0",
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",
@@ -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(`_.${base}._`)
26
+ h = t(pageTitleI18nPath(base))
26
27
  }
27
28
 
28
29
  return h
@@ -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 = `_${route.replace(/_$/, '').replace(/\//g, '.')}.${subpage}.source`
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 path = `_.${meta.type}${label}._`
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
- // # Events
160
- // Create
161
- const routes = $router.options.routes.slice(0, -2) // Delete last 2 routes
162
- const itemsArray = []
163
-
164
- let nodeBasepath = ''
165
- let nodeIndex = 0
166
- for (const [index, route] of routes.entries()) {
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
- if (nodeBasepath !== '') {
184
- itemsArray[nodeIndex].push(item)
185
- } else {
186
- itemsArray.push(item)
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
- items.value = Object.freeze(itemsArray.filter(item => item !== undefined))
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
- const path = `_${item.path.replace(/_$/, '').replace(/\//g, '.')}._`
40
- return t(path)
41
+ return t(routeTitleI18nPath(item.path))
41
42
  }
42
43
 
43
44
  const getMenuItemSubheader = (meta) => {
44
- const subheader = meta.menu.subheader
45
- const path = `_.${meta.type}${subheader}._`
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.subheader">
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.separator" role="listitem">
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"