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,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "docbook-frontend",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "vite build",
|
|
8
|
+
"dev": "vite",
|
|
9
|
+
"typecheck": "vue-tsc --noEmit"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"flexsearch": "^0.7.43",
|
|
13
|
+
"pinia": "^2.1.7",
|
|
14
|
+
"vue": "^3.4.21"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@vitejs/plugin-vue": "^5.0.4",
|
|
18
|
+
"typescript": "^5.4.0",
|
|
19
|
+
"vite": "^5.2.0",
|
|
20
|
+
"vue-tsc": "^2.0.6"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<EbookContainer :class="['h-screen overflow-hidden', ebookStore.getThemeClass()]" :style="ebookStore.getCssVariables()">
|
|
3
|
+
<!-- Mobile overlay -->
|
|
4
|
+
<div
|
|
5
|
+
v-if="uiStore.sidebarOpen"
|
|
6
|
+
@click="uiStore.closeSidebar"
|
|
7
|
+
class="fixed inset-0 bg-black/50 z-40 lg:hidden"
|
|
8
|
+
></div>
|
|
9
|
+
|
|
10
|
+
<AppSidebar />
|
|
11
|
+
|
|
12
|
+
<!-- Ebook Top Bar -->
|
|
13
|
+
<EbookTopBar
|
|
14
|
+
:title="documentStore.title"
|
|
15
|
+
:sidebar-open="uiStore.sidebarOpen"
|
|
16
|
+
@toggle-toc="toggleToc"
|
|
17
|
+
@toggle-settings="ebookStore.toggleSettings"
|
|
18
|
+
/>
|
|
19
|
+
|
|
20
|
+
<main ref="mainContent" :class="[
|
|
21
|
+
'h-screen overflow-y-auto pt-14 transition-all duration-200',
|
|
22
|
+
uiStore.sidebarOpen ? 'lg:pl-[280px]' : ''
|
|
23
|
+
]" @scroll="handleScroll">
|
|
24
|
+
<!-- Active section breadcrumb -->
|
|
25
|
+
<div v-if="activeSection" class="sticky top-0 z-10 border-b backdrop-blur-sm px-6 py-2 text-sm"
|
|
26
|
+
:class="[
|
|
27
|
+
'bg-white/90 dark:bg-gray-900/90 border-gray-200 dark:border-gray-700',
|
|
28
|
+
'text-gray-600 dark:text-gray-400'
|
|
29
|
+
]">
|
|
30
|
+
<div class="max-w-4xl mx-auto flex items-center gap-2">
|
|
31
|
+
<span v-if="activeSectionParent" :class="['text-gray-900', 'dark:text-gray-100']">{{ activeSectionParent.title }} /</span>
|
|
32
|
+
<span class="font-medium" :class="['text-gray-900 dark:text-gray-100']">{{ activeSection.title }}</span>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<div class="px-6 py-10 lg:px-8 lg:py-12 mx-auto" :style="{ maxWidth: '72rem', margin: '0 auto' }">
|
|
37
|
+
<!-- Article content (no sections) -->
|
|
38
|
+
<div v-if="!hasSections" class="db-content">
|
|
39
|
+
<BlockRenderer v-if="articleContent" :blocks="articleContent.blocks" />
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<!-- Section content -->
|
|
43
|
+
<template v-else>
|
|
44
|
+
<div v-for="section in documentStore.sections" :key="section.id">
|
|
45
|
+
<ChapterSection v-if="section.type === 'chapter'" :section="section" />
|
|
46
|
+
<AppendixSection v-else-if="section.type === 'appendix'" :section="section" />
|
|
47
|
+
<PartSection v-else-if="section.type === 'part'" :section="section" />
|
|
48
|
+
<SectionContent v-else :section="section" />
|
|
49
|
+
</div>
|
|
50
|
+
</template>
|
|
51
|
+
|
|
52
|
+
<!-- Footer -->
|
|
53
|
+
<footer class="mt-16 pt-8 border-t text-center text-sm" :class="['border-gray-200 dark:border-gray-700', 'text-gray-500 dark:text-gray-400']">
|
|
54
|
+
Generated by <a href="https://github.com/metanorma/metanorma-docbook" class="hover:underline" :class="['text-blue-600 dark:text-blue-400']">Metanorma Docbook</a> gem
|
|
55
|
+
</footer>
|
|
56
|
+
</div>
|
|
57
|
+
</main>
|
|
58
|
+
|
|
59
|
+
<SearchModal />
|
|
60
|
+
<SettingsPanel />
|
|
61
|
+
|
|
62
|
+
<!-- Keyboard shortcut listener -->
|
|
63
|
+
<Teleport to="body">
|
|
64
|
+
<div @keydown="handleGlobalKeydown"></div>
|
|
65
|
+
</Teleport>
|
|
66
|
+
</EbookContainer>
|
|
67
|
+
</template>
|
|
68
|
+
|
|
69
|
+
<script setup lang="ts">
|
|
70
|
+
import { onMounted, computed, ref } from 'vue'
|
|
71
|
+
import { useDocumentStore, type TocItem } from '@/stores/documentStore'
|
|
72
|
+
import { useUiStore } from '@/stores/uiStore'
|
|
73
|
+
import { useEbookStore } from '@/composables/useEbookStore'
|
|
74
|
+
import AppSidebar from '@/components/AppSidebar.vue'
|
|
75
|
+
import SearchModal from '@/components/SearchModal.vue'
|
|
76
|
+
import SettingsPanel from '@/components/SettingsPanel.vue'
|
|
77
|
+
import ChapterSection from '@/components/ChapterSection.vue'
|
|
78
|
+
import AppendixSection from '@/components/AppendixSection.vue'
|
|
79
|
+
import PartSection from '@/components/PartSection.vue'
|
|
80
|
+
import SectionContent from '@/components/SectionContent.vue'
|
|
81
|
+
import BlockRenderer from '@/components/BlockRenderer.vue'
|
|
82
|
+
import EbookTopBar from '@/components/EbookTopBar.vue'
|
|
83
|
+
import EbookContainer from '@/components/EbookContainer.vue'
|
|
84
|
+
|
|
85
|
+
const documentStore = useDocumentStore()
|
|
86
|
+
const uiStore = useUiStore()
|
|
87
|
+
const ebookStore = useEbookStore()
|
|
88
|
+
const mainContent = ref<HTMLElement | null>(null)
|
|
89
|
+
|
|
90
|
+
function toggleToc() {
|
|
91
|
+
uiStore.toggleSidebar()
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const hasSections = computed(() => documentStore.sections && documentStore.sections.length > 0)
|
|
95
|
+
|
|
96
|
+
// Article content for documents without sections
|
|
97
|
+
const articleContent = computed(() => documentStore.getSectionContent('article-content'))
|
|
98
|
+
|
|
99
|
+
// Find the currently active section based on scroll position
|
|
100
|
+
const activeSection = computed(() => {
|
|
101
|
+
if (!uiStore.activeSectionId) return null
|
|
102
|
+
return findSectionById(documentStore.sections, uiStore.activeSectionId)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
// Find parent section if active section is nested
|
|
106
|
+
const activeSectionParent = computed((): TocItem | null => {
|
|
107
|
+
if (!uiStore.activeSectionId) return null
|
|
108
|
+
for (const section of documentStore.sections) {
|
|
109
|
+
if (section.children && section.children.some(c => c.id === uiStore.activeSectionId)) {
|
|
110
|
+
return section
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return null
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
function findSectionById(sections: TocItem[], id: string): TocItem | null {
|
|
117
|
+
for (const section of sections) {
|
|
118
|
+
if (section.id === id) return section
|
|
119
|
+
if (section.children) {
|
|
120
|
+
const found = findSectionById(section.children, id)
|
|
121
|
+
if (found) return found
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return null
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let scrollTimeout: ReturnType<typeof setTimeout> | null = null
|
|
128
|
+
|
|
129
|
+
function handleScroll() {
|
|
130
|
+
if (scrollTimeout) clearTimeout(scrollTimeout)
|
|
131
|
+
scrollTimeout = setTimeout(() => {
|
|
132
|
+
updateActiveSection()
|
|
133
|
+
}, 50)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function updateActiveSection() {
|
|
137
|
+
if (!mainContent.value) return
|
|
138
|
+
|
|
139
|
+
const scrollTop = mainContent.value.scrollTop
|
|
140
|
+
const sections = documentStore.sections
|
|
141
|
+
|
|
142
|
+
let foundSection: TocItem | null = null
|
|
143
|
+
|
|
144
|
+
function checkSection(section: TocItem) {
|
|
145
|
+
const element = document.getElementById(section.id)
|
|
146
|
+
if (!element) return
|
|
147
|
+
|
|
148
|
+
const rect = element.getBoundingClientRect()
|
|
149
|
+
const offsetTop = rect.top + scrollTop - 100
|
|
150
|
+
|
|
151
|
+
if (scrollTop >= offsetTop - 100) {
|
|
152
|
+
foundSection = section
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function walkSections(sects: TocItem[]) {
|
|
157
|
+
for (const sect of sects) {
|
|
158
|
+
checkSection(sect)
|
|
159
|
+
if (sect.children) {
|
|
160
|
+
walkSections(sect.children)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
walkSections(sections)
|
|
166
|
+
|
|
167
|
+
if (foundSection && (foundSection as TocItem).id !== uiStore.activeSectionId) {
|
|
168
|
+
uiStore.setActiveSection((foundSection as TocItem).id)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
onMounted(() => {
|
|
173
|
+
documentStore.loadFromWindow()
|
|
174
|
+
|
|
175
|
+
// Clean up DOCBOOK_DATA from DOM to keep DOM small
|
|
176
|
+
delete (window as any).DOCBOOK_DATA
|
|
177
|
+
|
|
178
|
+
ebookStore.applyTheme()
|
|
179
|
+
|
|
180
|
+
// Force close settings panel on mount
|
|
181
|
+
if (ebookStore.settingsOpen.value) {
|
|
182
|
+
ebookStore.toggleSettings()
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Open sidebar by default on desktop
|
|
186
|
+
if (window.innerWidth >= 1024) {
|
|
187
|
+
uiStore.openSidebar()
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Handle hash navigation for anchor links
|
|
191
|
+
handleHashNavigation()
|
|
192
|
+
window.addEventListener('hashchange', handleHashNavigation)
|
|
193
|
+
|
|
194
|
+
// Global keyboard shortcuts
|
|
195
|
+
document.addEventListener('keydown', handleGlobalKeydown)
|
|
196
|
+
|
|
197
|
+
// Initial active section
|
|
198
|
+
setTimeout(updateActiveSection, 100)
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
function handleHashNavigation() {
|
|
202
|
+
const hash = window.location.hash
|
|
203
|
+
if (!hash) return
|
|
204
|
+
const id = hash.slice(1) // remove #
|
|
205
|
+
const element = document.getElementById(id)
|
|
206
|
+
if (element) {
|
|
207
|
+
element.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function handleGlobalKeydown(e: KeyboardEvent) {
|
|
212
|
+
if (e.key === '/' && !isInputFocused()) {
|
|
213
|
+
e.preventDefault()
|
|
214
|
+
uiStore.openSearch()
|
|
215
|
+
}
|
|
216
|
+
if (e.key === 'Escape') {
|
|
217
|
+
if (uiStore.searchOpen) {
|
|
218
|
+
uiStore.closeSearch()
|
|
219
|
+
}
|
|
220
|
+
if (uiStore.sidebarOpen) {
|
|
221
|
+
uiStore.closeSidebar()
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function isInputFocused(): boolean {
|
|
227
|
+
const active = document.activeElement
|
|
228
|
+
return active instanceof HTMLInputElement || active instanceof HTMLTextAreaElement
|
|
229
|
+
}
|
|
230
|
+
</script>
|
data/frontend/src/app.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<aside
|
|
3
|
+
:class="[
|
|
4
|
+
'fixed top-0 left-0 z-50 w-[280px] h-full bg-gray-50 dark:bg-gray-800 border-r border-gray-200 dark:border-gray-700 overflow-y-auto transition-transform duration-200',
|
|
5
|
+
uiStore.sidebarOpen ? 'translate-x-0' : '-translate-x-full'
|
|
6
|
+
]"
|
|
7
|
+
>
|
|
8
|
+
<!-- Sidebar Header -->
|
|
9
|
+
<div class="sticky top-0 bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 p-4 z-10">
|
|
10
|
+
<div class="flex items-center justify-between mb-3">
|
|
11
|
+
<span class="font-bold text-xs uppercase tracking-wider text-gray-500 dark:text-gray-400 truncate pr-2">
|
|
12
|
+
{{ documentStore.title }}
|
|
13
|
+
</span>
|
|
14
|
+
<button
|
|
15
|
+
@click="uiStore.closeSidebar"
|
|
16
|
+
class="p-1 rounded hover:bg-gray-200 dark:hover:bg-gray-700 text-gray-600 dark:text-gray-300"
|
|
17
|
+
>
|
|
18
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
19
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
|
20
|
+
</svg>
|
|
21
|
+
</button>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<!-- Search button -->
|
|
25
|
+
<button
|
|
26
|
+
@click="uiStore.openSearch"
|
|
27
|
+
class="w-full flex items-center gap-2 px-3 py-1.5 text-sm bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md hover:bg-gray-50 dark:hover:bg-gray-600 text-gray-500 dark:text-gray-400 transition-colors"
|
|
28
|
+
>
|
|
29
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
30
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
|
|
31
|
+
</svg>
|
|
32
|
+
<span>Search headings...</span>
|
|
33
|
+
<kbd class="ml-auto text-xs bg-gray-100 dark:bg-gray-600 px-1.5 py-0.5 rounded">/</kbd>
|
|
34
|
+
</button>
|
|
35
|
+
|
|
36
|
+
<!-- Font toggle -->
|
|
37
|
+
<div class="flex gap-1 mt-3">
|
|
38
|
+
<button
|
|
39
|
+
@click="setFontFamily('sans')"
|
|
40
|
+
:class="uiStore.fontFamily === 'sans' ? 'bg-blue-500 text-white' : 'bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300'"
|
|
41
|
+
class="flex-1 py-1 px-2 text-xs rounded-md transition-colors font-sans"
|
|
42
|
+
>
|
|
43
|
+
Sans
|
|
44
|
+
</button>
|
|
45
|
+
<button
|
|
46
|
+
@click="setFontFamily('serif')"
|
|
47
|
+
:class="uiStore.fontFamily === 'serif' ? 'bg-blue-500 text-white' : 'bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300'"
|
|
48
|
+
class="flex-1 py-1 px-2 text-xs rounded-md transition-colors font-serif"
|
|
49
|
+
>
|
|
50
|
+
Serif
|
|
51
|
+
</button>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<!-- Theme toggle -->
|
|
55
|
+
<button
|
|
56
|
+
@click="cycleTheme"
|
|
57
|
+
class="w-full flex items-center justify-between mt-2 py-1.5 px-3 text-xs bg-gray-200 dark:bg-gray-700 rounded-md hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors text-gray-700 dark:text-gray-300"
|
|
58
|
+
>
|
|
59
|
+
<span class="flex items-center gap-2">
|
|
60
|
+
<svg v-if="ebookStore.theme.value === 'day' || ebookStore.theme.value === 'sepia'" class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
61
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"/>
|
|
62
|
+
</svg>
|
|
63
|
+
<svg v-else class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
64
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"/>
|
|
65
|
+
</svg>
|
|
66
|
+
<span>{{ themeLabel }}</span>
|
|
67
|
+
</span>
|
|
68
|
+
</button>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<!-- TOC -->
|
|
72
|
+
<nav class="p-3">
|
|
73
|
+
<ul class="space-y-0.5">
|
|
74
|
+
<TocTreeItem
|
|
75
|
+
v-for="item in documentStore.sections"
|
|
76
|
+
:key="item.id"
|
|
77
|
+
:item="item"
|
|
78
|
+
:depth="1"
|
|
79
|
+
/>
|
|
80
|
+
</ul>
|
|
81
|
+
</nav>
|
|
82
|
+
</aside>
|
|
83
|
+
</template>
|
|
84
|
+
|
|
85
|
+
<script setup lang="ts">
|
|
86
|
+
import { computed } from 'vue'
|
|
87
|
+
import { useDocumentStore } from '@/stores/documentStore'
|
|
88
|
+
import { useUiStore } from '@/stores/uiStore'
|
|
89
|
+
import { useEbookStore, type Theme } from '@/composables/useEbookStore'
|
|
90
|
+
import TocTreeItem from '@/components/TocTreeItem.vue'
|
|
91
|
+
|
|
92
|
+
const documentStore = useDocumentStore()
|
|
93
|
+
const uiStore = useUiStore()
|
|
94
|
+
const ebookStore = useEbookStore()
|
|
95
|
+
|
|
96
|
+
const themeOrder: Theme[] = ['day', 'sepia', 'night', 'oled']
|
|
97
|
+
|
|
98
|
+
const themeLabel = computed(() => {
|
|
99
|
+
switch (ebookStore.theme.value) {
|
|
100
|
+
case 'day': return 'Day'
|
|
101
|
+
case 'sepia': return 'Sepia'
|
|
102
|
+
case 'night': return 'Night'
|
|
103
|
+
case 'oled': return 'OLED'
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
function cycleTheme() {
|
|
108
|
+
const currentIndex = themeOrder.indexOf(ebookStore.theme.value)
|
|
109
|
+
const nextIndex = (currentIndex + 1) % themeOrder.length
|
|
110
|
+
ebookStore.setTheme(themeOrder[nextIndex])
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function setFontFamily(font: 'sans' | 'serif') {
|
|
114
|
+
uiStore.setFontFamily(font)
|
|
115
|
+
}
|
|
116
|
+
</script>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section :id="section.id" class="mb-12 scroll-mt-20">
|
|
3
|
+
<div class="db-appendix-wrap">
|
|
4
|
+
<h2 class="flex items-baseline gap-3 text-xl font-bold mb-5 pb-2 border-b border-gray-200 dark:border-gray-700">
|
|
5
|
+
<span class="text-gray-500 dark:text-gray-400 text-lg font-normal">{{ numbering }}</span>
|
|
6
|
+
<span>{{ section.title }}</span>
|
|
7
|
+
</h2>
|
|
8
|
+
<BlockRenderer v-if="sectionContent" :blocks="sectionContent.blocks" />
|
|
9
|
+
<!-- Render non-changelog children -->
|
|
10
|
+
<template v-if="section.children && section.children.length > 0">
|
|
11
|
+
<div v-for="child in section.children" :key="child.id" class="ml-4">
|
|
12
|
+
<SectionContent v-if="!isChangelogSection(child)" :section="child" />
|
|
13
|
+
</div>
|
|
14
|
+
</template>
|
|
15
|
+
</div>
|
|
16
|
+
</section>
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<script setup lang="ts">
|
|
20
|
+
import { computed } from 'vue'
|
|
21
|
+
import { useDocumentStore, type TocItem } from '@/stores/documentStore'
|
|
22
|
+
import SectionContent from '@/components/SectionContent.vue'
|
|
23
|
+
import BlockRenderer from '@/components/BlockRenderer.vue'
|
|
24
|
+
|
|
25
|
+
interface Props {
|
|
26
|
+
section: TocItem
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const props = defineProps<Props>()
|
|
30
|
+
const documentStore = useDocumentStore()
|
|
31
|
+
|
|
32
|
+
const numbering = computed(() => documentStore.getNumbering(props.section.id))
|
|
33
|
+
const sectionContent = computed(() => documentStore.getSectionContent(props.section.id))
|
|
34
|
+
|
|
35
|
+
function isChangelogSection(section: TocItem): boolean {
|
|
36
|
+
const id = section.id.toLowerCase()
|
|
37
|
+
return id === 'changelog' || /\br\d+\b/.test(id)
|
|
38
|
+
}
|
|
39
|
+
</script>
|