@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 +17 -0
- package/bin/docsector.js +1 -1
- package/jsconfig.json +1 -1
- package/package.json +2 -1
- package/src/components/DPageEmbeddedUrl.vue +375 -0
- package/src/components/DPageTokens.vue +10 -0
- package/src/components/page-section-tokens.js +112 -7
- package/src/composables/useEmbeddedUrl.js +365 -0
- package/src/css/app.sass +43 -0
- package/src/pages/manual/content/blocks/embedded-urls.overview.en-US.md +33 -0
- package/src/pages/manual/content/blocks/embedded-urls.overview.pt-BR.md +33 -0
- package/src/pages/manual/content/blocks/embedded-urls.showcase.en-US.md +25 -0
- package/src/pages/manual/content/blocks/embedded-urls.showcase.pt-BR.md +25 -0
- package/src/pages/manual/content/blocks/task-lists.overview.en-US.md +20 -0
- package/src/pages/manual/content/blocks/task-lists.overview.pt-BR.md +20 -0
- package/src/pages/manual/content/blocks/task-lists.showcase.en-US.md +23 -0
- package/src/pages/manual/content/blocks/task-lists.showcase.pt-BR.md +23 -0
- package/src/pages/manual.index.js +56 -0
- package/src/pages/manual/components/d-subpage.overview.en-US.md +0 -60
- package/src/pages/manual/components/d-subpage.overview.pt-BR.md +0 -60
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
package/jsconfig.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@docsector/docsector-reader",
|
|
3
|
-
"version": "3.
|
|
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, '&')
|
|
122
|
+
.replace(/"/g, '"')
|
|
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(
|
|
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
|
|
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 +=
|
|
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 +=
|
|
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 +=
|
|
919
|
+
parent.content += `<li${renderTokenAttributes(element)}>`
|
|
815
920
|
break
|
|
816
921
|
|
|
817
922
|
case 'thead_open':
|