@tnotesjs/core 0.7.0 → 0.8.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/commands/BaseCommand.ts +58 -0
- package/commands/build/BuildCommand.ts +25 -0
- package/commands/build/PreviewCommand.ts +29 -0
- package/commands/build/index.ts +8 -0
- package/commands/dev/DevCommand.ts +75 -0
- package/commands/dev/index.ts +7 -0
- package/commands/git/PullCommand.ts +25 -0
- package/commands/git/PushCommand.ts +64 -0
- package/commands/git/index.ts +8 -0
- package/commands/index.ts +11 -0
- package/commands/init-sub-repo/InitSubRepoCommand.ts +206 -0
- package/commands/init-sub-repo/index.ts +1 -0
- package/commands/misc/HelpCommand.ts +104 -0
- package/commands/misc/index.ts +7 -0
- package/commands/models.ts +87 -0
- package/commands/note/CreateNoteCommand.ts +160 -0
- package/commands/note/RenameNoteCommand.ts +147 -0
- package/commands/note/UpdateNoteConfigCommand.ts +78 -0
- package/commands/note/index.ts +9 -0
- package/commands/registry.ts +47 -0
- package/commands/update/UpdateCommand.ts +219 -0
- package/commands/update/index.ts +7 -0
- package/commands/update-completed-count/UpdateCompletedCountCommand.ts +208 -0
- package/commands/update-completed-count/index.ts +5 -0
- package/dist/markdown/index.cjs +6 -9
- package/dist/markdown/index.js +6 -9
- package/dist/vitepress/config/index.cjs +214 -58
- package/dist/vitepress/config/index.js +208 -52
- package/markdown/components.ts +86 -0
- package/markdown/index.ts +17 -0
- package/markdown/noteFormatter.test.ts +44 -0
- package/markdown/noteFormatter.ts +237 -0
- package/package.json +7 -3
- package/vitepress/components/BilibiliOutsidePlayer/BilibiliOutsidePlayer.vue +9 -18
- package/vitepress/components/EnWordList/EnWordList.vue +16 -662
- package/vitepress/components/Footprints/Footprints.vue +15 -537
- package/vitepress/components/Mermaid/Mermaid.vue +13 -588
- package/vitepress/components/MindmapPreview/MindmapPreview.vue +12 -434
- package/vitepress/components/MindmapPreview/markdown.ts +1 -1
- package/vitepress/components/NotesTable/NotesTable.vue +11 -130
- package/vitepress/configs/markdown.config.ts +170 -26
- package/vitepress/theme/index.ts +9 -13
- package/vitepress/theme/styles/base.scss +15 -0
- package/workspace/atomic.ts +113 -0
- package/workspace/errors.ts +27 -0
- package/workspace/index.ts +40 -0
- package/workspace/mutationQueue.ts +28 -0
- package/workspace/paths.ts +64 -0
- package/workspace/reconcile.test.ts +300 -0
- package/workspace/reconcile.ts +95 -0
- package/workspace/scanner.ts +292 -0
- package/workspace/types.ts +224 -0
- package/workspace/workspace.test.ts +333 -0
- package/workspace/workspace.ts +1020 -0
- package/vitepress/components/EnWordList/RightClickMenu.vue +0 -93
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { generateAnchor } from '../utils/generateAnchor'
|
|
2
|
+
|
|
3
|
+
import type { WorkspaceNoteConfig } from '../workspace/types'
|
|
4
|
+
|
|
5
|
+
export const NOTE_TOC_START_TAG = '<!-- region:toc -->'
|
|
6
|
+
export const NOTE_TOC_END_TAG = '<!-- endregion:toc -->'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Lazy-loads prettier so consumers that never format (e.g. the read-only Nav
|
|
10
|
+
* VS Code extension) don't pay the top-level import cost / don't break their
|
|
11
|
+
* bundle (prettier's ESM entry crashes inside a CJS bundle at load time).
|
|
12
|
+
*/
|
|
13
|
+
async function prettierFormat(
|
|
14
|
+
content: string,
|
|
15
|
+
options: { parser: string; proseWrap: string; endOfLine: string },
|
|
16
|
+
): Promise<string> {
|
|
17
|
+
const module = await import('prettier')
|
|
18
|
+
const prettier = (module as { default?: typeof import('prettier') }).default ?? module
|
|
19
|
+
return prettier.format(content, options as Parameters<typeof prettier.format>[1])
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface FormatTNotesNoteInput {
|
|
23
|
+
content: string
|
|
24
|
+
noteIndex: string
|
|
25
|
+
title: string
|
|
26
|
+
repoOwner: string
|
|
27
|
+
repoName: string
|
|
28
|
+
noteConfig: WorkspaceNoteConfig
|
|
29
|
+
prettier?: boolean
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface FormatTNotesNoteResult {
|
|
33
|
+
content: string
|
|
34
|
+
generatedTitle: string
|
|
35
|
+
generatedToc: string[]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function generatedNoteTitle(input: FormatTNotesNoteInput): string {
|
|
39
|
+
const dirName = `${input.noteIndex}. ${input.title}`
|
|
40
|
+
const encodedDirName = encodeURIComponent(dirName)
|
|
41
|
+
const repositoryUrl = `https://github.com/${input.repoOwner}/${input.repoName}/tree/main/notes`
|
|
42
|
+
return `# [${dirName}](${repositoryUrl}/${encodedDirName})`
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function ensureGeneratedTitle(lines: string[], generatedTitle: string): void {
|
|
46
|
+
if (lines.length === 0) {
|
|
47
|
+
lines.push(generatedTitle)
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
lines[0] = lines[0].replace(/^\uFEFF/, '')
|
|
52
|
+
if (lines[0].trimStart().startsWith('# ')) {
|
|
53
|
+
lines[0] = generatedTitle
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
lines.unshift(generatedTitle, '')
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function ensureTocRegion(lines: string[]): { start: number; end: number } {
|
|
61
|
+
let start = lines.findIndex((line) => line.trim() === NOTE_TOC_START_TAG)
|
|
62
|
+
let end = lines.findIndex(
|
|
63
|
+
(line, index) => index > start && line.trim() === NOTE_TOC_END_TAG,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
if (start >= 0 && end > start) return { start, end }
|
|
67
|
+
|
|
68
|
+
if (start >= 0) lines.splice(start, 1)
|
|
69
|
+
if (end >= 0) lines.splice(end > start ? end - 1 : end, 1)
|
|
70
|
+
|
|
71
|
+
let insertAt = 1
|
|
72
|
+
while (insertAt < lines.length && lines[insertAt].trim() === '') insertAt++
|
|
73
|
+
lines.splice(
|
|
74
|
+
insertAt,
|
|
75
|
+
0,
|
|
76
|
+
'',
|
|
77
|
+
NOTE_TOC_START_TAG,
|
|
78
|
+
'',
|
|
79
|
+
NOTE_TOC_END_TAG,
|
|
80
|
+
'',
|
|
81
|
+
)
|
|
82
|
+
start = insertAt + 1
|
|
83
|
+
end = insertAt + 3
|
|
84
|
+
return { start, end }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
interface Heading {
|
|
88
|
+
level: number
|
|
89
|
+
text: string
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function normalizeHeadings(lines: string[]): Heading[] {
|
|
93
|
+
const headings: Heading[] = []
|
|
94
|
+
const counters = { h2: 0, h3: 0 }
|
|
95
|
+
let fence: { marker: '`' | '~'; length: number } | null = null
|
|
96
|
+
let inHtmlComment = false
|
|
97
|
+
|
|
98
|
+
for (let index = 0; index < lines.length; index++) {
|
|
99
|
+
const line = lines[index]
|
|
100
|
+
const fenceMatch = line.match(/^\s{0,3}(`{3,}|~{3,})/)
|
|
101
|
+
if (fenceMatch) {
|
|
102
|
+
const marker = fenceMatch[1][0] as '`' | '~'
|
|
103
|
+
if (!fence) {
|
|
104
|
+
fence = { marker, length: fenceMatch[1].length }
|
|
105
|
+
} else if (fence.marker === marker && fenceMatch[1].length >= fence.length) {
|
|
106
|
+
fence = null
|
|
107
|
+
}
|
|
108
|
+
continue
|
|
109
|
+
}
|
|
110
|
+
if (fence) continue
|
|
111
|
+
|
|
112
|
+
if (inHtmlComment) {
|
|
113
|
+
if (line.includes('-->')) inHtmlComment = false
|
|
114
|
+
continue
|
|
115
|
+
}
|
|
116
|
+
const commentStart = line.indexOf('<!--')
|
|
117
|
+
if (commentStart >= 0) {
|
|
118
|
+
const commentEnd = line.indexOf('-->', commentStart + 4)
|
|
119
|
+
if (commentEnd < 0) inHtmlComment = true
|
|
120
|
+
if (line.trimStart().startsWith('<!--')) continue
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const match = line.match(/^(#{2,6})\s+(.+?)\s*#*\s*$/)
|
|
124
|
+
if (!match) continue
|
|
125
|
+
|
|
126
|
+
const level = match[1].length
|
|
127
|
+
const plainText = match[2]
|
|
128
|
+
.replace(/^\d+(?:\.\d+)+\.?\s+|^\d+\.\s+/, '')
|
|
129
|
+
.trim()
|
|
130
|
+
let text = plainText
|
|
131
|
+
|
|
132
|
+
if (level === 2) {
|
|
133
|
+
counters.h2 += 1
|
|
134
|
+
counters.h3 = 0
|
|
135
|
+
text = `${counters.h2}. ${plainText}`
|
|
136
|
+
} else if (level === 3) {
|
|
137
|
+
counters.h3 += 1
|
|
138
|
+
text = `${counters.h2}.${counters.h3}. ${plainText}`
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
lines[index] = `${'#'.repeat(level)} ${text}`
|
|
142
|
+
headings.push({ level, text })
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return headings
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function buildHeadingToc(headings: Heading[]): string[] {
|
|
149
|
+
return headings.map((heading) => {
|
|
150
|
+
const indent = ' '.repeat(Math.max(0, heading.level - 2) * 2)
|
|
151
|
+
return `${indent}- [${heading.text}](#${generateAnchor(heading.text)})`
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function stringArray(value: unknown): string[] {
|
|
156
|
+
return Array.isArray(value)
|
|
157
|
+
? value.filter((item): item is string => typeof item === 'string')
|
|
158
|
+
: []
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function buildResourceToc(input: FormatTNotesNoteInput): string[] {
|
|
162
|
+
const bilibili = stringArray(input.noteConfig.bilibili)
|
|
163
|
+
const relatedTNotes = stringArray(input.noteConfig.tnotes)
|
|
164
|
+
const yuque = stringArray(input.noteConfig.yuque)
|
|
165
|
+
if (bilibili.length + relatedTNotes.length + yuque.length === 0) return []
|
|
166
|
+
|
|
167
|
+
const lines: string[] = ['::: details 📚 相关资源', '']
|
|
168
|
+
if (bilibili.length > 0) {
|
|
169
|
+
lines.push(
|
|
170
|
+
'- [📺 bilibili(笔记视频资源)](https://space.bilibili.com/407241004)',
|
|
171
|
+
...bilibili.map(
|
|
172
|
+
(bvid, index) =>
|
|
173
|
+
` - [bilibili.${input.repoName}.${input.noteIndex}.${index + 1}](https://www.bilibili.com/video/${bvid})`,
|
|
174
|
+
),
|
|
175
|
+
)
|
|
176
|
+
}
|
|
177
|
+
if (relatedTNotes.length > 0) {
|
|
178
|
+
lines.push(
|
|
179
|
+
'- [📒 TNotes(相关知识库)](https://tnotesjs.github.io/TNotes/)',
|
|
180
|
+
...relatedTNotes.map(
|
|
181
|
+
(repoName) =>
|
|
182
|
+
` - [TNotes.${repoName}](https://tnotesjs.github.io/TNotes.${repoName}/)`,
|
|
183
|
+
),
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
if (yuque.length > 0) {
|
|
187
|
+
const base = 'https://www.yuque.com/tdahuyou/tnotes.yuque/'
|
|
188
|
+
lines.push(
|
|
189
|
+
`- [📂 TNotes.yuque(笔记附件资源)](${base})`,
|
|
190
|
+
...yuque.map(
|
|
191
|
+
(slug) =>
|
|
192
|
+
` - [TNotes.yuque.${input.repoName.replace('TNotes.', '')}.${input.noteIndex}](${base}${slug})`,
|
|
193
|
+
),
|
|
194
|
+
)
|
|
195
|
+
}
|
|
196
|
+
lines.push('', ':::', '')
|
|
197
|
+
return lines
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export async function formatTNotesNote(
|
|
201
|
+
input: FormatTNotesNoteInput,
|
|
202
|
+
): Promise<FormatTNotesNoteResult> {
|
|
203
|
+
let content = input.content.replace(/\r\n?/g, '\n')
|
|
204
|
+
if (input.prettier !== false) {
|
|
205
|
+
content = await prettierFormat(content, {
|
|
206
|
+
parser: 'markdown',
|
|
207
|
+
proseWrap: 'never',
|
|
208
|
+
endOfLine: 'lf',
|
|
209
|
+
})
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const lines = content.replace(/\n$/, '').split('\n')
|
|
213
|
+
const title = generatedNoteTitle(input)
|
|
214
|
+
ensureGeneratedTitle(lines, title)
|
|
215
|
+
let region = ensureTocRegion(lines)
|
|
216
|
+
const headings = normalizeHeadings(lines)
|
|
217
|
+
|
|
218
|
+
// Heading normalization does not add/remove lines, but resolving the region
|
|
219
|
+
// again makes the invariant explicit and protects future format extensions.
|
|
220
|
+
region = ensureTocRegion(lines)
|
|
221
|
+
const toc = buildHeadingToc(headings)
|
|
222
|
+
const resources = buildResourceToc(input)
|
|
223
|
+
lines.splice(
|
|
224
|
+
region.start + 1,
|
|
225
|
+
region.end - region.start - 1,
|
|
226
|
+
'',
|
|
227
|
+
...resources,
|
|
228
|
+
...toc,
|
|
229
|
+
'',
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
content: `${lines.join('\n').replace(/\n{3,}$/g, '\n\n')}\n`,
|
|
234
|
+
generatedTitle: title,
|
|
235
|
+
generatedToc: toc,
|
|
236
|
+
}
|
|
237
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tnotesjs/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "TNotes 知识库核心框架 —— 基于 VitePress 的笔记管理系统",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "pnpm@10.17.1",
|
|
@@ -34,14 +34,17 @@
|
|
|
34
34
|
"./vitepress/*": "./vitepress/*"
|
|
35
35
|
},
|
|
36
36
|
"files": [
|
|
37
|
+
"commands/",
|
|
37
38
|
"config/",
|
|
38
39
|
"core/",
|
|
40
|
+
"markdown/",
|
|
39
41
|
"services/",
|
|
40
42
|
"utils/",
|
|
41
43
|
"dist/",
|
|
42
44
|
"templates/",
|
|
43
45
|
"vitepress/",
|
|
44
|
-
"types/"
|
|
46
|
+
"types/",
|
|
47
|
+
"workspace/"
|
|
45
48
|
],
|
|
46
49
|
"scripts": {
|
|
47
50
|
"build": "tsup",
|
|
@@ -61,7 +64,8 @@
|
|
|
61
64
|
"vue": "^3.5.0"
|
|
62
65
|
},
|
|
63
66
|
"dependencies": {
|
|
64
|
-
"@tnotesjs/mindmap-core": "^0.2.
|
|
67
|
+
"@tnotesjs/mindmap-core": "^0.2.2",
|
|
68
|
+
"@tnotesjs/ui": "^0.1.1",
|
|
65
69
|
"echarts": "^6.0.0",
|
|
66
70
|
"github-slugger": "^2.0.0",
|
|
67
71
|
"markdown-it-container": "^4.0.0",
|
|
@@ -1,24 +1,15 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
|
|
1
|
+
<!--
|
|
2
|
+
Deprecated local shell. Canonical implementation lives in @tnotesjs/ui as
|
|
3
|
+
BilibiliVideo. Kept so deep path imports do not break during migration.
|
|
3
4
|
-->
|
|
4
|
-
|
|
5
5
|
<template>
|
|
6
|
-
<
|
|
7
|
-
style="width: 100%; aspect-ratio: 16/9; margin: 1rem 0"
|
|
8
|
-
:src="'//player.bilibili.com/player.html?isOutside=true&bvid=' + id"
|
|
9
|
-
scrolling="no"
|
|
10
|
-
border="0"
|
|
11
|
-
frameborder="no"
|
|
12
|
-
framespacing="0"
|
|
13
|
-
allowfullscreen="true"
|
|
14
|
-
></iframe>
|
|
6
|
+
<BilibiliVideo :id="id" />
|
|
15
7
|
</template>
|
|
16
8
|
|
|
17
9
|
<script setup lang="ts">
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
})
|
|
10
|
+
import { BilibiliVideo } from '@tnotesjs/ui'
|
|
11
|
+
|
|
12
|
+
defineProps<{
|
|
13
|
+
id: string
|
|
14
|
+
}>()
|
|
24
15
|
</script>
|