@docsector/docsector-reader 3.6.0 → 4.0.1
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 +15 -15
- package/bin/docsector.js +1 -283
- package/package.json +1 -1
- package/src/components/{DPageBlockquote.vue → DBlockBlockquote.vue} +4 -0
- package/src/components/{DBlockCard.vue → DBlockCards.vue} +1 -1
- package/src/components/{DPageEmbeddedUrl.vue → DBlockEmbeddedUrl.vue} +1 -1
- package/src/components/{DPageExpandable.vue → DBlockExpandable.vue} +5 -0
- package/src/components/{DPageFile.vue → DBlockFile.vue} +1 -1
- package/src/components/{DPageImage.vue → DBlockImage.vue} +1 -1
- package/src/components/{DMermaidDiagram.vue → DBlockMermaidDiagram.vue} +4 -0
- package/src/components/{DQuickLinks.vue → DBlockQuickLinks.vue} +4 -0
- package/src/components/{DPageSourceCode.vue → DBlockSourceCode.vue} +6 -1
- package/src/components/DBlockStepper.vue +210 -0
- package/src/components/DBlockTimeline.vue +319 -0
- package/src/components/DPageSection.vue +5 -0
- package/src/components/DPageTokens.vue +56 -21
- package/src/components/DSubpage.vue +14 -2
- package/src/components/page-section-tokens.js +273 -10
- package/src/composables/useActiveAnchor.js +42 -0
- package/src/composables/useNavigator.js +24 -17
- package/src/home-page-mode.js +5 -0
- package/src/i18n/languages/en-US.hjson +5 -0
- package/src/i18n/languages/pt-BR.hjson +5 -0
- package/src/pages/guide/i18n-and-markdown.overview.en-US.md +6 -6
- package/src/pages/guide/i18n-and-markdown.overview.pt-BR.md +6 -6
- package/src/pages/guide/theming.overview.en-US.md +1 -1
- package/src/pages/guide/theming.overview.pt-BR.md +1 -1
- package/src/pages/manual/basic/d-page-anchor.overview.en-US.md +1 -1
- package/src/pages/manual/basic/d-page-anchor.overview.pt-BR.md +1 -1
- package/src/pages/manual/content/blocks/embedded-urls.overview.en-US.md +3 -3
- package/src/pages/manual/content/blocks/embedded-urls.overview.pt-BR.md +3 -3
- package/src/pages/manual/content/blocks/embedded-urls.showcase.en-US.md +8 -8
- package/src/pages/manual/content/blocks/embedded-urls.showcase.pt-BR.md +8 -8
- package/src/pages/manual/content/blocks/expandable.overview.en-US.md +8 -8
- package/src/pages/manual/content/blocks/expandable.overview.pt-BR.md +8 -8
- package/src/pages/manual/content/blocks/expandable.showcase.en-US.md +6 -6
- package/src/pages/manual/content/blocks/expandable.showcase.pt-BR.md +6 -6
- package/src/pages/manual/content/blocks/files.overview.en-US.md +3 -3
- package/src/pages/manual/content/blocks/files.overview.pt-BR.md +3 -3
- package/src/pages/manual/content/blocks/files.showcase.en-US.md +5 -5
- package/src/pages/manual/content/blocks/files.showcase.pt-BR.md +5 -5
- package/src/pages/manual/content/blocks/quick-links.overview.en-US.md +4 -4
- package/src/pages/manual/content/blocks/quick-links.overview.pt-BR.md +4 -4
- package/src/pages/manual/content/blocks/quick-links.showcase.en-US.md +9 -9
- package/src/pages/manual/content/blocks/quick-links.showcase.pt-BR.md +9 -9
- package/src/pages/manual/content/blocks/stepper.overview.en-US.md +59 -0
- package/src/pages/manual/content/blocks/stepper.overview.pt-BR.md +59 -0
- package/src/pages/manual/content/blocks/stepper.showcase.en-US.md +115 -0
- package/src/pages/manual/content/blocks/stepper.showcase.pt-BR.md +115 -0
- package/src/pages/manual/content/blocks/timeline.overview.en-US.md +47 -0
- package/src/pages/manual/content/blocks/timeline.overview.pt-BR.md +47 -0
- package/src/pages/manual/content/blocks/timeline.showcase.en-US.md +170 -0
- package/src/pages/manual/content/blocks/timeline.showcase.pt-BR.md +170 -0
- package/src/pages/manual.index.js +56 -0
- package/src/quasar.factory.js +13 -11
- package/src/store/Page.js +4 -2
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed } from 'vue'
|
|
3
3
|
import { useRoute } from 'vue-router'
|
|
4
|
+
import { useStore } from 'vuex'
|
|
5
|
+
import { homePageSourceMode } from 'virtual:docsector-homepage-override'
|
|
4
6
|
// components
|
|
5
7
|
import DPage from "./DPage.vue";
|
|
6
8
|
import DPageBar from "./DPageBar.vue";
|
|
7
9
|
import DH1 from "./DH1.vue";
|
|
8
10
|
import DPageSection from "./DPageSection.vue";
|
|
11
|
+
import { usesRemoteReadmeHomeContent } from '../home-page-mode'
|
|
9
12
|
|
|
10
13
|
const route = useRoute()
|
|
14
|
+
const store = useStore()
|
|
11
15
|
|
|
12
16
|
const id = computed(() => {
|
|
13
17
|
const path = route.path
|
|
@@ -19,6 +23,13 @@ const id = computed(() => {
|
|
|
19
23
|
|
|
20
24
|
return hash >>> 0
|
|
21
25
|
})
|
|
26
|
+
|
|
27
|
+
const usesRemoteReadmeHome = computed(() => {
|
|
28
|
+
return usesRemoteReadmeHomeContent({
|
|
29
|
+
pageBase: store.state.page.base,
|
|
30
|
+
homePageSourceMode
|
|
31
|
+
})
|
|
32
|
+
})
|
|
22
33
|
</script>
|
|
23
34
|
|
|
24
35
|
<template>
|
|
@@ -26,11 +37,12 @@ const id = computed(() => {
|
|
|
26
37
|
<header>
|
|
27
38
|
<d-page-bar />
|
|
28
39
|
<hr />
|
|
29
|
-
<d-h1 :id="0" />
|
|
40
|
+
<d-h1 v-if="!usesRemoteReadmeHome" :id="0" />
|
|
41
|
+
<span v-else id="0" aria-hidden="true"></span>
|
|
30
42
|
</header>
|
|
31
43
|
|
|
32
44
|
<main>
|
|
33
|
-
<d-page-section :id="id" />
|
|
45
|
+
<d-page-section :id="id" :render-primary-heading="usesRemoteReadmeHome" />
|
|
34
46
|
</main>
|
|
35
47
|
</d-page>
|
|
36
48
|
</template>
|
|
@@ -15,6 +15,8 @@ const ALERT_MESSAGE_TYPES = new Set([
|
|
|
15
15
|
|
|
16
16
|
const CARDS_MARKER_PREFIX = '@@DOCSECTOR_CARDS_'
|
|
17
17
|
const QUICK_LINKS_MARKER_PREFIX = '@@DOCSECTOR_QUICK_LINKS_'
|
|
18
|
+
const TIMELINE_MARKER_PREFIX = '@@DOCSECTOR_TIMELINE_'
|
|
19
|
+
const STEPPER_MARKER_PREFIX = '@@DOCSECTOR_STEPPER_'
|
|
18
20
|
const EXPANDABLE_MARKER_PREFIX = '@@DOCSECTOR_EXPANDABLE_'
|
|
19
21
|
const FILE_MARKER_PREFIX = '@@DOCSECTOR_FILE_'
|
|
20
22
|
const EMBEDDED_URL_MARKER_PREFIX = '@@DOCSECTOR_EMBEDDED_URL_'
|
|
@@ -147,11 +149,11 @@ const extractQuickLinksBlocks = (source = '') => {
|
|
|
147
149
|
const map = new Map()
|
|
148
150
|
let index = 0
|
|
149
151
|
|
|
150
|
-
const blockPattern = /<d-quick-links\b([^>]*)>([\s\S]*?)<\/d-quick-links>/gi
|
|
152
|
+
const blockPattern = /<d-block-quick-links\b([^>]*)>([\s\S]*?)<\/d-block-quick-links>/gi
|
|
151
153
|
const replaced = String(source).replace(blockPattern, (_, blockAttrsRaw, inner) => {
|
|
152
154
|
const blockAttrs = parseCustomTagAttributes(blockAttrsRaw)
|
|
153
155
|
const items = []
|
|
154
|
-
const itemPattern = /<d-quick-link\b([^>]*)\/?\s*>/gi
|
|
156
|
+
const itemPattern = /<d-block-quick-link\b([^>]*)\/?\s*>/gi
|
|
155
157
|
|
|
156
158
|
let itemMatch = itemPattern.exec(inner)
|
|
157
159
|
while (itemMatch !== null) {
|
|
@@ -195,11 +197,11 @@ const extractCardsBlocks = (source = '') => {
|
|
|
195
197
|
const map = new Map()
|
|
196
198
|
let index = 0
|
|
197
199
|
|
|
198
|
-
const blockPattern = /<d-
|
|
200
|
+
const blockPattern = /<d-block-cards\b([^>]*)>([\s\S]*?)<\/d-block-cards>/gi
|
|
199
201
|
const replaced = String(source).replace(blockPattern, (_, blockAttrsRaw, inner) => {
|
|
200
202
|
const blockAttrs = parseCustomTagAttributes(blockAttrsRaw)
|
|
201
203
|
const items = []
|
|
202
|
-
const itemPattern = /<d-
|
|
204
|
+
const itemPattern = /<d-block-card\b([^>]*)\/?\s*>/gi
|
|
203
205
|
|
|
204
206
|
let itemMatch = itemPattern.exec(inner)
|
|
205
207
|
while (itemMatch !== null) {
|
|
@@ -244,11 +246,166 @@ const parseExpandableOpenState = (raw = '') => {
|
|
|
244
246
|
return ['1', 'true', 'yes', 'on'].includes(String(raw).trim().toLowerCase())
|
|
245
247
|
}
|
|
246
248
|
|
|
249
|
+
const parseTimelineTags = (raw = '') => {
|
|
250
|
+
return decodeHtmlEntities(raw)
|
|
251
|
+
.split(',')
|
|
252
|
+
.map((value) => value.trim())
|
|
253
|
+
.filter(Boolean)
|
|
254
|
+
.map((label) => ({
|
|
255
|
+
label,
|
|
256
|
+
color: '',
|
|
257
|
+
textColor: '',
|
|
258
|
+
icon: ''
|
|
259
|
+
}))
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const parseTimelineTagLabel = (raw = '') => {
|
|
263
|
+
return decodeHtmlEntities(String(raw).replace(/<[^>]+>/g, ' '))
|
|
264
|
+
.replace(/\s+/g, ' ')
|
|
265
|
+
.trim()
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const createTimelineTag = (rawAttrs = '', rawLabel = '') => {
|
|
269
|
+
const attrs = parseCustomTagAttributes(rawAttrs)
|
|
270
|
+
const label = parseTimelineTagLabel(attrs.label || rawLabel)
|
|
271
|
+
|
|
272
|
+
if (label === '') {
|
|
273
|
+
return null
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return {
|
|
277
|
+
label,
|
|
278
|
+
color: decodeHtmlEntities(attrs.color || '').trim(),
|
|
279
|
+
textColor: decodeHtmlEntities(attrs['text-color'] || attrs.textColor || '').trim(),
|
|
280
|
+
icon: decodeHtmlEntities(attrs.icon || '').trim()
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const extractTimelineItemTags = (source = '') => {
|
|
285
|
+
const tags = []
|
|
286
|
+
|
|
287
|
+
const replaceBlock = (match, rawAttrs, rawLabel = '') => {
|
|
288
|
+
const tag = createTimelineTag(rawAttrs, rawLabel)
|
|
289
|
+
|
|
290
|
+
if (tag !== null) {
|
|
291
|
+
tags.push(tag)
|
|
292
|
+
return '\n'
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return match
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const replaced = String(source).replace(
|
|
299
|
+
/<d-block-timeline-tag\b([^>]*?)(?:\/\s*>|>([\s\S]*?)<\/d-block-timeline-tag>)/gi,
|
|
300
|
+
replaceBlock
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
return {
|
|
304
|
+
content: replaced,
|
|
305
|
+
tags
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const extractTimelineBlocks = (source = '') => {
|
|
310
|
+
const map = new Map()
|
|
311
|
+
let index = 0
|
|
312
|
+
|
|
313
|
+
const blockPattern = /<d-block-timeline\b([^>]*)>([\s\S]*?)<\/d-block-timeline>/gi
|
|
314
|
+
const replaced = String(source).replace(blockPattern, (match, _rawAttrs, inner) => {
|
|
315
|
+
const items = []
|
|
316
|
+
const itemPattern = /<d-block-timeline-item\b([^>]*)>([\s\S]*?)<\/d-block-timeline-item>/gi
|
|
317
|
+
|
|
318
|
+
let itemMatch = itemPattern.exec(inner)
|
|
319
|
+
while (itemMatch !== null) {
|
|
320
|
+
const attrs = parseCustomTagAttributes(itemMatch[1])
|
|
321
|
+
const date = decodeHtmlEntities(attrs.date || '').trim()
|
|
322
|
+
const { content: itemContent, tags } = extractTimelineItemTags(itemMatch[2])
|
|
323
|
+
|
|
324
|
+
if (date) {
|
|
325
|
+
items.push({
|
|
326
|
+
date,
|
|
327
|
+
tags: [...parseTimelineTags(attrs.tags || ''), ...tags],
|
|
328
|
+
anchor: decodeHtmlEntities(attrs.anchor || attrs.id || '').trim(),
|
|
329
|
+
content: itemContent
|
|
330
|
+
})
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
itemMatch = itemPattern.exec(inner)
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (items.length === 0) {
|
|
337
|
+
return match
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const marker = `${TIMELINE_MARKER_PREFIX}${index}@@`
|
|
341
|
+
index++
|
|
342
|
+
|
|
343
|
+
map.set(marker, {
|
|
344
|
+
items
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
return `\n${marker}\n`
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
return {
|
|
351
|
+
source: replaced,
|
|
352
|
+
timelineMap: map
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const extractStepperBlocks = (source = '') => {
|
|
357
|
+
const map = new Map()
|
|
358
|
+
let index = 0
|
|
359
|
+
|
|
360
|
+
const blockPattern = /<d-block-stepper\b([^>]*)>([\s\S]*?)<\/d-block-stepper>/gi
|
|
361
|
+
const replaced = String(source).replace(blockPattern, (match, _rawAttrs, inner) => {
|
|
362
|
+
const steps = []
|
|
363
|
+
const stepPattern = /<d-block-step\b([^>]*)>([\s\S]*?)<\/d-block-step>/gi
|
|
364
|
+
|
|
365
|
+
let stepMatch = stepPattern.exec(inner)
|
|
366
|
+
while (stepMatch !== null) {
|
|
367
|
+
const attrs = parseCustomTagAttributes(stepMatch[1])
|
|
368
|
+
const title = attrs.title || ''
|
|
369
|
+
|
|
370
|
+
if (title) {
|
|
371
|
+
steps.push({
|
|
372
|
+
title,
|
|
373
|
+
icon: attrs.icon || '',
|
|
374
|
+
activeIcon: attrs['active-icon'] || '',
|
|
375
|
+
doneIcon: attrs['done-icon'] || '',
|
|
376
|
+
errorIcon: attrs['error-icon'] || '',
|
|
377
|
+
content: stepMatch[2]
|
|
378
|
+
})
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
stepMatch = stepPattern.exec(inner)
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (steps.length === 0) {
|
|
385
|
+
return match
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const marker = `${STEPPER_MARKER_PREFIX}${index}@@`
|
|
389
|
+
index++
|
|
390
|
+
|
|
391
|
+
map.set(marker, {
|
|
392
|
+
steps
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
return `\n${marker}\n`
|
|
396
|
+
})
|
|
397
|
+
|
|
398
|
+
return {
|
|
399
|
+
source: replaced,
|
|
400
|
+
stepperMap: map
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
247
404
|
const extractExpandableBlocks = (source = '') => {
|
|
248
405
|
const map = new Map()
|
|
249
406
|
let index = 0
|
|
250
407
|
|
|
251
|
-
const blockPattern = /<d-expandable\b([^>]*)>([\s\S]*?)<\/d-expandable>/gi
|
|
408
|
+
const blockPattern = /<d-block-expandable\b([^>]*)>([\s\S]*?)<\/d-block-expandable>/gi
|
|
252
409
|
const replaced = String(source).replace(blockPattern, (_, rawAttrs, inner) => {
|
|
253
410
|
const attrs = parseCustomTagAttributes(rawAttrs)
|
|
254
411
|
const marker = `${EXPANDABLE_MARKER_PREFIX}${index}@@`
|
|
@@ -314,10 +471,10 @@ const extractFileBlocks = (source = '') => {
|
|
|
314
471
|
return `\n${marker}\n`
|
|
315
472
|
}
|
|
316
473
|
|
|
317
|
-
const replacedSelfClosing = String(source).replace(/<d-file\b([^>]*)\/\s*>/gi, (match, rawAttrs) => {
|
|
474
|
+
const replacedSelfClosing = String(source).replace(/<d-block-file\b([^>]*)\/\s*>/gi, (match, rawAttrs) => {
|
|
318
475
|
return replaceBlock(match, rawAttrs)
|
|
319
476
|
})
|
|
320
|
-
const replaced = replacedSelfClosing.replace(/<d-file\b([^>]*)>([\s\S]*?)<\/d-file>/gi, replaceBlock)
|
|
477
|
+
const replaced = replacedSelfClosing.replace(/<d-block-file\b([^>]*)>([\s\S]*?)<\/d-block-file>/gi, replaceBlock)
|
|
321
478
|
|
|
322
479
|
return {
|
|
323
480
|
source: replaced,
|
|
@@ -349,10 +506,10 @@ const extractEmbeddedUrlBlocks = (source = '') => {
|
|
|
349
506
|
return `\n${marker}\n`
|
|
350
507
|
}
|
|
351
508
|
|
|
352
|
-
const replacedSelfClosing = String(source).replace(/<d-embedded-url\b([^>]*)\/\s*>/gi, (match, rawAttrs) => {
|
|
509
|
+
const replacedSelfClosing = String(source).replace(/<d-block-embedded-url\b([^>]*)\/\s*>/gi, (match, rawAttrs) => {
|
|
353
510
|
return replaceBlock(match, rawAttrs)
|
|
354
511
|
})
|
|
355
|
-
const replaced = replacedSelfClosing.replace(/<d-embedded-url\b([^>]*)>([\s\S]*?)<\/d-embedded-url>/gi, replaceBlock)
|
|
512
|
+
const replaced = replacedSelfClosing.replace(/<d-block-embedded-url\b([^>]*)>([\s\S]*?)<\/d-block-embedded-url>/gi, replaceBlock)
|
|
356
513
|
|
|
357
514
|
return {
|
|
358
515
|
source: replaced,
|
|
@@ -403,6 +560,12 @@ const escapeHtml = (value = '') => {
|
|
|
403
560
|
.replace(/'/g, ''')
|
|
404
561
|
}
|
|
405
562
|
|
|
563
|
+
const stripHtmlTags = (value = '') => {
|
|
564
|
+
return decodeHtmlEntities(String(value).replace(/<[^>]+>/g, ' '))
|
|
565
|
+
.replace(/\s+/g, ' ')
|
|
566
|
+
.trim()
|
|
567
|
+
}
|
|
568
|
+
|
|
406
569
|
const extractTagAttributes = (html = '', tagName = '') => {
|
|
407
570
|
const match = String(html).match(new RegExp(`<${tagName}\\b([^>]*)\\/?>(?![\\s\\S]*<${tagName}\\b)`, 'i'))
|
|
408
571
|
|
|
@@ -635,6 +798,35 @@ const getHeadingAnchorId = (markdown, currentTag, element, env, parserState) =>
|
|
|
635
798
|
return parserState.headingSlugger.slug(headingText)
|
|
636
799
|
}
|
|
637
800
|
|
|
801
|
+
const getTimelineItemTitle = (tokens = []) => {
|
|
802
|
+
for (const token of tokens) {
|
|
803
|
+
if (typeof token?.content !== 'string') {
|
|
804
|
+
continue
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
const text = stripHtmlTags(token.content)
|
|
808
|
+
if (text !== '') {
|
|
809
|
+
return text
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
return ''
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
const getTimelineItemAnchorId = ({ item, itemTokens, itemIndex, parserState }) => {
|
|
817
|
+
const explicitAnchor = decodeHtmlEntities(item.anchor || '').trim()
|
|
818
|
+
|
|
819
|
+
if (explicitAnchor !== '') {
|
|
820
|
+
return parserState.headingSlugger.slug(explicitAnchor)
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
const title = getTimelineItemTitle(itemTokens)
|
|
824
|
+
const fallbackTitle = title || `item-${itemIndex + 1}`
|
|
825
|
+
const seed = [item.date, fallbackTitle].filter(Boolean).join(' ')
|
|
826
|
+
|
|
827
|
+
return parserState.headingSlugger.slug(seed)
|
|
828
|
+
}
|
|
829
|
+
|
|
638
830
|
export const tokenizePageSectionSource = (source = '', options = {}) => {
|
|
639
831
|
const {
|
|
640
832
|
allowHeadingTokens = true,
|
|
@@ -642,7 +834,31 @@ export const tokenizePageSectionSource = (source = '', options = {}) => {
|
|
|
642
834
|
} = options
|
|
643
835
|
const normalizedSource = normalizePageSectionSource(source)
|
|
644
836
|
const { source: sourceWithShieldedCode, codeSegmentsMap } = shieldMarkdownCodeSegments(normalizedSource)
|
|
645
|
-
const { source:
|
|
837
|
+
const { source: sourceWithTimelines, timelineMap } = extractTimelineBlocks(sourceWithShieldedCode)
|
|
838
|
+
|
|
839
|
+
timelineMap.forEach((data, marker) => {
|
|
840
|
+
timelineMap.set(marker, {
|
|
841
|
+
...data,
|
|
842
|
+
items: data.items.map((item) => ({
|
|
843
|
+
...item,
|
|
844
|
+
content: restoreShieldedCodeSegments(item.content, codeSegmentsMap)
|
|
845
|
+
}))
|
|
846
|
+
})
|
|
847
|
+
})
|
|
848
|
+
|
|
849
|
+
const { source: sourceWithSteppers, stepperMap } = extractStepperBlocks(sourceWithTimelines)
|
|
850
|
+
|
|
851
|
+
stepperMap.forEach((data, marker) => {
|
|
852
|
+
stepperMap.set(marker, {
|
|
853
|
+
...data,
|
|
854
|
+
steps: data.steps.map((step) => ({
|
|
855
|
+
...step,
|
|
856
|
+
content: restoreShieldedCodeSegments(step.content, codeSegmentsMap)
|
|
857
|
+
}))
|
|
858
|
+
})
|
|
859
|
+
})
|
|
860
|
+
|
|
861
|
+
const { source: sourceWithExpandables, expandableMap } = extractExpandableBlocks(sourceWithSteppers)
|
|
646
862
|
|
|
647
863
|
expandableMap.forEach((data, marker) => {
|
|
648
864
|
expandableMap.set(marker, {
|
|
@@ -815,6 +1031,53 @@ export const tokenizePageSectionSource = (source = '', options = {}) => {
|
|
|
815
1031
|
case 'inline': {
|
|
816
1032
|
const anchorId = getHeadingAnchorId(markdown, tag, element, markdownEnv, parserState)
|
|
817
1033
|
|
|
1034
|
+
if (timelineMap.has(element.content.trim())) {
|
|
1035
|
+
const data = timelineMap.get(element.content.trim())
|
|
1036
|
+
|
|
1037
|
+
tokens.push({
|
|
1038
|
+
tag: 'timeline',
|
|
1039
|
+
items: data.items.map((item, itemIndex) => {
|
|
1040
|
+
const itemTokens = tokenizePageSectionSource(item.content, {
|
|
1041
|
+
allowHeadingTokens: false,
|
|
1042
|
+
parserState
|
|
1043
|
+
})
|
|
1044
|
+
|
|
1045
|
+
return {
|
|
1046
|
+
date: item.date,
|
|
1047
|
+
tags: item.tags,
|
|
1048
|
+
anchorId: getTimelineItemAnchorId({
|
|
1049
|
+
item,
|
|
1050
|
+
itemTokens,
|
|
1051
|
+
itemIndex,
|
|
1052
|
+
parserState
|
|
1053
|
+
}),
|
|
1054
|
+
tokens: itemTokens
|
|
1055
|
+
}
|
|
1056
|
+
})
|
|
1057
|
+
})
|
|
1058
|
+
break
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
if (stepperMap.has(element.content.trim())) {
|
|
1062
|
+
const data = stepperMap.get(element.content.trim())
|
|
1063
|
+
|
|
1064
|
+
tokens.push({
|
|
1065
|
+
tag: 'stepper',
|
|
1066
|
+
steps: data.steps.map((step) => ({
|
|
1067
|
+
title: step.title,
|
|
1068
|
+
icon: step.icon,
|
|
1069
|
+
activeIcon: step.activeIcon,
|
|
1070
|
+
doneIcon: step.doneIcon,
|
|
1071
|
+
errorIcon: step.errorIcon,
|
|
1072
|
+
tokens: tokenizePageSectionSource(step.content, {
|
|
1073
|
+
allowHeadingTokens: false,
|
|
1074
|
+
parserState
|
|
1075
|
+
})
|
|
1076
|
+
}))
|
|
1077
|
+
})
|
|
1078
|
+
break
|
|
1079
|
+
}
|
|
1080
|
+
|
|
818
1081
|
if (expandableMap.has(element.content.trim())) {
|
|
819
1082
|
const data = expandableMap.get(element.content.trim())
|
|
820
1083
|
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export const DEFAULT_ACTIVE_ANCHOR_OFFSET = 50
|
|
2
|
+
|
|
3
|
+
const toFiniteNumber = (value) => {
|
|
4
|
+
const normalized = Number(value)
|
|
5
|
+
return Number.isFinite(normalized) ? normalized : 0
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function getActiveAnchorId({
|
|
9
|
+
anchors = [],
|
|
10
|
+
scrollTop = 0,
|
|
11
|
+
scrollOffset = DEFAULT_ACTIVE_ANCHOR_OFFSET,
|
|
12
|
+
rootAnchorId = 0,
|
|
13
|
+
getAnchorOffsetTop = () => undefined
|
|
14
|
+
} = {}) {
|
|
15
|
+
const thresholdTop = Math.max(0, toFiniteNumber(scrollTop)) + Math.max(0, toFiniteNumber(scrollOffset))
|
|
16
|
+
let activeAnchorId = rootAnchorId
|
|
17
|
+
const seenAnchors = new Set()
|
|
18
|
+
|
|
19
|
+
for (const anchorId of Array.isArray(anchors) ? anchors : []) {
|
|
20
|
+
if (anchorId === null || anchorId === undefined || anchorId === false || anchorId === 0 || anchorId === '0') {
|
|
21
|
+
continue
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (seenAnchors.has(anchorId)) {
|
|
25
|
+
continue
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
seenAnchors.add(anchorId)
|
|
29
|
+
|
|
30
|
+
const resolvedOffsetTop = Number(getAnchorOffsetTop(anchorId))
|
|
31
|
+
|
|
32
|
+
if (!Number.isFinite(resolvedOffsetTop)) {
|
|
33
|
+
continue
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (thresholdTop >= resolvedOffsetTop) {
|
|
37
|
+
activeAnchorId = anchorId
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return activeAnchorId
|
|
42
|
+
}
|
|
@@ -3,6 +3,8 @@ import { useStore } from 'vuex'
|
|
|
3
3
|
import { useRouter, useRoute } from 'vue-router'
|
|
4
4
|
import { ref } from 'vue'
|
|
5
5
|
|
|
6
|
+
import { DEFAULT_ACTIVE_ANCHOR_OFFSET, getActiveAnchorId } from './useActiveAnchor'
|
|
7
|
+
|
|
6
8
|
export default function useNavigator() {
|
|
7
9
|
const store = useStore()
|
|
8
10
|
const router = useRouter()
|
|
@@ -46,7 +48,10 @@ export default function useNavigator() {
|
|
|
46
48
|
const select = (id) => {
|
|
47
49
|
const normalized = normalizeStoreAnchorId(id)
|
|
48
50
|
|
|
49
|
-
store.
|
|
51
|
+
if (store.state.page.anchor !== normalized) {
|
|
52
|
+
store.commit('page/setAnchor', normalized)
|
|
53
|
+
}
|
|
54
|
+
|
|
50
55
|
store.commit('page/pushNodesExpanded', normalized)
|
|
51
56
|
}
|
|
52
57
|
|
|
@@ -78,27 +83,29 @@ export default function useNavigator() {
|
|
|
78
83
|
return
|
|
79
84
|
}
|
|
80
85
|
|
|
81
|
-
const
|
|
82
|
-
|
|
86
|
+
const activeAnchorId = getActiveAnchorId({
|
|
87
|
+
anchors: store.state.page.anchors,
|
|
88
|
+
scrollTop: scroll?.position?.top,
|
|
89
|
+
scrollOffset: DEFAULT_ACTIVE_ANCHOR_OFFSET,
|
|
90
|
+
rootAnchorId: 0,
|
|
91
|
+
getAnchorOffsetTop: (anchorId) => {
|
|
92
|
+
const domAnchorId = normalizeDomAnchorId(anchorId)
|
|
83
93
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
94
|
+
if (domAnchorId === '') {
|
|
95
|
+
return undefined
|
|
96
|
+
}
|
|
87
97
|
|
|
88
|
-
|
|
89
|
-
continue
|
|
90
|
-
}
|
|
98
|
+
const Anchor = document.getElementById(domAnchorId)
|
|
91
99
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
AnchorOffsetTop = Anchor.offsetTop
|
|
96
|
-
}
|
|
100
|
+
if (Anchor !== null && typeof Anchor === 'object') {
|
|
101
|
+
return Anchor.offsetTop
|
|
102
|
+
}
|
|
97
103
|
|
|
98
|
-
|
|
99
|
-
select(anchorId)
|
|
104
|
+
return undefined
|
|
100
105
|
}
|
|
101
|
-
}
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
select(activeAnchorId)
|
|
102
109
|
}
|
|
103
110
|
|
|
104
111
|
const navigate = (value, toAnchor = true) => {
|
|
@@ -45,6 +45,11 @@
|
|
|
45
45
|
download: 'Download',
|
|
46
46
|
open: 'Open'
|
|
47
47
|
},
|
|
48
|
+
stepper: {
|
|
49
|
+
continue: 'Continue',
|
|
50
|
+
back: 'Back',
|
|
51
|
+
finish: 'Finish'
|
|
52
|
+
},
|
|
48
53
|
openInChatGPT: 'Open in ChatGPT',
|
|
49
54
|
openInChatGPTCaption: 'Ask ChatGPT about this page',
|
|
50
55
|
openInClaude: 'Open in Claude',
|
|
@@ -44,6 +44,11 @@
|
|
|
44
44
|
download: 'Baixar',
|
|
45
45
|
open: 'Abrir'
|
|
46
46
|
},
|
|
47
|
+
stepper: {
|
|
48
|
+
continue: 'Continuar',
|
|
49
|
+
back: 'Voltar',
|
|
50
|
+
finish: 'Finalizar'
|
|
51
|
+
},
|
|
47
52
|
openInChatGPT: 'Abrir no ChatGPT',
|
|
48
53
|
openInChatGPTCaption: 'Pergunte ao ChatGPT sobre esta página',
|
|
49
54
|
openInClaude: 'Abrir no Claude',
|
|
@@ -118,14 +118,14 @@ echo "Example"
|
|
|
118
118
|
|
|
119
119
|
### Expandable Content
|
|
120
120
|
|
|
121
|
-
Use `<d-expandable>` to hide secondary content without removing rich Markdown features from the page:
|
|
121
|
+
Use `<d-block-expandable>` to hide secondary content without removing rich Markdown features from the page:
|
|
122
122
|
|
|
123
123
|
```markdown
|
|
124
|
-
<d-expandable title="More details">
|
|
124
|
+
<d-block-expandable title="More details">
|
|
125
125
|
|
|
126
126
|
Optional explanations, operational notes, or longer examples.
|
|
127
127
|
|
|
128
|
-
</d-expandable>
|
|
128
|
+
</d-block-expandable>
|
|
129
129
|
```
|
|
130
130
|
|
|
131
131
|
Set `open="true"` when the block should start expanded.
|
|
@@ -134,12 +134,12 @@ The expandable body supports paragraphs, lists, alerts, code blocks, Mermaid dia
|
|
|
134
134
|
|
|
135
135
|
### File Blocks
|
|
136
136
|
|
|
137
|
-
Use `<d-file>` to publish downloadable attachments from repo-tracked files or external storage without leaving Markdown:
|
|
137
|
+
Use `<d-block-file>` to publish downloadable attachments from repo-tracked files or external storage without leaving Markdown:
|
|
138
138
|
|
|
139
139
|
```html
|
|
140
|
-
<d-file src="/files/manual/release-checklist.txt" title="Release checklist" size="1 KB">
|
|
140
|
+
<d-block-file src="/files/manual/release-checklist.txt" title="Release checklist" size="1 KB">
|
|
141
141
|
Download the example attachment used in this manual.
|
|
142
|
-
</d-file>
|
|
142
|
+
</d-block-file>
|
|
143
143
|
```
|
|
144
144
|
|
|
145
145
|
Store repo-tracked files under `public/files/` and prefer absolute site paths such as `/files/...`.
|
|
@@ -118,14 +118,14 @@ echo "Exemplo"
|
|
|
118
118
|
|
|
119
119
|
### Conteúdo Expansível
|
|
120
120
|
|
|
121
|
-
Use `<d-expandable>` para esconder conteúdo secundário sem perder os recursos ricos de Markdown da página:
|
|
121
|
+
Use `<d-block-expandable>` para esconder conteúdo secundário sem perder os recursos ricos de Markdown da página:
|
|
122
122
|
|
|
123
123
|
```markdown
|
|
124
|
-
<d-expandable title="Mais detalhes">
|
|
124
|
+
<d-block-expandable title="Mais detalhes">
|
|
125
125
|
|
|
126
126
|
Explicações opcionais, notas operacionais ou exemplos maiores.
|
|
127
127
|
|
|
128
|
-
</d-expandable>
|
|
128
|
+
</d-block-expandable>
|
|
129
129
|
```
|
|
130
130
|
|
|
131
131
|
Defina `open="true"` quando o bloco precisar começar aberto.
|
|
@@ -134,12 +134,12 @@ O corpo do expansível suporta parágrafos, listas, alertas, blocos de código,
|
|
|
134
134
|
|
|
135
135
|
### Blocos de Arquivo
|
|
136
136
|
|
|
137
|
-
Use `<d-file>` para publicar anexos baixáveis a partir de arquivos versionados no repositório ou de storage externo sem sair do Markdown:
|
|
137
|
+
Use `<d-block-file>` para publicar anexos baixáveis a partir de arquivos versionados no repositório ou de storage externo sem sair do Markdown:
|
|
138
138
|
|
|
139
139
|
```html
|
|
140
|
-
<d-file src="/files/manual/release-checklist.txt" title="Checklist de release" size="1 KB">
|
|
140
|
+
<d-block-file src="/files/manual/release-checklist.txt" title="Checklist de release" size="1 KB">
|
|
141
141
|
Baixe o anexo de exemplo usado neste manual.
|
|
142
|
-
</d-file>
|
|
142
|
+
</d-block-file>
|
|
143
143
|
```
|
|
144
144
|
|
|
145
145
|
Guarde arquivos versionados no repositório em `public/files/` e prefira caminhos absolutos do site, como `/files/...`.
|
|
@@ -48,7 +48,7 @@ Components use CSS custom properties for theme-aware styling:
|
|
|
48
48
|
|
|
49
49
|
## Customizing Code Block Colors
|
|
50
50
|
|
|
51
|
-
The `
|
|
51
|
+
The `DBlockSourceCode` component has separate color schemes for light and dark modes, using Prism.js token classes. Override `token.*` classes in the `.source-code` SASS block to customize syntax highlighting colors.
|
|
52
52
|
|
|
53
53
|
## Font Families
|
|
54
54
|
|
|
@@ -48,7 +48,7 @@ Os componentes usam propriedades CSS customizadas para estilização consciente
|
|
|
48
48
|
|
|
49
49
|
## Customizando Cores dos Blocos de Código
|
|
50
50
|
|
|
51
|
-
O componente `
|
|
51
|
+
O componente `DBlockSourceCode` tem esquemas de cores separados para modos claro e escuro, usando classes de token do Prism.js. Sobrescreva as classes `token.*` no bloco SASS `.source-code` para customizar as cores de syntax highlighting.
|
|
52
52
|
|
|
53
53
|
## Famílias de Fontes
|
|
54
54
|
|
|
@@ -39,7 +39,7 @@ The root node (from DH1) shows the page title from i18n when no label is set:
|
|
|
39
39
|
|
|
40
40
|
## Scroll Synchronization
|
|
41
41
|
|
|
42
|
-
When the user scrolls the page content, the `DPage` scroll observer calls `useNavigator().scrolling()`, which
|
|
42
|
+
When the user scrolls the page content, the `DPage` scroll observer calls `useNavigator().scrolling()`, which selects the last registered heading that crossed the content threshold. Missing or stale anchors are ignored so the table of contents stays in sync with the visible section instead of jumping ahead.
|
|
43
43
|
|
|
44
44
|
## Lifecycle
|
|
45
45
|
|
|
@@ -39,7 +39,7 @@ O nó raiz (do DH1) mostra o título da página do i18n quando nenhum label é d
|
|
|
39
39
|
|
|
40
40
|
## Sincronização de Scroll
|
|
41
41
|
|
|
42
|
-
Quando o usuário faz scroll no conteúdo da página, o observador de scroll do `DPage` chama `useNavigator().scrolling()`, que
|
|
42
|
+
Quando o usuário faz scroll no conteúdo da página, o observador de scroll do `DPage` chama `useNavigator().scrolling()`, que seleciona o último heading registrado que cruzou o limite superior da área de conteúdo. Âncoras ausentes ou obsoletas são ignoradas para manter o índice sincronizado com a seção realmente visível, sem saltar adiante.
|
|
43
43
|
|
|
44
44
|
## Ciclo de Vida
|
|
45
45
|
|