@docsector/docsector-reader 3.2.2 → 3.4.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 +21 -2
- package/bin/docsector.js +1 -1
- package/package.json +2 -1
- package/public/files/manual/release-checklist.txt +7 -0
- package/src/components/DPageFile.vue +430 -0
- package/src/components/DPageTokens.vue +11 -0
- package/src/components/page-section-tokens.js +135 -7
- package/src/css/app.sass +43 -0
- 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/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 +57 -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
|
|
@@ -71,6 +72,7 @@ Transform Markdown content into beautiful, navigable documentation sites — wit
|
|
|
71
72
|
- 🔗 **GitHub-Compatible Heading Anchors** — Markdown headings use GitHub-style slugs so standard README Table of Contents links work inside Docsector
|
|
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
|
|
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
|
|
74
76
|
- 🧭 **Quick Links Custom Element** — Use `<d-quick-links>` and `<d-quick-link>` in Markdown to render rich home navigation cards
|
|
75
77
|
- 🗂️ **API Catalog Well-Known** — Auto-generates `/.well-known/api-catalog` as Linkset JSON for machine-readable API discovery
|
|
76
78
|
- 🗃️ **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
|
|
@@ -617,7 +619,7 @@ Docsector Reader works as a **rendering engine**: it provides the layout, compon
|
|
|
617
619
|
│ ├── quasar.config.js ← thin wrapper │
|
|
618
620
|
│ ├── src/pages/ ← Markdown + route defs │
|
|
619
621
|
│ ├── src/i18n/ ← language files + tags │
|
|
620
|
-
│ └── public/ ← logo, images, icons
|
|
622
|
+
│ └── public/ ← logo, images, icons, files │
|
|
621
623
|
│ │
|
|
622
624
|
│ ┌───────────────────────────────────────────────┐ │
|
|
623
625
|
│ │ @docsector/docsector-reader (engine) │ │
|
|
@@ -875,7 +877,8 @@ my-docs/
|
|
|
875
877
|
└── public/
|
|
876
878
|
├── images/logo.png # Project logo
|
|
877
879
|
├── flags/ # Locale flag images
|
|
878
|
-
|
|
880
|
+
├── icons/ # PWA icons
|
|
881
|
+
└── files/ # Downloadable attachments served as /files/...
|
|
879
882
|
```
|
|
880
883
|
|
|
881
884
|
A common manual pattern is to keep core UI references under `src/pages/manual/basic/` with user-friendly page titles and focused entry pages such as Search, Branding, Version Switcher, Edit on GitHub, Translation Progress, and Previous & Next, end-user content references under `src/pages/manual/content/blocks/`, structural docs under `src/pages/manual/content/structures/`, and legacy/internal engine-specific references under `src/pages/manual/components/`.
|
|
@@ -1018,8 +1021,24 @@ Notes:
|
|
|
1018
1021
|
Supported alert types: `NOTE`, `TIP`, `IMPORTANT`, `WARNING`, `CAUTION`.
|
|
1019
1022
|
Regular blockquotes without `[!TYPE]` continue to work normally.
|
|
1020
1023
|
|
|
1024
|
+
### File Attachment Blocks
|
|
1025
|
+
|
|
1026
|
+
```html
|
|
1027
|
+
<d-file src="/files/manual/release-checklist.txt" title="Release checklist" size="1 KB">
|
|
1028
|
+
Download the example file bundled with the docs.
|
|
1029
|
+
</d-file>
|
|
1030
|
+
```
|
|
1031
|
+
|
|
1032
|
+
Notes:
|
|
1033
|
+
|
|
1034
|
+
- Store small repo-tracked attachments in `public/files/` and link them with absolute paths such as `/files/manual/release-checklist.txt`.
|
|
1035
|
+
- `title` and `size` are optional. If `title` is omitted, the rendered card falls back to the filename from `src`.
|
|
1036
|
+
- The block body is rendered as an inline Markdown caption.
|
|
1037
|
+
- External URLs also work, so the same syntax can later point to R2 or another CDN without changing the page structure.
|
|
1038
|
+
|
|
1021
1039
|
---
|
|
1022
1040
|
|
|
1041
|
+
|
|
1023
1042
|
## 🖥️ CLI Commands
|
|
1024
1043
|
|
|
1025
1044
|
```bash
|
package/bin/docsector.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@docsector/docsector-reader",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.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,7 @@
|
|
|
1
|
+
Docsector Reader Release Checklist
|
|
2
|
+
=================================
|
|
3
|
+
|
|
4
|
+
1. Confirm the target route and page registry entry.
|
|
5
|
+
2. Update the overview and showcase markdown pages.
|
|
6
|
+
3. Run the focused test suite for the touched parser or helper.
|
|
7
|
+
4. Run a production build and inspect the published artifact.
|
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed, ref, watch } from 'vue'
|
|
3
|
+
import { useQuasar } from 'quasar'
|
|
4
|
+
import { useI18n } from 'vue-i18n'
|
|
5
|
+
|
|
6
|
+
import { resolveFileIconUrl } from '../composables/useFileIcon'
|
|
7
|
+
|
|
8
|
+
const BASE_URL = import.meta.env.BASE_URL || '/'
|
|
9
|
+
|
|
10
|
+
defineOptions({
|
|
11
|
+
name: 'DPageFile'
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
const props = defineProps({
|
|
15
|
+
src: {
|
|
16
|
+
type: String,
|
|
17
|
+
default: ''
|
|
18
|
+
},
|
|
19
|
+
title: {
|
|
20
|
+
type: String,
|
|
21
|
+
default: ''
|
|
22
|
+
},
|
|
23
|
+
size: {
|
|
24
|
+
type: String,
|
|
25
|
+
default: ''
|
|
26
|
+
},
|
|
27
|
+
caption: {
|
|
28
|
+
type: String,
|
|
29
|
+
default: ''
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const $q = useQuasar()
|
|
34
|
+
const { t } = useI18n()
|
|
35
|
+
const resolvedSize = ref(String(props.size || '').trim())
|
|
36
|
+
|
|
37
|
+
const isExternal = computed(() => /^https?:\/\//i.test(props.src || ''))
|
|
38
|
+
const resolvedHref = computed(() => {
|
|
39
|
+
const raw = String(props.src || '').trim()
|
|
40
|
+
|
|
41
|
+
if (!raw) {
|
|
42
|
+
return ''
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (/^(?:[a-z]+:)?\/\//i.test(raw) || /^(?:mailto:|tel:|data:)/i.test(raw)) {
|
|
46
|
+
return raw
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const trimmedBase = String(BASE_URL).replace(/\/$/, '')
|
|
50
|
+
|
|
51
|
+
if (raw.startsWith('/')) {
|
|
52
|
+
return `${trimmedBase}${raw}` || raw
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const normalized = raw.replace(/^\.\//, '')
|
|
56
|
+
return `${trimmedBase}/${normalized}`.replace(/\/+/g, '/')
|
|
57
|
+
})
|
|
58
|
+
const absoluteHref = computed(() => {
|
|
59
|
+
const href = resolvedHref.value
|
|
60
|
+
|
|
61
|
+
if (!href || /^(?:[a-z]+:)?\/\//i.test(href)) {
|
|
62
|
+
return href
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (typeof window === 'undefined') {
|
|
66
|
+
return href
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return new URL(href, window.location.origin).toString()
|
|
70
|
+
})
|
|
71
|
+
const fileName = computed(() => {
|
|
72
|
+
const normalized = String(props.src || '')
|
|
73
|
+
.split('#')[0]
|
|
74
|
+
.split('?')[0]
|
|
75
|
+
const rawSegment = normalized
|
|
76
|
+
.split('/')
|
|
77
|
+
.filter(Boolean)
|
|
78
|
+
.pop() || ''
|
|
79
|
+
|
|
80
|
+
if (!rawSegment) {
|
|
81
|
+
return ''
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
return decodeURIComponent(rawSegment)
|
|
86
|
+
} catch {
|
|
87
|
+
return rawSegment
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
const displayTitle = computed(() => {
|
|
91
|
+
return props.title || fileName.value || t('page.file.defaultTitle')
|
|
92
|
+
})
|
|
93
|
+
const displayHeading = computed(() => {
|
|
94
|
+
if (props.title && fileName.value && fileName.value !== props.title) {
|
|
95
|
+
return `${props.title} (${fileName.value})`
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return displayTitle.value
|
|
99
|
+
})
|
|
100
|
+
const iconUrl = computed(() => {
|
|
101
|
+
return resolveFileIconUrl(fileName.value || displayTitle.value || props.src, {
|
|
102
|
+
preferLight: !$q.dark.isActive
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
const formatFileSize = (bytes) => {
|
|
107
|
+
const value = Number(bytes)
|
|
108
|
+
|
|
109
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
110
|
+
return ''
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const units = ['B', 'KB', 'MB', 'GB', 'TB']
|
|
114
|
+
let size = value
|
|
115
|
+
let unitIndex = 0
|
|
116
|
+
|
|
117
|
+
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
118
|
+
size /= 1024
|
|
119
|
+
unitIndex++
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const maximumFractionDigits = size >= 100 || unitIndex === 0 ? 0 : (size >= 10 ? 1 : 2)
|
|
123
|
+
return `${new Intl.NumberFormat(undefined, {
|
|
124
|
+
maximumFractionDigits,
|
|
125
|
+
minimumFractionDigits: 0
|
|
126
|
+
}).format(size)} ${units[unitIndex]}`
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const readContentLength = async (href, options = {}) => {
|
|
130
|
+
const response = await fetch(href, options)
|
|
131
|
+
|
|
132
|
+
if (!response.ok) {
|
|
133
|
+
throw new Error(`HTTP ${response.status}`)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const contentLength = response.headers.get('content-length') || response.headers.get('Content-Length')
|
|
137
|
+
return formatFileSize(contentLength)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const hydrateSize = async () => {
|
|
141
|
+
const manualSize = String(props.size || '').trim()
|
|
142
|
+
resolvedSize.value = manualSize
|
|
143
|
+
|
|
144
|
+
if (manualSize || !absoluteHref.value || typeof window === 'undefined') {
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
const automaticSize = await readContentLength(absoluteHref.value, {
|
|
150
|
+
method: 'HEAD'
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
if (automaticSize) {
|
|
154
|
+
resolvedSize.value = automaticSize
|
|
155
|
+
return
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (!isExternal.value) {
|
|
159
|
+
const fallbackSize = await readContentLength(absoluteHref.value)
|
|
160
|
+
if (fallbackSize) {
|
|
161
|
+
resolvedSize.value = fallbackSize
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
} catch {
|
|
165
|
+
// External URLs may block access to Content-Length via CORS.
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
watch([
|
|
170
|
+
() => props.size,
|
|
171
|
+
absoluteHref
|
|
172
|
+
], () => {
|
|
173
|
+
hydrateSize()
|
|
174
|
+
}, {
|
|
175
|
+
immediate: true
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
const openFile = () => {
|
|
179
|
+
const href = absoluteHref.value
|
|
180
|
+
|
|
181
|
+
if (!href || typeof window === 'undefined') {
|
|
182
|
+
return
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const link = document.createElement('a')
|
|
186
|
+
|
|
187
|
+
link.href = href
|
|
188
|
+
link.target = '_blank'
|
|
189
|
+
link.rel = 'noopener noreferrer'
|
|
190
|
+
document.body.appendChild(link)
|
|
191
|
+
link.click()
|
|
192
|
+
link.remove()
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const downloadFile = async () => {
|
|
196
|
+
const href = absoluteHref.value
|
|
197
|
+
|
|
198
|
+
if (!href || typeof window === 'undefined') {
|
|
199
|
+
return
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (isExternal.value) {
|
|
203
|
+
openFile()
|
|
204
|
+
return
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
const response = await fetch(href)
|
|
209
|
+
|
|
210
|
+
if (!response.ok) {
|
|
211
|
+
throw new Error(`HTTP ${response.status}`)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const blob = await response.blob()
|
|
215
|
+
const objectUrl = window.URL.createObjectURL(blob)
|
|
216
|
+
const link = document.createElement('a')
|
|
217
|
+
|
|
218
|
+
link.href = objectUrl
|
|
219
|
+
link.download = fileName.value || displayTitle.value || 'download'
|
|
220
|
+
link.rel = 'noopener noreferrer'
|
|
221
|
+
document.body.appendChild(link)
|
|
222
|
+
link.click()
|
|
223
|
+
link.remove()
|
|
224
|
+
|
|
225
|
+
window.setTimeout(() => {
|
|
226
|
+
window.URL.revokeObjectURL(objectUrl)
|
|
227
|
+
}, 1000)
|
|
228
|
+
} catch {
|
|
229
|
+
window.location.assign(href)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
</script>
|
|
233
|
+
|
|
234
|
+
<template>
|
|
235
|
+
<div class="d-page-file">
|
|
236
|
+
<div class="d-page-file__body">
|
|
237
|
+
<div class="d-page-file__media-column">
|
|
238
|
+
<div class="d-page-file__media" aria-hidden="true">
|
|
239
|
+
<img
|
|
240
|
+
v-if="iconUrl"
|
|
241
|
+
class="d-page-file__icon"
|
|
242
|
+
:src="iconUrl"
|
|
243
|
+
alt=""
|
|
244
|
+
loading="lazy"
|
|
245
|
+
/>
|
|
246
|
+
<q-icon
|
|
247
|
+
v-else
|
|
248
|
+
name="attach_file"
|
|
249
|
+
size="30px"
|
|
250
|
+
/>
|
|
251
|
+
</div>
|
|
252
|
+
|
|
253
|
+
<div v-if="resolvedSize" class="d-page-file__size">{{ resolvedSize }}</div>
|
|
254
|
+
</div>
|
|
255
|
+
|
|
256
|
+
<div class="d-page-file__content">
|
|
257
|
+
<div class="d-page-file__title">{{ displayHeading }}</div>
|
|
258
|
+
|
|
259
|
+
<div
|
|
260
|
+
v-if="caption"
|
|
261
|
+
class="d-page-file__caption"
|
|
262
|
+
v-html="caption"
|
|
263
|
+
></div>
|
|
264
|
+
</div>
|
|
265
|
+
|
|
266
|
+
<div class="d-page-file__actions">
|
|
267
|
+
<q-btn
|
|
268
|
+
no-caps
|
|
269
|
+
unelevated
|
|
270
|
+
padding="8px 12px"
|
|
271
|
+
class="d-page-file__action-button d-page-file__action-button--primary"
|
|
272
|
+
icon="download"
|
|
273
|
+
:label="t('page.file.download')"
|
|
274
|
+
@click="downloadFile"
|
|
275
|
+
/>
|
|
276
|
+
|
|
277
|
+
<q-btn
|
|
278
|
+
no-caps
|
|
279
|
+
unelevated
|
|
280
|
+
padding="8px 12px"
|
|
281
|
+
class="d-page-file__action-button d-page-file__action-button--secondary"
|
|
282
|
+
icon="open_in_new"
|
|
283
|
+
:label="t('page.file.open')"
|
|
284
|
+
@click="openFile"
|
|
285
|
+
/>
|
|
286
|
+
</div>
|
|
287
|
+
</div>
|
|
288
|
+
</div>
|
|
289
|
+
</template>
|
|
290
|
+
|
|
291
|
+
<style lang="sass">
|
|
292
|
+
body.body--light
|
|
293
|
+
--d-page-file-bg: linear-gradient(180deg, #faf8f2 0%, #ffffff 100%)
|
|
294
|
+
--d-page-file-border: rgba(101, 85, 41, 0.18)
|
|
295
|
+
--d-page-file-shadow: rgba(101, 85, 41, 0.08)
|
|
296
|
+
--d-page-file-media-bg: rgba(255, 255, 255, 0.82)
|
|
297
|
+
--d-page-file-media-border: rgba(101, 85, 41, 0.1)
|
|
298
|
+
--d-page-file-meta: #665f4f
|
|
299
|
+
--d-page-file-caption: #4d5563
|
|
300
|
+
--d-page-file-accent: #655529
|
|
301
|
+
--d-page-file-action-text: #4d4020
|
|
302
|
+
--d-page-file-action-border: rgba(101, 85, 41, 0.22)
|
|
303
|
+
--d-page-file-action-bg-hover: rgba(101, 85, 41, 0.06)
|
|
304
|
+
|
|
305
|
+
body.body--dark
|
|
306
|
+
--d-page-file-bg: linear-gradient(180deg, rgba(255, 248, 235, 0.05) 0%, rgba(255, 255, 255, 0.02) 100%)
|
|
307
|
+
--d-page-file-border: rgba(255, 235, 194, 0.14)
|
|
308
|
+
--d-page-file-shadow: rgba(0, 0, 0, 0.28)
|
|
309
|
+
--d-page-file-media-bg: rgba(255, 255, 255, 0.94)
|
|
310
|
+
--d-page-file-media-border: rgba(255, 255, 255, 0.18)
|
|
311
|
+
--d-page-file-meta: rgba(255, 255, 255, 0.7)
|
|
312
|
+
--d-page-file-caption: rgba(255, 255, 255, 0.9)
|
|
313
|
+
--d-page-file-accent: #d7bc7e
|
|
314
|
+
--d-page-file-action-text: rgba(255, 255, 255, 0.92)
|
|
315
|
+
--d-page-file-action-border: rgba(255, 235, 194, 0.18)
|
|
316
|
+
--d-page-file-action-bg-hover: rgba(255, 235, 194, 0.08)
|
|
317
|
+
|
|
318
|
+
.d-page-file
|
|
319
|
+
margin: 1.5rem 0
|
|
320
|
+
border: 1px solid var(--d-page-file-border)
|
|
321
|
+
border-radius: 18px
|
|
322
|
+
background: var(--d-page-file-bg)
|
|
323
|
+
box-shadow: 0 16px 36px var(--d-page-file-shadow)
|
|
324
|
+
overflow: hidden
|
|
325
|
+
|
|
326
|
+
.d-page-file__body
|
|
327
|
+
display: flex
|
|
328
|
+
align-items: center
|
|
329
|
+
gap: 1rem
|
|
330
|
+
padding: 0.82rem 0.92rem
|
|
331
|
+
|
|
332
|
+
.d-page-file__media-column
|
|
333
|
+
display: flex
|
|
334
|
+
flex: 0 0 68px
|
|
335
|
+
flex-direction: column
|
|
336
|
+
align-items: center
|
|
337
|
+
gap: 0.16rem
|
|
338
|
+
|
|
339
|
+
.d-page-file__media
|
|
340
|
+
width: 56px
|
|
341
|
+
height: 56px
|
|
342
|
+
display: flex
|
|
343
|
+
align-items: center
|
|
344
|
+
justify-content: center
|
|
345
|
+
border-radius: 16px
|
|
346
|
+
background: var(--d-page-file-media-bg)
|
|
347
|
+
box-shadow: inset 0 0 0 1px var(--d-page-file-media-border)
|
|
348
|
+
color: var(--d-page-file-accent)
|
|
349
|
+
|
|
350
|
+
.d-page-file__size
|
|
351
|
+
width: 100%
|
|
352
|
+
text-align: center
|
|
353
|
+
color: var(--d-page-file-meta)
|
|
354
|
+
font-size: 0.8rem
|
|
355
|
+
font-weight: 700
|
|
356
|
+
line-height: 1.05
|
|
357
|
+
|
|
358
|
+
.d-page-file__icon
|
|
359
|
+
display: block
|
|
360
|
+
width: 32px
|
|
361
|
+
height: 32px
|
|
362
|
+
|
|
363
|
+
.d-page-file__content
|
|
364
|
+
min-width: 0
|
|
365
|
+
flex: 1 1 auto
|
|
366
|
+
|
|
367
|
+
.d-page-file__title
|
|
368
|
+
font-size: 1rem
|
|
369
|
+
font-weight: 700
|
|
370
|
+
line-height: 1.4
|
|
371
|
+
word-break: break-word
|
|
372
|
+
|
|
373
|
+
.d-page-file__caption
|
|
374
|
+
margin-top: 0.35rem
|
|
375
|
+
color: var(--d-page-file-caption)
|
|
376
|
+
|
|
377
|
+
> :first-child
|
|
378
|
+
margin-top: 0
|
|
379
|
+
|
|
380
|
+
> :last-child
|
|
381
|
+
margin-bottom: 0
|
|
382
|
+
|
|
383
|
+
.d-page-file__actions
|
|
384
|
+
display: flex
|
|
385
|
+
flex: 0 0 auto
|
|
386
|
+
align-items: center
|
|
387
|
+
gap: 0.6rem
|
|
388
|
+
align-self: center
|
|
389
|
+
|
|
390
|
+
.d-page-file__action-button
|
|
391
|
+
min-height: 40px
|
|
392
|
+
border-radius: 10px
|
|
393
|
+
border: 1px solid var(--d-page-file-action-border)
|
|
394
|
+
background: transparent !important
|
|
395
|
+
color: var(--d-page-file-action-text) !important
|
|
396
|
+
transition: transform 0.18s ease, background-color 0.18s ease, border-color 0.18s ease
|
|
397
|
+
|
|
398
|
+
.q-btn__content
|
|
399
|
+
gap: 0.45rem
|
|
400
|
+
font-size: 0.9rem
|
|
401
|
+
font-weight: 600
|
|
402
|
+
line-height: 1
|
|
403
|
+
|
|
404
|
+
.q-focus-helper,
|
|
405
|
+
&:before,
|
|
406
|
+
&:after
|
|
407
|
+
display: none
|
|
408
|
+
|
|
409
|
+
&:hover
|
|
410
|
+
transform: translateY(-1px)
|
|
411
|
+
background: var(--d-page-file-action-bg-hover) !important
|
|
412
|
+
|
|
413
|
+
&:focus-visible
|
|
414
|
+
outline: 2px solid var(--d-page-file-accent)
|
|
415
|
+
outline-offset: 2px
|
|
416
|
+
|
|
417
|
+
.d-page-file__action-button--primary
|
|
418
|
+
border-color: var(--d-page-file-action-border)
|
|
419
|
+
|
|
420
|
+
@media (max-width: 720px)
|
|
421
|
+
.d-page-file__body
|
|
422
|
+
flex-wrap: wrap
|
|
423
|
+
align-items: flex-start
|
|
424
|
+
|
|
425
|
+
.d-page-file__actions
|
|
426
|
+
width: 100%
|
|
427
|
+
|
|
428
|
+
.d-page-file__action-button
|
|
429
|
+
flex: 1 1 0
|
|
430
|
+
</style>
|
|
@@ -23,6 +23,7 @@ import DPageSourceCode from './DPageSourceCode.vue'
|
|
|
23
23
|
import DMermaidDiagram from './DMermaidDiagram.vue'
|
|
24
24
|
import DPageBlockquote from './DPageBlockquote.vue'
|
|
25
25
|
import DPageImage from './DPageImage.vue'
|
|
26
|
+
import DPageFile from './DPageFile.vue'
|
|
26
27
|
import DQuickLinks from './DQuickLinks.vue'
|
|
27
28
|
import DPageExpandable from './DPageExpandable.vue'
|
|
28
29
|
</script>
|
|
@@ -57,10 +58,12 @@ import DPageExpandable from './DPageExpandable.vue'
|
|
|
57
58
|
|
|
58
59
|
<ul
|
|
59
60
|
v-else-if="token.tag === 'ul'"
|
|
61
|
+
v-bind="token.attrs || {}"
|
|
60
62
|
v-html="token.content"
|
|
61
63
|
></ul>
|
|
62
64
|
<ol
|
|
63
65
|
v-else-if="token.tag === 'ol'"
|
|
66
|
+
v-bind="token.attrs || {}"
|
|
64
67
|
v-html="token.content"
|
|
65
68
|
></ol>
|
|
66
69
|
|
|
@@ -94,6 +97,14 @@ import DPageExpandable from './DPageExpandable.vue'
|
|
|
94
97
|
<div v-html="token.content"></div>
|
|
95
98
|
</d-page-blockquote>
|
|
96
99
|
|
|
100
|
+
<d-page-file
|
|
101
|
+
v-else-if="token.tag === 'file'"
|
|
102
|
+
:src="token.src"
|
|
103
|
+
:title="token.title"
|
|
104
|
+
:size="token.size"
|
|
105
|
+
:caption="token.caption"
|
|
106
|
+
/>
|
|
107
|
+
|
|
97
108
|
<d-page-source-code
|
|
98
109
|
v-else-if="token.tag === 'code'"
|
|
99
110
|
: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([
|
|
@@ -14,6 +15,7 @@ const ALERT_MESSAGE_TYPES = new Set([
|
|
|
14
15
|
|
|
15
16
|
const QUICK_LINKS_MARKER_PREFIX = '@@DOCSECTOR_QUICK_LINKS_'
|
|
16
17
|
const EXPANDABLE_MARKER_PREFIX = '@@DOCSECTOR_EXPANDABLE_'
|
|
18
|
+
const FILE_MARKER_PREFIX = '@@DOCSECTOR_FILE_'
|
|
17
19
|
const CODE_SEGMENT_MARKER_PREFIX = '@@DOCSECTOR_CODE_SEGMENT_'
|
|
18
20
|
const MATH_KATEX_OPTIONS = {
|
|
19
21
|
throwOnError: false,
|
|
@@ -113,6 +115,32 @@ const restoreShieldedCodeSegments = (source = '', codeSegmentsMap = new Map()) =
|
|
|
113
115
|
return restored
|
|
114
116
|
}
|
|
115
117
|
|
|
118
|
+
const escapeHtmlAttributeValue = (value = '') => {
|
|
119
|
+
return String(value)
|
|
120
|
+
.replace(/&/g, '&')
|
|
121
|
+
.replace(/"/g, '"')
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const getTokenAttributes = (element) => {
|
|
125
|
+
if (!Array.isArray(element?.attrs) || element.attrs.length === 0) {
|
|
126
|
+
return null
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return Object.fromEntries(element.attrs.map(([name, value]) => [name, value ?? '']))
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const renderTokenAttributes = (element) => {
|
|
133
|
+
const attributes = getTokenAttributes(element)
|
|
134
|
+
|
|
135
|
+
if (attributes === null) {
|
|
136
|
+
return ''
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return Object.entries(attributes)
|
|
140
|
+
.map(([name, value]) => ` ${name}="${escapeHtmlAttributeValue(value)}"`)
|
|
141
|
+
.join('')
|
|
142
|
+
}
|
|
143
|
+
|
|
116
144
|
const extractQuickLinksBlocks = (source = '') => {
|
|
117
145
|
const map = new Map()
|
|
118
146
|
let index = 0
|
|
@@ -190,6 +218,62 @@ const extractExpandableBlocks = (source = '') => {
|
|
|
190
218
|
}
|
|
191
219
|
}
|
|
192
220
|
|
|
221
|
+
const getFileTitleFromSrc = (src = '') => {
|
|
222
|
+
const normalized = String(src)
|
|
223
|
+
.split('#')[0]
|
|
224
|
+
.split('?')[0]
|
|
225
|
+
const rawSegment = normalized
|
|
226
|
+
.split('/')
|
|
227
|
+
.filter(Boolean)
|
|
228
|
+
.pop() || ''
|
|
229
|
+
|
|
230
|
+
if (!rawSegment) {
|
|
231
|
+
return 'Download file'
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
try {
|
|
235
|
+
return decodeURIComponent(rawSegment)
|
|
236
|
+
} catch {
|
|
237
|
+
return rawSegment
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const extractFileBlocks = (source = '') => {
|
|
242
|
+
const map = new Map()
|
|
243
|
+
let index = 0
|
|
244
|
+
|
|
245
|
+
const replaceBlock = (match, rawAttrs, rawCaption = '') => {
|
|
246
|
+
const attrs = parseCustomTagAttributes(rawAttrs)
|
|
247
|
+
const src = decodeHtmlEntities(attrs.src || attrs.href || '').trim()
|
|
248
|
+
|
|
249
|
+
if (!src) {
|
|
250
|
+
return match
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const marker = `${FILE_MARKER_PREFIX}${index}@@`
|
|
254
|
+
index++
|
|
255
|
+
|
|
256
|
+
map.set(marker, {
|
|
257
|
+
src,
|
|
258
|
+
title: decodeHtmlEntities(attrs.title || attrs.name || '').trim(),
|
|
259
|
+
size: decodeHtmlEntities(attrs.size || '').trim(),
|
|
260
|
+
caption: String(rawCaption).trim()
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
return `\n${marker}\n`
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const replacedSelfClosing = String(source).replace(/<d-file\b([^>]*)\/\s*>/gi, (match, rawAttrs) => {
|
|
267
|
+
return replaceBlock(match, rawAttrs)
|
|
268
|
+
})
|
|
269
|
+
const replaced = replacedSelfClosing.replace(/<d-file\b([^>]*)>([\s\S]*?)<\/d-file>/gi, replaceBlock)
|
|
270
|
+
|
|
271
|
+
return {
|
|
272
|
+
source: replaced,
|
|
273
|
+
fileMap: map
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
193
277
|
const parseFenceAttributes = (raw = '') => {
|
|
194
278
|
const parsed = {}
|
|
195
279
|
const pattern = /([\w-]+)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s;]+))/g
|
|
@@ -411,6 +495,14 @@ const renderBlockToken = (markdown, element, env) => {
|
|
|
411
495
|
return markdown.renderer.render([element], markdown.options, env).trim()
|
|
412
496
|
}
|
|
413
497
|
|
|
498
|
+
const renderInlineToken = (markdown, markdownInline, element, env) => {
|
|
499
|
+
if (Array.isArray(element.children) && element.children.length > 0) {
|
|
500
|
+
return markdown.renderer.renderInline(element.children, markdown.options, env)
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
return markdownInline.renderInline(element.content, env)
|
|
504
|
+
}
|
|
505
|
+
|
|
414
506
|
const createMarkdownBlockParser = () => {
|
|
415
507
|
const markdown = installMathSupport(new MarkdownIt({
|
|
416
508
|
html: true
|
|
@@ -421,6 +513,11 @@ const createMarkdownBlockParser = () => {
|
|
|
421
513
|
rightDelimiter: ';',
|
|
422
514
|
allowedAttributes: ['filename', 'group', 'tab', 'breadcrumb']
|
|
423
515
|
})
|
|
516
|
+
markdown.use(taskLists, {
|
|
517
|
+
enabled: false,
|
|
518
|
+
label: false,
|
|
519
|
+
labelAfter: false
|
|
520
|
+
})
|
|
424
521
|
|
|
425
522
|
return markdown
|
|
426
523
|
}
|
|
@@ -469,10 +566,19 @@ export const tokenizePageSectionSource = (source = '', options = {}) => {
|
|
|
469
566
|
})
|
|
470
567
|
|
|
471
568
|
const { source: sourceWithQuickLinks, quickLinksMap } = extractQuickLinksBlocks(sourceWithExpandables)
|
|
569
|
+
const { source: sourceWithFiles, fileMap } = extractFileBlocks(sourceWithQuickLinks)
|
|
570
|
+
|
|
571
|
+
fileMap.forEach((data, marker) => {
|
|
572
|
+
fileMap.set(marker, {
|
|
573
|
+
...data,
|
|
574
|
+
caption: restoreShieldedCodeSegments(data.caption, codeSegmentsMap)
|
|
575
|
+
})
|
|
576
|
+
})
|
|
577
|
+
|
|
472
578
|
const markdown = createMarkdownBlockParser()
|
|
473
579
|
const markdownInline = createMarkdownInlineParser()
|
|
474
580
|
const markdownEnv = {}
|
|
475
|
-
const parsed = markdown.parse(restoreShieldedCodeSegments(
|
|
581
|
+
const parsed = markdown.parse(restoreShieldedCodeSegments(sourceWithFiles, codeSegmentsMap), markdownEnv)
|
|
476
582
|
const tokens = []
|
|
477
583
|
|
|
478
584
|
let level = 0
|
|
@@ -596,7 +702,7 @@ export const tokenizePageSectionSource = (source = '', options = {}) => {
|
|
|
596
702
|
}
|
|
597
703
|
|
|
598
704
|
if (element.type === 'inline') {
|
|
599
|
-
element.content = markdownInline
|
|
705
|
+
element.content = renderInlineToken(markdown, markdownInline, element, markdownEnv)
|
|
600
706
|
}
|
|
601
707
|
|
|
602
708
|
if (level === 0) {
|
|
@@ -640,6 +746,22 @@ export const tokenizePageSectionSource = (source = '', options = {}) => {
|
|
|
640
746
|
break
|
|
641
747
|
}
|
|
642
748
|
|
|
749
|
+
if (fileMap.has(element.content.trim())) {
|
|
750
|
+
const data = fileMap.get(element.content.trim())
|
|
751
|
+
|
|
752
|
+
tokens.push({
|
|
753
|
+
tag: 'file',
|
|
754
|
+
map: element.map,
|
|
755
|
+
src: data.src,
|
|
756
|
+
title: data.title || getFileTitleFromSrc(data.src),
|
|
757
|
+
size: data.size,
|
|
758
|
+
caption: data.caption !== ''
|
|
759
|
+
? markdownInline.renderInline(data.caption, markdownEnv)
|
|
760
|
+
: ''
|
|
761
|
+
})
|
|
762
|
+
break
|
|
763
|
+
}
|
|
764
|
+
|
|
643
765
|
if (tag === 'p') {
|
|
644
766
|
const imageToken = parseStandaloneImageToken(element.content)
|
|
645
767
|
|
|
@@ -697,23 +819,29 @@ export const tokenizePageSectionSource = (source = '', options = {}) => {
|
|
|
697
819
|
switch (element.type) {
|
|
698
820
|
case 'bullet_list_open':
|
|
699
821
|
if (level === 1) {
|
|
822
|
+
const attributes = getTokenAttributes(element)
|
|
823
|
+
|
|
700
824
|
tokens.push({
|
|
701
825
|
tag: 'ul',
|
|
702
|
-
content: ''
|
|
826
|
+
content: '',
|
|
827
|
+
...(attributes !== null ? { attrs: attributes } : {})
|
|
703
828
|
})
|
|
704
829
|
} else {
|
|
705
|
-
parent.content +=
|
|
830
|
+
parent.content += `<ul${renderTokenAttributes(element)}>`
|
|
706
831
|
}
|
|
707
832
|
break
|
|
708
833
|
|
|
709
834
|
case 'ordered_list_open':
|
|
710
835
|
if (level === 1) {
|
|
836
|
+
const attributes = getTokenAttributes(element)
|
|
837
|
+
|
|
711
838
|
tokens.push({
|
|
712
839
|
tag: 'ol',
|
|
713
|
-
content: ''
|
|
840
|
+
content: '',
|
|
841
|
+
...(attributes !== null ? { attrs: attributes } : {})
|
|
714
842
|
})
|
|
715
843
|
} else {
|
|
716
|
-
parent.content +=
|
|
844
|
+
parent.content += `<ol${renderTokenAttributes(element)}>`
|
|
717
845
|
}
|
|
718
846
|
break
|
|
719
847
|
|
|
@@ -729,7 +857,7 @@ export const tokenizePageSectionSource = (source = '', options = {}) => {
|
|
|
729
857
|
break
|
|
730
858
|
|
|
731
859
|
case 'list_item_open':
|
|
732
|
-
parent.content +=
|
|
860
|
+
parent.content += `<li${renderTokenAttributes(element)}>`
|
|
733
861
|
break
|
|
734
862
|
|
|
735
863
|
case 'thead_open':
|
package/src/css/app.sass
CHANGED
|
@@ -15,6 +15,11 @@ body
|
|
|
15
15
|
body.body--light
|
|
16
16
|
--q-primary: #655529 !important
|
|
17
17
|
--q-secondary: #000080 !important
|
|
18
|
+
--task-list-checkbox-bg: #ffffff
|
|
19
|
+
--task-list-checkbox-border: #7c6a3a
|
|
20
|
+
--task-list-checkbox-checked-bg: #5e4c1f
|
|
21
|
+
--task-list-checkbox-checked-border: #4f3f18
|
|
22
|
+
--task-list-checkbox-check: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='none' stroke='%23ffffff' stroke-linecap='round' stroke-linejoin='round' stroke-width='2.3' d='M3.25 8.5 6.4 11.4 12.75 4.9'/%3E%3C/svg%3E")
|
|
18
23
|
|
|
19
24
|
--q-primary-background: #FFF !important
|
|
20
25
|
|
|
@@ -48,6 +53,11 @@ body.body--dark
|
|
|
48
53
|
--q-primary-in-dark-bg: #C1A667 !important
|
|
49
54
|
--q-secondary: #FFA07A !important
|
|
50
55
|
--q-negative: #fc001b !important
|
|
56
|
+
--task-list-checkbox-bg: #181818
|
|
57
|
+
--task-list-checkbox-border: #8f8a78
|
|
58
|
+
--task-list-checkbox-checked-bg: #d3b874
|
|
59
|
+
--task-list-checkbox-checked-border: #ebd08a
|
|
60
|
+
--task-list-checkbox-check: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='none' stroke='%23151515' stroke-linecap='round' stroke-linejoin='round' stroke-width='2.3' d='M3.25 8.5 6.4 11.4 12.75 4.9'/%3E%3C/svg%3E")
|
|
51
61
|
|
|
52
62
|
--q-light-in-dark-1: #ababab
|
|
53
63
|
--q-light-in-dark-2: #c9c9c9
|
|
@@ -199,6 +209,39 @@ body.body--dark
|
|
|
199
209
|
overflow-y: hidden
|
|
200
210
|
padding: 0.35rem 0.1rem
|
|
201
211
|
|
|
212
|
+
ul.contains-task-list,
|
|
213
|
+
ol.contains-task-list
|
|
214
|
+
list-style: none
|
|
215
|
+
padding-left: 1.75rem
|
|
216
|
+
|
|
217
|
+
li.task-list-item
|
|
218
|
+
list-style: none
|
|
219
|
+
|
|
220
|
+
input.task-list-item-checkbox
|
|
221
|
+
appearance: none
|
|
222
|
+
-webkit-appearance: none
|
|
223
|
+
width: 1rem
|
|
224
|
+
height: 1rem
|
|
225
|
+
margin: 0 0.55rem 0 -1.45rem
|
|
226
|
+
vertical-align: middle
|
|
227
|
+
pointer-events: none
|
|
228
|
+
opacity: 1
|
|
229
|
+
border: 2px solid var(--task-list-checkbox-border)
|
|
230
|
+
border-radius: 0.2rem
|
|
231
|
+
background-color: var(--task-list-checkbox-bg)
|
|
232
|
+
background-position: center
|
|
233
|
+
background-repeat: no-repeat
|
|
234
|
+
background-size: 0.72rem 0.72rem
|
|
235
|
+
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.08)
|
|
236
|
+
|
|
237
|
+
&:checked
|
|
238
|
+
background-color: var(--task-list-checkbox-checked-bg)
|
|
239
|
+
border-color: var(--task-list-checkbox-checked-border)
|
|
240
|
+
background-image: var(--task-list-checkbox-check)
|
|
241
|
+
|
|
242
|
+
&:disabled
|
|
243
|
+
opacity: 1
|
|
244
|
+
|
|
202
245
|
a
|
|
203
246
|
text-decoration: none
|
|
204
247
|
outline: 0
|
|
@@ -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.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
## Showcase
|
|
2
|
+
|
|
3
|
+
### Local File with Caption
|
|
4
|
+
|
|
5
|
+
<d-file src="/files/manual/release-checklist.txt" title="Release checklist" size="1 KB">
|
|
6
|
+
Download the example checklist published from `public/files/manual`.
|
|
7
|
+
</d-file>
|
|
8
|
+
|
|
9
|
+
### Local File with Automatic Size
|
|
10
|
+
|
|
11
|
+
<d-file src="/files/manual/release-checklist.txt" />
|
|
12
|
+
|
|
13
|
+
### External File URL
|
|
14
|
+
|
|
15
|
+
<d-file src="https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf" title="Reference PDF" size="13 KB">
|
|
16
|
+
The same block can point to a CDN or object storage URL without changing the page layout.
|
|
17
|
+
</d-file>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
## Demonstração
|
|
2
|
+
|
|
3
|
+
### Arquivo Local com Legenda
|
|
4
|
+
|
|
5
|
+
<d-file src="/files/manual/release-checklist.txt" title="Checklist de release" size="1 KB">
|
|
6
|
+
Baixe o checklist de exemplo publicado a partir de `public/files/manual`.
|
|
7
|
+
</d-file>
|
|
8
|
+
|
|
9
|
+
### Arquivo Local com Tamanho Automático
|
|
10
|
+
|
|
11
|
+
<d-file src="/files/manual/release-checklist.txt" />
|
|
12
|
+
|
|
13
|
+
### URL Externa de Arquivo
|
|
14
|
+
|
|
15
|
+
<d-file src="https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf" title="PDF de referência" size="13 KB">
|
|
16
|
+
O mesmo bloco pode apontar para um CDN ou URL de object storage sem mudar o layout da página.
|
|
17
|
+
</d-file>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
## Overview
|
|
2
|
+
|
|
3
|
+
Use task lists when each item should carry an explicit done or not-done state.
|
|
4
|
+
|
|
5
|
+
They work well for release preparation, editorial queues, migration follow-ups, and any checklist where progress matters as much as the content itself.
|
|
6
|
+
|
|
7
|
+
## Markdown Syntax
|
|
8
|
+
|
|
9
|
+
```markdown
|
|
10
|
+
- [ ] Write the overview page
|
|
11
|
+
- [x] Add localized examples
|
|
12
|
+
- [ ] Review screenshots
|
|
13
|
+
- [x] Update internal links
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Notes
|
|
17
|
+
|
|
18
|
+
- Task lists follow the same indentation rules as regular Markdown lists.
|
|
19
|
+
- Use `[ ]` for pending items and `[x]` for completed items.
|
|
20
|
+
- Published readers see static checkboxes and cannot toggle them from the page.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
## Visão Geral
|
|
2
|
+
|
|
3
|
+
Use listas de tarefas quando cada item precisar deixar explícito se foi concluído ou não.
|
|
4
|
+
|
|
5
|
+
Elas funcionam bem para preparação de releases, filas editoriais, acompanhamentos de migração e qualquer checklist em que o progresso importa tanto quanto o conteúdo.
|
|
6
|
+
|
|
7
|
+
## Sintaxe em Markdown
|
|
8
|
+
|
|
9
|
+
```markdown
|
|
10
|
+
- [ ] Escreva a página de visão geral
|
|
11
|
+
- [x] Adicione exemplos localizados
|
|
12
|
+
- [ ] Revise as capturas de tela
|
|
13
|
+
- [x] Atualize os links internos
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Observações
|
|
17
|
+
|
|
18
|
+
- Listas de tarefas seguem as mesmas regras de indentação das listas Markdown comuns.
|
|
19
|
+
- Use `[ ]` para itens pendentes e `[x]` para itens concluídos.
|
|
20
|
+
- Leitores da documentação publicada veem checkboxes estáticos e não podem alterná-los pela página.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
## Showcase
|
|
2
|
+
|
|
3
|
+
### Simple Tasks
|
|
4
|
+
|
|
5
|
+
- [ ] Write the overview page
|
|
6
|
+
- [x] Add the showcase page
|
|
7
|
+
- [ ] Review internal links
|
|
8
|
+
|
|
9
|
+
### Nested Tasks
|
|
10
|
+
|
|
11
|
+
- [ ] Prepare the release
|
|
12
|
+
- [x] Update the changelog
|
|
13
|
+
- [ ] Run smoke tests
|
|
14
|
+
- [ ] Publish the package
|
|
15
|
+
|
|
16
|
+
### Mixed Guidance
|
|
17
|
+
|
|
18
|
+
> [!NOTE]
|
|
19
|
+
> Readers can see the authored state of each task, but published checkboxes stay read-only.
|
|
20
|
+
|
|
21
|
+
- [x] Keep completed items checked in the source.
|
|
22
|
+
- [ ] Use task lists when the state matters more than the order.
|
|
23
|
+
- Regular bullet lists are still better for collections that do not need a done/not-done signal.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
## Showcase
|
|
2
|
+
|
|
3
|
+
### Tarefas Simples
|
|
4
|
+
|
|
5
|
+
- [ ] Escreva a página de visão geral
|
|
6
|
+
- [x] Adicione a página de showcase
|
|
7
|
+
- [ ] Revise os links internos
|
|
8
|
+
|
|
9
|
+
### Tarefas Aninhadas
|
|
10
|
+
|
|
11
|
+
- [ ] Prepare a release
|
|
12
|
+
- [x] Atualize o changelog
|
|
13
|
+
- [ ] Execute smoke tests
|
|
14
|
+
- [ ] Publique o pacote
|
|
15
|
+
|
|
16
|
+
### Orientação de Uso
|
|
17
|
+
|
|
18
|
+
> [!NOTE]
|
|
19
|
+
> Leitores conseguem ver o estado definido no Markdown, mas os checkboxes publicados continuam somente leitura.
|
|
20
|
+
|
|
21
|
+
- [x] Mantenha marcados os itens concluídos no conteúdo-fonte.
|
|
22
|
+
- [ ] Use listas de tarefas quando o estado importar mais do que a ordem.
|
|
23
|
+
- Listas com marcadores continuam melhores para coleções que não precisam de sinal de concluído ou pendente.
|
|
@@ -402,6 +402,34 @@ export default {
|
|
|
402
402
|
}
|
|
403
403
|
},
|
|
404
404
|
|
|
405
|
+
'/content/blocks/task-lists': {
|
|
406
|
+
config: {
|
|
407
|
+
icon: 'check_box',
|
|
408
|
+
status: 'done',
|
|
409
|
+
meta: {
|
|
410
|
+
description: {
|
|
411
|
+
'en-US': 'Task lists — Documentation of Docsector Reader',
|
|
412
|
+
'pt-BR': 'Listas de tarefas — Documentacao do Docsector Reader'
|
|
413
|
+
}
|
|
414
|
+
},
|
|
415
|
+
book: 'manual',
|
|
416
|
+
menu: {},
|
|
417
|
+
subpages: {
|
|
418
|
+
showcase: true
|
|
419
|
+
}
|
|
420
|
+
},
|
|
421
|
+
data: {
|
|
422
|
+
'en-US': { title: 'Task lists' },
|
|
423
|
+
'pt-BR': { title: 'Listas de tarefas' }
|
|
424
|
+
},
|
|
425
|
+
metadata: {
|
|
426
|
+
tags: {
|
|
427
|
+
'en-US': 'task list checklist checkbox todo done pending nested markdown gitbook',
|
|
428
|
+
'pt-BR': 'lista de tarefas checklist checkbox todo concluído pendente aninhada markdown gitbook'
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
},
|
|
432
|
+
|
|
405
433
|
'/content/blocks/hints': {
|
|
406
434
|
config: {
|
|
407
435
|
icon: 'lightbulb',
|
|
@@ -542,6 +570,35 @@ export default {
|
|
|
542
570
|
}
|
|
543
571
|
},
|
|
544
572
|
|
|
573
|
+
'/content/blocks/files': {
|
|
574
|
+
config: {
|
|
575
|
+
icon: 'attach_file',
|
|
576
|
+
status: 'new',
|
|
577
|
+
version: 'v3.3.0',
|
|
578
|
+
meta: {
|
|
579
|
+
description: {
|
|
580
|
+
'en-US': 'Files — Documentation of Docsector Reader',
|
|
581
|
+
'pt-BR': 'Arquivos — Documentacao do Docsector Reader'
|
|
582
|
+
}
|
|
583
|
+
},
|
|
584
|
+
book: 'manual',
|
|
585
|
+
menu: {},
|
|
586
|
+
subpages: {
|
|
587
|
+
showcase: true
|
|
588
|
+
}
|
|
589
|
+
},
|
|
590
|
+
data: {
|
|
591
|
+
'en-US': { title: 'Files' },
|
|
592
|
+
'pt-BR': { title: 'Arquivos' }
|
|
593
|
+
},
|
|
594
|
+
metadata: {
|
|
595
|
+
tags: {
|
|
596
|
+
'en-US': 'files download attachments markdown assets public r2 cloudflare github',
|
|
597
|
+
'pt-BR': 'arquivos download anexos markdown assets public r2 cloudflare github'
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
},
|
|
601
|
+
|
|
545
602
|
'/content/blocks/math-and-tex': {
|
|
546
603
|
config: {
|
|
547
604
|
icon: 'functions',
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
## Overview
|
|
2
|
-
|
|
3
|
-
`DSubpage` is a **convenience wrapper** that composes `DPage`, `DH1`, and `DPageSection` into a standard documentation page layout. It is the component loaded by the router for every subpage route.
|
|
4
|
-
|
|
5
|
-
## How It Works
|
|
6
|
-
|
|
7
|
-
DSubpage generates a deterministic numeric ID from the current route path using a hash function. This ID is passed to `DPageSection` to keep per-page renderer indexes stable across page navigations.
|
|
8
|
-
|
|
9
|
-
## Template
|
|
10
|
-
|
|
11
|
-
```html
|
|
12
|
-
<d-page show-back-to-top-control>
|
|
13
|
-
<header>
|
|
14
|
-
<d-h1 :id="0" />
|
|
15
|
-
</header>
|
|
16
|
-
<main>
|
|
17
|
-
<d-page-section :id="id" />
|
|
18
|
-
</main>
|
|
19
|
-
</d-page>
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
## ID Generation
|
|
23
|
-
|
|
24
|
-
The `id` computed property creates a consistent hash from the route path:
|
|
25
|
-
|
|
26
|
-
```javascript
|
|
27
|
-
const id = computed(() => {
|
|
28
|
-
const path = route.path
|
|
29
|
-
let hash = 5381
|
|
30
|
-
for (let i = 0; i < path.length; i++) {
|
|
31
|
-
hash = (hash * 33) ^ path.charCodeAt(i)
|
|
32
|
-
}
|
|
33
|
-
return hash >>> 0
|
|
34
|
-
})
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
This keeps per-page renderer state isolated when switching between pages. Markdown section headings themselves use GitHub-compatible slugs derived from the heading text, so README-style Table of Contents links keep working.
|
|
38
|
-
|
|
39
|
-
## When to Use
|
|
40
|
-
|
|
41
|
-
DSubpage is automatically used by the router for all documentation pages. You don't need to use it directly unless creating custom page layouts. For standard documentation, the router handles everything:
|
|
42
|
-
|
|
43
|
-
```javascript
|
|
44
|
-
// In routes.js - this happens automatically
|
|
45
|
-
{
|
|
46
|
-
path: 'overview',
|
|
47
|
-
component: () => import('components/DSubpage.vue'),
|
|
48
|
-
meta: { status: config.status }
|
|
49
|
-
}
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
## Relationship with DPage
|
|
53
|
-
|
|
54
|
-
- `DSubpage` **uses** `DPage` as its container
|
|
55
|
-
- `DPage` handles layout (scroll, toolbar, drawer)
|
|
56
|
-
- `DSubpage` handles content composition (H1 + sections)
|
|
57
|
-
|
|
58
|
-
## Built-in Back to Top Control
|
|
59
|
-
|
|
60
|
-
Routed documentation subpages enable DPage's floating back-to-top control automatically. The control is only shown when the content actually overflows, becomes visible after the reader scrolls a little, and visualizes the current reading progress with a circular indicator.
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
## Visão Geral
|
|
2
|
-
|
|
3
|
-
`DSubpage` é um **wrapper de conveniência** que compõe `DPage`, `DH1` e `DPageSection` em um layout de página de documentação padrão. É o componente carregado pelo roteador para cada rota de sub-página.
|
|
4
|
-
|
|
5
|
-
## Como Funciona
|
|
6
|
-
|
|
7
|
-
DSubpage gera um ID numérico determinístico a partir do caminho da rota atual usando uma função hash. Esse ID é passado ao `DPageSection` para manter estáveis os índices internos do renderer em cada página.
|
|
8
|
-
|
|
9
|
-
## Template
|
|
10
|
-
|
|
11
|
-
```html
|
|
12
|
-
<d-page show-back-to-top-control>
|
|
13
|
-
<header>
|
|
14
|
-
<d-h1 :id="0" />
|
|
15
|
-
</header>
|
|
16
|
-
<main>
|
|
17
|
-
<d-page-section :id="id" />
|
|
18
|
-
</main>
|
|
19
|
-
</d-page>
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
## Geração de ID
|
|
23
|
-
|
|
24
|
-
A propriedade computada `id` cria um hash consistente a partir do caminho da rota:
|
|
25
|
-
|
|
26
|
-
```javascript
|
|
27
|
-
const id = computed(() => {
|
|
28
|
-
const path = route.path
|
|
29
|
-
let hash = 5381
|
|
30
|
-
for (let i = 0; i < path.length; i++) {
|
|
31
|
-
hash = (hash * 33) ^ path.charCodeAt(i)
|
|
32
|
-
}
|
|
33
|
-
return hash >>> 0
|
|
34
|
-
})
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
Isso mantém o estado interno do renderer isolado ao navegar entre páginas. Os headings Markdown em si usam slugs compatíveis com GitHub derivados do texto do título, então links de Table of Contents no estilo README continuam funcionando.
|
|
38
|
-
|
|
39
|
-
## Quando Usar
|
|
40
|
-
|
|
41
|
-
DSubpage é usado automaticamente pelo roteador para todas as páginas de documentação. Você não precisa usá-lo diretamente, a menos que crie layouts de página customizados. Para documentação padrão, o roteador cuida de tudo:
|
|
42
|
-
|
|
43
|
-
```javascript
|
|
44
|
-
// Em routes.js - isso acontece automaticamente
|
|
45
|
-
{
|
|
46
|
-
path: 'overview',
|
|
47
|
-
component: () => import('components/DSubpage.vue'),
|
|
48
|
-
meta: { status: config.status }
|
|
49
|
-
}
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
## Relação com DPage
|
|
53
|
-
|
|
54
|
-
- `DSubpage` **usa** `DPage` como container
|
|
55
|
-
- `DPage` cuida do layout (scroll, toolbar, drawer)
|
|
56
|
-
- `DSubpage` cuida da composição de conteúdo (H1 + seções)
|
|
57
|
-
|
|
58
|
-
## Controle Integrado de Voltar ao Topo
|
|
59
|
-
|
|
60
|
-
As sub-páginas de documentação roteadas habilitam automaticamente o controle flutuante de voltar ao topo do DPage. O controle só é exibido quando o conteúdo realmente tem overflow, fica visível após uma pequena rolagem do leitor e mostra o progresso atual de leitura com um indicador circular.
|