@docsector/docsector-reader 2.0.2 → 2.0.3
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 +1 -0
- package/bin/docsector.js +1 -1
- package/package.json +1 -1
- package/src/components/DMenuItem.vue +51 -19
- package/src/components/DPage.vue +15 -1
- package/src/layouts/DefaultLayout.vue +15 -1
- package/src/pages/guide.book.js +1 -1
- package/src/pages/manual.book.js +1 -1
package/README.md
CHANGED
|
@@ -47,6 +47,7 @@ Transform Markdown content into beautiful, navigable documentation sites — wit
|
|
|
47
47
|
- 🌍 **Internationalization (i18n)** — Multi-language support with HJSON locale files and per-page translations
|
|
48
48
|
- 🌗 **Dark/Light Mode** — Automatic theme switching with Quasar Dark Plugin
|
|
49
49
|
- 🔗 **Anchor Navigation** — Right-side Table of Contents tree with scroll tracking and auto-scroll to active section
|
|
50
|
+
- 🖱️ **Active Menu Item UX** — Active menu entries keep pointer cursor, clear URL hash without redundant navigation, and prevent accidental label text selection
|
|
50
51
|
- 🔎 **Search** — Menu search across all documentation content and tags
|
|
51
52
|
- 🌐 **WebMCP Browser Tools** — Registers in-page tools for browser agents with `registerTool` and optional `provideContext` fallback
|
|
52
53
|
- 📱 **Responsive** — Mobile-friendly with collapsible sidebar and drawers
|
package/bin/docsector.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@docsector/docsector-reader",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
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",
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
// defineProps is a compiler macro in <script setup>, no import needed
|
|
3
|
-
import { useRoute } from 'vue-router'
|
|
4
|
-
import { useQuasar } from 'quasar'
|
|
3
|
+
import { useRoute, useRouter } from 'vue-router'
|
|
5
4
|
import { useI18n } from 'vue-i18n'
|
|
6
5
|
|
|
7
6
|
import { namespacedLabelI18nPath, routeTitleI18nPath } from '../i18n/path'
|
|
8
7
|
|
|
9
|
-
const
|
|
8
|
+
const $route = useRoute()
|
|
9
|
+
const $router = useRouter()
|
|
10
|
+
const { t } = useI18n()
|
|
11
|
+
|
|
12
|
+
defineProps({
|
|
10
13
|
items: {
|
|
11
14
|
type: Number,
|
|
12
15
|
required: true
|
|
@@ -24,24 +27,20 @@ const props = defineProps({
|
|
|
24
27
|
required: true
|
|
25
28
|
},
|
|
26
29
|
founds: {
|
|
27
|
-
type: [Boolean, Array],
|
|
30
|
+
type: [Boolean, Array, Object],
|
|
28
31
|
required: true
|
|
29
32
|
}
|
|
30
33
|
})
|
|
31
34
|
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const getMenuItemHeaderBackground = () => {
|
|
37
|
-
return $q.dark.isActive ? 'background-color: #1D1D1D !important' : 'background-color: #f5f5f5 !important'
|
|
38
|
-
}
|
|
35
|
+
const getMenuItemLabel = (item) => {
|
|
36
|
+
if (!item?.path) {
|
|
37
|
+
return ''
|
|
38
|
+
}
|
|
39
39
|
|
|
40
|
-
const getMenuItemLabel = (item, index) => {
|
|
41
40
|
return t(routeTitleI18nPath(item.path))
|
|
42
41
|
}
|
|
43
42
|
|
|
44
|
-
const getMenuItemSubheader = (meta) => {
|
|
43
|
+
const getMenuItemSubheader = (meta = {}) => {
|
|
45
44
|
const subheader = meta.menu?.subheader
|
|
46
45
|
if (!subheader) {
|
|
47
46
|
return ''
|
|
@@ -97,28 +96,54 @@ const normalizePath = (path) => {
|
|
|
97
96
|
const isMenuItemActive = (path) => {
|
|
98
97
|
return normalizePath(path) === normalizePath($route.path)
|
|
99
98
|
}
|
|
99
|
+
|
|
100
|
+
const getMenuItemTargetPath = (path) => {
|
|
101
|
+
return `${path}/overview/`
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const getMenuItemTo = (path) => {
|
|
105
|
+
return getMenuItemTargetPath(path)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const onMenuItemClick = (event, path, currentSubpage) => {
|
|
109
|
+
const currentPath = `${path}${currentSubpage}`
|
|
110
|
+
if (!isMenuItemActive(currentPath)) {
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
event?.preventDefault?.()
|
|
115
|
+
event?.stopPropagation?.()
|
|
116
|
+
|
|
117
|
+
if ($route.hash) {
|
|
118
|
+
$router.replace({ path: $route.path, hash: '' })
|
|
119
|
+
}
|
|
120
|
+
}
|
|
100
121
|
</script>
|
|
101
122
|
|
|
102
123
|
<template>
|
|
103
124
|
<!-- Menu Separator - Subheader -->
|
|
104
|
-
<q-item-section v-if="subitem
|
|
125
|
+
<q-item-section v-if="subitem?.meta?.menu?.subheader">
|
|
105
126
|
<q-item-label class="label subheader" header>
|
|
106
127
|
{{ getMenuItemSubheader(subitem.meta) }}
|
|
107
128
|
</q-item-label>
|
|
108
129
|
</q-item-section>
|
|
109
130
|
|
|
110
131
|
<q-item
|
|
111
|
-
|
|
132
|
+
v-if="subitem?.path"
|
|
133
|
+
:to="getMenuItemTo(subitem.path)"
|
|
112
134
|
:active="isMenuItemActive(subitem.path + subpage)"
|
|
135
|
+
:class="{ 'd-menu-item--active': isMenuItemActive(subitem.path + subpage) }"
|
|
136
|
+
clickable
|
|
137
|
+
@click="onMenuItemClick($event, subitem.path, subpage)"
|
|
113
138
|
v-show="founds[subitem.path] || !founds"
|
|
114
139
|
>
|
|
115
140
|
<q-item-section side>
|
|
116
|
-
<q-icon v-if="subitem
|
|
141
|
+
<q-icon v-if="subitem?.meta?.icon" :name="subitem.meta.icon" />
|
|
117
142
|
</q-item-section>
|
|
118
143
|
<q-item-section>
|
|
119
|
-
{{ getMenuItemLabel(subitem
|
|
144
|
+
{{ getMenuItemLabel(subitem) }}
|
|
120
145
|
</q-item-section>
|
|
121
|
-
<q-item-section class="page-status" v-if="subitem.meta.status !== 'done'" side>
|
|
146
|
+
<q-item-section class="page-status" v-if="subitem?.meta && subitem.meta.status !== 'done'" side>
|
|
122
147
|
<q-badge
|
|
123
148
|
:text-color="getPageStatusTextColor(subitem.meta.status)"
|
|
124
149
|
:color="getPageStatusColor(subitem.meta.status)"
|
|
@@ -129,10 +154,17 @@ const isMenuItemActive = (path) => {
|
|
|
129
154
|
</q-item>
|
|
130
155
|
|
|
131
156
|
<!-- Menu Separator -->
|
|
132
|
-
<li v-if="subitem
|
|
157
|
+
<li v-if="subitem?.meta?.menu?.separator" role="listitem">
|
|
133
158
|
<q-separator
|
|
134
159
|
:class="'separator' + (subitem.meta.menu.separator === true ? '' : subitem.meta.menu.separator)"
|
|
135
160
|
role="separator"
|
|
136
161
|
/>
|
|
137
162
|
</li>
|
|
138
163
|
</template>
|
|
164
|
+
|
|
165
|
+
<style lang="sass">
|
|
166
|
+
.d-menu-item--active
|
|
167
|
+
cursor: pointer
|
|
168
|
+
user-select: none
|
|
169
|
+
-webkit-user-select: none
|
|
170
|
+
</style>
|
package/src/components/DPage.vue
CHANGED
|
@@ -94,6 +94,20 @@ const pActive = (relative) => {
|
|
|
94
94
|
return null
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
const normalizeRoutePath = (path) => {
|
|
98
|
+
const normalized = String(path || '').trim()
|
|
99
|
+
if (normalized === '' || normalized === '/') {
|
|
100
|
+
return '/'
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const sanitized = normalized.replace(/\/+$/, '')
|
|
104
|
+
return sanitized === '' ? '/' : sanitized
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const isSameEffectivePage = (from, to) => {
|
|
108
|
+
return normalizeRoutePath(from?.path) === normalizeRoutePath(to?.path)
|
|
109
|
+
}
|
|
110
|
+
|
|
97
111
|
const subroute = (to) => {
|
|
98
112
|
const base = '/' + store.state.page.base
|
|
99
113
|
const relative = store.state.page.relative
|
|
@@ -204,7 +218,7 @@ onMounted(() => {
|
|
|
204
218
|
router.beforeEach((to, from, next) => {
|
|
205
219
|
resetPageScroll()
|
|
206
220
|
|
|
207
|
-
if (to.hash === '' && from
|
|
221
|
+
if (to.hash === '' && !isSameEffectivePage(from, to)) {
|
|
208
222
|
store.commit('page/resetAnchor')
|
|
209
223
|
store.commit('page/resetAnchors')
|
|
210
224
|
store.commit('page/resetNodes')
|
|
@@ -294,11 +294,25 @@ function onBookTabChange (bookId) {
|
|
|
294
294
|
}
|
|
295
295
|
}
|
|
296
296
|
|
|
297
|
+
const normalizeRoutePath = (path) => {
|
|
298
|
+
const normalized = String(path || '').trim()
|
|
299
|
+
if (normalized === '' || normalized === '/') {
|
|
300
|
+
return '/'
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const sanitized = normalized.replace(/\/+$/, '')
|
|
304
|
+
return sanitized === '' ? '/' : sanitized
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const isSameEffectivePage = (from, to) => {
|
|
308
|
+
return normalizeRoutePath(from?.path) === normalizeRoutePath(to?.path)
|
|
309
|
+
}
|
|
310
|
+
|
|
297
311
|
// --- created logic (runs at setup time) ---
|
|
298
312
|
store.dispatch('app/configureLanguage', route.matched)
|
|
299
313
|
|
|
300
314
|
router.afterEach((to, from) => {
|
|
301
|
-
if (!
|
|
315
|
+
if (!isSameEffectivePage(from, to)) {
|
|
302
316
|
store.dispatch('app/configureLanguage', to.matched)
|
|
303
317
|
}
|
|
304
318
|
})
|
package/src/pages/guide.book.js
CHANGED