@sullux/markdown-docs 2.0.0 → 2.0.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/lib/config.js +2 -1
- package/lib/layout.js +6 -51
- package/lib/nav.js +46 -0
- package/lib/site.js +2 -1
- package/lib/summary.js +10 -12
- package/lib/theme.css +10 -0
- package/package.json +1 -1
package/lib/config.js
CHANGED
|
@@ -62,8 +62,9 @@ const normalizeConfig = (opts = {}) => {
|
|
|
62
62
|
const favicon = fileConfig.favicon || opts.favicon || ''
|
|
63
63
|
const links = fileConfig.links || opts.links || []
|
|
64
64
|
const theme = { ...(fileConfig.theme || {}), ...(opts.theme || {}) }
|
|
65
|
+
const math = opts.math !== undefined ? opts.math : fileConfig.math
|
|
65
66
|
|
|
66
|
-
return { input, output, title, baseUrl, logo, favicon, links, theme }
|
|
67
|
+
return { input, output, title, baseUrl, logo, favicon, links, theme, math }
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
module.exports = { parseArgs, normalizeConfig, loadFileConfig }
|
package/lib/layout.js
CHANGED
|
@@ -1,58 +1,12 @@
|
|
|
1
|
-
const path = require('node:path')
|
|
2
1
|
const { getCss } = require('./theme')
|
|
3
2
|
const { getSearchScript } = require('./search')
|
|
4
3
|
const { SVGS } = require('./icons')
|
|
5
|
-
|
|
6
|
-
const normalizeTargetHref = (href) => {
|
|
7
|
-
if (!href) return '#'
|
|
8
|
-
if (href.startsWith('http://') || href.startsWith('https://') || href.startsWith('/') || href.startsWith('#') || href.startsWith('mailto:')) {
|
|
9
|
-
return href
|
|
10
|
-
}
|
|
11
|
-
return href
|
|
12
|
-
.replace(/(?:^|\/)README\.(?:md|html)(#.*)?$/i, (match) => match.replace(/README\.(?:md|html)/i, 'index.html'))
|
|
13
|
-
.replace(/\.md(#.*)?$/, (match) => match.replace(/\.md/, '.html'))
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const calcRelativeHref = (currentHref, targetHref) => {
|
|
17
|
-
if (!targetHref) return '#'
|
|
18
|
-
const normalizedTarget = normalizeTargetHref(targetHref)
|
|
19
|
-
if (normalizedTarget.startsWith('http://') || normalizedTarget.startsWith('https://') || normalizedTarget.startsWith('/') || normalizedTarget.startsWith('#') || normalizedTarget.startsWith('mailto:')) {
|
|
20
|
-
return normalizedTarget
|
|
21
|
-
}
|
|
22
|
-
const normalizedCurrent = normalizeTargetHref(currentHref)
|
|
23
|
-
const rel = path.relative(path.dirname(normalizedCurrent), normalizedTarget)
|
|
24
|
-
return rel || './'
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const renderNavTree = (items, currentHref) => {
|
|
28
|
-
if (!items?.length) return ''
|
|
29
|
-
let html = '', group = []
|
|
30
|
-
const flushGroup = () => {
|
|
31
|
-
if (!group.length) return ''
|
|
32
|
-
const list = `<ul class="nav-list">\n` + group.map((item) => {
|
|
33
|
-
const active = normalizeTargetHref(item.href) === normalizeTargetHref(currentHref) ? ' active' : ''
|
|
34
|
-
const href = calcRelativeHref(currentHref, item.href)
|
|
35
|
-
const sub = item.children?.length ? renderNavTree(item.children, currentHref) : ''
|
|
36
|
-
return `<li class="nav-item"><a href="${href}" class="nav-link${active}">${item.title}</a>${sub ? `<div class="nav-sub">${sub}</div>` : ''}</li>\n`
|
|
37
|
-
}).join('') + `</ul>\n`
|
|
38
|
-
group = []
|
|
39
|
-
return list
|
|
40
|
-
}
|
|
41
|
-
for (const item of items) {
|
|
42
|
-
if (item.type === 'section') {
|
|
43
|
-
html += `${flushGroup()}<div class="nav-section-title">${item.title}</div>\n`
|
|
44
|
-
if (item.items?.length) html += renderNavTree(item.items, currentHref)
|
|
45
|
-
} else group.push(item)
|
|
46
|
-
}
|
|
47
|
-
return html + flushGroup()
|
|
48
|
-
}
|
|
4
|
+
const { calcRelativeHref, renderNavTree } = require('./nav')
|
|
49
5
|
|
|
50
6
|
const renderTocList = (toc) => toc?.length ? `<div class="toc-title">On this page</div>\n<ul class="toc-list">\n` + toc.map((i) => `<li class="toc-item level-${i.level}"><a href="#${i.id}" class="toc-link" onclick="closeAllDrawers()">${i.title}</a></li>`).join('') + `</ul>\n` : ''
|
|
51
7
|
|
|
52
8
|
const resolveAssetHref = (currentHref, assetPath) => {
|
|
53
|
-
if (!assetPath ||
|
|
54
|
-
return assetPath
|
|
55
|
-
}
|
|
9
|
+
if (!assetPath || ['http', '<svg', 'data:', '/'].some((p) => assetPath.startsWith(p))) return assetPath
|
|
56
10
|
return calcRelativeHref(currentHref, assetPath.replace(/^\.\//, ''))
|
|
57
11
|
}
|
|
58
12
|
|
|
@@ -70,16 +24,17 @@ const renderFavicon = (f) => f ? `<link rel="icon" href="${f}" />` : `<link rel=
|
|
|
70
24
|
|
|
71
25
|
const renderHeaderLinks = (links = []) => links?.length ? `<div class="header-links">` + links.map((l) => `<a href="${l.url}" target="_blank" rel="noopener" class="header-link">${l.title} ↗</a>`).join('') + `</div>` : ''
|
|
72
26
|
|
|
73
|
-
const renderPageLayout = ({ title, siteTitle, navTree, toc, contentHtml, currentHref, logo, favicon, links, theme }) => {
|
|
27
|
+
const renderPageLayout = ({ title, siteTitle, navTree, toc, contentHtml, currentHref, logo, favicon, links, theme, head = [] }) => {
|
|
74
28
|
const sidebarNav = renderNavTree(navTree, currentHref)
|
|
75
29
|
return `<!DOCTYPE html><html lang="en"><head>
|
|
76
30
|
<meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
77
31
|
<title>${title}</title>${renderFavicon(favicon)}<style>${getCss(theme)}</style>
|
|
78
|
-
<script>
|
|
32
|
+
${head?.length ? head.join('\n') + '\n' : ''} <script>
|
|
79
33
|
function setThemeMode(m) {
|
|
80
34
|
try { localStorage.setItem('sullux-theme-mode', m); } catch(e) {}
|
|
81
35
|
if (m === 'light' || m === 'dark') document.documentElement.setAttribute('data-theme', m); else document.documentElement.removeAttribute('data-theme');
|
|
82
36
|
document.querySelectorAll('.theme-opt').forEach(function(b) { b.classList.toggle('active', b.getAttribute('data-mode') === m); });
|
|
37
|
+
try { window.dispatchEvent(new CustomEvent('@sullux/markdown:theme', { detail: { mode: m } })); } catch(e) {}
|
|
83
38
|
}
|
|
84
39
|
function toggleDrawer(id) {
|
|
85
40
|
var d = document.getElementById(id), b = document.getElementById('drawer-backdrop'), open = d?.classList.contains('open');
|
|
@@ -116,4 +71,4 @@ const renderPageLayout = ({ title, siteTitle, navTree, toc, contentHtml, current
|
|
|
116
71
|
</body></html>`
|
|
117
72
|
}
|
|
118
73
|
|
|
119
|
-
module.exports = {
|
|
74
|
+
module.exports = { renderPageLayout }
|
package/lib/nav.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const path = require('node:path')
|
|
2
|
+
|
|
3
|
+
const normalizeTargetHref = (href) => {
|
|
4
|
+
if (!href) return '#'
|
|
5
|
+
if (['http://', 'https://', '/', '#', 'mailto:'].some((p) => href.startsWith(p))) return href
|
|
6
|
+
return href
|
|
7
|
+
.replace(/(?:^|\/)README\.(?:md|html)(#.*)?$/i, (match) => match.replace(/README\.(?:md|html)/i, 'index.html'))
|
|
8
|
+
.replace(/\.md(#.*)?$/, (match) => match.replace(/\.md/, '.html'))
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const calcRelativeHref = (currentHref, targetHref) => {
|
|
12
|
+
if (!targetHref) return '#'
|
|
13
|
+
const normalizedTarget = normalizeTargetHref(targetHref)
|
|
14
|
+
if (['http://', 'https://', '/', '#', 'mailto:'].some((p) => normalizedTarget.startsWith(p))) return normalizedTarget
|
|
15
|
+
const normalizedCurrent = normalizeTargetHref(currentHref)
|
|
16
|
+
const rel = path.relative(path.dirname(normalizedCurrent), normalizedTarget)
|
|
17
|
+
return rel || './'
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const renderNavTree = (items, currentHref) => {
|
|
21
|
+
if (!items?.length) return ''
|
|
22
|
+
let html = '', group = []
|
|
23
|
+
const flushGroup = () => {
|
|
24
|
+
if (!group.length) return ''
|
|
25
|
+
const list = `<ul class="nav-list">\n` + group.map((item) => {
|
|
26
|
+
const sub = item.children?.length ? renderNavTree(item.children, currentHref) : ''
|
|
27
|
+
if (!item.href) {
|
|
28
|
+
return `<li class="nav-item nav-category"><span class="nav-label">${item.title}</span>${sub ? `<div class="nav-sub">${sub}</div>` : ''}</li>\n`
|
|
29
|
+
}
|
|
30
|
+
const active = normalizeTargetHref(item.href) === normalizeTargetHref(currentHref) ? ' active' : ''
|
|
31
|
+
const href = calcRelativeHref(currentHref, item.href)
|
|
32
|
+
return `<li class="nav-item"><a href="${href}" class="nav-link${active}">${item.title}</a>${sub ? `<div class="nav-sub">${sub}</div>` : ''}</li>\n`
|
|
33
|
+
}).join('') + `</ul>\n`
|
|
34
|
+
group = []
|
|
35
|
+
return list
|
|
36
|
+
}
|
|
37
|
+
for (const item of items) {
|
|
38
|
+
if (item.type === 'section') {
|
|
39
|
+
html += `${flushGroup()}<div class="nav-section-title">${item.title}</div>\n`
|
|
40
|
+
if (item.items?.length) html += renderNavTree(item.items, currentHref)
|
|
41
|
+
} else group.push(item)
|
|
42
|
+
}
|
|
43
|
+
return html + flushGroup()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = { normalizeTargetHref, calcRelativeHref, renderNavTree }
|
package/lib/site.js
CHANGED
|
@@ -53,7 +53,7 @@ const generateSite = (options = {}) => {
|
|
|
53
53
|
const rawMd = fs.readFileSync(file.fullPath, 'utf8')
|
|
54
54
|
const ast = parse(rawMd)
|
|
55
55
|
const toc = extractToc(ast)
|
|
56
|
-
const rawHtml = markdownToHtml(ast)
|
|
56
|
+
const { html: rawHtml, head: docHead } = markdownToHtml(ast)
|
|
57
57
|
const contentHtml = rewriteMarkdownLinks(rawHtml)
|
|
58
58
|
const relHtmlPath = normalizeHtmlPath(file.relPath)
|
|
59
59
|
const outHtmlPath = path.join(config.output, relHtmlPath)
|
|
@@ -76,6 +76,7 @@ const generateSite = (options = {}) => {
|
|
|
76
76
|
favicon: config.favicon,
|
|
77
77
|
links: config.links,
|
|
78
78
|
theme: config.theme,
|
|
79
|
+
head: docHead,
|
|
79
80
|
})
|
|
80
81
|
|
|
81
82
|
fs.writeFileSync(outHtmlPath, fullHtml, 'utf8')
|
package/lib/summary.js
CHANGED
|
@@ -4,7 +4,7 @@ const { parse } = require('@sullux/markdown-compiler')
|
|
|
4
4
|
|
|
5
5
|
const normalizeHref = (url) => {
|
|
6
6
|
if (!url) return '#'
|
|
7
|
-
if (
|
|
7
|
+
if (['http://', 'https://', '/', '#'].some((p) => url.startsWith(p))) return url
|
|
8
8
|
return url
|
|
9
9
|
.replace(/(?:^|\/)README\.md(#.*)?$/i, (match) => match.replace(/README\.md/i, 'index.html'))
|
|
10
10
|
.replace(/\.md(#.*)?$/, (match) => match.replace(/\.md/, '.html'))
|
|
@@ -19,7 +19,7 @@ const parseSummaryMd = (content) => {
|
|
|
19
19
|
const items = []
|
|
20
20
|
const listChildren = listNode.children || listNode.items || []
|
|
21
21
|
for (const item of listChildren) {
|
|
22
|
-
let linkInfo = null
|
|
22
|
+
let linkInfo = null, textTitle = ''
|
|
23
23
|
const subLists = []
|
|
24
24
|
const itemBlocks = item.children || (Array.isArray(item) ? [{ type: 'paragraph', children: item }] : [])
|
|
25
25
|
|
|
@@ -27,26 +27,26 @@ const parseSummaryMd = (content) => {
|
|
|
27
27
|
if (child.type === 'paragraph') {
|
|
28
28
|
for (const node of child.children || []) {
|
|
29
29
|
if (node.type === 'link') {
|
|
30
|
-
const title = node.children
|
|
30
|
+
const title = (node.children || []).map((c) => c.value || '').join('')
|
|
31
31
|
linkInfo = { title, href: normalizeHref(node.url) }
|
|
32
|
-
}
|
|
32
|
+
} else if (node.type === 'text') textTitle += node.value || ''
|
|
33
33
|
}
|
|
34
|
+
if (!textTitle) textTitle = (child.children || []).map((c) => c.value || '').join('')
|
|
34
35
|
} else if (child.type === 'bulletList' || child.type === 'orderedList') {
|
|
35
36
|
subLists.push(...extractItems(child))
|
|
36
37
|
}
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
if (linkInfo) {
|
|
40
|
-
|
|
41
|
-
}
|
|
40
|
+
if (linkInfo) items.push({ ...linkInfo, children: subLists })
|
|
41
|
+
else if (textTitle.trim()) items.push({ title: textTitle.trim(), href: null, children: subLists })
|
|
42
42
|
}
|
|
43
43
|
return items
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
for (const block of ast.blocks) {
|
|
47
47
|
if (block.type === 'header' && block.level > 1) {
|
|
48
|
-
const
|
|
49
|
-
currentSection = { type: 'section', title
|
|
48
|
+
const title = (block.children || []).map((c) => c.value || '').join('')
|
|
49
|
+
currentSection = { type: 'section', title, items: [] }
|
|
50
50
|
nav.push(currentSection)
|
|
51
51
|
} else if (block.type === 'bulletList' || block.type === 'orderedList') {
|
|
52
52
|
const listItems = extractItems(block)
|
|
@@ -85,9 +85,7 @@ const scanDir = (dir, rootDir = dir) => {
|
|
|
85
85
|
|
|
86
86
|
const getNavigationTree = (inputDir) => {
|
|
87
87
|
const summaryPath = path.join(inputDir, 'SUMMARY.md')
|
|
88
|
-
if (fs.existsSync(summaryPath))
|
|
89
|
-
return parseSummaryMd(fs.readFileSync(summaryPath, 'utf8'))
|
|
90
|
-
}
|
|
88
|
+
if (fs.existsSync(summaryPath)) return parseSummaryMd(fs.readFileSync(summaryPath, 'utf8'))
|
|
91
89
|
return scanDir(inputDir)
|
|
92
90
|
}
|
|
93
91
|
|
package/lib/theme.css
CHANGED
|
@@ -357,6 +357,16 @@ body {
|
|
|
357
357
|
border-left-color: var(--accent-color);
|
|
358
358
|
}
|
|
359
359
|
|
|
360
|
+
.nav-label {
|
|
361
|
+
display: block;
|
|
362
|
+
padding: 0.4rem 0.65rem;
|
|
363
|
+
color: var(--text-primary);
|
|
364
|
+
font-size: 0.88rem;
|
|
365
|
+
font-weight: 600;
|
|
366
|
+
line-height: 1.35;
|
|
367
|
+
cursor: default;
|
|
368
|
+
}
|
|
369
|
+
|
|
360
370
|
.nav-sub {
|
|
361
371
|
list-style: none;
|
|
362
372
|
padding-left: 0.75rem;
|
package/package.json
CHANGED