@sullux/markdown-docs 2.0.0 → 2.0.1

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/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 || assetPath.startsWith('http') || assetPath.startsWith('<svg') || assetPath.startsWith('data:') || assetPath.startsWith('/')) {
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
 
@@ -116,4 +70,4 @@ const renderPageLayout = ({ title, siteTitle, navTree, toc, contentHtml, current
116
70
  </body></html>`
117
71
  }
118
72
 
119
- module.exports = { calcRelativeHref, renderPageLayout }
73
+ 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/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 (url.startsWith('http://') || url.startsWith('https://') || url.startsWith('/') || url.startsWith('#')) return url
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 ? node.children.map((c) => c.value || '').join('') : ''
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
- items.push({ ...linkInfo, children: subLists })
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 sectionTitle = block.children ? block.children.map((c) => c.value || '').join('') : ''
49
- currentSection = { type: 'section', title: sectionTitle, items: [] }
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sullux/markdown-docs",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "A zero-dependency, local-first static documentation site generator compiling GitBook-style Markdown docs.",
5
5
  "main": "./index.js",
6
6
  "bin": {