@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 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
@@ -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.2'
26
+ const VERSION = '2.0.3'
27
27
 
28
28
  const HELP = `
29
29
  Docsector Reader v${VERSION}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@docsector/docsector-reader",
3
- "version": "2.0.2",
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 props = defineProps({
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 $q = useQuasar()
33
- const $route = useRoute()
34
- const { t } = useI18n()
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.meta.menu?.subheader">
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
- :to="subitem.path + '/overview/'"
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.meta.icon" :name="subitem.meta.icon" />
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, subindex) }}
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.meta.menu?.separator" role="listitem">
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>
@@ -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.path !== to.path) {
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 (!to.hash || (from.path !== to.path)) {
315
+ if (!isSameEffectivePage(from, to)) {
302
316
  store.dispatch('app/configureLanguage', to.matched)
303
317
  }
304
318
  })
@@ -7,6 +7,6 @@ export default defineBook({
7
7
  order: 2,
8
8
  color: {
9
9
  active: 'white',
10
- inactive: 'secondary'
10
+ inactive: 'white'
11
11
  }
12
12
  })
@@ -7,6 +7,6 @@ export default defineBook({
7
7
  order: 1,
8
8
  color: {
9
9
  active: 'white',
10
- inactive: 'primary'
10
+ inactive: 'white'
11
11
  }
12
12
  })