@docsector/docsector-reader 4.0.1 → 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 +19 -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 +445 -0
- package/src/components/DBlockSourceCode.vue +3 -11
- package/src/components/DMenu.vue +70 -25
- package/src/components/DPageTokens.vue +22 -0
- package/src/components/api-block-model.js +326 -0
- package/src/components/code-block-highlighting.js +16 -0
- package/src/components/code-example-source.js +363 -0
- package/src/components/page-section-tokens.js +141 -1
- package/src/components/source-code-lines.js +17 -0
- package/src/examples/manual/code-examples/BasicCounter.vue +63 -0
- package/src/examples/manual/code-examples/InlineNotice.vue +60 -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/content/blocks/code-examples.overview.en-US.md +56 -0
- package/src/pages/manual/content/blocks/code-examples.overview.pt-BR.md +56 -0
- package/src/pages/manual/content/blocks/code-examples.showcase.en-US.md +38 -0
- package/src/pages/manual/content/blocks/code-examples.showcase.pt-BR.md +38 -0
- package/src/pages/manual.index.js +56 -0
- package/src/quasar.factory.js +77 -0
- package/src/store/Page.js +26 -2
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed, markRaw, ref, watch } from 'vue'
|
|
3
|
+
import { openURL, Quasar, useQuasar } from 'quasar'
|
|
4
|
+
|
|
5
|
+
import { resolveCodeExample } from 'virtual:docsector-code-examples'
|
|
6
|
+
import docsectorConfig from 'docsector.config.js'
|
|
7
|
+
|
|
8
|
+
import DBlockSourceCode from './DBlockSourceCode.vue'
|
|
9
|
+
import {
|
|
10
|
+
canCreateCodepenPayload,
|
|
11
|
+
createCodeExampleGitHubUrl,
|
|
12
|
+
createCodeExampleTabs,
|
|
13
|
+
createCodepenPayload,
|
|
14
|
+
getCodepenUnsupportedReason
|
|
15
|
+
} from './code-example-source'
|
|
16
|
+
|
|
17
|
+
defineOptions({
|
|
18
|
+
name: 'DBlockCodeExample'
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const props = defineProps({
|
|
22
|
+
index: {
|
|
23
|
+
type: Number,
|
|
24
|
+
required: true
|
|
25
|
+
},
|
|
26
|
+
src: {
|
|
27
|
+
type: String,
|
|
28
|
+
default: ''
|
|
29
|
+
},
|
|
30
|
+
title: {
|
|
31
|
+
type: String,
|
|
32
|
+
default: ''
|
|
33
|
+
},
|
|
34
|
+
caption: {
|
|
35
|
+
type: String,
|
|
36
|
+
default: ''
|
|
37
|
+
},
|
|
38
|
+
expanded: {
|
|
39
|
+
type: Boolean,
|
|
40
|
+
default: false
|
|
41
|
+
},
|
|
42
|
+
codepen: {
|
|
43
|
+
type: Boolean,
|
|
44
|
+
default: true
|
|
45
|
+
},
|
|
46
|
+
scrollable: {
|
|
47
|
+
type: Boolean,
|
|
48
|
+
default: false
|
|
49
|
+
},
|
|
50
|
+
overflow: {
|
|
51
|
+
type: Boolean,
|
|
52
|
+
default: false
|
|
53
|
+
},
|
|
54
|
+
height: {
|
|
55
|
+
type: String,
|
|
56
|
+
default: ''
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
const $q = useQuasar()
|
|
61
|
+
|
|
62
|
+
const isBusy = ref(false)
|
|
63
|
+
const errorMessage = ref('')
|
|
64
|
+
const component = ref(null)
|
|
65
|
+
const sourceText = ref('')
|
|
66
|
+
const sourceTabs = ref([])
|
|
67
|
+
const sourceOpen = ref(props.expanded)
|
|
68
|
+
const exampleFilePath = ref('')
|
|
69
|
+
let requestIndex = 0
|
|
70
|
+
|
|
71
|
+
const displayTitle = computed(() => props.title || props.src || 'Code example')
|
|
72
|
+
const frameTone = computed(() => $q.dark.isActive ? 'dark' : 'light')
|
|
73
|
+
const hasSource = computed(() => sourceText.value.trim().length > 0)
|
|
74
|
+
const codepenUnsupportedReason = computed(() => {
|
|
75
|
+
if (!hasSource.value) {
|
|
76
|
+
return 'Source code is still loading.'
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return getCodepenUnsupportedReason(sourceText.value)
|
|
80
|
+
})
|
|
81
|
+
const canOpenCodepen = computed(() => props.codepen && hasSource.value && canCreateCodepenPayload(sourceText.value))
|
|
82
|
+
const codepenTooltip = computed(() => canOpenCodepen.value ? 'Edit in CodePen' : codepenUnsupportedReason.value)
|
|
83
|
+
const githubUrl = computed(() => createCodeExampleGitHubUrl(exampleFilePath.value, docsectorConfig))
|
|
84
|
+
const canOpenGitHub = computed(() => githubUrl.value !== '')
|
|
85
|
+
const previewStyle = computed(() => {
|
|
86
|
+
const style = {}
|
|
87
|
+
const normalizedHeight = normalizeCssLength(props.height)
|
|
88
|
+
|
|
89
|
+
if (normalizedHeight) {
|
|
90
|
+
style.height = normalizedHeight
|
|
91
|
+
} else if (props.scrollable) {
|
|
92
|
+
style.height = '500px'
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return style
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
watch(() => props.expanded, (value) => {
|
|
99
|
+
sourceOpen.value = value
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
watch(() => props.src, () => {
|
|
103
|
+
loadExample()
|
|
104
|
+
}, { immediate: true })
|
|
105
|
+
|
|
106
|
+
function normalizeCssLength (value = '') {
|
|
107
|
+
const normalized = String(value || '').trim()
|
|
108
|
+
|
|
109
|
+
if (!normalized) {
|
|
110
|
+
return ''
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (/^-?\d+(?:\.\d+)?$/.test(normalized)) {
|
|
114
|
+
return `${normalized}px`
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return normalized
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function loadExample () {
|
|
121
|
+
const currentRequest = ++requestIndex
|
|
122
|
+
const resolved = resolveCodeExample(props.src)
|
|
123
|
+
|
|
124
|
+
isBusy.value = true
|
|
125
|
+
errorMessage.value = ''
|
|
126
|
+
component.value = null
|
|
127
|
+
sourceText.value = ''
|
|
128
|
+
sourceTabs.value = []
|
|
129
|
+
exampleFilePath.value = ''
|
|
130
|
+
|
|
131
|
+
if (!props.src) {
|
|
132
|
+
errorMessage.value = 'Code example source is missing.'
|
|
133
|
+
isBusy.value = false
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!resolved.exists || typeof resolved.loadComponent !== 'function' || typeof resolved.loadSource !== 'function') {
|
|
138
|
+
errorMessage.value = `Code example not found: ${resolved.id || props.src}`
|
|
139
|
+
isBusy.value = false
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
const [componentModule, rawSource] = await Promise.all([
|
|
145
|
+
resolved.loadComponent(),
|
|
146
|
+
resolved.loadSource()
|
|
147
|
+
])
|
|
148
|
+
|
|
149
|
+
if (currentRequest !== requestIndex) {
|
|
150
|
+
return
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
component.value = markRaw(componentModule.default || componentModule)
|
|
154
|
+
sourceText.value = String(rawSource || '')
|
|
155
|
+
sourceTabs.value = createCodeExampleTabs(sourceText.value)
|
|
156
|
+
exampleFilePath.value = resolved.filePath || ''
|
|
157
|
+
} catch (err) {
|
|
158
|
+
if (currentRequest !== requestIndex) {
|
|
159
|
+
return
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
errorMessage.value = err?.message || `Unable to load code example: ${props.src}`
|
|
163
|
+
} finally {
|
|
164
|
+
if (currentRequest === requestIndex) {
|
|
165
|
+
isBusy.value = false
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function toggleSource () {
|
|
171
|
+
sourceOpen.value = !sourceOpen.value
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function submitCodepenPayload (payload) {
|
|
175
|
+
if (typeof document === 'undefined') {
|
|
176
|
+
return
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const form = document.createElement('form')
|
|
180
|
+
const input = document.createElement('input')
|
|
181
|
+
|
|
182
|
+
form.method = 'post'
|
|
183
|
+
form.action = 'https://codepen.io/pen/define/'
|
|
184
|
+
form.target = '_blank'
|
|
185
|
+
form.rel = 'noopener'
|
|
186
|
+
form.style.display = 'none'
|
|
187
|
+
|
|
188
|
+
input.type = 'hidden'
|
|
189
|
+
input.name = 'data'
|
|
190
|
+
input.value = JSON.stringify(payload)
|
|
191
|
+
|
|
192
|
+
form.appendChild(input)
|
|
193
|
+
document.body.appendChild(form)
|
|
194
|
+
form.submit()
|
|
195
|
+
form.remove()
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function openCodepen () {
|
|
199
|
+
if (!canOpenCodepen.value) {
|
|
200
|
+
return
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const sourceUrl = typeof window === 'undefined'
|
|
204
|
+
? ''
|
|
205
|
+
: `${window.location.origin}${window.location.pathname}${window.location.hash}`
|
|
206
|
+
|
|
207
|
+
submitCodepenPayload(createCodepenPayload(sourceText.value, {
|
|
208
|
+
title: displayTitle.value,
|
|
209
|
+
quasarVersion: Quasar.version,
|
|
210
|
+
sourceUrl
|
|
211
|
+
}))
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function openGitHub () {
|
|
215
|
+
if (canOpenGitHub.value) {
|
|
216
|
+
openURL(githubUrl.value)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
</script>
|
|
220
|
+
|
|
221
|
+
<template>
|
|
222
|
+
<div
|
|
223
|
+
class="d-block-code-example"
|
|
224
|
+
:class="`d-block-code-example--${frameTone}`"
|
|
225
|
+
>
|
|
226
|
+
<div class="d-block-code-example__toolbar">
|
|
227
|
+
<div class="d-block-code-example__title">{{ displayTitle }}</div>
|
|
228
|
+
|
|
229
|
+
<q-space />
|
|
230
|
+
|
|
231
|
+
<q-btn
|
|
232
|
+
class="d-block-code-example__button"
|
|
233
|
+
dense
|
|
234
|
+
flat
|
|
235
|
+
round
|
|
236
|
+
icon="fab fa-github"
|
|
237
|
+
:disable="!canOpenGitHub"
|
|
238
|
+
aria-label="View example on GitHub"
|
|
239
|
+
@click="openGitHub"
|
|
240
|
+
>
|
|
241
|
+
<q-tooltip>{{ canOpenGitHub ? 'View on GitHub' : 'GitHub source is unavailable' }}</q-tooltip>
|
|
242
|
+
</q-btn>
|
|
243
|
+
|
|
244
|
+
<q-btn
|
|
245
|
+
v-if="codepen"
|
|
246
|
+
class="d-block-code-example__button"
|
|
247
|
+
dense
|
|
248
|
+
flat
|
|
249
|
+
round
|
|
250
|
+
icon="fab fa-codepen"
|
|
251
|
+
:disable="!canOpenCodepen"
|
|
252
|
+
aria-label="Edit in CodePen"
|
|
253
|
+
@click="openCodepen"
|
|
254
|
+
>
|
|
255
|
+
<q-tooltip>{{ codepenTooltip }}</q-tooltip>
|
|
256
|
+
</q-btn>
|
|
257
|
+
|
|
258
|
+
<q-btn
|
|
259
|
+
class="d-block-code-example__button"
|
|
260
|
+
dense
|
|
261
|
+
flat
|
|
262
|
+
round
|
|
263
|
+
icon="code"
|
|
264
|
+
:disable="!hasSource"
|
|
265
|
+
:aria-label="sourceOpen ? 'Hide source' : 'View source'"
|
|
266
|
+
@click="toggleSource"
|
|
267
|
+
>
|
|
268
|
+
<q-tooltip>{{ sourceOpen ? 'Hide source' : 'View source' }}</q-tooltip>
|
|
269
|
+
</q-btn>
|
|
270
|
+
</div>
|
|
271
|
+
|
|
272
|
+
<q-slide-transition>
|
|
273
|
+
<div
|
|
274
|
+
v-show="sourceOpen && hasSource"
|
|
275
|
+
class="d-block-code-example__source"
|
|
276
|
+
>
|
|
277
|
+
<d-block-source-code
|
|
278
|
+
:index="index"
|
|
279
|
+
language="vue"
|
|
280
|
+
:text="sourceText"
|
|
281
|
+
:tabs="sourceTabs"
|
|
282
|
+
/>
|
|
283
|
+
</div>
|
|
284
|
+
</q-slide-transition>
|
|
285
|
+
|
|
286
|
+
<q-linear-progress
|
|
287
|
+
v-if="isBusy"
|
|
288
|
+
color="primary"
|
|
289
|
+
indeterminate
|
|
290
|
+
/>
|
|
291
|
+
|
|
292
|
+
<div
|
|
293
|
+
class="d-block-code-example__preview"
|
|
294
|
+
:class="{
|
|
295
|
+
'd-block-code-example__preview--scrollable': scrollable,
|
|
296
|
+
'd-block-code-example__preview--overflow': overflow
|
|
297
|
+
}"
|
|
298
|
+
:style="previewStyle"
|
|
299
|
+
>
|
|
300
|
+
<component
|
|
301
|
+
v-if="component && !errorMessage"
|
|
302
|
+
:is="component"
|
|
303
|
+
class="d-block-code-example__component"
|
|
304
|
+
/>
|
|
305
|
+
|
|
306
|
+
<div
|
|
307
|
+
v-else-if="errorMessage"
|
|
308
|
+
class="d-block-code-example__fallback"
|
|
309
|
+
>
|
|
310
|
+
<q-icon
|
|
311
|
+
name="warning"
|
|
312
|
+
size="22px"
|
|
313
|
+
/>
|
|
314
|
+
<span>{{ errorMessage }}</span>
|
|
315
|
+
</div>
|
|
316
|
+
</div>
|
|
317
|
+
|
|
318
|
+
<div
|
|
319
|
+
v-if="caption"
|
|
320
|
+
class="d-block-code-example__caption"
|
|
321
|
+
v-html="caption"
|
|
322
|
+
></div>
|
|
323
|
+
</div>
|
|
324
|
+
</template>
|
|
325
|
+
|
|
326
|
+
<style lang="sass">
|
|
327
|
+
body.body--light
|
|
328
|
+
--d-code-example-bg: #ffffff
|
|
329
|
+
--d-code-example-border: rgba(37, 67, 45, 0.16)
|
|
330
|
+
--d-code-example-button-bg: #edf3ee
|
|
331
|
+
--d-code-example-button-border: rgba(37, 67, 45, 0.12)
|
|
332
|
+
--d-code-example-button-hover-bg: #e2ebe4
|
|
333
|
+
--d-code-example-toolbar-bg: #f6f8f5
|
|
334
|
+
--d-code-example-toolbar-text: #26352b
|
|
335
|
+
--d-code-example-preview-bg: #ffffff
|
|
336
|
+
--d-code-example-caption: #405148
|
|
337
|
+
--d-code-example-muted: #5d7563
|
|
338
|
+
|
|
339
|
+
body.body--dark
|
|
340
|
+
--d-code-example-bg: #111512
|
|
341
|
+
--d-code-example-border: rgba(197, 220, 200, 0.18)
|
|
342
|
+
--d-code-example-button-bg: #29342d
|
|
343
|
+
--d-code-example-button-border: rgba(197, 220, 200, 0.18)
|
|
344
|
+
--d-code-example-button-hover-bg: #314036
|
|
345
|
+
--d-code-example-toolbar-bg: #1a211c
|
|
346
|
+
--d-code-example-toolbar-text: #e8efe9
|
|
347
|
+
--d-code-example-preview-bg: #0c0f0d
|
|
348
|
+
--d-code-example-caption: #c7d4ca
|
|
349
|
+
--d-code-example-muted: #9aafa0
|
|
350
|
+
|
|
351
|
+
.d-block-code-example
|
|
352
|
+
background: var(--d-code-example-bg)
|
|
353
|
+
border: 1px solid var(--d-code-example-border)
|
|
354
|
+
border-radius: 6px
|
|
355
|
+
box-shadow: 0 1px 1px rgb(0 0 0 / 8%)
|
|
356
|
+
margin: 18px 0
|
|
357
|
+
max-width: calc(100vw - 40px)
|
|
358
|
+
overflow: hidden
|
|
359
|
+
|
|
360
|
+
&__toolbar
|
|
361
|
+
align-items: center
|
|
362
|
+
background: var(--d-code-example-toolbar-bg)
|
|
363
|
+
color: var(--d-code-example-toolbar-text)
|
|
364
|
+
display: flex
|
|
365
|
+
gap: 4px
|
|
366
|
+
min-height: 42px
|
|
367
|
+
min-width: 0
|
|
368
|
+
padding: 4px 8px 4px 14px
|
|
369
|
+
|
|
370
|
+
&__title
|
|
371
|
+
font-size: 14px
|
|
372
|
+
font-weight: 600
|
|
373
|
+
line-height: 20px
|
|
374
|
+
min-width: 0
|
|
375
|
+
overflow: hidden
|
|
376
|
+
text-overflow: ellipsis
|
|
377
|
+
white-space: nowrap
|
|
378
|
+
|
|
379
|
+
&__button
|
|
380
|
+
background: var(--d-code-example-button-bg)
|
|
381
|
+
border: 1px solid var(--d-code-example-button-border)
|
|
382
|
+
border-radius: 999px
|
|
383
|
+
color: var(--d-code-example-muted)
|
|
384
|
+
flex: 0 0 auto
|
|
385
|
+
min-height: 30px
|
|
386
|
+
min-width: 30px
|
|
387
|
+
transition: background-color 0.18s ease, border-color 0.18s ease, color 0.18s ease
|
|
388
|
+
|
|
389
|
+
&:hover,
|
|
390
|
+
&:focus-visible
|
|
391
|
+
background: var(--d-code-example-button-hover-bg)
|
|
392
|
+
color: var(--d-code-example-toolbar-text)
|
|
393
|
+
|
|
394
|
+
&.q-btn--disabled,
|
|
395
|
+
&[disabled]
|
|
396
|
+
opacity: 0.55
|
|
397
|
+
|
|
398
|
+
&__source
|
|
399
|
+
border-top: 1px solid var(--d-code-example-border)
|
|
400
|
+
border-bottom: 1px solid var(--d-code-example-border)
|
|
401
|
+
|
|
402
|
+
.source-code
|
|
403
|
+
box-shadow: none
|
|
404
|
+
margin: 0
|
|
405
|
+
max-width: 100%
|
|
406
|
+
|
|
407
|
+
.source-code-frame
|
|
408
|
+
border: 0
|
|
409
|
+
border-radius: 0
|
|
410
|
+
|
|
411
|
+
&__preview
|
|
412
|
+
background: var(--d-code-example-preview-bg)
|
|
413
|
+
min-height: 96px
|
|
414
|
+
min-width: 0
|
|
415
|
+
overflow: hidden
|
|
416
|
+
position: relative
|
|
417
|
+
|
|
418
|
+
&--scrollable
|
|
419
|
+
overflow-y: auto
|
|
420
|
+
|
|
421
|
+
&--overflow
|
|
422
|
+
overflow: auto
|
|
423
|
+
|
|
424
|
+
&__component
|
|
425
|
+
display: block
|
|
426
|
+
min-width: 0
|
|
427
|
+
|
|
428
|
+
&__fallback
|
|
429
|
+
align-items: center
|
|
430
|
+
color: var(--d-code-example-muted)
|
|
431
|
+
display: flex
|
|
432
|
+
gap: 8px
|
|
433
|
+
min-height: 96px
|
|
434
|
+
padding: 18px
|
|
435
|
+
|
|
436
|
+
&__caption
|
|
437
|
+
border-top: 1px solid var(--d-code-example-border)
|
|
438
|
+
color: var(--d-code-example-caption)
|
|
439
|
+
font-size: 14px
|
|
440
|
+
line-height: 1.55
|
|
441
|
+
padding: 10px 14px 12px
|
|
442
|
+
|
|
443
|
+
p
|
|
444
|
+
margin: 0
|
|
445
|
+
</style>
|
|
@@ -2,13 +2,8 @@
|
|
|
2
2
|
import { ref, computed, watch } from 'vue'
|
|
3
3
|
import { useQuasar } from 'quasar'
|
|
4
4
|
import { useStore } from 'vuex'
|
|
5
|
-
import Prism from '
|
|
6
|
-
|
|
7
|
-
import 'prismjs/components/prism-markup-templating' // dependency for prism-php extension
|
|
8
|
-
// PHP
|
|
9
|
-
import 'prismjs/components/prism-php'
|
|
10
|
-
// Bash
|
|
11
|
-
import 'prismjs/components/prism-bash'
|
|
5
|
+
import Prism from './code-block-highlighting'
|
|
6
|
+
import { countRenderedCodeLines } from './source-code-lines'
|
|
12
7
|
import { looksLikeFileName, resolveFileIconUrl } from '../composables/useFileIcon'
|
|
13
8
|
|
|
14
9
|
defineOptions({
|
|
@@ -101,10 +96,7 @@ const activeBreadcrumbItems = computed(() => {
|
|
|
101
96
|
})
|
|
102
97
|
const activeBreadcrumbTitle = computed(() => activeBreadcrumbs.value.join(' > '))
|
|
103
98
|
const hasBreadcrumbs = computed(() => activeBreadcrumbs.value.length > 0)
|
|
104
|
-
const lines = computed(() =>
|
|
105
|
-
const splited = activeText.value.split(/\r\n|\n/)
|
|
106
|
-
return splited.length - 1
|
|
107
|
-
})
|
|
99
|
+
const lines = computed(() => countRenderedCodeLines(activeText.value))
|
|
108
100
|
const showHeader = computed(() => hasTabs.value || hasBreadcrumbs.value || (lines.value && lines.value > 1))
|
|
109
101
|
const codeLanguageClass = computed(() => `language-${activeLanguage.value}`)
|
|
110
102
|
const highlighted = computed(() => {
|
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
|
>
|
|
@@ -34,6 +34,8 @@ import DBlockQuickLinks from './DBlockQuickLinks.vue'
|
|
|
34
34
|
import DBlockTimeline from './DBlockTimeline.vue'
|
|
35
35
|
import DBlockExpandable from './DBlockExpandable.vue'
|
|
36
36
|
import DBlockStepper from './DBlockStepper.vue'
|
|
37
|
+
import DBlockCodeExample from './DBlockCodeExample.vue'
|
|
38
|
+
import DBlockApi from './DBlockApi.vue'
|
|
37
39
|
</script>
|
|
38
40
|
|
|
39
41
|
<template>
|
|
@@ -135,6 +137,26 @@ import DBlockStepper from './DBlockStepper.vue'
|
|
|
135
137
|
:tabs="token.tabs"
|
|
136
138
|
/>
|
|
137
139
|
|
|
140
|
+
<d-block-code-example
|
|
141
|
+
v-else-if="token.tag === 'code-example'"
|
|
142
|
+
:index="id + token.codeIndex"
|
|
143
|
+
:src="token.src"
|
|
144
|
+
:title="token.title"
|
|
145
|
+
:caption="token.caption"
|
|
146
|
+
:expanded="token.expanded"
|
|
147
|
+
:codepen="token.codepen"
|
|
148
|
+
:scrollable="token.scrollable"
|
|
149
|
+
:overflow="token.overflow"
|
|
150
|
+
:height="token.height"
|
|
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
|
+
|
|
138
160
|
<d-block-mermaid-diagram
|
|
139
161
|
v-else-if="token.tag === 'mermaid'"
|
|
140
162
|
:content="token.content"
|