@docsector/docsector-reader 3.3.1 → 3.5.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 CHANGED
@@ -42,6 +42,7 @@ Transform Markdown content into beautiful, navigable documentation sites — wit
42
42
 
43
43
  - 📝 **Markdown Rendering** — Write docs in Markdown, rendered with syntax highlighting (Prism.js)
44
44
  - 🔽 **Nested Markdown Lists** — Ordered and unordered lists preserve sublist hierarchy across multiple indentation levels
45
+ - ☑️ **Markdown Task Lists** — GitBook-style `- [ ]` and `- [x]` items render as read-only checkboxes with nested subtasks
45
46
  - 🖼️ **Block Image Captions & Zoom** — Standalone Markdown images render as zoomable figures, and raw `figure` / `picture` markup supports separate alt text and captions
46
47
  - 🧱 **Raw HTML in Markdown** — Renders inline and block HTML tags inside markdown sections (including homepage remote README content)
47
48
  - 🧩 **Mermaid Diagrams** — Native support for fenced ` ```mermaid ` blocks, with automatic dark/light theme switching
@@ -72,6 +73,7 @@ Transform Markdown content into beautiful, navigable documentation sites — wit
72
73
  - 🧬 **Scaffolded Homepage Override Wiring** — New consumer projects automatically wire `virtual:docsector-homepage-override` into i18n message building
73
74
  - 📖 **Expandable Markdown Sections** — Use `<d-expandable title="...">...</d-expandable>` to collapse secondary content while keeping rich Markdown support inside the body
74
75
  - 📎 **File Attachment Blocks** — Use `<d-file src="/files/...">...</d-file>` in Markdown to render downloadable file cards with automatic local size detection and support for external URLs
76
+ - 🌐 **Embedded URL Blocks** — Use `<d-embedded-url url="https://...">...</d-embedded-url>` to render curated embeds for YouTube, Vimeo, Spotify, and CodePen with a safe link-card fallback for unsupported URLs
75
77
  - 🧭 **Quick Links Custom Element** — Use `<d-quick-links>` and `<d-quick-link>` in Markdown to render rich home navigation cards
76
78
  - 🗂️ **API Catalog Well-Known** — Auto-generates `/.well-known/api-catalog` as Linkset JSON for machine-readable API discovery
77
79
  - 🗃️ **Multi-Version History** — Archive older major versions under `src/pages/.old/<version>/` and expose them at prefixed routes (e.g. `/v0.x/guide/...`) while keeping the current docs at unprefixed routes
@@ -1035,6 +1037,21 @@ Notes:
1035
1037
  - The block body is rendered as an inline Markdown caption.
1036
1038
  - External URLs also work, so the same syntax can later point to R2 or another CDN without changing the page structure.
1037
1039
 
1040
+ ### Embedded URL Blocks
1041
+
1042
+ ```html
1043
+ <d-embedded-url url="https://www.youtube.com/watch?v=M7lc1UVf-VE" title="YouTube player demo">
1044
+ Optional caption rendered as inline Markdown.
1045
+ </d-embedded-url>
1046
+ ```
1047
+
1048
+ Notes:
1049
+
1050
+ - Supported providers currently include YouTube, Vimeo, Spotify, and CodePen.
1051
+ - The block preserves the original query string, so provider options such as `autoplay=1&loop=1` keep working when supported by the destination service.
1052
+ - Unsupported or private URLs fall back to a safe external-link card instead of attempting a generic iframe.
1053
+ - Raw HTML remains the escape hatch when you need a provider outside the curated list or full manual iframe control.
1054
+
1038
1055
  ---
1039
1056
 
1040
1057
 
package/bin/docsector.js CHANGED
@@ -23,7 +23,7 @@ const packageRoot = resolve(__dirname, '..')
23
23
  const args = process.argv.slice(2)
24
24
  const command = args[0]
25
25
 
26
- const VERSION = '3.3.1'
26
+ const VERSION = '3.5.0'
27
27
 
28
28
  const HELP = `
29
29
  Docsector Reader v${VERSION}
package/jsconfig.json CHANGED
@@ -13,5 +13,5 @@
13
13
  "stores/*": ["src/stores/*"]
14
14
  }
15
15
  },
16
- "exclude": ["node_modules", "dist"]
16
+ "exclude": ["node_modules", "dist", "tmp"]
17
17
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@docsector/docsector-reader",
3
- "version": "3.3.1",
3
+ "version": "3.5.0",
4
4
  "description": "A documentation rendering engine built with Vue 3, Quasar v2 and Vite. Transform Markdown into beautiful, navigable documentation sites.",
5
5
  "productName": "Docsector Reader",
6
6
  "author": "Rodrigo de Araujo Vieira",
@@ -74,6 +74,7 @@
74
74
  "katex": "^0.17.0",
75
75
  "markdown-it": "^13.0.1",
76
76
  "markdown-it-attrs": "^4.1.6",
77
+ "markdown-it-task-lists": "^2.1.1",
77
78
  "markdown-it-texmath": "^1.0.0",
78
79
  "mermaid": "^11.0.0",
79
80
  "prismjs": "^1.27.0",
@@ -0,0 +1,375 @@
1
+ <script setup>
2
+ import { computed } from 'vue'
3
+ import { useI18n } from 'vue-i18n'
4
+
5
+ import { resolveEmbeddedUrl } from '../composables/useEmbeddedUrl'
6
+
7
+ defineOptions({
8
+ name: 'DPageEmbeddedUrl'
9
+ })
10
+
11
+ const props = defineProps({
12
+ url: {
13
+ type: String,
14
+ default: ''
15
+ },
16
+ title: {
17
+ type: String,
18
+ default: ''
19
+ },
20
+ caption: {
21
+ type: String,
22
+ default: ''
23
+ }
24
+ })
25
+
26
+ const { t } = useI18n()
27
+
28
+ const resolved = computed(() => {
29
+ return resolveEmbeddedUrl(props.url, {
30
+ title: props.title
31
+ })
32
+ })
33
+
34
+ const isEmbedded = computed(() => {
35
+ return resolved.value.mode === 'embed' && resolved.value.embedSrc !== ''
36
+ })
37
+
38
+ const isCompactEmbed = computed(() => {
39
+ return isEmbedded.value && resolved.value.provider === 'spotify'
40
+ })
41
+
42
+ const displayTitle = computed(() => {
43
+ return resolved.value.title || props.title || resolved.value.providerLabel || props.url
44
+ })
45
+
46
+ const displayUrl = computed(() => {
47
+ return resolved.value.displayUrl || props.url
48
+ })
49
+
50
+ const compactUrl = computed(() => {
51
+ return String(displayUrl.value || '').replace(/^https?:\/\//i, '')
52
+ })
53
+
54
+ const frameStyle = computed(() => {
55
+ const style = {}
56
+
57
+ if (resolved.value.aspectRatio) {
58
+ style.aspectRatio = resolved.value.aspectRatio
59
+ } else {
60
+ style.aspectRatio = 'auto'
61
+ }
62
+
63
+ if (resolved.value.frameHeight > 0) {
64
+ style.height = `${resolved.value.frameHeight}px`
65
+ style.minHeight = `${resolved.value.frameHeight}px`
66
+ }
67
+
68
+ return style
69
+ })
70
+
71
+ const openHref = computed(() => {
72
+ return resolved.value.canonicalUrl || props.url
73
+ })
74
+ </script>
75
+
76
+ <template>
77
+ <div
78
+ class="d-page-embedded-url"
79
+ :class="{
80
+ 'd-page-embedded-url--compact': isCompactEmbed,
81
+ 'd-page-embedded-url--embedded': isEmbedded,
82
+ 'd-page-embedded-url--fallback': !isEmbedded
83
+ }"
84
+ >
85
+ <div
86
+ v-if="isEmbedded"
87
+ class="d-page-embedded-url__frame-shell"
88
+ :style="frameStyle"
89
+ >
90
+ <iframe
91
+ class="d-page-embedded-url__frame"
92
+ :src="resolved.embedSrc"
93
+ :title="displayTitle"
94
+ :allow="resolved.allow || undefined"
95
+ :allowfullscreen="resolved.allowFullscreen"
96
+ loading="lazy"
97
+ referrerpolicy="strict-origin-when-cross-origin"
98
+ ></iframe>
99
+ </div>
100
+
101
+ <div
102
+ v-if="!isCompactEmbed || caption"
103
+ class="d-page-embedded-url__body"
104
+ >
105
+ <template v-if="!isCompactEmbed">
106
+ <div
107
+ v-if="!isEmbedded"
108
+ class="d-page-embedded-url__media"
109
+ aria-hidden="true"
110
+ >
111
+ <q-icon
112
+ :name="resolved.icon || 'link'"
113
+ size="28px"
114
+ />
115
+ </div>
116
+
117
+ <div class="d-page-embedded-url__content">
118
+ <div
119
+ v-if="resolved.providerLabel"
120
+ class="d-page-embedded-url__provider"
121
+ >
122
+ <q-icon
123
+ :name="resolved.icon || 'link'"
124
+ size="16px"
125
+ />
126
+ <span>{{ resolved.providerLabel }}</span>
127
+ </div>
128
+
129
+ <div class="d-page-embedded-url__title">{{ displayTitle }}</div>
130
+
131
+ <div
132
+ v-if="compactUrl"
133
+ class="d-page-embedded-url__url"
134
+ >{{ compactUrl }}</div>
135
+
136
+ <div
137
+ v-if="caption"
138
+ class="d-page-embedded-url__caption"
139
+ v-html="caption"
140
+ ></div>
141
+ </div>
142
+
143
+ <div class="d-page-embedded-url__actions">
144
+ <q-btn
145
+ no-caps
146
+ unelevated
147
+ padding="8px 12px"
148
+ class="d-page-embedded-url__action-button"
149
+ icon="open_in_new"
150
+ :label="t('page.file.open')"
151
+ :href="openHref"
152
+ target="_blank"
153
+ rel="noopener noreferrer"
154
+ />
155
+ </div>
156
+ </template>
157
+
158
+ <div
159
+ v-else-if="caption"
160
+ class="d-page-embedded-url__compact-caption"
161
+ v-html="caption"
162
+ ></div>
163
+ </div>
164
+ </div>
165
+ </template>
166
+
167
+ <style lang="sass">
168
+ body.body--light
169
+ --d-page-embedded-url-bg: linear-gradient(180deg, #f7faf5 0%, #ffffff 100%)
170
+ --d-page-embedded-url-border: rgba(52, 85, 54, 0.14)
171
+ --d-page-embedded-url-shadow: rgba(52, 85, 54, 0.08)
172
+ --d-page-embedded-url-frame-bg: rgba(44, 60, 45, 0.04)
173
+ --d-page-embedded-url-media-bg: rgba(255, 255, 255, 0.92)
174
+ --d-page-embedded-url-media-border: rgba(52, 85, 54, 0.08)
175
+ --d-page-embedded-url-accent: #30583a
176
+ --d-page-embedded-url-meta: #56715c
177
+ --d-page-embedded-url-caption: #405148
178
+ --d-page-embedded-url-action-border: rgba(52, 85, 54, 0.18)
179
+ --d-page-embedded-url-action-hover: rgba(52, 85, 54, 0.06)
180
+
181
+ body.body--dark
182
+ --d-page-embedded-url-bg: linear-gradient(180deg, rgba(226, 255, 234, 0.05) 0%, rgba(255, 255, 255, 0.02) 100%)
183
+ --d-page-embedded-url-border: rgba(214, 245, 224, 0.14)
184
+ --d-page-embedded-url-shadow: rgba(0, 0, 0, 0.3)
185
+ --d-page-embedded-url-frame-bg: rgba(255, 255, 255, 0.03)
186
+ --d-page-embedded-url-media-bg: rgba(255, 255, 255, 0.06)
187
+ --d-page-embedded-url-media-border: rgba(255, 255, 255, 0.1)
188
+ --d-page-embedded-url-accent: #b9e2c5
189
+ --d-page-embedded-url-meta: rgba(255, 255, 255, 0.72)
190
+ --d-page-embedded-url-caption: rgba(255, 255, 255, 0.9)
191
+ --d-page-embedded-url-action-border: rgba(214, 245, 224, 0.16)
192
+ --d-page-embedded-url-action-hover: rgba(214, 245, 224, 0.08)
193
+
194
+ .d-page-embedded-url
195
+ margin: 1.5rem 0
196
+ border: 1px solid var(--d-page-embedded-url-border)
197
+ border-radius: 18px
198
+ background: var(--d-page-embedded-url-bg)
199
+ box-shadow: 0 16px 36px var(--d-page-embedded-url-shadow)
200
+ overflow: hidden
201
+
202
+ .d-page-embedded-url__frame-shell
203
+ position: relative
204
+ width: 100%
205
+ min-height: 240px
206
+ aspect-ratio: 16 / 9
207
+ background: var(--d-page-embedded-url-frame-bg)
208
+ border-bottom: 1px solid var(--d-page-embedded-url-border)
209
+
210
+ .d-page-embedded-url__frame
211
+ position: absolute
212
+ inset: 0
213
+ width: 100%
214
+ height: 100%
215
+ border: 0
216
+ background: transparent
217
+
218
+ .d-page-embedded-url__body
219
+ display: flex
220
+ align-items: center
221
+ gap: 1rem
222
+ padding: 0.95rem 1rem
223
+
224
+ .d-page-embedded-url--embedded
225
+ .d-page-embedded-url__body
226
+ padding-top: 1.25rem
227
+
228
+ .d-page-embedded-url__content
229
+ padding-top: 0.1rem
230
+
231
+ .d-page-embedded-url--compact
232
+ border: 0
233
+ border-radius: 0
234
+ background: transparent
235
+ box-shadow: none
236
+ overflow: visible
237
+
238
+ .d-page-embedded-url__frame-shell
239
+ border: 0
240
+ background: transparent
241
+
242
+ .d-page-embedded-url__body
243
+ display: block
244
+ padding: 0.6rem 0 0
245
+
246
+ .d-page-embedded-url__compact-caption
247
+ margin-top: 0.65rem
248
+ color: var(--d-page-embedded-url-caption)
249
+
250
+ > :first-child
251
+ margin-top: 0
252
+
253
+ > :last-child
254
+ margin-bottom: 0
255
+
256
+ .d-page-embedded-url__media
257
+ width: 56px
258
+ height: 56px
259
+ flex: 0 0 56px
260
+ display: flex
261
+ align-items: center
262
+ justify-content: center
263
+ border-radius: 16px
264
+ background: var(--d-page-embedded-url-media-bg)
265
+ box-shadow: inset 0 0 0 1px var(--d-page-embedded-url-media-border)
266
+ color: var(--d-page-embedded-url-accent)
267
+
268
+ .d-page-embedded-url__content
269
+ min-width: 0
270
+ flex: 1 1 auto
271
+
272
+ .d-page-embedded-url__provider
273
+ display: inline-flex
274
+ align-items: center
275
+ gap: 0.35rem
276
+ margin-bottom: 0.35rem
277
+ color: var(--d-page-embedded-url-meta)
278
+ font-size: 0.82rem
279
+ font-weight: 700
280
+ line-height: 1
281
+ text-transform: uppercase
282
+ letter-spacing: 0.06em
283
+
284
+ .d-page-embedded-url__title
285
+ font-size: 1rem
286
+ font-weight: 700
287
+ line-height: 1.4
288
+ word-break: break-word
289
+
290
+ .d-page-embedded-url__url
291
+ margin-top: 0.2rem
292
+ color: var(--d-page-embedded-url-meta)
293
+ font-size: 0.84rem
294
+ line-height: 1.35
295
+ word-break: break-all
296
+
297
+ .d-page-embedded-url__caption
298
+ margin-top: 0.45rem
299
+ color: var(--d-page-embedded-url-caption)
300
+
301
+ > :first-child
302
+ margin-top: 0
303
+
304
+ > :last-child
305
+ margin-bottom: 0
306
+
307
+ .d-page-embedded-url__actions
308
+ flex: 0 0 auto
309
+ display: flex
310
+ align-items: center
311
+ align-self: stretch
312
+
313
+ .d-page-embedded-url__action-button
314
+ min-height: 40px
315
+ border-radius: 10px
316
+ border: 1px solid var(--d-page-embedded-url-action-border)
317
+ background: transparent !important
318
+ color: var(--d-page-embedded-url-accent) !important
319
+ transition: transform 0.18s ease, background-color 0.18s ease, border-color 0.18s ease
320
+
321
+ .q-btn__content
322
+ gap: 0.45rem
323
+ font-size: 0.9rem
324
+ font-weight: 600
325
+ line-height: 1
326
+
327
+ .q-focus-helper,
328
+ &:before,
329
+ &:after
330
+ display: none
331
+
332
+ &:hover
333
+ transform: translateY(-1px)
334
+ background: var(--d-page-embedded-url-action-hover) !important
335
+
336
+ &:focus-visible
337
+ outline: 2px solid var(--d-page-embedded-url-accent)
338
+ outline-offset: 2px
339
+
340
+ .d-page-embedded-url--fallback
341
+ .d-page-embedded-url__body
342
+ padding-block: 1.05rem
343
+
344
+ @media (max-width: 720px)
345
+ .d-page-embedded-url__frame-shell
346
+ min-height: 200px
347
+
348
+ .d-page-embedded-url__body
349
+ flex-wrap: wrap
350
+ align-items: flex-start
351
+
352
+ .d-page-embedded-url__actions
353
+ width: 100%
354
+ justify-content: flex-start
355
+
356
+ .d-page-embedded-url__action-button
357
+ width: 100%
358
+ justify-content: center
359
+
360
+ @media (max-width: 520px)
361
+ .d-page-embedded-url__body
362
+ gap: 0.85rem
363
+ padding: 0.9rem
364
+
365
+ .d-page-embedded-url__media
366
+ width: 48px
367
+ height: 48px
368
+ flex-basis: 48px
369
+
370
+ .d-page-embedded-url__provider
371
+ font-size: 0.76rem
372
+
373
+ .d-page-embedded-url__title
374
+ font-size: 0.95rem
375
+ </style>
@@ -24,6 +24,7 @@ import DMermaidDiagram from './DMermaidDiagram.vue'
24
24
  import DPageBlockquote from './DPageBlockquote.vue'
25
25
  import DPageImage from './DPageImage.vue'
26
26
  import DPageFile from './DPageFile.vue'
27
+ import DPageEmbeddedUrl from './DPageEmbeddedUrl.vue'
27
28
  import DQuickLinks from './DQuickLinks.vue'
28
29
  import DPageExpandable from './DPageExpandable.vue'
29
30
  </script>
@@ -58,10 +59,12 @@ import DPageExpandable from './DPageExpandable.vue'
58
59
 
59
60
  <ul
60
61
  v-else-if="token.tag === 'ul'"
62
+ v-bind="token.attrs || {}"
61
63
  v-html="token.content"
62
64
  ></ul>
63
65
  <ol
64
66
  v-else-if="token.tag === 'ol'"
67
+ v-bind="token.attrs || {}"
65
68
  v-html="token.content"
66
69
  ></ol>
67
70
 
@@ -103,6 +106,13 @@ import DPageExpandable from './DPageExpandable.vue'
103
106
  :caption="token.caption"
104
107
  />
105
108
 
109
+ <d-page-embedded-url
110
+ v-else-if="token.tag === 'embedded-url'"
111
+ :url="token.url"
112
+ :title="token.title"
113
+ :caption="token.caption"
114
+ />
115
+
106
116
  <d-page-source-code
107
117
  v-else-if="token.tag === 'code'"
108
118
  :index="id + token.codeIndex"
@@ -2,6 +2,7 @@ import MarkdownIt from 'markdown-it'
2
2
  import attrs from 'markdown-it-attrs'
3
3
  import GithubSlugger from 'github-slugger'
4
4
  import katex from 'katex'
5
+ import taskLists from 'markdown-it-task-lists'
5
6
  import texmath from 'markdown-it-texmath'
6
7
 
7
8
  const ALERT_MESSAGE_TYPES = new Set([
@@ -15,6 +16,7 @@ const ALERT_MESSAGE_TYPES = new Set([
15
16
  const QUICK_LINKS_MARKER_PREFIX = '@@DOCSECTOR_QUICK_LINKS_'
16
17
  const EXPANDABLE_MARKER_PREFIX = '@@DOCSECTOR_EXPANDABLE_'
17
18
  const FILE_MARKER_PREFIX = '@@DOCSECTOR_FILE_'
19
+ const EMBEDDED_URL_MARKER_PREFIX = '@@DOCSECTOR_EMBEDDED_URL_'
18
20
  const CODE_SEGMENT_MARKER_PREFIX = '@@DOCSECTOR_CODE_SEGMENT_'
19
21
  const MATH_KATEX_OPTIONS = {
20
22
  throwOnError: false,
@@ -114,6 +116,32 @@ const restoreShieldedCodeSegments = (source = '', codeSegmentsMap = new Map()) =
114
116
  return restored
115
117
  }
116
118
 
119
+ const escapeHtmlAttributeValue = (value = '') => {
120
+ return String(value)
121
+ .replace(/&/g, '&amp;')
122
+ .replace(/"/g, '&quot;')
123
+ }
124
+
125
+ const getTokenAttributes = (element) => {
126
+ if (!Array.isArray(element?.attrs) || element.attrs.length === 0) {
127
+ return null
128
+ }
129
+
130
+ return Object.fromEntries(element.attrs.map(([name, value]) => [name, value ?? '']))
131
+ }
132
+
133
+ const renderTokenAttributes = (element) => {
134
+ const attributes = getTokenAttributes(element)
135
+
136
+ if (attributes === null) {
137
+ return ''
138
+ }
139
+
140
+ return Object.entries(attributes)
141
+ .map(([name, value]) => ` ${name}="${escapeHtmlAttributeValue(value)}"`)
142
+ .join('')
143
+ }
144
+
117
145
  const extractQuickLinksBlocks = (source = '') => {
118
146
  const map = new Map()
119
147
  let index = 0
@@ -247,6 +275,41 @@ const extractFileBlocks = (source = '') => {
247
275
  }
248
276
  }
249
277
 
278
+ const extractEmbeddedUrlBlocks = (source = '') => {
279
+ const map = new Map()
280
+ let index = 0
281
+
282
+ const replaceBlock = (match, rawAttrs, rawCaption = '') => {
283
+ const attrs = parseCustomTagAttributes(rawAttrs)
284
+ const url = decodeHtmlEntities(attrs.url || attrs.href || attrs.src || '').trim()
285
+
286
+ if (!url) {
287
+ return match
288
+ }
289
+
290
+ const marker = `${EMBEDDED_URL_MARKER_PREFIX}${index}@@`
291
+ index++
292
+
293
+ map.set(marker, {
294
+ url,
295
+ title: decodeHtmlEntities(attrs.title || '').trim(),
296
+ caption: String(rawCaption).trim()
297
+ })
298
+
299
+ return `\n${marker}\n`
300
+ }
301
+
302
+ const replacedSelfClosing = String(source).replace(/<d-embedded-url\b([^>]*)\/\s*>/gi, (match, rawAttrs) => {
303
+ return replaceBlock(match, rawAttrs)
304
+ })
305
+ const replaced = replacedSelfClosing.replace(/<d-embedded-url\b([^>]*)>([\s\S]*?)<\/d-embedded-url>/gi, replaceBlock)
306
+
307
+ return {
308
+ source: replaced,
309
+ embeddedUrlMap: map
310
+ }
311
+ }
312
+
250
313
  const parseFenceAttributes = (raw = '') => {
251
314
  const parsed = {}
252
315
  const pattern = /([\w-]+)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s;]+))/g
@@ -468,6 +531,14 @@ const renderBlockToken = (markdown, element, env) => {
468
531
  return markdown.renderer.render([element], markdown.options, env).trim()
469
532
  }
470
533
 
534
+ const renderInlineToken = (markdown, markdownInline, element, env) => {
535
+ if (Array.isArray(element.children) && element.children.length > 0) {
536
+ return markdown.renderer.renderInline(element.children, markdown.options, env)
537
+ }
538
+
539
+ return markdownInline.renderInline(element.content, env)
540
+ }
541
+
471
542
  const createMarkdownBlockParser = () => {
472
543
  const markdown = installMathSupport(new MarkdownIt({
473
544
  html: true
@@ -478,6 +549,11 @@ const createMarkdownBlockParser = () => {
478
549
  rightDelimiter: ';',
479
550
  allowedAttributes: ['filename', 'group', 'tab', 'breadcrumb']
480
551
  })
552
+ markdown.use(taskLists, {
553
+ enabled: false,
554
+ label: false,
555
+ labelAfter: false
556
+ })
481
557
 
482
558
  return markdown
483
559
  }
@@ -527,6 +603,7 @@ export const tokenizePageSectionSource = (source = '', options = {}) => {
527
603
 
528
604
  const { source: sourceWithQuickLinks, quickLinksMap } = extractQuickLinksBlocks(sourceWithExpandables)
529
605
  const { source: sourceWithFiles, fileMap } = extractFileBlocks(sourceWithQuickLinks)
606
+ const { source: sourceWithEmbeddedUrls, embeddedUrlMap } = extractEmbeddedUrlBlocks(sourceWithFiles)
530
607
 
531
608
  fileMap.forEach((data, marker) => {
532
609
  fileMap.set(marker, {
@@ -535,10 +612,17 @@ export const tokenizePageSectionSource = (source = '', options = {}) => {
535
612
  })
536
613
  })
537
614
 
615
+ embeddedUrlMap.forEach((data, marker) => {
616
+ embeddedUrlMap.set(marker, {
617
+ ...data,
618
+ caption: restoreShieldedCodeSegments(data.caption, codeSegmentsMap)
619
+ })
620
+ })
621
+
538
622
  const markdown = createMarkdownBlockParser()
539
623
  const markdownInline = createMarkdownInlineParser()
540
624
  const markdownEnv = {}
541
- const parsed = markdown.parse(restoreShieldedCodeSegments(sourceWithFiles, codeSegmentsMap), markdownEnv)
625
+ const parsed = markdown.parse(restoreShieldedCodeSegments(sourceWithEmbeddedUrls, codeSegmentsMap), markdownEnv)
542
626
  const tokens = []
543
627
 
544
628
  let level = 0
@@ -662,7 +746,7 @@ export const tokenizePageSectionSource = (source = '', options = {}) => {
662
746
  }
663
747
 
664
748
  if (element.type === 'inline') {
665
- element.content = markdownInline.renderInline(element.content, markdownEnv)
749
+ element.content = renderInlineToken(markdown, markdownInline, element, markdownEnv)
666
750
  }
667
751
 
668
752
  if (level === 0) {
@@ -722,6 +806,21 @@ export const tokenizePageSectionSource = (source = '', options = {}) => {
722
806
  break
723
807
  }
724
808
 
809
+ if (embeddedUrlMap.has(element.content.trim())) {
810
+ const data = embeddedUrlMap.get(element.content.trim())
811
+
812
+ tokens.push({
813
+ tag: 'embedded-url',
814
+ map: element.map,
815
+ url: data.url,
816
+ title: data.title,
817
+ caption: data.caption !== ''
818
+ ? markdownInline.renderInline(data.caption, markdownEnv)
819
+ : ''
820
+ })
821
+ break
822
+ }
823
+
725
824
  if (tag === 'p') {
726
825
  const imageToken = parseStandaloneImageToken(element.content)
727
826
 
@@ -779,23 +878,29 @@ export const tokenizePageSectionSource = (source = '', options = {}) => {
779
878
  switch (element.type) {
780
879
  case 'bullet_list_open':
781
880
  if (level === 1) {
881
+ const attributes = getTokenAttributes(element)
882
+
782
883
  tokens.push({
783
884
  tag: 'ul',
784
- content: ''
885
+ content: '',
886
+ ...(attributes !== null ? { attrs: attributes } : {})
785
887
  })
786
888
  } else {
787
- parent.content += '<ul>'
889
+ parent.content += `<ul${renderTokenAttributes(element)}>`
788
890
  }
789
891
  break
790
892
 
791
893
  case 'ordered_list_open':
792
894
  if (level === 1) {
895
+ const attributes = getTokenAttributes(element)
896
+
793
897
  tokens.push({
794
898
  tag: 'ol',
795
- content: ''
899
+ content: '',
900
+ ...(attributes !== null ? { attrs: attributes } : {})
796
901
  })
797
902
  } else {
798
- parent.content += '<ol>'
903
+ parent.content += `<ol${renderTokenAttributes(element)}>`
799
904
  }
800
905
  break
801
906
 
@@ -811,7 +916,7 @@ export const tokenizePageSectionSource = (source = '', options = {}) => {
811
916
  break
812
917
 
813
918
  case 'list_item_open':
814
- parent.content += '<li>'
919
+ parent.content += `<li${renderTokenAttributes(element)}>`
815
920
  break
816
921
 
817
922
  case 'thead_open':