@univerjs/docs 1.0.0-alpha.0 → 1.0.0-alpha.2

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/lib/facade.js CHANGED
@@ -1,88 +1,7 @@
1
- import { ICommandService, IResourceLoaderService, IUniverInstanceService, Inject, Injector, JSONX, PresetListType, RedoCommand, TextX, TextXActionType, UndoCommand, UniverInstanceType, UpdateDocsAttributeType, createParagraphId, getRichTextEditPath } from "@univerjs/core";
1
+ import { BooleanNumber, DataStreamTreeTokenType, DocumentFlavor, ICommandService, IResourceLoaderService, IUniverInstanceService, Inject, Injector, JSONX, PresetListType, RESTORE_INSERTED_PARAGRAPH_IDS, RedoCommand, TextX, TextXActionType, UndoCommand, UniverInstanceType, UpdateDocsAttributeType, createParagraphId, generateRandomId, getRichTextEditPath } from "@univerjs/core";
2
2
  import { FBaseInitialable, FUniver } from "@univerjs/core/facade";
3
- import { InsertTextCommand, RichTextEditingMutation } from "@univerjs/docs";
3
+ import { CreateHeaderFooterCommand, HeaderFooterType, RichTextEditingMutation } from "@univerjs/docs";
4
4
 
5
- //#region src/facade/doc-element-registry.ts
6
- const PARAGRAPH_REGISTRY_MIGRATION_ERROR = "DocElementRegistry no longer tracks paragraph identity; use paragraphId.";
7
- /**
8
- * Error thrown when a facade element key can no longer be resolved uniquely.
9
- *
10
- * A stale paragraph usually means the paragraph was deleted, or the document no
11
- * longer contains exactly one paragraph with the requested `paragraphId`.
12
- *
13
- * @example
14
- * ```ts
15
- * const doc = univerAPI.getActiveDocument();
16
- * if (!doc) throw new Error('No active document');
17
- *
18
- * const paragraph = doc.getBody().getChild(0).asParagraph();
19
- * paragraph.removeFromParent();
20
- *
21
- * try {
22
- * paragraph.getText();
23
- * } catch (error) {
24
- * if (error instanceof DocElementStaleError) {
25
- * console.log('The paragraph handle is stale.');
26
- * }
27
- * }
28
- * ```
29
- */
30
- var DocElementStaleError = class extends Error {
31
- /**
32
- * Create a stale element error.
33
- * @param {string} message The error message.
34
- */
35
- constructor(message = "Doc element is stale") {
36
- super(message);
37
- this.name = "DocElementStaleError";
38
- }
39
- };
40
- /**
41
- * @deprecated Paragraph facade identity is now resolved directly from persisted
42
- * `paragraphId` values. This class remains as a compatibility shell for callers
43
- * that constructed document facade internals directly.
44
- *
45
- * @hideconstructor
46
- */
47
- var DocElementRegistry = class {
48
- /**
49
- * @deprecated Paragraph facade identity is now the persisted `paragraphId`.
50
- * @throws {Error} Always throws; use `paragraph.paragraphId` instead.
51
- */
52
- getParagraphKey(_segmentId, _body, _paragraphIndex) {
53
- throw new Error(PARAGRAPH_REGISTRY_MIGRATION_ERROR);
54
- }
55
- /**
56
- * @deprecated Paragraph facade identity is now the persisted `paragraphId`.
57
- * @throws {Error} Always throws; resolve paragraph handles by `paragraphId` instead.
58
- */
59
- resolveParagraphStartIndex(_segmentId, _key) {
60
- throw new Error(PARAGRAPH_REGISTRY_MIGRATION_ERROR);
61
- }
62
- /**
63
- * @deprecated Paragraph facade identity is now the persisted `paragraphId`.
64
- * @throws {Error} Always throws; resolve paragraph handles by `paragraphId` instead.
65
- */
66
- syncParagraph(_segmentId, _key, _body) {
67
- throw new Error(PARAGRAPH_REGISTRY_MIGRATION_ERROR);
68
- }
69
- /**
70
- * @deprecated Paragraph facade identity is now the persisted `paragraphId`.
71
- * @throws {Error} Always throws; stale state is detected from live `paragraphId` lookups.
72
- */
73
- markStale(_segmentId, _key) {
74
- throw new Error(PARAGRAPH_REGISTRY_MIGRATION_ERROR);
75
- }
76
- /**
77
- * @deprecated Paragraph facade identity is now the persisted `paragraphId`.
78
- * @throws {Error} Always throws; text edits no longer need registry offset tracking.
79
- */
80
- beforeTextEdit(_segmentId, _startOffset, _endOffset, _insertLength) {
81
- throw new Error(PARAGRAPH_REGISTRY_MIGRATION_ERROR);
82
- }
83
- };
84
-
85
- //#endregion
86
5
  //#region src/facade/utils.ts
87
6
  function cloneParagraphStyle(paragraphStyle) {
88
7
  return paragraphStyle == null ? paragraphStyle : JSON.parse(JSON.stringify(paragraphStyle));
@@ -95,16 +14,6 @@ function getRemovedLeadingParagraphBreakLength(dataStream, removeLeadingParagrap
95
14
  if (removeLeadingParagraphBreak && normalized.length > 1 && normalized.startsWith("\r")) return 1;
96
15
  return 0;
97
16
  }
98
- function getNormalizedPlainTextCursorOffset(dataStream, cursorOffset, removeLeadingParagraphBreak) {
99
- const normalizedPrefixLength = normalizePlainTextDataStream(dataStream.slice(0, cursorOffset)).length;
100
- return Math.max(0, normalizedPrefixLength - getRemovedLeadingParagraphBreakLength(dataStream, removeLeadingParagraphBreak));
101
- }
102
- function getParagraphStyleAtOffset(body, offset) {
103
- var _body$paragraphs, _paragraphs$find;
104
- const paragraphs = (_body$paragraphs = body.paragraphs) !== null && _body$paragraphs !== void 0 ? _body$paragraphs : [];
105
- const paragraph = (_paragraphs$find = paragraphs.find((item) => item.startIndex >= offset)) !== null && _paragraphs$find !== void 0 ? _paragraphs$find : paragraphs[paragraphs.length - 1];
106
- return paragraph === null || paragraph === void 0 ? void 0 : paragraph.paragraphStyle;
107
- }
108
17
  function buildPlainTextInsertBody(dataStream, options = {}) {
109
18
  const normalizedDataStream = normalizePlainTextDataStream(dataStream).slice(getRemovedLeadingParagraphBreakLength(dataStream, options.removeLeadingParagraphBreak));
110
19
  const body = {
@@ -123,243 +32,73 @@ function buildPlainTextInsertBody(dataStream, options = {}) {
123
32
  if (paragraphs.length > 0) body.paragraphs = paragraphs;
124
33
  return body;
125
34
  }
126
-
127
- //#endregion
128
- //#region src/facade/f-doc-block-range.ts
129
- /**
130
- * A facade wrapper for document block ranges, such as callout, quote, and code blocks.
131
- *
132
- * Block range identity is backed by the persisted `IDocumentBlockRange.blockId`,
133
- * so wrappers can be re-resolved after text is inserted before the block range.
134
- *
135
- * @hideconstructor
136
- */
137
- var FDocBlockRange = class {
138
- constructor(_body, _key) {
139
- this._body = _body;
140
- this._key = _key;
141
- }
142
- /**
143
- * Get the document element type.
144
- * @returns {'blockRange'} The literal block range element type.
145
- * @example
146
- * ```ts
147
- * const doc = univerAPI.getActiveDocument();
148
- * if (!doc) throw new Error('No active document');
149
- *
150
- * const blockRange = doc.getBody().getChild(0).asBlockRange();
151
- * console.log(blockRange.getType());
152
- * ```
153
- */
154
- getType() {
155
- return "blockRange";
156
- }
157
- /**
158
- * Get the block range key.
159
- * @returns {string} The persisted `blockId` for this block range.
160
- * @example
161
- * ```ts
162
- * const doc = univerAPI.getActiveDocument();
163
- * if (!doc) throw new Error('No active document');
164
- *
165
- * const blockRange = doc.getBody().getChild(0).asBlockRange();
166
- * console.log(blockRange.getKey());
167
- * ```
168
- */
169
- getKey() {
170
- return this._key;
171
- }
172
- /**
173
- * Get the parent body facade that owns this block range.
174
- * @returns {FDocBody} The document body facade.
175
- * @example
176
- * ```ts
177
- * const doc = univerAPI.getActiveDocument();
178
- * if (!doc) throw new Error('No active document');
179
- *
180
- * const blockRange = doc.getBody().getChild(0).asBlockRange();
181
- * console.log(blockRange.getParent().getChildIndex(blockRange));
182
- * ```
183
- */
184
- getParent() {
185
- return this._body;
186
- }
187
- /**
188
- * Remove this block range and its content from the parent body.
189
- * @returns {boolean} `true` if the block range content was removed.
190
- * @example
191
- * ```ts
192
- * const doc = univerAPI.getActiveDocument();
193
- * if (!doc) throw new Error('No active document');
194
- *
195
- * const blockRange = doc.getBody().getChild(0).asBlockRange();
196
- * blockRange.removeFromParent();
197
- * ```
198
- */
199
- removeFromParent() {
200
- return this._body.removeBlockRange(this._key);
201
- }
202
- /**
203
- * Get the block range type.
204
- * @returns {DocumentBlockRangeType} The block type, such as callout, quote, or code.
205
- * @example
206
- * ```ts
207
- * const doc = univerAPI.getActiveDocument();
208
- * if (!doc) throw new Error('No active document');
209
- *
210
- * const blockRange = doc.getBody().getChild(0).asBlockRange();
211
- * console.log(blockRange.getBlockType());
212
- * ```
213
- */
214
- getBlockType() {
215
- return this._body.getBlockRange(this._key).blockType;
216
- }
217
- /**
218
- * Get the plain text inside this block range.
219
- * @returns {string} The block range text.
220
- * @example
221
- * ```ts
222
- * const doc = univerAPI.getActiveDocument();
223
- * if (!doc) throw new Error('No active document');
224
- *
225
- * const blockRange = doc.getBody().getChild(0).asBlockRange();
226
- * console.log(blockRange.getText());
227
- * ```
228
- */
229
- getText() {
230
- return this._body.getBlockRangeText(this._key);
231
- }
232
- /**
233
- * Replace the plain text inside this block range.
234
- * @param {string} text The replacement text.
235
- * @returns {boolean} `true` if the block range text was replaced.
236
- * @example
237
- * ```ts
238
- * const doc = univerAPI.getActiveDocument();
239
- * if (!doc) throw new Error('No active document');
240
- *
241
- * const blockRange = doc.getBody().getChild(0).asBlockRange();
242
- * blockRange.setText('Updated block text');
243
- * ```
244
- */
245
- setText(text) {
246
- return this._body.setBlockRangeText(this._key, text);
247
- }
248
- /**
249
- * Remove this block range wrapper from the body.
250
- *
251
- * This currently removes the block range and its content, matching
252
- * `removeFromParent()`.
253
- *
254
- * @returns {boolean} `true` if the block range content was removed.
255
- * @example
256
- * ```ts
257
- * const doc = univerAPI.getActiveDocument();
258
- * if (!doc) throw new Error('No active document');
259
- *
260
- * const blockRange = doc.getBody().getChild(0).asBlockRange();
261
- * blockRange.unwrap();
262
- * ```
263
- */
264
- unwrap() {
265
- return this.removeFromParent();
266
- }
267
- };
268
-
269
- //#endregion
270
- //#region src/facade/f-doc-custom-block.ts
271
- /**
272
- * A facade wrapper for a document custom block, such as an embedded drawing or widget.
273
- *
274
- * Custom block identity is backed by the persisted `ICustomBlock.blockId`, so the
275
- * wrapper can be re-resolved after text is inserted before the custom block.
276
- *
277
- * @hideconstructor
278
- */
279
- var FDocCustomBlock = class {
280
- constructor(_body, _key) {
281
- this._body = _body;
282
- this._key = _key;
283
- }
284
- /**
285
- * Get the document element type.
286
- * @returns {'customBlock'} The literal custom block element type.
287
- * @example
288
- * ```ts
289
- * const doc = univerAPI.getActiveDocument();
290
- * if (!doc) throw new Error('No active document');
291
- *
292
- * const customBlock = doc.getBody().getChild(0).asCustomBlock();
293
- * console.log(customBlock.getType());
294
- * ```
295
- */
296
- getType() {
297
- return "customBlock";
298
- }
299
- /**
300
- * Get the custom block key.
301
- * @returns {string} The persisted `blockId` for this custom block.
302
- * @example
303
- * ```ts
304
- * const doc = univerAPI.getActiveDocument();
305
- * if (!doc) throw new Error('No active document');
306
- *
307
- * const customBlock = doc.getBody().getChild(0).asCustomBlock();
308
- * console.log(customBlock.getKey());
309
- * ```
310
- */
311
- getKey() {
312
- return this._key;
313
- }
314
- /**
315
- * Get the parent body facade that owns this custom block.
316
- * @returns {FDocBody} The document body facade.
317
- * @example
318
- * ```ts
319
- * const doc = univerAPI.getActiveDocument();
320
- * if (!doc) throw new Error('No active document');
321
- *
322
- * const customBlock = doc.getBody().getChild(0).asCustomBlock();
323
- * console.log(customBlock.getParent().getChildIndex(customBlock));
324
- * ```
325
- */
326
- getParent() {
327
- return this._body;
328
- }
329
- /**
330
- * Remove this custom block from the parent body.
331
- * @returns {boolean} `true` if the custom block placeholder was removed.
332
- * @example
333
- * ```ts
334
- * const doc = univerAPI.getActiveDocument();
335
- * if (!doc) throw new Error('No active document');
336
- *
337
- * const customBlock = doc.getBody().getChild(0).asCustomBlock();
338
- * customBlock.removeFromParent();
339
- * ```
340
- */
341
- removeFromParent() {
342
- return this._body.removeCustomBlock(this._key);
343
- }
344
- /**
345
- * Get the persisted custom block id.
346
- * @returns {string} The `ICustomBlock.blockId` value.
347
- * @example
348
- * ```ts
349
- * const doc = univerAPI.getActiveDocument();
350
- * if (!doc) throw new Error('No active document');
351
- *
352
- * const customBlock = doc.getBody().getChild(0).asCustomBlock();
353
- * console.log(customBlock.getBlockId());
354
- * ```
355
- */
356
- getBlockId() {
357
- return this._body.getCustomBlock(this._key).blockId;
35
+ function replaceBodyRange(range, insertBody, docDataModel, injector) {
36
+ const { startOffset, endOffset, segmentId } = range;
37
+ const textX = new TextX();
38
+ if (startOffset > 0) textX.push({
39
+ t: TextXActionType.RETAIN,
40
+ len: startOffset
41
+ });
42
+ if (endOffset > startOffset) textX.push({
43
+ t: TextXActionType.DELETE,
44
+ len: endOffset - startOffset
45
+ });
46
+ if (insertBody.dataStream.length > 0) textX.push({
47
+ t: TextXActionType.INSERT,
48
+ body: insertBody,
49
+ len: insertBody.dataStream.length
50
+ });
51
+ const actions = JSONX.getInstance().editOp(textX.serialize(), getRichTextEditPath(docDataModel, segmentId));
52
+ const result = injector.get(ICommandService).syncExecuteCommand(RichTextEditingMutation.id, {
53
+ unitId: docDataModel.getUnitId(),
54
+ segmentId,
55
+ actions,
56
+ textRanges: [],
57
+ isEditing: false
58
+ });
59
+ return Boolean((result === null || result === void 0 ? void 0 : result.actions) && result.actions.length > 0);
60
+ }
61
+ function retainBodyRange(range, updateBody, coverType, docDataModel, injector) {
62
+ var _updateBody$textRuns, _docDataModel$getSelf;
63
+ const { startOffset, endOffset, segmentId } = range;
64
+ const commandService = injector.get(ICommandService);
65
+ if (((_updateBody$textRuns = updateBody.textRuns) === null || _updateBody$textRuns === void 0 ? void 0 : _updateBody$textRuns.length) && ((_docDataModel$getSelf = docDataModel.getSelfOrHeaderFooterModel(segmentId)) === null || _docDataModel$getSelf === void 0 || (_docDataModel$getSelf = _docDataModel$getSelf.getBody()) === null || _docDataModel$getSelf === void 0 ? void 0 : _docDataModel$getSelf.textRuns) == null) {
66
+ const actions = JSONX.getInstance().replaceOp([...getRichTextEditPath(docDataModel, segmentId), "textRuns"], void 0, []);
67
+ commandService.syncExecuteCommand(RichTextEditingMutation.id, {
68
+ unitId: docDataModel.getUnitId(),
69
+ segmentId,
70
+ actions,
71
+ textRanges: [],
72
+ isEditing: false
73
+ });
358
74
  }
359
- };
75
+ const textX = new TextX();
76
+ if (startOffset > 0) textX.push({
77
+ t: TextXActionType.RETAIN,
78
+ len: startOffset
79
+ });
80
+ textX.push({
81
+ t: TextXActionType.RETAIN,
82
+ body: updateBody,
83
+ coverType,
84
+ len: endOffset - startOffset
85
+ });
86
+ const actions = JSONX.getInstance().editOp(textX.serialize(), getRichTextEditPath(docDataModel, segmentId));
87
+ const result = commandService.syncExecuteCommand(RichTextEditingMutation.id, {
88
+ unitId: docDataModel.getUnitId(),
89
+ segmentId,
90
+ actions,
91
+ textRanges: [],
92
+ isEditing: false
93
+ });
94
+ return Boolean((result === null || result === void 0 ? void 0 : result.actions) && result.actions.length > 0);
95
+ }
96
+ function stripBlockTokens(text) {
97
+ return Array.from(text).map((char) => char === DataStreamTreeTokenType.PARAGRAPH ? "\n" : char).filter((char) => char !== DataStreamTreeTokenType.BLOCK_START && char !== DataStreamTreeTokenType.BLOCK_END && char !== DataStreamTreeTokenType.SECTION_BREAK).join("").replace(/\n$/, "");
98
+ }
360
99
 
361
100
  //#endregion
362
- //#region src/facade/f-doc-paragraph.ts
101
+ //#region src/facade/f-document-paragraph.ts
363
102
  /**
364
103
  * A paragraph facade wrapper.
365
104
  *
@@ -369,1269 +108,284 @@ var FDocCustomBlock = class {
369
108
  *
370
109
  * @hideconstructor
371
110
  */
372
- var FDocParagraph = class {
373
- constructor(_body, _key) {
374
- this._body = _body;
375
- this._key = _key;
376
- }
377
- /**
378
- * Get the document element type.
379
- * @returns {'paragraph'} The literal paragraph element type.
380
- * @example
381
- * ```ts
382
- * const doc = univerAPI.getActiveDocument();
383
- * if (!doc) throw new Error('No active document');
384
- *
385
- * const paragraph = doc.getBody().getChild(0).asParagraph();
386
- * console.log(paragraph.getType());
387
- * ```
388
- */
389
- getType() {
390
- return "paragraph";
391
- }
392
- /**
393
- * Get the persisted paragraph id.
394
- * @returns {string} The paragraph id.
395
- * @example
396
- * ```ts
397
- * const doc = univerAPI.getActiveDocument();
398
- * if (!doc) throw new Error('No active document');
399
- *
400
- * const paragraph = doc.getBody().getChild(0).asParagraph();
401
- * console.log(paragraph.getKey());
402
- * ```
403
- */
404
- getKey() {
405
- return this._key;
111
+ var FDocumentParagraph = class {
112
+ constructor(_document, _paragraphId, _segmentId = "", _injector) {
113
+ this._document = _document;
114
+ this._paragraphId = _paragraphId;
115
+ this._segmentId = _segmentId;
116
+ this._injector = _injector;
406
117
  }
407
118
  /**
408
119
  * Get the persisted paragraph id.
409
120
  * @returns {string} The paragraph id.
410
121
  * @example
411
122
  * ```ts
412
- * const doc = univerAPI.getActiveDocument();
413
- * if (!doc) throw new Error('No active document');
414
- *
415
- * const paragraph = doc.getBody().getChild(0).asParagraph();
416
- * console.log(paragraph.getId());
123
+ * const fDocument = univerAPI.getActiveDocument();
124
+ * const paragraph = fDocument.getParagraphs()[0];
125
+ * console.log(paragraph?.getId());
417
126
  * ```
418
127
  */
419
128
  getId() {
420
- return this._key;
421
- }
422
- /**
423
- * Get the parent body facade that owns this paragraph.
424
- * @returns {FDocBody} The document body facade.
425
- * @example
426
- * ```ts
427
- * const doc = univerAPI.getActiveDocument();
428
- * if (!doc) throw new Error('No active document');
429
- *
430
- * const paragraph = doc.getBody().getChild(0).asParagraph();
431
- * console.log(paragraph.getParent().getChildIndex(paragraph));
432
- * ```
433
- */
434
- getParent() {
435
- return this._body;
436
- }
437
- /**
438
- * Remove this paragraph from its parent body.
439
- * @returns {boolean} `true` if the paragraph was removed.
440
- * @example
441
- * ```ts
442
- * const doc = univerAPI.getActiveDocument();
443
- * if (!doc) throw new Error('No active document');
444
- *
445
- * const paragraph = doc.getBody().getChild(0).asParagraph();
446
- * paragraph.removeFromParent();
447
- * ```
448
- */
449
- removeFromParent() {
450
- return this._body.removeParagraph(this._key);
451
- }
452
- /**
453
- * Get this paragraph's plain text.
454
- * @returns {string} The paragraph text without the trailing paragraph break.
455
- * @example
456
- * ```ts
457
- * const doc = univerAPI.getActiveDocument();
458
- * if (!doc) throw new Error('No active document');
459
- *
460
- * const paragraph = doc.getBody().getChild(0).asParagraph();
461
- * console.log(paragraph.getText());
462
- * ```
463
- */
464
- getText() {
465
- return this._body.getParagraphText(this._key);
466
- }
467
- /**
468
- * Replace this paragraph's plain text.
469
- * @param {string} text The replacement text. Do not include the paragraph break.
470
- * @returns {boolean} `true` if the paragraph text was replaced.
471
- * @example
472
- * ```ts
473
- * const doc = univerAPI.getActiveDocument();
474
- * if (!doc) throw new Error('No active document');
475
- *
476
- * const paragraph = doc.getBody().getChild(0).asParagraph();
477
- * paragraph.setText('Updated title');
478
- * ```
479
- */
480
- setText(text) {
481
- return this._body.setParagraphText(this._key, text);
482
- }
483
- /**
484
- * Append plain text before this paragraph's trailing paragraph break.
485
- * @param {string} text The plain text to append.
486
- * @returns {boolean} `true` if the text was appended.
487
- * @example
488
- * ```ts
489
- * const doc = univerAPI.getActiveDocument();
490
- * if (!doc) throw new Error('No active document');
491
- *
492
- * const paragraph = doc.getBody().getChild(0).asParagraph();
493
- * paragraph.appendText(' suffix');
494
- * ```
495
- */
496
- appendText(text) {
497
- return this._body.appendParagraphText(this._key, text);
498
- }
499
- /**
500
- * Get the current text range occupied by this paragraph.
501
- * @returns {IFDocTextRange} The paragraph text range, excluding the trailing paragraph break.
502
- * @example
503
- * ```ts
504
- * const doc = univerAPI.getActiveDocument();
505
- * if (!doc) throw new Error('No active document');
506
- *
507
- * const paragraph = doc.getBody().getChild(0).asParagraph();
508
- * const range = paragraph.getRange();
509
- * doc.getBody().setTextStyle(range, { bl: 1 });
510
- * ```
511
- */
512
- getRange() {
513
- return this._body.getParagraphRange(this._key);
514
- }
515
- /**
516
- * Check whether this paragraph is a bullet, ordered, or checklist item.
517
- * @returns {boolean} `true` if the paragraph has list metadata.
518
- * @example
519
- * ```ts
520
- * const doc = univerAPI.getActiveDocument();
521
- * if (!doc) throw new Error('No active document');
522
- *
523
- * const paragraph = doc.getBody().getChild(0).asParagraph();
524
- * console.log(paragraph.isListItem());
525
- * ```
526
- */
527
- isListItem() {
528
- return this._body.isListParagraph(this._key);
129
+ return this._paragraphId;
529
130
  }
530
131
  /**
531
- * Check whether this paragraph is a task/checklist item.
532
- * @returns {boolean} `true` if this paragraph is an unchecked or checked task item.
132
+ * Get the segment id of this paragraph.
133
+ * The main body paragraphs have an empty string segment id.
134
+ * The header and footer paragraphs have a non-empty string segment id.
135
+ * @returns {string} The segment id.
533
136
  * @example
534
137
  * ```ts
535
- * const doc = univerAPI.getActiveDocument();
536
- * if (!doc) throw new Error('No active document');
537
- *
538
- * const paragraph = doc.getBody().getChild(0).asParagraph();
539
- * if (paragraph.isTask()) {
540
- * paragraph.setTaskChecked(true);
541
- * }
138
+ * const fDocument = univerAPI.getActiveDocument();
139
+ * const paragraph = fDocument.getParagraphs()[0];
140
+ * console.log(paragraph?.getSegmentId());
542
141
  * ```
543
142
  */
544
- isTask() {
545
- return this._body.isTaskParagraph(this._key);
546
- }
547
- /**
548
- * Set the checked state of this task/checklist paragraph.
549
- * @param {boolean} checked Whether the task item should be checked.
550
- * @returns {boolean} `true` if the task state was updated, or `false` if this paragraph is not a task item.
551
- * @example
552
- * ```ts
553
- * const doc = univerAPI.getActiveDocument();
554
- * if (!doc) throw new Error('No active document');
555
- *
556
- * const paragraph = doc.getBody().getChild(0).asParagraph();
557
- * if (paragraph.isTask()) {
558
- * paragraph.setTaskChecked(false);
559
- * }
560
- * ```
561
- */
562
- setTaskChecked(checked) {
563
- return this._body.setTaskChecked(this._key, checked);
564
- }
565
- };
566
-
567
- //#endregion
568
- //#region src/facade/f-doc-table.ts
569
- /**
570
- * A facade wrapper for a document table marker.
571
- *
572
- * Table identity is backed by the persisted `ICustomTable.tableId`, so the
573
- * wrapper can be re-resolved after text is inserted before the table.
574
- *
575
- * @hideconstructor
576
- */
577
- var FDocTable = class {
578
- constructor(_body, _key) {
579
- this._body = _body;
580
- this._key = _key;
581
- }
582
- /**
583
- * Get the document element type.
584
- * @returns {'table'} The literal table element type.
585
- * @example
586
- * ```ts
587
- * const doc = univerAPI.getActiveDocument();
588
- * if (!doc) throw new Error('No active document');
589
- *
590
- * const table = doc.getBody().getChild(0).asTable();
591
- * console.log(table.getType());
592
- * ```
593
- */
594
- getType() {
595
- return "table";
596
- }
597
- /**
598
- * Get the table key.
599
- * @returns {string} The persisted `tableId` for this table.
600
- * @example
601
- * ```ts
602
- * const doc = univerAPI.getActiveDocument();
603
- * if (!doc) throw new Error('No active document');
604
- *
605
- * const table = doc.getBody().getChild(0).asTable();
606
- * console.log(table.getKey());
607
- * ```
608
- */
609
- getKey() {
610
- return this._key;
611
- }
612
- /**
613
- * Get the parent body facade that owns this table.
614
- * @returns {FDocBody} The document body facade.
615
- * @example
616
- * ```ts
617
- * const doc = univerAPI.getActiveDocument();
618
- * if (!doc) throw new Error('No active document');
619
- *
620
- * const table = doc.getBody().getChild(0).asTable();
621
- * console.log(table.getParent().getChildIndex(table));
622
- * ```
623
- */
624
- getParent() {
625
- return this._body;
626
- }
627
- /**
628
- * Remove this table from the parent body.
629
- * @returns {boolean} `true` if the table range was removed.
630
- * @example
631
- * ```ts
632
- * const doc = univerAPI.getActiveDocument();
633
- * if (!doc) throw new Error('No active document');
634
- *
635
- * const table = doc.getBody().getChild(0).asTable();
636
- * table.removeFromParent();
637
- * ```
638
- */
639
- removeFromParent() {
640
- return this._body.removeTable(this._key);
641
- }
642
- /**
643
- * Get the persisted table id.
644
- * @returns {string} The `ICustomTable.tableId` value.
645
- * @example
646
- * ```ts
647
- * const doc = univerAPI.getActiveDocument();
648
- * if (!doc) throw new Error('No active document');
649
- *
650
- * const table = doc.getBody().getChild(0).asTable();
651
- * console.log(table.getTableId());
652
- * ```
653
- */
654
- getTableId() {
655
- return this._body.getTable(this._key).tableId;
656
- }
657
- };
658
-
659
- //#endregion
660
- //#region src/facade/f-doc-element.ts
661
- /**
662
- * A generic top-level document body element.
663
- *
664
- * Use this wrapper when you need to inspect an element type first, navigate to
665
- * neighboring elements, or cast the element to a more specific facade wrapper.
666
- *
667
- * Paragraph keys are persisted `paragraphId` values. Tables, block ranges, and
668
- * custom blocks use their persisted ids.
669
- *
670
- * @hideconstructor
671
- */
672
- var FDocElement = class {
673
- constructor(_body, _type, _key) {
674
- this._body = _body;
675
- this._type = _type;
676
- this._key = _key;
677
- }
678
- /**
679
- * Get the document element type.
680
- * @returns {FDocElementType} The element type, such as `paragraph`, `table`, `blockRange`, or `customBlock`.
681
- * @example
682
- * ```ts
683
- * const doc = univerAPI.getActiveDocument();
684
- * if (!doc) throw new Error('No active document');
685
- *
686
- * const element = doc.getBody().getChild(0);
687
- * console.log(element.getType());
688
- * ```
689
- */
690
- getType() {
691
- return this._type;
692
- }
693
- /**
694
- * Get the facade key used to resolve this element.
695
- * @returns {string} The paragraph `paragraphId` or persisted table/block/custom block id.
696
- * @example
697
- * ```ts
698
- * const doc = univerAPI.getActiveDocument();
699
- * if (!doc) throw new Error('No active document');
700
- *
701
- * const body = doc.getBody();
702
- * const element = body.getChild(0);
703
- * console.log(element.getKey());
704
- * ```
705
- */
706
- getKey() {
707
- return this._key;
708
- }
709
- /**
710
- * Get the parent body facade that owns this element.
711
- * @returns {FDocBody} The document body facade.
712
- * @example
713
- * ```ts
714
- * const doc = univerAPI.getActiveDocument();
715
- * if (!doc) throw new Error('No active document');
716
- *
717
- * const element = doc.getBody().getChild(0);
718
- * console.log(element.getParent().getNumChildren());
719
- * ```
720
- */
721
- getParent() {
722
- return this._body;
723
- }
724
- /**
725
- * Get the next sibling element in the current body order.
726
- * @returns {FDocElement | null} The next sibling wrapper, or `null` when this is the last child.
727
- * @example
728
- * ```ts
729
- * const doc = univerAPI.getActiveDocument();
730
- * if (!doc) throw new Error('No active document');
731
- *
732
- * const first = doc.getBody().getChild(0);
733
- * const next = first.getNextSibling();
734
- * console.log(next?.getType());
735
- * ```
736
- */
737
- getNextSibling() {
738
- return this._body.createSibling(this._type, this._key, 1);
739
- }
740
- /**
741
- * Get the previous sibling element in the current body order.
742
- * @returns {FDocElement | null} The previous sibling wrapper, or `null` when this is the first child.
743
- * @example
744
- * ```ts
745
- * const doc = univerAPI.getActiveDocument();
746
- * if (!doc) throw new Error('No active document');
747
- *
748
- * const second = doc.getBody().getChild(1);
749
- * const previous = second.getPreviousSibling();
750
- * console.log(previous?.getType());
751
- * ```
752
- */
753
- getPreviousSibling() {
754
- return this._body.createSibling(this._type, this._key, -1);
755
- }
756
- /**
757
- * Remove this element from its parent body.
758
- * @returns {boolean} `true` if the element content was removed.
759
- * @example
760
- * ```ts
761
- * const doc = univerAPI.getActiveDocument();
762
- * if (!doc) throw new Error('No active document');
763
- *
764
- * const body = doc.getBody();
765
- * const element = body.getChild(0);
766
- * element.removeFromParent();
767
- * ```
768
- */
769
- removeFromParent() {
770
- if (this._type === "paragraph") return this._body.removeParagraph(this._key);
771
- if (this._type === "blockRange") return this._body.removeBlockRange(this._key);
772
- if (this._type === "table") return this._body.removeTable(this._key);
773
- return this._body.removeCustomBlock(this._key);
774
- }
775
- /**
776
- * Cast this generic element to a paragraph facade.
777
- * @returns {FDocParagraph} The paragraph facade wrapper.
778
- * @throws {TypeError} If the element is not a paragraph.
779
- * @example
780
- * ```ts
781
- * const doc = univerAPI.getActiveDocument();
782
- * if (!doc) throw new Error('No active document');
783
- *
784
- * const paragraph = doc.getBody().getChild(0).asParagraph();
785
- * console.log(paragraph.getText());
786
- * ```
787
- */
788
- asParagraph() {
789
- this._assertType("paragraph");
790
- return new FDocParagraph(this._body, this._key);
791
- }
792
- /**
793
- * Cast this generic element to a table facade.
794
- * @returns {FDocTable} The table facade wrapper.
795
- * @throws {TypeError} If the element is not a table.
796
- * @example
797
- * ```ts
798
- * const doc = univerAPI.getActiveDocument();
799
- * if (!doc) throw new Error('No active document');
800
- *
801
- * const table = doc.getBody().getChild(0).asTable();
802
- * console.log(table.getTableId());
803
- * ```
804
- */
805
- asTable() {
806
- this._assertType("table");
807
- return new FDocTable(this._body, this._key);
808
- }
809
- /**
810
- * Cast this generic element to a callout, quote, or code block range facade.
811
- * @returns {FDocBlockRange} The block range facade wrapper.
812
- * @throws {TypeError} If the element is not a block range.
813
- * @example
814
- * ```ts
815
- * const doc = univerAPI.getActiveDocument();
816
- * if (!doc) throw new Error('No active document');
817
- *
818
- * const blockRange = doc.getBody().getChild(0).asBlockRange();
819
- * console.log(blockRange.getBlockType());
820
- * ```
821
- */
822
- asBlockRange() {
823
- this._assertType("blockRange");
824
- return new FDocBlockRange(this._body, this._key);
825
- }
826
- /**
827
- * Cast this generic element to a custom block facade.
828
- * @returns {FDocCustomBlock} The custom block facade wrapper.
829
- * @throws {TypeError} If the element is not a custom block.
830
- * @example
831
- * ```ts
832
- * const doc = univerAPI.getActiveDocument();
833
- * if (!doc) throw new Error('No active document');
834
- *
835
- * const customBlock = doc.getBody().getChild(0).asCustomBlock();
836
- * console.log(customBlock.getBlockId());
837
- * ```
838
- */
839
- asCustomBlock() {
840
- this._assertType("customBlock");
841
- return new FDocCustomBlock(this._body, this._key);
842
- }
843
- _assertType(type) {
844
- if (this._type !== type) throw new TypeError(`Cannot cast ${this._type} to ${type}.`);
845
- }
846
- };
847
-
848
- //#endregion
849
- //#region src/facade/f-doc-body.ts
850
- function isRichTextLike(value) {
851
- return typeof value === "object" && value !== null && "getData" in value && typeof value.getData === "function";
852
- }
853
- const FACADE_TRIGGER = "doc-facade";
854
- const RESTORE_INSERTED_PARAGRAPH_IDS = "__textXRestoreParagraphIds";
855
- /**
856
- * A Facade API object bounded to a document body or header/footer segment.
857
- * It provides Google Docs-like element access and range editing methods.
858
- *
859
- * Paragraph elements use their persisted `paragraphId`. Tables, block ranges, and
860
- * custom blocks use their persisted ids.
861
- *
862
- * @hideconstructor
863
- */
864
- var FDocBody = class {
865
- constructor(_documentDataModel, _commandService, _registry, _segmentId = "") {
866
- this._documentDataModel = _documentDataModel;
867
- this._commandService = _commandService;
868
- this._segmentId = _segmentId;
869
- }
870
- /**
871
- * Get the number of top-level child elements in the body.
872
- * @returns {number} The number of child elements.
873
- * @example
874
- * ```ts
875
- * const doc = univerAPI.getActiveDocument();
876
- * if (!doc) throw new Error('No active document');
877
- *
878
- * const body = doc.getBody();
879
- * console.log(body.getNumChildren());
880
- * ```
881
- */
882
- getNumChildren() {
883
- return this._getChildren().length;
884
- }
885
- /**
886
- * Get a top-level child element by child index.
887
- * @param {number} index The zero-based child index.
888
- * @returns {FDocElement} The child element wrapper.
889
- * @example
890
- * ```ts
891
- * const doc = univerAPI.getActiveDocument();
892
- * if (!doc) throw new Error('No active document');
893
- *
894
- * const body = doc.getBody();
895
- * const firstChild = body.getChild(0);
896
- * console.log(firstChild.getType());
897
- * ```
898
- */
899
- getChild(index) {
900
- const child = this._getChildren()[index];
901
- if (!child) throw new RangeError(`Child index ${index} is out of range.`);
902
- return this._createElement(child.type, child.key);
903
- }
904
- /**
905
- * Get the current child index of an element handle.
906
- * The index is resolved from the element key, so a paragraph handle keeps pointing
907
- * to the same paragraph after facade edits insert content before it.
908
- * @param {IFDocElementHandle} element The element handle to locate.
909
- * @returns {number} The current zero-based child index.
910
- * @example
911
- * ```ts
912
- * const doc = univerAPI.getActiveDocument();
913
- * if (!doc) throw new Error('No active document');
914
- *
915
- * const body = doc.getBody();
916
- * const paragraph = body.getChild(1).asParagraph();
917
- * body.insertParagraph(0, 'Intro');
918
- * console.log(body.getChildIndex(paragraph));
919
- * ```
920
- */
921
- getChildIndex(element) {
922
- const resolved = this.resolveElement(element.getType(), element.getKey());
923
- const index = this._getChildren().findIndex((child) => child.type === resolved.type && child.key === resolved.key);
924
- if (index < 0) throw new Error("Doc element is stale");
925
- return index;
926
- }
927
- /**
928
- * Insert plain text at a document body offset.
929
- * @param {number} index The zero-based insertion offset.
930
- * @param {string} text The plain text to insert.
931
- * @returns {boolean} `true` if the edit was applied.
932
- * @example
933
- * ```ts
934
- * const doc = univerAPI.getActiveDocument();
935
- * if (!doc) throw new Error('No active document');
936
- *
937
- * const body = doc.getBody();
938
- * body.insertText(0, 'Hello ');
939
- * ```
940
- */
941
- insertText(index, text) {
942
- return this._replaceBodyRange({
943
- startOffset: index,
944
- endOffset: index
945
- }, buildPlainTextInsertBody(text));
946
- }
947
- /**
948
- * Insert a plain-text paragraph before the paragraph at the given paragraph index.
949
- * @param {number} index The zero-based paragraph insertion index.
950
- * @param {string} text The paragraph text. Defaults to an empty paragraph.
951
- * @returns {FDocParagraph} The inserted paragraph wrapper.
952
- * @example
953
- * ```ts
954
- * const doc = univerAPI.getActiveDocument();
955
- * if (!doc) throw new Error('No active document');
956
- *
957
- * const body = doc.getBody();
958
- * const paragraph = body.insertParagraph(0, 'Document title');
959
- * paragraph.appendText(' suffix');
960
- * ```
961
- */
962
- insertParagraph(index, text = "") {
963
- var _this$_getBody$paragr;
964
- const offset = this._getParagraphInsertOffset(index);
965
- if (!this._replaceBodyRange({
966
- startOffset: offset,
967
- endOffset: offset
968
- }, buildPlainTextInsertBody(`${text}\r`))) throw new Error("Failed to insert paragraph.");
969
- const paragraphIndex = this._normalizeInsertedParagraphIndex(index);
970
- const paragraph = (_this$_getBody$paragr = this._getBody().paragraphs) === null || _this$_getBody$paragr === void 0 ? void 0 : _this$_getBody$paragr[paragraphIndex];
971
- return new FDocParagraph(this, this._getParagraphId(paragraph, paragraphIndex));
972
- }
973
- /**
974
- * Append a plain-text paragraph at the end of the body.
975
- * @param {string} text The paragraph text. Defaults to an empty paragraph.
976
- * @returns {FDocParagraph} The appended paragraph wrapper.
977
- * @example
978
- * ```ts
979
- * const doc = univerAPI.getActiveDocument();
980
- * if (!doc) throw new Error('No active document');
981
- *
982
- * const body = doc.getBody();
983
- * const paragraph = body.appendParagraph('Summary');
984
- * console.log(paragraph.getText());
985
- * ```
986
- */
987
- appendParagraph(text = "") {
988
- var _this$_getBody$paragr2, _this$_getBody$paragr3;
989
- return this.insertParagraph((_this$_getBody$paragr2 = (_this$_getBody$paragr3 = this._getBody().paragraphs) === null || _this$_getBody$paragr3 === void 0 ? void 0 : _this$_getBody$paragr3.length) !== null && _this$_getBody$paragr2 !== void 0 ? _this$_getBody$paragr2 : 0, text);
990
- }
991
- /**
992
- * Delete a range from the body.
993
- * @param {IFDocTextRange} range The text range to delete.
994
- * @returns {boolean} `true` if the range was deleted.
995
- * @example
996
- * ```ts
997
- * const doc = univerAPI.getActiveDocument();
998
- * if (!doc) throw new Error('No active document');
999
- *
1000
- * const body = doc.getBody();
1001
- * body.deleteRange({ startOffset: 0, endOffset: 5 });
1002
- * ```
1003
- */
1004
- deleteRange(range) {
1005
- return this._replaceBodyRange(range, { dataStream: "" });
1006
- }
1007
- /**
1008
- * Replace a range with plain text or rich text body data.
1009
- * @param {IFDocTextRange} range The text range to replace.
1010
- * @param {string | IFDocRichTextLike | { body?: IDocumentBody }} value The replacement text or rich-text-like value.
1011
- * @returns {boolean} `true` if the replacement was applied.
1012
- * @example
1013
- * ```ts
1014
- * const doc = univerAPI.getActiveDocument();
1015
- * if (!doc) throw new Error('No active document');
1016
- *
1017
- * const body = doc.getBody();
1018
- * body.replaceRange({ startOffset: 0, endOffset: 5 }, 'Hello');
1019
- * ```
1020
- */
1021
- replaceRange(range, value) {
1022
- let body;
1023
- if (typeof value === "string") body = buildPlainTextInsertBody(value);
1024
- else if (isRichTextLike(value)) {
1025
- var _value$getData$body;
1026
- body = (_value$getData$body = value.getData().body) !== null && _value$getData$body !== void 0 ? _value$getData$body : { dataStream: "" };
1027
- } else {
1028
- var _value$body;
1029
- body = (_value$body = value.body) !== null && _value$body !== void 0 ? _value$body : { dataStream: "" };
1030
- }
1031
- return this._replaceBodyRange(range, body);
1032
- }
1033
- /**
1034
- * Apply text style to a body range.
1035
- * @param {IFDocTextRange} range The range to style.
1036
- * @param {ITextStyle} style The Univer text style patch.
1037
- * @returns {boolean} `true` if the style was applied.
1038
- * @example
1039
- * ```ts
1040
- * const doc = univerAPI.getActiveDocument();
1041
- * if (!doc) throw new Error('No active document');
1042
- *
1043
- * const body = doc.getBody();
1044
- * body.setTextStyle({ startOffset: 0, endOffset: 5 }, { bl: 1 });
1045
- * ```
1046
- */
1047
- setTextStyle(range, style) {
1048
- const updateBody = {
1049
- dataStream: "",
1050
- textRuns: [{
1051
- st: 0,
1052
- ed: range.endOffset - range.startOffset,
1053
- ts: style
1054
- }]
1055
- };
1056
- return this._retainBodyRange(range, updateBody, UpdateDocsAttributeType.COVER);
1057
- }
1058
- /**
1059
- * Apply paragraph style to a paragraph handle or text range.
1060
- * @param {FDocElement | FDocParagraph | IFDocTextRange} paragraph The paragraph handle or a range inside the paragraph.
1061
- * @param {IParagraphStyle} style The Univer paragraph style patch.
1062
- * @returns {boolean} `true` if the style was applied.
1063
- * @example
1064
- * ```ts
1065
- * const doc = univerAPI.getActiveDocument();
1066
- * if (!doc) throw new Error('No active document');
1067
- *
1068
- * const body = doc.getBody();
1069
- * const paragraph = body.getChild(0).asParagraph();
1070
- * body.setParagraphStyle(paragraph, { horizontalAlign: 2 });
1071
- * ```
1072
- */
1073
- setParagraphStyle(paragraph, style) {
1074
- const resolved = paragraph instanceof FDocElement || paragraph instanceof FDocParagraph ? this.resolveParagraph(paragraph.getKey()) : this._findParagraphByRange(paragraph);
1075
- const updateBody = {
1076
- dataStream: "",
1077
- paragraphs: [{
1078
- ...resolved.paragraph,
1079
- startIndex: 0,
1080
- paragraphStyle: {
1081
- ...resolved.paragraph.paragraphStyle,
1082
- ...style
1083
- }
1084
- }]
1085
- };
1086
- this._preserveExplicitParagraphIds(updateBody);
1087
- return this._retainBodyRange({
1088
- startOffset: resolved.endOffset,
1089
- endOffset: resolved.endOffset + 1
1090
- }, updateBody, UpdateDocsAttributeType.REPLACE);
1091
- }
1092
- /**
1093
- * Get the text content of a paragraph by paragraph id.
1094
- * @param {string} key The paragraph id.
1095
- * @returns {string} The paragraph text without the trailing paragraph break.
1096
- * @example
1097
- * ```ts
1098
- * const doc = univerAPI.getActiveDocument();
1099
- * if (!doc) throw new Error('No active document');
1100
- *
1101
- * const paragraph = doc.getBody().getChild(0).asParagraph();
1102
- * console.log(doc.getBody().getParagraphText(paragraph.getKey()));
1103
- * ```
1104
- */
1105
- getParagraphText(key) {
1106
- const resolved = this.resolveParagraph(key);
1107
- return this._getBody().dataStream.slice(resolved.startOffset, resolved.endOffset);
1108
- }
1109
- /**
1110
- * Replace the text content of a paragraph by paragraph id.
1111
- * @param {string} key The paragraph id.
1112
- * @param {string} text The replacement paragraph text.
1113
- * @returns {boolean} `true` if the paragraph text was replaced.
1114
- * @example
1115
- * ```ts
1116
- * const doc = univerAPI.getActiveDocument();
1117
- * if (!doc) throw new Error('No active document');
1118
- *
1119
- * const paragraph = doc.getBody().getChild(0).asParagraph();
1120
- * doc.getBody().setParagraphText(paragraph.getKey(), 'Updated text');
1121
- * ```
1122
- */
1123
- setParagraphText(key, text) {
1124
- const resolved = this.resolveParagraph(key);
1125
- return this.replaceRange({
1126
- startOffset: resolved.startOffset,
1127
- endOffset: resolved.endOffset
1128
- }, text);
1129
- }
1130
- /**
1131
- * Append text to a paragraph by paragraph id.
1132
- * @param {string} key The paragraph id.
1133
- * @param {string} text The text to append before the paragraph break.
1134
- * @returns {boolean} `true` if the text was appended.
1135
- * @example
1136
- * ```ts
1137
- * const doc = univerAPI.getActiveDocument();
1138
- * if (!doc) throw new Error('No active document');
1139
- *
1140
- * const paragraph = doc.getBody().getChild(0).asParagraph();
1141
- * doc.getBody().appendParagraphText(paragraph.getKey(), ' suffix');
1142
- * ```
1143
- */
1144
- appendParagraphText(key, text) {
1145
- const resolved = this.resolveParagraph(key);
1146
- return this.insertText(resolved.endOffset, text);
1147
- }
1148
- /**
1149
- * Remove a paragraph by paragraph id.
1150
- * @param {string} key The paragraph id.
1151
- * @returns {boolean} `true` if the paragraph was removed.
1152
- * @example
1153
- * ```ts
1154
- * const doc = univerAPI.getActiveDocument();
1155
- * if (!doc) throw new Error('No active document');
1156
- *
1157
- * const paragraph = doc.getBody().getChild(0).asParagraph();
1158
- * doc.getBody().removeParagraph(paragraph.getKey());
1159
- * ```
1160
- */
1161
- removeParagraph(key) {
1162
- const resolved = this.resolveParagraph(key);
1163
- return this.deleteRange({
1164
- startOffset: resolved.startOffset,
1165
- endOffset: resolved.endOffset + 1
1166
- });
1167
- }
1168
- /**
1169
- * Get a paragraph text range by paragraph id.
1170
- * @param {string} key The paragraph id.
1171
- * @returns {IFDocTextRange} The paragraph range excluding the trailing paragraph break.
1172
- * @example
1173
- * ```ts
1174
- * const doc = univerAPI.getActiveDocument();
1175
- * if (!doc) throw new Error('No active document');
1176
- *
1177
- * const paragraph = doc.getBody().getChild(0).asParagraph();
1178
- * const range = doc.getBody().getParagraphRange(paragraph.getKey());
1179
- * console.log(range.startOffset, range.endOffset);
1180
- * ```
1181
- */
1182
- getParagraphRange(key) {
1183
- const resolved = this.resolveParagraph(key);
1184
- return {
1185
- startOffset: resolved.startOffset,
1186
- endOffset: resolved.endOffset,
1187
- segmentId: this._segmentId
1188
- };
1189
- }
1190
- /**
1191
- * Check whether a paragraph has list metadata.
1192
- * @param {string} key The paragraph id.
1193
- * @returns {boolean} `true` if the paragraph is a list item.
1194
- * @example
1195
- * ```ts
1196
- * const doc = univerAPI.getActiveDocument();
1197
- * if (!doc) throw new Error('No active document');
1198
- *
1199
- * const paragraph = doc.getBody().getChild(0).asParagraph();
1200
- * console.log(doc.getBody().isListParagraph(paragraph.getKey()));
1201
- * ```
1202
- */
1203
- isListParagraph(key) {
1204
- return Boolean(this.resolveParagraph(key).paragraph.bullet);
1205
- }
1206
- /**
1207
- * Check whether a paragraph is a task/checklist item.
1208
- * @param {string} key The paragraph id.
1209
- * @returns {boolean} `true` if the paragraph is an unchecked or checked task item.
1210
- * @example
1211
- * ```ts
1212
- * const doc = univerAPI.getActiveDocument();
1213
- * if (!doc) throw new Error('No active document');
1214
- *
1215
- * const paragraph = doc.getBody().getChild(0).asParagraph();
1216
- * console.log(doc.getBody().isTaskParagraph(paragraph.getKey()));
1217
- * ```
1218
- */
1219
- isTaskParagraph(key) {
1220
- var _this$resolveParagrap;
1221
- const listType = (_this$resolveParagrap = this.resolveParagraph(key).paragraph.bullet) === null || _this$resolveParagrap === void 0 ? void 0 : _this$resolveParagrap.listType;
1222
- return listType === PresetListType.CHECK_LIST || listType === PresetListType.CHECK_LIST_CHECKED;
1223
- }
1224
- /**
1225
- * Set the checked state of a task/checklist paragraph.
1226
- * @param {string} key The paragraph id.
1227
- * @param {boolean} checked Whether the task should be checked.
1228
- * @returns {boolean} `true` if the task state was updated, or `false` if the paragraph is not a task item.
1229
- * @example
1230
- * ```ts
1231
- * const doc = univerAPI.getActiveDocument();
1232
- * if (!doc) throw new Error('No active document');
1233
- *
1234
- * const paragraph = doc.getBody().getChild(0).asParagraph();
1235
- * doc.getBody().setTaskChecked(paragraph.getKey(), true);
1236
- * ```
1237
- */
1238
- setTaskChecked(key, checked) {
1239
- const resolved = this.resolveParagraph(key);
1240
- const bullet = resolved.paragraph.bullet;
1241
- if (!bullet || !this.isTaskParagraph(key)) return false;
1242
- const updateBody = {
1243
- dataStream: "",
1244
- paragraphs: [{
1245
- ...resolved.paragraph,
1246
- startIndex: 0,
1247
- bullet: {
1248
- ...bullet,
1249
- listType: checked ? PresetListType.CHECK_LIST_CHECKED : PresetListType.CHECK_LIST
1250
- }
1251
- }]
1252
- };
1253
- this._preserveExplicitParagraphIds(updateBody);
1254
- return this._retainBodyRange({
1255
- startOffset: resolved.endOffset,
1256
- endOffset: resolved.endOffset + 1
1257
- }, updateBody, UpdateDocsAttributeType.REPLACE);
143
+ getSegmentId() {
144
+ return this._segmentId;
1258
145
  }
1259
146
  /**
1260
- * Resolve a paragraph id to its current paragraph metadata.
1261
- * @param {string} key The paragraph id.
1262
- * @returns {IFDocResolvedParagraph} The current paragraph metadata.
147
+ * Get this paragraph's metadata.
148
+ * @returns {IFDocumentParagraphInfo} The paragraph info.
1263
149
  * @example
1264
150
  * ```ts
1265
- * const doc = univerAPI.getActiveDocument();
1266
- * if (!doc) throw new Error('No active document');
1267
- *
1268
- * const paragraph = doc.getBody().getChild(0).asParagraph();
1269
- * const resolved = doc.getBody().resolveParagraph(paragraph.getKey());
1270
- * console.log(resolved.paragraphIndex);
151
+ * const fDocument = univerAPI.getActiveDocument();
152
+ * const paragraph = fDocument.getParagraphs()[0];
153
+ * console.log(paragraph?.getInfo());
1271
154
  * ```
1272
155
  */
1273
- resolveParagraph(key) {
1274
- var _body$paragraphs;
1275
- const body = this._getBody();
1276
- const matches = ((_body$paragraphs = body.paragraphs) !== null && _body$paragraphs !== void 0 ? _body$paragraphs : []).map((paragraph, paragraphIndex) => ({
156
+ getInfo() {
157
+ const { paragraphs = [] } = this._document.getBody(this._segmentId);
158
+ const matches = paragraphs.map((paragraph, paragraphIndex) => ({
1277
159
  paragraph,
1278
160
  paragraphIndex
1279
- })).filter(({ paragraph }) => paragraph.paragraphId === key);
1280
- if (matches.length !== 1) throw new DocElementStaleError(matches.length > 1 ? `Doc paragraph id "${key}" is duplicated.` : `Doc paragraph id "${key}" is stale.`);
161
+ })).filter(({ paragraph }) => paragraph.paragraphId === this._paragraphId);
162
+ if (matches.length === 0) throw new Error(`Document paragraph with id ${this._paragraphId} not found`);
163
+ if (matches.length > 1) throw new Error(`Multiple document paragraphs with id ${this._paragraphId} found`);
1281
164
  const { paragraph, paragraphIndex } = matches[0];
1282
165
  return {
1283
166
  paragraph,
1284
167
  paragraphIndex,
1285
- startOffset: paragraphIndex > 0 ? body.paragraphs[paragraphIndex - 1].startIndex + 1 : 0,
168
+ startOffset: paragraphIndex > 0 ? paragraphs[paragraphIndex - 1].startIndex + 1 : 0,
1286
169
  endOffset: paragraph.startIndex
1287
170
  };
1288
171
  }
1289
172
  /**
1290
- * Resolve an element key to its current child metadata.
1291
- * @param {FDocElementType} type The element type.
1292
- * @param {string} key The persisted element key.
1293
- * @returns {object} The current child metadata used by the facade.
173
+ * Get the current text range occupied by this paragraph.
174
+ * @returns {IFDocumentTextRange} The paragraph text range, excluding the trailing paragraph break.
1294
175
  * @example
1295
176
  * ```ts
1296
- * const doc = univerAPI.getActiveDocument();
1297
- * if (!doc) throw new Error('No active document');
1298
- *
1299
- * const element = doc.getBody().getChild(0);
1300
- * const resolved = doc.getBody().resolveElement(element.getType(), element.getKey());
1301
- * console.log(resolved.position);
177
+ * const fDocument = univerAPI.getActiveDocument();
178
+ * const paragraph = fDocument.getParagraphs()[0];
179
+ * console.log(paragraph?.getRange());
1302
180
  * ```
1303
181
  */
1304
- resolveElement(type, key) {
1305
- if (type === "paragraph") return {
1306
- type,
1307
- key,
1308
- position: this.resolveParagraph(key).startOffset,
1309
- priority: 3
182
+ getRange() {
183
+ const { startOffset, endOffset } = this.getInfo();
184
+ return {
185
+ startOffset,
186
+ endOffset,
187
+ segmentId: this._segmentId
1310
188
  };
1311
- const child = this._getChildren().find((item) => item.type === type && item.key === key);
1312
- if (!child) throw new Error("Doc element is stale");
1313
- return child;
1314
189
  }
1315
190
  /**
1316
- * Get a callout, quote, or code block range by block id.
1317
- * @param {string} key The persisted block range id.
1318
- * @returns {IDocumentBlockRange} The matching block range snapshot.
191
+ * Get this paragraph's plain text.
192
+ * @returns {string} The paragraph text without the trailing paragraph break.
1319
193
  * @example
1320
194
  * ```ts
1321
- * const doc = univerAPI.getActiveDocument();
1322
- * if (!doc) throw new Error('No active document');
1323
- *
1324
- * const block = doc.getBody().getChild(0).asBlockRange();
1325
- * console.log(doc.getBody().getBlockRange(block.getKey()).blockType);
195
+ * const fDocument = univerAPI.getActiveDocument();
196
+ * const paragraph = fDocument.getParagraphs()[0];
197
+ * console.log(paragraph?.getText());
1326
198
  * ```
1327
199
  */
1328
- getBlockRange(key) {
1329
- var _this$_getBody$blockR;
1330
- const blockRange = (_this$_getBody$blockR = this._getBody().blockRanges) === null || _this$_getBody$blockR === void 0 ? void 0 : _this$_getBody$blockR.find((item) => item.blockId === key);
1331
- if (!blockRange) throw new Error("Doc element is stale");
1332
- return blockRange;
200
+ getText() {
201
+ const { dataStream } = this._document.getBody(this._segmentId);
202
+ const { startOffset, endOffset } = this.getInfo();
203
+ return dataStream.slice(startOffset, endOffset);
1333
204
  }
1334
205
  /**
1335
- * Get the text inside a callout, quote, or code block range.
1336
- * @param {string} key The persisted block range id.
1337
- * @returns {string} The block range text.
206
+ * Replace this paragraph's plain text.
207
+ * @param {string} text The replacement text. Do not include the paragraph break.
208
+ * @returns {boolean} `true` if the paragraph text was replaced.
1338
209
  * @example
1339
210
  * ```ts
1340
- * const doc = univerAPI.getActiveDocument();
1341
- * if (!doc) throw new Error('No active document');
1342
- *
1343
- * const block = doc.getBody().getChild(0).asBlockRange();
1344
- * console.log(doc.getBody().getBlockRangeText(block.getKey()));
211
+ * const fDocument = univerAPI.getActiveDocument();
212
+ * const paragraph = fDocument.getParagraphs()[0];
213
+ * paragraph?.setText('New text');
214
+ * console.log(paragraph?.getText());
1345
215
  * ```
1346
216
  */
1347
- getBlockRangeText(key) {
1348
- const blockRange = this.getBlockRange(key);
1349
- return this._getBody().dataStream.slice(blockRange.startIndex, blockRange.endIndex);
217
+ setText(text) {
218
+ const { startOffset, endOffset } = this.getInfo();
219
+ return replaceBodyRange({
220
+ startOffset,
221
+ endOffset,
222
+ segmentId: this._segmentId
223
+ }, buildPlainTextInsertBody(text), this._document.getDocumentDataModel(), this._injector);
1350
224
  }
1351
225
  /**
1352
- * Replace the text inside a callout, quote, or code block range.
1353
- * @param {string} key The persisted block range id.
1354
- * @param {string} text The replacement text.
1355
- * @returns {boolean} `true` if the text was replaced.
226
+ * Append plain text before this paragraph's trailing paragraph break.
227
+ * @param {string} text The plain text to append.
228
+ * @returns {boolean} `true` if the text was appended.
1356
229
  * @example
1357
230
  * ```ts
1358
- * const doc = univerAPI.getActiveDocument();
1359
- * if (!doc) throw new Error('No active document');
1360
- *
1361
- * const block = doc.getBody().getChild(0).asBlockRange();
1362
- * doc.getBody().setBlockRangeText(block.getKey(), 'Updated block');
231
+ * const fDocument = univerAPI.getActiveDocument();
232
+ * const paragraph = fDocument.getParagraphs()[0];
233
+ * paragraph?.appendText(' Appended text');
234
+ * console.log(paragraph?.getText());
1363
235
  * ```
1364
236
  */
1365
- setBlockRangeText(key, text) {
1366
- const blockRange = this.getBlockRange(key);
1367
- const body = buildPlainTextInsertBody(`${text}\r`);
1368
- body.blockRanges = [{
1369
- ...blockRange,
1370
- startIndex: 0,
1371
- endIndex: text.length
1372
- }];
1373
- return this.replaceRange({
1374
- startOffset: blockRange.startIndex,
1375
- endOffset: blockRange.endIndex + 1
1376
- }, { body });
237
+ appendText(text) {
238
+ const { endOffset } = this.getInfo();
239
+ return this._document.insertText(endOffset, text, this._segmentId);
1377
240
  }
1378
241
  /**
1379
- * Remove a callout, quote, or code block range and its content.
1380
- * @param {string} key The persisted block range id.
1381
- * @returns {boolean} `true` if the block range content was removed.
242
+ * Apply paragraph style to a paragraph handle or text range.
243
+ * @param {IParagraphStyle} style The Univer paragraph style patch.
244
+ * @returns {boolean} `true` if the style was applied.
1382
245
  * @example
1383
246
  * ```ts
1384
- * const doc = univerAPI.getActiveDocument();
1385
- * if (!doc) throw new Error('No active document');
1386
- *
1387
- * const block = doc.getBody().getChild(0).asBlockRange();
1388
- * doc.getBody().removeBlockRange(block.getKey());
247
+ * const fDocument = univerAPI.getActiveDocument();
248
+ * const paragraph = fDocument.getParagraphs()[0];
249
+ * paragraph?.setText('Styled text');
250
+ * paragraph?.setStyle({
251
+ * textStyle: {
252
+ * cl: {
253
+ * rgb: '#FF0000',
254
+ * },
255
+ * fs: 14,
256
+ * },
257
+ * horizontalAlign: 2,
258
+ * });
259
+ * console.log(paragraph?.getInfo().paragraph.paragraphStyle);
1389
260
  * ```
1390
261
  */
1391
- removeBlockRange(key) {
1392
- const blockRange = this.getBlockRange(key);
1393
- return this.deleteRange({
1394
- startOffset: blockRange.startIndex,
1395
- endOffset: blockRange.endIndex + 1
1396
- });
262
+ setStyle(style) {
263
+ const { paragraph, startOffset, endOffset } = this.getInfo();
264
+ let result = true;
265
+ if (style.textStyle && startOffset < endOffset) result = retainBodyRange({
266
+ startOffset,
267
+ endOffset,
268
+ segmentId: this._segmentId
269
+ }, {
270
+ dataStream: "",
271
+ textRuns: [{
272
+ st: 0,
273
+ ed: endOffset - startOffset,
274
+ ts: style.textStyle
275
+ }]
276
+ }, UpdateDocsAttributeType.COVER, this._document.getDocumentDataModel(), this._injector);
277
+ const updateBody = {
278
+ dataStream: "",
279
+ paragraphs: [{
280
+ ...paragraph,
281
+ startIndex: 0,
282
+ paragraphStyle: {
283
+ ...paragraph.paragraphStyle,
284
+ ...style
285
+ }
286
+ }]
287
+ };
288
+ this._preserveExplicitParagraphIds(updateBody);
289
+ return retainBodyRange({
290
+ startOffset: endOffset,
291
+ endOffset: endOffset + 1,
292
+ segmentId: this._segmentId
293
+ }, updateBody, UpdateDocsAttributeType.REPLACE, this._document.getDocumentDataModel(), this._injector) && result;
1397
294
  }
1398
295
  /**
1399
- * Get a table marker by table id.
1400
- * @param {string} key The persisted table id.
1401
- * @returns {ICustomTable} The matching table marker.
296
+ * Check whether this paragraph is a bullet, ordered, or checklist item.
297
+ * @returns {boolean} `true` if the paragraph has list metadata.
1402
298
  * @example
1403
299
  * ```ts
1404
- * const doc = univerAPI.getActiveDocument();
1405
- * if (!doc) throw new Error('No active document');
1406
- *
1407
- * const table = doc.getBody().getChild(0).asTable();
1408
- * console.log(doc.getBody().getTable(table.getTableId()));
300
+ * const fDocument = univerAPI.getActiveDocument();
301
+ * const paragraph = fDocument.getParagraphs()[0];
302
+ * console.log(paragraph?.isListItem());
1409
303
  * ```
1410
304
  */
1411
- getTable(key) {
1412
- var _this$_getBody$tables;
1413
- const table = (_this$_getBody$tables = this._getBody().tables) === null || _this$_getBody$tables === void 0 ? void 0 : _this$_getBody$tables.find((item) => item.tableId === key);
1414
- if (!table) throw new Error("Doc element is stale");
1415
- return table;
305
+ isListItem() {
306
+ const { paragraph } = this.getInfo();
307
+ return Boolean(paragraph.bullet);
1416
308
  }
1417
309
  /**
1418
- * Remove a table marker and its content range.
1419
- * @param {string} key The persisted table id.
1420
- * @returns {boolean} `true` if the table range was removed.
310
+ * Check whether this paragraph is a task/checklist item.
311
+ * @returns {boolean} `true` if this paragraph is an unchecked or checked task item.
1421
312
  * @example
1422
313
  * ```ts
1423
- * const doc = univerAPI.getActiveDocument();
1424
- * if (!doc) throw new Error('No active document');
1425
- *
1426
- * const table = doc.getBody().getChild(0).asTable();
1427
- * doc.getBody().removeTable(table.getTableId());
314
+ * const fDocument = univerAPI.getActiveDocument();
315
+ * const paragraph = fDocument.getParagraphs()[0];
316
+ * console.log(paragraph?.isTask());
1428
317
  * ```
1429
318
  */
1430
- removeTable(key) {
1431
- const table = this.getTable(key);
1432
- return this.deleteRange({
1433
- startOffset: table.startIndex,
1434
- endOffset: table.endIndex + 1
1435
- });
319
+ isTask() {
320
+ var _paragraph$bullet;
321
+ const { paragraph } = this.getInfo();
322
+ const listType = (_paragraph$bullet = paragraph.bullet) === null || _paragraph$bullet === void 0 ? void 0 : _paragraph$bullet.listType;
323
+ return listType === PresetListType.CHECK_LIST || listType === PresetListType.CHECK_LIST_CHECKED;
1436
324
  }
1437
325
  /**
1438
- * Get a custom block marker by block id.
1439
- * @param {string} key The persisted custom block id.
1440
- * @returns {ICustomBlock} The matching custom block marker.
326
+ * Set the checked state of this task/checklist paragraph.
327
+ * @param {boolean} checked Whether the task item should be checked.
328
+ * @returns {boolean} `true` if the task state was updated, or `false` if this paragraph is not a task item.
1441
329
  * @example
1442
330
  * ```ts
1443
- * const doc = univerAPI.getActiveDocument();
1444
- * if (!doc) throw new Error('No active document');
331
+ * const fDocument = univerAPI.getActiveDocument();
332
+ * const paragraph = fDocument.getParagraphs()[0];
1445
333
  *
1446
- * const customBlock = doc.getBody().getChild(0).asCustomBlock();
1447
- * console.log(doc.getBody().getCustomBlock(customBlock.getBlockId()));
334
+ * if (paragraph.isTask()) {
335
+ * const success = paragraph.setTaskChecked(true);
336
+ * console.log(success ? 'Task checked' : 'Failed to check task');
337
+ * }
1448
338
  * ```
1449
339
  */
1450
- getCustomBlock(key) {
1451
- var _this$_getBody$custom;
1452
- const block = (_this$_getBody$custom = this._getBody().customBlocks) === null || _this$_getBody$custom === void 0 ? void 0 : _this$_getBody$custom.find((item) => item.blockId === key);
1453
- if (!block) throw new Error("Doc element is stale");
1454
- return block;
340
+ setTaskChecked(checked) {
341
+ if (!this.isTask()) return false;
342
+ const { paragraph, endOffset } = this.getInfo();
343
+ const bullet = paragraph.bullet;
344
+ const updateBody = {
345
+ dataStream: "",
346
+ paragraphs: [{
347
+ ...paragraph,
348
+ startIndex: 0,
349
+ bullet: {
350
+ ...bullet,
351
+ listType: checked ? PresetListType.CHECK_LIST_CHECKED : PresetListType.CHECK_LIST
352
+ }
353
+ }]
354
+ };
355
+ this._preserveExplicitParagraphIds(updateBody);
356
+ return retainBodyRange({
357
+ startOffset: endOffset,
358
+ endOffset: endOffset + 1,
359
+ segmentId: this._segmentId
360
+ }, updateBody, UpdateDocsAttributeType.REPLACE, this._document.getDocumentDataModel(), this._injector);
1455
361
  }
1456
362
  /**
1457
- * Remove a custom block marker and its placeholder character.
1458
- * @param {string} key The persisted custom block id.
1459
- * @returns {boolean} `true` if the custom block placeholder was removed.
363
+ * Remove this paragraph.
364
+ * @returns {boolean} `true` if the paragraph was removed.
1460
365
  * @example
1461
366
  * ```ts
1462
- * const doc = univerAPI.getActiveDocument();
1463
- * if (!doc) throw new Error('No active document');
1464
- *
1465
- * const customBlock = doc.getBody().getChild(0).asCustomBlock();
1466
- * doc.getBody().removeCustomBlock(customBlock.getBlockId());
367
+ * const fDocument = univerAPI.getActiveDocument();
368
+ * const paragraph = fDocument.getParagraphs()[0];
369
+ * const success = paragraph?.remove();
370
+ * console.log(success ? 'Paragraph removed' : 'Failed to remove paragraph');
1467
371
  * ```
1468
372
  */
1469
- removeCustomBlock(key) {
1470
- const block = this.getCustomBlock(key);
1471
- return this.deleteRange({
1472
- startOffset: block.startIndex,
1473
- endOffset: block.startIndex + 1
373
+ remove() {
374
+ const { startOffset, endOffset } = this.getInfo();
375
+ return this._document.deleteRange({
376
+ startOffset,
377
+ endOffset: endOffset + 1,
378
+ segmentId: this._segmentId
1474
379
  });
1475
380
  }
1476
- /**
1477
- * Create a sibling element wrapper relative to the current element key.
1478
- * @param {FDocElementType} type The current element type.
1479
- * @param {string} key The current element key.
1480
- * @param {-1 | 1} direction `-1` for previous sibling, `1` for next sibling.
1481
- * @returns {FDocElement | null} The sibling wrapper, or `null` if none exists.
1482
- * @example
1483
- * ```ts
1484
- * const doc = univerAPI.getActiveDocument();
1485
- * if (!doc) throw new Error('No active document');
1486
- *
1487
- * const element = doc.getBody().getChild(0);
1488
- * const next = doc.getBody().createSibling(element.getType(), element.getKey(), 1);
1489
- * console.log(next?.getType());
1490
- * ```
1491
- */
1492
- createSibling(type, key, direction) {
1493
- const index = this.getChildIndex(this._createElement(type, key));
1494
- const child = this._getChildren()[index + direction];
1495
- return child ? this._createElement(child.type, child.key) : null;
1496
- }
1497
- _getChildren() {
1498
- var _body$paragraphs$leng, _body$paragraphs2, _body$blockRanges, _body$tables, _body$customBlocks;
1499
- const body = this._getBody();
1500
- const children = [];
1501
- for (let index = 0; index < ((_body$paragraphs$leng = (_body$paragraphs2 = body.paragraphs) === null || _body$paragraphs2 === void 0 ? void 0 : _body$paragraphs2.length) !== null && _body$paragraphs$leng !== void 0 ? _body$paragraphs$leng : 0); index++) {
1502
- const paragraph = body.paragraphs[index];
1503
- children.push({
1504
- type: "paragraph",
1505
- key: this._getParagraphId(paragraph, index),
1506
- position: index > 0 ? body.paragraphs[index - 1].startIndex + 1 : 0,
1507
- priority: 3
1508
- });
1509
- }
1510
- (_body$blockRanges = body.blockRanges) === null || _body$blockRanges === void 0 || _body$blockRanges.forEach((blockRange) => children.push({
1511
- type: "blockRange",
1512
- key: blockRange.blockId,
1513
- position: blockRange.startIndex,
1514
- priority: 0
1515
- }));
1516
- (_body$tables = body.tables) === null || _body$tables === void 0 || _body$tables.forEach((table) => children.push({
1517
- type: "table",
1518
- key: table.tableId,
1519
- position: table.startIndex,
1520
- priority: 1
1521
- }));
1522
- (_body$customBlocks = body.customBlocks) === null || _body$customBlocks === void 0 || _body$customBlocks.forEach((customBlock) => children.push({
1523
- type: "customBlock",
1524
- key: customBlock.blockId,
1525
- position: customBlock.startIndex,
1526
- priority: 2
1527
- }));
1528
- return children.sort((a, b) => a.position - b.position || a.priority - b.priority);
1529
- }
1530
- _createElement(type, key) {
1531
- return new FDocElement(this, type, key);
1532
- }
1533
- _getBody() {
1534
- const body = this._documentDataModel.getSelfOrHeaderFooterModel(this._segmentId).getBody();
1535
- if (!body) throw new Error("The document body is empty");
1536
- return body;
1537
- }
1538
- _getParagraphInsertOffset(index) {
1539
- var _body$paragraphs3;
1540
- const body = this._getBody();
1541
- if (index <= 0) return 0;
1542
- const paragraphs = (_body$paragraphs3 = body.paragraphs) !== null && _body$paragraphs3 !== void 0 ? _body$paragraphs3 : [];
1543
- if (paragraphs.length === 0) return Math.max(0, body.dataStream.length - 1);
1544
- if (index >= paragraphs.length) return paragraphs[paragraphs.length - 1].startIndex + 1;
1545
- return paragraphs[index - 1].startIndex + 1;
1546
- }
1547
- _normalizeInsertedParagraphIndex(index) {
1548
- var _this$_getBody$paragr4;
1549
- const paragraphs = (_this$_getBody$paragr4 = this._getBody().paragraphs) !== null && _this$_getBody$paragr4 !== void 0 ? _this$_getBody$paragr4 : [];
1550
- return Math.max(0, Math.min(index, paragraphs.length - 1));
1551
- }
1552
- _getParagraphId(paragraph, paragraphIndex) {
1553
- if (!paragraph) throw new RangeError(`Paragraph index ${paragraphIndex} is out of range.`);
1554
- if (!paragraph.paragraphId) throw new DocElementStaleError(`Paragraph at index ${paragraphIndex} is missing paragraphId.`);
1555
- return paragraph.paragraphId;
1556
- }
1557
381
  _preserveExplicitParagraphIds(body) {
1558
382
  body[RESTORE_INSERTED_PARAGRAPH_IDS] = true;
1559
383
  }
1560
- _findParagraphByRange(range) {
1561
- var _this$_getBody$paragr5;
1562
- const paragraphs = (_this$_getBody$paragr5 = this._getBody().paragraphs) !== null && _this$_getBody$paragr5 !== void 0 ? _this$_getBody$paragr5 : [];
1563
- const paragraphIndex = paragraphs.findIndex((paragraph, index) => {
1564
- return (index > 0 ? paragraphs[index - 1].startIndex + 1 : 0) <= range.startOffset && range.endOffset <= paragraph.startIndex;
1565
- });
1566
- if (paragraphIndex < 0) throw new RangeError("Range does not resolve to a paragraph.");
1567
- const paragraph = paragraphs[paragraphIndex];
1568
- return {
1569
- paragraph,
1570
- paragraphIndex,
1571
- startOffset: paragraphIndex > 0 ? paragraphs[paragraphIndex - 1].startIndex + 1 : 0,
1572
- endOffset: paragraph.startIndex
1573
- };
1574
- }
1575
- _replaceBodyRange(range, insertBody) {
1576
- const startOffset = range.startOffset;
1577
- const endOffset = range.endOffset;
1578
- const textX = new TextX();
1579
- if (startOffset > 0) textX.push({
1580
- t: TextXActionType.RETAIN,
1581
- len: startOffset
1582
- });
1583
- if (endOffset > startOffset) textX.push({
1584
- t: TextXActionType.DELETE,
1585
- len: endOffset - startOffset
1586
- });
1587
- if (insertBody.dataStream.length > 0) textX.push({
1588
- t: TextXActionType.INSERT,
1589
- body: insertBody,
1590
- len: insertBody.dataStream.length
1591
- });
1592
- return this._executeTextX(textX);
1593
- }
1594
- _retainBodyRange(range, body, coverType) {
1595
- var _body$textRuns;
1596
- if (((_body$textRuns = body.textRuns) === null || _body$textRuns === void 0 ? void 0 : _body$textRuns.length) && this._getBody().textRuns == null) this._ensureTextRuns();
1597
- const textX = new TextX();
1598
- if (range.startOffset > 0) textX.push({
1599
- t: TextXActionType.RETAIN,
1600
- len: range.startOffset
1601
- });
1602
- textX.push({
1603
- t: TextXActionType.RETAIN,
1604
- body,
1605
- coverType,
1606
- len: range.endOffset - range.startOffset
1607
- });
1608
- return this._executeTextX(textX);
1609
- }
1610
- _ensureTextRuns() {
1611
- if (this._getBody().textRuns != null) return;
1612
- const actions = JSONX.getInstance().replaceOp([...getRichTextEditPath(this._documentDataModel, this._segmentId), "textRuns"], void 0, []);
1613
- this._commandService.syncExecuteCommand(RichTextEditingMutation.id, {
1614
- unitId: this._documentDataModel.getUnitId(),
1615
- segmentId: this._segmentId,
1616
- actions,
1617
- textRanges: [],
1618
- trigger: FACADE_TRIGGER,
1619
- isEditing: false
1620
- });
1621
- }
1622
- _executeTextX(textX) {
1623
- const actions = JSONX.getInstance().editOp(textX.serialize(), getRichTextEditPath(this._documentDataModel, this._segmentId));
1624
- const result = this._commandService.syncExecuteCommand(RichTextEditingMutation.id, {
1625
- unitId: this._documentDataModel.getUnitId(),
1626
- segmentId: this._segmentId,
1627
- actions,
1628
- textRanges: [],
1629
- trigger: FACADE_TRIGGER,
1630
- isEditing: false
1631
- });
1632
- return Boolean(result);
1633
- }
1634
384
  };
385
+ function isParagraphFacade(value) {
386
+ if (typeof value !== "object" || value === null) return false;
387
+ return typeof value.getId === "function" && typeof value.getSegmentId === "function" && typeof value.getInfo === "function" && typeof value.getRange === "function";
388
+ }
1635
389
 
1636
390
  //#endregion
1637
391
  //#region \0@oxc-project+runtime@0.137.0/helpers/esm/typeof.js
@@ -1703,43 +457,46 @@ let FDocument = class FDocument extends FBaseInitialable {
1703
457
  this._resourceLoaderService = _resourceLoaderService;
1704
458
  this._commandService = _commandService;
1705
459
  _defineProperty(this, "id", void 0);
1706
- _defineProperty(this, "_docElementRegistry", new DocElementRegistry());
1707
460
  this.id = this._documentDataModel.getUnitId();
1708
461
  }
1709
462
  /**
1710
463
  * Get the document data model of the document.
464
+ * @param {string} segmentId The segment id used to get the header/footer data model. Defaults to an empty string for the document data model of the document.
1711
465
  * @returns {DocumentDataModel} The document data model.
1712
466
  * @example
1713
467
  * ```typescript
1714
468
  * const fDocument = univerAPI.getActiveDocument();
1715
- * const documentDataModel = fDocument.getDocumentDataModel();
1716
- * console.log(documentDataModel);
469
+ * console.log(fDocument.getDocumentDataModel());
470
+ *
471
+ * const headerSegmentId = fDocument.ensurePageHeader();
472
+ * console.log(fDocument.getDocumentDataModel(headerSegmentId));
1717
473
  * ```
1718
474
  */
1719
- getDocumentDataModel() {
1720
- return this._documentDataModel;
475
+ getDocumentDataModel(segmentId = "") {
476
+ const documentDataModel = this._documentDataModel.getSelfOrHeaderFooterModel(segmentId);
477
+ if (!documentDataModel) throw new Error(segmentId === "" ? "Document data model is not found." : `Document data model is not found in the segment: ${segmentId}`);
478
+ return documentDataModel;
1721
479
  }
1722
480
  /**
1723
- * Get the document body facade.
1724
- *
1725
- * The returned body facade provides synchronous Google Docs-like element APIs
1726
- * for reading and editing top-level document body elements. Paragraph elements
1727
- * use their persisted `paragraphId` values. Persisted elements, such as tables
1728
- * and custom blocks, use their existing ids.
1729
- *
1730
- * @returns {FDocBody} The document body API instance.
481
+ * Get the document body or header/footer body by the segment id.
482
+ * The main body has an empty segment id.
483
+ * The header and footer body have their respective segment ids.
484
+ * @param {string} segmentId The segment id of the body. Defaults to an empty string for the main body.
485
+ * @returns {IDocumentBody} The document body.
1731
486
  * @example
1732
487
  * ```typescript
1733
- * const doc = univerAPI.getActiveDocument();
1734
- * if (!doc) throw new Error('No active document');
488
+ * const fDocument = univerAPI.getActiveDocument();
489
+ * console.log(fDocument.getBody()); // Get the main body
1735
490
  *
1736
- * const body = doc.getBody();
1737
- * const paragraph = body.getChild(0).asParagraph();
1738
- * paragraph.appendText(' updated');
491
+ * const footerSegmentId = fDocument.ensurePageFooter();
492
+ * console.log(fDocument.getBody(footerSegmentId)); // Get the footer body
1739
493
  * ```
1740
494
  */
1741
- getBody() {
1742
- return new FDocBody(this._documentDataModel, this._commandService, this._docElementRegistry);
495
+ getBody(segmentId = "") {
496
+ var _this$_documentDataMo;
497
+ const body = (_this$_documentDataMo = this._documentDataModel.getSelfOrHeaderFooterModel(segmentId)) === null || _this$_documentDataMo === void 0 ? void 0 : _this$_documentDataMo.getBody();
498
+ if (!body) throw new Error(segmentId === "" ? "Body is not found in the document." : `Body is not found in the segment: ${segmentId}`);
499
+ return body;
1743
500
  }
1744
501
  dispose() {
1745
502
  super.dispose();
@@ -1750,8 +507,7 @@ let FDocument = class FDocument extends FBaseInitialable {
1750
507
  * @example
1751
508
  * ```typescript
1752
509
  * const fDocument = univerAPI.getActiveDocument();
1753
- * const unitId = fDocument.getId();
1754
- * console.log(unitId);
510
+ * console.log(fDocument.getId());
1755
511
  * ```
1756
512
  */
1757
513
  getId() {
@@ -1763,14 +519,25 @@ let FDocument = class FDocument extends FBaseInitialable {
1763
519
  * @example
1764
520
  * ```typescript
1765
521
  * const fDocument = univerAPI.getActiveDocument();
1766
- * const name = fDocument.getName();
1767
- * console.log(name);
522
+ * console.log(fDocument.getName());
1768
523
  * ```
1769
524
  */
1770
525
  getName() {
1771
526
  return this._documentDataModel.getTitle() || "";
1772
527
  }
1773
528
  /**
529
+ * Whether the document is a modern document or not.
530
+ * @returns {boolean} `true` if the document is a modern document, or `false` if it is not.
531
+ * @example
532
+ * ```typescript
533
+ * const fDocument = univerAPI.getActiveDocument();
534
+ * console.log(fDocument.isModern());
535
+ * ```
536
+ */
537
+ isModern() {
538
+ return this._documentDataModel.getSnapshot().documentStyle.documentFlavor === DocumentFlavor.MODERN;
539
+ }
540
+ /**
1774
541
  * Save the document snapshot data, including the document content and resource data, etc.
1775
542
  * @returns {IDocumentData} The document snapshot data.
1776
543
  * @example
@@ -1812,127 +579,274 @@ let FDocument = class FDocument extends FBaseInitialable {
1812
579
  return this._commandService.syncExecuteCommand(RedoCommand.id);
1813
580
  }
1814
581
  /**
1815
- * Adds the specified text to the end of this text region.
1816
- * @param {string} text - The text to be added to the end of this text region.
1817
- * @return {boolean} `true` if the text was successfully appended, or `false` if it failed.
582
+ * Ensure the page header segment exists and return its segment id.
583
+ * @param {number} pageIndex The zero-based page index. Defaults to the first page.
584
+ * @returns {string} The header segment id.
1818
585
  * @example
1819
- * ```typescript
586
+ * ```ts
1820
587
  * const fDocument = univerAPI.getActiveDocument();
1821
- * const success = fDocument.appendText('Hello, world!');
1822
- * console.log(success);
588
+ * const headerSegmentId = fDocument.ensurePageHeader();
589
+ * fDocument.insertText(0, 'Header text', headerSegmentId);
1823
590
  * ```
1824
591
  */
1825
- appendText(text) {
1826
- const { body } = this.save();
1827
- if (!body) throw new Error("The document body is empty");
1828
- const lastPosition = body.dataStream.length - 2;
1829
- return this.insertText(text, {
1830
- startOffset: lastPosition,
1831
- endOffset: lastPosition,
1832
- segmentId: ""
1833
- });
592
+ ensurePageHeader(pageIndex = 0) {
593
+ return this._ensureHeaderFooter("header", pageIndex);
1834
594
  }
1835
595
  /**
1836
- * Inserts text at the provided document range. Defaults to appending before the final section break.
1837
- * @param {string} text - The text to insert.
1838
- * @param {IDocumentInsertTextFacadeOptions} options - Optional target range, segment id, and cursor offset.
1839
- * @returns {boolean} `true` if the text was successfully inserted, or `false` if it failed.
596
+ * Ensure the page footer segment exists and return its segment id.
597
+ * @param {number} pageIndex The zero-based page index. Defaults to the first page.
598
+ * @returns {string} The footer segment id.
1840
599
  * @example
1841
- *
1842
- * // Insert text at a specific range in the document body
1843
- * ```typescript
600
+ * ```ts
1844
601
  * const fDocument = univerAPI.getActiveDocument();
1845
- * const success = fDocument.insertText('Hello, world!', {
1846
- * startOffset: 5,
1847
- * endOffset: 5,
1848
- * segmentId: '',
1849
- * cursorOffset: 13,
1850
- * });
1851
- * console.log(success);
602
+ * const footerSegmentId = fDocument.ensurePageFooter();
603
+ * fDocument.insertText(0, 'Footer text', footerSegmentId);
1852
604
  * ```
605
+ */
606
+ ensurePageFooter(pageIndex = 0) {
607
+ return this._ensureHeaderFooter("footer", pageIndex);
608
+ }
609
+ /**
610
+ * Insert plain text at a document body offset.
611
+ * @param {number} index The zero-based insertion offset.
612
+ * @param {string} text The plain text to insert.
613
+ * @param {string} segmentId The segment id of the body. Defaults to an empty string for the main body.
614
+ * @returns {boolean} `true` if the edit was applied.
615
+ * @example
616
+ * ```ts
617
+ * const fDocument = univerAPI.getActiveDocument();
618
+ * fDocument.insertText(0, 'Hello ');
1853
619
  *
1854
- * // Insert text at the beginning of a header or footer segment
1855
- * ```typescript
620
+ * const headerSegmentId = fDocument.ensurePageHeader();
621
+ * fDocument.insertText(0, 'Header text', headerSegmentId);
622
+ * ```
623
+ */
624
+ insertText(index, text, segmentId = "") {
625
+ return replaceBodyRange({
626
+ startOffset: index,
627
+ endOffset: index,
628
+ segmentId
629
+ }, buildPlainTextInsertBody(text), this._documentDataModel, this._injector);
630
+ }
631
+ /**
632
+ * Get all paragraphs in the document body or header/footer body by the segment id.
633
+ * @param {string} segmentId The segment id of the body. Defaults to an empty string for the main body.
634
+ * @returns {FDocumentParagraph[]} An array of paragraph facade instances.
635
+ * @example
636
+ * ```ts
1856
637
  * const fDocument = univerAPI.getActiveDocument();
1857
- * const snapshot = fDocument.save();
1858
- * const { headers, footers } = snapshot;
638
+ * const paragraphs = fDocument.getParagraphs();
639
+ * console.log(paragraphs);
1859
640
  *
1860
- * if (headers) {
1861
- * for (const headerId in headers) {
1862
- * if (headerId === 'target-header-id') {
1863
- * fDocument.insertText('Hello, header!', {
1864
- * startOffset: 0,
1865
- * endOffset: 0,
1866
- * segmentId: headerId,
1867
- * });
1868
- * }
1869
- * }
1870
- * }
641
+ * const headerSegmentId = fDocument.ensurePageHeader();
642
+ * const headerParagraphs = fDocument.getParagraphs(headerSegmentId);
643
+ * console.log(headerParagraphs);
644
+ * ```
645
+ */
646
+ getParagraphs(segmentId = "") {
647
+ const { paragraphs = [] } = this.getBody(segmentId);
648
+ return paragraphs.map((paragraph) => this._createFDocumentParagraph(paragraph.paragraphId, segmentId));
649
+ }
650
+ /**
651
+ * Get a paragraph by its paragraph id and segment id.
652
+ * @param {string} paragraphId The paragraph id.
653
+ * @param {string} segmentId The segment id of the body. Defaults to an empty string for the main body.
654
+ * @returns {FDocumentParagraph | null} The paragraph facade instance, or `null` if the paragraph is not found.
655
+ * @example
656
+ * ```ts
657
+ * const fDocument = univerAPI.getActiveDocument();
658
+ * const paragraph = fDocument.getParagraph('paragraph-01');
659
+ * console.log(paragraph);
1871
660
  *
1872
- * if (footers) {
1873
- * for (const footerId in footers) {
1874
- * if (footerId === 'target-footer-id') {
1875
- * fDocument.insertText('Hello, footer!', {
1876
- * startOffset: 0,
1877
- * endOffset: 0,
1878
- * segmentId: footerId,
1879
- * });
1880
- * }
1881
- * }
1882
- * }
661
+ * const headerSegmentId = fDocument.ensurePageHeader();
662
+ * const headerParagraph = fDocument.getParagraph('header-paragraph-01', headerSegmentId);
663
+ * console.log(headerParagraph);
1883
664
  * ```
1884
665
  */
1885
- insertText(text, options = {}) {
1886
- var _options$startOffset, _options$endOffset, _options$segmentId;
1887
- const unitId = this.id;
1888
- const { body } = this.save();
1889
- if (!body) throw new Error("The document body is empty");
1890
- const startOffset = (_options$startOffset = options.startOffset) !== null && _options$startOffset !== void 0 ? _options$startOffset : Math.max(0, body.dataStream.length - 2);
1891
- const endOffset = (_options$endOffset = options.endOffset) !== null && _options$endOffset !== void 0 ? _options$endOffset : startOffset;
1892
- const segmentId = (_options$segmentId = options.segmentId) !== null && _options$segmentId !== void 0 ? _options$segmentId : "";
1893
- const activeRange = {
1894
- startOffset,
1895
- endOffset,
1896
- collapsed: startOffset === endOffset,
666
+ getParagraph(paragraphId, segmentId = "") {
667
+ const { paragraphs = [] } = this.getBody(segmentId);
668
+ if (!paragraphs.find((paragraph) => paragraph.paragraphId === paragraphId)) return null;
669
+ return this._createFDocumentParagraph(paragraphId, segmentId);
670
+ }
671
+ /**
672
+ * Find a paragraph by its text content and segment id.
673
+ * @param {string} text The text content to search for.
674
+ * @param {string} segmentId The segment id of the body. Defaults to an empty string for the main body.
675
+ * @returns {FDocumentParagraph | null} The paragraph facade instance, or `null` if the paragraph is not found.
676
+ * @example
677
+ * ```ts
678
+ * const fDocument = univerAPI.getActiveDocument();
679
+ * const paragraph = fDocument.findParagraphByText('Hello');
680
+ * console.log(paragraph);
681
+ *
682
+ * const footerSegmentId = fDocument.ensurePageFooter();
683
+ * const footerParagraph = fDocument.findParagraphByText('Page', footerSegmentId);
684
+ * console.log(footerParagraph);
685
+ * ```
686
+ */
687
+ findParagraphByText(text, segmentId = "") {
688
+ return this.findParagraphs({
689
+ text,
1897
690
  segmentId
1898
- };
1899
- const removeLeadingParagraphBreak = startOffset === 0;
1900
- const insertBody = buildPlainTextInsertBody(text, {
1901
- paragraphStyle: getParagraphStyleAtOffset(body, startOffset),
1902
- removeLeadingParagraphBreak
1903
- });
1904
- const cursorOffset = options.cursorOffset == null ? void 0 : getNormalizedPlainTextCursorOffset(text, options.cursorOffset, removeLeadingParagraphBreak);
1905
- return this._commandService.syncExecuteCommand(InsertTextCommand.id, {
1906
- unitId,
1907
- body: insertBody,
1908
- range: activeRange,
1909
- segmentId,
1910
- ...cursorOffset == null ? {} : { cursorOffset }
1911
- });
691
+ })[0] || null;
1912
692
  }
1913
693
  /**
1914
- * Inserts one or more plain-text paragraphs at the provided document range.
1915
- * @param {string} text - The paragraph text to insert. Newlines are normalized to document paragraph separators.
1916
- * @param {IDocumentInsertTextFacadeOptions} options - Optional target range, segment id, and cursor offset.
1917
- * @returns {boolean} `true` if the paragraphs were successfully inserted, or `false` if it failed.
694
+ * Find paragraphs by a query object, which can include text content, paragraph id, and segment id.
695
+ * @param {string | IFDocumentParagraphQuery} query The query object or text content to search for.
696
+ * @returns {FDocumentParagraph[]} An array of paragraph facade instances that match the query.
1918
697
  * @example
1919
- * ```typescript
698
+ * ```ts
1920
699
  * const fDocument = univerAPI.getActiveDocument();
1921
- * const success = fDocument.insertParagraph('Hello, world! This is a new paragraph.', {
1922
- * startOffset: 5,
1923
- * endOffset: 5,
1924
- * });
1925
- * console.log(success);
700
+ * const paragraphsWithText = fDocument.findParagraphs('Hello');
701
+ * console.log(paragraphsWithText);
702
+ *
703
+ * const paragraphsWithId = fDocument.findParagraphs({ paragraphId: 'paragraph-01' });
704
+ * console.log(paragraphsWithId);
705
+ *
706
+ * const headerSegmentId = fDocument.ensurePageHeader();
707
+ * const paragraphsWithSegment = fDocument.findParagraphs({ segmentId: headerSegmentId });
708
+ * console.log(paragraphsWithSegment);
1926
709
  * ```
1927
710
  */
1928
- insertParagraph(text = "", options = {}) {
1929
- var _options$cursorOffset;
1930
- const dataStream = `${text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n").join("\r\n")}\r\n`;
1931
- return this.insertText(dataStream, {
1932
- ...options,
1933
- cursorOffset: (_options$cursorOffset = options.cursorOffset) !== null && _options$cursorOffset !== void 0 ? _options$cursorOffset : dataStream.length
711
+ findParagraphs(query) {
712
+ const { text, paragraphId, segmentId = "" } = typeof query === "string" ? { text: query } : query;
713
+ return this.getParagraphs(segmentId).filter((paragraph) => {
714
+ if (paragraphId && paragraph.getId() !== paragraphId) return false;
715
+ if (text && !paragraph.getText().includes(text)) return false;
716
+ return true;
1934
717
  });
1935
718
  }
719
+ /**
720
+ * Insert a plain-text paragraph before the paragraph at the given paragraph index.
721
+ * @param {number} index The zero-based paragraph insertion index.
722
+ * @param {string} text The paragraph text. Defaults to an empty paragraph.
723
+ * @param {string} segmentId The segment id of the body. Defaults to an empty string for the main body.
724
+ * @returns {FDocumentParagraph} The inserted paragraph facade instance.
725
+ * @example
726
+ * ```ts
727
+ * const fDocument = univerAPI.getActiveDocument();
728
+ * const paragraph = fDocument.insertParagraph(0, 'Document title');
729
+ * paragraph.appendText(' suffix');
730
+ *
731
+ * const headerSegmentId = fDocument.ensurePageHeader();
732
+ * const headerParagraph = fDocument.insertParagraph(0, 'Header title', headerSegmentId);
733
+ * headerParagraph.appendText(' suffix');
734
+ * ```
735
+ */
736
+ insertParagraph(index, text = "", segmentId = "") {
737
+ const offset = this._getParagraphInsertOffset(index, segmentId);
738
+ if (!replaceBodyRange({
739
+ startOffset: offset,
740
+ endOffset: offset,
741
+ segmentId
742
+ }, buildPlainTextInsertBody(`${text}\r`), this._documentDataModel, this._injector)) throw new Error("Failed to insert paragraph.");
743
+ const { paragraphs = [] } = this.getBody(segmentId);
744
+ const paragraph = paragraphs[index];
745
+ if (!paragraph) throw new Error("Failed to insert paragraph.");
746
+ return this._createFDocumentParagraph(paragraph.paragraphId, segmentId);
747
+ }
748
+ /**
749
+ * Append a plain-text paragraph at the end of the body.
750
+ * @param {string} text The paragraph text. Defaults to an empty paragraph.
751
+ * @returns {FDocumentParagraph} The appended paragraph wrapper.
752
+ * @example
753
+ * ```ts
754
+ * const fDocument = univerAPI.getActiveDocument();
755
+ * const paragraph = fDocument.appendParagraph('Summary');
756
+ * console.log(paragraph.getText());
757
+ *
758
+ * const footerSegmentId = fDocument.ensurePageFooter();
759
+ * const footerParagraph = fDocument.appendParagraph('Confidential', footerSegmentId);
760
+ * console.log(footerParagraph.getText());
761
+ * ```
762
+ */
763
+ appendParagraph(text = "", segmentId = "") {
764
+ const { paragraphs = [] } = this.getBody(segmentId);
765
+ return this.insertParagraph(paragraphs.length, text, segmentId);
766
+ }
767
+ /**
768
+ * Delete a range from the body.
769
+ * @param {IFDocumentTextRange} range The text range to delete.
770
+ * @returns {boolean} `true` if the range was deleted.
771
+ * @example
772
+ * ```ts
773
+ * const fDocument = univerAPI.getActiveDocument();
774
+ * fDocument.deleteRange({ startOffset: 0, endOffset: 5 });
775
+ *
776
+ * const headerSegmentId = fDocument.ensurePageHeader();
777
+ * fDocument.deleteRange({ startOffset: 0, endOffset: 5, segmentId: headerSegmentId });
778
+ * ```
779
+ */
780
+ deleteRange(range) {
781
+ const normalizedRange = this._normalizeDeleteRange(range);
782
+ if (normalizedRange.startOffset >= normalizedRange.endOffset) return false;
783
+ return replaceBodyRange(normalizedRange, { dataStream: "" }, this._documentDataModel, this._injector);
784
+ }
785
+ _createFDocumentParagraph(paragraphId, segmentId = "") {
786
+ return this._injector.createInstance(FDocumentParagraph, this, paragraphId, segmentId, this._injector);
787
+ }
788
+ _normalizeDeleteRange(range) {
789
+ const body = this.getBody(range.segmentId);
790
+ const protectedEndOffset = body.dataStream.endsWith("\r\n") ? Math.max(0, body.dataStream.length - 2) : body.dataStream.length;
791
+ const endOffset = Math.min(Math.max(range.endOffset, 0), protectedEndOffset);
792
+ return {
793
+ ...range,
794
+ startOffset: Math.min(Math.max(range.startOffset, 0), endOffset),
795
+ endOffset
796
+ };
797
+ }
798
+ _getParagraphInsertOffset(index, segmentId = "") {
799
+ if (index <= 0) return 0;
800
+ const { dataStream, paragraphs = [] } = this.getBody(segmentId);
801
+ if (paragraphs.length === 0) return Math.max(0, dataStream.length - 1);
802
+ if (index >= paragraphs.length) return paragraphs[paragraphs.length - 1].startIndex + 1;
803
+ return paragraphs[index - 1].startIndex + 1;
804
+ }
805
+ _ensureHeaderFooter(kind, pageIndex) {
806
+ if (this.isModern()) throw new Error("The document is a modern document, header/footer is not supported.");
807
+ const { createType, segmentId: existingSegmentId } = this._getHeaderFooterCreateInfo(kind, pageIndex);
808
+ if (existingSegmentId) return existingSegmentId;
809
+ const segmentId = generateRandomId(6);
810
+ if (!this._commandService.syncExecuteCommand(CreateHeaderFooterCommand.id, {
811
+ unitId: this.getId(),
812
+ segmentId,
813
+ createType
814
+ })) throw new Error(`Failed to create page ${kind}.`);
815
+ return segmentId;
816
+ }
817
+ _getHeaderFooterCreateInfo(kind, pageIndex) {
818
+ var _documentStyle$defaul, _documentStyle$defaul2;
819
+ const { documentStyle } = this._documentDataModel.getSnapshot();
820
+ const isFirstPage = pageIndex === 0;
821
+ const isEvenPage = (pageIndex + 1) % 2 === 0;
822
+ if (isFirstPage && documentStyle.useFirstPageHeaderFooter === BooleanNumber.TRUE) {
823
+ var _documentStyle$firstP, _documentStyle$firstP2;
824
+ return kind === "header" ? {
825
+ createType: HeaderFooterType.FIRST_PAGE_HEADER,
826
+ segmentId: (_documentStyle$firstP = documentStyle.firstPageHeaderId) !== null && _documentStyle$firstP !== void 0 ? _documentStyle$firstP : ""
827
+ } : {
828
+ createType: HeaderFooterType.FIRST_PAGE_FOOTER,
829
+ segmentId: (_documentStyle$firstP2 = documentStyle.firstPageFooterId) !== null && _documentStyle$firstP2 !== void 0 ? _documentStyle$firstP2 : ""
830
+ };
831
+ }
832
+ if (isEvenPage && documentStyle.evenAndOddHeaders === BooleanNumber.TRUE) {
833
+ var _documentStyle$evenPa, _documentStyle$evenPa2;
834
+ return kind === "header" ? {
835
+ createType: HeaderFooterType.EVEN_PAGE_HEADER,
836
+ segmentId: (_documentStyle$evenPa = documentStyle.evenPageHeaderId) !== null && _documentStyle$evenPa !== void 0 ? _documentStyle$evenPa : ""
837
+ } : {
838
+ createType: HeaderFooterType.EVEN_PAGE_FOOTER,
839
+ segmentId: (_documentStyle$evenPa2 = documentStyle.evenPageFooterId) !== null && _documentStyle$evenPa2 !== void 0 ? _documentStyle$evenPa2 : ""
840
+ };
841
+ }
842
+ return kind === "header" ? {
843
+ createType: HeaderFooterType.DEFAULT_HEADER,
844
+ segmentId: (_documentStyle$defaul = documentStyle.defaultHeaderId) !== null && _documentStyle$defaul !== void 0 ? _documentStyle$defaul : ""
845
+ } : {
846
+ createType: HeaderFooterType.DEFAULT_FOOTER,
847
+ segmentId: (_documentStyle$defaul2 = documentStyle.defaultFooterId) !== null && _documentStyle$defaul2 !== void 0 ? _documentStyle$defaul2 : ""
848
+ };
849
+ }
1936
850
  };
1937
851
  FDocument = __decorate([
1938
852
  __decorateParam(1, Inject(Injector)),
@@ -1962,4 +876,4 @@ var FUniverDocsMixin = class extends FUniver {
1962
876
  FUniver.extend(FUniverDocsMixin);
1963
877
 
1964
878
  //#endregion
1965
- export { DocElementRegistry, DocElementStaleError, FDocBlockRange, FDocBody, FDocCustomBlock, FDocElement, FDocParagraph, FDocTable, FDocument };
879
+ export { FDocument, FDocumentParagraph, isParagraphFacade, stripBlockTokens };