@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 +2 -0
- package/README.md +94 -9
- package/bin/docsector.js +29 -14
- 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 +22 -4
- 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
|
@@ -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,
|
|
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
|
|
781
|
+
import { books } from 'virtual:docsector-books'
|
|
776
782
|
|
|
777
|
-
export default buildMessages({ langModules, mdModules,
|
|
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
|
-
│ │ ├──
|
|
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
|
-
'/
|
|
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 +
|
|
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 = '
|
|
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',
|
|
@@ -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 '
|
|
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
|
|
390
|
-
* 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.
|
|
391
402
|
*
|
|
392
|
-
* config.
|
|
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
|
-
|
|
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/
|
|
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.
|
|
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/
|
|
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(' ├──
|
|
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": "
|
|
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"
|