@firok-arc-project/arc-centrifuge 0.5.0
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/centrifuge.iml +9 -0
- package/dist/doc.d.ts +5 -0
- package/dist/doc.d.ts.map +1 -0
- package/dist/doc.js +60 -0
- package/dist/doc.js.map +1 -0
- package/dist/file-system.d.ts +3 -0
- package/dist/file-system.d.ts.map +1 -0
- package/dist/file-system.js +39 -0
- package/dist/file-system.js.map +1 -0
- package/dist/fs/fs-meta.d.ts +3 -0
- package/dist/fs/fs-meta.d.ts.map +1 -0
- package/dist/fs/fs-meta.js +36 -0
- package/dist/fs/fs-meta.js.map +1 -0
- package/dist/gen-all-tag-json.d.ts +3 -0
- package/dist/gen-all-tag-json.d.ts.map +1 -0
- package/dist/gen-all-tag-json.js +7 -0
- package/dist/gen-all-tag-json.js.map +1 -0
- package/dist/gen-tag-indexing.d.ts +4 -0
- package/dist/gen-tag-indexing.d.ts.map +1 -0
- package/dist/gen-tag-indexing.js +29 -0
- package/dist/gen-tag-indexing.js.map +1 -0
- package/dist/gen-timeline-indexing.d.ts +4 -0
- package/dist/gen-timeline-indexing.d.ts.map +1 -0
- package/dist/gen-timeline-indexing.js +17 -0
- package/dist/gen-timeline-indexing.js.map +1 -0
- package/dist/gen.d.ts +4 -0
- package/dist/gen.d.ts.map +1 -0
- package/dist/gen.js +16 -0
- package/dist/gen.js.map +1 -0
- package/dist/id-entity.d.ts +3 -0
- package/dist/id-entity.d.ts.map +1 -0
- package/dist/id-entity.js +8 -0
- package/dist/id-entity.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/meta-config.d.ts +3 -0
- package/dist/meta-config.d.ts.map +1 -0
- package/dist/meta-config.js +49 -0
- package/dist/meta-config.js.map +1 -0
- package/dist/tag/tags.d.ts +5 -0
- package/dist/tag/tags.d.ts.map +1 -0
- package/dist/tag/tags.js +39 -0
- package/dist/tag/tags.js.map +1 -0
- package/dist/tag-indexing.d.ts +6 -0
- package/dist/tag-indexing.d.ts.map +1 -0
- package/dist/tag-indexing.js +41 -0
- package/dist/tag-indexing.js.map +1 -0
- package/package.json +29 -0
- package/readme.md +74 -0
- package/src/doc.ts +92 -0
- package/src/file-system.ts +59 -0
- package/src/gen-all-tag-json.ts +11 -0
- package/src/gen-tag-indexing.ts +50 -0
- package/src/gen-timeline-indexing.ts +27 -0
- package/src/gen.ts +26 -0
- package/src/id-entity.ts +11 -0
- package/src/index.ts +42 -0
- package/src/meta-config.ts +76 -0
- package/src/types/doc-meta-def.d.ts +17 -0
- package/src/types/file-system-def.d.ts +25 -0
- package/src/types/id-entity-def.d.ts +7 -0
- package/src/types/meta-config-def.d.ts +11 -0
- package/src/types/tag-data-def.d.ts +44 -0
- package/src/types/tag-indexing-def.d.ts +16 -0
- package/tsconfig.json +61 -0
package/readme.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Centrifuge
|
|
2
|
+
|
|
3
|
+
用于识别和处理指定目录内的博客文档文件.
|
|
4
|
+
|
|
5
|
+
设计初衷是制作一个脱离特定客户端环境且 markdown-first 的博文处理工具.
|
|
6
|
+
|
|
7
|
+
使用时需要指定一个 JSON5 数据文件, 这个文件包含如下字段:
|
|
8
|
+
|
|
9
|
+
```json5
|
|
10
|
+
{
|
|
11
|
+
"source_folder": "./input", // 一个相对路径, 指定从哪里读取文档数据
|
|
12
|
+
"target_folder": "./output", // 一个相对路径, 指定将各个索引数据写入到哪里
|
|
13
|
+
"tags": [
|
|
14
|
+
{
|
|
15
|
+
// 标签数据
|
|
16
|
+
},
|
|
17
|
+
// ...
|
|
18
|
+
], // 标签列表
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
根据文档数据, 将生成如下结构:
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
{target_folder}/
|
|
26
|
+
├─ all-tag.json 所有的标签数据
|
|
27
|
+
│
|
|
28
|
+
├─ tag-indexing-{tag_id}-all.json 指定标签的索引全部数据
|
|
29
|
+
├─ tag-indexing-{tag_id}-0.json 指定标签的索引分页数据, 第 1 页
|
|
30
|
+
├─ tag-indexing-{tag_id}-1.json 指定标签的索引分页数据, 第 2 页
|
|
31
|
+
├─ ...
|
|
32
|
+
│
|
|
33
|
+
├─ timeline-indexing-all.json 按照时间线排序的索引全部数据
|
|
34
|
+
├─ timeline-indexing-0.json 按照时间线排序的索引分页数据, 第 1 页
|
|
35
|
+
├─ timeline-indexing-1.json 按照时间线排序的索引分页数据, 第 2 页
|
|
36
|
+
└─ ...
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
生成的内容不会包含各个文档的原始数据.
|
|
40
|
+
|
|
41
|
+
## 文档配置
|
|
42
|
+
|
|
43
|
+
此工具会递归加载指定目录树内所有文件, 按照不同格式将对其进行不同处理.
|
|
44
|
+
|
|
45
|
+
### 文档 front-matter 数据
|
|
46
|
+
|
|
47
|
+
每个 `.md` 文件对应一篇可渲染的博文.
|
|
48
|
+
|
|
49
|
+
我们使用 `gray-matter` 读取和处理其中的 front-matter,
|
|
50
|
+
将所有的数据整理成若干个 JSON 数据文件输出到指定目录.
|
|
51
|
+
|
|
52
|
+
要正常处理一个文档文件, 它的 front-matter 中需要包含下述字段:
|
|
53
|
+
|
|
54
|
+
* `id: string` 文档唯一 ID. 这会用于生成唯一 URL
|
|
55
|
+
* `title: string` 文档标题
|
|
56
|
+
* `createTimestamp: string` 文档创建时间
|
|
57
|
+
* `updateTimestamp: string` 文档更新时间
|
|
58
|
+
* `sortTimestamp: string` 文档索引排序时间
|
|
59
|
+
* `tags: string[]` 文档标签. 值需要是正确配置了的标签 ID 列表
|
|
60
|
+
|
|
61
|
+
原始的 front-matter 数据会作为文档的简述数据输出到各个索引数据中.
|
|
62
|
+
|
|
63
|
+
### 标签数据
|
|
64
|
+
|
|
65
|
+
标签数据包含如下字段:
|
|
66
|
+
|
|
67
|
+
* `id: string` 标签唯一 ID
|
|
68
|
+
* `name: string` 标签名称
|
|
69
|
+
* `desc: string` 标签描述
|
|
70
|
+
* `hidden: boolean` 是否是隐藏标签
|
|
71
|
+
* `indexing: boolean` 是否为包含此标签的博文创建索引列表
|
|
72
|
+
* `indexingPagination: boolean` 为此标签创建索引列表时是否需要分页
|
|
73
|
+
* `indexingPaginationSize: int` 为此标签创建索引列表时每页的博文数量
|
|
74
|
+
|
package/src/doc.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
|
|
2
|
+
import * as fs from 'node:fs'
|
|
3
|
+
|
|
4
|
+
import matter from 'gray-matter'
|
|
5
|
+
import {FolderNode} from './types/file-system-def'
|
|
6
|
+
import {DocMeta, DocMetaMap} from './types/doc-meta-def'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 读取文档中的元数据
|
|
10
|
+
* */
|
|
11
|
+
function loadDocMeta(
|
|
12
|
+
textDoc: string,
|
|
13
|
+
pathRelative: string,
|
|
14
|
+
): DocMeta
|
|
15
|
+
{
|
|
16
|
+
const fileMatter = matter(textDoc)
|
|
17
|
+
// console.log('matter', fileMatter)
|
|
18
|
+
const json = fileMatter.data as DocMeta
|
|
19
|
+
let {
|
|
20
|
+
id,
|
|
21
|
+
title,
|
|
22
|
+
createTimestamp,
|
|
23
|
+
updateTimestamp,
|
|
24
|
+
sortTimestamp,
|
|
25
|
+
tags,
|
|
26
|
+
} = json
|
|
27
|
+
|
|
28
|
+
if('string' !== typeof id)
|
|
29
|
+
throw 'doc id not found: ' + json
|
|
30
|
+
if('string' !== typeof title)
|
|
31
|
+
throw 'doc title not found: ' + json
|
|
32
|
+
|
|
33
|
+
const dateCreate = new Date(json.createTimestamp)
|
|
34
|
+
const dateUpdate = new Date(json.updateTimestamp)
|
|
35
|
+
const dateSort = new Date(json.sortTimestamp)
|
|
36
|
+
createTimestamp = dateCreate.getTime()
|
|
37
|
+
updateTimestamp = dateUpdate.getTime()
|
|
38
|
+
sortTimestamp = dateSort.getTime()
|
|
39
|
+
|
|
40
|
+
if(!Array.isArray(tags))
|
|
41
|
+
tags = []
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
...json,
|
|
45
|
+
id,
|
|
46
|
+
title,
|
|
47
|
+
createTimestamp,
|
|
48
|
+
updateTimestamp,
|
|
49
|
+
sortTimestamp,
|
|
50
|
+
tags,
|
|
51
|
+
pathRelative,
|
|
52
|
+
} as DocMeta
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function findDocData(folder: FolderNode, map: DocMetaMap): void
|
|
56
|
+
{
|
|
57
|
+
for(const file of folder.listChildrenFile)
|
|
58
|
+
{
|
|
59
|
+
if(file.filename.endsWith('.md'))
|
|
60
|
+
{
|
|
61
|
+
const textFile = fs.readFileSync(file.pathAbsolute, {
|
|
62
|
+
encoding: 'utf-8',
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
const dataDoc = loadDocMeta(textFile, file.pathRelative)
|
|
66
|
+
|
|
67
|
+
if(map[dataDoc.id] != null)
|
|
68
|
+
throw 'doc id duplicated: ' + dataDoc.id
|
|
69
|
+
|
|
70
|
+
map[dataDoc.id] = dataDoc
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
for(const childFolder of folder.listChildrenFolder)
|
|
75
|
+
{
|
|
76
|
+
findDocData(childFolder, map)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function collectDocMeta(folder: FolderNode): Promise<DocMetaMap>
|
|
81
|
+
{
|
|
82
|
+
const ret: DocMetaMap = {}
|
|
83
|
+
findDocData(folder, ret)
|
|
84
|
+
return ret
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function getSortedDocMeta(map: DocMetaMap): DocMeta[]
|
|
88
|
+
{
|
|
89
|
+
return Object.values(map).sort((a, b) => {
|
|
90
|
+
return a.sortTimestamp - b.sortTimestamp
|
|
91
|
+
})
|
|
92
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
|
|
2
|
+
import * as fs from 'node:fs'
|
|
3
|
+
import * as path from 'node:path'
|
|
4
|
+
import {Dirent} from 'node:fs'
|
|
5
|
+
import {FileNode, FolderNode} from './types/file-system-def'
|
|
6
|
+
|
|
7
|
+
export function readFileSystem(
|
|
8
|
+
pathDir: string,
|
|
9
|
+
pathBaseDir?: string,
|
|
10
|
+
): FolderNode
|
|
11
|
+
{
|
|
12
|
+
if(pathBaseDir == null)
|
|
13
|
+
pathBaseDir = pathDir
|
|
14
|
+
|
|
15
|
+
const listDirent: Dirent[] = fs.readdirSync(pathDir, {
|
|
16
|
+
withFileTypes: true,
|
|
17
|
+
})
|
|
18
|
+
const pathAbsolute = path.resolve(pathDir)
|
|
19
|
+
const pathRelative = path.relative(pathBaseDir, pathDir)
|
|
20
|
+
console.log(
|
|
21
|
+
'pathBaseDir', pathBaseDir,
|
|
22
|
+
'pathDir', pathDir,
|
|
23
|
+
'pathAbsolute', pathAbsolute,
|
|
24
|
+
'pathRelative', pathRelative,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
const listFolder: FolderNode[] = []
|
|
28
|
+
const listFile: FileNode[] = []
|
|
29
|
+
|
|
30
|
+
for(const obj of listDirent)
|
|
31
|
+
{
|
|
32
|
+
const pathAbsolute = path.resolve(pathDir, obj.name)
|
|
33
|
+
const pathRelative = path.relative(pathBaseDir, pathAbsolute)
|
|
34
|
+
|
|
35
|
+
if(obj.isFile())
|
|
36
|
+
{
|
|
37
|
+
const filename = obj.name
|
|
38
|
+
const fileExt = filename.split('.').pop()
|
|
39
|
+
listFile.push({
|
|
40
|
+
filename,
|
|
41
|
+
fileExt,
|
|
42
|
+
pathAbsolute,
|
|
43
|
+
pathRelative,
|
|
44
|
+
} as FileNode)
|
|
45
|
+
}
|
|
46
|
+
else if(obj.isDirectory())
|
|
47
|
+
{
|
|
48
|
+
const folderNode = readFileSystem(pathAbsolute, pathBaseDir)
|
|
49
|
+
listFolder.push(folderNode)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
listChildrenFile: listFile,
|
|
55
|
+
listChildrenFolder: listFolder,
|
|
56
|
+
pathAbsolute,
|
|
57
|
+
pathRelative,
|
|
58
|
+
} as FolderNode
|
|
59
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import {TagMap} from './types/tag-data-def'
|
|
2
|
+
import {write, resolve} from './gen.js'
|
|
3
|
+
|
|
4
|
+
export async function genAllTagJson(mapTag: TagMap, pathFolderTarget: string): Promise<void>
|
|
5
|
+
{
|
|
6
|
+
const pathFileTarget = resolve(pathFolderTarget, 'all-tag.json')
|
|
7
|
+
|
|
8
|
+
let content: string = JSON.stringify(Object.values(mapTag))
|
|
9
|
+
|
|
10
|
+
await write(pathFileTarget, content)
|
|
11
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import {TagData, TagMap} from './types/tag-data-def'
|
|
2
|
+
import {DocMeta, DocMetaMap} from './types/doc-meta-def'
|
|
3
|
+
import {resolve, write} from './gen.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 为某个标签生成索引内容
|
|
7
|
+
* */
|
|
8
|
+
async function genTagIndexingSingle(
|
|
9
|
+
tag: TagData,
|
|
10
|
+
listSortedDocMeta: DocMeta[],
|
|
11
|
+
pathFolderTarget: string,
|
|
12
|
+
)
|
|
13
|
+
{
|
|
14
|
+
if(!tag.indexing)
|
|
15
|
+
return
|
|
16
|
+
|
|
17
|
+
const tagId = tag.id
|
|
18
|
+
|
|
19
|
+
const listDocMeta =
|
|
20
|
+
listSortedDocMeta.filter(docMeta => docMeta.tags.includes(tagId))
|
|
21
|
+
|
|
22
|
+
const pathFileIndexingAll = resolve(pathFolderTarget, `tag-indexing-${tagId}-all.json`)
|
|
23
|
+
await write(pathFileIndexingAll, listDocMeta)
|
|
24
|
+
|
|
25
|
+
if(!tag.indexingPagination)
|
|
26
|
+
return
|
|
27
|
+
|
|
28
|
+
const pageSize = tag.indexingPaginationSize
|
|
29
|
+
const pageCount = Math.ceil(listDocMeta.length / pageSize)
|
|
30
|
+
for(let pageIndex = 0; pageIndex < pageCount; pageIndex++)
|
|
31
|
+
{
|
|
32
|
+
const start = pageIndex * pageSize
|
|
33
|
+
const end = Math.min(start + pageSize, listDocMeta.length)
|
|
34
|
+
const listDocMetaPage = listDocMeta.slice(start, end)
|
|
35
|
+
const pathFileIndexingPage = resolve(pathFolderTarget, `tag-indexing-${tagId}-${pageIndex}.json`)
|
|
36
|
+
await write(pathFileIndexingPage, listDocMetaPage)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function genTagIndexing(
|
|
41
|
+
mapTag: TagMap,
|
|
42
|
+
listSortedDocMeta: DocMeta[],
|
|
43
|
+
pathFolderTarget: string
|
|
44
|
+
): Promise<void>
|
|
45
|
+
{
|
|
46
|
+
for(const tag of Object.values(mapTag))
|
|
47
|
+
{
|
|
48
|
+
await genTagIndexingSingle(tag, listSortedDocMeta, pathFolderTarget)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import {DocMeta} from './types/doc-meta-def'
|
|
2
|
+
import {resolve, write} from './gen.js'
|
|
3
|
+
import { MetaConfig } from './types/meta-config-def'
|
|
4
|
+
|
|
5
|
+
export async function genTimelineIndexing(
|
|
6
|
+
listSortedDocMeta: DocMeta[],
|
|
7
|
+
pathFolderTarget: string,
|
|
8
|
+
metaConfig: MetaConfig,
|
|
9
|
+
): Promise<void>
|
|
10
|
+
{
|
|
11
|
+
const pathFileIndexingAll = resolve(pathFolderTarget, 'timeline-indexing-all.json')
|
|
12
|
+
await write(pathFileIndexingAll, listSortedDocMeta)
|
|
13
|
+
|
|
14
|
+
if(!metaConfig.timelinePagination)
|
|
15
|
+
return
|
|
16
|
+
|
|
17
|
+
const pageSize = metaConfig.timelinePaginationSize
|
|
18
|
+
const pageCount = Math.ceil(listSortedDocMeta.length / pageSize)
|
|
19
|
+
for(let pageIndex = 0; pageIndex < pageCount; pageIndex++)
|
|
20
|
+
{
|
|
21
|
+
const start = pageIndex * pageSize
|
|
22
|
+
const end = Math.min(start + pageSize, listSortedDocMeta.length)
|
|
23
|
+
const listDocMeta = listSortedDocMeta.slice(start, end)
|
|
24
|
+
const pathFileIndexing = resolve(pathFolderTarget, `timeline-indexing-${pageIndex}.json`)
|
|
25
|
+
await write(pathFileIndexing, listDocMeta)
|
|
26
|
+
}
|
|
27
|
+
}
|
package/src/gen.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
|
|
4
|
+
export async function write(pathFile: string, content: string | object)
|
|
5
|
+
{
|
|
6
|
+
const pathFolderParent = path.dirname(pathFile)
|
|
7
|
+
if(!fs.existsSync(pathFolderParent))
|
|
8
|
+
fs.mkdirSync(pathFolderParent, { recursive: true })
|
|
9
|
+
|
|
10
|
+
content = 'object' === typeof content ? JSON.stringify(content) : content
|
|
11
|
+
fs.writeFileSync(
|
|
12
|
+
pathFile,
|
|
13
|
+
content,
|
|
14
|
+
{ encoding: 'utf8', },
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function resolve(pathFrom: string, pathTo: string): string
|
|
19
|
+
{
|
|
20
|
+
return path.resolve(pathFrom, pathTo)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function relative(pathFrom: string, pathTo: string): string
|
|
24
|
+
{
|
|
25
|
+
return path.relative(pathFrom, pathTo)
|
|
26
|
+
}
|
package/src/id-entity.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import {IdEntity, IdMap} from './types/id-entity-def'
|
|
2
|
+
|
|
3
|
+
export function toMap<TypeEntity extends IdEntity>(list: TypeEntity[]): IdMap<TypeEntity>
|
|
4
|
+
{
|
|
5
|
+
const ret: IdMap<TypeEntity> = {}
|
|
6
|
+
for(const entity of list)
|
|
7
|
+
{
|
|
8
|
+
ret[entity.id] = entity
|
|
9
|
+
}
|
|
10
|
+
return ret
|
|
11
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
|
|
2
|
+
import * as process from 'node:process'
|
|
3
|
+
import * as path from 'node:path'
|
|
4
|
+
import {readFileSystem} from './file-system.js'
|
|
5
|
+
import {readMetaConfig} from './meta-config.js'
|
|
6
|
+
import {TagMap} from './types/tag-data-def'
|
|
7
|
+
import {toMap} from './id-entity.js'
|
|
8
|
+
import {DocMetaMap} from './types/doc-meta-def'
|
|
9
|
+
import {collectDocMeta, getSortedDocMeta} from './doc.js'
|
|
10
|
+
import {genAllTagJson} from './gen-all-tag-json.js'
|
|
11
|
+
import {genTagIndexing} from './gen-tag-indexing.js'
|
|
12
|
+
import {genTimelineIndexing} from './gen-timeline-indexing.js'
|
|
13
|
+
|
|
14
|
+
export async function main()
|
|
15
|
+
{
|
|
16
|
+
// 获取命令行参数
|
|
17
|
+
const args: string[] = process.argv.slice(2)
|
|
18
|
+
|
|
19
|
+
if(args.length < 1)
|
|
20
|
+
throw '未指定配置文件路径'
|
|
21
|
+
|
|
22
|
+
// 始终把第一个字符串当做本次识别和处理的基础目录
|
|
23
|
+
const pathMetaConfig = path.resolve(args[0])
|
|
24
|
+
const metaConfig = await readMetaConfig(pathMetaConfig)
|
|
25
|
+
|
|
26
|
+
const mapTag: TagMap = toMap(metaConfig.tags) // 处理 tag 数据
|
|
27
|
+
|
|
28
|
+
const folderSource = readFileSystem(metaConfig.sourceFolder)
|
|
29
|
+
const mapDocMeta: DocMetaMap = await collectDocMeta(folderSource)
|
|
30
|
+
const listSortedDocMeta = getSortedDocMeta(mapDocMeta)
|
|
31
|
+
|
|
32
|
+
console.log('mapTag', mapTag)
|
|
33
|
+
console.log('mapDocMeta', mapDocMeta)
|
|
34
|
+
|
|
35
|
+
// 开始生成并写入索引数据
|
|
36
|
+
const pathFolderTarget = metaConfig.targetFolder
|
|
37
|
+
await genAllTagJson(mapTag, pathFolderTarget)
|
|
38
|
+
await genTimelineIndexing(listSortedDocMeta, pathFolderTarget, metaConfig)
|
|
39
|
+
await genTagIndexing(mapTag, listSortedDocMeta, pathFolderTarget)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
await main()
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import {MetaConfig} from './types/meta-config-def'
|
|
2
|
+
import * as fs from 'node:fs'
|
|
3
|
+
import * as path from 'node:path'
|
|
4
|
+
import JSON5 from 'json5'
|
|
5
|
+
|
|
6
|
+
export async function readMetaConfig(pathConfig: string): Promise<MetaConfig>
|
|
7
|
+
{
|
|
8
|
+
pathConfig = path.resolve(pathConfig) // 把 pathConfig 处理成绝对路径
|
|
9
|
+
|
|
10
|
+
const textMetaConfig = fs.readFileSync(pathConfig, {
|
|
11
|
+
encoding: 'utf8',
|
|
12
|
+
})
|
|
13
|
+
const jsonMetaConfig = JSON5.parse(textMetaConfig) as MetaConfig
|
|
14
|
+
let {
|
|
15
|
+
sourceFolder,
|
|
16
|
+
targetFolder,
|
|
17
|
+
tags,
|
|
18
|
+
timelinePagination,
|
|
19
|
+
timelinePaginationSize,
|
|
20
|
+
} = jsonMetaConfig
|
|
21
|
+
|
|
22
|
+
// 把 sourceFolder 和 targetFolder 处理成绝对路径
|
|
23
|
+
sourceFolder = path.resolve(path.dirname(pathConfig), sourceFolder)
|
|
24
|
+
targetFolder = path.resolve(path.dirname(pathConfig), targetFolder)
|
|
25
|
+
|
|
26
|
+
if('string' !== typeof sourceFolder)
|
|
27
|
+
throw 'sourceFolder not found: ' + jsonMetaConfig
|
|
28
|
+
if('string' !== typeof targetFolder)
|
|
29
|
+
throw 'targetFolder not found: ' + jsonMetaConfig
|
|
30
|
+
if(!Array.isArray(tags))
|
|
31
|
+
tags = []
|
|
32
|
+
|
|
33
|
+
timelinePagination = timelinePagination ?? true
|
|
34
|
+
timelinePaginationSize = timelinePaginationSize ?? 10
|
|
35
|
+
|
|
36
|
+
const setTagId = new Set<string>()
|
|
37
|
+
for(const tag of tags)
|
|
38
|
+
{
|
|
39
|
+
let {
|
|
40
|
+
id,
|
|
41
|
+
name,
|
|
42
|
+
desc,
|
|
43
|
+
icon,
|
|
44
|
+
hidden,
|
|
45
|
+
indexing,
|
|
46
|
+
indexingPagination,
|
|
47
|
+
indexingPaginationSize,
|
|
48
|
+
} = tag
|
|
49
|
+
|
|
50
|
+
if('string' !== typeof id)
|
|
51
|
+
throw 'tag id not found: ' + tag
|
|
52
|
+
if(setTagId.has(id))
|
|
53
|
+
throw 'tag id duplicated: ' + id
|
|
54
|
+
setTagId.add(id)
|
|
55
|
+
|
|
56
|
+
if('string' !== typeof name)
|
|
57
|
+
tag.name = null
|
|
58
|
+
if('string' !== typeof desc)
|
|
59
|
+
tag.desc = null
|
|
60
|
+
if('string' !== typeof icon)
|
|
61
|
+
tag.icon = null
|
|
62
|
+
|
|
63
|
+
tag.hidden = !!hidden
|
|
64
|
+
tag.indexing = indexing ?? false
|
|
65
|
+
tag.indexingPagination = indexingPagination ?? true
|
|
66
|
+
tag.indexingPaginationSize = indexingPaginationSize ?? 10
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
sourceFolder,
|
|
71
|
+
targetFolder,
|
|
72
|
+
tags,
|
|
73
|
+
timelinePagination,
|
|
74
|
+
timelinePaginationSize,
|
|
75
|
+
} as MetaConfig
|
|
76
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* 文档的元数据
|
|
4
|
+
* */
|
|
5
|
+
export interface DocMeta
|
|
6
|
+
{
|
|
7
|
+
id: string
|
|
8
|
+
title: string
|
|
9
|
+
subtitle?: string
|
|
10
|
+
createTimestamp: number
|
|
11
|
+
updateTimestamp: number
|
|
12
|
+
sortTimestamp: number
|
|
13
|
+
tags: string[]
|
|
14
|
+
pathRelative: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type DocMetaMap = Record<string, DocMeta>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
|
|
2
|
+
export declare interface FileSystemNode
|
|
3
|
+
{
|
|
4
|
+
/**
|
|
5
|
+
* 完整路径
|
|
6
|
+
* */
|
|
7
|
+
pathAbsolute: string
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 相对路径
|
|
11
|
+
* */
|
|
12
|
+
pathRelative: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export declare interface FileNode extends FileSystemNode
|
|
16
|
+
{
|
|
17
|
+
filename: string
|
|
18
|
+
fileExt: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export declare interface FolderNode extends FileSystemNode
|
|
22
|
+
{
|
|
23
|
+
listChildrenFolder: FolderNode[]
|
|
24
|
+
listChildrenFile: FileNode[]
|
|
25
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {IdEntity, IdMap} from './id-entity-def'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 标签数据
|
|
5
|
+
* */
|
|
6
|
+
export declare interface TagData extends IdEntity
|
|
7
|
+
{
|
|
8
|
+
/**
|
|
9
|
+
* 标签名称
|
|
10
|
+
* */
|
|
11
|
+
name?: string
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 标签图标
|
|
15
|
+
* */
|
|
16
|
+
icon?: string
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 标签描述
|
|
20
|
+
* */
|
|
21
|
+
desc?: string
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 是否是隐藏标签
|
|
25
|
+
* */
|
|
26
|
+
hidden?: boolean
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 是否为包含此标签的博文创建索引列表
|
|
30
|
+
* */
|
|
31
|
+
indexing?: boolean
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 为此标签创建索引列表时是否需要分页
|
|
35
|
+
* */
|
|
36
|
+
indexingPagination?: boolean
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 为此标签创建索引列表时每页包含多少条信息
|
|
40
|
+
* */
|
|
41
|
+
indexingPaginationSize?: number
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export declare type TagMap = IdMap<TagData>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import {DocMeta} from './doc-meta-def'
|
|
2
|
+
|
|
3
|
+
export declare interface TagIndexingPage
|
|
4
|
+
{
|
|
5
|
+
pageIndex: number
|
|
6
|
+
listDocMeta: DocMeta[]
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export declare interface TagIndexingData
|
|
10
|
+
{
|
|
11
|
+
tagId: string
|
|
12
|
+
pageSize: number
|
|
13
|
+
pageCount: number
|
|
14
|
+
listPage: TagIndexingPage[]
|
|
15
|
+
}
|
|
16
|
+
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
// Visit https://aka.ms/tsconfig to read more about this file
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
// File Layout
|
|
5
|
+
"rootDir": "./src",
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
|
|
8
|
+
// Environment Settings
|
|
9
|
+
// See also https://aka.ms/tsconfig/module
|
|
10
|
+
"module": "preserve",
|
|
11
|
+
"target": "es2020",
|
|
12
|
+
"types": [
|
|
13
|
+
"@types/node"
|
|
14
|
+
],
|
|
15
|
+
// For nodejs:
|
|
16
|
+
// "lib": ["esnext"],
|
|
17
|
+
// "types": ["node"],
|
|
18
|
+
// and npm install -D @types/node
|
|
19
|
+
|
|
20
|
+
// Other Outputs
|
|
21
|
+
"sourceMap": true,
|
|
22
|
+
"declaration": true,
|
|
23
|
+
"declarationMap": true,
|
|
24
|
+
|
|
25
|
+
// Stricter Typechecking Options
|
|
26
|
+
"noUncheckedIndexedAccess": true,
|
|
27
|
+
"exactOptionalPropertyTypes": false,
|
|
28
|
+
|
|
29
|
+
// Style Options
|
|
30
|
+
"noImplicitReturns": false,
|
|
31
|
+
"noImplicitOverride": false,
|
|
32
|
+
"noUnusedLocals": false,
|
|
33
|
+
"noUnusedParameters": false,
|
|
34
|
+
"noFallthroughCasesInSwitch": false,
|
|
35
|
+
"noPropertyAccessFromIndexSignature": false,
|
|
36
|
+
|
|
37
|
+
// Recommended Options
|
|
38
|
+
"strict": false,
|
|
39
|
+
"jsx": "react-jsx",
|
|
40
|
+
"verbatimModuleSyntax": false,
|
|
41
|
+
"isolatedModules": true,
|
|
42
|
+
"noUncheckedSideEffectImports": true,
|
|
43
|
+
"moduleDetection": "force",
|
|
44
|
+
"skipLibCheck": true,
|
|
45
|
+
|
|
46
|
+
// custom
|
|
47
|
+
|
|
48
|
+
"allowJs": true,
|
|
49
|
+
"allowSyntheticDefaultImports": true,
|
|
50
|
+
"allowUnreachableCode": true,
|
|
51
|
+
"allowUnusedLabels": true,
|
|
52
|
+
"checkJs": true,
|
|
53
|
+
"strictNullChecks": false,
|
|
54
|
+
"moduleResolution": "node",
|
|
55
|
+
"preserveConstEnums": true,
|
|
56
|
+
"preserveSymlinks": true
|
|
57
|
+
},
|
|
58
|
+
"include": [
|
|
59
|
+
"./src/*"
|
|
60
|
+
],
|
|
61
|
+
}
|