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,249 @@
1
+ import FlexSearch from 'flexsearch'
2
+ import { useDocumentStore, type TocItem } from '@/stores/documentStore'
3
+
4
+ interface SearchResult {
5
+ id: string
6
+ title: string
7
+ type: string
8
+ }
9
+
10
+ export type { SearchResult }
11
+
12
+ const DB_NAME = 'docbook_search'
13
+ const STORE_NAME = 'index'
14
+ const DB_VERSION = 1
15
+
16
+ class SearchIndexDB {
17
+ private db: IDBDatabase | null = null
18
+
19
+ async init(): Promise<void> {
20
+ return new Promise((resolve, reject) => {
21
+ const request = indexedDB.open(DB_NAME, DB_VERSION)
22
+
23
+ request.onerror = () => reject(request.error)
24
+
25
+ request.onsuccess = () => {
26
+ this.db = request.result
27
+ resolve()
28
+ }
29
+
30
+ request.onupgradeneeded = (event) => {
31
+ const db = (event.target as IDBOpenDBRequest).result
32
+ if (!db.objectStoreNames.contains(STORE_NAME)) {
33
+ db.createObjectStore(STORE_NAME, { keyPath: 'docId' })
34
+ }
35
+ }
36
+ })
37
+ }
38
+
39
+ async saveIndex(docId: string, data: unknown): Promise<void> {
40
+ if (!this.db) return
41
+ return new Promise((resolve, reject) => {
42
+ const tx = this.db.transaction(STORE_NAME, 'readwrite')
43
+ const store = tx.objectStore(STORE_NAME)
44
+ const request = store.put({ docId, data, timestamp: Date.now() })
45
+ request.onsuccess = () => resolve()
46
+ request.onerror = () => reject(request.error)
47
+ })
48
+ }
49
+
50
+ async loadIndex(docId: string): Promise<unknown | null> {
51
+ if (!this.db) return null
52
+ return new Promise((resolve, reject) => {
53
+ const tx = this.db.transaction(STORE_NAME, 'readonly')
54
+ const store = tx.objectStore(STORE_NAME)
55
+ const request = store.get(docId)
56
+ request.onsuccess = () => resolve(request.result?.data || null)
57
+ request.onerror = () => reject(request.error)
58
+ })
59
+ }
60
+
61
+ async clear(): Promise<void> {
62
+ if (!this.db) return
63
+ return new Promise((resolve, reject) => {
64
+ const tx = this.db.transaction(STORE_NAME, 'readwrite')
65
+ const store = tx.objectStore(STORE_NAME)
66
+ const request = store.clear()
67
+ request.onsuccess = () => resolve()
68
+ request.onerror = () => reject(request.error)
69
+ })
70
+ }
71
+ }
72
+
73
+ let searchDb: SearchIndexDB | null = null
74
+
75
+ async function getSearchDb(): Promise<SearchIndexDB> {
76
+ if (!searchDb) {
77
+ searchDb = new SearchIndexDB()
78
+ await searchDb.init()
79
+ }
80
+ return searchDb
81
+ }
82
+
83
+ export function useSearch() {
84
+ const documentStore = useDocumentStore()
85
+
86
+ const query = ref('')
87
+ const results = ref<SearchResult[]>([])
88
+ const isSearching = ref(false)
89
+ const isIndexed = ref(false)
90
+ let debounceTimer: ReturnType<typeof setTimeout> | null = null
91
+
92
+ // FlexSearch Document index for TOC items
93
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
94
+ let index: any = new (FlexSearch as any).Document({
95
+ document: {
96
+ id: 'id',
97
+ index: ['title'],
98
+ store: ['id', 'title', 'type']
99
+ },
100
+ tokenize: 'forward',
101
+ resolution: 9
102
+ })
103
+
104
+ // Serialize index for IndexedDB storage
105
+ function serializeIndex(): string {
106
+ return index.export()
107
+ }
108
+
109
+ // Load index from IndexedDB
110
+ async function loadFromDb(): Promise<boolean> {
111
+ try {
112
+ const db = await getSearchDb()
113
+ const docId = window.location.pathname + '-' + (documentStore.title || 'default')
114
+ const data = await db.loadIndex(docId)
115
+ if (data) {
116
+ index = new (FlexSearch as any).Document({
117
+ document: {
118
+ id: 'id',
119
+ index: ['title'],
120
+ store: ['id', 'title', 'type']
121
+ },
122
+ tokenize: 'forward',
123
+ resolution: 9
124
+ })
125
+ index.import(data)
126
+ isIndexed.value = true
127
+ return true
128
+ }
129
+ } catch (e) {
130
+ console.warn('Failed to load search index from IndexedDB:', e)
131
+ }
132
+ return false
133
+ }
134
+
135
+ // Save index to IndexedDB
136
+ async function saveToDb() {
137
+ try {
138
+ const db = await getSearchDb()
139
+ const docId = window.location.pathname + '-' + (documentStore.title || 'default')
140
+ await db.saveIndex(docId, serializeIndex())
141
+ } catch (e) {
142
+ console.warn('Failed to save search index to IndexedDB:', e)
143
+ }
144
+ }
145
+
146
+ function buildIndexes() {
147
+ index.clear()
148
+ addToIndex(documentStore.sections)
149
+ saveToDb() // Persist to IndexedDB
150
+ isIndexed.value = true
151
+ }
152
+
153
+ function addToIndex(items: TocItem[]) {
154
+ items.forEach(item => {
155
+ index.add({
156
+ id: item.id,
157
+ title: item.title,
158
+ type: item.type
159
+ })
160
+ if (item.children && item.children.length > 0) {
161
+ addToIndex(item.children)
162
+ }
163
+ })
164
+ }
165
+
166
+ function search() {
167
+ if (!query.value.trim()) {
168
+ results.value = []
169
+ return
170
+ }
171
+
172
+ isSearching.value = true
173
+
174
+ const searchResults = index.search(query.value, { limit: 30, enrich: true })
175
+
176
+ const combined: SearchResult[] = []
177
+ const seen = new Set<string>()
178
+
179
+ if (Array.isArray(searchResults)) {
180
+ searchResults.forEach((field: { result: Array<{ id: string; doc?: SearchResult }> }) => {
181
+ if (field.result && Array.isArray(field.result)) {
182
+ field.result.forEach((r: { id: string; doc?: SearchResult }) => {
183
+ if (r.doc && !seen.has(r.id)) {
184
+ seen.add(r.id)
185
+ combined.push(r.doc)
186
+ }
187
+ })
188
+ }
189
+ })
190
+ }
191
+
192
+ results.value = combined.slice(0, 50)
193
+ isSearching.value = false
194
+ }
195
+
196
+ function debouncedSearch() {
197
+ if (debounceTimer) clearTimeout(debounceTimer)
198
+ debounceTimer = setTimeout(search, 150)
199
+ }
200
+
201
+ watch(query, debouncedSearch)
202
+
203
+ // Watch for document data changes to rebuild indexes
204
+ watch(
205
+ () => documentStore.documentData,
206
+ async () => {
207
+ // Try loading from IndexedDB first
208
+ const loaded = await loadFromDb()
209
+ if (!loaded) {
210
+ // Defer index building to after page load to avoid blocking
211
+ if ('requestIdleCallback' in window) {
212
+ requestIdleCallback(() => buildIndexes(), { timeout: 2000 })
213
+ } else {
214
+ setTimeout(buildIndexes, 100)
215
+ }
216
+ }
217
+ },
218
+ { immediate: true }
219
+ )
220
+
221
+ function selectResult(result: SearchResult) {
222
+ // Scroll to section and close search
223
+ const element = document.getElementById(result.id)
224
+ if (element) {
225
+ element.scrollIntoView({ behavior: 'smooth' })
226
+ closeSearch()
227
+ }
228
+ }
229
+
230
+ function closeSearch() {
231
+ query.value = ''
232
+ results.value = []
233
+ }
234
+
235
+ function rebuildIndex() {
236
+ buildIndexes()
237
+ }
238
+
239
+ return {
240
+ query,
241
+ results,
242
+ isSearching,
243
+ isIndexed,
244
+ search,
245
+ selectResult,
246
+ closeSearch,
247
+ rebuildIndex
248
+ }
249
+ }
@@ -0,0 +1,7 @@
1
+ /// <reference types="vite/client" />
2
+
3
+ declare module '*.vue' {
4
+ import type { DefineComponent } from 'vue'
5
+ const component: DefineComponent<{}, {}, any>
6
+ export default component
7
+ }
@@ -0,0 +1,221 @@
1
+ import { defineStore } from 'pinia'
2
+ import { ref, computed } from 'vue'
3
+
4
+ export interface TocItem {
5
+ id: string
6
+ title: string
7
+ type: 'chapter' | 'appendix' | 'part' | 'section' | 'glossary' | 'bibliography' | 'index' | 'reference' | 'preface'
8
+ children: TocItem[]
9
+ number?: string
10
+ }
11
+
12
+ // Block types for structured content (includes both block-level and inline types)
13
+ export type BlockType =
14
+ | 'paragraph'
15
+ | 'code'
16
+ | 'image'
17
+ | 'blockquote'
18
+ | 'ordered_list'
19
+ | 'itemized_list'
20
+ | 'list_item'
21
+ | 'definition_list'
22
+ | 'definition_term'
23
+ | 'definition_description'
24
+ | 'bibliography_entry'
25
+ | 'reference_entry'
26
+ | 'reference_meta'
27
+ | 'reference_class'
28
+ | 'reference_badge'
29
+ | 'reference_name'
30
+ | 'reference_definition'
31
+ | 'description_section'
32
+ | 'example_output'
33
+ | 'note'
34
+ | 'warning'
35
+ | 'caution'
36
+ | 'important'
37
+ | 'tip'
38
+ | 'danger'
39
+ | 'section'
40
+ | 'heading'
41
+ // Index types
42
+ | 'index_section'
43
+ | 'index_letter'
44
+ | 'index_entry'
45
+ | 'index_reference'
46
+ | 'index_see'
47
+ | 'index_see_also'
48
+ // Bibliography types
49
+ | 'biblio_abbrev'
50
+ | 'biblio_citetitle'
51
+ | 'biblio_author'
52
+ | 'biblio_personname'
53
+ | 'biblio_firstname'
54
+ | 'biblio_surname'
55
+ | 'biblio_publishername'
56
+ | 'biblio_pubdate'
57
+ | 'biblio_orgname'
58
+ | 'biblio_bibliosource'
59
+ // Inline types (used in children of paragraph blocks)
60
+ | 'text'
61
+ | 'strong'
62
+ | 'italic'
63
+ | 'emphasis'
64
+ | 'codetext'
65
+ | 'link'
66
+ | 'biblioref'
67
+ | 'citation'
68
+ | 'citation_link'
69
+ | 'xref'
70
+ | 'inline_image'
71
+
72
+ export interface ContentBlock {
73
+ type: BlockType
74
+ text?: string
75
+ src?: string
76
+ alt?: string
77
+ title?: string
78
+ language?: string
79
+ className?: string // For bibliography elements
80
+ children?: ContentBlock[]
81
+ }
82
+
83
+ export interface SectionContent {
84
+ section_id: string
85
+ blocks: ContentBlock[]
86
+ }
87
+
88
+ // Computed index data from Ruby
89
+ export interface IndexTerm {
90
+ primary: string
91
+ secondary?: string
92
+ tertiary?: string
93
+ section_id?: string
94
+ section_title?: string
95
+ sort_as?: string
96
+ sees?: string[]
97
+ see_alsos?: string[]
98
+ }
99
+
100
+ export interface IndexGroup {
101
+ letter: string
102
+ entries: IndexTerm[]
103
+ }
104
+
105
+ export interface IndexData {
106
+ title?: string
107
+ type?: string
108
+ groups: IndexGroup[]
109
+ }
110
+
111
+ export interface DocumentData {
112
+ title: string
113
+ sections: TocItem[]
114
+ numbering: Record<string, string>
115
+ content: Record<string, SectionContent>
116
+ index: IndexData | null
117
+ }
118
+
119
+ export const useDocumentStore = defineStore('document', () => {
120
+ const documentData = ref<DocumentData | null>(null)
121
+
122
+ function convertNumbering(numbering: any): Record<string, string> {
123
+ const map: Record<string, string> = {}
124
+ if (Array.isArray(numbering)) {
125
+ // NumberingEntry collection serializes as direct array [{id, value}]
126
+ numbering.forEach((e: { id: string, value: string }) => { map[e.id] = e.value })
127
+ } else if (numbering && typeof numbering === 'object' && Array.isArray(numbering.entries)) {
128
+ numbering.entries.forEach((e: { id: string, value: string }) => { map[e.id] = e.value })
129
+ }
130
+ return map
131
+ }
132
+
133
+ function convertContent(content: any): Record<string, SectionContent> {
134
+ const map: Record<string, SectionContent> = {}
135
+ if (content && typeof content === 'object' && Array.isArray(content.entries)) {
136
+ content.entries.forEach((e: { key: string, value: SectionContent }) => { map[e.key] = e.value })
137
+ }
138
+ return map
139
+ }
140
+
141
+ function loadFromWindow(): Promise<void> {
142
+ // Check if DOCBOOK_DATA is set (single_file mode with inline JSON)
143
+ const inlineData = (window as any).DOCBOOK_DATA
144
+ if (inlineData && inlineData !== null) {
145
+ const data = inlineData as any
146
+
147
+ // Extract sections and numbering from toc model
148
+ if (data.toc) {
149
+ data.sections = data.toc.sections || []
150
+ data.numbering = convertNumbering(data.toc.numbering)
151
+ delete data.toc
152
+ }
153
+
154
+ // Content from ContentData model
155
+ data.content = convertContent(data.content)
156
+
157
+ // Index is already a proper Index model, keep as-is
158
+ if (!data.index || (data.index && typeof data.index === 'object' && !Array.isArray(data.index.groups) && !Object.keys(data.index).length)) {
159
+ data.index = null
160
+ }
161
+
162
+ documentData.value = data as DocumentData
163
+ return Promise.resolve()
164
+ }
165
+
166
+ // Otherwise try to fetch from docbook.data.json (directory mode)
167
+ return fetch('docbook.data.json')
168
+ .then(res => {
169
+ if (!res.ok) throw new Error('Failed to load docbook.data.json')
170
+ return res.json()
171
+ })
172
+ .then(data => {
173
+ const d = data as any
174
+ if (d.toc) {
175
+ d.sections = d.toc.sections || []
176
+ d.numbering = convertNumbering(d.toc.numbering)
177
+ delete d.toc
178
+ }
179
+ d.content = convertContent(d.content)
180
+ if (!d.index) d.index = null
181
+ documentData.value = d as DocumentData
182
+ })
183
+ .catch(err => {
184
+ console.error('Failed to load document data:', err)
185
+ documentData.value = null
186
+ })
187
+ }
188
+
189
+ const title = computed(() => documentData.value?.title || 'DocBook Document')
190
+ const sections = computed(() => documentData.value?.sections || [])
191
+ const numbering = computed(() => documentData.value?.numbering || {})
192
+ const content = computed(() => documentData.value?.content || {})
193
+ const index = computed(() => documentData.value?.index || null)
194
+
195
+ // Get structured content for a section
196
+ function getSectionContent(id: string): SectionContent | null {
197
+ return content.value[id] || null
198
+ }
199
+
200
+ // Legacy: get pre-rendered HTML content (for backward compatibility - returns empty string)
201
+ function getContent(_id: string): string {
202
+ return ''
203
+ }
204
+
205
+ function getNumbering(id: string): string {
206
+ return numbering.value[id] || ''
207
+ }
208
+
209
+ return {
210
+ documentData,
211
+ loadFromWindow,
212
+ title,
213
+ sections,
214
+ numbering,
215
+ content,
216
+ index,
217
+ getSectionContent,
218
+ getContent,
219
+ getNumbering
220
+ }
221
+ })
@@ -0,0 +1,98 @@
1
+ import { defineStore } from 'pinia'
2
+ import { ref } from 'vue'
3
+
4
+ export type Theme = 'light' | 'dark' | 'system'
5
+ export type FontFamily = 'sans' | 'serif'
6
+
7
+ export const useUiStore = defineStore('ui', () => {
8
+ const theme = ref<Theme>((localStorage.getItem('docbook-theme') as Theme) || 'system')
9
+ const fontFamily = ref<FontFamily>((localStorage.getItem('docbook-font') as FontFamily) || 'sans')
10
+ const sidebarOpen = ref(false)
11
+ const searchOpen = ref(false)
12
+ const activeSectionId = ref<string | null>(null)
13
+
14
+ function initTheme() {
15
+ applyTheme()
16
+ }
17
+
18
+ function applyTheme() {
19
+ const isDark = theme.value === 'dark' ||
20
+ (theme.value === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches)
21
+ document.documentElement.classList.toggle('dark', isDark)
22
+ }
23
+
24
+ function setTheme(newTheme: Theme) {
25
+ theme.value = newTheme
26
+ localStorage.setItem('docbook-theme', newTheme)
27
+ applyTheme()
28
+ }
29
+
30
+ function toggleTheme() {
31
+ if (theme.value === 'light') {
32
+ setTheme('dark')
33
+ } else if (theme.value === 'dark') {
34
+ setTheme('system')
35
+ } else {
36
+ setTheme('light')
37
+ }
38
+ }
39
+
40
+ function setFontFamily(newFont: FontFamily) {
41
+ fontFamily.value = newFont
42
+ localStorage.setItem('docbook-font', newFont)
43
+ document.body.classList.toggle('font-serif', newFont === 'serif')
44
+ document.body.classList.toggle('font-sans', newFont === 'sans')
45
+ }
46
+
47
+ function openSidebar() {
48
+ sidebarOpen.value = true
49
+ }
50
+
51
+ function closeSidebar() {
52
+ sidebarOpen.value = false
53
+ }
54
+
55
+ function toggleSidebar() {
56
+ sidebarOpen.value = !sidebarOpen.value
57
+ }
58
+
59
+ function openSearch() {
60
+ searchOpen.value = true
61
+ }
62
+
63
+ function closeSearch() {
64
+ searchOpen.value = false
65
+ }
66
+
67
+ function setActiveSection(id: string) {
68
+ activeSectionId.value = id
69
+ }
70
+
71
+ // Watch for system theme changes
72
+ if (typeof window !== 'undefined') {
73
+ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
74
+ if (theme.value === 'system') {
75
+ applyTheme()
76
+ }
77
+ })
78
+ }
79
+
80
+ return {
81
+ theme,
82
+ fontFamily,
83
+ sidebarOpen,
84
+ searchOpen,
85
+ activeSectionId,
86
+ initTheme,
87
+ applyTheme,
88
+ setTheme,
89
+ toggleTheme,
90
+ setFontFamily,
91
+ openSidebar,
92
+ closeSidebar,
93
+ toggleSidebar,
94
+ openSearch,
95
+ closeSearch,
96
+ setActiveSection
97
+ }
98
+ })