@docsector/docsector-reader 3.3.1 → 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 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
package/bin/docsector.js CHANGED
@@ -23,7 +23,7 @@ const packageRoot = resolve(__dirname, '..')
23
23
  const args = process.argv.slice(2)
24
24
  const command = args[0]
25
25
 
26
- const VERSION = '3.3.1'
26
+ const VERSION = '3.4.0'
27
27
 
28
28
  const HELP = `
29
29
  Docsector Reader v${VERSION}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@docsector/docsector-reader",
3
- "version": "3.3.1",
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",
@@ -58,10 +58,12 @@ import DPageExpandable from './DPageExpandable.vue'
58
58
 
59
59
  <ul
60
60
  v-else-if="token.tag === 'ul'"
61
+ v-bind="token.attrs || {}"
61
62
  v-html="token.content"
62
63
  ></ul>
63
64
  <ol
64
65
  v-else-if="token.tag === 'ol'"
66
+ v-bind="token.attrs || {}"
65
67
  v-html="token.content"
66
68
  ></ol>
67
69
 
@@ -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([
@@ -114,6 +115,32 @@ const restoreShieldedCodeSegments = (source = '', codeSegmentsMap = new Map()) =
114
115
  return restored
115
116
  }
116
117
 
118
+ const escapeHtmlAttributeValue = (value = '') => {
119
+ return String(value)
120
+ .replace(/&/g, '&amp;')
121
+ .replace(/"/g, '&quot;')
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
+
117
144
  const extractQuickLinksBlocks = (source = '') => {
118
145
  const map = new Map()
119
146
  let index = 0
@@ -468,6 +495,14 @@ const renderBlockToken = (markdown, element, env) => {
468
495
  return markdown.renderer.render([element], markdown.options, env).trim()
469
496
  }
470
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
+
471
506
  const createMarkdownBlockParser = () => {
472
507
  const markdown = installMathSupport(new MarkdownIt({
473
508
  html: true
@@ -478,6 +513,11 @@ const createMarkdownBlockParser = () => {
478
513
  rightDelimiter: ';',
479
514
  allowedAttributes: ['filename', 'group', 'tab', 'breadcrumb']
480
515
  })
516
+ markdown.use(taskLists, {
517
+ enabled: false,
518
+ label: false,
519
+ labelAfter: false
520
+ })
481
521
 
482
522
  return markdown
483
523
  }
@@ -662,7 +702,7 @@ export const tokenizePageSectionSource = (source = '', options = {}) => {
662
702
  }
663
703
 
664
704
  if (element.type === 'inline') {
665
- element.content = markdownInline.renderInline(element.content, markdownEnv)
705
+ element.content = renderInlineToken(markdown, markdownInline, element, markdownEnv)
666
706
  }
667
707
 
668
708
  if (level === 0) {
@@ -779,23 +819,29 @@ export const tokenizePageSectionSource = (source = '', options = {}) => {
779
819
  switch (element.type) {
780
820
  case 'bullet_list_open':
781
821
  if (level === 1) {
822
+ const attributes = getTokenAttributes(element)
823
+
782
824
  tokens.push({
783
825
  tag: 'ul',
784
- content: ''
826
+ content: '',
827
+ ...(attributes !== null ? { attrs: attributes } : {})
785
828
  })
786
829
  } else {
787
- parent.content += '<ul>'
830
+ parent.content += `<ul${renderTokenAttributes(element)}>`
788
831
  }
789
832
  break
790
833
 
791
834
  case 'ordered_list_open':
792
835
  if (level === 1) {
836
+ const attributes = getTokenAttributes(element)
837
+
793
838
  tokens.push({
794
839
  tag: 'ol',
795
- content: ''
840
+ content: '',
841
+ ...(attributes !== null ? { attrs: attributes } : {})
796
842
  })
797
843
  } else {
798
- parent.content += '<ol>'
844
+ parent.content += `<ol${renderTokenAttributes(element)}>`
799
845
  }
800
846
  break
801
847
 
@@ -811,7 +857,7 @@ export const tokenizePageSectionSource = (source = '', options = {}) => {
811
857
  break
812
858
 
813
859
  case 'list_item_open':
814
- parent.content += '<li>'
860
+ parent.content += `<li${renderTokenAttributes(element)}>`
815
861
  break
816
862
 
817
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
@@ -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',
@@ -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(() => &#123;
28
- const path = route.path
29
- let hash = 5381
30
- for (let i = 0; i < path.length; i++) &#123;
31
- hash = (hash * 33) ^ path.charCodeAt(i)
32
- &#125;
33
- return hash >>> 0
34
- &#125;)
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
- &#123;
46
- path: 'overview',
47
- component: () => import('components/DSubpage.vue'),
48
- meta: &#123; status: config.status &#125;
49
- &#125;
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(() => &#123;
28
- const path = route.path
29
- let hash = 5381
30
- for (let i = 0; i < path.length; i++) &#123;
31
- hash = (hash * 33) ^ path.charCodeAt(i)
32
- &#125;
33
- return hash >>> 0
34
- &#125;)
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
- &#123;
46
- path: 'overview',
47
- component: () => import('components/DSubpage.vue'),
48
- meta: &#123; status: config.status &#125;
49
- &#125;
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.