@incremark/vue 0.1.0 → 0.2.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.
@@ -1,19 +1,15 @@
1
- import { ref, shallowRef, computed, markRaw, watch, onUnmounted, type Ref, type ComputedRef } from 'vue'
1
+ import { ref, shallowRef, computed, markRaw, type ComputedRef } from 'vue'
2
2
  import {
3
- IncremarkParser,
4
3
  createIncremarkParser,
5
- createBlockTransformer,
6
- defaultPlugins,
7
4
  type ParserOptions,
8
5
  type ParsedBlock,
9
6
  type IncrementalUpdate,
10
7
  type Root,
11
- type RootContent,
12
- type DisplayBlock,
13
8
  type TransformerPlugin,
14
- type AnimationEffect,
15
- type BlockTransformer
9
+ type AnimationEffect
16
10
  } from '@incremark/core'
11
+ import { useProvideDefinations } from './useProvideDefinations'
12
+ import { useTypewriter } from './useTypewriter'
17
13
 
18
14
  /** 打字机效果配置 */
19
15
  export interface TypewriterOptions {
@@ -40,8 +36,10 @@ export interface UseIncremarkOptions extends ParserOptions {
40
36
 
41
37
  /** 打字机控制对象 */
42
38
  export interface TypewriterControls {
43
- /** 是否启用 */
44
- enabled: Ref<boolean>
39
+ /** 是否启用(只读) */
40
+ enabled: ComputedRef<boolean>
41
+ /** 设置是否启用 */
42
+ setEnabled: (enabled: boolean) => void
45
43
  /** 是否正在处理中 */
46
44
  isProcessing: ComputedRef<boolean>
47
45
  /** 是否已暂停 */
@@ -94,39 +92,33 @@ export type UseIncremarkReturn = ReturnType<typeof useIncremark>
94
92
  * ```
95
93
  */
96
94
  export function useIncremark(options: UseIncremarkOptions = {}) {
95
+ // 内部自动提供 definitions context
96
+ const { setDefinations, setFootnoteDefinitions, setFootnoteReferenceOrder } = useProvideDefinations()
97
+
97
98
  // 解析器
98
- const parser = createIncremarkParser(options)
99
+ const parser = createIncremarkParser({
100
+ ...options,
101
+ onChange: (state) => {
102
+ setDefinations(state.definitions)
103
+ setFootnoteDefinitions(state.footnoteDefinitions)
104
+ // 调用用户提供的 onChange
105
+ options.onChange?.(state)
106
+ }
107
+ })
108
+
99
109
  const completedBlocks = shallowRef<ParsedBlock[]>([])
100
110
  const pendingBlocks = shallowRef<ParsedBlock[]>([])
101
111
  const isLoading = ref(false)
102
112
  const markdown = ref('')
113
+ const isFinalized = ref(false)
114
+ const footnoteReferenceOrder = ref<string[]>([])
103
115
 
104
- // 打字机相关状态
105
- const typewriterEnabled = ref(options.typewriter?.enabled ?? !!options.typewriter)
106
- const displayBlocksRef = shallowRef<DisplayBlock<RootContent>[]>([])
107
- const isTypewriterProcessing = ref(false)
108
- const isTypewriterPaused = ref(false)
109
- const typewriterEffect = ref<AnimationEffect>(options.typewriter?.effect ?? 'none')
110
- const typewriterCursor = ref(options.typewriter?.cursor ?? '|')
111
-
112
- // 创建 transformer(如果有 typewriter 配置)
113
- let transformer: BlockTransformer<RootContent> | null = null
114
-
115
- if (options.typewriter) {
116
- const twOptions = options.typewriter
117
- transformer = createBlockTransformer<RootContent>({
118
- charsPerTick: twOptions.charsPerTick ?? [1, 3],
119
- tickInterval: twOptions.tickInterval ?? 30,
120
- effect: twOptions.effect ?? 'none',
121
- pauseOnHidden: twOptions.pauseOnHidden ?? true,
122
- plugins: twOptions.plugins ?? defaultPlugins,
123
- onChange: (blocks) => {
124
- displayBlocksRef.value = blocks
125
- isTypewriterProcessing.value = transformer?.isProcessing() ?? false
126
- isTypewriterPaused.value = transformer?.isPausedState() ?? false
127
- }
128
- })
129
- }
116
+ // 使用 useTypewriter composable 管理打字机效果
117
+ const { blocks, typewriter, transformer } = useTypewriter({
118
+ typewriter: options.typewriter,
119
+ completedBlocks,
120
+ pendingBlocks
121
+ })
130
122
 
131
123
  // AST
132
124
  const ast = computed<Root>(() => ({
@@ -137,115 +129,6 @@ export function useIncremark(options: UseIncremarkOptions = {}) {
137
129
  ]
138
130
  }))
139
131
 
140
- // 将 completedBlocks 转换为 SourceBlock 格式
141
- const sourceBlocks = computed(() => {
142
- return completedBlocks.value.map(block => ({
143
- id: block.id,
144
- node: block.node,
145
- status: block.status as 'pending' | 'stable' | 'completed'
146
- }))
147
- })
148
-
149
- // 监听 sourceBlocks 变化,推送给 transformer
150
- if (transformer) {
151
- watch(
152
- sourceBlocks,
153
- (blocks) => {
154
- transformer!.push(blocks)
155
-
156
- // 更新正在显示的 block
157
- const currentDisplaying = displayBlocksRef.value.find((b) => !b.isDisplayComplete)
158
- if (currentDisplaying) {
159
- const updated = blocks.find((b) => b.id === currentDisplaying.id)
160
- if (updated) {
161
- transformer!.update(updated)
162
- }
163
- }
164
- },
165
- { immediate: true, deep: true }
166
- )
167
- }
168
-
169
- /**
170
- * 在节点末尾添加光标
171
- */
172
- function addCursorToNode(node: RootContent, cursor: string): RootContent {
173
- const cloned = JSON.parse(JSON.stringify(node))
174
-
175
- function addToLast(n: { children?: unknown[]; type?: string; value?: string }): boolean {
176
- if (n.children && n.children.length > 0) {
177
- for (let i = n.children.length - 1; i >= 0; i--) {
178
- if (addToLast(n.children[i] as { children?: unknown[]; type?: string; value?: string })) {
179
- return true
180
- }
181
- }
182
- n.children.push({ type: 'text', value: cursor })
183
- return true
184
- }
185
- if (n.type === 'text' && typeof n.value === 'string') {
186
- n.value += cursor
187
- return true
188
- }
189
- if (typeof n.value === 'string') {
190
- n.value += cursor
191
- return true
192
- }
193
- return false
194
- }
195
-
196
- addToLast(cloned)
197
- return cloned
198
- }
199
-
200
- // 原始 blocks(不经过打字机)
201
- const rawBlocks = computed(() => {
202
- const result: Array<ParsedBlock & { stableId: string }> = []
203
-
204
- for (const block of completedBlocks.value) {
205
- result.push({ ...block, stableId: block.id })
206
- }
207
-
208
- for (let i = 0; i < pendingBlocks.value.length; i++) {
209
- result.push({
210
- ...pendingBlocks.value[i],
211
- stableId: `pending-${i}`
212
- })
213
- }
214
-
215
- return result
216
- })
217
-
218
- // 最终用于渲染的 blocks
219
- const blocks = computed(() => {
220
- // 未启用打字机或没有 transformer:返回原始 blocks
221
- if (!typewriterEnabled.value || !transformer) {
222
- return rawBlocks.value
223
- }
224
-
225
- // 启用打字机:使用 displayBlocks
226
- return displayBlocksRef.value.map((db, index) => {
227
- const isPending = !db.isDisplayComplete
228
- const isLastPending = isPending && index === displayBlocksRef.value.length - 1
229
-
230
- // typing 效果时添加光标
231
- let node = db.displayNode
232
- if (typewriterEffect.value === 'typing' && isLastPending) {
233
- node = addCursorToNode(db.displayNode, typewriterCursor.value)
234
- }
235
-
236
- return {
237
- id: db.id,
238
- stableId: db.id,
239
- status: (db.isDisplayComplete ? 'completed' : 'pending') as 'pending' | 'stable' | 'completed',
240
- isLastPending,
241
- node,
242
- startOffset: 0,
243
- endOffset: 0,
244
- rawText: ''
245
- }
246
- })
247
- })
248
-
249
132
  function append(chunk: string): IncrementalUpdate {
250
133
  isLoading.value = true
251
134
  const update = parser.append(chunk)
@@ -260,6 +143,10 @@ export function useIncremark(options: UseIncremarkOptions = {}) {
260
143
  }
261
144
  pendingBlocks.value = update.pending.map((b) => markRaw(b))
262
145
 
146
+ // 更新脚注引用顺序
147
+ footnoteReferenceOrder.value = update.footnoteReferenceOrder
148
+ setFootnoteReferenceOrder(update.footnoteReferenceOrder)
149
+
263
150
  return update
264
151
  }
265
152
 
@@ -276,6 +163,11 @@ export function useIncremark(options: UseIncremarkOptions = {}) {
276
163
  }
277
164
  pendingBlocks.value = []
278
165
  isLoading.value = false
166
+ isFinalized.value = true
167
+
168
+ // 更新脚注引用顺序
169
+ footnoteReferenceOrder.value = update.footnoteReferenceOrder
170
+ setFootnoteReferenceOrder(update.footnoteReferenceOrder)
279
171
 
280
172
  return update
281
173
  }
@@ -290,6 +182,8 @@ export function useIncremark(options: UseIncremarkOptions = {}) {
290
182
  pendingBlocks.value = []
291
183
  markdown.value = ''
292
184
  isLoading.value = false
185
+ isFinalized.value = false
186
+ footnoteReferenceOrder.value = []
293
187
 
294
188
  // 重置 transformer
295
189
  transformer?.reset()
@@ -302,51 +196,13 @@ export function useIncremark(options: UseIncremarkOptions = {}) {
302
196
  completedBlocks.value = parser.getCompletedBlocks().map(b => markRaw(b))
303
197
  pendingBlocks.value = []
304
198
  isLoading.value = false
199
+ isFinalized.value = true
200
+ footnoteReferenceOrder.value = update.footnoteReferenceOrder
201
+ setFootnoteReferenceOrder(update.footnoteReferenceOrder)
305
202
 
306
203
  return update
307
204
  }
308
205
 
309
- // 打字机控制对象
310
- const typewriter: TypewriterControls = {
311
- enabled: typewriterEnabled,
312
- isProcessing: computed(() => isTypewriterProcessing.value),
313
- isPaused: computed(() => isTypewriterPaused.value),
314
- effect: computed(() => typewriterEffect.value),
315
- skip: () => transformer?.skip(),
316
- pause: () => {
317
- transformer?.pause()
318
- isTypewriterPaused.value = true
319
- },
320
- resume: () => {
321
- transformer?.resume()
322
- isTypewriterPaused.value = false
323
- },
324
- setOptions: (opts) => {
325
- if (opts.enabled !== undefined) {
326
- typewriterEnabled.value = opts.enabled
327
- }
328
- if (opts.charsPerTick !== undefined || opts.tickInterval !== undefined || opts.effect !== undefined || opts.pauseOnHidden !== undefined) {
329
- transformer?.setOptions({
330
- charsPerTick: opts.charsPerTick,
331
- tickInterval: opts.tickInterval,
332
- effect: opts.effect,
333
- pauseOnHidden: opts.pauseOnHidden
334
- })
335
- }
336
- if (opts.effect !== undefined) {
337
- typewriterEffect.value = opts.effect
338
- }
339
- if (opts.cursor !== undefined) {
340
- typewriterCursor.value = opts.cursor
341
- }
342
- }
343
- }
344
-
345
- // 清理
346
- onUnmounted(() => {
347
- transformer?.destroy()
348
- })
349
-
350
206
  return {
351
207
  /** 已收集的完整 Markdown 字符串 */
352
208
  markdown,
@@ -360,6 +216,10 @@ export function useIncremark(options: UseIncremarkOptions = {}) {
360
216
  blocks,
361
217
  /** 是否正在加载 */
362
218
  isLoading,
219
+ /** 是否已完成(finalize) */
220
+ isFinalized,
221
+ /** 脚注引用的出现顺序 */
222
+ footnoteReferenceOrder,
363
223
  /** 追加内容 */
364
224
  append,
365
225
  /** 完成解析 */
@@ -0,0 +1,61 @@
1
+ import { ref, provide, type InjectionKey, type Ref } from 'vue';
2
+ import type { Definition, FootnoteDefinition } from 'mdast';
3
+
4
+ export const definationsInjectionKey: InjectionKey<{
5
+ definations: Ref<Record<string, Definition>>
6
+ footnoteDefinitions: Ref<Record<string, FootnoteDefinition>>
7
+ footnoteReferenceOrder: Ref<string[]>
8
+ }> = Symbol('provideDefinations');
9
+
10
+ export function useProvideDefinations() {
11
+ const definations = ref<Record<string, Definition>>({});
12
+ const footnoteDefinitions = ref<Record<string, FootnoteDefinition>>({});
13
+ const footnoteReferenceOrder = ref<string[]>([]);
14
+
15
+ provide(definationsInjectionKey, {
16
+ definations,
17
+ footnoteDefinitions,
18
+ footnoteReferenceOrder
19
+ });
20
+
21
+ function setDefinations(definitions: Record<string, Definition>) {
22
+ definations.value = definitions;
23
+ }
24
+
25
+ function setFootnoteDefinitions(definitions: Record<string, FootnoteDefinition>) {
26
+ footnoteDefinitions.value = definitions;
27
+ }
28
+
29
+ function setFootnoteReferenceOrder(order: string[]) {
30
+ footnoteReferenceOrder.value = order;
31
+ }
32
+
33
+ function clearDefinations() {
34
+ definations.value = {};
35
+ }
36
+
37
+ function clearFootnoteDefinitions() {
38
+ footnoteDefinitions.value = {};
39
+ }
40
+
41
+ function clearFootnoteReferenceOrder() {
42
+ footnoteReferenceOrder.value = [];
43
+ }
44
+
45
+ function clearAllDefinations() {
46
+ clearDefinations();
47
+ clearFootnoteDefinitions();
48
+ clearFootnoteReferenceOrder();
49
+ }
50
+
51
+ return {
52
+ setDefinations,
53
+ setFootnoteDefinitions,
54
+ setFootnoteReferenceOrder,
55
+ clearDefinations,
56
+ clearFootnoteDefinitions,
57
+ clearFootnoteReferenceOrder,
58
+ clearAllDefinations
59
+ }
60
+
61
+ }
@@ -0,0 +1,205 @@
1
+ /**
2
+ * @file useTypewriter Composable - 打字机效果管理
3
+ *
4
+ * @description
5
+ * 管理打字机效果的状态和控制逻辑,从 useIncremark 中拆分出来以简化代码。
6
+ *
7
+ * @author Incremark Team
8
+ * @license MIT
9
+ */
10
+
11
+ import { ref, shallowRef, computed, watch, onUnmounted, type Ref, type ComputedRef } from 'vue'
12
+ import {
13
+ createBlockTransformer,
14
+ defaultPlugins,
15
+ type RootContent,
16
+ type ParsedBlock,
17
+ type DisplayBlock,
18
+ type AnimationEffect,
19
+ type BlockTransformer
20
+ } from '@incremark/core'
21
+ import type { TypewriterOptions, TypewriterControls } from './useIncremark'
22
+ import { addCursorToNode } from '../utils/cursor'
23
+
24
+ export interface UseTypewriterOptions {
25
+ typewriter?: TypewriterOptions
26
+ completedBlocks: Ref<ParsedBlock[]>
27
+ pendingBlocks: Ref<ParsedBlock[]>
28
+ }
29
+
30
+ export interface UseTypewriterReturn {
31
+ /** 用于渲染的 blocks(经过打字机处理或原始blocks) */
32
+ blocks: ComputedRef<Array<ParsedBlock & { stableId: string }>>
33
+ /** 打字机控制对象 */
34
+ typewriter: TypewriterControls
35
+ /** transformer 实例 */
36
+ transformer: BlockTransformer<RootContent> | null
37
+ }
38
+
39
+ /**
40
+ * useTypewriter Composable
41
+ *
42
+ * @description
43
+ * 管理打字机效果的所有状态和逻辑。
44
+ *
45
+ * @param options - 打字机配置和数据
46
+ * @returns 打字机状态和控制对象
47
+ */
48
+ export function useTypewriter(options: UseTypewriterOptions): UseTypewriterReturn {
49
+ const { typewriter: typewriterConfig, completedBlocks, pendingBlocks } = options
50
+
51
+ // 打字机状态
52
+ const typewriterEnabled = ref(typewriterConfig?.enabled ?? !!typewriterConfig)
53
+ const displayBlocksRef = shallowRef<DisplayBlock<RootContent>[]>([])
54
+ const isTypewriterProcessing = ref(false)
55
+ const isTypewriterPaused = ref(false)
56
+ const typewriterEffect = ref<AnimationEffect>(typewriterConfig?.effect ?? 'none')
57
+ const typewriterCursor = ref(typewriterConfig?.cursor ?? '|')
58
+
59
+ // 创建 transformer(如果有 typewriter 配置)
60
+ let transformer: BlockTransformer<RootContent> | null = null
61
+
62
+ if (typewriterConfig) {
63
+ const twOptions = typewriterConfig
64
+ transformer = createBlockTransformer<RootContent>({
65
+ charsPerTick: twOptions.charsPerTick ?? [1, 3],
66
+ tickInterval: twOptions.tickInterval ?? 30,
67
+ effect: twOptions.effect ?? 'none',
68
+ pauseOnHidden: twOptions.pauseOnHidden ?? true,
69
+ plugins: twOptions.plugins ?? defaultPlugins,
70
+ onChange: (blocks) => {
71
+ displayBlocksRef.value = blocks as DisplayBlock<RootContent>[]
72
+ isTypewriterProcessing.value = transformer?.isProcessing() ?? false
73
+ isTypewriterPaused.value = transformer?.isPausedState() ?? false
74
+ }
75
+ })
76
+ }
77
+
78
+ // 将 completedBlocks 转换为 SourceBlock 格式
79
+ const sourceBlocks = computed(() => {
80
+ return completedBlocks.value.map(block => ({
81
+ id: block.id,
82
+ node: block.node,
83
+ status: block.status as 'pending' | 'stable' | 'completed'
84
+ }))
85
+ })
86
+
87
+ // 监听 sourceBlocks 变化,推送给 transformer
88
+ if (transformer) {
89
+ watch(
90
+ sourceBlocks,
91
+ (blocks) => {
92
+ transformer!.push(blocks)
93
+
94
+ // 更新正在显示的 block
95
+ const currentDisplaying = displayBlocksRef.value.find((b) => !b.isDisplayComplete)
96
+ if (currentDisplaying) {
97
+ const updated = blocks.find((b) => b.id === currentDisplaying.id)
98
+ if (updated) {
99
+ transformer!.update(updated)
100
+ }
101
+ }
102
+ },
103
+ { immediate: true, deep: true }
104
+ )
105
+ }
106
+
107
+ // 原始 blocks(不经过打字机)
108
+ const rawBlocks = computed(() => {
109
+ const result: Array<ParsedBlock & { stableId: string }> = []
110
+
111
+ for (const block of completedBlocks.value) {
112
+ result.push({ ...block, stableId: block.id })
113
+ }
114
+
115
+ for (let i = 0; i < pendingBlocks.value.length; i++) {
116
+ result.push({
117
+ ...pendingBlocks.value[i],
118
+ stableId: `pending-${i}`
119
+ })
120
+ }
121
+
122
+ return result
123
+ })
124
+
125
+ // 最终用于渲染的 blocks
126
+ const blocks = computed(() => {
127
+ // 未启用打字机或没有 transformer:返回原始 blocks
128
+ if (!typewriterEnabled.value || !transformer) {
129
+ return rawBlocks.value
130
+ }
131
+
132
+ // 启用打字机:使用 displayBlocks
133
+ return displayBlocksRef.value.map((db, index) => {
134
+ const isPending = !db.isDisplayComplete
135
+ const isLastPending = isPending && index === displayBlocksRef.value.length - 1
136
+
137
+ // typing 效果时添加光标
138
+ let node = db.displayNode
139
+ if (typewriterEffect.value === 'typing' && isLastPending) {
140
+ node = addCursorToNode(db.displayNode, typewriterCursor.value)
141
+ }
142
+
143
+ return {
144
+ id: db.id,
145
+ stableId: db.id,
146
+ status: (db.isDisplayComplete ? 'completed' : 'pending') as 'pending' | 'stable' | 'completed',
147
+ isLastPending,
148
+ node,
149
+ startOffset: 0,
150
+ endOffset: 0,
151
+ rawText: ''
152
+ }
153
+ })
154
+ })
155
+
156
+ // 打字机控制对象
157
+ const typewriterControls: TypewriterControls = {
158
+ enabled: computed(() => typewriterEnabled.value),
159
+ setEnabled: (value: boolean) => {
160
+ typewriterEnabled.value = value
161
+ },
162
+ isProcessing: computed(() => isTypewriterProcessing.value),
163
+ isPaused: computed(() => isTypewriterPaused.value),
164
+ effect: computed(() => typewriterEffect.value),
165
+ skip: () => transformer?.skip(),
166
+ pause: () => {
167
+ transformer?.pause()
168
+ isTypewriterPaused.value = true
169
+ },
170
+ resume: () => {
171
+ transformer?.resume()
172
+ isTypewriterPaused.value = false
173
+ },
174
+ setOptions: (opts) => {
175
+ if (opts.enabled !== undefined) {
176
+ typewriterEnabled.value = opts.enabled
177
+ }
178
+ if (opts.charsPerTick !== undefined || opts.tickInterval !== undefined || opts.effect !== undefined || opts.pauseOnHidden !== undefined) {
179
+ transformer?.setOptions({
180
+ charsPerTick: opts.charsPerTick,
181
+ tickInterval: opts.tickInterval,
182
+ effect: opts.effect,
183
+ pauseOnHidden: opts.pauseOnHidden
184
+ })
185
+ }
186
+ if (opts.effect !== undefined) {
187
+ typewriterEffect.value = opts.effect
188
+ }
189
+ if (opts.cursor !== undefined) {
190
+ typewriterCursor.value = opts.cursor
191
+ }
192
+ }
193
+ }
194
+
195
+ // 清理
196
+ onUnmounted(() => {
197
+ transformer?.destroy()
198
+ })
199
+
200
+ return {
201
+ blocks,
202
+ typewriter: typewriterControls,
203
+ transformer
204
+ }
205
+ }
package/src/index.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  // Composables
2
2
  export { useIncremark, useStreamRenderer, useDevTools, useBlockTransformer } from './composables'
3
+ export { useProvideDefinations } from './composables/useProvideDefinations'
4
+ export { useDefinationsContext } from './composables/useDefinationsContext'
3
5
  export type {
4
6
  UseIncremarkOptions,
5
7
  TypewriterOptions,
@@ -23,10 +25,13 @@ export {
23
25
  IncremarkThematicBreak,
24
26
  IncremarkInline,
25
27
  IncremarkMath,
28
+ IncremarkHtmlElement,
26
29
  IncremarkDefault,
30
+ IncremarkFootnotes,
27
31
  AutoScrollContainer
28
32
  } from './components'
29
33
  export type { ComponentMap, BlockWithStableId } from './components'
34
+ export { default as ThemeProvider } from './ThemeProvider.vue'
30
35
 
31
36
  // Re-export core types
32
37
  export type {
@@ -61,3 +66,13 @@ export {
61
66
  allPlugins,
62
67
  createPlugin
63
68
  } from '@incremark/core'
69
+
70
+ // Re-export theme utilities
71
+ export {
72
+ type DesignTokens,
73
+ defaultTheme,
74
+ darkTheme,
75
+ generateCSSVars,
76
+ mergeTheme,
77
+ applyTheme
78
+ } from '@incremark/theme'
@@ -0,0 +1,46 @@
1
+ /**
2
+ * @file Cursor Utils - 光标工具函数
3
+ *
4
+ * @description
5
+ * 用于在 AST 节点末尾添加光标的工具函数。
6
+ *
7
+ * @author Incremark Team
8
+ * @license MIT
9
+ */
10
+
11
+ import type { RootContent } from '@incremark/core'
12
+
13
+ /**
14
+ * 在节点末尾添加光标
15
+ *
16
+ * @param node - 要添加光标的节点
17
+ * @param cursor - 光标字符
18
+ * @returns 添加了光标的新节点
19
+ */
20
+ export function addCursorToNode(node: RootContent, cursor: string): RootContent {
21
+ const cloned = JSON.parse(JSON.stringify(node))
22
+
23
+ function addToLast(n: { children?: unknown[]; type?: string; value?: string }): boolean {
24
+ if (n.children && n.children.length > 0) {
25
+ for (let i = n.children.length - 1; i >= 0; i--) {
26
+ if (addToLast(n.children[i] as { children?: unknown[]; type?: string; value?: string })) {
27
+ return true
28
+ }
29
+ }
30
+ n.children.push({ type: 'text', value: cursor })
31
+ return true
32
+ }
33
+ if (n.type === 'text' && typeof n.value === 'string') {
34
+ n.value += cursor
35
+ return true
36
+ }
37
+ if (typeof n.value === 'string') {
38
+ n.value += cursor
39
+ return true
40
+ }
41
+ return false
42
+ }
43
+
44
+ addToLast(cloned)
45
+ return cloned
46
+ }