@docsector/docsector-reader 3.2.1 → 3.3.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 +22 -2
- package/bin/docsector.js +1 -1
- package/jsconfig.json +1 -0
- package/package.json +1 -1
- package/public/files/manual/release-checklist.txt +7 -0
- package/src/components/DPageFile.vue +430 -0
- package/src/components/DPageImage.vue +80 -0
- package/src/components/DPageTokens.vue +16 -0
- package/src/components/QZoom.js +68 -14
- package/src/components/QZoom.sass +38 -3
- package/src/components/page-section-tokens.js +243 -16
- package/src/i18n/languages/en-US.hjson +7 -0
- package/src/i18n/languages/pt-BR.hjson +7 -0
- package/src/pages/guide/i18n-and-markdown.overview.en-US.md +15 -1
- package/src/pages/guide/i18n-and-markdown.overview.pt-BR.md +15 -1
- package/src/pages/manual/content/blocks/files.overview.en-US.md +27 -0
- package/src/pages/manual/content/blocks/files.overview.pt-BR.md +27 -0
- package/src/pages/manual/content/blocks/files.showcase.en-US.md +17 -0
- package/src/pages/manual/content/blocks/files.showcase.pt-BR.md +17 -0
- package/src/pages/manual/content/blocks/images.overview.en-US.md +20 -2
- package/src/pages/manual/content/blocks/images.showcase.en-US.md +10 -2
- package/src/pages/manual.index.js +29 -0
package/src/components/QZoom.js
CHANGED
|
@@ -6,15 +6,13 @@
|
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
8
|
h,
|
|
9
|
+
Teleport,
|
|
9
10
|
ref, computed,
|
|
10
11
|
onMounted, onBeforeUnmount
|
|
11
12
|
} from 'vue'
|
|
12
13
|
|
|
13
14
|
import { useColorize } from 'q-colorize-mixin'
|
|
14
15
|
|
|
15
|
-
import { dom } from 'quasar'
|
|
16
|
-
const { offset } = dom
|
|
17
|
-
|
|
18
16
|
import './QZoom.sass'
|
|
19
17
|
|
|
20
18
|
// @
|
|
@@ -34,7 +32,12 @@ export default {
|
|
|
34
32
|
initialScaleText: { type: Number, default: 100, validator: v => v >= 50 && v <= 500 },
|
|
35
33
|
noCenter: Boolean,
|
|
36
34
|
noWheelScale: Boolean,
|
|
37
|
-
noEscClose: Boolean
|
|
35
|
+
noEscClose: Boolean,
|
|
36
|
+
showCloseButton: Boolean,
|
|
37
|
+
closeButtonLabel: {
|
|
38
|
+
type: String,
|
|
39
|
+
default: 'Close zoom'
|
|
40
|
+
}
|
|
38
41
|
},
|
|
39
42
|
|
|
40
43
|
setup (props, { emit, slots }) {
|
|
@@ -183,13 +186,29 @@ export default {
|
|
|
183
186
|
}
|
|
184
187
|
}
|
|
185
188
|
|
|
189
|
+
const onOverlayClick = (e) => {
|
|
190
|
+
if (isZoomed.value) {
|
|
191
|
+
hide()
|
|
192
|
+
|
|
193
|
+
e.preventDefault()
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const stopEvent = (e) => {
|
|
198
|
+
e.stopPropagation()
|
|
199
|
+
}
|
|
200
|
+
|
|
186
201
|
const getPosition = () => {
|
|
187
|
-
const
|
|
202
|
+
const rect = vComponent.value.getBoundingClientRect()
|
|
203
|
+
const position = {
|
|
204
|
+
left: rect.left,
|
|
205
|
+
top: rect.top
|
|
206
|
+
}
|
|
188
207
|
|
|
189
208
|
position.left = position.left + 'px'
|
|
190
209
|
position.top = position.top + 'px'
|
|
191
|
-
position.width =
|
|
192
|
-
position.height =
|
|
210
|
+
position.width = rect.width + 'px'
|
|
211
|
+
position.height = rect.height + 'px'
|
|
193
212
|
|
|
194
213
|
return position
|
|
195
214
|
}
|
|
@@ -245,7 +264,34 @@ export default {
|
|
|
245
264
|
fontSize: props.scaleText && !props.scale && `${scaleTextValue.value}%`
|
|
246
265
|
}
|
|
247
266
|
}), [
|
|
248
|
-
|
|
267
|
+
h('div', {
|
|
268
|
+
class: 'q-zoom__content-inner',
|
|
269
|
+
onClick: stopEvent
|
|
270
|
+
}, [
|
|
271
|
+
slot && slot({ zoomed: isZoomed.value })
|
|
272
|
+
])
|
|
273
|
+
])
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const __renderCloseButton = () => {
|
|
277
|
+
if (!props.showCloseButton) {
|
|
278
|
+
return null
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return h('button', {
|
|
282
|
+
type: 'button',
|
|
283
|
+
class: 'q-zoom__close-button',
|
|
284
|
+
'aria-label': props.closeButtonLabel,
|
|
285
|
+
title: props.closeButtonLabel,
|
|
286
|
+
onClick: (e) => {
|
|
287
|
+
stopEvent(e)
|
|
288
|
+
hide()
|
|
289
|
+
}
|
|
290
|
+
}, [
|
|
291
|
+
h('span', {
|
|
292
|
+
class: 'q-zoom__close-icon',
|
|
293
|
+
'aria-hidden': 'true'
|
|
294
|
+
}, '×')
|
|
249
295
|
])
|
|
250
296
|
}
|
|
251
297
|
|
|
@@ -256,13 +302,18 @@ export default {
|
|
|
256
302
|
|
|
257
303
|
return h('div', Colorize.setBackgroundColor(bgColor.value, {
|
|
258
304
|
class: 'q-zoom__overlay' +
|
|
259
|
-
(props.manual ? '' : ' q-zoom__zoom-out')
|
|
305
|
+
(props.manual ? '' : ' q-zoom__zoom-out'),
|
|
306
|
+
onClick: onOverlayClick
|
|
260
307
|
}), [
|
|
308
|
+
__renderCloseButton(),
|
|
261
309
|
__renderOverlayContent()
|
|
262
310
|
])
|
|
263
311
|
}
|
|
264
312
|
|
|
265
|
-
return () =>
|
|
313
|
+
return () => {
|
|
314
|
+
const overlay = __renderOverlay()
|
|
315
|
+
|
|
316
|
+
return h('div', {
|
|
266
317
|
class: 'q-zoom' +
|
|
267
318
|
(props.manual ? '' : ' q-zoom__zoom-in'),
|
|
268
319
|
|
|
@@ -270,9 +321,12 @@ export default {
|
|
|
270
321
|
onWheel: wheelEvent,
|
|
271
322
|
|
|
272
323
|
ref: vComponent
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
324
|
+
}, [
|
|
325
|
+
slots.default && slots.default({ zoomed: isZoomed.value }),
|
|
326
|
+
overlay
|
|
327
|
+
? h(Teleport, { to: 'body' }, overlay)
|
|
328
|
+
: null
|
|
329
|
+
])
|
|
330
|
+
}
|
|
277
331
|
}
|
|
278
332
|
}
|
|
@@ -22,19 +22,54 @@ $box-shadow: 1px 1px 7px 1px rgba(0,0,0,.2) !default
|
|
|
22
22
|
padding: 0
|
|
23
23
|
margin: 0
|
|
24
24
|
z-index: 6000
|
|
25
|
+
overflow: hidden
|
|
25
26
|
|
|
26
27
|
&__content
|
|
27
|
-
position:
|
|
28
|
-
display:
|
|
28
|
+
position: absolute
|
|
29
|
+
display: flex
|
|
30
|
+
align-items: center
|
|
31
|
+
justify-content: center
|
|
29
32
|
transition: all .5s cubic-bezier(.2,0,.2,1)
|
|
30
33
|
text-align: center
|
|
31
|
-
vertical-align: middle
|
|
32
34
|
width: 100%
|
|
33
35
|
height: 0
|
|
34
36
|
max-width: 100%
|
|
35
37
|
max-height: 100%
|
|
36
38
|
overflow: hidden
|
|
37
39
|
|
|
40
|
+
& > *
|
|
41
|
+
max-width: 100%
|
|
42
|
+
max-height: 100%
|
|
43
|
+
|
|
44
|
+
&__content-inner
|
|
45
|
+
display: inline-flex
|
|
46
|
+
align-items: center
|
|
47
|
+
justify-content: center
|
|
48
|
+
max-width: 100%
|
|
49
|
+
max-height: 100%
|
|
50
|
+
|
|
51
|
+
&__close-button
|
|
52
|
+
position: absolute
|
|
53
|
+
top: 24px
|
|
54
|
+
right: 24px
|
|
55
|
+
width: 56px
|
|
56
|
+
height: 56px
|
|
57
|
+
display: inline-flex
|
|
58
|
+
align-items: center
|
|
59
|
+
justify-content: center
|
|
60
|
+
border: 0
|
|
61
|
+
border-radius: 999px
|
|
62
|
+
background: rgba(255,255,255,.12)
|
|
63
|
+
color: #fff
|
|
64
|
+
cursor: pointer
|
|
65
|
+
z-index: 6001
|
|
66
|
+
box-shadow: $box-shadow
|
|
67
|
+
backdrop-filter: blur(8px)
|
|
68
|
+
|
|
69
|
+
&__close-icon
|
|
70
|
+
font-size: 34px
|
|
71
|
+
line-height: 1
|
|
72
|
+
|
|
38
73
|
&__no-center
|
|
39
74
|
text-align: unset
|
|
40
75
|
vertical-align: unset
|
|
@@ -14,6 +14,7 @@ const ALERT_MESSAGE_TYPES = new Set([
|
|
|
14
14
|
|
|
15
15
|
const QUICK_LINKS_MARKER_PREFIX = '@@DOCSECTOR_QUICK_LINKS_'
|
|
16
16
|
const EXPANDABLE_MARKER_PREFIX = '@@DOCSECTOR_EXPANDABLE_'
|
|
17
|
+
const FILE_MARKER_PREFIX = '@@DOCSECTOR_FILE_'
|
|
17
18
|
const CODE_SEGMENT_MARKER_PREFIX = '@@DOCSECTOR_CODE_SEGMENT_'
|
|
18
19
|
const MATH_KATEX_OPTIONS = {
|
|
19
20
|
throwOnError: false,
|
|
@@ -190,6 +191,62 @@ const extractExpandableBlocks = (source = '') => {
|
|
|
190
191
|
}
|
|
191
192
|
}
|
|
192
193
|
|
|
194
|
+
const getFileTitleFromSrc = (src = '') => {
|
|
195
|
+
const normalized = String(src)
|
|
196
|
+
.split('#')[0]
|
|
197
|
+
.split('?')[0]
|
|
198
|
+
const rawSegment = normalized
|
|
199
|
+
.split('/')
|
|
200
|
+
.filter(Boolean)
|
|
201
|
+
.pop() || ''
|
|
202
|
+
|
|
203
|
+
if (!rawSegment) {
|
|
204
|
+
return 'Download file'
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
return decodeURIComponent(rawSegment)
|
|
209
|
+
} catch {
|
|
210
|
+
return rawSegment
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const extractFileBlocks = (source = '') => {
|
|
215
|
+
const map = new Map()
|
|
216
|
+
let index = 0
|
|
217
|
+
|
|
218
|
+
const replaceBlock = (match, rawAttrs, rawCaption = '') => {
|
|
219
|
+
const attrs = parseCustomTagAttributes(rawAttrs)
|
|
220
|
+
const src = decodeHtmlEntities(attrs.src || attrs.href || '').trim()
|
|
221
|
+
|
|
222
|
+
if (!src) {
|
|
223
|
+
return match
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const marker = `${FILE_MARKER_PREFIX}${index}@@`
|
|
227
|
+
index++
|
|
228
|
+
|
|
229
|
+
map.set(marker, {
|
|
230
|
+
src,
|
|
231
|
+
title: decodeHtmlEntities(attrs.title || attrs.name || '').trim(),
|
|
232
|
+
size: decodeHtmlEntities(attrs.size || '').trim(),
|
|
233
|
+
caption: String(rawCaption).trim()
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
return `\n${marker}\n`
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const replacedSelfClosing = String(source).replace(/<d-file\b([^>]*)\/\s*>/gi, (match, rawAttrs) => {
|
|
240
|
+
return replaceBlock(match, rawAttrs)
|
|
241
|
+
})
|
|
242
|
+
const replaced = replacedSelfClosing.replace(/<d-file\b([^>]*)>([\s\S]*?)<\/d-file>/gi, replaceBlock)
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
source: replaced,
|
|
246
|
+
fileMap: map
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
193
250
|
const parseFenceAttributes = (raw = '') => {
|
|
194
251
|
const parsed = {}
|
|
195
252
|
const pattern = /([\w-]+)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s;]+))/g
|
|
@@ -215,6 +272,97 @@ const parseTokenAttributes = (element) => {
|
|
|
215
272
|
return parsed
|
|
216
273
|
}
|
|
217
274
|
|
|
275
|
+
const decodeHtmlEntities = (value = '') => {
|
|
276
|
+
return String(value)
|
|
277
|
+
.replace(/"/g, '"')
|
|
278
|
+
.replace(/'|'/g, "'")
|
|
279
|
+
.replace(/</g, '<')
|
|
280
|
+
.replace(/>/g, '>')
|
|
281
|
+
.replace(/&/g, '&')
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const escapeHtml = (value = '') => {
|
|
285
|
+
return String(value)
|
|
286
|
+
.replace(/&/g, '&')
|
|
287
|
+
.replace(/</g, '<')
|
|
288
|
+
.replace(/>/g, '>')
|
|
289
|
+
.replace(/"/g, '"')
|
|
290
|
+
.replace(/'/g, ''')
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const extractTagAttributes = (html = '', tagName = '') => {
|
|
294
|
+
const match = String(html).match(new RegExp(`<${tagName}\\b([^>]*)\\/?>(?![\\s\\S]*<${tagName}\\b)`, 'i'))
|
|
295
|
+
|
|
296
|
+
if (!match) {
|
|
297
|
+
return {}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return parseCustomTagAttributes(match[1])
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const createImageTokenData = (mediaHtml = '', options = {}) => {
|
|
304
|
+
const {
|
|
305
|
+
captionHtml = '',
|
|
306
|
+
fallbackCaptionFromAlt = false
|
|
307
|
+
} = options
|
|
308
|
+
const attrs = extractTagAttributes(mediaHtml, 'img')
|
|
309
|
+
const alt = decodeHtmlEntities(attrs.alt || '')
|
|
310
|
+
const title = decodeHtmlEntities(attrs.title || '')
|
|
311
|
+
|
|
312
|
+
return {
|
|
313
|
+
tag: 'image',
|
|
314
|
+
content: String(mediaHtml).trim(),
|
|
315
|
+
alt,
|
|
316
|
+
title,
|
|
317
|
+
captionHtml: captionHtml !== ''
|
|
318
|
+
? captionHtml
|
|
319
|
+
: (fallbackCaptionFromAlt ? escapeHtml(alt) : '')
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const parseStandaloneImageToken = (content = '') => {
|
|
324
|
+
const trimmed = String(content).trim()
|
|
325
|
+
|
|
326
|
+
if (!trimmed.match(/^<img\b[^>]*\/?>(?:\s*)$/i)) {
|
|
327
|
+
return null
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return createImageTokenData(trimmed, {
|
|
331
|
+
fallbackCaptionFromAlt: true
|
|
332
|
+
})
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const parseFigureImageToken = (content = '') => {
|
|
336
|
+
const trimmed = String(content).trim()
|
|
337
|
+
|
|
338
|
+
if (!trimmed.match(/^<figure\b[\s\S]*<\/figure>$/i)) {
|
|
339
|
+
return null
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const figureBody = trimmed
|
|
343
|
+
.replace(/^<figure\b[^>]*>/i, '')
|
|
344
|
+
.replace(/<\/figure>$/i, '')
|
|
345
|
+
.trim()
|
|
346
|
+
const figcaptionMatch = figureBody.match(/<figcaption\b[^>]*>([\s\S]*?)<\/figcaption>/i)
|
|
347
|
+
const mediaBody = figcaptionMatch
|
|
348
|
+
? figureBody.replace(figcaptionMatch[0], '').trim()
|
|
349
|
+
: figureBody
|
|
350
|
+
const pictureMatch = mediaBody.match(/^<picture\b[\s\S]*?<\/picture>$/i)
|
|
351
|
+
const imgMatch = pictureMatch
|
|
352
|
+
? pictureMatch[0].match(/<img\b[^>]*\/?>/i)
|
|
353
|
+
: mediaBody.match(/^<img\b[^>]*\/?>$/i)
|
|
354
|
+
|
|
355
|
+
if (!imgMatch) {
|
|
356
|
+
return null
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const mediaHtml = pictureMatch ? pictureMatch[0] : imgMatch[0]
|
|
360
|
+
|
|
361
|
+
return createImageTokenData(mediaHtml, {
|
|
362
|
+
captionHtml: figcaptionMatch ? figcaptionMatch[1].trim() : ''
|
|
363
|
+
})
|
|
364
|
+
}
|
|
365
|
+
|
|
218
366
|
const parseFenceLanguage = (raw = '') => {
|
|
219
367
|
const cleaned = String(raw)
|
|
220
368
|
.replace(/:[^;]*;/g, ' ')
|
|
@@ -378,10 +526,19 @@ export const tokenizePageSectionSource = (source = '', options = {}) => {
|
|
|
378
526
|
})
|
|
379
527
|
|
|
380
528
|
const { source: sourceWithQuickLinks, quickLinksMap } = extractQuickLinksBlocks(sourceWithExpandables)
|
|
529
|
+
const { source: sourceWithFiles, fileMap } = extractFileBlocks(sourceWithQuickLinks)
|
|
530
|
+
|
|
531
|
+
fileMap.forEach((data, marker) => {
|
|
532
|
+
fileMap.set(marker, {
|
|
533
|
+
...data,
|
|
534
|
+
caption: restoreShieldedCodeSegments(data.caption, codeSegmentsMap)
|
|
535
|
+
})
|
|
536
|
+
})
|
|
537
|
+
|
|
381
538
|
const markdown = createMarkdownBlockParser()
|
|
382
539
|
const markdownInline = createMarkdownInlineParser()
|
|
383
540
|
const markdownEnv = {}
|
|
384
|
-
const parsed = markdown.parse(restoreShieldedCodeSegments(
|
|
541
|
+
const parsed = markdown.parse(restoreShieldedCodeSegments(sourceWithFiles, codeSegmentsMap), markdownEnv)
|
|
385
542
|
const tokens = []
|
|
386
543
|
|
|
387
544
|
let level = 0
|
|
@@ -520,7 +677,7 @@ export const tokenizePageSectionSource = (source = '', options = {}) => {
|
|
|
520
677
|
}
|
|
521
678
|
|
|
522
679
|
switch (element.type) {
|
|
523
|
-
case 'inline':
|
|
680
|
+
case 'inline': {
|
|
524
681
|
const anchorId = getHeadingAnchorId(markdown, tag, element, markdownEnv, parserState)
|
|
525
682
|
|
|
526
683
|
if (expandableMap.has(element.content.trim())) {
|
|
@@ -549,6 +706,34 @@ export const tokenizePageSectionSource = (source = '', options = {}) => {
|
|
|
549
706
|
break
|
|
550
707
|
}
|
|
551
708
|
|
|
709
|
+
if (fileMap.has(element.content.trim())) {
|
|
710
|
+
const data = fileMap.get(element.content.trim())
|
|
711
|
+
|
|
712
|
+
tokens.push({
|
|
713
|
+
tag: 'file',
|
|
714
|
+
map: element.map,
|
|
715
|
+
src: data.src,
|
|
716
|
+
title: data.title || getFileTitleFromSrc(data.src),
|
|
717
|
+
size: data.size,
|
|
718
|
+
caption: data.caption !== ''
|
|
719
|
+
? markdownInline.renderInline(data.caption, markdownEnv)
|
|
720
|
+
: ''
|
|
721
|
+
})
|
|
722
|
+
break
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
if (tag === 'p') {
|
|
726
|
+
const imageToken = parseStandaloneImageToken(element.content)
|
|
727
|
+
|
|
728
|
+
if (imageToken !== null) {
|
|
729
|
+
tokens.push({
|
|
730
|
+
...imageToken,
|
|
731
|
+
map: element.map
|
|
732
|
+
})
|
|
733
|
+
break
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
552
737
|
tokens.push({
|
|
553
738
|
tag,
|
|
554
739
|
map: element.map,
|
|
@@ -557,6 +742,7 @@ export const tokenizePageSectionSource = (source = '', options = {}) => {
|
|
|
557
742
|
info: element.info
|
|
558
743
|
})
|
|
559
744
|
break
|
|
745
|
+
}
|
|
560
746
|
|
|
561
747
|
case 'fence':
|
|
562
748
|
pushSourceCodeToken(tokens, element, parserState)
|
|
@@ -569,36 +755,59 @@ export const tokenizePageSectionSource = (source = '', options = {}) => {
|
|
|
569
755
|
})
|
|
570
756
|
break
|
|
571
757
|
|
|
572
|
-
case 'html_block':
|
|
758
|
+
case 'html_block': {
|
|
759
|
+
const figureImageToken = parseFigureImageToken(element.content)
|
|
760
|
+
|
|
761
|
+
if (figureImageToken !== null) {
|
|
762
|
+
tokens.push({
|
|
763
|
+
...figureImageToken,
|
|
764
|
+
map: element.map
|
|
765
|
+
})
|
|
766
|
+
break
|
|
767
|
+
}
|
|
768
|
+
|
|
573
769
|
tokens.push({
|
|
574
770
|
tag: 'html',
|
|
575
771
|
content: element.content
|
|
576
772
|
})
|
|
577
773
|
break
|
|
774
|
+
}
|
|
578
775
|
}
|
|
579
|
-
} else if (level
|
|
776
|
+
} else if (level > 0) {
|
|
580
777
|
const parent = tokens[tokens.length - 1]
|
|
581
778
|
|
|
582
779
|
switch (element.type) {
|
|
583
780
|
case 'bullet_list_open':
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
781
|
+
if (level === 1) {
|
|
782
|
+
tokens.push({
|
|
783
|
+
tag: 'ul',
|
|
784
|
+
content: ''
|
|
785
|
+
})
|
|
786
|
+
} else {
|
|
787
|
+
parent.content += '<ul>'
|
|
788
|
+
}
|
|
588
789
|
break
|
|
589
790
|
|
|
590
791
|
case 'ordered_list_open':
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
792
|
+
if (level === 1) {
|
|
793
|
+
tokens.push({
|
|
794
|
+
tag: 'ol',
|
|
795
|
+
content: ''
|
|
796
|
+
})
|
|
797
|
+
} else {
|
|
798
|
+
parent.content += '<ol>'
|
|
799
|
+
}
|
|
595
800
|
break
|
|
596
801
|
|
|
597
802
|
case 'table_open':
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
803
|
+
if (level === 1) {
|
|
804
|
+
tokens.push({
|
|
805
|
+
tag: 'table',
|
|
806
|
+
content: ''
|
|
807
|
+
})
|
|
808
|
+
} else {
|
|
809
|
+
parent.content += '<table>'
|
|
810
|
+
}
|
|
602
811
|
break
|
|
603
812
|
|
|
604
813
|
case 'list_item_open':
|
|
@@ -636,6 +845,24 @@ export const tokenizePageSectionSource = (source = '', options = {}) => {
|
|
|
636
845
|
parent.content += '</li>'
|
|
637
846
|
break
|
|
638
847
|
|
|
848
|
+
case 'bullet_list_close':
|
|
849
|
+
if (level > 1) {
|
|
850
|
+
parent.content += '</ul>'
|
|
851
|
+
}
|
|
852
|
+
break
|
|
853
|
+
|
|
854
|
+
case 'ordered_list_close':
|
|
855
|
+
if (level > 1) {
|
|
856
|
+
parent.content += '</ol>'
|
|
857
|
+
}
|
|
858
|
+
break
|
|
859
|
+
|
|
860
|
+
case 'table_close':
|
|
861
|
+
if (level > 1) {
|
|
862
|
+
parent.content += '</table>'
|
|
863
|
+
}
|
|
864
|
+
break
|
|
865
|
+
|
|
639
866
|
case 'thead_close':
|
|
640
867
|
parent.content += '</thead>'
|
|
641
868
|
break
|
|
@@ -38,6 +38,13 @@
|
|
|
38
38
|
copied: 'Copied!',
|
|
39
39
|
viewAsMarkdown: 'View as Markdown',
|
|
40
40
|
viewAsMarkdownCaption: 'View this page as plain text',
|
|
41
|
+
file: {
|
|
42
|
+
label: 'File',
|
|
43
|
+
defaultTitle: 'Download file',
|
|
44
|
+
external: 'External file',
|
|
45
|
+
download: 'Download',
|
|
46
|
+
open: 'Open'
|
|
47
|
+
},
|
|
41
48
|
openInChatGPT: 'Open in ChatGPT',
|
|
42
49
|
openInChatGPTCaption: 'Ask ChatGPT about this page',
|
|
43
50
|
openInClaude: 'Open in Claude',
|
|
@@ -37,6 +37,13 @@
|
|
|
37
37
|
copied: 'Copiado!',
|
|
38
38
|
viewAsMarkdown: 'Ver como Markdown',
|
|
39
39
|
viewAsMarkdownCaption: 'Ver esta página como texto simples',
|
|
40
|
+
file: {
|
|
41
|
+
label: 'Arquivo',
|
|
42
|
+
defaultTitle: 'Baixar arquivo',
|
|
43
|
+
external: 'Arquivo externo',
|
|
44
|
+
download: 'Baixar',
|
|
45
|
+
open: 'Abrir'
|
|
46
|
+
},
|
|
40
47
|
openInChatGPT: 'Abrir no ChatGPT',
|
|
41
48
|
openInChatGPTCaption: 'Pergunte ao ChatGPT sobre esta página',
|
|
42
49
|
openInClaude: 'Abrir no Claude',
|
|
@@ -45,7 +45,7 @@ See the dedicated manual pages for block-by-block reference:
|
|
|
45
45
|
|
|
46
46
|
- [Paragraphs](/manual/content/blocks/paragraphs/overview/), [Headings](/manual/content/blocks/headings/overview/), [Unordered lists](/manual/content/blocks/unordered-lists/overview/), [Ordered lists](/manual/content/blocks/ordered-lists/overview/)
|
|
47
47
|
- [Hints](/manual/content/blocks/hints/overview/), [Quote](/manual/content/blocks/quotes/overview/), [Code blocks](/manual/content/blocks/code-blocks/overview/), [Mermaid diagrams](/manual/content/blocks/mermaid-diagrams/overview/)
|
|
48
|
-
- [Images](/manual/content/blocks/images/overview/), [Math & TeX](/manual/content/blocks/math-and-tex/overview/), [Expandable](/manual/content/blocks/expandable/overview/), [Tables](/manual/content/blocks/tables/overview/), [Raw HTML](/manual/content/blocks/raw-html/overview/), and [Quick Links](/manual/content/blocks/quick-links/overview/)
|
|
48
|
+
- [Images](/manual/content/blocks/images/overview/), [Files](/manual/content/blocks/files/overview/), [Math & TeX](/manual/content/blocks/math-and-tex/overview/), [Expandable](/manual/content/blocks/expandable/overview/), [Tables](/manual/content/blocks/tables/overview/), [Raw HTML](/manual/content/blocks/raw-html/overview/), and [Quick Links](/manual/content/blocks/quick-links/overview/)
|
|
49
49
|
|
|
50
50
|
### Headings
|
|
51
51
|
|
|
@@ -132,6 +132,20 @@ Set `open="true"` when the block should start expanded.
|
|
|
132
132
|
|
|
133
133
|
The expandable body supports paragraphs, lists, alerts, code blocks, Mermaid diagrams, tables, raw HTML, and quick links. Keep headings outside the expandable block in this first version, because headings inside the body are flattened to regular paragraphs to preserve the page ToC.
|
|
134
134
|
|
|
135
|
+
### File Blocks
|
|
136
|
+
|
|
137
|
+
Use `<d-file>` to publish downloadable attachments from repo-tracked files or external storage without leaving Markdown:
|
|
138
|
+
|
|
139
|
+
```html
|
|
140
|
+
<d-file src="/files/manual/release-checklist.txt" title="Release checklist" size="1 KB">
|
|
141
|
+
Download the example attachment used in this manual.
|
|
142
|
+
</d-file>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Store repo-tracked files under `public/files/` and prefer absolute site paths such as `/files/...`.
|
|
146
|
+
|
|
147
|
+
`title` and `size` are optional. When `title` is omitted, the UI falls back to the file name from `src`. The caption body supports inline Markdown, and the same syntax also works with external URLs if you later move storage to R2 or another CDN.
|
|
148
|
+
|
|
135
149
|
## Adding a New Language
|
|
136
150
|
|
|
137
151
|
1. Create `src/i18n/languages/xx-XX.hjson` with all UI translations
|
|
@@ -45,7 +45,7 @@ Veja também as páginas dedicadas do manual para cada bloco:
|
|
|
45
45
|
|
|
46
46
|
- [Parágrafos](/manual/content/blocks/paragraphs/overview/), [Títulos](/manual/content/blocks/headings/overview/), [Listas não ordenadas](/manual/content/blocks/unordered-lists/overview/), [Listas ordenadas](/manual/content/blocks/ordered-lists/overview/)
|
|
47
47
|
- [Hints](/manual/content/blocks/hints/overview/), [Citação](/manual/content/blocks/quotes/overview/), [Blocos de código](/manual/content/blocks/code-blocks/overview/), [Diagramas Mermaid](/manual/content/blocks/mermaid-diagrams/overview/)
|
|
48
|
-
- [Imagens](/manual/content/blocks/images/overview/), [Math & TeX](/manual/content/blocks/math-and-tex/overview/), [Expansível](/manual/content/blocks/expandable/overview/), [Tabelas](/manual/content/blocks/tables/overview/), [HTML bruto](/manual/content/blocks/raw-html/overview/) e [Quick Links](/manual/content/blocks/quick-links/overview/)
|
|
48
|
+
- [Imagens](/manual/content/blocks/images/overview/), [Arquivos](/manual/content/blocks/files/overview/), [Math & TeX](/manual/content/blocks/math-and-tex/overview/), [Expansível](/manual/content/blocks/expandable/overview/), [Tabelas](/manual/content/blocks/tables/overview/), [HTML bruto](/manual/content/blocks/raw-html/overview/) e [Quick Links](/manual/content/blocks/quick-links/overview/)
|
|
49
49
|
|
|
50
50
|
### Títulos
|
|
51
51
|
|
|
@@ -132,6 +132,20 @@ Defina `open="true"` quando o bloco precisar começar aberto.
|
|
|
132
132
|
|
|
133
133
|
O corpo do expansível suporta parágrafos, listas, alertas, blocos de código, diagramas Mermaid, tabelas, HTML bruto e quick links. Nesta primeira versão, mantenha títulos fora do bloco expansível, porque títulos dentro do corpo viram parágrafos comuns para preservar o ToC da página.
|
|
134
134
|
|
|
135
|
+
### Blocos de Arquivo
|
|
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:
|
|
138
|
+
|
|
139
|
+
```html
|
|
140
|
+
<d-file src="/files/manual/release-checklist.txt" title="Checklist de release" size="1 KB">
|
|
141
|
+
Baixe o anexo de exemplo usado neste manual.
|
|
142
|
+
</d-file>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Guarde arquivos versionados no repositório em `public/files/` e prefira caminhos absolutos do site, como `/files/...`.
|
|
146
|
+
|
|
147
|
+
`title` e `size` são opcionais. Quando `title` não é informado, a UI usa o nome do arquivo presente em `src`. O corpo do bloco funciona como legenda com Markdown inline, e a mesma sintaxe também aceita URLs externas caso você mova o storage para R2 ou outro CDN no futuro.
|
|
148
|
+
|
|
135
149
|
## Adicionando um Novo Idioma
|
|
136
150
|
|
|
137
151
|
1. Crie `src/i18n/languages/xx-XX.hjson` com todas as traduções de UI
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
## Overview
|
|
2
|
+
|
|
3
|
+
Files render a download card directly inside Markdown so a page or subpage can publish attachments without leaving the normal authoring flow.
|
|
4
|
+
|
|
5
|
+
They are useful for checklists, sample bundles, PDFs, release notes, and any other file that should be linked from the reading flow.
|
|
6
|
+
|
|
7
|
+
## Markdown Syntax
|
|
8
|
+
|
|
9
|
+
```html
|
|
10
|
+
<d-file src="/files/manual/release-checklist.txt" title="Release checklist" size="1 KB">
|
|
11
|
+
Download the example attachment used in this manual.
|
|
12
|
+
</d-file>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
You can also omit the caption body when the file name already provides enough context:
|
|
16
|
+
|
|
17
|
+
```html
|
|
18
|
+
<d-file src="/files/manual/release-checklist.txt" size="1 KB" />
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Notes
|
|
22
|
+
|
|
23
|
+
- Store small repo-tracked attachments under `public/files/` and prefer absolute paths such as `/files/...`.
|
|
24
|
+
- `src` is required. `title` and `size` are optional.
|
|
25
|
+
- When `title` is omitted, the rendered card falls back to the file name from `src`.
|
|
26
|
+
- The block body is rendered as an inline Markdown caption.
|
|
27
|
+
- External URLs also work, so the same syntax can point to a future R2 bucket or another CDN.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
## Visão Geral
|
|
2
|
+
|
|
3
|
+
Arquivos renderizam um card de download diretamente no Markdown para que uma page ou subpage publique anexos sem sair do fluxo normal de autoria.
|
|
4
|
+
|
|
5
|
+
Eles são úteis para checklists, bundles de exemplo, PDFs, notas de release e qualquer outro arquivo que precise ficar no fluxo de leitura.
|
|
6
|
+
|
|
7
|
+
## Sintaxe em Markdown
|
|
8
|
+
|
|
9
|
+
```html
|
|
10
|
+
<d-file src="/files/manual/release-checklist.txt" title="Checklist de release" size="1 KB">
|
|
11
|
+
Baixe o anexo de exemplo usado neste manual.
|
|
12
|
+
</d-file>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Você também pode omitir o corpo da legenda quando o nome do arquivo já fornece contexto suficiente:
|
|
16
|
+
|
|
17
|
+
```html
|
|
18
|
+
<d-file src="/files/manual/release-checklist.txt" size="1 KB" />
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Observações
|
|
22
|
+
|
|
23
|
+
- Guarde anexos pequenos versionados no repositório em `public/files/` e prefira caminhos absolutos como `/files/...`.
|
|
24
|
+
- `src` é obrigatório. `title` e `size` são opcionais.
|
|
25
|
+
- Quando `title` não é informado, o card renderizado usa o nome do arquivo presente em `src`.
|
|
26
|
+
- O corpo do bloco é renderizado como legenda com Markdown inline.
|
|
27
|
+
- URLs externas também funcionam, então a mesma sintaxe pode apontar no futuro para um bucket R2 ou outro CDN.
|