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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +5 -0
- data/CLAUDE.md +19 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/README.adoc +335 -0
- data/Rakefile +12 -0
- data/docs/.lycheeignore +33 -0
- data/docs/Gemfile +10 -0
- data/docs/INDEX.adoc +67 -0
- data/docs/_config.yml +186 -0
- data/docs/advanced/element-classes.adoc +185 -0
- data/docs/advanced/frontend-customization.adoc +193 -0
- data/docs/advanced/index.adoc +14 -0
- data/docs/advanced/templates.adoc +123 -0
- data/docs/features/element-coverage.adoc +373 -0
- data/docs/features/html-output/data-model.adoc +285 -0
- data/docs/features/html-output/directory-mode.adoc +180 -0
- data/docs/features/html-output/index.adoc +90 -0
- data/docs/features/html-output/single-file-mode.adoc +125 -0
- data/docs/features/index-generation.adoc +197 -0
- data/docs/features/index.adoc +63 -0
- data/docs/features/numbering.adoc +183 -0
- data/docs/features/toc-generation.adoc +150 -0
- data/docs/features/xinclude/fragid-schemes.adoc +287 -0
- data/docs/features/xinclude/index.adoc +119 -0
- data/docs/features/xinclude/text-includes.adoc +123 -0
- data/docs/features/xinclude/xml-includes.adoc +167 -0
- data/docs/getting-started/index.adoc +50 -0
- data/docs/getting-started/installation.adoc +113 -0
- data/docs/getting-started/quick-start.adoc +161 -0
- data/docs/guides/converting-article.adoc +188 -0
- data/docs/guides/converting-book.adoc +192 -0
- data/docs/guides/index.adoc +12 -0
- data/docs/guides/roundtrip-testing.adoc +129 -0
- data/docs/interfaces/cli/format-command.adoc +109 -0
- data/docs/interfaces/cli/index.adoc +73 -0
- data/docs/interfaces/cli/roundtrip-command.adoc +125 -0
- data/docs/interfaces/cli/to-html-command.adoc +178 -0
- data/docs/interfaces/cli/validate-command.adoc +104 -0
- data/docs/interfaces/index.adoc +101 -0
- data/docs/interfaces/ruby-api/html-output.adoc +186 -0
- data/docs/interfaces/ruby-api/index.adoc +111 -0
- data/docs/interfaces/ruby-api/parsing.adoc +202 -0
- data/docs/interfaces/ruby-api/xinclude.adoc +162 -0
- data/docs/interfaces/ruby-api/xref-resolution.adoc +156 -0
- data/docs/lychee.toml +42 -0
- data/docs/reference/cli-options.adoc +155 -0
- data/docs/reference/content-block-types.adoc +243 -0
- data/docs/reference/glossary.adoc +119 -0
- data/docs/reference/index.adoc +12 -0
- data/docs/reference/supported-elements.adoc +749 -0
- data/docs/understanding/architecture.adoc +145 -0
- data/docs/understanding/content-pipeline.adoc +102 -0
- data/docs/understanding/data-models.adoc +156 -0
- data/docs/understanding/index.adoc +34 -0
- data/exe/docbook +7 -0
- data/frontend/dist/app.css +1 -0
- data/frontend/dist/app.iife.js +24 -0
- data/frontend/package-lock.json +1445 -0
- data/frontend/package.json +22 -0
- data/frontend/src/App.vue +230 -0
- data/frontend/src/app.ts +8 -0
- data/frontend/src/components/AppSidebar.vue +116 -0
- data/frontend/src/components/AppendixSection.vue +39 -0
- data/frontend/src/components/BlockRenderer.vue +358 -0
- data/frontend/src/components/ChapterSection.vue +32 -0
- data/frontend/src/components/ContentRenderer.vue +13 -0
- data/frontend/src/components/EbookContainer.vue +147 -0
- data/frontend/src/components/EbookTopBar.vue +116 -0
- data/frontend/src/components/PartSection.vue +44 -0
- data/frontend/src/components/ReferenceEntry.vue +80 -0
- data/frontend/src/components/SearchModal.vue +286 -0
- data/frontend/src/components/SectionContent.vue +31 -0
- data/frontend/src/components/SettingsPanel.vue +236 -0
- data/frontend/src/components/TocTreeItem.vue +135 -0
- data/frontend/src/composables/useEbookStore.ts +191 -0
- data/frontend/src/composables/useSearch.ts +249 -0
- data/frontend/src/env.d.ts +7 -0
- data/frontend/src/stores/documentStore.ts +221 -0
- data/frontend/src/stores/uiStore.ts +98 -0
- data/frontend/src/styles.css +253 -0
- data/frontend/tsconfig.json +24 -0
- data/frontend/tsconfig.node.json +11 -0
- data/frontend/vite.config.ts +30 -0
- data/lib/docbook/cli.rb +123 -0
- data/lib/docbook/document.rb +67 -0
- data/lib/docbook/elements/abbrev.rb +16 -0
- data/lib/docbook/elements/acknowledgements.rb +22 -0
- data/lib/docbook/elements/address.rb +18 -0
- data/lib/docbook/elements/alt.rb +16 -0
- data/lib/docbook/elements/annotation.rb +18 -0
- data/lib/docbook/elements/appendix.rb +34 -0
- data/lib/docbook/elements/article.rb +31 -0
- data/lib/docbook/elements/att.rb +15 -0
- data/lib/docbook/elements/attribution.rb +20 -0
- data/lib/docbook/elements/audioobject.rb +18 -0
- data/lib/docbook/elements/author.rb +18 -0
- data/lib/docbook/elements/bibliography.rb +24 -0
- data/lib/docbook/elements/bibliomixed.rb +40 -0
- data/lib/docbook/elements/biblioref.rb +20 -0
- data/lib/docbook/elements/blockquote.rb +26 -0
- data/lib/docbook/elements/book.rb +36 -0
- data/lib/docbook/elements/buildtarget.rb +16 -0
- data/lib/docbook/elements/callout.rb +22 -0
- data/lib/docbook/elements/calloutlist.rb +22 -0
- data/lib/docbook/elements/caution.rb +30 -0
- data/lib/docbook/elements/chapter.rb +62 -0
- data/lib/docbook/elements/citation.rb +16 -0
- data/lib/docbook/elements/citerefentry.rb +26 -0
- data/lib/docbook/elements/citetitle.rb +20 -0
- data/lib/docbook/elements/classname.rb +16 -0
- data/lib/docbook/elements/code.rb +16 -0
- data/lib/docbook/elements/colophon.rb +22 -0
- data/lib/docbook/elements/computeroutput.rb +18 -0
- data/lib/docbook/elements/copyright.rb +17 -0
- data/lib/docbook/elements/danger.rb +28 -0
- data/lib/docbook/elements/date.rb +16 -0
- data/lib/docbook/elements/dedication.rb +22 -0
- data/lib/docbook/elements/dir.rb +16 -0
- data/lib/docbook/elements/emphasis.rb +18 -0
- data/lib/docbook/elements/entry.rb +30 -0
- data/lib/docbook/elements/entrytbl.rb +22 -0
- data/lib/docbook/elements/equation.rb +26 -0
- data/lib/docbook/elements/example.rb +30 -0
- data/lib/docbook/elements/fieldsynopsis.rb +21 -0
- data/lib/docbook/elements/figure.rb +28 -0
- data/lib/docbook/elements/filename.rb +16 -0
- data/lib/docbook/elements/firstname.rb +16 -0
- data/lib/docbook/elements/firstterm.rb +18 -0
- data/lib/docbook/elements/footnote.rb +22 -0
- data/lib/docbook/elements/footnoteref.rb +21 -0
- data/lib/docbook/elements/foreignphrase.rb +18 -0
- data/lib/docbook/elements/formalpara.rb +20 -0
- data/lib/docbook/elements/function.rb +16 -0
- data/lib/docbook/elements/glossary.rb +24 -0
- data/lib/docbook/elements/glossdef.rb +18 -0
- data/lib/docbook/elements/glossentry.rb +26 -0
- data/lib/docbook/elements/glosssee.rb +18 -0
- data/lib/docbook/elements/glossseealso.rb +18 -0
- data/lib/docbook/elements/glossterm.rb +18 -0
- data/lib/docbook/elements/holder.rb +16 -0
- data/lib/docbook/elements/honorific.rb +16 -0
- data/lib/docbook/elements/imagedata.rb +29 -0
- data/lib/docbook/elements/imageobject.rb +22 -0
- data/lib/docbook/elements/important.rb +30 -0
- data/lib/docbook/elements/index.rb +26 -0
- data/lib/docbook/elements/indexdiv.rb +22 -0
- data/lib/docbook/elements/indexentry.rb +22 -0
- data/lib/docbook/elements/indexterm.rb +34 -0
- data/lib/docbook/elements/info.rb +25 -0
- data/lib/docbook/elements/informalexample.rb +24 -0
- data/lib/docbook/elements/informalfigure.rb +20 -0
- data/lib/docbook/elements/inlinemediaobject.rb +22 -0
- data/lib/docbook/elements/itemizedlist.rb +21 -0
- data/lib/docbook/elements/legalnotice.rb +22 -0
- data/lib/docbook/elements/link.rb +26 -0
- data/lib/docbook/elements/listitem.rb +32 -0
- data/lib/docbook/elements/literal.rb +16 -0
- data/lib/docbook/elements/literallayout.rb +22 -0
- data/lib/docbook/elements/mediaobject.rb +26 -0
- data/lib/docbook/elements/msg.rb +20 -0
- data/lib/docbook/elements/msgexplan.rb +22 -0
- data/lib/docbook/elements/msgset.rb +22 -0
- data/lib/docbook/elements/note.rb +30 -0
- data/lib/docbook/elements/orderedlist.rb +23 -0
- data/lib/docbook/elements/orgname.rb +16 -0
- data/lib/docbook/elements/para.rb +61 -0
- data/lib/docbook/elements/paragraph_like.rb +18 -0
- data/lib/docbook/elements/parameter.rb +16 -0
- data/lib/docbook/elements/part.rb +38 -0
- data/lib/docbook/elements/personname.rb +22 -0
- data/lib/docbook/elements/phrase.rb +20 -0
- data/lib/docbook/elements/preface.rb +58 -0
- data/lib/docbook/elements/primary.rb +16 -0
- data/lib/docbook/elements/procedure.rb +24 -0
- data/lib/docbook/elements/productname.rb +18 -0
- data/lib/docbook/elements/productnumber.rb +16 -0
- data/lib/docbook/elements/programlisting.rb +28 -0
- data/lib/docbook/elements/property.rb +16 -0
- data/lib/docbook/elements/pubdate.rb +16 -0
- data/lib/docbook/elements/publishername.rb +16 -0
- data/lib/docbook/elements/quotation.rb +24 -0
- data/lib/docbook/elements/quote.rb +18 -0
- data/lib/docbook/elements/refentry.rb +32 -0
- data/lib/docbook/elements/refentrytitle.rb +16 -0
- data/lib/docbook/elements/reference.rb +26 -0
- data/lib/docbook/elements/refmeta.rb +29 -0
- data/lib/docbook/elements/refmiscinfo.rb +16 -0
- data/lib/docbook/elements/refname.rb +16 -0
- data/lib/docbook/elements/refnamediv.rb +22 -0
- data/lib/docbook/elements/refpurpose.rb +16 -0
- data/lib/docbook/elements/refsect1.rb +22 -0
- data/lib/docbook/elements/refsect2.rb +22 -0
- data/lib/docbook/elements/refsect3.rb +22 -0
- data/lib/docbook/elements/refsection.rb +32 -0
- data/lib/docbook/elements/releaseinfo.rb +16 -0
- data/lib/docbook/elements/remark.rb +20 -0
- data/lib/docbook/elements/replaceable.rb +16 -0
- data/lib/docbook/elements/row.rb +15 -0
- data/lib/docbook/elements/screen.rb +28 -0
- data/lib/docbook/elements/secondary.rb +16 -0
- data/lib/docbook/elements/sect1.rb +26 -0
- data/lib/docbook/elements/sect2.rb +24 -0
- data/lib/docbook/elements/sect3.rb +24 -0
- data/lib/docbook/elements/sect4.rb +24 -0
- data/lib/docbook/elements/sect5.rb +22 -0
- data/lib/docbook/elements/section.rb +66 -0
- data/lib/docbook/elements/see.rb +16 -0
- data/lib/docbook/elements/seealso.rb +18 -0
- data/lib/docbook/elements/set.rb +26 -0
- data/lib/docbook/elements/setindex.rb +24 -0
- data/lib/docbook/elements/sidebar.rb +22 -0
- data/lib/docbook/elements/simplesect.rb +32 -0
- data/lib/docbook/elements/step.rb +26 -0
- data/lib/docbook/elements/substeps.rb +18 -0
- data/lib/docbook/elements/subtitle.rb +16 -0
- data/lib/docbook/elements/surname.rb +16 -0
- data/lib/docbook/elements/table.rb +24 -0
- data/lib/docbook/elements/tag.rb +20 -0
- data/lib/docbook/elements/tbody.rb +15 -0
- data/lib/docbook/elements/term.rb +37 -0
- data/lib/docbook/elements/tertiary.rb +16 -0
- data/lib/docbook/elements/textobject.rb +18 -0
- data/lib/docbook/elements/tfoot.rb +15 -0
- data/lib/docbook/elements/tgroup.rb +21 -0
- data/lib/docbook/elements/thead.rb +15 -0
- data/lib/docbook/elements/tip.rb +30 -0
- data/lib/docbook/elements/title.rb +16 -0
- data/lib/docbook/elements/toc.rb +24 -0
- data/lib/docbook/elements/tocdiv.rb +22 -0
- data/lib/docbook/elements/tocentry.rb +20 -0
- data/lib/docbook/elements/topic.rb +26 -0
- data/lib/docbook/elements/type.rb +16 -0
- data/lib/docbook/elements/uri.rb +16 -0
- data/lib/docbook/elements/userinput.rb +18 -0
- data/lib/docbook/elements/variable.rb +16 -0
- data/lib/docbook/elements/variablelist.rb +19 -0
- data/lib/docbook/elements/varlistentry.rb +17 -0
- data/lib/docbook/elements/version.rb +16 -0
- data/lib/docbook/elements/videoobject.rb +18 -0
- data/lib/docbook/elements/warning.rb +30 -0
- data/lib/docbook/elements/xref.rb +18 -0
- data/lib/docbook/elements/year.rb +16 -0
- data/lib/docbook/elements.rb +204 -0
- data/lib/docbook/models/document_metadata.rb +30 -0
- data/lib/docbook/models/document_root.rb +33 -0
- data/lib/docbook/models/index_entry.rb +28 -0
- data/lib/docbook/models/reading_position.rb +22 -0
- data/lib/docbook/models/section_number.rb +18 -0
- data/lib/docbook/models/section_root.rb +29 -0
- data/lib/docbook/models/toc_node.rb +24 -0
- data/lib/docbook/models.rb +16 -0
- data/lib/docbook/output/data.rb +196 -0
- data/lib/docbook/output/html.rb +1450 -0
- data/lib/docbook/output/index_generator.rb +281 -0
- data/lib/docbook/output.rb +8 -0
- data/lib/docbook/services/document_builder.rb +258 -0
- data/lib/docbook/services/element_to_hash.rb +287 -0
- data/lib/docbook/services/index_generator.rb +134 -0
- data/lib/docbook/services/numbering_service.rb +186 -0
- data/lib/docbook/services/toc_generator.rb +138 -0
- data/lib/docbook/services.rb +14 -0
- data/lib/docbook/templates/document.html.liquid +20 -0
- data/lib/docbook/templates/partials/_head.liquid +39 -0
- data/lib/docbook/templates/partials/_scripts.liquid +10 -0
- data/lib/docbook/version.rb +5 -0
- data/lib/docbook/xinclude_resolver.rb +217 -0
- data/lib/docbook/xref_resolver.rb +78 -0
- data/lib/docbook.rb +17 -0
- data/sig/docbook.rbs +4 -0
- 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,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
|
+
})
|