diff --git a/.vitepress/config.js b/.vitepress/config.js index b9f6082e0..9cc890d87 100644 --- a/.vitepress/config.js +++ b/.vitepress/config.js @@ -2,16 +2,11 @@ import gitCommitInfo from 'git-commit-info' import { defineConfig } from 'vitepress' import { pagefindPlugin } from 'vitepress-plugin-pagefind' import { generateSidebar } from 'vitepress-sidebar' -import { dovecotMdExtend, initDovecotMd } from '../lib/markdown.js' +import { dovecotMdExtend } from '../lib/markdown.js' import { getExcludes } from '../lib/utility.js' const base = '/2.4' -// Need to bootstrap configuration for Dovecot markdown driver (specifically, -// loading all data files to allow existence checking), or else the markdown -// processing will begin before Dovecot link markup is enabled -await initDovecotMd(base) - export default defineConfig({ title: "Dovecot CE", description: "Dovecot CE Documentation", @@ -107,7 +102,7 @@ export default defineConfig({ }, markdown: { - config: (md) => dovecotMdExtend(md), + config: async (md) => await dovecotMdExtend(md), image: { lazyLoading: true, }, diff --git a/.vitepress/local.js.dist b/.vitepress/local.js.dist deleted file mode 100644 index a43e56659..000000000 --- a/.vitepress/local.js.dist +++ /dev/null @@ -1,58 +0,0 @@ -/* This file allows configuration overrides of various core settings. */ - -// Allows custom mapping of data sources. -// It is used by the VitePress data loaders to determine what data to -// load for export. -// -// By default, all data files are loaded from "../data" directory -// (paths are relative to "/lib"). -// -// Keys are data identifiers, Values are location RELATIVE TO -// * "/lib" DIRECTORY. -export const data_paths = { - // doveadm: '../data/doveadm.js', -} - -// A listing of files to watch to refresh data loaders in dev mode. -// See: https://vitepress.dev/guide/data-loading#data-from-local-files -// Paths are relative to project base. -// -// Supports fast-glob: https://github.com/mrmlnc/fast-glob#pattern-syntax -// -// Default: [ 'docs/**/*.md', 'docs/**/*.inc', 'data/**/*' ] -export const watch_paths = [] - -// A listing of paths containing man files. -// Paths are relative to project base. -// -// Supports fast-glob: https://github.com/mrmlnc/fast-glob#pattern-syntax -// -// Default: [ 'docs/core/man/*.[[:digit:]].md' ] -export const man_paths = [] - -// A listing of paths containing plugin files. -// Paths are relative to project base. -// -// Supports fast-glob: https://github.com/mrmlnc/fast-glob#pattern-syntax -// -// Default: [ 'docs/core/plugins/*.md' ] -export const plugin_paths = [] - -// Enable additional labels to support in Dovecot-specific markdown -// processing (i.e. [[xyz,...]]). -export const markdown_extend = { - - // Init function. Return value is configuration options to add to markdown - // object. - // init: () => { return {} }, - - // Opening tag function. Returns opening tag. - // open: (mode, parts, opts, env) => { return '' }, - - // Body function. Returns body text. - // body: (mode, env) => { return '' }, - - // Close tag function. Returns closing tag. - // close: (mode, env) => { return '' }, - -} diff --git a/README.md b/README.md index 18a6b4c34..28f3aba27 100644 --- a/README.md +++ b/README.md @@ -158,6 +158,71 @@ The data files live in the base `/data` directory. Each file attempts to be self-documenting, but they are all essentially large JSON objects. Developers should need to know basically no JavaScript to be able to edit the files. +### VitePress Path Overrides + +Various paths used for VitePress static generation can be overridden using +the `dovecot` object in the `themeConfig` VitePress configuration setting. + +#### data_paths + +Allows custom mapping of data sources. + +It is used by the VitePress data loaders to determine what data to +load for export. + +Keys are data identifiers, Values are location RELATIVE TO `/lib` +DIRECTORY. + +#### man_includes + +A list of additional paths (other than the `include/` directory of the man +folder) where include files can live. + +#### man_paths + +A listing of paths containing man files. +Paths are relative to project base. + +Supports fast-glob: https://github.com/mrmlnc/fast-glob#pattern-syntax + +#### markdown_extend + +An object containing callbacks that enable additional labels to support in +Dovecot-specific markdown processing (i.e. [[xyz,...]]). + +##### open + +Opening tag function. Returns opening tag. + +Example: `open: (mode, parts, opts, env) => { return '' }` + +##### body + +Body function. Returns body text. + +Example: `body: (mode, env) => { return '' }` + +##### close + +Close tag function. Returns closing tag. + +Example: `close: (mode, env) => { return '' }` + +#### plugin_paths + +A listing of paths containing plugin files. +Paths are relative to project base. + +Supports fast-glob: https://github.com/mrmlnc/fast-glob#pattern-syntax + +#### watch_paths + +An array of file patterns to watch to refresh data loaders in dev mode. +See: https://vitepress.dev/guide/data-loading#data-from-local-files +Paths are relative to project base. + +Supports fast-glob: https://github.com/mrmlnc/fast-glob#pattern-syntax + ### Dovecot Markdown Extensions Markdown has been extended to allow various Dovecot-specific tasks to be diff --git a/lib/data/doveadm.data.js b/lib/data/doveadm.data.js index c76fd5e93..1c54c7e47 100644 --- a/lib/data/doveadm.data.js +++ b/lib/data/doveadm.data.js @@ -1,6 +1,6 @@ import { doveadm_arg_types, doveadm_flag_types, getDoveadmCmdLine } from '../doveadm.js' import { getVitepressMd } from '../markdown.js' -import { loadData, normalizeArrayData, watchFiles } from '../utility.js' +import { addWatchPaths, loadData, normalizeArrayData } from '../utility.js' import slugify from '@sindresorhus/slugify' const doveadm_userargs = { @@ -147,11 +147,10 @@ async function normalizeDoveadm(doveadm) { } } -export default { - watch: await watchFiles(), +export default addWatchPaths({ async load() { return await normalizeDoveadm( structuredClone((await loadData('doveadm')).doveadm) ) } -} +}) diff --git a/lib/data/event_categories.data.js b/lib/data/event_categories.data.js index 4e009ab4f..37edcb12a 100644 --- a/lib/data/event_categories.data.js +++ b/lib/data/event_categories.data.js @@ -1,5 +1,5 @@ import { getVitepressMd } from '../markdown.js' -import { loadData, watchFiles } from '../utility.js' +import { addWatchPaths, loadData } from '../utility.js' async function normalizeEventCategories(categories) { const md = await getVitepressMd() @@ -11,11 +11,10 @@ async function normalizeEventCategories(categories) { return categories } -export default { - watch: await watchFiles(), +export default addWatchPaths({ async load() { return await normalizeEventCategories( structuredClone((await loadData('event_categories')).categories) ) } -} +}) diff --git a/lib/data/event_reasons.data.js b/lib/data/event_reasons.data.js index 9ddd930ac..5f3cdb11f 100644 --- a/lib/data/event_reasons.data.js +++ b/lib/data/event_reasons.data.js @@ -1,5 +1,5 @@ import { getVitepressMd } from '../markdown.js' -import { loadData, watchFiles } from '../utility.js' +import { addWatchPaths, loadData } from '../utility.js' async function normalizeEventReasons(reasons) { const md = await getVitepressMd() @@ -11,11 +11,10 @@ async function normalizeEventReasons(reasons) { return reasons } -export default { - watch: await watchFiles(), +export default addWatchPaths({ async load() { return await normalizeEventReasons( structuredClone((await loadData('event_reasons')).reasons) ) } -} +}) diff --git a/lib/data/events.data.js b/lib/data/events.data.js index 82696b976..e3446f255 100644 --- a/lib/data/events.data.js +++ b/lib/data/events.data.js @@ -1,4 +1,4 @@ -import { loadData, normalizeArrayData, watchFiles } from '../utility.js' +import { addWatchPaths, loadData, normalizeArrayData } from '../utility.js' import { getVitepressMd } from '../markdown.js' /* Take the events list and normalize entries and process inheritance. */ @@ -124,8 +124,7 @@ async function normalizeEvents(events, global_inherits, inherits) { return events } -export default { - watch: await watchFiles(), +export default addWatchPaths({ async load() { const data = await loadData('events') return await normalizeEvents( @@ -134,4 +133,4 @@ export default { structuredClone(data.inherits) ) } -} +}) diff --git a/lib/data/lua.data.js b/lib/data/lua.data.js index 9490c5c87..363982f5f 100644 --- a/lib/data/lua.data.js +++ b/lib/data/lua.data.js @@ -1,5 +1,5 @@ import { getVitepressMd } from '../markdown.js' -import { loadData, watchFiles } from '../utility.js' +import { addWatchPaths, loadData } from '../utility.js' async function normalizeLuaConstants(lua) { const md = await getVitepressMd() @@ -62,8 +62,7 @@ async function normalizeLuaVariables(lua) { return out } -export default { - watch: await watchFiles(), +export default addWatchPaths({ async load() { const data = await(loadData('lua')) @@ -73,4 +72,4 @@ export default { variables: await normalizeLuaVariables(data.lua_variables) } } -} +}) diff --git a/lib/data/settings.data.js b/lib/data/settings.data.js index 9311ee2a4..7df26c155 100644 --- a/lib/data/settings.data.js +++ b/lib/data/settings.data.js @@ -1,4 +1,4 @@ -import { loadData, normalizeArrayData, watchFiles } from '../utility.js' +import { addWatchPaths, loadData, normalizeArrayData } from '../utility.js' import { getVitepressMd } from '../markdown.js' async function normalizeSettings(settings) { @@ -69,11 +69,10 @@ async function normalizeSettings(settings) { return data } -export default { - watch: await watchFiles(), +export default addWatchPaths({ async load() { return await normalizeSettings( structuredClone((await loadData('settings')).settings) ) } -} +}) diff --git a/lib/markdown.js b/lib/markdown.js index 9949e0ea1..60feacbee 100644 --- a/lib/markdown.js +++ b/lib/markdown.js @@ -6,34 +6,7 @@ import { createMarkdownRenderer } from 'vitepress' import { frontmatterIter, loadData, manFiles, markdownExtension, pluginFiles, resolveURL } from './utility.js' let md_conf = null -export async function initDovecotMd(base) { - if (md_conf !== null) { - return - } - - md_conf = { - ...{ - base: base, - doveadm: (await loadData('doveadm')).doveadm, - events: (await loadData('events')).events, - linkoverrides: (await loadData('links_overrides')).links_overrides, - man: (await manFiles()).flatMap((x) => { - return fg.sync(x).map((y) => { - const str = path.basename(y) - return str.substring(0, str.indexOf('.')) - }) - }), - plugins: (await pluginFiles()).flatMap((x) => - fg.sync(x).map((y) => path.basename(y, '.md')) - ), - settings: (await loadData('settings')).settings, - updates: (await loadData('updates')).updates - }, - ...(await markdownExtension()) - } -} - -export function dovecotMdExtend(md) { +export async function dovecotMdExtend(md) { md.use(containerPlugin, 'todo', { render: function(tokens, idx) { if (tokens[idx].nesting === 1) { @@ -46,7 +19,14 @@ export function dovecotMdExtend(md) { md.use(deflistPlugin) if (md_conf === null) { - throw new Error('Must call initDovecotMd() before calling this function!') + md_conf = { + base: globalThis.VITEPRESS_CONFIG.site.base, + doveadm: (await loadData('doveadm')).doveadm, + events: (await loadData('events')).events, + linkoverrides: (await loadData('links_overrides')).links_overrides, + settings: (await loadData('settings')).settings, + updates: (await loadData('updates')).updates + } } md.use(dovecot_markdown, md_conf) @@ -58,8 +38,7 @@ export async function getVitepressMd() { if (vitepress_md === null) { const config = globalThis.VITEPRESS_CONFIG - await initDovecotMd(config.site.base) - vitepress_md = dovecotMdExtend(await createMarkdownRenderer( + vitepress_md = await dovecotMdExtend(await createMarkdownRenderer( config.srcDir, config.markdown, config.site.base, @@ -219,6 +198,8 @@ function dovecot_markdown(md, opts) { hash = parts[2] ? parts[2] : false; env.args = parts[3] ? parts[3] : 1; + initManFiles() + if (!opts.man.includes(env.inner)) { handle_error('man link missing: ' + env.inner) return '' @@ -233,6 +214,8 @@ function dovecot_markdown(md, opts) { env.args = parts[2] ? parts[2] : undefined const plugin = env.inner.replaceAll('-', '_') + initPluginFiles() + if (!opts.plugins.includes(plugin)) { handle_error('plugin link missing: ' + env.inner) return '' @@ -296,6 +279,8 @@ function dovecot_markdown(md, opts) { opts.base) + '">' default: + initMarkdownExtend() + return handle_default(mode, opts.markdown?.open?.(mode, parts, opts, env)) } @@ -426,6 +411,31 @@ function dovecot_markdown(md, opts) { opts.dovecotlinks = { ...links, ...opts.linkoverrides } } + function initManFiles() { + if (!opts.man) { + opts.man = manFiles().flatMap((x) => { + return fg.sync(x).map((y) => { + const str = path.basename(y) + return str.substring(0, str.indexOf('.')) + }) + }) + } + } + + function initMarkdownExtend() { + if (!opts.markdown) { + opts.markdown = markdownExtension() + } + } + + function initPluginFiles() { + if (!opts.plugins) { + opts.plugins = pluginFiles().flatMap((x) => + fg.sync(x).map((y) => path.basename(y, '.md')) + ) + } + } + md.inline.ruler.after('emphasis', 'dovecot_brackets', process_brackets) md.renderer.rules.dovecot_open = dovecot_open md.renderer.rules.dovecot_body = dovecot_body diff --git a/lib/utility.js b/lib/utility.js index dbb5d10cf..e6587952a 100644 --- a/lib/utility.js +++ b/lib/utility.js @@ -9,8 +9,6 @@ import { fileURLToPath } from 'url' const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); -const local_conf_path = __dirname + '/../.vitepress/local.js' - export function normalizeArrayData(data, keys) { for (const [k, v] of Object.entries(data)) { if (v) { @@ -28,34 +26,13 @@ export function normalizeArrayData(data, keys) { return data } -let local_conf = null -async function loadLocalConf() { - if (local_conf === null) { - if (fs.existsSync(local_conf_path)) { - local_conf = await import(local_conf_path) - } else { - local_conf = false - } - } - - return local_conf -} - -export async function markdownExtension() { - const lconf = await loadLocalConf() - - return { - ...{ - markdown: lconf.markdown_extend ?? {}, - }, - ...(lconf.markdown_extend?.init?.() ?? {}) - } +export function markdownExtension() { + return globalThis.VITEPRESS_CONFIG.userConfig.themeConfig.dovecot?.markdown_extend + ?? {} } export async function loadData(id) { - /* Check for config override file. */ - const lconf = await loadLocalConf() - const path = lconf?.data_paths?.[id] ?? + const path = globalThis.VITEPRESS_CONFIG.userConfig.themeConfig.dovecot?.data_paths?.[id] ?? ('../data/' + id + '.js') try { @@ -66,34 +43,28 @@ export async function loadData(id) { } } -export async function watchFiles() { - /* Check for config override file. */ - const lconf = await loadLocalConf() - - return lconf?.watch_paths ?? - [ 'docs/**/*.md', 'docs/**/*.inc', 'data/**/*' ] +export function addWatchPaths(obj) { + return { + ...obj, + ...{ + watch: globalThis.VITEPRESS_CONFIG.userConfig.themeConfig.dovecot?.watch_paths ?? [ 'docs/**/*.md', 'docs/**/*.inc', 'data/**/*' ] + } + } } -export async function manFiles() { - /* Check for config override file. */ - const lconf = await loadLocalConf() - - return lconf?.man_paths ?? - [ 'docs/core/man/*.[[:digit:]].md' ] +export function manFiles() { + return globalThis.VITEPRESS_CONFIG.userConfig.themeConfig.dovecot?.man_paths + ?? [ 'docs/core/man/*.[[:digit:]].md' ] } -export async function manIncludes() { - const lconf = await loadLocalConf() - return lconf?.man_includes ?? - [ 'docs/core/man/include/*.inc' ] +export function manIncludes() { + return globalThis.VITEPRESS_CONFIG.userConfig.themeConfig.dovecot?.man_includes + ?? [ 'docs/core/man/include/*.inc' ] } -export async function pluginFiles() { - /* Check for config override file. */ - const lconf = await loadLocalConf() - - return lconf?.plugin_paths ?? - [ 'docs/core/plugins/*.md' ] +export function pluginFiles() { + return globalThis.VITEPRESS_CONFIG.userConfig.themeConfig.dovecot?.plugin_paths + ?? [ 'docs/core/plugins/*.md' ] } export function getExcludes(srcDirs = [ 'docs' ]) { diff --git a/util/generate_man.js b/util/generate_man.js index a9e566775..d93fb270d 100755 --- a/util/generate_man.js +++ b/util/generate_man.js @@ -126,8 +126,8 @@ const main = async (component, outPath) => { } /* Generate list of man files. */ - const files = (await manFiles()).flatMap((x) => fg.sync(x)) - const includes = (await manIncludes()).flatMap((x) => fg.sync(x)) + const files = manFiles().flatMap((x) => fg.sync(x)) + const includes = manIncludes().flatMap((x) => fg.sync(x)) /* Get hash of last git commit. */ const gitHash = gitCommitInfo().shortHash