@docsector/docsector-reader 4.1.0 → 4.3.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/README.md +16 -6
- package/bin/docsector.js +1 -1
- package/docsector.config.js +15 -0
- package/package.json +1 -1
- package/public/.well-known/agent-skills/docsector-documentation-authoring/SKILL.md +118 -0
- package/public/.well-known/agent-skills/docsector-documentation-authoring/references/authoring-patterns.md +98 -0
- package/public/.well-known/agent-skills/docsector-documentation-authoring/references/block-catalog.md +321 -0
- package/public/.well-known/agent-skills/docsector-documentation-authoring/references/mcp-webmcp.md +90 -0
- package/public/.well-known/agent-skills/docsector-documentation-authoring/references/page-structure.md +101 -0
- package/public/api/manual/http-client.json +91 -0
- package/public/quasar-api/QSeparator.json +39 -0
- package/src/components/DBlockApi.vue +634 -0
- package/src/components/DBlockApiEntry.js +623 -0
- package/src/components/DBlockCodeExample.vue +22 -0
- package/src/components/DBlockSourceCode.vue +2 -4
- package/src/components/DMenu.vue +70 -25
- package/src/components/DPageTokens.vue +8 -0
- package/src/components/api-block-model.js +326 -0
- package/src/components/page-section-tokens.js +53 -1
- package/src/components/source-code-lines.js +17 -0
- package/src/pages/manual/basic/agent-skills.overview.en-US.md +77 -0
- package/src/pages/manual/basic/agent-skills.overview.pt-BR.md +77 -0
- package/src/pages/manual/content/blocks/api-reference.overview.en-US.md +40 -0
- package/src/pages/manual/content/blocks/api-reference.overview.pt-BR.md +40 -0
- package/src/pages/manual/content/blocks/api-reference.showcase.en-US.md +33 -0
- package/src/pages/manual/content/blocks/api-reference.showcase.pt-BR.md +33 -0
- package/src/pages/manual.index.js +57 -0
package/src/components/DMenu.vue
CHANGED
|
@@ -22,6 +22,8 @@ const term = ref(null)
|
|
|
22
22
|
const founds = ref(false)
|
|
23
23
|
const items = ref([])
|
|
24
24
|
const scrolling = ref(null)
|
|
25
|
+
const isMenuHovered = ref(false)
|
|
26
|
+
const pendingScroll = ref(false)
|
|
25
27
|
|
|
26
28
|
const subpage = computed(() => {
|
|
27
29
|
const parent = $route.matched[0]?.path
|
|
@@ -312,41 +314,79 @@ const getMenuItemHeaderLabel = (meta) => {
|
|
|
312
314
|
return label // String raw
|
|
313
315
|
}
|
|
314
316
|
|
|
317
|
+
const executeScrollToActiveMenuItem = () => {
|
|
318
|
+
const menu = document.getElementById('menu')
|
|
319
|
+
if (!menu) {
|
|
320
|
+
return
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const menuItemActive = (menu.getElementsByClassName('q-router-link--active'))[0]
|
|
324
|
+
if (!menuItemActive || typeof menuItemActive !== 'object') {
|
|
325
|
+
return
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const offsetTop1 = menuItemActive.closest('.menu-list-expansion')?.offsetTop ?? 0
|
|
329
|
+
const offsetTop2 = menuItemActive.offsetTop
|
|
330
|
+
|
|
331
|
+
const innerHeightBy2 = window.innerHeight / 2
|
|
332
|
+
|
|
333
|
+
const searchBarHeight = 50
|
|
334
|
+
let expansionHeaderHeight = 0
|
|
335
|
+
if (offsetTop1 > 0) {
|
|
336
|
+
expansionHeaderHeight = 45
|
|
337
|
+
}
|
|
338
|
+
const fixedHeight = searchBarHeight + expansionHeaderHeight
|
|
339
|
+
|
|
340
|
+
const target = scroll.getScrollTarget(menuItemActive)
|
|
341
|
+
const offset = (offsetTop1 + offsetTop2) - innerHeightBy2 + fixedHeight
|
|
342
|
+
const duration = 300
|
|
343
|
+
|
|
344
|
+
if (offset > 0) {
|
|
345
|
+
scroll.setVerticalScrollPosition(target, offset, duration)
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const flushPendingMenuScroll = () => {
|
|
350
|
+
if (!pendingScroll.value || isMenuHovered.value) {
|
|
351
|
+
return
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (scrolling.value) {
|
|
355
|
+
clearTimeout(scrolling.value)
|
|
356
|
+
scrolling.value = null
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
pendingScroll.value = false
|
|
360
|
+
executeScrollToActiveMenuItem()
|
|
361
|
+
}
|
|
362
|
+
|
|
315
363
|
const scrollToActiveMenuItem = () => {
|
|
364
|
+
pendingScroll.value = true
|
|
365
|
+
|
|
316
366
|
if (scrolling.value) {
|
|
317
367
|
clearTimeout(scrolling.value)
|
|
368
|
+
scrolling.value = null
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (isMenuHovered.value) {
|
|
372
|
+
return
|
|
318
373
|
}
|
|
319
374
|
|
|
320
375
|
scrolling.value = setTimeout(() => {
|
|
321
|
-
const menu = document.getElementById('menu')
|
|
322
|
-
if (menu) {
|
|
323
|
-
const menuItemActive = (menu.getElementsByClassName('q-router-link--active'))[0]
|
|
324
|
-
if (menuItemActive && typeof menuItemActive === 'object') {
|
|
325
|
-
const offsetTop1 = menuItemActive.closest('.menu-list-expansion')?.offsetTop ?? 0
|
|
326
|
-
const offsetTop2 = menuItemActive.offsetTop
|
|
327
|
-
|
|
328
|
-
const innerHeightBy2 = window.innerHeight / 2
|
|
329
|
-
|
|
330
|
-
const searchBarHeight = 50
|
|
331
|
-
let expansionHeaderHeight = 0
|
|
332
|
-
if (offsetTop1 > 0) {
|
|
333
|
-
expansionHeaderHeight = 45
|
|
334
|
-
}
|
|
335
|
-
const fixedHeight = searchBarHeight + expansionHeaderHeight
|
|
336
|
-
|
|
337
|
-
const target = scroll.getScrollTarget(menuItemActive)
|
|
338
|
-
const offset = (offsetTop1 + offsetTop2) - innerHeightBy2 + fixedHeight
|
|
339
|
-
const duration = 300
|
|
340
|
-
|
|
341
|
-
if (offset > 0) {
|
|
342
|
-
scroll.setVerticalScrollPosition(target, offset, duration)
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
376
|
scrolling.value = null
|
|
377
|
+
flushPendingMenuScroll()
|
|
347
378
|
}, 1500)
|
|
348
379
|
}
|
|
349
380
|
|
|
381
|
+
const handleMenuMouseEnter = () => {
|
|
382
|
+
isMenuHovered.value = true
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const handleMenuMouseLeave = () => {
|
|
386
|
+
isMenuHovered.value = false
|
|
387
|
+
flushPendingMenuScroll()
|
|
388
|
+
}
|
|
389
|
+
|
|
350
390
|
onMounted(() => {
|
|
351
391
|
scrollToActiveMenuItem()
|
|
352
392
|
|
|
@@ -361,6 +401,9 @@ onBeforeUnmount(() => {
|
|
|
361
401
|
if (scrolling.value) {
|
|
362
402
|
clearTimeout(scrolling.value)
|
|
363
403
|
}
|
|
404
|
+
|
|
405
|
+
isMenuHovered.value = false
|
|
406
|
+
pendingScroll.value = false
|
|
364
407
|
})
|
|
365
408
|
|
|
366
409
|
const buildMenuItems = () => {
|
|
@@ -428,6 +471,8 @@ watch([currentBookId, activeVersionId], rebuildItems)
|
|
|
428
471
|
</transition>
|
|
429
472
|
|
|
430
473
|
<q-scroll-area id="menu"
|
|
474
|
+
@mouseenter="handleMenuMouseEnter"
|
|
475
|
+
@mouseleave="handleMenuMouseLeave"
|
|
431
476
|
:visible="true"
|
|
432
477
|
:class="$q.dark.isActive ? '' : 'bg-grey-2'"
|
|
433
478
|
>
|
|
@@ -35,6 +35,7 @@ import DBlockTimeline from './DBlockTimeline.vue'
|
|
|
35
35
|
import DBlockExpandable from './DBlockExpandable.vue'
|
|
36
36
|
import DBlockStepper from './DBlockStepper.vue'
|
|
37
37
|
import DBlockCodeExample from './DBlockCodeExample.vue'
|
|
38
|
+
import DBlockApi from './DBlockApi.vue'
|
|
38
39
|
</script>
|
|
39
40
|
|
|
40
41
|
<template>
|
|
@@ -149,6 +150,13 @@ import DBlockCodeExample from './DBlockCodeExample.vue'
|
|
|
149
150
|
:height="token.height"
|
|
150
151
|
/>
|
|
151
152
|
|
|
153
|
+
<d-block-api
|
|
154
|
+
v-else-if="token.tag === 'api'"
|
|
155
|
+
:src="token.src"
|
|
156
|
+
:title="token.title"
|
|
157
|
+
:page-link="token.pageLink"
|
|
158
|
+
/>
|
|
159
|
+
|
|
152
160
|
<d-block-mermaid-diagram
|
|
153
161
|
v-else-if="token.tag === 'mermaid'"
|
|
154
162
|
:content="token.content"
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
const defaultInnerTabName = '__default'
|
|
2
|
+
const fallbackCategoryName = 'general'
|
|
3
|
+
|
|
4
|
+
const isPlainObject = (value) => {
|
|
5
|
+
return Object.prototype.toString.call(value) === '[object Object]'
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const isSupportedTopLevelSection = (value) => {
|
|
9
|
+
return typeof value === 'string' || isPlainObject(value)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const getEntryCategories = (entry = {}) => {
|
|
13
|
+
const raw = String(entry?.category || '').trim()
|
|
14
|
+
|
|
15
|
+
if (raw === '') {
|
|
16
|
+
return [fallbackCategoryName]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const groups = raw
|
|
20
|
+
.split('|')
|
|
21
|
+
.map((value) => value.trim())
|
|
22
|
+
.filter(Boolean)
|
|
23
|
+
|
|
24
|
+
return groups.length === 0 ? [fallbackCategoryName] : groups
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const pruneInternalEntries = (value) => {
|
|
28
|
+
if (Array.isArray(value)) {
|
|
29
|
+
return value
|
|
30
|
+
.map((entry) => pruneInternalEntries(entry))
|
|
31
|
+
.filter((entry) => entry !== undefined)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!isPlainObject(value)) {
|
|
35
|
+
return value
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (value.internal === true) {
|
|
39
|
+
return undefined
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const acc = {}
|
|
43
|
+
|
|
44
|
+
Object.entries(value).forEach(([key, entryValue]) => {
|
|
45
|
+
if (key === 'internal') {
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const nextValue = pruneInternalEntries(entryValue)
|
|
50
|
+
|
|
51
|
+
if (nextValue !== undefined) {
|
|
52
|
+
acc[key] = nextValue
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
return acc
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const getApiSourceName = (sourceName = '') => {
|
|
60
|
+
const normalized = String(sourceName || '')
|
|
61
|
+
.split('?')[0]
|
|
62
|
+
.split('#')[0]
|
|
63
|
+
.replace(/\\/g, '/')
|
|
64
|
+
const fileName = normalized.split('/').filter(Boolean).pop() || ''
|
|
65
|
+
|
|
66
|
+
return fileName.replace(/\.json$/i, '') || 'API'
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const isEmptySingleEntry = (value) => {
|
|
70
|
+
if (value === undefined || value === null) {
|
|
71
|
+
return true
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (typeof value === 'string') {
|
|
75
|
+
return value.trim() === ''
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (Array.isArray(value)) {
|
|
79
|
+
return value.length === 0
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (isPlainObject(value)) {
|
|
83
|
+
return Object.keys(value).length === 0
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return false
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export const normalizeApiDocsLink = (value = '') => {
|
|
90
|
+
const normalized = String(value || '').trim()
|
|
91
|
+
|
|
92
|
+
if (normalized === '') {
|
|
93
|
+
return ''
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return normalized
|
|
97
|
+
.replace(/^https:\/\/v[\d]+\.quasar\.dev/i, '')
|
|
98
|
+
.replace(/^https:\/\/quasar\.dev/i, '')
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export const getPropsCategories = (props = {}) => {
|
|
102
|
+
const acc = new Set()
|
|
103
|
+
|
|
104
|
+
Object.values(props || {}).forEach((value) => {
|
|
105
|
+
if (value !== undefined && value !== null) {
|
|
106
|
+
getEntryCategories(value).forEach((groupKey) => {
|
|
107
|
+
acc.add(groupKey)
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
return acc.size === 1 ? [defaultInnerTabName] : [...acc].sort()
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export const getInnerTabs = (api = {}, tabs = []) => {
|
|
116
|
+
const acc = {}
|
|
117
|
+
|
|
118
|
+
tabs.forEach((tab) => {
|
|
119
|
+
acc[tab] = tab === 'props' && isPlainObject(api[tab])
|
|
120
|
+
? getPropsCategories(api[tab])
|
|
121
|
+
: [defaultInnerTabName]
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
return acc
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export const parseApi = (api = {}, tabs = [], innerTabs = {}) => {
|
|
128
|
+
const acc = {}
|
|
129
|
+
|
|
130
|
+
tabs.forEach((tab) => {
|
|
131
|
+
const apiValue = api[tab]
|
|
132
|
+
|
|
133
|
+
if (innerTabs[tab]?.length > 1 && isPlainObject(apiValue)) {
|
|
134
|
+
const inner = {}
|
|
135
|
+
|
|
136
|
+
innerTabs[tab].forEach((subTab) => {
|
|
137
|
+
inner[subTab] = {}
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
Object.entries(apiValue).forEach(([key, value]) => {
|
|
141
|
+
if (value === undefined || value === null) {
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
getEntryCategories(value).forEach((groupKey) => {
|
|
146
|
+
if (inner[groupKey] !== undefined) {
|
|
147
|
+
inner[groupKey][key] = value
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
acc[tab] = inner
|
|
153
|
+
return
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
acc[tab] = {
|
|
157
|
+
[defaultInnerTabName]: apiValue ?? {}
|
|
158
|
+
}
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
return acc
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const passesFilter = (filter, name, desc) => {
|
|
165
|
+
const normalizedFilter = String(filter || '').trim().toLowerCase()
|
|
166
|
+
|
|
167
|
+
if (normalizedFilter === '') {
|
|
168
|
+
return true
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
String(name || '').toLowerCase().includes(normalizedFilter) ||
|
|
173
|
+
String(desc || '').toLowerCase().includes(normalizedFilter)
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export const getFilteredApi = (parsedApi = {}, filter = '', tabs = [], innerTabs = {}) => {
|
|
178
|
+
const normalizedFilter = String(filter || '').trim().toLowerCase()
|
|
179
|
+
|
|
180
|
+
if (normalizedFilter === '') {
|
|
181
|
+
return parsedApi
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const acc = {}
|
|
185
|
+
|
|
186
|
+
tabs.forEach((tab) => {
|
|
187
|
+
if (tab === 'injection') {
|
|
188
|
+
const name = parsedApi?.[tab]?.[defaultInnerTabName]
|
|
189
|
+
|
|
190
|
+
acc[tab] = {
|
|
191
|
+
[defaultInnerTabName]: passesFilter(normalizedFilter, name, '') ? name : {}
|
|
192
|
+
}
|
|
193
|
+
return
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (tab === 'quasarConfOptions') {
|
|
197
|
+
const api = parsedApi?.[tab]?.[defaultInnerTabName] || {}
|
|
198
|
+
const result = {
|
|
199
|
+
...api,
|
|
200
|
+
definition: {}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
Object.entries(api.definition || {}).forEach(([name, entry]) => {
|
|
204
|
+
if (passesFilter(normalizedFilter, name, entry?.desc)) {
|
|
205
|
+
result.definition[name] = entry
|
|
206
|
+
}
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
acc[tab] = {
|
|
210
|
+
[defaultInnerTabName]: Object.keys(result.definition).length === 0 && !passesFilter(normalizedFilter, api.propName, '')
|
|
211
|
+
? {}
|
|
212
|
+
: result
|
|
213
|
+
}
|
|
214
|
+
return
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const tabApi = parsedApi?.[tab] || {}
|
|
218
|
+
const tabCategories = innerTabs[tab] || [defaultInnerTabName]
|
|
219
|
+
|
|
220
|
+
acc[tab] = {}
|
|
221
|
+
|
|
222
|
+
tabCategories.forEach((category) => {
|
|
223
|
+
const subTabs = {}
|
|
224
|
+
const categoryEntries = tabApi[category] || {}
|
|
225
|
+
|
|
226
|
+
Object.entries(categoryEntries).forEach(([name, entry]) => {
|
|
227
|
+
if (passesFilter(normalizedFilter, name, entry?.desc)) {
|
|
228
|
+
subTabs[name] = entry
|
|
229
|
+
}
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
acc[tab][category] = subTabs
|
|
233
|
+
})
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
return acc
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export const getApiCount = (parsedApi = {}, tabs = [], innerTabs = {}) => {
|
|
240
|
+
const acc = {}
|
|
241
|
+
|
|
242
|
+
tabs.forEach((tab) => {
|
|
243
|
+
const tabApi = parsedApi?.[tab] || {}
|
|
244
|
+
const tabCategories = innerTabs[tab] || [defaultInnerTabName]
|
|
245
|
+
|
|
246
|
+
if (['value', 'arg', 'injection'].includes(tab)) {
|
|
247
|
+
const value = tabApi[tabCategories[0]]
|
|
248
|
+
|
|
249
|
+
acc[tab] = {
|
|
250
|
+
overall: isEmptySingleEntry(value) ? 0 : 1
|
|
251
|
+
}
|
|
252
|
+
return
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (tab === 'quasarConfOptions') {
|
|
256
|
+
const api = tabApi[tabCategories[0]] || {}
|
|
257
|
+
|
|
258
|
+
acc[tab] = {
|
|
259
|
+
overall: Object.keys(api).length === 0
|
|
260
|
+
? 0
|
|
261
|
+
: api.definition === undefined
|
|
262
|
+
? 1
|
|
263
|
+
: Object.keys(api.definition || {}).length
|
|
264
|
+
}
|
|
265
|
+
return
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const nextValue = {
|
|
269
|
+
overall: 0,
|
|
270
|
+
category: {}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
tabCategories.forEach((category) => {
|
|
274
|
+
const count = Object.keys(tabApi[category] || {}).length
|
|
275
|
+
|
|
276
|
+
nextValue.category[category] = count
|
|
277
|
+
nextValue.overall += count
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
acc[tab] = nextValue
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
return acc
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export const createApiBlockModel = (sourceName = '', apiDocument = {}) => {
|
|
287
|
+
const rawDocument = isPlainObject(apiDocument) ? apiDocument : {}
|
|
288
|
+
const {
|
|
289
|
+
type: _type,
|
|
290
|
+
behavior: _behavior,
|
|
291
|
+
meta,
|
|
292
|
+
addedIn: _addedIn,
|
|
293
|
+
internal: _internalSection,
|
|
294
|
+
...apiSectionsRaw
|
|
295
|
+
} = rawDocument
|
|
296
|
+
const apiSections = {}
|
|
297
|
+
|
|
298
|
+
Object.entries(apiSectionsRaw).forEach(([sectionName, sectionValue]) => {
|
|
299
|
+
const sanitizedValue = pruneInternalEntries(sectionValue)
|
|
300
|
+
|
|
301
|
+
if (sanitizedValue !== undefined && isSupportedTopLevelSection(sanitizedValue)) {
|
|
302
|
+
apiSections[sectionName] = sanitizedValue
|
|
303
|
+
}
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
const tabs = Object.keys(apiSections)
|
|
307
|
+
const innerTabs = getInnerTabs(apiSections, tabs)
|
|
308
|
+
const api = parseApi(apiSections, tabs, innerTabs)
|
|
309
|
+
const sourceLabel = getApiSourceName(sourceName)
|
|
310
|
+
const docsUrl = String(meta?.docsUrl || '').trim()
|
|
311
|
+
|
|
312
|
+
return {
|
|
313
|
+
sourceLabel,
|
|
314
|
+
title: `${sourceLabel} API`,
|
|
315
|
+
docsUrl,
|
|
316
|
+
docsLink: normalizeApiDocsLink(docsUrl),
|
|
317
|
+
tabs,
|
|
318
|
+
innerTabs,
|
|
319
|
+
api,
|
|
320
|
+
nothingToShow: tabs.length === 0
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export {
|
|
325
|
+
defaultInnerTabName
|
|
326
|
+
}
|
|
@@ -21,6 +21,7 @@ const EXPANDABLE_MARKER_PREFIX = '@@DOCSECTOR_EXPANDABLE_'
|
|
|
21
21
|
const FILE_MARKER_PREFIX = '@@DOCSECTOR_FILE_'
|
|
22
22
|
const EMBEDDED_URL_MARKER_PREFIX = '@@DOCSECTOR_EMBEDDED_URL_'
|
|
23
23
|
const CODE_EXAMPLE_MARKER_PREFIX = '@@DOCSECTOR_CODE_EXAMPLE_'
|
|
24
|
+
const API_BLOCK_MARKER_PREFIX = '@@DOCSECTOR_API_BLOCK_'
|
|
24
25
|
const CODE_SEGMENT_MARKER_PREFIX = '@@DOCSECTOR_CODE_SEGMENT_'
|
|
25
26
|
const MATH_KATEX_OPTIONS = {
|
|
26
27
|
throwOnError: false,
|
|
@@ -576,6 +577,43 @@ const extractCodeExampleBlocks = (source = '') => {
|
|
|
576
577
|
}
|
|
577
578
|
}
|
|
578
579
|
|
|
580
|
+
const extractApiBlocks = (source = '') => {
|
|
581
|
+
const map = new Map()
|
|
582
|
+
let index = 0
|
|
583
|
+
|
|
584
|
+
const replaceBlock = (match, rawAttrs) => {
|
|
585
|
+
const attrs = parseCustomTagAttributes(rawAttrs)
|
|
586
|
+
const src = decodeHtmlEntities(attrs.src || '').trim()
|
|
587
|
+
|
|
588
|
+
if (!src) {
|
|
589
|
+
return match
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
const marker = `${API_BLOCK_MARKER_PREFIX}${index}@@`
|
|
593
|
+
index++
|
|
594
|
+
|
|
595
|
+
map.set(marker, {
|
|
596
|
+
src,
|
|
597
|
+
title: decodeHtmlEntities(attrs.title || '').trim(),
|
|
598
|
+
pageLink: parseBooleanAttribute(attrs['page-link'], false)
|
|
599
|
+
})
|
|
600
|
+
|
|
601
|
+
return `\n${marker}\n`
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
const replacedSelfClosing = String(source).replace(/<d-block-api\b([^>]*)\/\s*>/gi, (match, rawAttrs) => {
|
|
605
|
+
return replaceBlock(match, rawAttrs)
|
|
606
|
+
})
|
|
607
|
+
const replaced = replacedSelfClosing.replace(/<d-block-api\b([^>]*)>([\s\S]*?)<\/d-block-api>/gi, (match, rawAttrs) => {
|
|
608
|
+
return replaceBlock(match, rawAttrs)
|
|
609
|
+
})
|
|
610
|
+
|
|
611
|
+
return {
|
|
612
|
+
source: replaced,
|
|
613
|
+
apiBlockMap: map
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
579
617
|
const parseFenceAttributes = (raw = '') => {
|
|
580
618
|
const parsed = {}
|
|
581
619
|
const pattern = /([\w-]+)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s;]+))/g
|
|
@@ -931,6 +969,7 @@ export const tokenizePageSectionSource = (source = '', options = {}) => {
|
|
|
931
969
|
const { source: sourceWithFiles, fileMap } = extractFileBlocks(sourceWithQuickLinks)
|
|
932
970
|
const { source: sourceWithEmbeddedUrls, embeddedUrlMap } = extractEmbeddedUrlBlocks(sourceWithFiles)
|
|
933
971
|
const { source: sourceWithCodeExamples, codeExampleMap } = extractCodeExampleBlocks(sourceWithEmbeddedUrls)
|
|
972
|
+
const { source: sourceWithApiBlocks, apiBlockMap } = extractApiBlocks(sourceWithCodeExamples)
|
|
934
973
|
|
|
935
974
|
fileMap.forEach((data, marker) => {
|
|
936
975
|
fileMap.set(marker, {
|
|
@@ -956,7 +995,7 @@ export const tokenizePageSectionSource = (source = '', options = {}) => {
|
|
|
956
995
|
const markdown = createMarkdownBlockParser()
|
|
957
996
|
const markdownInline = createMarkdownInlineParser()
|
|
958
997
|
const markdownEnv = {}
|
|
959
|
-
const parsed = markdown.parse(restoreShieldedCodeSegments(
|
|
998
|
+
const parsed = markdown.parse(restoreShieldedCodeSegments(sourceWithApiBlocks, codeSegmentsMap), markdownEnv)
|
|
960
999
|
const tokens = []
|
|
961
1000
|
|
|
962
1001
|
let level = 0
|
|
@@ -1234,6 +1273,19 @@ export const tokenizePageSectionSource = (source = '', options = {}) => {
|
|
|
1234
1273
|
break
|
|
1235
1274
|
}
|
|
1236
1275
|
|
|
1276
|
+
if (apiBlockMap.has(element.content.trim())) {
|
|
1277
|
+
const data = apiBlockMap.get(element.content.trim())
|
|
1278
|
+
|
|
1279
|
+
tokens.push({
|
|
1280
|
+
tag: 'api',
|
|
1281
|
+
map: element.map,
|
|
1282
|
+
src: data.src,
|
|
1283
|
+
title: data.title,
|
|
1284
|
+
pageLink: data.pageLink
|
|
1285
|
+
})
|
|
1286
|
+
break
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1237
1289
|
if (tag === 'p') {
|
|
1238
1290
|
const imageToken = parseStandaloneImageToken(element.content)
|
|
1239
1291
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const normalizeLineBreaks = (text = '') => String(text || '').replace(/\r\n/g, '\n')
|
|
2
|
+
|
|
3
|
+
export const countRenderedCodeLines = (text = '') => {
|
|
4
|
+
const normalized = normalizeLineBreaks(text)
|
|
5
|
+
|
|
6
|
+
if (normalized === '') {
|
|
7
|
+
return 0
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const withoutTerminalBreak = normalized.replace(/\n$/, '')
|
|
11
|
+
|
|
12
|
+
if (withoutTerminalBreak === '') {
|
|
13
|
+
return 1
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return withoutTerminalBreak.split('\n').length
|
|
17
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
## Overview
|
|
2
|
+
|
|
3
|
+
Docsector Reader ships a public authoring skill for AI agents that need to understand how Docsector documentation works.
|
|
4
|
+
|
|
5
|
+
The skill is a `SKILL.md` file with companion references. It explains Docsector Markdown conventions, every documented block, page structure, asset paths, authoring patterns, MCP lookup, and WebMCP browser tools.
|
|
6
|
+
|
|
7
|
+
## Public URLs
|
|
8
|
+
|
|
9
|
+
The built-in skill is published as a static artifact:
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
/.well-known/agent-skills/docsector-documentation-authoring/SKILL.md
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The discovery index points agents to the same file and includes a SHA-256 digest:
|
|
16
|
+
|
|
17
|
+
```text
|
|
18
|
+
/.well-known/agent-skills/index.json
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## What the Skill Contains
|
|
22
|
+
|
|
23
|
+
<d-block-quick-links title="Skill references">
|
|
24
|
+
<d-block-quick-link
|
|
25
|
+
title="SKILL.md"
|
|
26
|
+
description="The compact workflow agents load first"
|
|
27
|
+
href="/.well-known/agent-skills/docsector-documentation-authoring/SKILL.md"
|
|
28
|
+
/>
|
|
29
|
+
<d-block-quick-link
|
|
30
|
+
title="Block catalog"
|
|
31
|
+
description="All Docsector blocks, syntax, and when-to-use guidance"
|
|
32
|
+
href="/.well-known/agent-skills/docsector-documentation-authoring/references/block-catalog.md"
|
|
33
|
+
/>
|
|
34
|
+
<d-block-quick-link
|
|
35
|
+
title="Page structure"
|
|
36
|
+
description="Page files, locales, assets, examples, and API JSON conventions"
|
|
37
|
+
href="/.well-known/agent-skills/docsector-documentation-authoring/references/page-structure.md"
|
|
38
|
+
/>
|
|
39
|
+
<d-block-quick-link
|
|
40
|
+
title="MCP and WebMCP"
|
|
41
|
+
description="How agents can search, fetch, navigate, and copy live docs"
|
|
42
|
+
href="/.well-known/agent-skills/docsector-documentation-authoring/references/mcp-webmcp.md"
|
|
43
|
+
/>
|
|
44
|
+
</d-block-quick-links>
|
|
45
|
+
|
|
46
|
+
## Local and Published Copies
|
|
47
|
+
|
|
48
|
+
Docsector keeps two synchronized copies of the same skill:
|
|
49
|
+
|
|
50
|
+
- `.github/skills/docsector-documentation-authoring/` for repository-local assistants such as GitHub Copilot in VS Code.
|
|
51
|
+
- `public/.well-known/agent-skills/docsector-documentation-authoring/` for the built documentation site.
|
|
52
|
+
|
|
53
|
+
During build, Docsector copies the public artifact into `dist/spa/.well-known/agent-skills/` and generates the discovery index.
|
|
54
|
+
|
|
55
|
+
## How Agents Should Use It
|
|
56
|
+
|
|
57
|
+
Agents should load `SKILL.md` first, then open reference files only when a task needs more detail.
|
|
58
|
+
|
|
59
|
+
For current page examples, agents can combine the skill with Docsector's MCP tools:
|
|
60
|
+
|
|
61
|
+
```text
|
|
62
|
+
search_docsector
|
|
63
|
+
get_page_docsector
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Browser agents can also use WebMCP tools when `navigator.modelContext` is available:
|
|
67
|
+
|
|
68
|
+
- `docs.search_docs`
|
|
69
|
+
- `docs.get_page`
|
|
70
|
+
- `docs.navigate_to`
|
|
71
|
+
- `docs.copy_current_page`
|
|
72
|
+
|
|
73
|
+
## When to Publish Your Own Skill
|
|
74
|
+
|
|
75
|
+
Publish a project-specific skill when your documentation has domain rules that are not covered by the built-in Docsector authoring skill.
|
|
76
|
+
|
|
77
|
+
Use Docsector's `agentSkills` config to expose the skill through `/.well-known/agent-skills/index.json`, and keep the artifact under `public/.well-known/agent-skills/...` when you want Docsector to compute the digest automatically.
|