@incremark/core 0.0.4 → 0.0.5

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.
@@ -0,0 +1,113 @@
1
+ import type { RootContent } from 'mdast'
2
+
3
+ /**
4
+ * 源 Block 类型(来自解析器)
5
+ */
6
+ export interface SourceBlock<T = unknown> {
7
+ /** 唯一标识 */
8
+ id: string
9
+ /** AST 节点 */
10
+ node: RootContent
11
+ /** 块状态 */
12
+ status: 'pending' | 'stable' | 'completed'
13
+ /** 用户自定义元数据 */
14
+ meta?: T
15
+ }
16
+
17
+ /**
18
+ * 显示用的 Block(转换后)
19
+ */
20
+ export interface DisplayBlock<T = unknown> extends SourceBlock<T> {
21
+ /** 用于显示的 AST 节点(可能是截断的) */
22
+ displayNode: RootContent
23
+ /** 显示进度 0-1 */
24
+ progress: number
25
+ /** 是否已完成显示 */
26
+ isDisplayComplete: boolean
27
+ }
28
+
29
+ /**
30
+ * 动画效果类型
31
+ * - 'none': 无动画效果
32
+ * - 'typing': 打字机光标效果(需配合 CSS)
33
+ */
34
+ export type AnimationEffect = 'none' | 'typing'
35
+
36
+ /**
37
+ * Transformer 插件
38
+ */
39
+ export interface TransformerPlugin {
40
+ /** 插件名称 */
41
+ name: string
42
+
43
+ /**
44
+ * 判断是否处理此节点
45
+ * 返回 true 表示这个插件要处理此节点
46
+ */
47
+ match?: (node: RootContent) => boolean
48
+
49
+ /**
50
+ * 自定义字符数计算
51
+ * 返回 undefined 则使用默认逻辑
52
+ * 返回 0 表示立即显示(不参与逐字符效果)
53
+ */
54
+ countChars?: (node: RootContent) => number | undefined
55
+
56
+ /**
57
+ * 自定义截断逻辑
58
+ * @param node 原始节点
59
+ * @param displayedChars 当前应显示的字符数
60
+ * @param totalChars 该节点的总字符数
61
+ * @returns 截断后的节点,null 表示不显示
62
+ */
63
+ sliceNode?: (
64
+ node: RootContent,
65
+ displayedChars: number,
66
+ totalChars: number
67
+ ) => RootContent | null
68
+
69
+ /**
70
+ * 节点显示完成时的回调
71
+ */
72
+ onComplete?: (node: RootContent) => void
73
+ }
74
+
75
+ /**
76
+ * Transformer 配置选项
77
+ */
78
+ export interface TransformerOptions {
79
+ /**
80
+ * 每 tick 增加的字符数
81
+ * - number: 固定步长(默认 1)
82
+ * - [min, max]: 随机步长区间(更自然的打字效果)
83
+ */
84
+ charsPerTick?: number | [number, number]
85
+ /** tick 间隔 (ms),默认 20 */
86
+ tickInterval?: number
87
+ /** 动画效果,默认 'none' */
88
+ effect?: AnimationEffect
89
+ /** 插件列表 */
90
+ plugins?: TransformerPlugin[]
91
+ /** 状态变化回调 */
92
+ onChange?: (displayBlocks: DisplayBlock[]) => void
93
+ /**
94
+ * 是否在页面不可见时自动暂停
95
+ * 默认 true,节省资源
96
+ */
97
+ pauseOnHidden?: boolean
98
+ }
99
+
100
+ /**
101
+ * Transformer 内部状态
102
+ */
103
+ export interface TransformerState<T = unknown> {
104
+ /** 已完成显示的 blocks */
105
+ completedBlocks: SourceBlock<T>[]
106
+ /** 当前正在显示的 block */
107
+ currentBlock: SourceBlock<T> | null
108
+ /** 当前 block 已显示的字符数 */
109
+ currentProgress: number
110
+ /** 等待显示的 blocks */
111
+ pendingBlocks: SourceBlock<T>[]
112
+ }
113
+
@@ -0,0 +1,85 @@
1
+ import type { RootContent } from 'mdast'
2
+
3
+ /**
4
+ * 计算 AST 节点的总字符数
5
+ */
6
+ export function countChars(node: RootContent): number {
7
+ let count = 0
8
+
9
+ function traverse(n: any): void {
10
+ // 文本类节点
11
+ if (n.value && typeof n.value === 'string') {
12
+ count += n.value.length
13
+ return
14
+ }
15
+
16
+ // 容器节点,递归处理子节点
17
+ if (n.children && Array.isArray(n.children)) {
18
+ for (const child of n.children) {
19
+ traverse(child)
20
+ }
21
+ }
22
+ }
23
+
24
+ traverse(node)
25
+ return count
26
+ }
27
+
28
+ /**
29
+ * 截断 AST 节点,只保留前 maxChars 个字符
30
+ *
31
+ * @param node 原始节点
32
+ * @param maxChars 最大字符数
33
+ * @returns 截断后的节点,如果 maxChars <= 0 返回 null
34
+ */
35
+ export function sliceAst(node: RootContent, maxChars: number): RootContent | null {
36
+ if (maxChars <= 0) return null
37
+
38
+ let remaining = maxChars
39
+
40
+ function process(n: any): any {
41
+ if (remaining <= 0) return null
42
+
43
+ // 文本类节点:截断 value
44
+ if (n.value && typeof n.value === 'string') {
45
+ const take = Math.min(n.value.length, remaining)
46
+ remaining -= take
47
+ if (take === 0) return null
48
+ return { ...n, value: n.value.slice(0, take) }
49
+ }
50
+
51
+ // 容器节点:递归处理 children
52
+ if (n.children && Array.isArray(n.children)) {
53
+ const newChildren: any[] = []
54
+ for (const child of n.children) {
55
+ if (remaining <= 0) break
56
+ const processed = process(child)
57
+ if (processed) {
58
+ newChildren.push(processed)
59
+ }
60
+ }
61
+ // 如果没有 children 了,根据节点类型决定是否保留
62
+ if (newChildren.length === 0) {
63
+ // 对于某些容器节点,即使没有内容也应该保留结构
64
+ // 例如 list 节点如果没有 children 就不应该渲染
65
+ return null
66
+ }
67
+ return { ...n, children: newChildren }
68
+ }
69
+
70
+ // 其他节点(如 thematicBreak, image):整体处理
71
+ // 算作 1 个字符的消耗
72
+ remaining -= 1
73
+ return { ...n }
74
+ }
75
+
76
+ return process(node)
77
+ }
78
+
79
+ /**
80
+ * 深拷贝 AST 节点
81
+ */
82
+ export function cloneNode<T extends RootContent>(node: T): T {
83
+ return JSON.parse(JSON.stringify(node))
84
+ }
85
+