docbook 0.1.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.
Files changed (271) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +5 -0
  3. data/CLAUDE.md +19 -0
  4. data/CODE_OF_CONDUCT.md +10 -0
  5. data/README.adoc +335 -0
  6. data/Rakefile +12 -0
  7. data/docs/.lycheeignore +33 -0
  8. data/docs/Gemfile +10 -0
  9. data/docs/INDEX.adoc +67 -0
  10. data/docs/_config.yml +186 -0
  11. data/docs/advanced/element-classes.adoc +185 -0
  12. data/docs/advanced/frontend-customization.adoc +193 -0
  13. data/docs/advanced/index.adoc +14 -0
  14. data/docs/advanced/templates.adoc +123 -0
  15. data/docs/features/element-coverage.adoc +373 -0
  16. data/docs/features/html-output/data-model.adoc +285 -0
  17. data/docs/features/html-output/directory-mode.adoc +180 -0
  18. data/docs/features/html-output/index.adoc +90 -0
  19. data/docs/features/html-output/single-file-mode.adoc +125 -0
  20. data/docs/features/index-generation.adoc +197 -0
  21. data/docs/features/index.adoc +63 -0
  22. data/docs/features/numbering.adoc +183 -0
  23. data/docs/features/toc-generation.adoc +150 -0
  24. data/docs/features/xinclude/fragid-schemes.adoc +287 -0
  25. data/docs/features/xinclude/index.adoc +119 -0
  26. data/docs/features/xinclude/text-includes.adoc +123 -0
  27. data/docs/features/xinclude/xml-includes.adoc +167 -0
  28. data/docs/getting-started/index.adoc +50 -0
  29. data/docs/getting-started/installation.adoc +113 -0
  30. data/docs/getting-started/quick-start.adoc +161 -0
  31. data/docs/guides/converting-article.adoc +188 -0
  32. data/docs/guides/converting-book.adoc +192 -0
  33. data/docs/guides/index.adoc +12 -0
  34. data/docs/guides/roundtrip-testing.adoc +129 -0
  35. data/docs/interfaces/cli/format-command.adoc +109 -0
  36. data/docs/interfaces/cli/index.adoc +73 -0
  37. data/docs/interfaces/cli/roundtrip-command.adoc +125 -0
  38. data/docs/interfaces/cli/to-html-command.adoc +178 -0
  39. data/docs/interfaces/cli/validate-command.adoc +104 -0
  40. data/docs/interfaces/index.adoc +101 -0
  41. data/docs/interfaces/ruby-api/html-output.adoc +186 -0
  42. data/docs/interfaces/ruby-api/index.adoc +111 -0
  43. data/docs/interfaces/ruby-api/parsing.adoc +202 -0
  44. data/docs/interfaces/ruby-api/xinclude.adoc +162 -0
  45. data/docs/interfaces/ruby-api/xref-resolution.adoc +156 -0
  46. data/docs/lychee.toml +42 -0
  47. data/docs/reference/cli-options.adoc +155 -0
  48. data/docs/reference/content-block-types.adoc +243 -0
  49. data/docs/reference/glossary.adoc +119 -0
  50. data/docs/reference/index.adoc +12 -0
  51. data/docs/reference/supported-elements.adoc +749 -0
  52. data/docs/understanding/architecture.adoc +145 -0
  53. data/docs/understanding/content-pipeline.adoc +102 -0
  54. data/docs/understanding/data-models.adoc +156 -0
  55. data/docs/understanding/index.adoc +34 -0
  56. data/exe/docbook +7 -0
  57. data/frontend/dist/app.css +1 -0
  58. data/frontend/dist/app.iife.js +24 -0
  59. data/frontend/package-lock.json +1445 -0
  60. data/frontend/package.json +22 -0
  61. data/frontend/src/App.vue +230 -0
  62. data/frontend/src/app.ts +8 -0
  63. data/frontend/src/components/AppSidebar.vue +116 -0
  64. data/frontend/src/components/AppendixSection.vue +39 -0
  65. data/frontend/src/components/BlockRenderer.vue +358 -0
  66. data/frontend/src/components/ChapterSection.vue +32 -0
  67. data/frontend/src/components/ContentRenderer.vue +13 -0
  68. data/frontend/src/components/EbookContainer.vue +147 -0
  69. data/frontend/src/components/EbookTopBar.vue +116 -0
  70. data/frontend/src/components/PartSection.vue +44 -0
  71. data/frontend/src/components/ReferenceEntry.vue +80 -0
  72. data/frontend/src/components/SearchModal.vue +286 -0
  73. data/frontend/src/components/SectionContent.vue +31 -0
  74. data/frontend/src/components/SettingsPanel.vue +236 -0
  75. data/frontend/src/components/TocTreeItem.vue +135 -0
  76. data/frontend/src/composables/useEbookStore.ts +191 -0
  77. data/frontend/src/composables/useSearch.ts +249 -0
  78. data/frontend/src/env.d.ts +7 -0
  79. data/frontend/src/stores/documentStore.ts +221 -0
  80. data/frontend/src/stores/uiStore.ts +98 -0
  81. data/frontend/src/styles.css +253 -0
  82. data/frontend/tsconfig.json +24 -0
  83. data/frontend/tsconfig.node.json +11 -0
  84. data/frontend/vite.config.ts +30 -0
  85. data/lib/docbook/cli.rb +123 -0
  86. data/lib/docbook/document.rb +67 -0
  87. data/lib/docbook/elements/abbrev.rb +16 -0
  88. data/lib/docbook/elements/acknowledgements.rb +22 -0
  89. data/lib/docbook/elements/address.rb +18 -0
  90. data/lib/docbook/elements/alt.rb +16 -0
  91. data/lib/docbook/elements/annotation.rb +18 -0
  92. data/lib/docbook/elements/appendix.rb +34 -0
  93. data/lib/docbook/elements/article.rb +31 -0
  94. data/lib/docbook/elements/att.rb +15 -0
  95. data/lib/docbook/elements/attribution.rb +20 -0
  96. data/lib/docbook/elements/audioobject.rb +18 -0
  97. data/lib/docbook/elements/author.rb +18 -0
  98. data/lib/docbook/elements/bibliography.rb +24 -0
  99. data/lib/docbook/elements/bibliomixed.rb +40 -0
  100. data/lib/docbook/elements/biblioref.rb +20 -0
  101. data/lib/docbook/elements/blockquote.rb +26 -0
  102. data/lib/docbook/elements/book.rb +36 -0
  103. data/lib/docbook/elements/buildtarget.rb +16 -0
  104. data/lib/docbook/elements/callout.rb +22 -0
  105. data/lib/docbook/elements/calloutlist.rb +22 -0
  106. data/lib/docbook/elements/caution.rb +30 -0
  107. data/lib/docbook/elements/chapter.rb +62 -0
  108. data/lib/docbook/elements/citation.rb +16 -0
  109. data/lib/docbook/elements/citerefentry.rb +26 -0
  110. data/lib/docbook/elements/citetitle.rb +20 -0
  111. data/lib/docbook/elements/classname.rb +16 -0
  112. data/lib/docbook/elements/code.rb +16 -0
  113. data/lib/docbook/elements/colophon.rb +22 -0
  114. data/lib/docbook/elements/computeroutput.rb +18 -0
  115. data/lib/docbook/elements/copyright.rb +17 -0
  116. data/lib/docbook/elements/danger.rb +28 -0
  117. data/lib/docbook/elements/date.rb +16 -0
  118. data/lib/docbook/elements/dedication.rb +22 -0
  119. data/lib/docbook/elements/dir.rb +16 -0
  120. data/lib/docbook/elements/emphasis.rb +18 -0
  121. data/lib/docbook/elements/entry.rb +30 -0
  122. data/lib/docbook/elements/entrytbl.rb +22 -0
  123. data/lib/docbook/elements/equation.rb +26 -0
  124. data/lib/docbook/elements/example.rb +30 -0
  125. data/lib/docbook/elements/fieldsynopsis.rb +21 -0
  126. data/lib/docbook/elements/figure.rb +28 -0
  127. data/lib/docbook/elements/filename.rb +16 -0
  128. data/lib/docbook/elements/firstname.rb +16 -0
  129. data/lib/docbook/elements/firstterm.rb +18 -0
  130. data/lib/docbook/elements/footnote.rb +22 -0
  131. data/lib/docbook/elements/footnoteref.rb +21 -0
  132. data/lib/docbook/elements/foreignphrase.rb +18 -0
  133. data/lib/docbook/elements/formalpara.rb +20 -0
  134. data/lib/docbook/elements/function.rb +16 -0
  135. data/lib/docbook/elements/glossary.rb +24 -0
  136. data/lib/docbook/elements/glossdef.rb +18 -0
  137. data/lib/docbook/elements/glossentry.rb +26 -0
  138. data/lib/docbook/elements/glosssee.rb +18 -0
  139. data/lib/docbook/elements/glossseealso.rb +18 -0
  140. data/lib/docbook/elements/glossterm.rb +18 -0
  141. data/lib/docbook/elements/holder.rb +16 -0
  142. data/lib/docbook/elements/honorific.rb +16 -0
  143. data/lib/docbook/elements/imagedata.rb +29 -0
  144. data/lib/docbook/elements/imageobject.rb +22 -0
  145. data/lib/docbook/elements/important.rb +30 -0
  146. data/lib/docbook/elements/index.rb +26 -0
  147. data/lib/docbook/elements/indexdiv.rb +22 -0
  148. data/lib/docbook/elements/indexentry.rb +22 -0
  149. data/lib/docbook/elements/indexterm.rb +34 -0
  150. data/lib/docbook/elements/info.rb +25 -0
  151. data/lib/docbook/elements/informalexample.rb +24 -0
  152. data/lib/docbook/elements/informalfigure.rb +20 -0
  153. data/lib/docbook/elements/inlinemediaobject.rb +22 -0
  154. data/lib/docbook/elements/itemizedlist.rb +21 -0
  155. data/lib/docbook/elements/legalnotice.rb +22 -0
  156. data/lib/docbook/elements/link.rb +26 -0
  157. data/lib/docbook/elements/listitem.rb +32 -0
  158. data/lib/docbook/elements/literal.rb +16 -0
  159. data/lib/docbook/elements/literallayout.rb +22 -0
  160. data/lib/docbook/elements/mediaobject.rb +26 -0
  161. data/lib/docbook/elements/msg.rb +20 -0
  162. data/lib/docbook/elements/msgexplan.rb +22 -0
  163. data/lib/docbook/elements/msgset.rb +22 -0
  164. data/lib/docbook/elements/note.rb +30 -0
  165. data/lib/docbook/elements/orderedlist.rb +23 -0
  166. data/lib/docbook/elements/orgname.rb +16 -0
  167. data/lib/docbook/elements/para.rb +61 -0
  168. data/lib/docbook/elements/paragraph_like.rb +18 -0
  169. data/lib/docbook/elements/parameter.rb +16 -0
  170. data/lib/docbook/elements/part.rb +38 -0
  171. data/lib/docbook/elements/personname.rb +22 -0
  172. data/lib/docbook/elements/phrase.rb +20 -0
  173. data/lib/docbook/elements/preface.rb +58 -0
  174. data/lib/docbook/elements/primary.rb +16 -0
  175. data/lib/docbook/elements/procedure.rb +24 -0
  176. data/lib/docbook/elements/productname.rb +18 -0
  177. data/lib/docbook/elements/productnumber.rb +16 -0
  178. data/lib/docbook/elements/programlisting.rb +28 -0
  179. data/lib/docbook/elements/property.rb +16 -0
  180. data/lib/docbook/elements/pubdate.rb +16 -0
  181. data/lib/docbook/elements/publishername.rb +16 -0
  182. data/lib/docbook/elements/quotation.rb +24 -0
  183. data/lib/docbook/elements/quote.rb +18 -0
  184. data/lib/docbook/elements/refentry.rb +32 -0
  185. data/lib/docbook/elements/refentrytitle.rb +16 -0
  186. data/lib/docbook/elements/reference.rb +26 -0
  187. data/lib/docbook/elements/refmeta.rb +29 -0
  188. data/lib/docbook/elements/refmiscinfo.rb +16 -0
  189. data/lib/docbook/elements/refname.rb +16 -0
  190. data/lib/docbook/elements/refnamediv.rb +22 -0
  191. data/lib/docbook/elements/refpurpose.rb +16 -0
  192. data/lib/docbook/elements/refsect1.rb +22 -0
  193. data/lib/docbook/elements/refsect2.rb +22 -0
  194. data/lib/docbook/elements/refsect3.rb +22 -0
  195. data/lib/docbook/elements/refsection.rb +32 -0
  196. data/lib/docbook/elements/releaseinfo.rb +16 -0
  197. data/lib/docbook/elements/remark.rb +20 -0
  198. data/lib/docbook/elements/replaceable.rb +16 -0
  199. data/lib/docbook/elements/row.rb +15 -0
  200. data/lib/docbook/elements/screen.rb +28 -0
  201. data/lib/docbook/elements/secondary.rb +16 -0
  202. data/lib/docbook/elements/sect1.rb +26 -0
  203. data/lib/docbook/elements/sect2.rb +24 -0
  204. data/lib/docbook/elements/sect3.rb +24 -0
  205. data/lib/docbook/elements/sect4.rb +24 -0
  206. data/lib/docbook/elements/sect5.rb +22 -0
  207. data/lib/docbook/elements/section.rb +66 -0
  208. data/lib/docbook/elements/see.rb +16 -0
  209. data/lib/docbook/elements/seealso.rb +18 -0
  210. data/lib/docbook/elements/set.rb +26 -0
  211. data/lib/docbook/elements/setindex.rb +24 -0
  212. data/lib/docbook/elements/sidebar.rb +22 -0
  213. data/lib/docbook/elements/simplesect.rb +32 -0
  214. data/lib/docbook/elements/step.rb +26 -0
  215. data/lib/docbook/elements/substeps.rb +18 -0
  216. data/lib/docbook/elements/subtitle.rb +16 -0
  217. data/lib/docbook/elements/surname.rb +16 -0
  218. data/lib/docbook/elements/table.rb +24 -0
  219. data/lib/docbook/elements/tag.rb +20 -0
  220. data/lib/docbook/elements/tbody.rb +15 -0
  221. data/lib/docbook/elements/term.rb +37 -0
  222. data/lib/docbook/elements/tertiary.rb +16 -0
  223. data/lib/docbook/elements/textobject.rb +18 -0
  224. data/lib/docbook/elements/tfoot.rb +15 -0
  225. data/lib/docbook/elements/tgroup.rb +21 -0
  226. data/lib/docbook/elements/thead.rb +15 -0
  227. data/lib/docbook/elements/tip.rb +30 -0
  228. data/lib/docbook/elements/title.rb +16 -0
  229. data/lib/docbook/elements/toc.rb +24 -0
  230. data/lib/docbook/elements/tocdiv.rb +22 -0
  231. data/lib/docbook/elements/tocentry.rb +20 -0
  232. data/lib/docbook/elements/topic.rb +26 -0
  233. data/lib/docbook/elements/type.rb +16 -0
  234. data/lib/docbook/elements/uri.rb +16 -0
  235. data/lib/docbook/elements/userinput.rb +18 -0
  236. data/lib/docbook/elements/variable.rb +16 -0
  237. data/lib/docbook/elements/variablelist.rb +19 -0
  238. data/lib/docbook/elements/varlistentry.rb +17 -0
  239. data/lib/docbook/elements/version.rb +16 -0
  240. data/lib/docbook/elements/videoobject.rb +18 -0
  241. data/lib/docbook/elements/warning.rb +30 -0
  242. data/lib/docbook/elements/xref.rb +18 -0
  243. data/lib/docbook/elements/year.rb +16 -0
  244. data/lib/docbook/elements.rb +204 -0
  245. data/lib/docbook/models/document_metadata.rb +30 -0
  246. data/lib/docbook/models/document_root.rb +33 -0
  247. data/lib/docbook/models/index_entry.rb +28 -0
  248. data/lib/docbook/models/reading_position.rb +22 -0
  249. data/lib/docbook/models/section_number.rb +18 -0
  250. data/lib/docbook/models/section_root.rb +29 -0
  251. data/lib/docbook/models/toc_node.rb +24 -0
  252. data/lib/docbook/models.rb +16 -0
  253. data/lib/docbook/output/data.rb +196 -0
  254. data/lib/docbook/output/html.rb +1450 -0
  255. data/lib/docbook/output/index_generator.rb +281 -0
  256. data/lib/docbook/output.rb +8 -0
  257. data/lib/docbook/services/document_builder.rb +258 -0
  258. data/lib/docbook/services/element_to_hash.rb +287 -0
  259. data/lib/docbook/services/index_generator.rb +134 -0
  260. data/lib/docbook/services/numbering_service.rb +186 -0
  261. data/lib/docbook/services/toc_generator.rb +138 -0
  262. data/lib/docbook/services.rb +14 -0
  263. data/lib/docbook/templates/document.html.liquid +20 -0
  264. data/lib/docbook/templates/partials/_head.liquid +39 -0
  265. data/lib/docbook/templates/partials/_scripts.liquid +10 -0
  266. data/lib/docbook/version.rb +5 -0
  267. data/lib/docbook/xinclude_resolver.rb +217 -0
  268. data/lib/docbook/xref_resolver.rb +78 -0
  269. data/lib/docbook.rb +17 -0
  270. data/sig/docbook.rbs +4 -0
  271. metadata +385 -0
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "docbook-frontend",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "build": "vite build",
8
+ "dev": "vite",
9
+ "typecheck": "vue-tsc --noEmit"
10
+ },
11
+ "dependencies": {
12
+ "flexsearch": "^0.7.43",
13
+ "pinia": "^2.1.7",
14
+ "vue": "^3.4.21"
15
+ },
16
+ "devDependencies": {
17
+ "@vitejs/plugin-vue": "^5.0.4",
18
+ "typescript": "^5.4.0",
19
+ "vite": "^5.2.0",
20
+ "vue-tsc": "^2.0.6"
21
+ }
22
+ }
@@ -0,0 +1,230 @@
1
+ <template>
2
+ <EbookContainer :class="['h-screen overflow-hidden', ebookStore.getThemeClass()]" :style="ebookStore.getCssVariables()">
3
+ <!-- Mobile overlay -->
4
+ <div
5
+ v-if="uiStore.sidebarOpen"
6
+ @click="uiStore.closeSidebar"
7
+ class="fixed inset-0 bg-black/50 z-40 lg:hidden"
8
+ ></div>
9
+
10
+ <AppSidebar />
11
+
12
+ <!-- Ebook Top Bar -->
13
+ <EbookTopBar
14
+ :title="documentStore.title"
15
+ :sidebar-open="uiStore.sidebarOpen"
16
+ @toggle-toc="toggleToc"
17
+ @toggle-settings="ebookStore.toggleSettings"
18
+ />
19
+
20
+ <main ref="mainContent" :class="[
21
+ 'h-screen overflow-y-auto pt-14 transition-all duration-200',
22
+ uiStore.sidebarOpen ? 'lg:pl-[280px]' : ''
23
+ ]" @scroll="handleScroll">
24
+ <!-- Active section breadcrumb -->
25
+ <div v-if="activeSection" class="sticky top-0 z-10 border-b backdrop-blur-sm px-6 py-2 text-sm"
26
+ :class="[
27
+ 'bg-white/90 dark:bg-gray-900/90 border-gray-200 dark:border-gray-700',
28
+ 'text-gray-600 dark:text-gray-400'
29
+ ]">
30
+ <div class="max-w-4xl mx-auto flex items-center gap-2">
31
+ <span v-if="activeSectionParent" :class="['text-gray-900', 'dark:text-gray-100']">{{ activeSectionParent.title }} /</span>
32
+ <span class="font-medium" :class="['text-gray-900 dark:text-gray-100']">{{ activeSection.title }}</span>
33
+ </div>
34
+ </div>
35
+
36
+ <div class="px-6 py-10 lg:px-8 lg:py-12 mx-auto" :style="{ maxWidth: '72rem', margin: '0 auto' }">
37
+ <!-- Article content (no sections) -->
38
+ <div v-if="!hasSections" class="db-content">
39
+ <BlockRenderer v-if="articleContent" :blocks="articleContent.blocks" />
40
+ </div>
41
+
42
+ <!-- Section content -->
43
+ <template v-else>
44
+ <div v-for="section in documentStore.sections" :key="section.id">
45
+ <ChapterSection v-if="section.type === 'chapter'" :section="section" />
46
+ <AppendixSection v-else-if="section.type === 'appendix'" :section="section" />
47
+ <PartSection v-else-if="section.type === 'part'" :section="section" />
48
+ <SectionContent v-else :section="section" />
49
+ </div>
50
+ </template>
51
+
52
+ <!-- Footer -->
53
+ <footer class="mt-16 pt-8 border-t text-center text-sm" :class="['border-gray-200 dark:border-gray-700', 'text-gray-500 dark:text-gray-400']">
54
+ Generated by <a href="https://github.com/metanorma/metanorma-docbook" class="hover:underline" :class="['text-blue-600 dark:text-blue-400']">Metanorma Docbook</a> gem
55
+ </footer>
56
+ </div>
57
+ </main>
58
+
59
+ <SearchModal />
60
+ <SettingsPanel />
61
+
62
+ <!-- Keyboard shortcut listener -->
63
+ <Teleport to="body">
64
+ <div @keydown="handleGlobalKeydown"></div>
65
+ </Teleport>
66
+ </EbookContainer>
67
+ </template>
68
+
69
+ <script setup lang="ts">
70
+ import { onMounted, computed, ref } from 'vue'
71
+ import { useDocumentStore, type TocItem } from '@/stores/documentStore'
72
+ import { useUiStore } from '@/stores/uiStore'
73
+ import { useEbookStore } from '@/composables/useEbookStore'
74
+ import AppSidebar from '@/components/AppSidebar.vue'
75
+ import SearchModal from '@/components/SearchModal.vue'
76
+ import SettingsPanel from '@/components/SettingsPanel.vue'
77
+ import ChapterSection from '@/components/ChapterSection.vue'
78
+ import AppendixSection from '@/components/AppendixSection.vue'
79
+ import PartSection from '@/components/PartSection.vue'
80
+ import SectionContent from '@/components/SectionContent.vue'
81
+ import BlockRenderer from '@/components/BlockRenderer.vue'
82
+ import EbookTopBar from '@/components/EbookTopBar.vue'
83
+ import EbookContainer from '@/components/EbookContainer.vue'
84
+
85
+ const documentStore = useDocumentStore()
86
+ const uiStore = useUiStore()
87
+ const ebookStore = useEbookStore()
88
+ const mainContent = ref<HTMLElement | null>(null)
89
+
90
+ function toggleToc() {
91
+ uiStore.toggleSidebar()
92
+ }
93
+
94
+ const hasSections = computed(() => documentStore.sections && documentStore.sections.length > 0)
95
+
96
+ // Article content for documents without sections
97
+ const articleContent = computed(() => documentStore.getSectionContent('article-content'))
98
+
99
+ // Find the currently active section based on scroll position
100
+ const activeSection = computed(() => {
101
+ if (!uiStore.activeSectionId) return null
102
+ return findSectionById(documentStore.sections, uiStore.activeSectionId)
103
+ })
104
+
105
+ // Find parent section if active section is nested
106
+ const activeSectionParent = computed((): TocItem | null => {
107
+ if (!uiStore.activeSectionId) return null
108
+ for (const section of documentStore.sections) {
109
+ if (section.children && section.children.some(c => c.id === uiStore.activeSectionId)) {
110
+ return section
111
+ }
112
+ }
113
+ return null
114
+ })
115
+
116
+ function findSectionById(sections: TocItem[], id: string): TocItem | null {
117
+ for (const section of sections) {
118
+ if (section.id === id) return section
119
+ if (section.children) {
120
+ const found = findSectionById(section.children, id)
121
+ if (found) return found
122
+ }
123
+ }
124
+ return null
125
+ }
126
+
127
+ let scrollTimeout: ReturnType<typeof setTimeout> | null = null
128
+
129
+ function handleScroll() {
130
+ if (scrollTimeout) clearTimeout(scrollTimeout)
131
+ scrollTimeout = setTimeout(() => {
132
+ updateActiveSection()
133
+ }, 50)
134
+ }
135
+
136
+ function updateActiveSection() {
137
+ if (!mainContent.value) return
138
+
139
+ const scrollTop = mainContent.value.scrollTop
140
+ const sections = documentStore.sections
141
+
142
+ let foundSection: TocItem | null = null
143
+
144
+ function checkSection(section: TocItem) {
145
+ const element = document.getElementById(section.id)
146
+ if (!element) return
147
+
148
+ const rect = element.getBoundingClientRect()
149
+ const offsetTop = rect.top + scrollTop - 100
150
+
151
+ if (scrollTop >= offsetTop - 100) {
152
+ foundSection = section
153
+ }
154
+ }
155
+
156
+ function walkSections(sects: TocItem[]) {
157
+ for (const sect of sects) {
158
+ checkSection(sect)
159
+ if (sect.children) {
160
+ walkSections(sect.children)
161
+ }
162
+ }
163
+ }
164
+
165
+ walkSections(sections)
166
+
167
+ if (foundSection && (foundSection as TocItem).id !== uiStore.activeSectionId) {
168
+ uiStore.setActiveSection((foundSection as TocItem).id)
169
+ }
170
+ }
171
+
172
+ onMounted(() => {
173
+ documentStore.loadFromWindow()
174
+
175
+ // Clean up DOCBOOK_DATA from DOM to keep DOM small
176
+ delete (window as any).DOCBOOK_DATA
177
+
178
+ ebookStore.applyTheme()
179
+
180
+ // Force close settings panel on mount
181
+ if (ebookStore.settingsOpen.value) {
182
+ ebookStore.toggleSettings()
183
+ }
184
+
185
+ // Open sidebar by default on desktop
186
+ if (window.innerWidth >= 1024) {
187
+ uiStore.openSidebar()
188
+ }
189
+
190
+ // Handle hash navigation for anchor links
191
+ handleHashNavigation()
192
+ window.addEventListener('hashchange', handleHashNavigation)
193
+
194
+ // Global keyboard shortcuts
195
+ document.addEventListener('keydown', handleGlobalKeydown)
196
+
197
+ // Initial active section
198
+ setTimeout(updateActiveSection, 100)
199
+ })
200
+
201
+ function handleHashNavigation() {
202
+ const hash = window.location.hash
203
+ if (!hash) return
204
+ const id = hash.slice(1) // remove #
205
+ const element = document.getElementById(id)
206
+ if (element) {
207
+ element.scrollIntoView({ behavior: 'smooth', block: 'start' })
208
+ }
209
+ }
210
+
211
+ function handleGlobalKeydown(e: KeyboardEvent) {
212
+ if (e.key === '/' && !isInputFocused()) {
213
+ e.preventDefault()
214
+ uiStore.openSearch()
215
+ }
216
+ if (e.key === 'Escape') {
217
+ if (uiStore.searchOpen) {
218
+ uiStore.closeSearch()
219
+ }
220
+ if (uiStore.sidebarOpen) {
221
+ uiStore.closeSidebar()
222
+ }
223
+ }
224
+ }
225
+
226
+ function isInputFocused(): boolean {
227
+ const active = document.activeElement
228
+ return active instanceof HTMLInputElement || active instanceof HTMLTextAreaElement
229
+ }
230
+ </script>
@@ -0,0 +1,8 @@
1
+ import { createApp } from 'vue'
2
+ import { createPinia } from 'pinia'
3
+ import App from './App.vue'
4
+ import './styles.css'
5
+
6
+ const app = createApp(App)
7
+ app.use(createPinia())
8
+ app.mount('#docbook-app')
@@ -0,0 +1,116 @@
1
+ <template>
2
+ <aside
3
+ :class="[
4
+ 'fixed top-0 left-0 z-50 w-[280px] h-full bg-gray-50 dark:bg-gray-800 border-r border-gray-200 dark:border-gray-700 overflow-y-auto transition-transform duration-200',
5
+ uiStore.sidebarOpen ? 'translate-x-0' : '-translate-x-full'
6
+ ]"
7
+ >
8
+ <!-- Sidebar Header -->
9
+ <div class="sticky top-0 bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 p-4 z-10">
10
+ <div class="flex items-center justify-between mb-3">
11
+ <span class="font-bold text-xs uppercase tracking-wider text-gray-500 dark:text-gray-400 truncate pr-2">
12
+ {{ documentStore.title }}
13
+ </span>
14
+ <button
15
+ @click="uiStore.closeSidebar"
16
+ class="p-1 rounded hover:bg-gray-200 dark:hover:bg-gray-700 text-gray-600 dark:text-gray-300"
17
+ >
18
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
19
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
20
+ </svg>
21
+ </button>
22
+ </div>
23
+
24
+ <!-- Search button -->
25
+ <button
26
+ @click="uiStore.openSearch"
27
+ class="w-full flex items-center gap-2 px-3 py-1.5 text-sm bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md hover:bg-gray-50 dark:hover:bg-gray-600 text-gray-500 dark:text-gray-400 transition-colors"
28
+ >
29
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
30
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
31
+ </svg>
32
+ <span>Search headings...</span>
33
+ <kbd class="ml-auto text-xs bg-gray-100 dark:bg-gray-600 px-1.5 py-0.5 rounded">/</kbd>
34
+ </button>
35
+
36
+ <!-- Font toggle -->
37
+ <div class="flex gap-1 mt-3">
38
+ <button
39
+ @click="setFontFamily('sans')"
40
+ :class="uiStore.fontFamily === 'sans' ? 'bg-blue-500 text-white' : 'bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300'"
41
+ class="flex-1 py-1 px-2 text-xs rounded-md transition-colors font-sans"
42
+ >
43
+ Sans
44
+ </button>
45
+ <button
46
+ @click="setFontFamily('serif')"
47
+ :class="uiStore.fontFamily === 'serif' ? 'bg-blue-500 text-white' : 'bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300'"
48
+ class="flex-1 py-1 px-2 text-xs rounded-md transition-colors font-serif"
49
+ >
50
+ Serif
51
+ </button>
52
+ </div>
53
+
54
+ <!-- Theme toggle -->
55
+ <button
56
+ @click="cycleTheme"
57
+ class="w-full flex items-center justify-between mt-2 py-1.5 px-3 text-xs bg-gray-200 dark:bg-gray-700 rounded-md hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors text-gray-700 dark:text-gray-300"
58
+ >
59
+ <span class="flex items-center gap-2">
60
+ <svg v-if="ebookStore.theme.value === 'day' || ebookStore.theme.value === 'sepia'" class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
61
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"/>
62
+ </svg>
63
+ <svg v-else class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
64
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"/>
65
+ </svg>
66
+ <span>{{ themeLabel }}</span>
67
+ </span>
68
+ </button>
69
+ </div>
70
+
71
+ <!-- TOC -->
72
+ <nav class="p-3">
73
+ <ul class="space-y-0.5">
74
+ <TocTreeItem
75
+ v-for="item in documentStore.sections"
76
+ :key="item.id"
77
+ :item="item"
78
+ :depth="1"
79
+ />
80
+ </ul>
81
+ </nav>
82
+ </aside>
83
+ </template>
84
+
85
+ <script setup lang="ts">
86
+ import { computed } from 'vue'
87
+ import { useDocumentStore } from '@/stores/documentStore'
88
+ import { useUiStore } from '@/stores/uiStore'
89
+ import { useEbookStore, type Theme } from '@/composables/useEbookStore'
90
+ import TocTreeItem from '@/components/TocTreeItem.vue'
91
+
92
+ const documentStore = useDocumentStore()
93
+ const uiStore = useUiStore()
94
+ const ebookStore = useEbookStore()
95
+
96
+ const themeOrder: Theme[] = ['day', 'sepia', 'night', 'oled']
97
+
98
+ const themeLabel = computed(() => {
99
+ switch (ebookStore.theme.value) {
100
+ case 'day': return 'Day'
101
+ case 'sepia': return 'Sepia'
102
+ case 'night': return 'Night'
103
+ case 'oled': return 'OLED'
104
+ }
105
+ })
106
+
107
+ function cycleTheme() {
108
+ const currentIndex = themeOrder.indexOf(ebookStore.theme.value)
109
+ const nextIndex = (currentIndex + 1) % themeOrder.length
110
+ ebookStore.setTheme(themeOrder[nextIndex])
111
+ }
112
+
113
+ function setFontFamily(font: 'sans' | 'serif') {
114
+ uiStore.setFontFamily(font)
115
+ }
116
+ </script>
@@ -0,0 +1,39 @@
1
+ <template>
2
+ <section :id="section.id" class="mb-12 scroll-mt-20">
3
+ <div class="db-appendix-wrap">
4
+ <h2 class="flex items-baseline gap-3 text-xl font-bold mb-5 pb-2 border-b border-gray-200 dark:border-gray-700">
5
+ <span class="text-gray-500 dark:text-gray-400 text-lg font-normal">{{ numbering }}</span>
6
+ <span>{{ section.title }}</span>
7
+ </h2>
8
+ <BlockRenderer v-if="sectionContent" :blocks="sectionContent.blocks" />
9
+ <!-- Render non-changelog children -->
10
+ <template v-if="section.children && section.children.length > 0">
11
+ <div v-for="child in section.children" :key="child.id" class="ml-4">
12
+ <SectionContent v-if="!isChangelogSection(child)" :section="child" />
13
+ </div>
14
+ </template>
15
+ </div>
16
+ </section>
17
+ </template>
18
+
19
+ <script setup lang="ts">
20
+ import { computed } from 'vue'
21
+ import { useDocumentStore, type TocItem } from '@/stores/documentStore'
22
+ import SectionContent from '@/components/SectionContent.vue'
23
+ import BlockRenderer from '@/components/BlockRenderer.vue'
24
+
25
+ interface Props {
26
+ section: TocItem
27
+ }
28
+
29
+ const props = defineProps<Props>()
30
+ const documentStore = useDocumentStore()
31
+
32
+ const numbering = computed(() => documentStore.getNumbering(props.section.id))
33
+ const sectionContent = computed(() => documentStore.getSectionContent(props.section.id))
34
+
35
+ function isChangelogSection(section: TocItem): boolean {
36
+ const id = section.id.toLowerCase()
37
+ return id === 'changelog' || /\br\d+\b/.test(id)
38
+ }
39
+ </script>