@docsector/docsector-reader 4.1.0 → 4.2.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 +2 -0
- package/bin/docsector.js +1 -1
- package/package.json +1 -1
- 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/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 +28 -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,40 @@
|
|
|
1
|
+
## Overview
|
|
2
|
+
|
|
3
|
+
API Reference blocks render a JSON document that follows the existing Quasar API schema directly inside Markdown.
|
|
4
|
+
|
|
5
|
+
This keeps the viewer compatible with Quasar-style API files while still allowing non-Vue APIs to reuse the same section model for props, methods, events, values, arguments, and config shapes.
|
|
6
|
+
|
|
7
|
+
The block is authored with the custom Markdown element `<d-block-api>`.
|
|
8
|
+
|
|
9
|
+
## Markdown Syntax
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<d-block-api src="/quasar-api/QSeparator.json" />
|
|
13
|
+
|
|
14
|
+
<d-block-api
|
|
15
|
+
src="/api/manual/http-client.json"
|
|
16
|
+
title="HTTP Client API"
|
|
17
|
+
page-link="true"
|
|
18
|
+
/>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Attributes
|
|
22
|
+
|
|
23
|
+
| Attribute | Purpose |
|
|
24
|
+
|-----------|---------|
|
|
25
|
+
| `src` | Same-origin JSON path to fetch in the browser |
|
|
26
|
+
| `title` | Optional header override shown above the API card |
|
|
27
|
+
| `page-link` | Shows the Docs button when the JSON has `meta.docsUrl` |
|
|
28
|
+
|
|
29
|
+
## JSON Source Model
|
|
30
|
+
|
|
31
|
+
- The first implementation follows the same delivery model as Quasar Docs: the JSON file is served as a public asset and fetched on demand.
|
|
32
|
+
- No Docsector-specific schema is required. If your file already follows the Quasar API structure, it can be rendered as-is.
|
|
33
|
+
- Non-Vue APIs can still use the same shape by filling the sections they need, such as `props`, `methods`, `events`, `value`, `arg`, or `quasarConfOptions`.
|
|
34
|
+
|
|
35
|
+
## Notes
|
|
36
|
+
|
|
37
|
+
- `props` are grouped into subtabs when more than one `category` is present.
|
|
38
|
+
- Entries marked with `internal: true` are hidden from the rendered block.
|
|
39
|
+
- The current version expects same-origin JSON assets so the browser can fetch them without CORS workarounds.
|
|
40
|
+
- If the JSON exposes `meta.docsUrl`, `page-link="true"` can surface a Docs button without changing the schema.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
## Visão geral
|
|
2
|
+
|
|
3
|
+
Os blocos de Referência de API renderizam um documento JSON que segue o schema de API já existente do Quasar diretamente dentro do Markdown.
|
|
4
|
+
|
|
5
|
+
Isso mantém o viewer compatível com arquivos de API no estilo do Quasar e ainda permite que APIs não-Vue reutilizem o mesmo modelo de seções para props, methods, events, values, arguments e estruturas de configuração.
|
|
6
|
+
|
|
7
|
+
O bloco é escrito com o elemento Markdown customizado `<d-block-api>`.
|
|
8
|
+
|
|
9
|
+
## Sintaxe Markdown
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<d-block-api src="/quasar-api/QSeparator.json" />
|
|
13
|
+
|
|
14
|
+
<d-block-api
|
|
15
|
+
src="/api/manual/http-client.json"
|
|
16
|
+
title="HTTP Client API"
|
|
17
|
+
page-link="true"
|
|
18
|
+
/>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Atributos
|
|
22
|
+
|
|
23
|
+
| Atributo | Finalidade |
|
|
24
|
+
|----------|------------|
|
|
25
|
+
| `src` | Caminho same-origin do JSON a ser buscado no navegador |
|
|
26
|
+
| `title` | Sobrescreve opcionalmente o título exibido acima do card |
|
|
27
|
+
| `page-link` | Exibe o botão Docs quando o JSON possui `meta.docsUrl` |
|
|
28
|
+
|
|
29
|
+
## Modelo da Fonte JSON
|
|
30
|
+
|
|
31
|
+
- A primeira implementação segue o mesmo modelo de entrega do Quasar Docs: o arquivo JSON é servido como asset público e carregado sob demanda.
|
|
32
|
+
- Nenhum schema específico do Docsector é exigido. Se o arquivo já seguir a estrutura de API do Quasar, ele pode ser renderizado sem alterações.
|
|
33
|
+
- APIs não-Vue ainda podem usar a mesma forma preenchendo apenas as seções necessárias, como `props`, `methods`, `events`, `value`, `arg` ou `quasarConfOptions`.
|
|
34
|
+
|
|
35
|
+
## Notas
|
|
36
|
+
|
|
37
|
+
- `props` são agrupadas em subtabs quando mais de uma `category` está presente.
|
|
38
|
+
- Entradas marcadas com `internal: true` são ocultadas do bloco renderizado.
|
|
39
|
+
- A versão atual espera assets JSON same-origin para que o navegador faça o fetch sem workarounds de CORS.
|
|
40
|
+
- Se o JSON expuser `meta.docsUrl`, `page-link="true"` pode exibir um botão Docs sem alterar o schema.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
## Showcase
|
|
2
|
+
|
|
3
|
+
### Quasar JSON Without Refactoring
|
|
4
|
+
|
|
5
|
+
This example renders a real Quasar API JSON file copied into `public/quasar-api/`.
|
|
6
|
+
|
|
7
|
+
<d-block-api src="/quasar-api/QSeparator.json" />
|
|
8
|
+
|
|
9
|
+
### Generic SDK JSON With the Same Schema
|
|
10
|
+
|
|
11
|
+
This example uses the same section model for a non-Vue HTTP client and also enables the optional Docs button.
|
|
12
|
+
|
|
13
|
+
<d-block-api src="/api/manual/http-client.json" title="HTTP Client API" page-link="true" />
|
|
14
|
+
|
|
15
|
+
## Authoring Syntax
|
|
16
|
+
|
|
17
|
+
```html
|
|
18
|
+
<d-block-api src="/quasar-api/QSeparator.json" />
|
|
19
|
+
|
|
20
|
+
<d-block-api
|
|
21
|
+
src="/api/manual/http-client.json"
|
|
22
|
+
title="HTTP Client API"
|
|
23
|
+
page-link="true"
|
|
24
|
+
/>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Features Visible Above
|
|
28
|
+
|
|
29
|
+
- **Quasar JSON compatibility** with a real file served from `public/quasar-api/`
|
|
30
|
+
- **Generic API support** without introducing a new schema
|
|
31
|
+
- **Local filter** across names and descriptions inside the loaded API sections
|
|
32
|
+
- **Grouped props subtabs** when multiple categories exist in the JSON
|
|
33
|
+
- **Optional Docs link** when `meta.docsUrl` is present and `page-link="true"` is used
|