@life-and-dev/mdsite 0.5.0 → 0.5.2
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/mdsite-nuxt/app/components/AppTableOfContents.vue +11 -17
- package/mdsite-nuxt/app/composables/useSiteConfig.test.ts +117 -0
- package/mdsite-nuxt/app/composables/useSiteConfig.ts +46 -8
- package/mdsite-nuxt/app/composables/useTableOfContents.test.ts +5 -5
- package/mdsite-nuxt/app/composables/useTableOfContents.ts +1 -1
- package/mdsite-nuxt/nuxt.config.ts +19 -1
- package/mdsite-nuxt/scripts/generate-indices.ts +11 -8
- package/mdsite-nuxt/utils/mdsite-config.ts +25 -1
- package/package.json +1 -1
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
|
|
2
|
+
<!--
|
|
3
|
+
Guard at the root: the consuming composable's `shouldShowTOC` already
|
|
4
|
+
requires `tocItems.length >= TOC_MIN_HEADINGS (3)`, so by the time we
|
|
5
|
+
reach this template a usable TOC is guaranteed. Render nothing otherwise.
|
|
6
|
+
-->
|
|
7
|
+
<div
|
|
8
|
+
v-if="tocItems.length >= 3"
|
|
9
|
+
class="toc-sidebar"
|
|
10
|
+
ref="tocContainer"
|
|
11
|
+
>
|
|
3
12
|
<!-- Header -->
|
|
4
13
|
<h3 v-if="showHeader" class="toc-header">On This Page</h3>
|
|
5
14
|
<v-divider v-if="showHeader" class="mb-2" />
|
|
6
15
|
|
|
7
16
|
<!-- TOC Items -->
|
|
8
|
-
<nav
|
|
17
|
+
<nav class="toc-nav">
|
|
9
18
|
<TocItem
|
|
10
19
|
v-for="item in tocItems"
|
|
11
20
|
:key="item.id"
|
|
@@ -15,11 +24,6 @@
|
|
|
15
24
|
/>
|
|
16
25
|
</nav>
|
|
17
26
|
|
|
18
|
-
<!-- Empty state -->
|
|
19
|
-
<div v-else class="toc-empty">
|
|
20
|
-
<p class="text-caption text-center">No table of contents available</p>
|
|
21
|
-
</div>
|
|
22
|
-
|
|
23
27
|
<!-- Fade gradient for overflow -->
|
|
24
28
|
<div v-if="showFade" class="fade-gradient" />
|
|
25
29
|
</div>
|
|
@@ -122,16 +126,6 @@ watch(() => props.tocItems, () => {
|
|
|
122
126
|
scrollbar-width: none;
|
|
123
127
|
}
|
|
124
128
|
|
|
125
|
-
.toc-empty {
|
|
126
|
-
flex: 1;
|
|
127
|
-
display: flex;
|
|
128
|
-
align-items: center;
|
|
129
|
-
justify-content: center;
|
|
130
|
-
padding: 32px 16px;
|
|
131
|
-
color: rgb(var(--v-theme-on-surface-rail));
|
|
132
|
-
opacity: 0.6;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
129
|
.fade-gradient {
|
|
136
130
|
position: absolute;
|
|
137
131
|
bottom: 0;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the pure `mapSiteConfig` helper extracted from
|
|
3
|
+
* `useSiteConfig`. The `sourceEdit` Edit-on-GitHub link in `AppBar` and
|
|
4
|
+
* `AppFooter` is only rendered when `getEditUrl()` produces a URL, which
|
|
5
|
+
* in turn requires `contentGitRepo` to be populated from `server.repo`.
|
|
6
|
+
* These tests pin that mapping down so a future refactor cannot regress
|
|
7
|
+
* it back to the empty-string default.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { describe, expect, it } from 'vitest'
|
|
11
|
+
import { mapSiteConfig } from './useSiteConfig'
|
|
12
|
+
|
|
13
|
+
describe('mapSiteConfig', () => {
|
|
14
|
+
it('returns safe defaults when siteConfig is undefined', () => {
|
|
15
|
+
const result = mapSiteConfig(undefined, undefined)
|
|
16
|
+
|
|
17
|
+
expect(result).toEqual({
|
|
18
|
+
siteName: '',
|
|
19
|
+
siteCanonical: '',
|
|
20
|
+
contentGitRepo: '',
|
|
21
|
+
contentGitBranch: 'main',
|
|
22
|
+
contentGitPath: '.',
|
|
23
|
+
contentPath: '.',
|
|
24
|
+
features: {
|
|
25
|
+
bibleTooltips: false,
|
|
26
|
+
sourceEdit: false
|
|
27
|
+
},
|
|
28
|
+
themeColorLight: '#000000',
|
|
29
|
+
themeColorDark: '#ffffff'
|
|
30
|
+
})
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('reads site metadata from site.name and site.canonical', () => {
|
|
34
|
+
const result = mapSiteConfig({
|
|
35
|
+
site: { name: 'My Site', canonical: 'https://example.test' }
|
|
36
|
+
}, undefined)
|
|
37
|
+
|
|
38
|
+
expect(result.siteName).toBe('My Site')
|
|
39
|
+
expect(result.siteCanonical).toBe('https://example.test')
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
describe('contentGitRepo (Edit on GitHub source)', () => {
|
|
43
|
+
it('reads server.repo into contentGitRepo', () => {
|
|
44
|
+
const result = mapSiteConfig({
|
|
45
|
+
server: { repo: 'https://github.com/life-and-dev/mdsite' }
|
|
46
|
+
}, undefined)
|
|
47
|
+
|
|
48
|
+
expect(result.contentGitRepo).toBe('https://github.com/life-and-dev/mdsite')
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('defaults to empty string when server.repo is missing', () => {
|
|
52
|
+
const result = mapSiteConfig({ server: {} }, undefined)
|
|
53
|
+
|
|
54
|
+
expect(result.contentGitRepo).toBe('')
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('defaults to empty string when server is missing entirely', () => {
|
|
58
|
+
const result = mapSiteConfig({}, undefined)
|
|
59
|
+
|
|
60
|
+
expect(result.contentGitRepo).toBe('')
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
describe('features', () => {
|
|
65
|
+
it('defaults both feature flags to false when features is missing', () => {
|
|
66
|
+
const result = mapSiteConfig({}, undefined)
|
|
67
|
+
|
|
68
|
+
expect(result.features.bibleTooltips).toBe(false)
|
|
69
|
+
expect(result.features.sourceEdit).toBe(false)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('reads sourceEdit from features.sourceEdit', () => {
|
|
73
|
+
const result = mapSiteConfig({
|
|
74
|
+
features: { sourceEdit: true, bibleTooltips: false }
|
|
75
|
+
}, undefined)
|
|
76
|
+
|
|
77
|
+
expect(result.features.sourceEdit).toBe(true)
|
|
78
|
+
expect(result.features.bibleTooltips).toBe(false)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('reads bibleTooltips from features.bibleTooltips', () => {
|
|
82
|
+
const result = mapSiteConfig({
|
|
83
|
+
features: { sourceEdit: false, bibleTooltips: true }
|
|
84
|
+
}, undefined)
|
|
85
|
+
|
|
86
|
+
expect(result.features.sourceEdit).toBe(false)
|
|
87
|
+
expect(result.features.bibleTooltips).toBe(true)
|
|
88
|
+
})
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
describe('theme colors', () => {
|
|
92
|
+
it('uses the configured light/dark primary colors when present', () => {
|
|
93
|
+
const result = mapSiteConfig({
|
|
94
|
+
themes: {
|
|
95
|
+
light: { colors: { primary: '#111111' } },
|
|
96
|
+
dark: { colors: { primary: '#eeeeee' } }
|
|
97
|
+
}
|
|
98
|
+
}, undefined)
|
|
99
|
+
|
|
100
|
+
expect(result.themeColorLight).toBe('#111111')
|
|
101
|
+
expect(result.themeColorDark).toBe('#eeeeee')
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it('falls back to defaults when theme colors are missing', () => {
|
|
105
|
+
const result = mapSiteConfig({ themes: { light: {}, dark: {} } }, undefined)
|
|
106
|
+
|
|
107
|
+
expect(result.themeColorLight).toBe('#000000')
|
|
108
|
+
expect(result.themeColorDark).toBe('#ffffff')
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('passes through the contentPath argument with a dot default', () => {
|
|
113
|
+
expect(mapSiteConfig({}, '/abs/docs').contentPath).toBe('/abs/docs')
|
|
114
|
+
expect(mapSiteConfig({}, undefined).contentPath).toBe('.')
|
|
115
|
+
expect(mapSiteConfig({}, '').contentPath).toBe('.')
|
|
116
|
+
})
|
|
117
|
+
})
|
|
@@ -1,4 +1,28 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Subset of the runtime `mdsite.yml` config shape that `useSiteConfig`
|
|
3
|
+
* consumes. The runtime value is the full `MdsiteConfig`, but we type only
|
|
4
|
+
* the fields this composable reads so the pure mapper below can be unit
|
|
5
|
+
* tested without pulling in the entire config schema.
|
|
6
|
+
*/
|
|
7
|
+
export interface RawSiteConfig {
|
|
8
|
+
site?: {
|
|
9
|
+
name?: string
|
|
10
|
+
canonical?: string
|
|
11
|
+
}
|
|
12
|
+
server?: {
|
|
13
|
+
repo?: string
|
|
14
|
+
}
|
|
15
|
+
features?: {
|
|
16
|
+
bibleTooltips?: boolean
|
|
17
|
+
sourceEdit?: boolean
|
|
18
|
+
}
|
|
19
|
+
themes?: {
|
|
20
|
+
light?: { colors?: { primary?: string } }
|
|
21
|
+
dark?: { colors?: { primary?: string } }
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface SiteConfig {
|
|
2
26
|
siteName: string
|
|
3
27
|
siteCanonical: string
|
|
4
28
|
contentGitRepo: string
|
|
@@ -14,19 +38,23 @@ interface SiteConfig {
|
|
|
14
38
|
}
|
|
15
39
|
|
|
16
40
|
/**
|
|
17
|
-
*
|
|
41
|
+
* Pure helper that maps the raw runtime `siteConfig` object into the shape
|
|
42
|
+
* the renderer actually consumes. Extracted from `useSiteConfig` so the
|
|
43
|
+
* field mapping (notably `contentGitRepo` ← `server.repo`, which is what
|
|
44
|
+
* powers the Edit on GitHub link in `AppBar` and `AppFooter`) can be unit
|
|
45
|
+
* tested independently of the Nuxt runtime.
|
|
18
46
|
*/
|
|
19
|
-
export function
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
47
|
+
export function mapSiteConfig(
|
|
48
|
+
siteConfig: RawSiteConfig | undefined,
|
|
49
|
+
contentPath: string | undefined,
|
|
50
|
+
): SiteConfig {
|
|
23
51
|
return {
|
|
24
52
|
siteName: siteConfig?.site?.name || '',
|
|
25
53
|
siteCanonical: siteConfig?.site?.canonical || '',
|
|
26
|
-
contentGitRepo: '',
|
|
54
|
+
contentGitRepo: siteConfig?.server?.repo || '',
|
|
27
55
|
contentGitBranch: 'main',
|
|
28
56
|
contentGitPath: '.',
|
|
29
|
-
contentPath:
|
|
57
|
+
contentPath: contentPath || '.',
|
|
30
58
|
features: {
|
|
31
59
|
bibleTooltips: siteConfig?.features?.bibleTooltips ?? false,
|
|
32
60
|
sourceEdit: siteConfig?.features?.sourceEdit ?? false
|
|
@@ -35,3 +63,13 @@ export function useSiteConfig(): SiteConfig {
|
|
|
35
63
|
themeColorDark: siteConfig?.themes?.dark?.colors?.primary || '#ffffff'
|
|
36
64
|
}
|
|
37
65
|
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get site configuration based on runtime config
|
|
69
|
+
*/
|
|
70
|
+
export function useSiteConfig(): SiteConfig {
|
|
71
|
+
const config = useRuntimeConfig()
|
|
72
|
+
const siteConfig = config.public.siteConfig as RawSiteConfig | undefined
|
|
73
|
+
|
|
74
|
+
return mapSiteConfig(siteConfig, config.public.contentPath)
|
|
75
|
+
}
|
|
@@ -8,7 +8,7 @@ import { computeShouldShowTOC, TOC_MIN_HEADINGS, TOC_MIN_LINES } from './useTabl
|
|
|
8
8
|
describe('useTableOfContents threshold', () => {
|
|
9
9
|
it('exposes the expected minimum constants', () => {
|
|
10
10
|
expect(TOC_MIN_HEADINGS).toBe(3)
|
|
11
|
-
expect(TOC_MIN_LINES).toBe(
|
|
11
|
+
expect(TOC_MIN_LINES).toBe(15)
|
|
12
12
|
})
|
|
13
13
|
|
|
14
14
|
describe('computeShouldShowTOC', () => {
|
|
@@ -18,13 +18,13 @@ describe('useTableOfContents threshold', () => {
|
|
|
18
18
|
expect(computeShouldShowTOC(2, 100)).toBe(false)
|
|
19
19
|
})
|
|
20
20
|
|
|
21
|
-
it('returns false when lines <
|
|
21
|
+
it('returns false when lines < 15 (and headings >= 3)', () => {
|
|
22
22
|
expect(computeShouldShowTOC(3, 0)).toBe(false)
|
|
23
|
-
expect(computeShouldShowTOC(5,
|
|
23
|
+
expect(computeShouldShowTOC(5, 14)).toBe(false)
|
|
24
24
|
})
|
|
25
25
|
|
|
26
|
-
it('returns true when headings >= 3 and lines >=
|
|
27
|
-
expect(computeShouldShowTOC(3,
|
|
26
|
+
it('returns true when headings >= 3 and lines >= 15', () => {
|
|
27
|
+
expect(computeShouldShowTOC(3, 15)).toBe(true)
|
|
28
28
|
expect(computeShouldShowTOC(5, 100)).toBe(true)
|
|
29
29
|
})
|
|
30
30
|
|
|
@@ -8,7 +8,7 @@ export interface TocItem {
|
|
|
8
8
|
/** Minimum number of headings required to show the TOC. */
|
|
9
9
|
export const TOC_MIN_HEADINGS = 3
|
|
10
10
|
/** Minimum number of non-empty lines in the rendered content required to show the TOC. */
|
|
11
|
-
export const TOC_MIN_LINES =
|
|
11
|
+
export const TOC_MIN_LINES = 15
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Pure helper that decides whether the TOC should be shown.
|
|
@@ -18,7 +18,25 @@ export default defineNuxtConfig({
|
|
|
18
18
|
public: {
|
|
19
19
|
contentDomain: path.basename(mdsite.contentDir),
|
|
20
20
|
contentPath: mdsite.contentDir,
|
|
21
|
-
|
|
21
|
+
// `mdsite.config` is a valid `MdsiteConfig` at runtime, but
|
|
22
|
+
// Nuxt's runtime-config type generator collapses every complex
|
|
23
|
+
// field of `siteConfig` to a degenerate shape — `menu` becomes
|
|
24
|
+
// `Array<{}>` (regardless of whether the source type is recursive
|
|
25
|
+
// or contains `Array<any>`) and `footer` becomes `Array<any>`.
|
|
26
|
+
// We verified this by:
|
|
27
|
+
// 1. Tightening `MdsiteConfig.menu` to a proper recursive
|
|
28
|
+
// `MdsiteMenuItem` (no `any`) and clearing `.nuxt` cache:
|
|
29
|
+
// the generated `menu` was still `Array<{}>`.
|
|
30
|
+
// 2. Trying `declare module 'nuxt/schema'` augmentations of
|
|
31
|
+
// `PublicRuntimeConfig.siteConfig`: the augmentation
|
|
32
|
+
// *intersects* with the broken generated type rather than
|
|
33
|
+
// overriding it, producing an even narrower target.
|
|
34
|
+
// Since the runtime value is unchanged, `as any` is the most
|
|
35
|
+
// honest pragmatic escape hatch. The recursive `MdsiteMenuItem`
|
|
36
|
+
// type is still useful: it gives the rest of the codebase
|
|
37
|
+
// (notably `scripts/generate-indices.ts`) a single source of
|
|
38
|
+
// truth and proper types.
|
|
39
|
+
siteConfig: siteConfig as any
|
|
22
40
|
}
|
|
23
41
|
},
|
|
24
42
|
|
|
@@ -4,6 +4,7 @@ import fs from 'fs-extra'
|
|
|
4
4
|
import path from 'path'
|
|
5
5
|
import { fileURLToPath } from 'url'
|
|
6
6
|
import { parse as parseYaml } from 'yaml'
|
|
7
|
+
import type { MdsiteMenuItem } from '../utils/mdsite-config'
|
|
7
8
|
|
|
8
9
|
const __filename = fileURLToPath(import.meta.url)
|
|
9
10
|
const __dirname = path.dirname(__filename)
|
|
@@ -108,8 +109,10 @@ export interface MinimalTreeNode {
|
|
|
108
109
|
isPrimary?: boolean
|
|
109
110
|
}
|
|
110
111
|
|
|
111
|
-
|
|
112
|
-
|
|
112
|
+
// `MdsiteMenuItem` is imported from `~/utils/mdsite-config` (see top of file).
|
|
113
|
+
// It is the same recursive shape that was previously defined locally as
|
|
114
|
+
// `MdsiteMenuItem`. Single source of truth lives in `utils/mdsite-config.ts`
|
|
115
|
+
// so the Nuxt runtime-config type generator can infer it correctly.
|
|
113
116
|
/**
|
|
114
117
|
* Normalize URL path so a trailing /index resolves to its parent.
|
|
115
118
|
* Mirrors filePathToUrlPath behavior so menu paths match content routes.
|
|
@@ -161,7 +164,7 @@ function resolvePath(menuPath: string, contextPath: string): string {
|
|
|
161
164
|
* Process menu items recursively and build minimal tree structure
|
|
162
165
|
*/
|
|
163
166
|
async function processMenuItems(
|
|
164
|
-
items:
|
|
167
|
+
items: MdsiteMenuItem[],
|
|
165
168
|
contextPath: string,
|
|
166
169
|
order: number = 0
|
|
167
170
|
): Promise<{ nodes: MinimalTreeNode[], nextOrder: number }> {
|
|
@@ -371,14 +374,14 @@ async function buildFallbackNavigationTree(sourceDir: string): Promise<MinimalTr
|
|
|
371
374
|
* Load the menu array from a candidate config file (legacy _menu.yml/yaml or mdsite.yml).
|
|
372
375
|
* Returns null if the file is missing, unreadable, has no menu key, or has an empty menu.
|
|
373
376
|
*/
|
|
374
|
-
async function tryReadMenuFromConfig(configPath: string): Promise<
|
|
377
|
+
async function tryReadMenuFromConfig(configPath: string): Promise<MdsiteMenuItem[] | null> {
|
|
375
378
|
if (!await fs.pathExists(configPath)) {
|
|
376
379
|
return null
|
|
377
380
|
}
|
|
378
381
|
|
|
379
382
|
try {
|
|
380
383
|
const content = await fs.readFile(configPath, 'utf-8')
|
|
381
|
-
const parsed = parseYaml(content) as { menu?:
|
|
384
|
+
const parsed = parseYaml(content) as { menu?: MdsiteMenuItem[] } | null
|
|
382
385
|
if (parsed && Array.isArray(parsed.menu) && parsed.menu.length > 0) {
|
|
383
386
|
return parsed.menu
|
|
384
387
|
}
|
|
@@ -393,14 +396,14 @@ async function tryReadMenuFromConfig(configPath: string): Promise<MenuItemType[]
|
|
|
393
396
|
* Try to read menu items from a plain (non-wrapped) legacy _menu.yml/yaml file.
|
|
394
397
|
* Returns null if the file is missing, unreadable, or doesn't contain a non-empty array.
|
|
395
398
|
*/
|
|
396
|
-
async function tryReadLegacyMenuFile(menuPath: string): Promise<
|
|
399
|
+
async function tryReadLegacyMenuFile(menuPath: string): Promise<MdsiteMenuItem[] | null> {
|
|
397
400
|
if (!await fs.pathExists(menuPath)) {
|
|
398
401
|
return null
|
|
399
402
|
}
|
|
400
403
|
|
|
401
404
|
try {
|
|
402
405
|
const content = await fs.readFile(menuPath, 'utf-8')
|
|
403
|
-
const parsed = parseYaml(content) as
|
|
406
|
+
const parsed = parseYaml(content) as MdsiteMenuItem[] | null
|
|
404
407
|
if (Array.isArray(parsed) && parsed.length > 0) {
|
|
405
408
|
return parsed
|
|
406
409
|
}
|
|
@@ -420,7 +423,7 @@ async function tryReadLegacyMenuFile(menuPath: string): Promise<MenuItemType[] |
|
|
|
420
423
|
* 5. <sourceDir>/mdsite.yml
|
|
421
424
|
* Returns the first non-empty menu array, or null if none resolve to a menu.
|
|
422
425
|
*/
|
|
423
|
-
async function loadMenuConfig(sourceDir: string): Promise<
|
|
426
|
+
async function loadMenuConfig(sourceDir: string): Promise<MdsiteMenuItem[] | null> {
|
|
424
427
|
const candidates: { path: string, isLegacy: boolean }[] = [
|
|
425
428
|
{ path: path.join(sourceDir, '_menu.yml'), isLegacy: true },
|
|
426
429
|
{ path: path.join(sourceDir, '_menu.yaml'), isLegacy: true }
|
|
@@ -2,6 +2,30 @@ import fs from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import YAML from 'yaml';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Recursive shape of a single `mdsite.yml` `menu:` entry.
|
|
7
|
+
*
|
|
8
|
+
* - `string` → flat link to a markdown slug
|
|
9
|
+
* (e.g. `- genesis`, `- resurrections`)
|
|
10
|
+
* - `null` → visual separator (`===` in YAML)
|
|
11
|
+
* - `Record<string, MdsiteMenuValue>` → group: object whose keys are group
|
|
12
|
+
* labels (e.g. `"Members of the Trinity":`) and whose
|
|
13
|
+
* values are `MdsiteMenuValue` items
|
|
14
|
+
* - `MdsiteMenuValue` (the value side of a group) can be:
|
|
15
|
+
* - `string` → alias link with custom title
|
|
16
|
+
* (e.g. `"Job": https://...`, `"Homepage": index`)
|
|
17
|
+
* - `null` → group label only (renders as a heading, no link)
|
|
18
|
+
* - `MdsiteMenuItem[]` → nested submenu (recursive)
|
|
19
|
+
*
|
|
20
|
+
* Keeping this a `type` alias (not an `interface`) and avoiding `any` lets
|
|
21
|
+
* Nuxt's runtime-config type generator infer `runtimeConfig.public.siteConfig`
|
|
22
|
+
* without collapsing `menu` to `{}[]`. See `nuxt.config.ts` for the cast
|
|
23
|
+
* history this replaces.
|
|
24
|
+
*/
|
|
25
|
+
export type MdsiteMenuItem = string | null | MdsiteMenuGroup
|
|
26
|
+
export type MdsiteMenuGroup = { [key: string]: MdsiteMenuValue }
|
|
27
|
+
export type MdsiteMenuValue = string | null | MdsiteMenuItem[]
|
|
28
|
+
|
|
5
29
|
export interface MdsiteConfig {
|
|
6
30
|
content?: {
|
|
7
31
|
path?: string
|
|
@@ -11,7 +35,7 @@ export interface MdsiteConfig {
|
|
|
11
35
|
bibleTooltips: boolean
|
|
12
36
|
sourceEdit: boolean
|
|
13
37
|
}
|
|
14
|
-
menu:
|
|
38
|
+
menu: MdsiteMenuItem[]
|
|
15
39
|
footer: string[]
|
|
16
40
|
server: {
|
|
17
41
|
output: string
|