@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.
Files changed (56) hide show
  1. package/README.md +15 -15
  2. package/bin/docsector.js +1 -283
  3. package/package.json +1 -1
  4. package/src/components/{DPageBlockquote.vue → DBlockBlockquote.vue} +4 -0
  5. package/src/components/{DBlockCard.vue → DBlockCards.vue} +1 -1
  6. package/src/components/{DPageEmbeddedUrl.vue → DBlockEmbeddedUrl.vue} +1 -1
  7. package/src/components/{DPageExpandable.vue → DBlockExpandable.vue} +5 -0
  8. package/src/components/{DPageFile.vue → DBlockFile.vue} +1 -1
  9. package/src/components/{DPageImage.vue → DBlockImage.vue} +1 -1
  10. package/src/components/{DMermaidDiagram.vue → DBlockMermaidDiagram.vue} +4 -0
  11. package/src/components/{DQuickLinks.vue → DBlockQuickLinks.vue} +4 -0
  12. package/src/components/{DPageSourceCode.vue → DBlockSourceCode.vue} +6 -1
  13. package/src/components/DBlockStepper.vue +210 -0
  14. package/src/components/DBlockTimeline.vue +319 -0
  15. package/src/components/DPageSection.vue +5 -0
  16. package/src/components/DPageTokens.vue +56 -21
  17. package/src/components/DSubpage.vue +14 -2
  18. package/src/components/page-section-tokens.js +273 -10
  19. package/src/composables/useActiveAnchor.js +42 -0
  20. package/src/composables/useNavigator.js +24 -17
  21. package/src/home-page-mode.js +5 -0
  22. package/src/i18n/languages/en-US.hjson +5 -0
  23. package/src/i18n/languages/pt-BR.hjson +5 -0
  24. package/src/pages/guide/i18n-and-markdown.overview.en-US.md +6 -6
  25. package/src/pages/guide/i18n-and-markdown.overview.pt-BR.md +6 -6
  26. package/src/pages/guide/theming.overview.en-US.md +1 -1
  27. package/src/pages/guide/theming.overview.pt-BR.md +1 -1
  28. package/src/pages/manual/basic/d-page-anchor.overview.en-US.md +1 -1
  29. package/src/pages/manual/basic/d-page-anchor.overview.pt-BR.md +1 -1
  30. package/src/pages/manual/content/blocks/embedded-urls.overview.en-US.md +3 -3
  31. package/src/pages/manual/content/blocks/embedded-urls.overview.pt-BR.md +3 -3
  32. package/src/pages/manual/content/blocks/embedded-urls.showcase.en-US.md +8 -8
  33. package/src/pages/manual/content/blocks/embedded-urls.showcase.pt-BR.md +8 -8
  34. package/src/pages/manual/content/blocks/expandable.overview.en-US.md +8 -8
  35. package/src/pages/manual/content/blocks/expandable.overview.pt-BR.md +8 -8
  36. package/src/pages/manual/content/blocks/expandable.showcase.en-US.md +6 -6
  37. package/src/pages/manual/content/blocks/expandable.showcase.pt-BR.md +6 -6
  38. package/src/pages/manual/content/blocks/files.overview.en-US.md +3 -3
  39. package/src/pages/manual/content/blocks/files.overview.pt-BR.md +3 -3
  40. package/src/pages/manual/content/blocks/files.showcase.en-US.md +5 -5
  41. package/src/pages/manual/content/blocks/files.showcase.pt-BR.md +5 -5
  42. package/src/pages/manual/content/blocks/quick-links.overview.en-US.md +4 -4
  43. package/src/pages/manual/content/blocks/quick-links.overview.pt-BR.md +4 -4
  44. package/src/pages/manual/content/blocks/quick-links.showcase.en-US.md +9 -9
  45. package/src/pages/manual/content/blocks/quick-links.showcase.pt-BR.md +9 -9
  46. package/src/pages/manual/content/blocks/stepper.overview.en-US.md +59 -0
  47. package/src/pages/manual/content/blocks/stepper.overview.pt-BR.md +59 -0
  48. package/src/pages/manual/content/blocks/stepper.showcase.en-US.md +115 -0
  49. package/src/pages/manual/content/blocks/stepper.showcase.pt-BR.md +115 -0
  50. package/src/pages/manual/content/blocks/timeline.overview.en-US.md +47 -0
  51. package/src/pages/manual/content/blocks/timeline.overview.pt-BR.md +47 -0
  52. package/src/pages/manual/content/blocks/timeline.showcase.en-US.md +170 -0
  53. package/src/pages/manual/content/blocks/timeline.showcase.pt-BR.md +170 -0
  54. package/src/pages/manual.index.js +56 -0
  55. package/src/quasar.factory.js +13 -11
  56. 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-(?:block-)?cards\b([^>]*)>([\s\S]*?)<\/d-(?:block-)?cards>/gi
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-(?:block-)?card\b([^>]*)\/?\s*>/gi
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, '&#39;')
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: sourceWithExpandables, expandableMap } = extractExpandableBlocks(sourceWithShieldedCode)
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.commit('page/setAnchor', normalized)
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 scrollPositionTop = scroll.position.top + 50
82
- const anchors = store.state.page.anchors
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
- for (let i = 0; i < anchors.length; i++) {
85
- const anchorId = anchors[i]
86
- const domAnchorId = normalizeDomAnchorId(anchorId)
94
+ if (domAnchorId === '') {
95
+ return undefined
96
+ }
87
97
 
88
- if (domAnchorId === '0') {
89
- continue
90
- }
98
+ const Anchor = document.getElementById(domAnchorId)
91
99
 
92
- const Anchor = document.getElementById(domAnchorId)
93
- let AnchorOffsetTop = 20
94
- if (Anchor !== null && typeof Anchor === 'object') {
95
- AnchorOffsetTop = Anchor.offsetTop
96
- }
100
+ if (Anchor !== null && typeof Anchor === 'object') {
101
+ return Anchor.offsetTop
102
+ }
97
103
 
98
- if (scrollPositionTop >= AnchorOffsetTop) {
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) => {
@@ -0,0 +1,5 @@
1
+ export const REMOTE_README_HOME_PAGE_MODE = 'remote-readme'
2
+
3
+ export function usesRemoteReadmeHomeContent ({ pageBase = '', homePageSourceMode = 'local' } = {}) {
4
+ return pageBase === 'home' && homePageSourceMode === REMOTE_README_HOME_PAGE_MODE
5
+ }
@@ -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 `DPageSourceCode` 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.
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 `DPageSourceCode` 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.
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 iterates over registered anchors and selects the one closest to the current scroll position. This keeps the table of contents in sync with the visible content.
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 itera sobre as âncoras registradas e seleciona a mais próxima da posição de scroll atual. Isso mantém o índice sincronizado com o conteúdo visível.
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