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,236 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Teleport to="body">
|
|
3
|
+
<Transition name="modal">
|
|
4
|
+
<div
|
|
5
|
+
v-if="isSettingsOpen"
|
|
6
|
+
class="fixed inset-0 z-50 flex items-center justify-center"
|
|
7
|
+
@click.self="ebookStore.toggleSettings"
|
|
8
|
+
>
|
|
9
|
+
<div class="absolute inset-0 bg-black/50 backdrop-blur-sm" @click="ebookStore.toggleSettings"></div>
|
|
10
|
+
|
|
11
|
+
<div class="relative bg-white dark:bg-gray-900 rounded-xl shadow-2xl w-full max-w-md mx-4 overflow-hidden">
|
|
12
|
+
<!-- Header -->
|
|
13
|
+
<div class="flex items-center justify-between px-6 py-4 border-b border-gray-200 dark:border-gray-700">
|
|
14
|
+
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">Display Settings</h2>
|
|
15
|
+
<button
|
|
16
|
+
@click="ebookStore.toggleSettings"
|
|
17
|
+
class="p-1 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 text-gray-500"
|
|
18
|
+
>
|
|
19
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
20
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
|
21
|
+
</svg>
|
|
22
|
+
</button>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<!-- Content -->
|
|
26
|
+
<div class="px-6 py-5 space-y-6">
|
|
27
|
+
<!-- Reading Mode -->
|
|
28
|
+
<div>
|
|
29
|
+
<label class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-3 block">Reading Mode</label>
|
|
30
|
+
<div class="grid grid-cols-4 gap-2">
|
|
31
|
+
<button
|
|
32
|
+
v-for="mode in readingModes"
|
|
33
|
+
:key="mode.value"
|
|
34
|
+
@click="ebookStore.setReadingMode(mode.value)"
|
|
35
|
+
:class="[
|
|
36
|
+
'flex flex-col items-center justify-center p-3 rounded-lg border-2 transition-all',
|
|
37
|
+
currentReadingMode === mode.value
|
|
38
|
+
? 'border-cyan-500 bg-cyan-50 dark:bg-cyan-900/30 text-cyan-700 dark:text-cyan-300'
|
|
39
|
+
: 'border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600 text-gray-600 dark:text-gray-400'
|
|
40
|
+
]"
|
|
41
|
+
>
|
|
42
|
+
<span class="text-xl mb-1">{{ mode.icon }}</span>
|
|
43
|
+
<span class="text-xs">{{ mode.label }}</span>
|
|
44
|
+
</button>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<!-- Divider -->
|
|
49
|
+
<div class="border-t border-gray-200 dark:border-gray-700"></div>
|
|
50
|
+
|
|
51
|
+
<!-- Theme -->
|
|
52
|
+
<div>
|
|
53
|
+
<label class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-3 block">Theme</label>
|
|
54
|
+
<div class="grid grid-cols-4 gap-2">
|
|
55
|
+
<button
|
|
56
|
+
v-for="t in themes"
|
|
57
|
+
:key="t.value"
|
|
58
|
+
@click="ebookStore.setTheme(t.value)"
|
|
59
|
+
:class="[
|
|
60
|
+
'flex flex-col items-center justify-center p-3 rounded-lg border-2 transition-all',
|
|
61
|
+
currentTheme === t.value
|
|
62
|
+
? 'border-cyan-500 bg-cyan-50 dark:bg-cyan-900/30'
|
|
63
|
+
: 'border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600'
|
|
64
|
+
]"
|
|
65
|
+
>
|
|
66
|
+
<div :class="['w-8 h-8 rounded-full mb-1', t.preview]"></div>
|
|
67
|
+
<span class="text-xs" :class="t.textClass">{{ t.label }}</span>
|
|
68
|
+
</button>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<!-- Divider -->
|
|
73
|
+
<div class="border-t border-gray-200 dark:border-gray-700"></div>
|
|
74
|
+
|
|
75
|
+
<!-- Font Size -->
|
|
76
|
+
<div>
|
|
77
|
+
<div class="flex items-center justify-between mb-2">
|
|
78
|
+
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">Font Size</label>
|
|
79
|
+
<span class="text-sm text-gray-500 dark:text-gray-400">{{ currentFontSize }}px</span>
|
|
80
|
+
</div>
|
|
81
|
+
<input
|
|
82
|
+
type="range"
|
|
83
|
+
min="12"
|
|
84
|
+
max="32"
|
|
85
|
+
:value="currentFontSize"
|
|
86
|
+
@input="ebookStore.setFontSize(Number(($event.target as HTMLInputElement).value))"
|
|
87
|
+
class="w-full h-2 bg-gray-200 dark:bg-gray-700 rounded-lg appearance-none cursor-pointer accent-cyan-500"
|
|
88
|
+
/>
|
|
89
|
+
<div class="flex justify-between text-xs text-gray-400 mt-1">
|
|
90
|
+
<span>12px</span>
|
|
91
|
+
<span>32px</span>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
<!-- Font Weight -->
|
|
96
|
+
<div>
|
|
97
|
+
<div class="flex items-center justify-between mb-2">
|
|
98
|
+
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">Font Weight</label>
|
|
99
|
+
<span class="text-sm text-gray-500 dark:text-gray-400">{{ currentFontWeight }}</span>
|
|
100
|
+
</div>
|
|
101
|
+
<input
|
|
102
|
+
type="range"
|
|
103
|
+
min="300"
|
|
104
|
+
max="700"
|
|
105
|
+
step="100"
|
|
106
|
+
:value="currentFontWeight"
|
|
107
|
+
@input="ebookStore.setFontWeight(Number(($event.target as HTMLInputElement).value))"
|
|
108
|
+
class="w-full h-2 bg-gray-200 dark:bg-gray-700 rounded-lg appearance-none cursor-pointer accent-cyan-500"
|
|
109
|
+
/>
|
|
110
|
+
<div class="flex justify-between text-xs text-gray-400 mt-1">
|
|
111
|
+
<span>Light</span>
|
|
112
|
+
<span>Bold</span>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
<!-- Line Height -->
|
|
117
|
+
<div>
|
|
118
|
+
<div class="flex items-center justify-between mb-2">
|
|
119
|
+
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">Line Height</label>
|
|
120
|
+
<span class="text-sm text-gray-500 dark:text-gray-400">{{ currentLineHeight }}</span>
|
|
121
|
+
</div>
|
|
122
|
+
<input
|
|
123
|
+
type="range"
|
|
124
|
+
min="1.2"
|
|
125
|
+
max="2.0"
|
|
126
|
+
step="0.1"
|
|
127
|
+
:value="currentLineHeight"
|
|
128
|
+
@input="ebookStore.setLineHeight(Number(($event.target as HTMLInputElement).value))"
|
|
129
|
+
class="w-full h-2 bg-gray-200 dark:bg-gray-700 rounded-lg appearance-none cursor-pointer accent-cyan-500"
|
|
130
|
+
/>
|
|
131
|
+
<div class="flex justify-between text-xs text-gray-400 mt-1">
|
|
132
|
+
<span>Compact</span>
|
|
133
|
+
<span>Spacious</span>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
<!-- Margin -->
|
|
138
|
+
<div>
|
|
139
|
+
<div class="flex items-center justify-between mb-2">
|
|
140
|
+
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">Margins</label>
|
|
141
|
+
<span class="text-sm text-gray-500 dark:text-gray-400">{{ currentMargin }}px</span>
|
|
142
|
+
</div>
|
|
143
|
+
<input
|
|
144
|
+
type="range"
|
|
145
|
+
min="16"
|
|
146
|
+
max="96"
|
|
147
|
+
step="8"
|
|
148
|
+
:value="currentMargin"
|
|
149
|
+
@input="ebookStore.setMargin(Number(($event.target as HTMLInputElement).value))"
|
|
150
|
+
class="w-full h-2 bg-gray-200 dark:bg-gray-700 rounded-lg appearance-none cursor-pointer accent-cyan-500"
|
|
151
|
+
/>
|
|
152
|
+
<div class="flex justify-between text-xs text-gray-400 mt-1">
|
|
153
|
+
<span>Narrow</span>
|
|
154
|
+
<span>Wide</span>
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
<!-- Footer -->
|
|
160
|
+
<div class="px-6 py-4 bg-gray-50 dark:bg-gray-800 border-t border-gray-200 dark:border-gray-700">
|
|
161
|
+
<button
|
|
162
|
+
@click="resetToDefaults"
|
|
163
|
+
class="w-full py-2 text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors"
|
|
164
|
+
>
|
|
165
|
+
Reset to Defaults
|
|
166
|
+
</button>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
</Transition>
|
|
171
|
+
</Teleport>
|
|
172
|
+
</template>
|
|
173
|
+
|
|
174
|
+
<script setup lang="ts">
|
|
175
|
+
import { computed } from 'vue'
|
|
176
|
+
import { useEbookStore, type ReadingMode, type Theme } from '@/composables/useEbookStore'
|
|
177
|
+
|
|
178
|
+
const ebookStore = useEbookStore()
|
|
179
|
+
|
|
180
|
+
// Settings open state - use local computed to ensure reactivity
|
|
181
|
+
const isSettingsOpen = computed(() => ebookStore.settingsOpen.value)
|
|
182
|
+
|
|
183
|
+
// Computed properties for template comparisons
|
|
184
|
+
const currentReadingMode = computed(() => ebookStore.readingMode.value)
|
|
185
|
+
const currentTheme = computed(() => ebookStore.theme.value)
|
|
186
|
+
const currentFontSize = computed(() => ebookStore.fontSize.value)
|
|
187
|
+
const currentFontWeight = computed(() => ebookStore.fontWeight.value)
|
|
188
|
+
const currentLineHeight = computed(() => ebookStore.lineHeight.value.toFixed(1))
|
|
189
|
+
const currentMargin = computed(() => ebookStore.margin.value)
|
|
190
|
+
|
|
191
|
+
const readingModes = [
|
|
192
|
+
{ value: 'scroll' as ReadingMode, label: 'Scroll', icon: '📜' },
|
|
193
|
+
{ value: 'page' as ReadingMode, label: 'Page', icon: '📄' },
|
|
194
|
+
{ value: 'chapter' as ReadingMode, label: 'Chapter', icon: '📑' },
|
|
195
|
+
{ value: 'reference' as ReadingMode, label: 'Cards', icon: '💳' },
|
|
196
|
+
]
|
|
197
|
+
|
|
198
|
+
const themes = [
|
|
199
|
+
{ value: 'day' as Theme, label: 'Day', preview: 'bg-white border border-gray-300', textClass: 'text-gray-700' },
|
|
200
|
+
{ value: 'sepia' as Theme, label: 'Sepia', preview: 'bg-amber-100', textClass: 'text-amber-800' },
|
|
201
|
+
{ value: 'night' as Theme, label: 'Night', preview: 'bg-gray-800', textClass: 'text-gray-300' },
|
|
202
|
+
{ value: 'oled' as Theme, label: 'OLED', preview: 'bg-black border border-gray-600', textClass: 'text-gray-300' },
|
|
203
|
+
]
|
|
204
|
+
|
|
205
|
+
function resetToDefaults() {
|
|
206
|
+
ebookStore.setFontSize(18)
|
|
207
|
+
ebookStore.setFontWeight(400)
|
|
208
|
+
ebookStore.setLineHeight(1.6)
|
|
209
|
+
ebookStore.setMargin(48)
|
|
210
|
+
ebookStore.setTheme('day')
|
|
211
|
+
ebookStore.setReadingMode('scroll')
|
|
212
|
+
}
|
|
213
|
+
</script>
|
|
214
|
+
|
|
215
|
+
<style scoped>
|
|
216
|
+
.modal-enter-active,
|
|
217
|
+
.modal-leave-active {
|
|
218
|
+
transition: opacity 0.2s ease;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.modal-enter-from,
|
|
222
|
+
.modal-leave-to {
|
|
223
|
+
opacity: 0;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.modal-enter-active > div:last-child,
|
|
227
|
+
.modal-leave-active > div:last-child {
|
|
228
|
+
transition: transform 0.2s ease, opacity 0.2s ease;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.modal-enter-from > div:last-child,
|
|
232
|
+
.modal-leave-to > div:last-child {
|
|
233
|
+
transform: scale(0.95);
|
|
234
|
+
opacity: 0;
|
|
235
|
+
}
|
|
236
|
+
</style>
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<li>
|
|
3
|
+
<!-- Item with children (collapsible) -->
|
|
4
|
+
<template v-if="item.children && item.children.length > 0">
|
|
5
|
+
<div class="flex items-start gap-1">
|
|
6
|
+
<button
|
|
7
|
+
@click="toggle"
|
|
8
|
+
class="flex-shrink-0 w-6 h-6 flex items-center justify-center p-1 text-sm text-left rounded-md hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors self-center"
|
|
9
|
+
:class="buttonClass"
|
|
10
|
+
>
|
|
11
|
+
<svg
|
|
12
|
+
class="w-3.5 h-3.5 transition-transform"
|
|
13
|
+
:class="{ 'rotate-90': isOpen }"
|
|
14
|
+
fill="none"
|
|
15
|
+
stroke="currentColor"
|
|
16
|
+
viewBox="0 0 24 24"
|
|
17
|
+
>
|
|
18
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
|
|
19
|
+
</svg>
|
|
20
|
+
</button>
|
|
21
|
+
<a
|
|
22
|
+
:href="'#' + item.id"
|
|
23
|
+
class="flex items-center gap-2 py-1 px-1 text-sm rounded-md hover:bg-gray-200 dark:hover:bg-gray-700 hover:text-gray-900 dark:hover:text-gray-100 transition-colors flex-grow truncate"
|
|
24
|
+
:class="linkClass"
|
|
25
|
+
>
|
|
26
|
+
<span v-if="typeLabel" class="w-8 text-center text-xs px-1 py-0.5 rounded flex-shrink-0" :class="typeBadgeClass">{{ typeLabel }}</span>
|
|
27
|
+
<span class="w-10 text-xs text-gray-500 dark:text-gray-400 flex-shrink-0 font-mono text-right">{{ getNumber(item.id) }}</span>
|
|
28
|
+
<span class="truncate">{{ item.title }}</span>
|
|
29
|
+
</a>
|
|
30
|
+
</div>
|
|
31
|
+
<ul v-if="isOpen" class="pl-4 mt-0.5 space-y-0.5 border-l border-gray-200 dark:border-gray-700">
|
|
32
|
+
<TocTreeItem
|
|
33
|
+
v-for="child in item.children"
|
|
34
|
+
:key="child.id"
|
|
35
|
+
:item="child"
|
|
36
|
+
:depth="depth + 1"
|
|
37
|
+
/>
|
|
38
|
+
</ul>
|
|
39
|
+
</template>
|
|
40
|
+
|
|
41
|
+
<!-- Leaf item (no children) -->
|
|
42
|
+
<a
|
|
43
|
+
v-else
|
|
44
|
+
:href="'#' + item.id"
|
|
45
|
+
class="flex items-center gap-2 py-1 px-1 pl-5 text-sm rounded-md hover:bg-gray-200 dark:hover:bg-gray-700 hover:text-gray-900 dark:hover:text-gray-100 transition-colors truncate"
|
|
46
|
+
:class="linkClass"
|
|
47
|
+
>
|
|
48
|
+
<span v-if="typeLabel" class="w-8 text-center text-xs px-1 py-0.5 rounded flex-shrink-0" :class="typeBadgeClass">{{ typeLabel }}</span>
|
|
49
|
+
<span class="w-10 text-xs text-gray-500 dark:text-gray-400 flex-shrink-0 font-mono text-right">{{ getNumber(item.id) }}</span>
|
|
50
|
+
<span class="truncate">{{ item.title }}</span>
|
|
51
|
+
</a>
|
|
52
|
+
</li>
|
|
53
|
+
</template>
|
|
54
|
+
|
|
55
|
+
<script setup lang="ts">
|
|
56
|
+
import { ref, computed } from 'vue'
|
|
57
|
+
import { useDocumentStore, type TocItem } from '@/stores/documentStore'
|
|
58
|
+
import { useUiStore } from '@/stores/uiStore'
|
|
59
|
+
|
|
60
|
+
interface Props {
|
|
61
|
+
item: TocItem
|
|
62
|
+
depth: number
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const props = defineProps<Props>()
|
|
66
|
+
const documentStore = useDocumentStore()
|
|
67
|
+
const uiStore = useUiStore()
|
|
68
|
+
const isOpen = ref(props.depth <= 1)
|
|
69
|
+
|
|
70
|
+
const typeLabel = computed(() => {
|
|
71
|
+
switch (props.item.type) {
|
|
72
|
+
case 'part': return 'Pt'
|
|
73
|
+
case 'chapter': return 'Ch'
|
|
74
|
+
case 'appendix': return 'App'
|
|
75
|
+
case 'section': return ''
|
|
76
|
+
case 'glossary': return 'Gl'
|
|
77
|
+
case 'bibliography': return 'Bib'
|
|
78
|
+
case 'index': return 'Idx'
|
|
79
|
+
case 'preface': return 'Pref'
|
|
80
|
+
case 'reference': return 'Ref'
|
|
81
|
+
default: return ''
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
const typeBadgeClass = computed(() => {
|
|
86
|
+
switch (props.item.type) {
|
|
87
|
+
case 'part':
|
|
88
|
+
return 'bg-purple-200 dark:bg-purple-800 text-purple-800 dark:text-purple-200 font-medium'
|
|
89
|
+
case 'chapter':
|
|
90
|
+
return 'bg-blue-200 dark:bg-blue-800 text-blue-800 dark:text-blue-200 font-medium'
|
|
91
|
+
case 'appendix':
|
|
92
|
+
return 'bg-green-200 dark:bg-green-800 text-green-800 dark:text-green-200 font-medium'
|
|
93
|
+
case 'section':
|
|
94
|
+
return 'bg-gray-100 dark:bg-gray-700 text-gray-500 dark:text-gray-400'
|
|
95
|
+
case 'glossary':
|
|
96
|
+
return 'bg-yellow-200 dark:bg-yellow-800 text-yellow-800 dark:text-yellow-200 font-medium'
|
|
97
|
+
case 'bibliography':
|
|
98
|
+
return 'bg-red-200 dark:bg-red-800 text-red-800 dark:text-red-200 font-medium'
|
|
99
|
+
case 'index':
|
|
100
|
+
return 'bg-indigo-200 dark:bg-indigo-800 text-indigo-800 dark:text-indigo-200 font-medium'
|
|
101
|
+
case 'preface':
|
|
102
|
+
return 'bg-orange-200 dark:bg-orange-800 text-orange-800 dark:text-orange-200 font-medium'
|
|
103
|
+
case 'reference':
|
|
104
|
+
return 'bg-cyan-200 dark:bg-cyan-800 text-cyan-800 dark:text-cyan-200 font-medium'
|
|
105
|
+
default:
|
|
106
|
+
return 'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400'
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
const buttonClass = computed(() => {
|
|
111
|
+
if (props.item.type === 'part') {
|
|
112
|
+
return 'font-semibold text-gray-800 dark:text-gray-100'
|
|
113
|
+
}
|
|
114
|
+
return 'text-gray-700 dark:text-gray-300'
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
const linkClass = computed(() => {
|
|
118
|
+
const isActive = uiStore.activeSectionId === props.item.id
|
|
119
|
+
if (isActive) {
|
|
120
|
+
return 'bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 font-medium'
|
|
121
|
+
}
|
|
122
|
+
if (props.item.type === 'part') {
|
|
123
|
+
return 'font-semibold text-gray-800 dark:text-gray-100'
|
|
124
|
+
}
|
|
125
|
+
return 'text-gray-600 dark:text-gray-400'
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
function toggle() {
|
|
129
|
+
isOpen.value = !isOpen.value
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function getNumber(id: string): string {
|
|
133
|
+
return documentStore.getNumbering(id)
|
|
134
|
+
}
|
|
135
|
+
</script>
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { reactive, computed, watch } from 'vue'
|
|
2
|
+
|
|
3
|
+
export type ReadingMode = 'scroll' | 'page' | 'chapter' | 'reference'
|
|
4
|
+
export type Theme = 'day' | 'sepia' | 'night' | 'oled'
|
|
5
|
+
|
|
6
|
+
interface EbookState {
|
|
7
|
+
readingMode: ReadingMode
|
|
8
|
+
fontSize: number
|
|
9
|
+
fontWeight: number
|
|
10
|
+
lineHeight: number
|
|
11
|
+
margin: number
|
|
12
|
+
theme: Theme
|
|
13
|
+
uiVisible: boolean
|
|
14
|
+
tocOpen: boolean
|
|
15
|
+
settingsOpen: boolean
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const STORAGE_KEY = 'docbook_ebook_preferences'
|
|
19
|
+
|
|
20
|
+
// Default state
|
|
21
|
+
const defaultState: EbookState = {
|
|
22
|
+
readingMode: 'scroll',
|
|
23
|
+
fontSize: 18,
|
|
24
|
+
fontWeight: 400,
|
|
25
|
+
lineHeight: 1.6,
|
|
26
|
+
margin: 48,
|
|
27
|
+
theme: 'day',
|
|
28
|
+
uiVisible: true,
|
|
29
|
+
tocOpen: false,
|
|
30
|
+
settingsOpen: false
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Load from localStorage
|
|
34
|
+
function loadPreferences(): Partial<EbookState> {
|
|
35
|
+
try {
|
|
36
|
+
const stored = localStorage.getItem(STORAGE_KEY)
|
|
37
|
+
if (stored) {
|
|
38
|
+
return JSON.parse(stored)
|
|
39
|
+
}
|
|
40
|
+
} catch (e) {
|
|
41
|
+
console.warn('Failed to load preferences:', e)
|
|
42
|
+
}
|
|
43
|
+
return {}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Save to localStorage
|
|
47
|
+
function savePreferences(state: EbookState) {
|
|
48
|
+
try {
|
|
49
|
+
const { uiVisible, tocOpen, settingsOpen, ...persisted } = state
|
|
50
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(persisted))
|
|
51
|
+
} catch (e) {
|
|
52
|
+
console.warn('Failed to save preferences:', e)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Global reactive state - settingsOpen ALWAYS starts false regardless of localStorage
|
|
57
|
+
const loadedPrefs = loadPreferences()
|
|
58
|
+
// Ensure settingsOpen is never true from localStorage
|
|
59
|
+
delete loadedPrefs.settingsOpen
|
|
60
|
+
const state = reactive<EbookState>(Object.assign({}, defaultState, loadedPrefs, { settingsOpen: false }))
|
|
61
|
+
|
|
62
|
+
// Auto-save when persisted fields change (not deep watch for performance)
|
|
63
|
+
watch(
|
|
64
|
+
() => ({
|
|
65
|
+
readingMode: state.readingMode,
|
|
66
|
+
fontSize: state.fontSize,
|
|
67
|
+
fontWeight: state.fontWeight,
|
|
68
|
+
lineHeight: state.lineHeight,
|
|
69
|
+
margin: state.margin,
|
|
70
|
+
theme: state.theme
|
|
71
|
+
}),
|
|
72
|
+
() => savePreferences(state),
|
|
73
|
+
{ immediate: true }
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
// Theme application function - applies Tailwind dark class based on theme
|
|
77
|
+
function applyTheme() {
|
|
78
|
+
const isDark = state.theme === 'night' || state.theme === 'oled'
|
|
79
|
+
document.documentElement.classList.toggle('dark', isDark)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Apply theme on load and when theme changes
|
|
83
|
+
applyTheme()
|
|
84
|
+
watch(() => state.theme, applyTheme)
|
|
85
|
+
|
|
86
|
+
export function useEbookStore() {
|
|
87
|
+
// Computed getters - these ARE reactive and will auto-unwrap in templates
|
|
88
|
+
const readingMode = computed(() => state.readingMode)
|
|
89
|
+
const fontSize = computed(() => state.fontSize)
|
|
90
|
+
const fontWeight = computed(() => state.fontWeight)
|
|
91
|
+
const lineHeight = computed(() => state.lineHeight)
|
|
92
|
+
const margin = computed(() => state.margin)
|
|
93
|
+
const theme = computed(() => state.theme)
|
|
94
|
+
const uiVisible = computed(() => state.uiVisible)
|
|
95
|
+
const tocOpen = computed(() => state.tocOpen)
|
|
96
|
+
const settingsOpen = computed(() => state.settingsOpen)
|
|
97
|
+
|
|
98
|
+
// Actions
|
|
99
|
+
function setReadingMode(mode: ReadingMode) {
|
|
100
|
+
state.readingMode = mode
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function setFontSize(size: number) {
|
|
104
|
+
state.fontSize = Math.max(12, Math.min(32, size))
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function setFontWeight(weight: number) {
|
|
108
|
+
state.fontWeight = Math.max(300, Math.min(700, weight))
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function setLineHeight(height: number) {
|
|
112
|
+
state.lineHeight = Math.max(1.2, Math.min(2.0, height))
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function setMargin(m: number) {
|
|
116
|
+
state.margin = Math.max(16, Math.min(96, m))
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function setTheme(t: Theme) {
|
|
120
|
+
state.theme = t
|
|
121
|
+
applyTheme()
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function setUiVisible(visible: boolean) {
|
|
125
|
+
state.uiVisible = visible
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function toggleToc() {
|
|
129
|
+
state.tocOpen = !state.tocOpen
|
|
130
|
+
if (state.tocOpen) {
|
|
131
|
+
state.settingsOpen = false
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function toggleSettings() {
|
|
136
|
+
state.settingsOpen = !state.settingsOpen
|
|
137
|
+
if (state.settingsOpen) {
|
|
138
|
+
state.tocOpen = false
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function closeAll() {
|
|
143
|
+
state.tocOpen = false
|
|
144
|
+
state.settingsOpen = false
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Theme CSS class
|
|
148
|
+
function getThemeClass(): string {
|
|
149
|
+
return `theme-${state.theme}`
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Generate CSS variables object
|
|
153
|
+
function getCssVariables(): Record<string, string | number> {
|
|
154
|
+
return {
|
|
155
|
+
'--ebook-font-size': `${state.fontSize}px`,
|
|
156
|
+
'--ebook-font-weight': state.fontWeight,
|
|
157
|
+
'--ebook-line-height': state.lineHeight,
|
|
158
|
+
'--ebook-margin': `${state.margin}px`,
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
// Computed getters (reactive, auto-unwrap in templates)
|
|
164
|
+
readingMode,
|
|
165
|
+
fontSize,
|
|
166
|
+
fontWeight,
|
|
167
|
+
lineHeight,
|
|
168
|
+
margin,
|
|
169
|
+
theme,
|
|
170
|
+
uiVisible,
|
|
171
|
+
tocOpen,
|
|
172
|
+
settingsOpen,
|
|
173
|
+
|
|
174
|
+
// Actions
|
|
175
|
+
setReadingMode,
|
|
176
|
+
setFontSize,
|
|
177
|
+
setFontWeight,
|
|
178
|
+
setLineHeight,
|
|
179
|
+
setMargin,
|
|
180
|
+
setTheme,
|
|
181
|
+
applyTheme,
|
|
182
|
+
setUiVisible,
|
|
183
|
+
toggleToc,
|
|
184
|
+
toggleSettings,
|
|
185
|
+
closeAll,
|
|
186
|
+
|
|
187
|
+
// Utilities
|
|
188
|
+
getThemeClass,
|
|
189
|
+
getCssVariables
|
|
190
|
+
}
|
|
191
|
+
}
|