generated from atian25/nodejs-template
-
Notifications
You must be signed in to change notification settings - Fork 36
/
Copy pathdoc.ts
115 lines (98 loc) · 3.84 KB
/
doc.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
import path from 'path';
import type { Link, Text } from 'mdast';
import { remark } from 'remark';
import { selectAll } from 'unist-util-select';
import yaml from 'yaml';
import fg from 'fast-glob';
import { TreeNode } from './types.js';
import { readJSON, download, getRedirectLink } from './utils.js';
import { config } from '../config.js';
const { host, metaDir, outputDir, userAgent } = config;
const docsPublishedAtPath = await fg('**/docs-published-at.json', { cwd: metaDir, deep: 3 });
const docsPublishedAtMap = await readJSON(path.join(metaDir, docsPublishedAtPath[0]));
interface Options {
doc: TreeNode;
mapping: Record<string, TreeNode>;
}
export async function buildDoc(doc: TreeNode, mapping: Record<string, TreeNode>) {
const docDetail = await readJSON(path.join(metaDir, doc.namespace, 'docs', `${doc.url}.json`));
if (typeof docsPublishedAtMap[docDetail.id] !== 'undefined' && docsPublishedAtMap[docDetail.id] === docDetail.published_at) {
return null;
}
const content = await remark()
.data('settings', { bullet: '-', listItemIndent: 'one' })
.use([
[ replaceHTML ],
[ relativeLink, { doc, mapping }],
[ downloadAsset, { doc, mapping }],
])
.process(docDetail.body);
doc.content = frontmatter(doc) + content.toString();
// FIXME: remark will transform `*` to `\*`
doc.content = doc.content.replaceAll('\\*', '*');
return doc;
}
function frontmatter(doc) {
const frontMatter = yaml.stringify({
title: doc.title,
url: `${host}/${doc.namespace}/${doc.url}`,
// slug: doc.slug,
// public: doc.public,
// status: doc.status,
// description: doc.description,
});
return `---\n${frontMatter}---\n\n`;
}
function replaceHTML() {
return tree => {
const htmlNodes = selectAll('html', tree) as Text[];
for (const node of htmlNodes) {
if (node.value === '<br />' || node.value === '<br/>') {
node.type = 'text';
node.value = '\n';
}
}
};
}
function relativeLink({ doc, mapping }: Options) {
return async tree => {
const links = selectAll('link', tree) as Link[];
for (const node of links) {
if (!isYuqueDocLink(node.url)) continue;
// 语雀分享链接功能已下线,替换为 302 后的地址
if (node.url.startsWith(`${host}/docs/share/`)) {
node.url = await getRedirectLink(node.url, host);
}
// 语雀链接有多种显示方式,其中一种会插入该参数,会导致点击后的页面缺少头部导航
node.url = node.url.replace('view=doc_embed', '');
const { pathname } = new URL(node.url);
const targetNode = mapping[pathname.substring(1)];
if (!targetNode) {
console.warn(`[WARN] ${node.url}, ${pathname.substring(1)} not found`);
} else {
node.url = path.relative(path.dirname(doc.filePath), targetNode.filePath) + '.md';
}
}
};
}
function isYuqueDocLink(url?: string) {
if (!url) return false;
if (!url.startsWith(host)) return false;
if (url.startsWith(host + '/attachments/')) return false;
return true;
}
function downloadAsset(opts: Options) {
return async tree => {
const docFilePath = opts.doc.filePath;
const assetsDir = path.join(docFilePath.split('/')[0], 'assets');
// FIXME: 语雀附件现在不允许直接访问,需要登录后才能下载,这里先跳过。
// const assetNodes = selectAll(`image[url^=http], link[url^=${host}/attachments/]`, tree) as Link[];
const assetNodes = selectAll('image[url^=http]', tree) as Link[];
for (const node of assetNodes) {
const assetName = `${opts.doc.url}/${new URL(node.url).pathname.split('/').pop()}`;
const filePath = path.join(assetsDir, assetName);
await download(node.url, path.join(outputDir, filePath), { headers: { 'User-Agent': userAgent } });
node.url = path.relative(path.dirname(docFilePath), filePath);
}
};
}