Skip to content

Commit 08f137d

Browse files
committed
update doc builder
- fix links - reorganize the sidebar
1 parent d62b2d7 commit 08f137d

File tree

12 files changed

+474
-337
lines changed

12 files changed

+474
-337
lines changed

.github/workflows/jekyll-gh-pages.yml

+17
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,23 @@ jobs:
3030
- name: Setup Pages
3131
uses: actions/configure-pages@v3
3232

33+
- name: Cache node modules
34+
id: cache-node-modules
35+
uses: actions/cache@v3
36+
with:
37+
path: node_modules
38+
key: ${{ runner.os }}-node-modules
39+
40+
41+
- uses: actions/setup-node@v3
42+
with:
43+
node-version: 18
44+
45+
- if: steps.cache-node-modules.outputs.cache-hit != 'true'
46+
run: npm i --production
47+
48+
- run: make update-meta
49+
3350
- name: Build with Jekyll
3451
uses: actions/jekyll-build-pages@v1
3552
with:

.jekyll/sass/custom/custom.scss

+8
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,18 @@
3737
.nav-list .nav-list-item {
3838
.nav-list-link:hover,
3939
.nav-list-link.active {
40+
color: #264aff !important;
4041
background-image: linear-gradient(-90deg, #262831 0%, #1e2d4f 80%, rgba(32, 31, 35, 0) 100%);
4142
}
4243
}
4344

4445
.main-content a {
4546
text-decoration: none;
4647
}
48+
49+
.main-content {
50+
h1,h2,h3,h4,h5,h6 {
51+
margin-top: 1em;
52+
margin-bottom: 0.5em;
53+
}
54+
}

Makefile

+22-1
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,44 @@ include ./makefile-utils/*.mk
33

44
GH_PAGE_IMAGE=adoyle/gh-pages:v228
55

6+
# 本地编辑与浏览(不用先执行 make build,会自动构建,支持热更新)
67
.PHONY: serve
78
serve:
89
echo "You should press 'ctrl-c' when \"Auto-regeneration: enabled for '/src/site'\" appeared."
910
docker run -it --rm -p 4000:4000 -v "${PWD}:/src/site" ${GH_PAGE_IMAGE}
1011

12+
.PHONY: update-readme
13+
update-readme:
14+
@node ./_doc_builder/update-readme.mjs
15+
16+
.PHONY: update-meta
17+
update-meta:
18+
@node ./_doc_builder/update-meta.mjs
19+
20+
# 本地构建
21+
.PHONY: build
1122
build:
1223
docker run -it --rm -v "${PWD}:/src/site" ${GH_PAGE_IMAGE} \
1324
build --verbose
1425

26+
.PHONY: build-gh
1527
build-gh:
1628
docker run -it --rm -v "${PWD}:/src/site" --entrypoint github-pages ${GH_PAGE_IMAGE} \
1729
build --verbose
1830

31+
# 本地浏览(要先执行 make build)
32+
.PHONY: http
1933
http:
20-
docker run --rm -it -p 4000:80 -v "${PWD}/_site:/usr/share/caddy:ro" caddy:2.6.3-alpine
34+
docker run --rm -it -p 4000:80 \
35+
-v "${PWD}/_doc_builder/Caddyfile:/etc/caddy/Caddyfile" \
36+
-v "${PWD}/_site:/usr/share/caddy:ro" caddy:2.6.3-alpine
2137

38+
.PHONY: debug-gh-pages
2239
debug-gh-pages:
2340
docker run -it --rm -p 4000:4000 -v "${PWD}:/src/site" \
2441
--entrypoint ash \
2542
${GH_PAGE_IMAGE}
43+
44+
.PHONY: clean-index-md
45+
clean-index-md:
46+
fd '^index.md' ./*/ | xargs rm -i

_doc_builder/Caddyfile

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
:80 {
2+
handle_path /Today-I-Learned/* {
3+
root * /usr/share/caddy/
4+
file_server
5+
}
6+
7+
handle {
8+
root * /usr/share/caddy
9+
file_server
10+
}
11+
}

_doc_builder/json2md.mjs

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import json2md from 'json2md';
2+
import {readFile} from 'node:fs/promises';
3+
4+
json2md.converters.text = async (text) => {
5+
return text;
6+
}
7+
8+
json2md.converters.ul = async (array) => {
9+
return `- ${array.join('\n- ')}`;
10+
};
11+
12+
json2md.converters.md = async (filepath) => {
13+
return await readFile(filepath, {encoding: 'utf8'});
14+
};
15+
16+
json2md.converters.toc = ({contents, type}) => {
17+
let tocStr;
18+
19+
if (type === 'html') {
20+
const lastParent = {};
21+
22+
const makeLink = (name) => `<a href="#${encodeURI(name.toLowerCase())}">${name}</a>`;
23+
24+
const makeUI = () => ({
25+
children: [],
26+
toString(parentSpaces = '') {
27+
const {children} = this;
28+
const spaces = parentSpaces.repeat(2);
29+
const childStr = children.map((n) => n.toString(spaces)).join('\n');
30+
return `${parentSpaces}<ul>
31+
${childStr}
32+
${parentSpaces}</ul>`;
33+
},
34+
});
35+
36+
const makeLI = ({name, level}) => ({
37+
name, level,
38+
children: [],
39+
toString(parentSpaces = '') {
40+
const {name, children} = this;
41+
const spaces = parentSpaces.repeat(2);
42+
43+
if (children.length === 0) {
44+
return `${parentSpaces}<li>${makeLink(name)}</li>`;
45+
} else {
46+
const childStr = children.map((n) => n.toString(spaces)).join('\n');
47+
return `${parentSpaces}<li>
48+
${parentSpaces} ${makeLink(name)}
49+
${parentSpaces} <ul>
50+
${childStr}
51+
${parentSpaces} </ul>
52+
${parentSpaces}</li>`;
53+
}
54+
},
55+
});
56+
57+
const ui = makeUI();
58+
contents.forEach((c) => {
59+
const {level} = c;
60+
const li = makeLI(c);
61+
62+
if (lastParent[level - 1]) {
63+
lastParent[level - 1].children.push(li);
64+
}
65+
lastParent[level] = li;
66+
67+
if (level === 2) ui.children.push(li);
68+
});
69+
70+
tocStr = ui.toString(' ');
71+
} else if (type === 'markdown') {
72+
const indent = 2;
73+
const ul = '-';
74+
const headers = [];
75+
const space = ' '.repeat(indent);
76+
const usedHeaders = {};
77+
contents.forEach((c) => {
78+
const {name, level} = c;
79+
let anchor = name.trim().toLowerCase().replace(/\s+/g, '-').replace(/-+$/, '');
80+
const usedHeader = usedHeaders[anchor];
81+
if (usedHeader) {
82+
usedHeaders[anchor] = usedHeader + 1;
83+
anchor = `${anchor}-${usedHeader}`;
84+
} else {
85+
usedHeaders[anchor] = 1;
86+
}
87+
const header = `${space.repeat(level - 2)}${ul} [${name}](#${anchor})`;
88+
headers.push(header);
89+
});
90+
91+
tocStr = headers.join('\n');
92+
}
93+
94+
return `## TOC
95+
96+
<!-- toc -->
97+
<!-- <details close> -->
98+
<!-- <summary>点击展开/折叠目录</summary> -->
99+
100+
${tocStr}
101+
102+
<!-- </details> -->
103+
<!-- tocstop -->`;
104+
};
105+
106+
export default json2md;

_doc_builder/lib.mjs

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import Path from 'node:path';
2+
3+
import {lstat as getStat, readFile, readdir, writeFile} from 'node:fs/promises';
4+
import pMap from 'p-map';
5+
6+
import { fileURLToPath } from 'node:url';
7+
const __filename = fileURLToPath(import.meta.url);
8+
export const projectDir = Path.resolve(__filename, '../../');
9+
10+
// import PKG from '../package.json' with { type: 'json' };
11+
import {createRequire} from 'module';
12+
const require = createRequire(import.meta.url);
13+
export const PKG = require('../package.json' );
14+
15+
16+
import {ignoreDirs, categoryNameMap} from '../build.config.mjs';
17+
18+
export const absPath = (...paths) => Path.resolve(projectDir, ...paths);
19+
export const relativePath = (path) => Path.relative(projectDir, path);
20+
export const toCamelCase = (str) => str
21+
.replace(/\s(.)/g, ($1) => $1.toUpperCase())
22+
.replace(/\s/g, '')
23+
.replace(/^(.)/, ($1) => $1.toUpperCase());
24+
25+
async function getTitle(path) {
26+
const content = await readFile(path, {encoding: 'utf8'});
27+
const matched = content.match(/##? (.+)/);
28+
if (!matched) return '';
29+
30+
let title = matched[1];
31+
title = title.replace(/\[(.*?)\]/g, (_m, p) => p);
32+
33+
return title;
34+
}
35+
36+
export async function scanDir(dirName, nodeMap, parentPath, _parent, level = 3) {
37+
const dirPath = absPath(parentPath, dirName);
38+
const filenames = await readdir(dirPath);
39+
const children = [];
40+
41+
const curNode = nodeMap[dirPath] = nodeMap[dirPath] || {
42+
name: dirName,
43+
path: dirPath,
44+
title: categoryNameMap[dirName] || toCamelCase(dirName),
45+
isDir: true,
46+
intro: undefined,
47+
level, children,
48+
parent: undefined,
49+
};
50+
51+
await pMap(filenames, async (filename, index) => {
52+
const curPath = absPath(dirPath, filename);
53+
const stats = await getStat(curPath)
54+
55+
if (stats.isSymbolicLink()) {
56+
return;
57+
}
58+
59+
if (stats.isFile()) {
60+
if (filename.toLowerCase() === 'readme.md') {
61+
curNode.intro = await readFile(curPath, {encoding: 'utf8'});
62+
return;
63+
}
64+
65+
return getTitle(curPath).then((title) => {
66+
children[index] = {
67+
name: dirName,
68+
path: curPath,
69+
title,
70+
isDir: false,
71+
intro: undefined,
72+
level,
73+
children: [],
74+
parent: curNode,
75+
};
76+
});
77+
} else if (stats.isDirectory()) {
78+
const subNode = await scanDir(filename, nodeMap, dirPath, curNode, level + 1);
79+
subNode.parent = curNode;
80+
children[index] = subNode;
81+
} else {
82+
// ignore
83+
}
84+
});
85+
86+
curNode.children = children.filter((n) => n)
87+
88+
return curNode;
89+
}
90+
91+
export async function getDirNames(dir) {
92+
const filenames = await readdir(dir);
93+
94+
let dirNames = filenames.filter((name) =>
95+
!name.startsWith('_') && !name.startsWith('.')
96+
&& (!ignoreDirs.includes(name)));
97+
98+
dirNames = await pMap(dirNames, async (dirName) => {
99+
const stats = await getStat(absPath(dirName));
100+
return stats.isDirectory() ? dirName : null;
101+
})
102+
103+
return dirNames.filter((name) => name);
104+
}
105+
106+
107+
export async function run(func) {
108+
try {
109+
await func();
110+
} catch(err) {
111+
console.error('[failed] Error stack: %s', err.stack);
112+
process.exit(1);
113+
}
114+
}

0 commit comments

Comments
 (0)