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,116 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="ebook-topbar" :class="{ 'ebook-topbar--with-sidebar': sidebarOpen }">
|
|
3
|
+
<div class="flex items-center justify-between px-4 py-2">
|
|
4
|
+
<!-- Left: Menu button -->
|
|
5
|
+
<button
|
|
6
|
+
@click="$emit('toggle-toc')"
|
|
7
|
+
class="p-2 rounded-lg hover:bg-black/10 dark:hover:bg-white/10 text-gray-700 dark:text-gray-300 transition-colors"
|
|
8
|
+
>
|
|
9
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
10
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
|
|
11
|
+
</svg>
|
|
12
|
+
</button>
|
|
13
|
+
|
|
14
|
+
<!-- Center: Title -->
|
|
15
|
+
<div class="flex-1 text-center px-4">
|
|
16
|
+
<h1 class="text-sm font-medium text-gray-700 dark:text-gray-300 truncate">
|
|
17
|
+
{{ title }}
|
|
18
|
+
</h1>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<!-- Right: Actions -->
|
|
22
|
+
<div class="flex items-center gap-1">
|
|
23
|
+
<!-- Font settings -->
|
|
24
|
+
<button
|
|
25
|
+
@click="$emit('toggle-settings')"
|
|
26
|
+
class="p-2 rounded-lg hover:bg-black/10 dark:hover:bg-white/10 text-gray-700 dark:text-gray-300 transition-colors"
|
|
27
|
+
title="Display settings"
|
|
28
|
+
>
|
|
29
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
30
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h7"/>
|
|
31
|
+
</svg>
|
|
32
|
+
</button>
|
|
33
|
+
|
|
34
|
+
<!-- Theme toggle -->
|
|
35
|
+
<button
|
|
36
|
+
@click="cycleTheme"
|
|
37
|
+
class="p-2 rounded-lg hover:bg-black/10 dark:hover:bg-white/10 text-gray-700 dark:text-gray-300 transition-colors"
|
|
38
|
+
:title="`Theme: ${currentTheme}`"
|
|
39
|
+
>
|
|
40
|
+
<svg v-if="currentTheme === 'day'" class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
41
|
+
<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"/>
|
|
42
|
+
</svg>
|
|
43
|
+
<svg v-else-if="currentTheme === 'sepia'" class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
44
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/>
|
|
45
|
+
</svg>
|
|
46
|
+
<svg v-else-if="currentTheme === 'night'" class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
47
|
+
<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"/>
|
|
48
|
+
</svg>
|
|
49
|
+
<svg v-else class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
50
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
|
|
51
|
+
</svg>
|
|
52
|
+
</button>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
</template>
|
|
57
|
+
|
|
58
|
+
<script setup lang="ts">
|
|
59
|
+
import { computed } from 'vue'
|
|
60
|
+
import { useEbookStore, type Theme } from '@/composables/useEbookStore'
|
|
61
|
+
|
|
62
|
+
defineProps<{
|
|
63
|
+
title?: string
|
|
64
|
+
sidebarOpen?: boolean
|
|
65
|
+
}>()
|
|
66
|
+
|
|
67
|
+
defineEmits<{
|
|
68
|
+
'toggle-toc': []
|
|
69
|
+
'toggle-settings': []
|
|
70
|
+
}>()
|
|
71
|
+
|
|
72
|
+
const ebookStore = useEbookStore()
|
|
73
|
+
const currentTheme = computed(() => ebookStore.theme.value)
|
|
74
|
+
|
|
75
|
+
const themeOrder: Theme[] = ['day', 'sepia', 'night', 'oled']
|
|
76
|
+
|
|
77
|
+
function cycleTheme() {
|
|
78
|
+
const currentIndex = themeOrder.indexOf(ebookStore.theme.value)
|
|
79
|
+
const nextIndex = (currentIndex + 1) % themeOrder.length
|
|
80
|
+
ebookStore.setTheme(themeOrder[nextIndex])
|
|
81
|
+
}
|
|
82
|
+
</script>
|
|
83
|
+
|
|
84
|
+
<style scoped>
|
|
85
|
+
.ebook-topbar {
|
|
86
|
+
position: fixed;
|
|
87
|
+
top: 0;
|
|
88
|
+
left: 0;
|
|
89
|
+
right: 0;
|
|
90
|
+
z-index: 30;
|
|
91
|
+
background: rgba(255, 255, 255, 0.9);
|
|
92
|
+
backdrop-filter: blur(8px);
|
|
93
|
+
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
|
94
|
+
transition: left 0.2s ease;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
:global(.dark) .ebook-topbar {
|
|
98
|
+
background: rgba(17, 24, 39, 0.9);
|
|
99
|
+
border-bottom-color: rgba(255, 255, 255, 0.1);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.ebook-topbar--with-sidebar {
|
|
103
|
+
left: 280px;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
@media (max-width: 1023px) {
|
|
107
|
+
.ebook-topbar--with-sidebar {
|
|
108
|
+
left: 0;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.ebook-topbar button {
|
|
113
|
+
min-width: 44px;
|
|
114
|
+
min-height: 44px;
|
|
115
|
+
}
|
|
116
|
+
</style>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section :id="section.id" class="mb-12 scroll-mt-20">
|
|
3
|
+
<h1 class="text-2xl font-bold mb-6 pb-2 border-b-2 border-gray-300 dark:border-gray-600">
|
|
4
|
+
{{ section.title }}
|
|
5
|
+
</h1>
|
|
6
|
+
<BlockRenderer v-if="sectionContent" :blocks="sectionContent.blocks" />
|
|
7
|
+
<!-- Render children -->
|
|
8
|
+
<template v-if="section.children && section.children.length > 0">
|
|
9
|
+
<div v-for="child in section.children" :key="child.id">
|
|
10
|
+
<component :is="getChildComponent(child.type)" :section="child" />
|
|
11
|
+
</div>
|
|
12
|
+
</template>
|
|
13
|
+
</section>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<script setup lang="ts">
|
|
17
|
+
import { computed, markRaw } from 'vue'
|
|
18
|
+
import { useDocumentStore, type TocItem } from '@/stores/documentStore'
|
|
19
|
+
import SectionContent from '@/components/SectionContent.vue'
|
|
20
|
+
import ChapterSection from '@/components/ChapterSection.vue'
|
|
21
|
+
import AppendixSection from '@/components/AppendixSection.vue'
|
|
22
|
+
import BlockRenderer from '@/components/BlockRenderer.vue'
|
|
23
|
+
|
|
24
|
+
interface Props {
|
|
25
|
+
section: TocItem
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const props = defineProps<Props>()
|
|
29
|
+
const documentStore = useDocumentStore()
|
|
30
|
+
|
|
31
|
+
const sectionContent = computed(() => documentStore.getSectionContent(props.section.id))
|
|
32
|
+
|
|
33
|
+
function getChildComponent(type: string) {
|
|
34
|
+
switch (type) {
|
|
35
|
+
case 'chapter':
|
|
36
|
+
case 'reference':
|
|
37
|
+
return markRaw(ChapterSection)
|
|
38
|
+
case 'appendix':
|
|
39
|
+
return markRaw(AppendixSection)
|
|
40
|
+
default:
|
|
41
|
+
return markRaw(SectionContent)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
</script>
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="reference-entry mb-8 border-l-4 border-cyan-600 pl-4 py-3 bg-cyan-50/50 dark:bg-cyan-900/10 rounded-r-lg">
|
|
3
|
+
<!-- Reference meta (from refmeta - subtle metadata at top) -->
|
|
4
|
+
<template v-for="(child, idx) in block.children" :key="'meta-' + idx">
|
|
5
|
+
<span
|
|
6
|
+
v-if="child.type === 'reference_meta'"
|
|
7
|
+
class="reference-meta text-xs text-gray-400 dark:text-gray-500 block mb-1"
|
|
8
|
+
>
|
|
9
|
+
{{ child.text }}
|
|
10
|
+
</span>
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<!-- Reference badge (from refnamediv.refclass - e.g., "pi") -->
|
|
14
|
+
<template v-for="(child, idx) in block.children" :key="'badge-' + idx">
|
|
15
|
+
<span
|
|
16
|
+
v-if="child.type === 'reference_badge'"
|
|
17
|
+
class="reference-badge inline-block text-[0.65rem] font-semibold uppercase tracking-wide px-2 py-0.5 rounded-full bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400 mb-2"
|
|
18
|
+
>
|
|
19
|
+
{{ child.text }}
|
|
20
|
+
</span>
|
|
21
|
+
</template>
|
|
22
|
+
|
|
23
|
+
<!-- Reference name (from refnamediv.refname - THE HEADWORD) -->
|
|
24
|
+
<template v-for="(child, idx) in block.children" :key="'name-' + idx">
|
|
25
|
+
<h2
|
|
26
|
+
v-if="child.type === 'reference_name'"
|
|
27
|
+
class="reference-name text-2xl font-bold font-mono text-cyan-700 dark:text-cyan-400 mb-1"
|
|
28
|
+
>
|
|
29
|
+
{{ child.text }}
|
|
30
|
+
</h2>
|
|
31
|
+
</template>
|
|
32
|
+
|
|
33
|
+
<!-- Reference definition (from refnamediv.refpurpose - THE DEFINITION) -->
|
|
34
|
+
<template v-for="(child, idx) in block.children" :key="'def-' + idx">
|
|
35
|
+
<p
|
|
36
|
+
v-if="child.type === 'reference_definition'"
|
|
37
|
+
class="reference-definition text-base text-gray-600 dark:text-gray-400 italic mb-4"
|
|
38
|
+
>
|
|
39
|
+
{{ child.text }}
|
|
40
|
+
</p>
|
|
41
|
+
</template>
|
|
42
|
+
|
|
43
|
+
<!-- Description section (from refsection) -->
|
|
44
|
+
<template v-for="(child, idx) in block.children" :key="'section-' + idx">
|
|
45
|
+
<div
|
|
46
|
+
v-if="child.type === 'description_section'"
|
|
47
|
+
class="description-section mt-4 pt-4 border-t border-gray-200 dark:border-gray-700"
|
|
48
|
+
>
|
|
49
|
+
<BlockRenderer :blocks="child.children || []" />
|
|
50
|
+
</div>
|
|
51
|
+
</template>
|
|
52
|
+
</div>
|
|
53
|
+
</template>
|
|
54
|
+
|
|
55
|
+
<script setup lang="ts">
|
|
56
|
+
import type { ContentBlock } from '../stores/documentStore'
|
|
57
|
+
import BlockRenderer from './BlockRenderer.vue'
|
|
58
|
+
|
|
59
|
+
defineProps<{
|
|
60
|
+
block: ContentBlock
|
|
61
|
+
}>()
|
|
62
|
+
</script>
|
|
63
|
+
|
|
64
|
+
<style scoped>
|
|
65
|
+
.reference-entry {
|
|
66
|
+
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.reference-name {
|
|
70
|
+
font-family: 'JetBrains Mono', 'Fira Code', ui-monospace, monospace;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.reference-badge {
|
|
74
|
+
letter-spacing: 0.05em;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.description-section {
|
|
78
|
+
/* Container styling handled by parent */
|
|
79
|
+
}
|
|
80
|
+
</style>
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Teleport to="body">
|
|
3
|
+
<div
|
|
4
|
+
v-if="uiStore.searchOpen"
|
|
5
|
+
class="fixed inset-0 bg-black/50 z-50 flex items-start justify-center pt-[10vh] transition-opacity duration-200"
|
|
6
|
+
@click.self="uiStore.closeSearch"
|
|
7
|
+
>
|
|
8
|
+
<div class="w-full max-w-xl bg-white dark:bg-gray-800 rounded-xl shadow-xl overflow-hidden transition-transform duration-200">
|
|
9
|
+
<!-- Search Input -->
|
|
10
|
+
<div class="flex items-center gap-3 p-4 border-b border-gray-200 dark:border-gray-700">
|
|
11
|
+
<svg class="w-5 h-5 text-gray-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
12
|
+
<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"/>
|
|
13
|
+
</svg>
|
|
14
|
+
<input
|
|
15
|
+
ref="inputRef"
|
|
16
|
+
v-model="searchQuery"
|
|
17
|
+
type="text"
|
|
18
|
+
class="flex-1 bg-transparent border-none outline-none text-gray-900 dark:text-gray-100 placeholder-gray-400 text-base"
|
|
19
|
+
placeholder="Search headings and content..."
|
|
20
|
+
@keydown="handleKeydown"
|
|
21
|
+
/>
|
|
22
|
+
<button
|
|
23
|
+
v-if="searchQuery"
|
|
24
|
+
@click="searchQuery = ''"
|
|
25
|
+
class="p-1 rounded hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-400"
|
|
26
|
+
>
|
|
27
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
28
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
|
29
|
+
</svg>
|
|
30
|
+
</button>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<!-- Search Results -->
|
|
34
|
+
<div class="max-h-80 overflow-y-auto">
|
|
35
|
+
<div v-if="isSearching" class="p-8 text-center text-gray-500">
|
|
36
|
+
Searching...
|
|
37
|
+
</div>
|
|
38
|
+
<div v-else-if="searchQuery && results.length === 0" class="p-8 text-center text-gray-500">
|
|
39
|
+
No results found for "{{ searchQuery }}"
|
|
40
|
+
</div>
|
|
41
|
+
<div v-else-if="results.length > 0" class="p-2">
|
|
42
|
+
<a
|
|
43
|
+
v-for="(result, index) in results"
|
|
44
|
+
:key="result.id"
|
|
45
|
+
:href="'#' + result.id"
|
|
46
|
+
@click="selectResult(result)"
|
|
47
|
+
class="flex flex-col gap-1 px-3 py-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 cursor-pointer"
|
|
48
|
+
:class="{ 'bg-gray-100 dark:bg-gray-700': focusedIndex === index }"
|
|
49
|
+
@mouseenter="focusedIndex = index"
|
|
50
|
+
>
|
|
51
|
+
<div class="flex items-center gap-3">
|
|
52
|
+
<span class="text-xs px-2 py-0.5 rounded bg-gray-200 dark:bg-gray-600 text-gray-600 dark:text-gray-300">
|
|
53
|
+
{{ result.type }}
|
|
54
|
+
</span>
|
|
55
|
+
<span class="flex-1 text-gray-900 dark:text-gray-100" v-html="highlightMatch(result.title)"></span>
|
|
56
|
+
</div>
|
|
57
|
+
<div v-if="result.snippet" class="text-xs text-gray-500 dark:text-gray-400 pl-[3.5rem] line-clamp-2" v-html="highlightMatch(result.snippet)"></div>
|
|
58
|
+
</a>
|
|
59
|
+
</div>
|
|
60
|
+
<div v-else class="p-8 text-center text-gray-400 text-sm">
|
|
61
|
+
Type to search...
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<!-- Footer hints -->
|
|
66
|
+
<div class="px-4 py-3 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900">
|
|
67
|
+
<div class="flex gap-4 text-xs text-gray-400">
|
|
68
|
+
<span><kbd class="px-1.5 py-0.5 bg-gray-200 dark:bg-gray-700 rounded text-gray-600 dark:text-gray-300">↑</kbd><kbd class="px-1.5 py-0.5 bg-gray-200 dark:bg-gray-700 rounded text-gray-600 dark:text-gray-300 ml-0.5">↓</kbd> Navigate</span>
|
|
69
|
+
<span><kbd class="px-1.5 py-0.5 bg-gray-200 dark:bg-gray-700 rounded text-gray-600 dark:text-gray-300">Enter</kbd> Select</span>
|
|
70
|
+
<span><kbd class="px-1.5 py-0.5 bg-gray-200 dark:bg-gray-700 rounded text-gray-600 dark:text-gray-300">Esc</kbd> Close</span>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
</Teleport>
|
|
76
|
+
</template>
|
|
77
|
+
|
|
78
|
+
<script setup lang="ts">
|
|
79
|
+
import { ref, watch, onMounted, nextTick } from 'vue'
|
|
80
|
+
import { useDocumentStore, type TocItem, type ContentBlock } from '@/stores/documentStore'
|
|
81
|
+
import { useUiStore } from '@/stores/uiStore'
|
|
82
|
+
import FlexSearch from 'flexsearch'
|
|
83
|
+
|
|
84
|
+
const documentStore = useDocumentStore()
|
|
85
|
+
const uiStore = useUiStore()
|
|
86
|
+
|
|
87
|
+
interface SearchResult {
|
|
88
|
+
id: string
|
|
89
|
+
title: string
|
|
90
|
+
type: 'chapter' | 'appendix' | 'part' | 'section' | 'glossary' | 'bibliography' | 'index' | 'reference' | 'preface'
|
|
91
|
+
children?: TocItem[]
|
|
92
|
+
snippet?: string
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const searchQuery = ref('')
|
|
96
|
+
const results = ref<SearchResult[]>([])
|
|
97
|
+
const isSearching = ref(false)
|
|
98
|
+
const focusedIndex = ref(0)
|
|
99
|
+
const inputRef = ref<HTMLInputElement | null>(null)
|
|
100
|
+
|
|
101
|
+
// FlexSearch Document index for titles
|
|
102
|
+
const index = new (FlexSearch as any).Document({
|
|
103
|
+
document: {
|
|
104
|
+
id: 'id',
|
|
105
|
+
index: ['title'],
|
|
106
|
+
store: ['id', 'title', 'type']
|
|
107
|
+
},
|
|
108
|
+
tokenize: 'forward',
|
|
109
|
+
resolution: 9
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
function buildIndexes() {
|
|
113
|
+
const sections = documentStore.sections
|
|
114
|
+
addToIndex(sections)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function addToIndex(items: TocItem[]) {
|
|
118
|
+
items.forEach(item => {
|
|
119
|
+
index.add({
|
|
120
|
+
id: item.id,
|
|
121
|
+
title: item.title,
|
|
122
|
+
type: item.type
|
|
123
|
+
})
|
|
124
|
+
if (item.children && item.children.length > 0) {
|
|
125
|
+
addToIndex(item.children)
|
|
126
|
+
}
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function extractTextFromBlocks(blocks: ContentBlock[]): string {
|
|
131
|
+
return blocks.map(block => {
|
|
132
|
+
let text = block.text || ''
|
|
133
|
+
if (block.children) {
|
|
134
|
+
text += ' ' + extractTextFromBlocks(block.children)
|
|
135
|
+
}
|
|
136
|
+
return text
|
|
137
|
+
}).join(' ')
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function searchContent(query: string): SearchResult[] {
|
|
141
|
+
const content = documentStore.content
|
|
142
|
+
const searchResults: SearchResult[] = []
|
|
143
|
+
const lowerQuery = query.toLowerCase()
|
|
144
|
+
|
|
145
|
+
for (const [id, sectionContent] of Object.entries(content)) {
|
|
146
|
+
const text = extractTextFromBlocks(sectionContent.blocks || []).toLowerCase()
|
|
147
|
+
if (text.includes(lowerQuery)) {
|
|
148
|
+
// Find the snippet around the match
|
|
149
|
+
const matchPos = text.indexOf(lowerQuery)
|
|
150
|
+
const start = Math.max(0, matchPos - 50)
|
|
151
|
+
const end = Math.min(text.length, matchPos + query.length + 100)
|
|
152
|
+
let snippet = extractTextFromBlocks(sectionContent.blocks || []).substring(start, end)
|
|
153
|
+
if (start > 0) snippet = '...' + snippet
|
|
154
|
+
if (end < text.length) snippet = snippet + '...'
|
|
155
|
+
|
|
156
|
+
// Get section info from TOC
|
|
157
|
+
const section = findSection(documentStore.sections, id)
|
|
158
|
+
searchResults.push({
|
|
159
|
+
id,
|
|
160
|
+
title: section?.title || id,
|
|
161
|
+
type: (section?.type || 'section') as SearchResult['type'],
|
|
162
|
+
snippet
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return searchResults
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function findSection(items: TocItem[], id: string): TocItem | null {
|
|
170
|
+
for (const item of items) {
|
|
171
|
+
if (item.id === id) return item
|
|
172
|
+
if (item.children && item.children.length > 0) {
|
|
173
|
+
const found = findSection(item.children, id)
|
|
174
|
+
if (found) return found
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return null
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function search() {
|
|
181
|
+
if (!searchQuery.value.trim()) {
|
|
182
|
+
results.value = []
|
|
183
|
+
return
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
isSearching.value = true
|
|
187
|
+
|
|
188
|
+
// Search titles with FlexSearch
|
|
189
|
+
const searchResults = index.search(searchQuery.value, { limit: 30, enrich: true })
|
|
190
|
+
|
|
191
|
+
const titleResults: Map<string, TocItem> = new Map()
|
|
192
|
+
const seen = new Set<string>()
|
|
193
|
+
|
|
194
|
+
if (Array.isArray(searchResults)) {
|
|
195
|
+
searchResults.forEach((field: { result: Array<{ id: string; doc?: TocItem }> }) => {
|
|
196
|
+
if (field.result && Array.isArray(field.result)) {
|
|
197
|
+
field.result.forEach((r: { id: string; doc?: TocItem }) => {
|
|
198
|
+
if (r.doc && !seen.has(r.id)) {
|
|
199
|
+
seen.add(r.id)
|
|
200
|
+
titleResults.set(r.id, r.doc)
|
|
201
|
+
}
|
|
202
|
+
})
|
|
203
|
+
}
|
|
204
|
+
})
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Search full content
|
|
208
|
+
const contentResults = searchContent(searchQuery.value)
|
|
209
|
+
|
|
210
|
+
// Combine results, prioritizing title matches
|
|
211
|
+
const combined: SearchResult[] = []
|
|
212
|
+
|
|
213
|
+
// First add title matches
|
|
214
|
+
titleResults.forEach((tocItem) => {
|
|
215
|
+
combined.push({
|
|
216
|
+
id: tocItem.id,
|
|
217
|
+
title: tocItem.title,
|
|
218
|
+
type: tocItem.type,
|
|
219
|
+
children: tocItem.children
|
|
220
|
+
})
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
// Then add content-only matches
|
|
224
|
+
contentResults.forEach((cr) => {
|
|
225
|
+
if (!seen.has(cr.id)) {
|
|
226
|
+
seen.add(cr.id)
|
|
227
|
+
combined.push(cr)
|
|
228
|
+
}
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
results.value = combined.slice(0, 50)
|
|
232
|
+
isSearching.value = false
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Debounced search
|
|
236
|
+
let debounceTimer: ReturnType<typeof setTimeout> | null = null
|
|
237
|
+
watch(searchQuery, () => {
|
|
238
|
+
if (debounceTimer) clearTimeout(debounceTimer)
|
|
239
|
+
debounceTimer = setTimeout(search, 150)
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
// Watch for document data changes to rebuild indexes
|
|
243
|
+
watch(
|
|
244
|
+
() => documentStore.documentData,
|
|
245
|
+
() => {
|
|
246
|
+
buildIndexes()
|
|
247
|
+
},
|
|
248
|
+
{ immediate: true }
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
onMounted(() => {
|
|
252
|
+
nextTick(() => inputRef.value?.focus())
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
function handleKeydown(e: KeyboardEvent) {
|
|
256
|
+
if (e.key === 'ArrowDown') {
|
|
257
|
+
e.preventDefault()
|
|
258
|
+
focusedIndex.value = Math.min(focusedIndex.value + 1, results.value.length - 1)
|
|
259
|
+
} else if (e.key === 'ArrowUp') {
|
|
260
|
+
e.preventDefault()
|
|
261
|
+
focusedIndex.value = Math.max(focusedIndex.value - 1, 0)
|
|
262
|
+
} else if (e.key === 'Enter' && results.value[focusedIndex.value]) {
|
|
263
|
+
e.preventDefault()
|
|
264
|
+
selectResult(results.value[focusedIndex.value])
|
|
265
|
+
} else if (e.key === 'Escape') {
|
|
266
|
+
uiStore.closeSearch()
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function selectResult(result: SearchResult) {
|
|
271
|
+
// Scroll to section and close search
|
|
272
|
+
const element = document.getElementById(result.id)
|
|
273
|
+
if (element) {
|
|
274
|
+
element.scrollIntoView({ behavior: 'smooth' })
|
|
275
|
+
uiStore.closeSearch()
|
|
276
|
+
searchQuery.value = ''
|
|
277
|
+
results.value = []
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function highlightMatch(text: string): string {
|
|
282
|
+
if (!searchQuery.value) return text
|
|
283
|
+
const regex = new RegExp(`(${searchQuery.value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi')
|
|
284
|
+
return text.replace(regex, '<mark class="bg-yellow-200 dark:bg-yellow-600 rounded px-0.5">$1</mark>')
|
|
285
|
+
}
|
|
286
|
+
</script>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section :id="section.id" class="mb-8 scroll-mt-20">
|
|
3
|
+
<h3 class="text-lg font-semibold mb-3 text-gray-800 dark:text-gray-200">
|
|
4
|
+
<span v-if="numbering" class="text-gray-500 dark:text-gray-400 mr-2">{{ numbering }}</span>
|
|
5
|
+
{{ section.title }}
|
|
6
|
+
</h3>
|
|
7
|
+
<BlockRenderer v-if="sectionContent" :blocks="sectionContent.blocks" />
|
|
8
|
+
<!-- Render children -->
|
|
9
|
+
<template v-if="section.children && section.children.length > 0">
|
|
10
|
+
<div v-for="child in section.children" :key="child.id" class="ml-4">
|
|
11
|
+
<SectionContent :section="child" />
|
|
12
|
+
</div>
|
|
13
|
+
</template>
|
|
14
|
+
</section>
|
|
15
|
+
</template>
|
|
16
|
+
|
|
17
|
+
<script setup lang="ts">
|
|
18
|
+
import { computed } from 'vue'
|
|
19
|
+
import { useDocumentStore, type TocItem } from '@/stores/documentStore'
|
|
20
|
+
import BlockRenderer from '@/components/BlockRenderer.vue'
|
|
21
|
+
|
|
22
|
+
interface Props {
|
|
23
|
+
section: TocItem
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const props = defineProps<Props>()
|
|
27
|
+
const documentStore = useDocumentStore()
|
|
28
|
+
|
|
29
|
+
const numbering = computed(() => documentStore.getNumbering(props.section.id))
|
|
30
|
+
const sectionContent = computed(() => documentStore.getSectionContent(props.section.id))
|
|
31
|
+
</script>
|