@life-and-dev/mdsite 0.0.3
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.
- package/README.md +228 -0
- package/bin/mdsite.js +3 -0
- package/dist/commands/commands.test.d.ts +1 -0
- package/dist/commands/commands.test.js +302 -0
- package/dist/commands/commands.test.js.map +1 -0
- package/dist/commands/generate.d.ts +1 -0
- package/dist/commands/generate.js +20 -0
- package/dist/commands/generate.js.map +1 -0
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +16 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/prepare.d.ts +1 -0
- package/dist/commands/prepare.js +111 -0
- package/dist/commands/prepare.js.map +1 -0
- package/dist/commands/prepare.test.d.ts +1 -0
- package/dist/commands/prepare.test.js +131 -0
- package/dist/commands/prepare.test.js.map +1 -0
- package/dist/commands/preview.d.ts +5 -0
- package/dist/commands/preview.js +62 -0
- package/dist/commands/preview.js.map +1 -0
- package/dist/commands/start.d.ts +5 -0
- package/dist/commands/start.js +50 -0
- package/dist/commands/start.js.map +1 -0
- package/dist/commands/stop.d.ts +1 -0
- package/dist/commands/stop.js +26 -0
- package/dist/commands/stop.js.map +1 -0
- package/dist/commands/workflows.test.d.ts +1 -0
- package/dist/commands/workflows.test.js +183 -0
- package/dist/commands/workflows.test.js.map +1 -0
- package/dist/config/default-mdsite-config.d.ts +2 -0
- package/dist/config/default-mdsite-config.js +82 -0
- package/dist/config/default-mdsite-config.js.map +1 -0
- package/dist/config/default-mdsite-config.test.d.ts +1 -0
- package/dist/config/default-mdsite-config.test.js +42 -0
- package/dist/config/default-mdsite-config.test.js.map +1 -0
- package/dist/config/mdsite-config.d.ts +42 -0
- package/dist/config/mdsite-config.js +74 -0
- package/dist/config/mdsite-config.js.map +1 -0
- package/dist/config/mdsite-config.test.d.ts +1 -0
- package/dist/config/mdsite-config.test.js +115 -0
- package/dist/config/mdsite-config.test.js.map +1 -0
- package/dist/config/menu.d.ts +3 -0
- package/dist/config/menu.js +58 -0
- package/dist/config/menu.js.map +1 -0
- package/dist/config/menu.test.d.ts +1 -0
- package/dist/config/menu.test.js +53 -0
- package/dist/config/menu.test.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +99 -0
- package/dist/index.js.map +1 -0
- package/dist/index.test.d.ts +1 -0
- package/dist/index.test.js +133 -0
- package/dist/index.test.js.map +1 -0
- package/dist/process/child-process.d.ts +9 -0
- package/dist/process/child-process.js +142 -0
- package/dist/process/child-process.js.map +1 -0
- package/dist/process/child-process.test.d.ts +1 -0
- package/dist/process/child-process.test.js +131 -0
- package/dist/process/child-process.test.js.map +1 -0
- package/dist/process/runtime-state.d.ts +16 -0
- package/dist/process/runtime-state.js +42 -0
- package/dist/process/runtime-state.js.map +1 -0
- package/dist/process/runtime-state.test.d.ts +1 -0
- package/dist/process/runtime-state.test.js +57 -0
- package/dist/process/runtime-state.test.js.map +1 -0
- package/dist/renderer/mdsite-nuxt.d.ts +22 -0
- package/dist/renderer/mdsite-nuxt.js +194 -0
- package/dist/renderer/mdsite-nuxt.js.map +1 -0
- package/dist/renderer/mdsite-nuxt.test.d.ts +1 -0
- package/dist/renderer/mdsite-nuxt.test.js +226 -0
- package/dist/renderer/mdsite-nuxt.test.js.map +1 -0
- package/mdsite-nuxt/app/app.vue +7 -0
- package/mdsite-nuxt/app/assets/css/bible-tooltips.css +87 -0
- package/mdsite-nuxt/app/assets/css/markdown.css +195 -0
- package/mdsite-nuxt/app/assets/css/print.css +133 -0
- package/mdsite-nuxt/app/components/AppBar.vue +138 -0
- package/mdsite-nuxt/app/components/AppFooter.vue +88 -0
- package/mdsite-nuxt/app/components/AppNavigation.vue +162 -0
- package/mdsite-nuxt/app/components/AppTableOfContents.vue +149 -0
- package/mdsite-nuxt/app/components/BreadcrumbNav.vue +139 -0
- package/mdsite-nuxt/app/components/NavigationTree.vue +169 -0
- package/mdsite-nuxt/app/components/SearchBox.vue +212 -0
- package/mdsite-nuxt/app/components/TocItem.vue +75 -0
- package/mdsite-nuxt/app/components/TreeNode.vue +264 -0
- package/mdsite-nuxt/app/components/content/MarkdownAlert.vue +42 -0
- package/mdsite-nuxt/app/components/content/Mermaid.vue +103 -0
- package/mdsite-nuxt/app/components/content/ProseA.vue +57 -0
- package/mdsite-nuxt/app/components/content/ProseBlockquote.vue +14 -0
- package/mdsite-nuxt/app/components/content/ProsePre.vue +49 -0
- package/mdsite-nuxt/app/components/content/ProseTable.vue +143 -0
- package/mdsite-nuxt/app/composables/useAppTheme.ts +89 -0
- package/mdsite-nuxt/app/composables/useBibleTooltips.ts +21 -0
- package/mdsite-nuxt/app/composables/useBreadcrumbs.ts +70 -0
- package/mdsite-nuxt/app/composables/useContentPostProcessing.ts +89 -0
- package/mdsite-nuxt/app/composables/useNavigationTree.ts +174 -0
- package/mdsite-nuxt/app/composables/useSearchIndex.ts +47 -0
- package/mdsite-nuxt/app/composables/useSearchRelevance.test.ts +255 -0
- package/mdsite-nuxt/app/composables/useSearchRelevance.ts +207 -0
- package/mdsite-nuxt/app/composables/useSidebarState.ts +62 -0
- package/mdsite-nuxt/app/composables/useSiteConfig.ts +37 -0
- package/mdsite-nuxt/app/composables/useSourceEdit.ts +69 -0
- package/mdsite-nuxt/app/composables/useTableOfContents.ts +141 -0
- package/mdsite-nuxt/app/composables/useTableParser.ts +106 -0
- package/mdsite-nuxt/app/composables/useTooltipConfig.ts +20 -0
- package/mdsite-nuxt/app/config/themes.ts +17 -0
- package/mdsite-nuxt/app/layouts/default.vue +376 -0
- package/mdsite-nuxt/app/pages/[...slug].vue +56 -0
- package/mdsite-nuxt/app/pages/index.vue +54 -0
- package/mdsite-nuxt/app/plugins/bible-tooltips.client.ts +401 -0
- package/mdsite-nuxt/app/plugins/bible-tooltips.test.ts +284 -0
- package/mdsite-nuxt/app/types/content.d.ts +10 -0
- package/mdsite-nuxt/app/types/table.ts +57 -0
- package/mdsite-nuxt/app/utils/bible-book-names.ts +37 -0
- package/mdsite-nuxt/app/utils/bible-verse-utils.test.ts +148 -0
- package/mdsite-nuxt/app/utils/bible-verse-utils.ts +236 -0
- package/mdsite-nuxt/content.config.ts +18 -0
- package/mdsite-nuxt/example.config.yml +67 -0
- package/mdsite-nuxt/nuxt.config.ts +247 -0
- package/mdsite-nuxt/package-lock.json +15922 -0
- package/mdsite-nuxt/package.json +50 -0
- package/mdsite-nuxt/playwright.config.js +47 -0
- package/mdsite-nuxt/public/robots.txt +2 -0
- package/mdsite-nuxt/scripts/generate-favicons.ts +192 -0
- package/mdsite-nuxt/scripts/generate-indices.ts +508 -0
- package/mdsite-nuxt/scripts/renderer-hooks.test.ts +277 -0
- package/mdsite-nuxt/scripts/renderer-hooks.ts +141 -0
- package/mdsite-nuxt/scripts/start.test.ts +164 -0
- package/mdsite-nuxt/scripts/start.ts +55 -0
- package/mdsite-nuxt/scripts/sync-content.ts +504 -0
- package/mdsite-nuxt/tsconfig.json +18 -0
- package/mdsite-nuxt/utils/mdsite-config.ts +240 -0
- package/mdsite-nuxt/vitest.config.ts +7 -0
- package/package.json +36 -0
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="layout-wrapper">
|
|
3
|
+
<!-- App Bar -->
|
|
4
|
+
<AppBar
|
|
5
|
+
:sidebars-visible="mdAndUp && sidebarsVisible"
|
|
6
|
+
@toggle-menu="handleToggleMenu"
|
|
7
|
+
/>
|
|
8
|
+
|
|
9
|
+
<!-- Desktop Layout (md and up) -->
|
|
10
|
+
<div v-if="mdAndUp" class="desktop-wrapper">
|
|
11
|
+
<div class="desktop-layout">
|
|
12
|
+
<!-- Left Sidebar (Navigation) -->
|
|
13
|
+
<v-navigation-drawer
|
|
14
|
+
v-model="sidebarsVisible"
|
|
15
|
+
:permanent="!isPrinting"
|
|
16
|
+
absolute
|
|
17
|
+
location="left"
|
|
18
|
+
width="320"
|
|
19
|
+
:touchless="true"
|
|
20
|
+
:scrim="false"
|
|
21
|
+
:disableRouteWatcher="true"
|
|
22
|
+
class="desktop-drawer-left"
|
|
23
|
+
>
|
|
24
|
+
<AppNavigation
|
|
25
|
+
:show-search="true"
|
|
26
|
+
@select="handleNavSelect"
|
|
27
|
+
/>
|
|
28
|
+
</v-navigation-drawer>
|
|
29
|
+
|
|
30
|
+
<!-- Center Content Column (flexible) -->
|
|
31
|
+
<v-main class="content-area">
|
|
32
|
+
<v-container :fluid="isPrinting" :class="{ 'print-force-full': isPrinting }">
|
|
33
|
+
<div ref="desktopContentContainer">
|
|
34
|
+
<slot />
|
|
35
|
+
</div>
|
|
36
|
+
</v-container>
|
|
37
|
+
</v-main>
|
|
38
|
+
|
|
39
|
+
<!-- Right Sidebar (Table of Contents) -->
|
|
40
|
+
<v-navigation-drawer
|
|
41
|
+
v-if="shouldShowTOC"
|
|
42
|
+
v-model="sidebarsVisible"
|
|
43
|
+
:permanent="!isPrinting"
|
|
44
|
+
absolute
|
|
45
|
+
location="right"
|
|
46
|
+
width="320"
|
|
47
|
+
:touchless="true"
|
|
48
|
+
:scrim="false"
|
|
49
|
+
:disableRouteWatcher="true"
|
|
50
|
+
class="desktop-drawer-right"
|
|
51
|
+
>
|
|
52
|
+
<AppTableOfContents
|
|
53
|
+
:toc-items="tocItems"
|
|
54
|
+
:active-id="activeHeadingId"
|
|
55
|
+
/>
|
|
56
|
+
</v-navigation-drawer>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<!-- Mobile Layout (sm and below) -->
|
|
61
|
+
<div v-else class="mobile-layout">
|
|
62
|
+
<!-- Mobile Drawer -->
|
|
63
|
+
<v-navigation-drawer
|
|
64
|
+
v-model="drawerOpen"
|
|
65
|
+
temporary
|
|
66
|
+
location="left"
|
|
67
|
+
width="320"
|
|
68
|
+
>
|
|
69
|
+
<div class="drawer-content">
|
|
70
|
+
<!-- Search Box -->
|
|
71
|
+
<SearchBox
|
|
72
|
+
@select="handleMobileSelection"
|
|
73
|
+
/>
|
|
74
|
+
|
|
75
|
+
<v-divider class="my-2" />
|
|
76
|
+
|
|
77
|
+
<!-- "On This Page" Section (mobile only) -->
|
|
78
|
+
<v-expansion-panels v-if="shouldShowTOC" flat>
|
|
79
|
+
<v-expansion-panel color="surface-rail">
|
|
80
|
+
<v-expansion-panel-title class="toc-panel-title">
|
|
81
|
+
On This Page
|
|
82
|
+
</v-expansion-panel-title>
|
|
83
|
+
<v-expansion-panel-text class="bg-surface-rail">
|
|
84
|
+
<AppTableOfContents
|
|
85
|
+
:toc-items="tocItems"
|
|
86
|
+
:active-id="activeHeadingId"
|
|
87
|
+
:show-header="false"
|
|
88
|
+
@item-click="handleMobileSelection"
|
|
89
|
+
/>
|
|
90
|
+
</v-expansion-panel-text>
|
|
91
|
+
</v-expansion-panel>
|
|
92
|
+
</v-expansion-panels>
|
|
93
|
+
|
|
94
|
+
<v-divider v-if="shouldShowTOC" class="my-2" />
|
|
95
|
+
|
|
96
|
+
<!-- Navigation Tree -->
|
|
97
|
+
<AppNavigation
|
|
98
|
+
:show-search="false"
|
|
99
|
+
@select="handleMobileSelection"
|
|
100
|
+
/>
|
|
101
|
+
</div>
|
|
102
|
+
</v-navigation-drawer>
|
|
103
|
+
|
|
104
|
+
<!-- Full-width Content -->
|
|
105
|
+
<v-main class="content-area-mobile">
|
|
106
|
+
<v-container>
|
|
107
|
+
<div ref="mobileContentContainer">
|
|
108
|
+
<slot />
|
|
109
|
+
</div>
|
|
110
|
+
</v-container>
|
|
111
|
+
</v-main>
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
<!-- Footer Bar (always visible on all layouts) -->
|
|
115
|
+
<AppFooter />
|
|
116
|
+
</div>
|
|
117
|
+
</template>
|
|
118
|
+
|
|
119
|
+
<script setup lang="ts">
|
|
120
|
+
import { useTableOfContents } from '~/composables/useTableOfContents'
|
|
121
|
+
|
|
122
|
+
const { mdAndUp } = useDisplay()
|
|
123
|
+
const route = useRoute()
|
|
124
|
+
|
|
125
|
+
// Table of Contents state
|
|
126
|
+
const desktopContentContainer = ref<HTMLElement>()
|
|
127
|
+
const mobileContentContainer = ref<HTMLElement>()
|
|
128
|
+
const { tocItems, activeId: activeHeadingId, shouldShowTOC, generateTOC } = useTableOfContents()
|
|
129
|
+
|
|
130
|
+
// Provide TOC generation function to child pages
|
|
131
|
+
provide('generateTOC', () => {
|
|
132
|
+
const container = mdAndUp.value ? desktopContentContainer.value : mobileContentContainer.value
|
|
133
|
+
if (container) {
|
|
134
|
+
generateTOC(container)
|
|
135
|
+
}
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
// Sidebar state - initialize based on screen size
|
|
139
|
+
const sidebarsVisible = ref(mdAndUp.value)
|
|
140
|
+
const drawerOpen = ref(false)
|
|
141
|
+
|
|
142
|
+
// Update sidebars visibility when screen size changes
|
|
143
|
+
watch(mdAndUp, (newValue) => {
|
|
144
|
+
sidebarsVisible.value = newValue
|
|
145
|
+
if (!newValue) {
|
|
146
|
+
drawerOpen.value = false
|
|
147
|
+
}
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
// Print handling - close sidebars to allow content to expand natively
|
|
151
|
+
// Print handling
|
|
152
|
+
const isPrinting = ref(false)
|
|
153
|
+
let previousSidebarState = true
|
|
154
|
+
|
|
155
|
+
const handlePrint = async () => {
|
|
156
|
+
previousSidebarState = sidebarsVisible.value
|
|
157
|
+
|
|
158
|
+
// 1. Enter print mode
|
|
159
|
+
isPrinting.value = true
|
|
160
|
+
sidebarsVisible.value = false
|
|
161
|
+
|
|
162
|
+
// 2. Wait for Vue to update the DOM (including style recalculations)
|
|
163
|
+
await nextTick()
|
|
164
|
+
// Small delay to ensure layout shifts have completed visually for the browser's print capture
|
|
165
|
+
// 500ms to accommodate the 0.3s sidebar transition
|
|
166
|
+
await new Promise(resolve => setTimeout(resolve, 500))
|
|
167
|
+
|
|
168
|
+
// 3. Print
|
|
169
|
+
window.print()
|
|
170
|
+
|
|
171
|
+
// 4. Cleanup after print dialog closes
|
|
172
|
+
isPrinting.value = false
|
|
173
|
+
sidebarsVisible.value = previousSidebarState
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Provide to children (like AppBar)
|
|
177
|
+
provide('triggerPrint', handlePrint)
|
|
178
|
+
|
|
179
|
+
// Also listen for Ctrl+P (native browser print)
|
|
180
|
+
// Note: This might still have a race condition on some browsers since we can't "pause" the event.
|
|
181
|
+
// The button click approach (triggerPrint) is preferred.
|
|
182
|
+
const onBeforePrint = () => {
|
|
183
|
+
if (!isPrinting.value) { // Only run if not already triggered by our helper
|
|
184
|
+
previousSidebarState = sidebarsVisible.value
|
|
185
|
+
isPrinting.value = true
|
|
186
|
+
sidebarsVisible.value = false
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const onAfterPrint = () => {
|
|
191
|
+
// Only restore if we weren't triggered by the helper (which handles its own restore)
|
|
192
|
+
// Or just safe to always restore if we are in print mode
|
|
193
|
+
if (isPrinting.value) {
|
|
194
|
+
isPrinting.value = false
|
|
195
|
+
sidebarsVisible.value = previousSidebarState
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
onMounted(() => {
|
|
200
|
+
if (import.meta.client) {
|
|
201
|
+
window.addEventListener('beforeprint', onBeforePrint)
|
|
202
|
+
window.addEventListener('afterprint', onAfterPrint)
|
|
203
|
+
}
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
onUnmounted(() => {
|
|
207
|
+
if (import.meta.client) {
|
|
208
|
+
window.removeEventListener('beforeprint', onBeforePrint)
|
|
209
|
+
window.removeEventListener('afterprint', onAfterPrint)
|
|
210
|
+
}
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Handle toggle menu (hamburger click)
|
|
215
|
+
*/
|
|
216
|
+
function handleToggleMenu() {
|
|
217
|
+
if (mdAndUp.value) {
|
|
218
|
+
// Desktop: toggle both sidebars
|
|
219
|
+
sidebarsVisible.value = !sidebarsVisible.value
|
|
220
|
+
} else {
|
|
221
|
+
// Mobile: toggle drawer
|
|
222
|
+
drawerOpen.value = !drawerOpen.value
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Handle navigation selection (desktop)
|
|
228
|
+
*/
|
|
229
|
+
function handleNavSelect(path: string) {
|
|
230
|
+
// Don't auto-close on desktop
|
|
231
|
+
navigateTo(path)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Handle any selection in mobile drawer (navigation, search, or TOC)
|
|
236
|
+
* Auto-closes drawer after selection
|
|
237
|
+
*/
|
|
238
|
+
function handleMobileSelection(path?: string) {
|
|
239
|
+
drawerOpen.value = false
|
|
240
|
+
if (path) {
|
|
241
|
+
navigateTo(path)
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* NOTE: We do NOT clear TOC on route change here
|
|
247
|
+
* The page component's useContentPostProcessing handles clearing and regenerating
|
|
248
|
+
* Clearing here causes a race condition where we clear AFTER the page has already generated TOC
|
|
249
|
+
*/
|
|
250
|
+
</script>
|
|
251
|
+
|
|
252
|
+
<style>
|
|
253
|
+
/* Global: Prevent horizontal scroll on body */
|
|
254
|
+
body {
|
|
255
|
+
overflow-x: hidden;
|
|
256
|
+
max-width: 100vw;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/* CSS Variables - must be unscoped */
|
|
260
|
+
:root {
|
|
261
|
+
--app-bar-height: 56px;
|
|
262
|
+
--footer-height: 56px;
|
|
263
|
+
--sidebar-transition: 0.3s ease;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/* Print styles - hide navigation elements */
|
|
267
|
+
/* Print styles - hide navigation elements */
|
|
268
|
+
@media print {
|
|
269
|
+
.v-navigation-drawer,
|
|
270
|
+
.v-app-bar,
|
|
271
|
+
.v-footer {
|
|
272
|
+
display: none !important;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/* Reset layout constraints - Force block to kill flexbox gaps */
|
|
276
|
+
.desktop-layout,
|
|
277
|
+
.v-application,
|
|
278
|
+
.v-main,
|
|
279
|
+
.content-area {
|
|
280
|
+
display: block !important;
|
|
281
|
+
width: 100% !important;
|
|
282
|
+
max-width: none !important;
|
|
283
|
+
margin: 0 !important;
|
|
284
|
+
padding: 0 !important;
|
|
285
|
+
/* Force reset Vuetify layout variables to ensure no space is reserved */
|
|
286
|
+
--v-layout-left: 0px !important;
|
|
287
|
+
--v-layout-right: 0px !important;
|
|
288
|
+
--v-layout-top: 0px !important;
|
|
289
|
+
--v-layout-bottom: 0px !important;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/* Ensure container takes full width - High Specificity to override scoped styles */
|
|
293
|
+
.v-application .v-container,
|
|
294
|
+
.v-container.print-force-full,
|
|
295
|
+
.v-container {
|
|
296
|
+
max-width: none !important;
|
|
297
|
+
width: 100% !important;
|
|
298
|
+
padding: 0 16px !important;
|
|
299
|
+
margin: 0 !important;
|
|
300
|
+
flex: 0 0 100% !important;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
</style>
|
|
304
|
+
|
|
305
|
+
<style scoped>
|
|
306
|
+
|
|
307
|
+
/* Desktop Wrapper - clips overflow for slide animations */
|
|
308
|
+
.desktop-wrapper {
|
|
309
|
+
width: 100%;
|
|
310
|
+
max-width: 100vw;
|
|
311
|
+
overflow-x: hidden;
|
|
312
|
+
overflow-y: visible;
|
|
313
|
+
position: relative;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/* Desktop Layout - full height flex container */
|
|
317
|
+
.desktop-layout {
|
|
318
|
+
display: flex;
|
|
319
|
+
min-height: 100vh;
|
|
320
|
+
width: 100%;
|
|
321
|
+
position: relative;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/* Desktop drawers - fixed positioning with independent scrolling */
|
|
325
|
+
.desktop-layout :deep(.v-navigation-drawer.desktop-drawer-left),
|
|
326
|
+
.desktop-layout :deep(.v-navigation-drawer.desktop-drawer-right) {
|
|
327
|
+
position: fixed !important;
|
|
328
|
+
top: 0 !important;
|
|
329
|
+
bottom: 0 !important;
|
|
330
|
+
left: auto !important;
|
|
331
|
+
right: auto !important;
|
|
332
|
+
height: 100vh !important;
|
|
333
|
+
z-index: 1100 !important;
|
|
334
|
+
overflow-y: auto;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.desktop-layout :deep(.v-navigation-drawer.desktop-drawer-left) {
|
|
338
|
+
left: 0 !important;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.desktop-layout :deep(.v-navigation-drawer.desktop-drawer-right) {
|
|
342
|
+
right: 0 !important;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/* Content area - Vuetify handles spacing automatically via --v-layout-* vars */
|
|
346
|
+
.content-area {
|
|
347
|
+
flex: 1;
|
|
348
|
+
min-width: 0;
|
|
349
|
+
/* Remove custom margins - Vuetify calculates based on drawer width */
|
|
350
|
+
margin: 0 !important;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.content-area-mobile {
|
|
354
|
+
width: 100%;
|
|
355
|
+
overflow-y: auto;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
.drawer-content {
|
|
359
|
+
height: 100%;
|
|
360
|
+
display: flex;
|
|
361
|
+
flex-direction: column;
|
|
362
|
+
overflow-y: auto;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
.toc-panel-title {
|
|
366
|
+
font-weight: 600;
|
|
367
|
+
font-size: 0.875rem;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/* Ensure proper spacing for container - Screen only */
|
|
371
|
+
@media screen {
|
|
372
|
+
:deep(.v-container) {
|
|
373
|
+
max-width: 1200px;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
</style>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<article>
|
|
3
|
+
<div v-if="pending" class="text-center py-8">
|
|
4
|
+
<v-progress-circular indeterminate color="primary"></v-progress-circular>
|
|
5
|
+
</div>
|
|
6
|
+
<div v-else-if="!page">
|
|
7
|
+
<v-alert type="error">Page not found</v-alert>
|
|
8
|
+
</div>
|
|
9
|
+
<div v-else>
|
|
10
|
+
<div class="content-body">
|
|
11
|
+
<ContentRenderer :value="page" />
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
14
|
+
</article>
|
|
15
|
+
</template>
|
|
16
|
+
|
|
17
|
+
<script setup lang="ts">
|
|
18
|
+
const route = useRoute()
|
|
19
|
+
|
|
20
|
+
// Query content using Nuxt Content v3 API
|
|
21
|
+
// server: true = Only query during SSR/prerendering, never on client
|
|
22
|
+
// This prevents 3.5MB database download - client uses prerendered HTML
|
|
23
|
+
const { data: page, pending } = await useAsyncData(
|
|
24
|
+
`content-${route.path}`,
|
|
25
|
+
() => queryCollection('content').path(route.path).first(),
|
|
26
|
+
{ server: true }
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
const siteConfig = useSiteConfig()
|
|
30
|
+
const title = page.value?.title || 'Page'
|
|
31
|
+
const description = page.value?.description || ''
|
|
32
|
+
|
|
33
|
+
useHead(() => ({
|
|
34
|
+
title,
|
|
35
|
+
htmlAttrs: { lang: 'en' },
|
|
36
|
+
meta: [
|
|
37
|
+
{ name: 'description', content: description },
|
|
38
|
+
{ name: 'keywords', content: page.value?.keywords?.join(', ') || '' },
|
|
39
|
+
{ name: 'robots', content: 'index, follow' },
|
|
40
|
+
{ name: 'theme-color', content: siteConfig.themeColorLight, media: '(prefers-color-scheme: light)' },
|
|
41
|
+
{ name: 'theme-color', content: siteConfig.themeColorDark, media: '(prefers-color-scheme: dark)' },
|
|
42
|
+
// Open Graph
|
|
43
|
+
{ property: 'og:title', content: title },
|
|
44
|
+
{ property: 'og:description', content: description },
|
|
45
|
+
{ property: 'og:url', content: `${siteConfig.siteCanonical}${route.path}` },
|
|
46
|
+
{ property: 'og:type', content: 'article' },
|
|
47
|
+
{ property: 'og:image', content: '/icon-512.png' }
|
|
48
|
+
],
|
|
49
|
+
link: [
|
|
50
|
+
...(siteConfig.siteCanonical ? [{ rel: 'canonical', href: `${siteConfig.siteCanonical}${route.path}` }] : [])
|
|
51
|
+
]
|
|
52
|
+
}))
|
|
53
|
+
|
|
54
|
+
// Post-process content: Bible tooltips + TOC generation
|
|
55
|
+
useContentPostProcessing(page)
|
|
56
|
+
</script>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<article>
|
|
3
|
+
<div v-if="pending" class="text-center py-8">
|
|
4
|
+
<v-progress-circular indeterminate color="primary"></v-progress-circular>
|
|
5
|
+
</div>
|
|
6
|
+
<div v-else-if="!page">
|
|
7
|
+
<v-alert type="error">Home page not found</v-alert>
|
|
8
|
+
</div>
|
|
9
|
+
<div v-else>
|
|
10
|
+
<div class="content-body">
|
|
11
|
+
<ContentRenderer :value="page" />
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
14
|
+
</article>
|
|
15
|
+
</template>
|
|
16
|
+
|
|
17
|
+
<script setup lang="ts">
|
|
18
|
+
// Query home page content using Nuxt Content v3 API
|
|
19
|
+
// server: true = Only query during SSR/prerendering, never on client
|
|
20
|
+
// This prevents 3.5MB database download - client uses prerendered HTML
|
|
21
|
+
const { data: page, pending } = await useAsyncData(
|
|
22
|
+
'content-home',
|
|
23
|
+
() => queryCollection('content').path('/').first(),
|
|
24
|
+
{ server: true } // true = SSR/prerender only
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
const siteConfig = useSiteConfig()
|
|
28
|
+
const title = page.value?.title || siteConfig.siteName || 'Home'
|
|
29
|
+
const description = page.value?.description || ''
|
|
30
|
+
|
|
31
|
+
useHead(() => ({
|
|
32
|
+
title,
|
|
33
|
+
htmlAttrs: { lang: 'en' },
|
|
34
|
+
meta: [
|
|
35
|
+
{ name: 'description', content: description },
|
|
36
|
+
{ name: 'keywords', content: page.value?.keywords?.join(', ') || '' },
|
|
37
|
+
{ name: 'robots', content: 'index, follow' },
|
|
38
|
+
{ name: 'theme-color', content: siteConfig.themeColorLight, media: '(prefers-color-scheme: light)' },
|
|
39
|
+
{ name: 'theme-color', content: siteConfig.themeColorDark, media: '(prefers-color-scheme: dark)' },
|
|
40
|
+
// Open Graph
|
|
41
|
+
{ property: 'og:title', content: title },
|
|
42
|
+
{ property: 'og:description', content: description },
|
|
43
|
+
{ property: 'og:url', content: siteConfig.siteCanonical },
|
|
44
|
+
{ property: 'og:type', content: 'website' },
|
|
45
|
+
{ property: 'og:image', content: '/icon-512.png' }
|
|
46
|
+
],
|
|
47
|
+
link: [
|
|
48
|
+
...(siteConfig.siteCanonical ? [{ rel: 'canonical', href: siteConfig.siteCanonical }] : [])
|
|
49
|
+
]
|
|
50
|
+
}))
|
|
51
|
+
|
|
52
|
+
// Post-process content: like Bible tooltips + TOC generation
|
|
53
|
+
useContentPostProcessing(page)
|
|
54
|
+
</script>
|