@univerjs/docs 1.0.0-insiders.20260813-7c9aa50 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/LICENSE +176 -0
  2. package/README.md +0 -1
  3. package/lib/cjs/facade.js +395 -78
  4. package/lib/cjs/index.js +2734 -100
  5. package/lib/es/facade.js +390 -80
  6. package/lib/es/index.js +2703 -105
  7. package/lib/facade.js +390 -80
  8. package/lib/index.js +2703 -105
  9. package/lib/types/commands/commands/core-editing.command.d.ts +5 -1
  10. package/lib/types/commands/commands/set-document-permission.command.d.ts +27 -0
  11. package/lib/types/commands/commands/set-document-permissions.command.d.ts +17 -0
  12. package/lib/types/commands/mutations/core-editing.mutation-id.d.ts +16 -0
  13. package/lib/types/commands/mutations/core-editing.mutation.d.ts +22 -1
  14. package/lib/types/commands/mutations/doc-structure-mutation-validation.d.ts +18 -0
  15. package/lib/types/commands/mutations/set-document-permission-rule.mutation.d.ts +17 -0
  16. package/lib/types/commands/mutations/set-document-permission-rules.mutation.d.ts +17 -0
  17. package/lib/types/controllers/doc-permission.controller.d.ts +26 -0
  18. package/lib/types/facade/f-document-paragraph.d.ts +15 -2
  19. package/lib/types/facade/f-document-permission.d.ts +177 -0
  20. package/lib/types/facade/f-document-section.d.ts +43 -2
  21. package/lib/types/facade/f-document-text-range.d.ts +7 -1
  22. package/lib/types/facade/f-document.d.ts +63 -15
  23. package/lib/types/facade/f-univer.d.ts +5 -4
  24. package/lib/types/facade/index.d.ts +1 -0
  25. package/lib/types/facade/utils.d.ts +2 -1
  26. package/lib/types/index.d.ts +13 -1
  27. package/lib/types/layout-worker/config/config.d.ts +32 -0
  28. package/lib/types/layout-worker/document-font-metrics.d.ts +18 -0
  29. package/lib/types/layout-worker/font-loader.d.ts +26 -0
  30. package/lib/types/layout-worker/index.d.ts +73 -0
  31. package/lib/types/layout-worker/performance-tracker.d.ts +27 -0
  32. package/lib/types/layout-worker/protocol.d.ts +37 -0
  33. package/lib/types/layout-worker/worker.d.ts +51 -0
  34. package/lib/types/plugin.d.ts +1 -0
  35. package/lib/types/services/doc-interceptor/interceptor-const.d.ts +2 -1
  36. package/lib/types/services/doc-layout-executor.service.d.ts +224 -0
  37. package/lib/types/services/doc-selection-manager.service.d.ts +7 -1
  38. package/lib/types/services/doc-skeleton-manager.service.d.ts +6 -1
  39. package/lib/types/services/doc-state-change-manager.service.d.ts +3 -0
  40. package/lib/types/services/doc-state-emit.service.d.ts +1 -0
  41. package/lib/types/services/document-layout-snapshot.d.ts +22 -0
  42. package/lib/types/services/permission/document-permission-resolver.d.ts +27 -0
  43. package/lib/types/services/permission/document-permission-rule.model.d.ts +19 -0
  44. package/lib/types/services/permission/document-permission.d.ts +35 -0
  45. package/lib/types/services/permission/permission-point/document/comment.d.ts +27 -0
  46. package/lib/types/services/permission/permission-point/document/copy.d.ts +27 -0
  47. package/lib/types/services/permission/permission-point/document/editable.d.ts +27 -0
  48. package/lib/types/services/permission/permission-point/document/export.d.ts +27 -0
  49. package/lib/types/services/permission/permission-point/document/print.d.ts +27 -0
  50. package/lib/types/services/permission/permission-point/entity/edit.d.ts +28 -0
  51. package/lib/types/services/permission/permission-point/paragraph/edit.d.ts +28 -0
  52. package/lib/types/services/permission/permission-point/section/edit.d.ts +28 -0
  53. package/lib/umd/facade.js +3 -3
  54. package/lib/umd/index.js +2 -2
  55. package/package.json +7 -5
package/lib/index.js CHANGED
@@ -1,6 +1,8 @@
1
- import { AlignTypeH, BuildTextUtils, ColumnSeparatorType, CommandType, CustomRangeType, DOCS_FORMULA_BAR_EDITOR_UNIT_ID_KEY, DOCS_NORMAL_EDITOR_UNIT_ID_KEY, DataStreamTreeTokenType, DeleteDirection, Disposable, DisposableCollection, DocumentFlavor, DocxBreakType, DrawingTypeEnum, ICommandService, IConfigService, IUndoRedoService, IUniverInstanceService, Inject, Injector, JSONX, LocaleService, MemoryCursor, ObjectRelativeFromH, ObjectRelativeFromV, Optional, PAGE_SIZE, PageOrientType, PaperType, Plugin, PositionedObjectLayoutType, RESTORE_INSERTED_PARAGRAPH_IDS, RedoCommandId, RxDisposable, SectionType, TRADITIONAL_DOCUMENT_DEFAULT_MARGIN, TextX, TextXActionType, Tools, UndoCommandId, UniverInstanceType, UpdateDocsAttributeType, composeInterceptors, containsInteriorInsertionOffset, createIdentifier, createInterceptorKey, createParagraphId, createSectionId, generateRandomId, getBlockRangeInterval, getColumnGroupRangeInterval, getParagraphContentStartOffset, getRichTextEditPath, getSectionHeaderFooterReferenceKey, getTableRangeInterval, isInternalEditorID, merge, remove, resolveSectionHeaderFooterReference, toDisposable, validateDocBodyStructure } from "@univerjs/core";
2
- import { DocumentSkeleton, DocumentViewModel, IRenderManagerService, NORMAL_TEXT_SELECTION_PLUGIN_STYLE } from "@univerjs/engine-render";
3
- import { BehaviorSubject, Subject, takeUntil } from "rxjs";
1
+ import { AlignTypeH, BooleanNumber, BuildTextUtils, ColumnSeparatorType, CommandType, CustomCommandExecutionError, CustomRangeType, DEFAULT_STYLES, DOCS_FORMULA_BAR_EDITOR_UNIT_ID_KEY, DOCS_NORMAL_EDITOR_UNIT_ID_KEY, DataStreamTreeTokenType, DeleteDirection, DependentOn, Disposable, DisposableCollection, DocumentDataModel, DocumentFlavor, DocxBreakType, DrawingTypeEnum, ErrorService, ICommandService, IConfigService, ILogService, IPermissionService, IResourceManagerService, IUndoRedoService, IUniverInstanceService, Inject, Injector, JSON1, JSONX, LocaleService, MemoryCursor, ObjectPermissionRuleModel, ObjectPermissionService, ObjectRelativeFromH, ObjectRelativeFromV, Optional, PAGE_SIZE, PageOrientType, PaperType, PermissionStatus, Plugin, PositionedObjectLayoutType, RESTORE_INSERTED_PARAGRAPH_IDS, RedoCommandId, RxDisposable, SectionType, TRADITIONAL_DOCUMENT_DEFAULT_MARGIN, TextX, TextXActionType, Tools, UndoCommandId, UniverInstanceType, UpdateDocsAttributeType, composeInterceptors, containsInteriorInsertionOffset, createIdentifier, createInterceptorKey, createParagraphId, createSectionId, generateRandomId, getBlockRangeInterval, getColumnGroupRangeInterval, getParagraphContentStartOffset, getParagraphContentStartOffsets, getRichTextEditPath, getSdtBindingKey, getSdtBindingValue, getSectionHeaderFooterReferenceKey, getTableRangeInterval, isInternalEditorID, merge, remove, requestImmediateMacroTask, resolveSectionHeaderFooterReference, toDisposable, validateDocBodyStructure, validateDocumentStructure } from "@univerjs/core";
2
+ import { DocumentLayoutSession, DocumentSkeleton, DocumentViewModel, FontCache, IRenderManagerService, NORMAL_TEXT_SELECTION_PLUGIN_STYLE, getDocsCustomBlockRenderViewport, getDocumentLayoutPresentation, getFontStyleString, setDocsCustomBlockRenderViewportProvider } from "@univerjs/engine-render";
3
+ import { BehaviorSubject, Observable, Subject, shareReplay, takeUntil } from "rxjs";
4
+ import { UnitAction, UnitObject } from "@univerjs/protocol";
5
+ import { ChannelService, fromModule, toModule } from "@univerjs/rpc";
4
6
 
5
7
  //#region src/commands/operations/text-selection.operation.ts
6
8
  const SetTextSelectionsOperation = {
@@ -12,7 +14,7 @@ const SetTextSelectionsOperation = {
12
14
  };
13
15
 
14
16
  //#endregion
15
- //#region \0@oxc-project+runtime@0.140.0/helpers/esm/typeof.js
17
+ //#region \0@oxc-project+runtime@0.149.0/helpers/esm/typeof.js
16
18
  function _typeof(o) {
17
19
  "@babel/helpers - typeof";
18
20
  return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o) {
@@ -23,7 +25,7 @@ function _typeof(o) {
23
25
  }
24
26
 
25
27
  //#endregion
26
- //#region \0@oxc-project+runtime@0.140.0/helpers/esm/toPrimitive.js
28
+ //#region \0@oxc-project+runtime@0.149.0/helpers/esm/toPrimitive.js
27
29
  function toPrimitive(t, r) {
28
30
  if ("object" != _typeof(t) || !t) return t;
29
31
  var e = t[Symbol.toPrimitive];
@@ -36,14 +38,14 @@ function toPrimitive(t, r) {
36
38
  }
37
39
 
38
40
  //#endregion
39
- //#region \0@oxc-project+runtime@0.140.0/helpers/esm/toPropertyKey.js
41
+ //#region \0@oxc-project+runtime@0.149.0/helpers/esm/toPropertyKey.js
40
42
  function toPropertyKey(t) {
41
43
  var i = toPrimitive(t, "string");
42
44
  return "symbol" == _typeof(i) ? i : i + "";
43
45
  }
44
46
 
45
47
  //#endregion
46
- //#region \0@oxc-project+runtime@0.140.0/helpers/esm/defineProperty.js
48
+ //#region \0@oxc-project+runtime@0.149.0/helpers/esm/defineProperty.js
47
49
  function _defineProperty(e, r, t) {
48
50
  return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
49
51
  value: t,
@@ -54,7 +56,7 @@ function _defineProperty(e, r, t) {
54
56
  }
55
57
 
56
58
  //#endregion
57
- //#region \0@oxc-project+runtime@0.140.0/helpers/esm/decorateParam.js
59
+ //#region \0@oxc-project+runtime@0.149.0/helpers/esm/decorateParam.js
58
60
  function __decorateParam(paramIndex, decorator) {
59
61
  return function(target, key) {
60
62
  decorator(target, key, paramIndex);
@@ -62,7 +64,7 @@ function __decorateParam(paramIndex, decorator) {
62
64
  }
63
65
 
64
66
  //#endregion
65
- //#region \0@oxc-project+runtime@0.140.0/helpers/esm/decorate.js
67
+ //#region \0@oxc-project+runtime@0.149.0/helpers/esm/decorate.js
66
68
  function __decorate(decorators, target, key, desc) {
67
69
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
68
70
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -106,9 +108,9 @@ let DocSelectionManagerService = class DocSelectionManagerService extends RxDisp
106
108
  getSelectionInfo(params = this._currentSelection) {
107
109
  return this._getTextRanges(params);
108
110
  }
109
- refreshSelection(params = this._currentSelection) {
111
+ refreshSelection(params = this._currentSelection, isEditing = false) {
110
112
  if (params == null) return;
111
- this._refresh(params);
113
+ this._refresh(params, isEditing);
112
114
  }
113
115
  __TEST_ONLY_setCurrentSelection(param) {
114
116
  this._currentSelection = param;
@@ -160,8 +162,19 @@ let DocSelectionManagerService = class DocSelectionManagerService extends RxDisp
160
162
  options
161
163
  });
162
164
  }
165
+ /**
166
+ * Replaces logical selection state without rebuilding render ranges. Atomic
167
+ * external mutations use this before applying document actions so layout can
168
+ * follow transformed offsets, then refresh after new geometry is available.
169
+ */
170
+ replaceSelectionInfoWithoutRefresh(selectionInfo, params = this._currentSelection) {
171
+ if (params == null) return;
172
+ this._replaceByParam({
173
+ ...selectionInfo,
174
+ ...params
175
+ });
176
+ }
163
177
  __replaceTextRangesWithNoRefresh(textSelectionInfo, search) {
164
- if (this._currentSelection == null) return;
165
178
  const params = {
166
179
  ...textSelectionInfo,
167
180
  ...search
@@ -196,7 +209,7 @@ let DocSelectionManagerService = class DocSelectionManagerService extends RxDisp
196
209
  const { unitId, subUnitId = "" } = param;
197
210
  return (_this$_textSelectionI = this._textSelectionInfo.get(unitId)) === null || _this$_textSelectionI === void 0 ? void 0 : _this$_textSelectionI.get(subUnitId);
198
211
  }
199
- _refresh(param) {
212
+ _refresh(param, isEditing = false) {
200
213
  const allTextSelectionInfo = this._getTextRanges(param);
201
214
  if (allTextSelectionInfo == null) return;
202
215
  const { textRanges, rectRanges, options } = allTextSelectionInfo;
@@ -206,8 +219,11 @@ let DocSelectionManagerService = class DocSelectionManagerService extends RxDisp
206
219
  unitId,
207
220
  subUnitId,
208
221
  docRanges,
209
- isEditing: false,
210
- options
222
+ isEditing,
223
+ options: (options === null || options === void 0 ? void 0 : options.forceFocus) ? {
224
+ ...options,
225
+ forceFocus: false
226
+ } : options
211
227
  });
212
228
  }
213
229
  _replaceByParam(insertParam) {
@@ -225,14 +241,664 @@ let DocSelectionManagerService = class DocSelectionManagerService extends RxDisp
225
241
  };
226
242
  DocSelectionManagerService = __decorate([__decorateParam(0, ICommandService), __decorateParam(1, IUniverInstanceService)], DocSelectionManagerService);
227
243
 
244
+ //#endregion
245
+ //#region src/commands/mutations/core-editing.mutation-id.ts
246
+ /**
247
+ * Copyright 2023-present DreamNum Co., Ltd.
248
+ *
249
+ * Licensed under the Apache License, Version 2.0 (the "License");
250
+ * you may not use this file except in compliance with the License.
251
+ * You may obtain a copy of the License at
252
+ *
253
+ * http://www.apache.org/licenses/LICENSE-2.0
254
+ *
255
+ * Unless required by applicable law or agreed to in writing, software
256
+ * distributed under the License is distributed on an "AS IS" BASIS,
257
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
258
+ * See the License for the specific language governing permissions and
259
+ * limitations under the License.
260
+ */
261
+ const RICH_TEXT_EDITING_MUTATION_ID = "doc.mutation.rich-text-editing";
262
+
263
+ //#endregion
264
+ //#region src/services/document-layout-snapshot.ts
265
+ function projectCustomBlock(block) {
266
+ const { docxRawXml: _docxRawXml, docxExportTs: _docxExportTs, ...layoutBlock } = block;
267
+ return layoutBlock;
268
+ }
269
+ function projectDocumentBody(body) {
270
+ const { docxRawCustomBlocks: _docxRawCustomBlocks, docxRawBlocks: _docxRawBlocks, docxExportExcludedRanges: _docxExportExcludedRanges, payloads: _payloads, customBlocks, ...layoutBody } = body;
271
+ return {
272
+ ...layoutBody,
273
+ ...customBlocks == null ? {} : { customBlocks: customBlocks.map(projectCustomBlock) }
274
+ };
275
+ }
276
+ function projectHeader(header) {
277
+ return {
278
+ ...header,
279
+ body: projectDocumentBody(header.body)
280
+ };
281
+ }
282
+ function projectFooter(footer) {
283
+ return {
284
+ ...footer,
285
+ body: projectDocumentBody(footer.body)
286
+ };
287
+ }
288
+ /**
289
+ * Builds the model snapshot owned by the layout Worker. Exchange-only payloads
290
+ * remain in the authoritative Main model and are deliberately excluded from
291
+ * the structured-clone boundary because they do not affect document geometry.
292
+ */
293
+ function createDocumentLayoutSnapshot(snapshot) {
294
+ const { resources: _resources, body, headers, footers, footnotes, endnotes, ...layoutSnapshot } = snapshot;
295
+ return {
296
+ ...layoutSnapshot,
297
+ ...footnotes == null ? {} : { footnotes: Object.fromEntries(Object.entries(footnotes).map(([id, note]) => [id, {
298
+ ...note,
299
+ body: projectDocumentBody(note.body)
300
+ }])) },
301
+ ...endnotes == null ? {} : { endnotes: Object.fromEntries(Object.entries(endnotes).map(([id, note]) => [id, {
302
+ ...note,
303
+ body: projectDocumentBody(note.body)
304
+ }])) },
305
+ ...body == null ? {} : { body: projectDocumentBody(body) },
306
+ ...headers == null ? {} : { headers: Object.fromEntries(Object.entries(headers).map(([headerId, header]) => [headerId, projectHeader(header)])) },
307
+ ...footers == null ? {} : { footers: Object.fromEntries(Object.entries(footers).map(([footerId, footer]) => [footerId, projectFooter(footer)])) }
308
+ };
309
+ }
310
+
311
+ //#endregion
312
+ //#region src/services/doc-layout-executor.service.ts
313
+ var _DocLayoutExecutorService;
314
+ let DocLayoutExecutorType = /* @__PURE__ */ function(DocLayoutExecutorType) {
315
+ DocLayoutExecutorType["MAIN_THREAD"] = "main-thread";
316
+ DocLayoutExecutorType["WORKER"] = "worker";
317
+ return DocLayoutExecutorType;
318
+ }({});
319
+ let DocLayoutSessionStatus = /* @__PURE__ */ function(DocLayoutSessionStatus) {
320
+ DocLayoutSessionStatus["ACCEPTED"] = "accepted";
321
+ DocLayoutSessionStatus["NOT_FOUND"] = "not-found";
322
+ DocLayoutSessionStatus["RESNAPSHOT_REQUIRED"] = "resnapshot-required";
323
+ DocLayoutSessionStatus["SUPERSEDED"] = "superseded";
324
+ return DocLayoutSessionStatus;
325
+ }({});
326
+ let DocLayoutExecutorState = /* @__PURE__ */ function(DocLayoutExecutorState) {
327
+ DocLayoutExecutorState["UNREGISTERED"] = "unregistered";
328
+ DocLayoutExecutorState["INITIALIZING"] = "initializing";
329
+ DocLayoutExecutorState["ACTIVE"] = "active";
330
+ DocLayoutExecutorState["RECOVERING"] = "recovering";
331
+ DocLayoutExecutorState["FAILED"] = "failed";
332
+ return DocLayoutExecutorState;
333
+ }({});
334
+ function areShallowRecordsEqual(left, right) {
335
+ const leftRecord = left;
336
+ const rightRecord = right;
337
+ const leftKeys = Object.keys(leftRecord);
338
+ const rightKeys = Object.keys(rightRecord);
339
+ return leftKeys.length === rightKeys.length && leftKeys.every((key) => leftRecord[key] === rightRecord[key]);
340
+ }
341
+ function createRecordPatch(previous, current) {
342
+ const upserts = {};
343
+ const removals = [];
344
+ for (const [key, value] of Object.entries(current)) if (previous[key] == null || !areShallowRecordsEqual(previous[key], value)) upserts[key] = value;
345
+ for (const key of Object.keys(previous)) if (current[key] == null) removals.push(key);
346
+ return {
347
+ removals,
348
+ upserts
349
+ };
350
+ }
351
+ function getCustomRangePresentationKey$1(entry) {
352
+ return JSON.stringify([entry.segmentId, entry.rangeId]);
353
+ }
354
+ function toCustomRangePresentationMap(entries) {
355
+ return new Map(entries.map((entry) => [getCustomRangePresentationKey$1(entry), entry]));
356
+ }
357
+ function createCustomRangePresentationPatch(previous, current) {
358
+ const upserts = [];
359
+ const removals = [];
360
+ for (const [key, entry] of current) {
361
+ const previousEntry = previous.get(key);
362
+ if (previousEntry == null || !areShallowRecordsEqual(previousEntry.presentation, entry.presentation)) upserts.push(entry);
363
+ }
364
+ for (const [key, entry] of previous) if (!current.has(key)) removals.push({
365
+ segmentId: entry.segmentId,
366
+ rangeId: entry.rangeId
367
+ });
368
+ return {
369
+ removals,
370
+ upserts
371
+ };
372
+ }
373
+ function isRichTextEditingMutationParams(value) {
374
+ return typeof value === "object" && value != null && "unitId" in value && typeof value.unitId === "string" && "actions" in value;
375
+ }
376
+ function collectCustomBlockViewports(dataModel) {
377
+ const snapshot = dataModel.getSnapshot();
378
+ const documentStyle = snapshot.documentStyle;
379
+ const viewports = {};
380
+ const bodies = [
381
+ snapshot.body,
382
+ ...Object.values(snapshot.headers ?? {}).map((header) => header.body),
383
+ ...Object.values(snapshot.footers ?? {}).map((footer) => footer.body)
384
+ ];
385
+ for (const customBlock of bodies.flatMap((body) => (body === null || body === void 0 ? void 0 : body.customBlocks) ?? [])) {
386
+ var _snapshot$drawings, _documentStyle$pageSi;
387
+ const drawing = (_snapshot$drawings = snapshot.drawings) === null || _snapshot$drawings === void 0 ? void 0 : _snapshot$drawings[customBlock.blockId];
388
+ if (drawing == null) continue;
389
+ const viewport = getDocsCustomBlockRenderViewport(dataModel.getUnitId(), customBlock.blockId, {
390
+ fallbackHeight: drawing.docTransform.size.height ?? 0,
391
+ fallbackWidth: drawing.docTransform.size.width ?? 0,
392
+ pageMarginLeft: documentStyle.marginLeft,
393
+ pageMarginRight: documentStyle.marginRight,
394
+ pageWidth: (_documentStyle$pageSi = documentStyle.pageSize) === null || _documentStyle$pageSi === void 0 ? void 0 : _documentStyle$pageSi.width
395
+ });
396
+ if (viewport != null) viewports[customBlock.blockId] = { ...viewport };
397
+ }
398
+ return viewports;
399
+ }
400
+ let DocLayoutExecutorService = _DocLayoutExecutorService = class DocLayoutExecutorService extends Disposable {
401
+ constructor(_univerInstanceService, _commandService, _localeService, _logService) {
402
+ super();
403
+ this._univerInstanceService = _univerInstanceService;
404
+ this._commandService = _commandService;
405
+ this._localeService = _localeService;
406
+ this._logService = _logService;
407
+ _defineProperty(this, "_executor", null);
408
+ _defineProperty(this, "_executorReady", null);
409
+ _defineProperty(this, "_recoveryFailure", null);
410
+ _defineProperty(this, "_sessionEpoch", 0);
411
+ _defineProperty(this, "_sessions", /* @__PURE__ */ new Map());
412
+ _defineProperty(this, "_hydrationSamples", /* @__PURE__ */ new Map());
413
+ _defineProperty(this, "_customRangePresentationProviders", /* @__PURE__ */ new Set());
414
+ _defineProperty(this, "_executorStatus$", new BehaviorSubject({
415
+ state: "unregistered",
416
+ executor: null,
417
+ diagnostic: null,
418
+ recoveryUnitId: null
419
+ }));
420
+ _defineProperty(this, "executorStatus$", this._executorStatus$.asObservable());
421
+ this.disposeWithMe(this._univerInstanceService.getTypeOfUnitAdded$(UniverInstanceType.UNIVER_DOC).subscribe(({ unit }) => this._createSessionIfEligible(unit)));
422
+ this.disposeWithMe(this._univerInstanceService.getTypeOfUnitDisposed$(UniverInstanceType.UNIVER_DOC).subscribe((unit) => this._disposeSession(unit.getUnitId())));
423
+ this.disposeWithMe(this._commandService.onCommandExecuted((command) => {
424
+ if (command.id !== "doc.mutation.rich-text-editing") return;
425
+ const params = command.params;
426
+ if (!isRichTextEditingMutationParams(params)) {
427
+ this._logService.warn("[DocLayoutExecutorService]: ignored malformed document mutation parameters.");
428
+ return;
429
+ }
430
+ this._enqueueCommittedMutation(params);
431
+ }));
432
+ this.disposeWithMe(this._localeService.localeChanged$.subscribe(() => {
433
+ for (const unitId of this._sessions.keys()) {
434
+ const dataModel = this._getEligibleModel(unitId);
435
+ if (dataModel != null) this._replaceSession(dataModel).catch((error) => {
436
+ this._logService.error("[DocLayoutExecutorService]: failed to refresh Worker locale.", error);
437
+ });
438
+ }
439
+ }));
440
+ }
441
+ register(executor) {
442
+ if (this._executor != null) throw new Error("A document layout executor is already registered.");
443
+ this._executor = executor;
444
+ this._recoveryFailure = null;
445
+ this._executorStatus$.next({
446
+ state: "initializing",
447
+ executor: executor.type,
448
+ diagnostic: null,
449
+ recoveryUnitId: null
450
+ });
451
+ const executorReady = executor.initialize();
452
+ this._executorReady = executorReady;
453
+ executorReady.then(() => {
454
+ if (this._executor === executor) this._executorStatus$.next({
455
+ state: "active",
456
+ executor: executor.type,
457
+ diagnostic: null,
458
+ recoveryUnitId: null
459
+ });
460
+ }).catch((error) => {
461
+ if (this._executor !== executor) return;
462
+ this._executor = null;
463
+ this._executorReady = null;
464
+ this._disposeManagedSessions();
465
+ this._sessions.clear();
466
+ const diagnostic = error instanceof Error ? error.message : String(error);
467
+ this._executorStatus$.next({
468
+ state: "failed",
469
+ executor: executor.type,
470
+ diagnostic,
471
+ recoveryUnitId: null
472
+ });
473
+ this._logService.error("[DocLayoutExecutorService]: document layout executor initialization failed.", error);
474
+ });
475
+ for (const unit of this._univerInstanceService.getAllUnitsForType(UniverInstanceType.UNIVER_DOC)) this._createSessionIfEligible(unit);
476
+ return toDisposable(() => {
477
+ if (this._executor === executor) {
478
+ this._executor = null;
479
+ this._executorReady = null;
480
+ this._disposeManagedSessions();
481
+ this._sessions.clear();
482
+ this._executorStatus$.next({
483
+ state: "unregistered",
484
+ executor: null,
485
+ diagnostic: null,
486
+ recoveryUnitId: null
487
+ });
488
+ }
489
+ });
490
+ }
491
+ getExecutor() {
492
+ return this._executor;
493
+ }
494
+ getExecutorStatus() {
495
+ return this._executorStatus$.value;
496
+ }
497
+ registerCustomRangePresentationProvider(provider) {
498
+ this._customRangePresentationProviders.add(provider);
499
+ return toDisposable(() => this._customRangePresentationProviders.delete(provider));
500
+ }
501
+ async getPerformanceMetrics(unitId) {
502
+ var _this$_executor;
503
+ const executorMetrics = await ((_this$_executor = this._executor) === null || _this$_executor === void 0 ? void 0 : _this$_executor.getPerformanceMetrics(unitId));
504
+ return {
505
+ hydrationMs: [...this._hydrationSamples.get(unitId) ?? []],
506
+ mutationTransferMs: [...(executorMetrics === null || executorMetrics === void 0 ? void 0 : executorMetrics.mutationTransferMs) ?? []],
507
+ patchTransferMs: [...(executorMetrics === null || executorMetrics === void 0 ? void 0 : executorMetrics.patchTransferMs) ?? []],
508
+ snapshotTransferMs: [...(executorMetrics === null || executorMetrics === void 0 ? void 0 : executorMetrics.snapshotTransferMs) ?? []]
509
+ };
510
+ }
511
+ async resetPerformanceMetrics(unitId) {
512
+ var _this$_executor2;
513
+ this._hydrationSamples.delete(unitId);
514
+ await ((_this$_executor2 = this._executor) === null || _this$_executor2 === void 0 ? void 0 : _this$_executor2.resetPerformanceMetrics(unitId));
515
+ }
516
+ recordHydrationDuration(unitId, durationMs) {
517
+ this._appendPerformanceSample(this._hydrationSamples, unitId, durationMs);
518
+ }
519
+ async recoverExecutor(unitId, diagnostic) {
520
+ const executor = this._requireExecutor();
521
+ this._executorStatus$.next({
522
+ state: "recovering",
523
+ executor: executor.type,
524
+ diagnostic,
525
+ recoveryUnitId: unitId
526
+ });
527
+ for (const session of this._sessions.values()) this._markSessionDisposed(session);
528
+ this._sessions.clear();
529
+ this._hydrationSamples.clear();
530
+ try {
531
+ const executorReady = executor.recover();
532
+ this._executorReady = executorReady;
533
+ await executorReady;
534
+ } catch (error) {
535
+ if (this._executor === executor) {
536
+ const recoveryDiagnostic = error instanceof Error ? error.message : String(error);
537
+ this._executor = null;
538
+ this._executorReady = null;
539
+ this._recoveryFailure = {
540
+ diagnostic: recoveryDiagnostic,
541
+ unitId
542
+ };
543
+ this._executorStatus$.next({
544
+ state: "recovering",
545
+ executor: executor.type,
546
+ diagnostic: recoveryDiagnostic,
547
+ recoveryUnitId: unitId
548
+ });
549
+ }
550
+ throw error;
551
+ }
552
+ }
553
+ completeRecovery(unitId) {
554
+ var _this$_recoveryFailur;
555
+ const status = this._executorStatus$.value;
556
+ if (status.state !== "recovering" || status.recoveryUnitId !== unitId) return;
557
+ const recoveryFailure = ((_this$_recoveryFailur = this._recoveryFailure) === null || _this$_recoveryFailur === void 0 ? void 0 : _this$_recoveryFailur.unitId) === unitId ? this._recoveryFailure : null;
558
+ this._recoveryFailure = null;
559
+ this._executorStatus$.next({
560
+ state: recoveryFailure == null ? "active" : "failed",
561
+ executor: status.executor,
562
+ diagnostic: (recoveryFailure === null || recoveryFailure === void 0 ? void 0 : recoveryFailure.diagnostic) ?? null,
563
+ recoveryUnitId: null
564
+ });
565
+ }
566
+ async startLayout(identity, options, budgetMs) {
567
+ const { unitId } = identity;
568
+ const executor = this._executor;
569
+ const dataModel = this._getEligibleModel(unitId);
570
+ if (executor == null || dataModel == null) return null;
571
+ let session = this._sessions.get(unitId);
572
+ if (session != null && session.sourceMutationRevision !== dataModel.getMutationRevision()) session.needsResnapshot = true;
573
+ if (session == null || session.needsResnapshot) session = await this._replaceSession(dataModel);
574
+ if (session.disposed || this._executor !== executor || this._getEligibleModel(unitId) !== dataModel) return null;
575
+ let result = await this._startSynchronizedLayout(executor, session, identity, options, budgetMs);
576
+ if (session.disposed || this._executor !== executor || this._getEligibleModel(unitId) !== dataModel) return null;
577
+ if (result.status === "not-found" || result.status === "resnapshot-required") {
578
+ session = await this._replaceSession(dataModel);
579
+ result = await this._startSynchronizedLayout(executor, session, identity, options, budgetMs);
580
+ }
581
+ return result;
582
+ }
583
+ stepLayout(request) {
584
+ return this._requireExecutor().stepLayout(request);
585
+ }
586
+ publishBacklog(request) {
587
+ return this._requireExecutor().publishBacklog(request);
588
+ }
589
+ getLayoutPage(request) {
590
+ return this._requireExecutor().getLayoutPage(request);
591
+ }
592
+ cancelLayout(request) {
593
+ const executor = this._executor;
594
+ return executor == null ? Promise.resolve() : executor.cancelLayout(request);
595
+ }
596
+ disposeLayoutMount(request) {
597
+ const executor = this._executor;
598
+ const session = this._sessions.get(request.unitId);
599
+ const latestStart = session === null || session === void 0 ? void 0 : session.latestStarts.get(request.mountId);
600
+ if ((latestStart === null || latestStart === void 0 ? void 0 : latestStart.mountEpoch) === request.mountEpoch && latestStart.viewportEpoch === request.viewportEpoch) {
601
+ session === null || session === void 0 || session.latestStarts.delete(request.mountId);
602
+ session === null || session === void 0 || session.customBlockViewportsByMount.delete(request.mountId);
603
+ }
604
+ return executor == null ? Promise.resolve() : executor.disposeLayoutMount(request);
605
+ }
606
+ dispose() {
607
+ const executor = this._executor;
608
+ if (executor != null) for (const [unitId, session] of this._sessions) {
609
+ this._markSessionDisposed(session);
610
+ executor.disposeSession({
611
+ unitId,
612
+ sessionEpoch: session.sessionEpoch
613
+ }).catch((error) => {
614
+ if (this._executor === executor) this._logService.error("[DocLayoutExecutorService]: failed to dispose a Worker session.", error);
615
+ });
616
+ }
617
+ this._sessions.clear();
618
+ this._executor = null;
619
+ this._executorReady = null;
620
+ this._recoveryFailure = null;
621
+ this._executorStatus$.complete();
622
+ this._customRangePresentationProviders.clear();
623
+ super.dispose();
624
+ }
625
+ _createSessionIfEligible(dataModel) {
626
+ if (this._executor == null || !this._isEligible(dataModel) || this._sessions.has(dataModel.getUnitId())) return;
627
+ this._replaceSession(dataModel).catch((error) => {
628
+ this._logService.error("[DocLayoutExecutorService]: failed to create a Worker session.", error);
629
+ });
630
+ }
631
+ _enqueueCommittedMutation(params) {
632
+ const executor = this._executor;
633
+ const dataModel = this._getEligibleModel(params.unitId);
634
+ if (executor == null || dataModel == null) return;
635
+ const session = this._sessions.get(params.unitId);
636
+ if (session == null) {
637
+ this._replaceSession(dataModel).catch((error) => {
638
+ this._logService.error("[DocLayoutExecutorService]: failed to snapshot a document mutation.", error);
639
+ });
640
+ return;
641
+ }
642
+ if (JSONX.isNoop(params.actions)) return;
643
+ const sourceMutationRevision = dataModel.getMutationRevision();
644
+ if (sourceMutationRevision !== session.sourceMutationRevision + 1) {
645
+ session.needsResnapshot = true;
646
+ return;
647
+ }
648
+ const baseRevision = session.modelRevision;
649
+ const modelRevision = baseRevision + 1;
650
+ session.modelRevision = modelRevision;
651
+ session.sourceMutationRevision = sourceMutationRevision;
652
+ session.pendingMutations.push({
653
+ baseRevision,
654
+ modelRevision,
655
+ actions: params.actions
656
+ });
657
+ }
658
+ async _startSynchronizedLayout(executor, session, identity, options, budgetMs) {
659
+ var _session$latestStarts;
660
+ const { unitId, mountId } = identity;
661
+ const startId = (((_session$latestStarts = session.latestStarts.get(mountId)) === null || _session$latestStarts === void 0 ? void 0 : _session$latestStarts.startId) ?? 0) + 1;
662
+ const startIdentity = {
663
+ startId,
664
+ mountEpoch: identity.mountEpoch,
665
+ viewportEpoch: identity.viewportEpoch
666
+ };
667
+ session.latestStarts.set(mountId, startIdentity);
668
+ const startTask = session.queue.then(async () => {
669
+ if (session.disposed || this._executor !== executor) return { status: "not-found" };
670
+ const currentModel = this._getEligibleModel(unitId);
671
+ if (currentModel == null || currentModel.getMutationRevision() !== session.sourceMutationRevision) {
672
+ session.needsResnapshot = true;
673
+ return {
674
+ status: "resnapshot-required",
675
+ modelRevision: session.workerRevision
676
+ };
677
+ }
678
+ if (session.latestStarts.get(mountId) !== startIdentity) return { status: "superseded" };
679
+ const mutations = session.pendingMutations.slice();
680
+ const projections = this._createLayoutProjectionPayload(session, identity, this._getRequiredModel(unitId));
681
+ const request = {
682
+ ...identity,
683
+ metricsRevision: startId,
684
+ baseRevision: session.workerRevision,
685
+ modelRevision: session.modelRevision,
686
+ mutations,
687
+ ...projections.request,
688
+ budgetMs,
689
+ ...options
690
+ };
691
+ const result = await executor.startLayout(request);
692
+ if (result.status === "accepted") {
693
+ session.workerRevision = request.modelRevision;
694
+ session.pendingMutations.splice(0, mutations.length);
695
+ session.customBlockViewportsByMount.set(mountId, {
696
+ mountEpoch: identity.mountEpoch,
697
+ value: projections.customBlockViewports
698
+ });
699
+ session.customRangePresentations = projections.customRangePresentations;
700
+ } else session.needsResnapshot = true;
701
+ return result;
702
+ });
703
+ session.queue = startTask.then(() => void 0, (error) => {
704
+ if (session.disposed || this._executor !== executor) return;
705
+ session.needsResnapshot = true;
706
+ this._logService.error("[DocLayoutExecutorService]: synchronized layout start failed.", error);
707
+ });
708
+ return startTask;
709
+ }
710
+ _createLayoutProjectionPayload(session, identity, dataModel) {
711
+ const customBlockViewports = collectCustomBlockViewports(dataModel);
712
+ const previousCustomBlockViewports = session.customBlockViewportsByMount.get(identity.mountId);
713
+ const customRangePresentations = this._collectCustomRangePresentations(dataModel);
714
+ const customRangePresentationMap = toCustomRangePresentationMap(customRangePresentations);
715
+ const request = {};
716
+ if (previousCustomBlockViewports == null || previousCustomBlockViewports.mountEpoch !== identity.mountEpoch) request.customBlockViewports = customBlockViewports;
717
+ else {
718
+ const patch = createRecordPatch(previousCustomBlockViewports.value, customBlockViewports);
719
+ if (patch.removals.length > 0 || Object.keys(patch.upserts).length > 0) request.customBlockViewportPatch = patch;
720
+ }
721
+ if (session.customRangePresentations == null) request.customRangePresentations = customRangePresentations;
722
+ else {
723
+ const patch = createCustomRangePresentationPatch(session.customRangePresentations, customRangePresentationMap);
724
+ if (patch.removals.length > 0 || patch.upserts.length > 0) request.customRangePresentationPatch = patch;
725
+ }
726
+ return {
727
+ request,
728
+ customBlockViewports,
729
+ customRangePresentations: customRangePresentationMap
730
+ };
731
+ }
732
+ async _replaceSession(dataModel) {
733
+ const executor = this._requireExecutor();
734
+ const unitId = dataModel.getUnitId();
735
+ const previous = this._sessions.get(unitId);
736
+ if (previous != null) this._markSessionDisposed(previous);
737
+ const modelRevision = (previous === null || previous === void 0 ? void 0 : previous.modelRevision) ?? 0;
738
+ const session = {
739
+ sessionEpoch: ++this._sessionEpoch,
740
+ modelRevision,
741
+ sourceMutationRevision: dataModel.getMutationRevision(),
742
+ workerRevision: modelRevision,
743
+ pendingMutations: [],
744
+ latestStarts: /* @__PURE__ */ new Map(),
745
+ customBlockViewportsByMount: /* @__PURE__ */ new Map(),
746
+ customRangePresentations: null,
747
+ queue: Promise.resolve(),
748
+ needsResnapshot: false,
749
+ disposed: false,
750
+ cancelPendingCreateTask: null
751
+ };
752
+ const createTask = this._createSessionOnNextMacroTask(executor, session, dataModel);
753
+ session.queue = createTask;
754
+ this._sessions.set(unitId, session);
755
+ try {
756
+ await createTask;
757
+ } catch (error) {
758
+ if (!session.disposed && this._executor === executor) {
759
+ session.needsResnapshot = true;
760
+ throw error;
761
+ }
762
+ }
763
+ return session;
764
+ }
765
+ _disposeSession(unitId) {
766
+ const session = this._sessions.get(unitId);
767
+ if (session == null) return;
768
+ this._markSessionDisposed(session);
769
+ this._sessions.delete(unitId);
770
+ this._hydrationSamples.delete(unitId);
771
+ const executor = this._executor;
772
+ executor === null || executor === void 0 || executor.disposeSession({
773
+ unitId,
774
+ sessionEpoch: session.sessionEpoch
775
+ }).catch((error) => {
776
+ if (this._executor === executor) this._logService.error("[DocLayoutExecutorService]: failed to dispose a Worker session.", error);
777
+ });
778
+ }
779
+ _getEligibleModel(unitId) {
780
+ const dataModel = this._univerInstanceService.getUnit(unitId, UniverInstanceType.UNIVER_DOC);
781
+ return dataModel != null && this._isEligible(dataModel) ? dataModel : null;
782
+ }
783
+ _getRequiredModel(unitId) {
784
+ const dataModel = this._getEligibleModel(unitId);
785
+ if (dataModel == null) throw new Error(`Eligible document model not found for Worker layout: "${unitId}".`);
786
+ return dataModel;
787
+ }
788
+ _collectCustomRangePresentations(dataModel) {
789
+ const presentations = [];
790
+ const providers = Array.from(this._customRangePresentationProviders).reverse();
791
+ if (providers.length === 0) return presentations;
792
+ const collectSegment = (segmentId, body) => {
793
+ if (body == null) return;
794
+ const seenRangeIds = /* @__PURE__ */ new Set();
795
+ for (const range of body.customRanges ?? []) {
796
+ if (seenRangeIds.has(range.rangeId)) continue;
797
+ seenRangeIds.add(range.rangeId);
798
+ for (const provider of providers) {
799
+ const presentation = provider(dataModel.getUnitId(), range, {
800
+ segmentId,
801
+ body
802
+ });
803
+ if (presentation != null) {
804
+ presentations.push({
805
+ segmentId,
806
+ rangeId: range.rangeId,
807
+ presentation: { ...presentation }
808
+ });
809
+ break;
810
+ }
811
+ }
812
+ }
813
+ };
814
+ collectSegment("", dataModel.getBody());
815
+ for (const [segmentId, headerModel] of dataModel.headerModelMap) collectSegment(segmentId, headerModel.getBody());
816
+ for (const [segmentId, footerModel] of dataModel.footerModelMap) collectSegment(segmentId, footerModel.getBody());
817
+ return presentations;
818
+ }
819
+ _isEligible(dataModel) {
820
+ if (isInternalEditorID(dataModel.getUnitId())) return false;
821
+ const flavor = dataModel.getSnapshot().documentStyle.documentFlavor;
822
+ return flavor === DocumentFlavor.TRADITIONAL || flavor === DocumentFlavor.MODERN;
823
+ }
824
+ _requireExecutor() {
825
+ if (this._executor == null) throw new Error("No document layout executor is registered.");
826
+ return this._executor;
827
+ }
828
+ _appendPerformanceSample(target, unitId, durationMs) {
829
+ const samples = target.get(unitId) ?? [];
830
+ samples.push(durationMs);
831
+ if (samples.length > _DocLayoutExecutorService._performanceSampleLimit) samples.splice(0, samples.length - _DocLayoutExecutorService._performanceSampleLimit);
832
+ target.set(unitId, samples);
833
+ }
834
+ _createSessionOnNextMacroTask(executor, session, dataModel) {
835
+ return new Promise((resolve, reject) => {
836
+ const cancelTask = requestImmediateMacroTask(() => {
837
+ session.cancelPendingCreateTask = null;
838
+ const unitId = dataModel.getUnitId();
839
+ if (session.disposed || this._executor !== executor || this._sessions.get(unitId) !== session) {
840
+ resolve();
841
+ return;
842
+ }
843
+ const executorReady = this._executorReady;
844
+ if (executorReady == null) {
845
+ resolve();
846
+ return;
847
+ }
848
+ executorReady.then(() => {
849
+ if (session.disposed || this._executor !== executor || this._sessions.get(unitId) !== session || this._getEligibleModel(unitId) !== dataModel) {
850
+ resolve();
851
+ return;
852
+ }
853
+ session.sourceMutationRevision = dataModel.getMutationRevision();
854
+ session.workerRevision = session.modelRevision;
855
+ session.pendingMutations.length = 0;
856
+ const request = {
857
+ unitId,
858
+ sessionEpoch: session.sessionEpoch,
859
+ snapshot: createDocumentLayoutSnapshot(dataModel.getSnapshot()),
860
+ modelRevision: session.modelRevision,
861
+ locale: this._localeService.getCurrentLocale(),
862
+ localeData: this._localeService.getLocales(),
863
+ direction: this._localeService.getDirection()
864
+ };
865
+ executor.createSession(request).then(resolve, reject);
866
+ }, reject);
867
+ });
868
+ session.cancelPendingCreateTask = () => {
869
+ cancelTask();
870
+ session.cancelPendingCreateTask = null;
871
+ resolve();
872
+ };
873
+ });
874
+ }
875
+ _markSessionDisposed(session) {
876
+ var _session$cancelPendin;
877
+ session.disposed = true;
878
+ (_session$cancelPendin = session.cancelPendingCreateTask) === null || _session$cancelPendin === void 0 || _session$cancelPendin.call(session);
879
+ session.cancelPendingCreateTask = null;
880
+ }
881
+ _disposeManagedSessions() {
882
+ for (const session of this._sessions.values()) this._markSessionDisposed(session);
883
+ }
884
+ };
885
+ _defineProperty(DocLayoutExecutorService, "_performanceSampleLimit", 2048);
886
+ DocLayoutExecutorService = _DocLayoutExecutorService = __decorate([
887
+ __decorateParam(0, IUniverInstanceService),
888
+ __decorateParam(1, ICommandService),
889
+ __decorateParam(2, Inject(LocaleService)),
890
+ __decorateParam(3, ILogService)
891
+ ], DocLayoutExecutorService);
892
+
228
893
  //#endregion
229
894
  //#region src/services/doc-skeleton-manager.service.ts
230
895
  let DocSkeletonManagerService = class DocSkeletonManagerService extends RxDisposable {
231
- constructor(_context, _localeService, _univerInstanceService) {
896
+ constructor(_context, _localeService, _univerInstanceService, _docLayoutExecutorService) {
232
897
  super();
233
898
  this._context = _context;
234
899
  this._localeService = _localeService;
235
900
  this._univerInstanceService = _univerInstanceService;
901
+ this._docLayoutExecutorService = _docLayoutExecutorService;
236
902
  _defineProperty(this, "_skeleton", void 0);
237
903
  _defineProperty(this, "_docViewModel", void 0);
238
904
  _defineProperty(this, "_currentSkeleton$", new BehaviorSubject(null));
@@ -257,12 +923,23 @@ let DocSkeletonManagerService = class DocSkeletonManagerService extends RxDispos
257
923
  getViewModel() {
258
924
  return this._docViewModel;
259
925
  }
926
+ getLayoutPerformanceMetrics() {
927
+ return this._docLayoutExecutorService.getPerformanceMetrics(this._context.unitId);
928
+ }
929
+ supportsIncrementalLayout() {
930
+ var _this$_context$unit$g;
931
+ const documentFlavor = (_this$_context$unit$g = this._context.unit.getSnapshot().documentStyle) === null || _this$_context$unit$g === void 0 ? void 0 : _this$_context$unit$g.documentFlavor;
932
+ return this._docLayoutExecutorService.getExecutor() != null && !isInternalEditorID(this._context.unitId) && !getDocumentLayoutPresentation(this._context.unit) && (documentFlavor === DocumentFlavor.TRADITIONAL || documentFlavor === DocumentFlavor.MODERN);
933
+ }
260
934
  recalculate() {
261
935
  const skeleton = this._skeleton;
262
936
  skeleton.calculate();
937
+ this._publishSkeleton(skeleton);
938
+ return skeleton;
939
+ }
940
+ _publishSkeleton(skeleton) {
263
941
  this._currentSkeletonBefore$.next(skeleton);
264
942
  this._currentSkeleton$.next(skeleton);
265
- return skeleton;
266
943
  }
267
944
  _init() {
268
945
  const documentDataModel = this._context.unit;
@@ -276,7 +953,8 @@ let DocSkeletonManagerService = class DocSkeletonManagerService extends RxDispos
276
953
  this._context.unit = documentDataModel;
277
954
  } else if (!this._docViewModel) this._docViewModel = this._buildDocViewModel(documentDataModel);
278
955
  if (!this._skeleton) this._skeleton = this._buildSkeleton(this._docViewModel);
279
- this.recalculate();
956
+ if (this.supportsIncrementalLayout()) this._publishSkeleton(this._skeleton);
957
+ else this.recalculate();
280
958
  this._currentViewModel$.next(this._docViewModel);
281
959
  }
282
960
  _buildSkeleton(documentViewModel) {
@@ -286,7 +964,11 @@ let DocSkeletonManagerService = class DocSkeletonManagerService extends RxDispos
286
964
  return new DocumentViewModel(documentDataModel);
287
965
  }
288
966
  };
289
- DocSkeletonManagerService = __decorate([__decorateParam(1, Inject(LocaleService)), __decorateParam(2, IUniverInstanceService)], DocSkeletonManagerService);
967
+ DocSkeletonManagerService = __decorate([
968
+ __decorateParam(1, Inject(LocaleService)),
969
+ __decorateParam(2, IUniverInstanceService),
970
+ __decorateParam(3, Inject(DocLayoutExecutorService))
971
+ ], DocSkeletonManagerService);
290
972
 
291
973
  //#endregion
292
974
  //#region src/services/doc-state-emit.service.ts
@@ -306,40 +988,404 @@ var DocStateEmitService = class extends RxDisposable {
306
988
  };
307
989
 
308
990
  //#endregion
309
- //#region src/commands/mutations/core-editing.mutation.ts
310
- const RichTextEditingMutationId = "doc.mutation.rich-text-editing";
991
+ //#region src/commands/mutations/doc-structure-mutation-validation.ts
992
+ const STRUCTURAL_BODY_FIELDS = [
993
+ "paragraphs",
994
+ "sectionBreaks",
995
+ "customBlocks",
996
+ "docxRawCustomBlocks",
997
+ "docxRawBlocks",
998
+ "docxExportExcludedRanges",
999
+ "tables",
1000
+ "columnGroups",
1001
+ "blockRanges"
1002
+ ];
1003
+ const STRUCTURAL_DATA_STREAM_TOKENS = /* @__PURE__ */ new Set([
1004
+ DataStreamTreeTokenType.PARAGRAPH,
1005
+ DataStreamTreeTokenType.SECTION_BREAK,
1006
+ DataStreamTreeTokenType.TABLE_START,
1007
+ DataStreamTreeTokenType.TABLE_ROW_START,
1008
+ DataStreamTreeTokenType.TABLE_CELL_START,
1009
+ DataStreamTreeTokenType.TABLE_CELL_END,
1010
+ DataStreamTreeTokenType.TABLE_ROW_END,
1011
+ DataStreamTreeTokenType.TABLE_END,
1012
+ DataStreamTreeTokenType.COLUMN_GROUP_START,
1013
+ DataStreamTreeTokenType.COLUMN_START,
1014
+ DataStreamTreeTokenType.COLUMN_END,
1015
+ DataStreamTreeTokenType.COLUMN_GROUP_END,
1016
+ DataStreamTreeTokenType.BLOCK_START,
1017
+ DataStreamTreeTokenType.BLOCK_END,
1018
+ DataStreamTreeTokenType.CUSTOM_BLOCK
1019
+ ]);
1020
+ const NON_STRUCTURAL_ROOT_FIELDS = /* @__PURE__ */ new Set([
1021
+ "disabled",
1022
+ "documentStyle",
1023
+ "drawings",
1024
+ "drawingsOrder",
1025
+ "lists",
1026
+ "locale",
1027
+ "resources",
1028
+ "rev",
1029
+ "settings",
1030
+ "styles",
1031
+ "tableSource",
1032
+ "title"
1033
+ ]);
1034
+ function isRecord$3(value) {
1035
+ return typeof value === "object" && value != null && !Array.isArray(value);
1036
+ }
1037
+ function containsStructuralToken(dataStream) {
1038
+ for (let index = 0; index < dataStream.length; index++) if (STRUCTURAL_DATA_STREAM_TOKENS.has(dataStream[index])) return true;
1039
+ return false;
1040
+ }
1041
+ function isStructurePreservingBody(value) {
1042
+ if (!isRecord$3(value)) return false;
1043
+ const dataStream = value.dataStream;
1044
+ if (dataStream != null && (typeof dataStream !== "string" || containsStructuralToken(dataStream))) return false;
1045
+ return STRUCTURAL_BODY_FIELDS.every((field) => {
1046
+ const metadata = value[field];
1047
+ return metadata == null || Array.isArray(metadata) && metadata.length === 0;
1048
+ });
1049
+ }
1050
+ function isStructurePreservingTextXEdit(value) {
1051
+ if (!Array.isArray(value)) return false;
1052
+ return value.every((action) => {
1053
+ if (!isRecord$3(action) || typeof action.len !== "number" || !Number.isFinite(action.len)) return false;
1054
+ if (action.t === TextXActionType.DELETE) return true;
1055
+ if (action.t === TextXActionType.INSERT) return isStructurePreservingBody(action.body);
1056
+ return action.t === TextXActionType.RETAIN && (action.body == null || isStructurePreservingBody(action.body));
1057
+ });
1058
+ }
1059
+ function pathsEqual(left, right) {
1060
+ return left.length === right.length && left.every((item, index) => item === right[index]);
1061
+ }
1062
+ function isStructurePreservingJSONXEdit(actions, expectedPath) {
1063
+ const cursor = JSON1.type.readCursor(actions);
1064
+ let hasComponent = false;
1065
+ let isStructurePreserving = true;
1066
+ cursor.traverse(null, (component) => {
1067
+ if (!isStructurePreserving) return;
1068
+ const path = cursor.getPath();
1069
+ hasComponent = true;
1070
+ if (NON_STRUCTURAL_ROOT_FIELDS.has(String(path[0]))) return;
1071
+ const componentKeys = Object.keys(component);
1072
+ if (component.et !== TextX.id || componentKeys.some((key) => key !== "et" && key !== "e") || !pathsEqual(path, expectedPath)) {
1073
+ isStructurePreserving = false;
1074
+ return;
1075
+ }
1076
+ const edit = component.e;
1077
+ isStructurePreserving = isStructurePreservingTextXEdit(edit);
1078
+ });
1079
+ return hasComponent && isStructurePreserving;
1080
+ }
311
1081
  function getSegmentType(documentDataModel, segmentId) {
1082
+ var _documentDataModel$ge;
312
1083
  if (!segmentId) return "body";
313
1084
  const { headers, footers } = documentDataModel.getSnapshot();
314
1085
  if (headers === null || headers === void 0 ? void 0 : headers[segmentId]) return "header";
315
1086
  if (footers === null || footers === void 0 ? void 0 : footers[segmentId]) return "footer";
1087
+ if ((_documentDataModel$ge = documentDataModel.getSnapshot().notes) === null || _documentDataModel$ge === void 0 ? void 0 : _documentDataModel$ge[segmentId]) return "note";
316
1088
  return "body";
317
1089
  }
318
- function assertValidDocBodyStructure(documentDataModel, segmentId) {
1090
+ function getSnapshotBody(snapshot, segmentId) {
1091
+ var _snapshot$headers, _snapshot$footers, _snapshot$notes;
1092
+ if (!segmentId) return snapshot.body;
1093
+ return ((_snapshot$headers = snapshot.headers) === null || _snapshot$headers === void 0 || (_snapshot$headers = _snapshot$headers[segmentId]) === null || _snapshot$headers === void 0 ? void 0 : _snapshot$headers.body) ?? ((_snapshot$footers = snapshot.footers) === null || _snapshot$footers === void 0 || (_snapshot$footers = _snapshot$footers[segmentId]) === null || _snapshot$footers === void 0 ? void 0 : _snapshot$footers.body) ?? ((_snapshot$notes = snapshot.notes) === null || _snapshot$notes === void 0 || (_snapshot$notes = _snapshot$notes[segmentId]) === null || _snapshot$notes === void 0 ? void 0 : _snapshot$notes.body);
1094
+ }
1095
+ function containsTextXEdit(actions) {
1096
+ const cursor = JSON1.type.readCursor(actions);
1097
+ let found = false;
1098
+ cursor.traverse(null, (component) => {
1099
+ if (component.et === TextX.id) found = true;
1100
+ });
1101
+ return found;
1102
+ }
1103
+ function subtractBaselineIssues(issues, baseline) {
1104
+ const counts = /* @__PURE__ */ new Map();
1105
+ baseline.forEach((issue) => counts.set(issue.code, (counts.get(issue.code) ?? 0) + 1));
1106
+ return issues.filter((issue) => {
1107
+ const count = counts.get(issue.code) ?? 0;
1108
+ if (count === 0) return true;
1109
+ counts.set(issue.code, count - 1);
1110
+ return false;
1111
+ });
1112
+ }
1113
+ function assertValidDocBodyStructure(documentDataModel, segmentId, undoActions) {
1114
+ var _documentDataModel$ge2;
319
1115
  const segmentModel = documentDataModel.getSelfOrHeaderFooterModel(segmentId);
320
1116
  const body = segmentModel === null || segmentModel === void 0 ? void 0 : segmentModel.getBody();
321
1117
  if (!body) return;
322
1118
  const segmentType = getSegmentType(documentDataModel, segmentId);
323
- const issues = validateDocBodyStructure(body, {
1119
+ const context = {
1120
+ segmentType,
1121
+ segmentId: segmentId || void 0
1122
+ };
1123
+ const note = (_documentDataModel$ge2 = documentDataModel.getSnapshot().notes) === null || _documentDataModel$ge2 === void 0 ? void 0 : _documentDataModel$ge2[segmentId];
1124
+ const issues = note ? validateDocumentStructure({ notes: { [segmentId]: note } }) : validateDocBodyStructure(body, {
324
1125
  segmentType,
325
1126
  segmentId: segmentId || void 0
326
1127
  });
327
1128
  if (!issues.length) return;
328
- const detail = issues.map((issue) => `${issue.code}${issue.index == null ? "" : `@${issue.index}`}`).join(", ");
1129
+ const snapshot = Tools.deepClone(documentDataModel.getSnapshot());
1130
+ const baselineBody = getSnapshotBody(JSONX.apply(snapshot, undoActions), segmentId);
1131
+ const newIssues = baselineBody ? subtractBaselineIssues(issues, validateDocBodyStructure(baselineBody, context)) : issues;
1132
+ if (!newIssues.length) return;
1133
+ const detail = newIssues.map((issue) => `${issue.code}${issue.index == null ? "" : `@${issue.index}`}`).join(", ");
329
1134
  const segmentLabel = segmentId ? `${segmentType} ${segmentId}` : segmentType;
330
1135
  throw new Error(`[DocStructure] ${segmentLabel}: ${detail}`);
331
1136
  }
1137
+ function getLockedSdtContent(body, range) {
1138
+ var _range$properties;
1139
+ if (((_range$properties = range.properties) === null || _range$properties === void 0 ? void 0 : _range$properties.kind) !== "group") return body.dataStream.slice(range.startIndex, range.endIndex + 1);
1140
+ const children = (body.customRanges ?? []).filter((child) => child.rangeType === CustomRangeType.SDT && child.rangeId !== range.rangeId && child.startIndex >= range.startIndex && child.endIndex <= range.endIndex).sort((left, right) => {
1141
+ const position = left.startIndex - right.startIndex || right.endIndex - left.endIndex;
1142
+ if (position || left.rangeId === right.rangeId) return position;
1143
+ return left.rangeId < right.rangeId ? -1 : 1;
1144
+ });
1145
+ const protectedContent = [];
1146
+ let offset = range.startIndex;
1147
+ for (const child of children) {
1148
+ if (child.startIndex < offset) continue;
1149
+ protectedContent.push(body.dataStream.slice(offset, child.startIndex), child.rangeId);
1150
+ offset = child.endIndex + 1;
1151
+ }
1152
+ protectedContent.push(body.dataStream.slice(offset, range.endIndex + 1));
1153
+ return JSON.stringify(protectedContent);
1154
+ }
1155
+ function getRepeatingItemIds(body, section) {
1156
+ const ranges = body.customRanges ?? [];
1157
+ const sectionIndex = ranges.indexOf(section);
1158
+ const nestedSections = ranges.filter((range, index) => {
1159
+ var _range$properties2;
1160
+ return range.rangeType === CustomRangeType.SDT && ((_range$properties2 = range.properties) === null || _range$properties2 === void 0 ? void 0 : _range$properties2.kind) === "repeatingSection" && range.rangeId !== section.rangeId && range.startIndex >= section.startIndex && range.endIndex <= section.endIndex && (range.startIndex > section.startIndex || range.endIndex < section.endIndex || index < sectionIndex);
1161
+ });
1162
+ return ranges.filter((range, index) => {
1163
+ var _range$properties3;
1164
+ return range.rangeType === CustomRangeType.SDT && ((_range$properties3 = range.properties) === null || _range$properties3 === void 0 ? void 0 : _range$properties3.kind) === "repeatingSectionItem" && range.startIndex >= section.startIndex && range.endIndex <= section.endIndex && !nestedSections.some((nested) => nested.startIndex <= range.startIndex && range.endIndex <= nested.endIndex && (nested.startIndex < range.startIndex || range.endIndex < nested.endIndex || index < ranges.indexOf(nested)));
1165
+ }).map((range) => range.rangeId).sort();
1166
+ }
1167
+ function isDocSdtMutationAllowed(documentDataModel, segmentId, actions) {
1168
+ var _previousBody$customR;
1169
+ if (!containsTextXEdit(actions) && isStructurePreservingJSONXEdit(actions, getRichTextEditPath(documentDataModel, segmentId))) return true;
1170
+ const snapshot = documentDataModel.getSnapshot();
1171
+ const previousBody = getSnapshotBody(snapshot, segmentId);
1172
+ const lockedRanges = previousBody === null || previousBody === void 0 || (_previousBody$customR = previousBody.customRanges) === null || _previousBody$customR === void 0 ? void 0 : _previousBody$customR.filter((range) => {
1173
+ var _range$properties4, _range$properties5, _range$properties$rep;
1174
+ return range.rangeType === CustomRangeType.SDT && (((_range$properties4 = range.properties) === null || _range$properties4 === void 0 ? void 0 : _range$properties4.lock) && range.properties.lock !== "unlocked" || ((_range$properties5 = range.properties) === null || _range$properties5 === void 0 ? void 0 : _range$properties5.kind) === "repeatingSection" && ((_range$properties$rep = range.properties.repeatingSection) === null || _range$properties$rep === void 0 ? void 0 : _range$properties$rep.doNotAllowInsertDelete));
1175
+ });
1176
+ if (!previousBody || !(lockedRanges === null || lockedRanges === void 0 ? void 0 : lockedRanges.length)) return true;
1177
+ const candidate = JSONX.apply(Tools.deepClone(snapshot), actions);
1178
+ const currentBody = getSnapshotBody(candidate, segmentId);
1179
+ for (const previous of lockedRanges) {
1180
+ var _previous$properties, _currentBody$customRa, _previous$properties2, _previous$properties$;
1181
+ const lock = (_previous$properties = previous.properties) === null || _previous$properties === void 0 ? void 0 : _previous$properties.lock;
1182
+ const current = currentBody === null || currentBody === void 0 || (_currentBody$customRa = currentBody.customRanges) === null || _currentBody$customRa === void 0 ? void 0 : _currentBody$customRa.find((range) => range.rangeId === previous.rangeId && range.rangeType === CustomRangeType.SDT);
1183
+ const wrapperLocked = lock === "sdtLocked" || lock === "sdtContentLocked";
1184
+ const contentLocked = lock === "contentLocked" || lock === "sdtContentLocked";
1185
+ if (wrapperLocked && !current) return false;
1186
+ if (((_previous$properties2 = previous.properties) === null || _previous$properties2 === void 0 ? void 0 : _previous$properties2.kind) === "repeatingSection" && ((_previous$properties$ = previous.properties.repeatingSection) === null || _previous$properties$ === void 0 ? void 0 : _previous$properties$.doNotAllowInsertDelete) && current && currentBody) {
1187
+ const before = getRepeatingItemIds(previousBody, previous);
1188
+ const after = getRepeatingItemIds(currentBody, current);
1189
+ if (before.length !== after.length || before.some((id, index) => id !== after[index])) return false;
1190
+ }
1191
+ if (contentLocked && current && currentBody) {
1192
+ if (getLockedSdtContent(previousBody, previous) !== getLockedSdtContent(currentBody, current) && !isBoundSdtValueUpdate(snapshot, candidate, previous, current, currentBody)) return false;
1193
+ }
1194
+ }
1195
+ return true;
1196
+ }
1197
+ function isBoundSdtValueUpdate(before, after, previous, current, currentBody) {
1198
+ const key = getSdtBindingKey(previous);
1199
+ const value = getSdtBindingValue(currentBody, current);
1200
+ if (!key || key !== getSdtBindingKey(current) || value === void 0) return false;
1201
+ return [
1202
+ "",
1203
+ ...Object.keys(before.headers ?? {}),
1204
+ ...Object.keys(before.footers ?? {}),
1205
+ ...Object.keys(before.notes ?? {})
1206
+ ].some((segmentId) => {
1207
+ var _previousBody$customR2;
1208
+ const previousBody = getSnapshotBody(before, segmentId);
1209
+ const nextBody = getSnapshotBody(after, segmentId);
1210
+ if (!previousBody || !nextBody) return false;
1211
+ return ((_previousBody$customR2 = previousBody.customRanges) === null || _previousBody$customR2 === void 0 ? void 0 : _previousBody$customR2.some((range) => {
1212
+ var _range$properties6, _range$properties7, _nextBody$customRange;
1213
+ if (range.rangeType !== CustomRangeType.SDT || ((_range$properties6 = range.properties) === null || _range$properties6 === void 0 ? void 0 : _range$properties6.lock) === "contentLocked" || ((_range$properties7 = range.properties) === null || _range$properties7 === void 0 ? void 0 : _range$properties7.lock) === "sdtContentLocked" || getSdtBindingKey(range) !== key) return false;
1214
+ const next = (_nextBody$customRange = nextBody.customRanges) === null || _nextBody$customRange === void 0 ? void 0 : _nextBody$customRange.find((item) => item.rangeType === CustomRangeType.SDT && item.rangeId === range.rangeId);
1215
+ return next != null && getSdtBindingKey(next) === key && getSdtBindingValue(previousBody, range) !== value && getSdtBindingValue(nextBody, next) === value;
1216
+ })) ?? false;
1217
+ });
1218
+ }
1219
+ function validateDocStructureMutation(documentDataModel, segmentId, actions, undoActions, isHistoryReplay = false) {
1220
+ const editPath = getRichTextEditPath(documentDataModel, segmentId);
1221
+ const preservesStructure = isStructurePreservingJSONXEdit(actions, editPath) && isStructurePreservingJSONXEdit(undoActions, editPath);
1222
+ let changesFootnoteStructure = false;
1223
+ const cursor = JSON1.type.readCursor(actions);
1224
+ cursor.traverse(null, (component) => {
1225
+ const path = cursor.getPath();
1226
+ if (path[0] === "notes" && (path.length <= 2 || path[2] !== "body") || path.includes("customRanges")) changesFootnoteStructure = true;
1227
+ if (component.et === TextX.id && Array.isArray(component.e)) for (const action of component.e) {
1228
+ if (isRecord$3(action) && Array.isArray(action.rangeUpdates) && action.rangeUpdates.some((update) => isRecord$3(update) && isRecord$3(update.range) && (update.range.rangeType === CustomRangeType.FOOTNOTE || update.range.rangeType === CustomRangeType.ENDNOTE))) changesFootnoteStructure = true;
1229
+ const ranges = isRecord$3(action) && isRecord$3(action.body) ? action.body.customRanges : void 0;
1230
+ if (Array.isArray(ranges) && ranges.some((range) => isRecord$3(range) && (range.rangeType === CustomRangeType.FOOTNOTE || range.rangeType === CustomRangeType.ENDNOTE))) changesFootnoteStructure = true;
1231
+ }
1232
+ });
1233
+ if (changesFootnoteStructure) {
1234
+ const issues = validateDocumentStructure(documentDataModel.getSnapshot());
1235
+ if (issues.length > 0) throw new Error(`[DocStructure] ${issues.map((issue) => `${issue.code}@${issue.index ?? issue.segmentId ?? ""}`).join(", ")}`);
1236
+ } else if (!preservesStructure && !isHistoryReplay) assertValidDocBodyStructure(documentDataModel, segmentId, undoActions);
1237
+ return preservesStructure && !changesFootnoteStructure;
1238
+ }
1239
+
1240
+ //#endregion
1241
+ //#region src/commands/mutations/core-editing.mutation.ts
1242
+ let DocHistoryAction = /* @__PURE__ */ function(DocHistoryAction) {
1243
+ DocHistoryAction["DeleteChart"] = "delete-chart";
1244
+ DocHistoryAction["DeleteDivider"] = "delete-divider";
1245
+ DocHistoryAction["DeleteImage"] = "delete-image";
1246
+ DocHistoryAction["DeleteShape"] = "delete-shape";
1247
+ DocHistoryAction["EditTableCell"] = "edit-table-cell";
1248
+ DocHistoryAction["FormatParagraph"] = "format-paragraph";
1249
+ DocHistoryAction["InsertCustomRange"] = "insert-custom-range";
1250
+ DocHistoryAction["UpdateImage"] = "update-image";
1251
+ DocHistoryAction["UpdatePageLayout"] = "update-page-layout";
1252
+ return DocHistoryAction;
1253
+ }({});
1254
+ function extractDocumentBodyActions(actions, segmentId) {
1255
+ if (!Array.isArray(actions)) return;
1256
+ const bodyActions = actions.indexOf("body") > -1 ? actions : actions.find((action) => Array.isArray(action) && action.indexOf("body") > -1);
1257
+ if (!Array.isArray(bodyActions)) return;
1258
+ const bodyIndex = bodyActions.indexOf("body");
1259
+ if (bodyIndex === -1) return;
1260
+ return (bodyIndex === 0 ? "" : bodyActions[bodyIndex - 1]) === segmentId ? bodyActions.slice(bodyIndex) : void 0;
1261
+ }
1262
+ /**
1263
+ * Transforms document selections through the same JSONX actions applied by a rich-text mutation.
1264
+ * Collaboration and rendering use this shared offset rule so the Main interaction window follows
1265
+ * the transformed local caret before an authoritative background layout is published.
1266
+ */
1267
+ function transformDocumentTextRanges(actions, textRanges, priority = "right") {
1268
+ if (textRanges.length === 0) return [];
1269
+ const bodyActions = extractDocumentBodyActions(actions, textRanges[0].segmentId ?? "");
1270
+ if (bodyActions == null) return textRanges;
1271
+ return textRanges.map((textRange) => {
1272
+ const startOffset = JSONX.transformPosition(bodyActions, textRange.startOffset, priority);
1273
+ const endOffset = JSONX.transformPosition(bodyActions, textRange.endOffset, priority);
1274
+ return {
1275
+ ...textRange,
1276
+ startOffset,
1277
+ endOffset,
1278
+ collapsed: startOffset === endOffset
1279
+ };
1280
+ });
1281
+ }
1282
+ function canRemoveFootnoteReference(actions, references) {
1283
+ let canRemove = false;
1284
+ const cursor = JSON1.type.readCursor(actions);
1285
+ cursor.traverse(null, (component) => {
1286
+ const path = cursor.getPath();
1287
+ if (path.length === 0) canRemove = true;
1288
+ if (canRemove || path[0] !== "body") return;
1289
+ if (path.length === 1 && component.et === TextX.id && Array.isArray(component.e)) {
1290
+ let offset = 0;
1291
+ for (const action of component.e) {
1292
+ var _action$body, _action$rangeUpdates;
1293
+ if (action.t === TextXActionType.DELETE && references.some((reference) => offset <= reference.endIndex && offset + action.len > reference.startIndex)) {
1294
+ canRemove = true;
1295
+ return;
1296
+ }
1297
+ if (action.t === TextXActionType.RETAIN && (((_action$body = action.body) === null || _action$body === void 0 ? void 0 : _action$body.customRanges) != null || ((_action$rangeUpdates = action.rangeUpdates) === null || _action$rangeUpdates === void 0 ? void 0 : _action$rangeUpdates.some((update) => references.some((reference) => reference.rangeId === update.rangeId))))) {
1298
+ canRemove = true;
1299
+ return;
1300
+ }
1301
+ if (action.t !== TextXActionType.INSERT) offset += action.len;
1302
+ }
1303
+ } else if (path.length === 1 || path[1] === "customRanges" || path[1] === "dataStream") canRemove = true;
1304
+ });
1305
+ return canRemove;
1306
+ }
1307
+ function includeFootnoteCleanup(before, actions) {
1308
+ var _before$body, _after$body;
1309
+ if (!before.notes) return actions;
1310
+ const references = ((_before$body = before.body) === null || _before$body === void 0 || (_before$body = _before$body.customRanges) === null || _before$body === void 0 ? void 0 : _before$body.filter((range) => range.rangeType === CustomRangeType.FOOTNOTE || range.rangeType === CustomRangeType.ENDNOTE)) ?? [];
1311
+ if (references.length === 0 || !canRemoveFootnoteReference(actions, references)) return actions;
1312
+ const after = JSONX.apply(Tools.deepClone(before), actions);
1313
+ const remaining = new Set((_after$body = after.body) === null || _after$body === void 0 || (_after$body = _after$body.customRanges) === null || _after$body === void 0 ? void 0 : _after$body.filter((range) => range.rangeType === CustomRangeType.FOOTNOTE || range.rangeType === CustomRangeType.ENDNOTE).map((range) => {
1314
+ var _range$properties;
1315
+ return (_range$properties = range.properties) === null || _range$properties === void 0 ? void 0 : _range$properties.noteId;
1316
+ }));
1317
+ let cleanup = null;
1318
+ const previousIds = new Set(references.map((reference) => {
1319
+ var _reference$properties;
1320
+ return (_reference$properties = reference.properties) === null || _reference$properties === void 0 ? void 0 : _reference$properties.noteId;
1321
+ }));
1322
+ for (const id of previousIds) {
1323
+ var _after$notes;
1324
+ if (typeof id === "string" && !remaining.has(id) && ((_after$notes = after.notes) === null || _after$notes === void 0 ? void 0 : _after$notes[id])) cleanup = JSONX.compose(cleanup, JSONX.getInstance().removeOp(["notes", id], after.notes[id]));
1325
+ }
1326
+ return JSONX.isNoop(cleanup) ? actions : JSONX.compose(actions, cleanup);
1327
+ }
1328
+ function applyValidatedDocumentActions(documentDataModel, segmentId, actions, isHistoryReplay = false) {
1329
+ const before = documentDataModel.getSnapshot();
1330
+ const appliedActions = includeFootnoteCleanup(before, actions);
1331
+ const undoActions = JSONX.invertWithDoc(appliedActions, before);
1332
+ documentDataModel.apply(appliedActions);
1333
+ try {
1334
+ return {
1335
+ actions: appliedActions,
1336
+ undoActions,
1337
+ preservesStructure: validateDocStructureMutation(documentDataModel, segmentId, appliedActions, undoActions, isHistoryReplay)
1338
+ };
1339
+ } catch (error) {
1340
+ documentDataModel.apply(undoActions);
1341
+ throw error;
1342
+ }
1343
+ }
1344
+ function resetDocumentViewModel(documentViewModel, documentDataModel, segmentId, actions, preservesStructure) {
1345
+ var _documentViewModel$re, _documentViewModel$re2;
1346
+ if (documentViewModel == null) return;
1347
+ if (!(segmentId === "" && preservesStructure && (((_documentViewModel$re = documentViewModel.resetByValidatedTextMutation) === null || _documentViewModel$re === void 0 ? void 0 : _documentViewModel$re.call(documentViewModel, documentDataModel, actions)) || ((_documentViewModel$re2 = documentViewModel.resetByValidatedMetadataMutation) === null || _documentViewModel$re2 === void 0 ? void 0 : _documentViewModel$re2.call(documentViewModel, documentDataModel, actions))))) documentViewModel.reset(documentDataModel);
1348
+ }
1349
+ function scheduleDocumentSelectionUpdate(selectionManager, params, isSync) {
1350
+ const { unitId, textRanges, trigger, noNeedSetTextRange, isEditing = true } = params;
1351
+ if (noNeedSetTextRange || textRanges == null || textRanges.length === 0 || trigger == null || isSync) return;
1352
+ const selectionTarget = {
1353
+ unitId,
1354
+ subUnitId: unitId
1355
+ };
1356
+ const currentSelection = selectionManager.getSelectionInfo(selectionTarget);
1357
+ const logicalTextRanges = textRanges.map((textRange, index) => ({
1358
+ ...textRange,
1359
+ segmentId: textRange.segmentId ?? params.segmentId ?? "",
1360
+ collapsed: textRange.startOffset === textRange.endOffset,
1361
+ isActive: index === textRanges.length - 1
1362
+ }));
1363
+ if (currentSelection != null) selectionManager.replaceSelectionInfoWithoutRefresh({
1364
+ ...currentSelection,
1365
+ segmentId: logicalTextRanges[logicalTextRanges.length - 1].segmentId,
1366
+ textRanges: logicalTextRanges,
1367
+ rectRanges: [],
1368
+ isEditing,
1369
+ options: params.options
1370
+ }, selectionTarget);
1371
+ const updatedSelection = selectionManager.getSelectionInfo(selectionTarget);
1372
+ queueMicrotask(() => {
1373
+ if (selectionManager.getSelectionInfo(selectionTarget) !== updatedSelection) return;
1374
+ if (updatedSelection == null) selectionManager.replaceDocRanges(logicalTextRanges, selectionTarget, isEditing, params.options);
1375
+ else selectionManager.refreshSelection(selectionTarget, isEditing);
1376
+ });
1377
+ }
332
1378
  /**
333
1379
  * The core mutator to change rich text actions. The execution result would be undo mutation params. Could be directly
334
1380
  * send to undo redo service (will be used by the triggering command).
335
1381
  */
336
1382
  const RichTextEditingMutation = {
337
- id: RichTextEditingMutationId,
1383
+ id: RICH_TEXT_EDITING_MUTATION_ID,
338
1384
  type: CommandType.MUTATION,
339
1385
  handler: (accessor, params, options) => {
340
1386
  var _renderManagerService;
341
- const { unitId, segmentId = "", actions, textRanges, prevTextRanges, trigger, noHistory, isCompositionEnd, noNeedSetTextRange, debounce, isEditing = true, isSync: paramsIsSync, syncer } = params;
342
- const isSync = paramsIsSync || (options === null || options === void 0 ? void 0 : options.fromCollab) || (options === null || options === void 0 ? void 0 : options.fromChangeset);
1387
+ const { unitId, segmentId = "", actions, textRanges, prevTextRanges, trigger, noHistory, isCompositionEnd, debounce, isEditing = true, isSync: paramsIsSync, syncer } = params;
1388
+ const isSync = Boolean(paramsIsSync || (options === null || options === void 0 ? void 0 : options.fromCollab) || (options === null || options === void 0 ? void 0 : options.fromChangeset));
343
1389
  const univerInstanceService = accessor.get(IUniverInstanceService);
344
1390
  const renderManagerService = accessor.get(IRenderManagerService);
345
1391
  const docStateEmitService = accessor.get(DocStateEmitService);
@@ -355,35 +1401,46 @@ const RichTextEditingMutation = {
355
1401
  actions: [],
356
1402
  textRanges: docRanges
357
1403
  };
358
- const undoActions = JSONX.invertWithDoc(actions, documentDataModel.getSnapshot());
359
- documentDataModel.apply(actions);
360
- try {
361
- assertValidDocBodyStructure(documentDataModel, segmentId);
1404
+ const isHistoryReplay = trigger === UndoCommandId || trigger === RedoCommandId;
1405
+ if (!isHistoryReplay && !isDocSdtMutationAllowed(documentDataModel, segmentId, actions)) {
1406
+ params.actions = [];
1407
+ return {
1408
+ unitId,
1409
+ actions: [],
1410
+ textRanges: docRanges
1411
+ };
1412
+ }
1413
+ const presentation = getDocumentLayoutPresentation(documentDataModel);
1414
+ const previousLayoutState = presentation === null || presentation === void 0 ? void 0 : presentation.captureState();
1415
+ const { actions: appliedActions, undoActions, preservesStructure } = applyValidatedDocumentActions(documentDataModel, segmentId, actions, isHistoryReplay);
1416
+ if (presentation) try {
1417
+ if (params.layoutState !== void 0) presentation.restoreState(params.layoutState);
1418
+ else presentation.applyActions(appliedActions, documentDataModel.getSnapshot());
1419
+ params.layoutState = presentation.captureState();
362
1420
  } catch (error) {
363
1421
  documentDataModel.apply(undoActions);
1422
+ presentation.restoreState(previousLayoutState);
364
1423
  throw error;
365
1424
  }
366
- documentViewModel === null || documentViewModel === void 0 || documentViewModel.reset(documentDataModel);
367
- if (!noNeedSetTextRange && textRanges && trigger != null && !isSync) queueMicrotask(() => {
368
- docSelectionManagerService.replaceDocRanges(textRanges, {
369
- unitId,
370
- subUnitId: unitId
371
- }, isEditing, params.options);
372
- });
1425
+ params.actions = appliedActions;
1426
+ resetDocumentViewModel(documentViewModel, documentDataModel, segmentId, appliedActions, preservesStructure);
1427
+ scheduleDocumentSelectionUpdate(docSelectionManagerService, params, isSync);
373
1428
  const changeState = {
374
- commandId: RichTextEditingMutationId,
1429
+ commandId: RICH_TEXT_EDITING_MUTATION_ID,
375
1430
  unitId,
376
1431
  segmentId,
377
1432
  trigger,
378
1433
  noHistory,
379
1434
  debounce,
380
1435
  redoState: {
381
- actions,
1436
+ ...presentation ? { layoutState: params.layoutState } : {},
1437
+ actions: appliedActions,
382
1438
  textRanges,
383
1439
  options: params.options,
384
1440
  isEditing
385
1441
  },
386
1442
  undoState: {
1443
+ ...presentation ? { layoutState: previousLayoutState } : {},
387
1444
  actions: undoActions,
388
1445
  textRanges: prevTextRanges ?? docRanges,
389
1446
  options: selectionInfo === null || selectionInfo === void 0 ? void 0 : selectionInfo.options,
@@ -397,6 +1454,7 @@ const RichTextEditingMutation = {
397
1454
  return {
398
1455
  unitId,
399
1456
  actions: undoActions,
1457
+ ...presentation ? { layoutState: previousLayoutState } : {},
400
1458
  textRanges: docRanges
401
1459
  };
402
1460
  }
@@ -413,59 +1471,63 @@ const InsertTextCommand = {
413
1471
  handler: (accessor, params) => {
414
1472
  var _docDataModel$getSelf;
415
1473
  const commandService = accessor.get(ICommandService);
416
- const { range, segmentId, body, unitId, cursorOffset } = params;
1474
+ const { range, segmentId, body, unitId, cursorOffset, debounce = true, textRanges, noNeedSetTextRange, isEditing } = params;
417
1475
  const docSelectionManagerService = accessor.get(DocSelectionManagerService);
418
1476
  const docDataModel = accessor.get(IUniverInstanceService).getUnit(unitId, UniverInstanceType.UNIVER_DOC);
419
1477
  if (docDataModel == null) return false;
420
1478
  const activeRange = docSelectionManagerService.getActiveTextRange();
421
- const rangeSegmentId = "segmentId" in range ? range.segmentId : void 0;
1479
+ const rangeSegmentId = "segmentId" in range && typeof range.segmentId === "string" ? range.segmentId : void 0;
422
1480
  const targetSegmentId = segmentId ?? rangeSegmentId ?? (activeRange === null || activeRange === void 0 ? void 0 : activeRange.segmentId) ?? "";
423
1481
  const originBody = (_docDataModel$getSelf = docDataModel.getSelfOrHeaderFooterModel(targetSegmentId)) === null || _docDataModel$getSelf === void 0 ? void 0 : _docDataModel$getSelf.getBody();
424
1482
  if (originBody == null) return false;
425
1483
  const { startOffset, collapsed } = range;
426
1484
  const cursorMove = cursorOffset ?? body.dataStream.length;
427
- const textRanges = [{
1485
+ const mutationTextRanges = textRanges ?? [{
428
1486
  startOffset: startOffset + cursorMove,
429
1487
  endOffset: startOffset + cursorMove,
430
- style: activeRange === null || activeRange === void 0 ? void 0 : activeRange.style,
431
1488
  collapsed
432
1489
  }];
433
1490
  const doMutation = {
434
1491
  id: RichTextEditingMutation.id,
435
1492
  params: {
436
1493
  unitId,
1494
+ segmentId: targetSegmentId,
437
1495
  actions: [],
438
- textRanges,
439
- debounce: true
1496
+ textRanges: mutationTextRanges,
1497
+ debounce,
1498
+ noNeedSetTextRange,
1499
+ isEditing
440
1500
  }
441
1501
  };
442
- const textX = new TextX();
443
- const jsonX = JSONX.getInstance();
444
- if (collapsed) {
445
- if (startOffset > 0) textX.push({
446
- t: TextXActionType.RETAIN,
447
- len: startOffset
448
- });
449
- textX.push({
450
- t: TextXActionType.INSERT,
451
- body,
452
- len: body.dataStream.length
453
- });
454
- } else {
455
- const dos = BuildTextUtils.selection.delete([range], originBody, 0, body);
456
- textX.push(...dos);
457
- }
458
- doMutation.params.textRanges = [{
459
- startOffset: startOffset + cursorMove,
460
- endOffset: startOffset + cursorMove,
461
- collapsed
462
- }];
463
- const path = getRichTextEditPath(docDataModel, segmentId);
464
- doMutation.params.actions = jsonX.editOp(textX.serialize(), path);
1502
+ doMutation.params.actions = buildInsertTextActions(docDataModel, originBody, range, body, targetSegmentId);
465
1503
  const result = commandService.syncExecuteCommand(doMutation.id, doMutation.params);
466
1504
  return Boolean(result);
467
1505
  }
468
1506
  };
1507
+ function buildInsertTextActions(documentDataModel, originBody, range, insertBody, segmentId) {
1508
+ const { startOffset, endOffset, collapsed } = range;
1509
+ const textX = new TextX();
1510
+ if (collapsed) {
1511
+ if (startOffset > 0) textX.push({
1512
+ t: TextXActionType.RETAIN,
1513
+ len: startOffset
1514
+ });
1515
+ textX.push({
1516
+ t: TextXActionType.INSERT,
1517
+ body: insertBody,
1518
+ len: insertBody.dataStream.length
1519
+ });
1520
+ } else textX.push(...BuildTextUtils.selection.delete([range], originBody, 0, insertBody));
1521
+ const textActions = JSONX.getInstance().editOp(textX.serialize(), getRichTextEditPath(documentDataModel, segmentId));
1522
+ return collapsed ? textActions : appendRemovedDrawingActions(textActions, documentDataModel, originBody, startOffset, endOffset, segmentId);
1523
+ }
1524
+ function appendRemovedDrawingActions(textActions, documentDataModel, body, startOffset, endOffset, segmentId) {
1525
+ return [textActions, ...BuildTextUtils.drawing.remove(documentDataModel.getSnapshot(), [{
1526
+ startOffset,
1527
+ endOffset,
1528
+ collapsed: false
1529
+ }], body, segmentId)].reduce((accumulator, action) => JSONX.compose(accumulator, action));
1530
+ }
469
1531
  /**
470
1532
  * The command to delete text, mainly used in BACKSPACE and DELETE when collapsed is true. ONLY handle collapsed range!!!
471
1533
  */
@@ -492,6 +1554,7 @@ const DeleteTextCommand = {
492
1554
  id: RichTextEditingMutation.id,
493
1555
  params: {
494
1556
  unitId,
1557
+ segmentId,
495
1558
  actions: [],
496
1559
  textRanges: [{
497
1560
  startOffset: start,
@@ -508,9 +1571,9 @@ const DeleteTextCommand = {
508
1571
  startOffset: start,
509
1572
  endOffset: end + 1,
510
1573
  collapsed: false
511
- }], body));
1574
+ }], body, 0, null, true, docDataModel.getDocumentStyle().documentFlavor));
512
1575
  const path = getRichTextEditPath(docDataModel, segmentId);
513
- doMutation.params.actions = jsonX.editOp(textX.serialize(), path);
1576
+ doMutation.params.actions = appendRemovedDrawingActions(jsonX.editOp(textX.serialize(), path), docDataModel, body, start, end + 1, segmentId);
514
1577
  const result = commandService.syncExecuteCommand(doMutation.id, doMutation.params);
515
1578
  return Boolean(result);
516
1579
  }
@@ -789,6 +1852,235 @@ const SetDocumentNameCommand = {
789
1852
  }
790
1853
  };
791
1854
 
1855
+ //#endregion
1856
+ //#region src/services/permission/permission-point/document/comment.ts
1857
+ var DocumentCommentPermission = class {
1858
+ constructor(unitId) {
1859
+ this.unitId = unitId;
1860
+ _defineProperty(this, "type", UnitObject.Document);
1861
+ _defineProperty(this, "subType", UnitAction.Comment);
1862
+ _defineProperty(this, "status", PermissionStatus.INIT);
1863
+ _defineProperty(this, "id", void 0);
1864
+ _defineProperty(this, "value", true);
1865
+ this.id = `${this.type}.${this.subType}_${unitId}`;
1866
+ }
1867
+ };
1868
+
1869
+ //#endregion
1870
+ //#region src/services/permission/permission-point/document/copy.ts
1871
+ var DocumentCopyPermission = class {
1872
+ constructor(unitId) {
1873
+ this.unitId = unitId;
1874
+ _defineProperty(this, "type", UnitObject.Document);
1875
+ _defineProperty(this, "subType", UnitAction.Copy);
1876
+ _defineProperty(this, "status", PermissionStatus.INIT);
1877
+ _defineProperty(this, "id", void 0);
1878
+ _defineProperty(this, "value", true);
1879
+ this.id = `${this.type}.${this.subType}_${unitId}`;
1880
+ }
1881
+ };
1882
+
1883
+ //#endregion
1884
+ //#region src/services/permission/permission-point/document/editable.ts
1885
+ var DocumentEditablePermission = class {
1886
+ constructor(unitId) {
1887
+ this.unitId = unitId;
1888
+ _defineProperty(this, "type", UnitObject.Document);
1889
+ _defineProperty(this, "subType", UnitAction.Edit);
1890
+ _defineProperty(this, "status", PermissionStatus.INIT);
1891
+ _defineProperty(this, "id", void 0);
1892
+ _defineProperty(this, "value", true);
1893
+ this.id = `${this.type}.${this.subType}_${unitId}`;
1894
+ }
1895
+ };
1896
+
1897
+ //#endregion
1898
+ //#region src/services/permission/permission-point/document/export.ts
1899
+ var DocumentExportPermission = class {
1900
+ constructor(unitId) {
1901
+ this.unitId = unitId;
1902
+ _defineProperty(this, "type", UnitObject.Document);
1903
+ _defineProperty(this, "subType", UnitAction.Export);
1904
+ _defineProperty(this, "status", PermissionStatus.INIT);
1905
+ _defineProperty(this, "id", void 0);
1906
+ _defineProperty(this, "value", true);
1907
+ this.id = `${this.type}.${this.subType}_${unitId}`;
1908
+ }
1909
+ };
1910
+
1911
+ //#endregion
1912
+ //#region src/services/permission/permission-point/document/print.ts
1913
+ var DocumentPrintPermission = class {
1914
+ constructor(unitId) {
1915
+ this.unitId = unitId;
1916
+ _defineProperty(this, "type", UnitObject.Document);
1917
+ _defineProperty(this, "subType", UnitAction.Print);
1918
+ _defineProperty(this, "status", PermissionStatus.INIT);
1919
+ _defineProperty(this, "id", void 0);
1920
+ _defineProperty(this, "value", true);
1921
+ this.id = `${this.type}.${this.subType}_${unitId}`;
1922
+ }
1923
+ };
1924
+
1925
+ //#endregion
1926
+ //#region src/services/permission/permission-point/entity/edit.ts
1927
+ var DocumentEntityEditPermission = class {
1928
+ constructor(unitId, objectId) {
1929
+ this.unitId = unitId;
1930
+ this.objectId = objectId;
1931
+ _defineProperty(this, "type", UnitObject.DocumentEntity);
1932
+ _defineProperty(this, "subType", UnitAction.Edit);
1933
+ _defineProperty(this, "status", PermissionStatus.INIT);
1934
+ _defineProperty(this, "id", void 0);
1935
+ _defineProperty(this, "value", true);
1936
+ this.id = `${this.type}.${this.subType}_${unitId}_${objectId}`;
1937
+ }
1938
+ };
1939
+
1940
+ //#endregion
1941
+ //#region src/services/permission/permission-point/paragraph/edit.ts
1942
+ var DocumentParagraphEditPermission = class {
1943
+ constructor(unitId, objectId) {
1944
+ this.unitId = unitId;
1945
+ this.objectId = objectId;
1946
+ _defineProperty(this, "type", UnitObject.DocumentParagraph);
1947
+ _defineProperty(this, "subType", UnitAction.Edit);
1948
+ _defineProperty(this, "status", PermissionStatus.INIT);
1949
+ _defineProperty(this, "id", void 0);
1950
+ _defineProperty(this, "value", true);
1951
+ this.id = `${this.type}.${this.subType}_${unitId}_${objectId}`;
1952
+ }
1953
+ };
1954
+
1955
+ //#endregion
1956
+ //#region src/services/permission/permission-point/section/edit.ts
1957
+ var DocumentSectionEditPermission = class {
1958
+ constructor(unitId, objectId) {
1959
+ this.unitId = unitId;
1960
+ this.objectId = objectId;
1961
+ _defineProperty(this, "type", UnitObject.DocumentSection);
1962
+ _defineProperty(this, "subType", UnitAction.Edit);
1963
+ _defineProperty(this, "status", PermissionStatus.INIT);
1964
+ _defineProperty(this, "id", void 0);
1965
+ _defineProperty(this, "value", true);
1966
+ this.id = `${this.type}.${this.subType}_${unitId}_${objectId}`;
1967
+ }
1968
+ };
1969
+
1970
+ //#endregion
1971
+ //#region src/services/permission/document-permission.ts
1972
+ const DOCUMENT_UNIT_PERMISSION_ACTIONS = [
1973
+ UnitAction.Edit,
1974
+ UnitAction.Copy,
1975
+ UnitAction.Print,
1976
+ UnitAction.Export,
1977
+ UnitAction.Comment
1978
+ ];
1979
+ const DOCUMENT_PERMISSION_OBJECT_TYPES = /* @__PURE__ */ new Set([
1980
+ UnitObject.Document,
1981
+ UnitObject.DocumentSection,
1982
+ UnitObject.DocumentParagraph,
1983
+ UnitObject.DocumentEntity
1984
+ ]);
1985
+ const DOCUMENT_SECTION_PERMISSION_OBJECT_PREFIX = "section/";
1986
+ const DOCUMENT_PARAGRAPH_PERMISSION_OBJECT_PREFIX = "paragraph/";
1987
+ const DOCUMENT_ENTITY_PERMISSION_OBJECT_PREFIX = "entity/";
1988
+ function getDocumentSectionPermissionObjectId(segmentId, sectionId) {
1989
+ return `${DOCUMENT_SECTION_PERMISSION_OBJECT_PREFIX}${encodeURIComponent(segmentId)}/${encodeURIComponent(sectionId)}`;
1990
+ }
1991
+ function getDocumentParagraphPermissionObjectId(segmentId, paragraphId) {
1992
+ return `${DOCUMENT_PARAGRAPH_PERMISSION_OBJECT_PREFIX}${encodeURIComponent(segmentId)}/${encodeURIComponent(paragraphId)}`;
1993
+ }
1994
+ function getDocumentEntityPermissionObjectId(segmentId, entityType, entityId) {
1995
+ return `${DOCUMENT_ENTITY_PERMISSION_OBJECT_PREFIX}${encodeURIComponent(segmentId)}/${encodeURIComponent(entityType)}/${encodeURIComponent(entityId)}`;
1996
+ }
1997
+ function createDocumentPermissionPoint(unitId, objectId, action) {
1998
+ if (objectId === unitId) switch (action) {
1999
+ case UnitAction.Edit: return new DocumentEditablePermission(unitId);
2000
+ case UnitAction.Copy: return new DocumentCopyPermission(unitId);
2001
+ case UnitAction.Print: return new DocumentPrintPermission(unitId);
2002
+ case UnitAction.Export: return new DocumentExportPermission(unitId);
2003
+ case UnitAction.Comment: return new DocumentCommentPermission(unitId);
2004
+ default: throw new Error(`Unsupported Document permission action: ${action}`);
2005
+ }
2006
+ if (action !== UnitAction.Edit) throw new Error(`Document object permissions only support Edit: ${objectId}`);
2007
+ if (objectId.startsWith(DOCUMENT_SECTION_PERMISSION_OBJECT_PREFIX)) return new DocumentSectionEditPermission(unitId, objectId);
2008
+ if (objectId.startsWith(DOCUMENT_PARAGRAPH_PERMISSION_OBJECT_PREFIX)) return new DocumentParagraphEditPermission(unitId, objectId);
2009
+ if (objectId.startsWith(DOCUMENT_ENTITY_PERMISSION_OBJECT_PREFIX)) return new DocumentEntityEditPermission(unitId, objectId);
2010
+ throw new Error(`Unsupported Document permission object: ${objectId}`);
2011
+ }
2012
+ function getDocumentPermissionValue(permissionService, unitId, objectId, action) {
2013
+ var _permissionService$ge;
2014
+ return ((_permissionService$ge = permissionService.getPermissionPoint(createDocumentPermissionPoint(unitId, objectId, action).id)) === null || _permissionService$ge === void 0 ? void 0 : _permissionService$ge.value) ?? true;
2015
+ }
2016
+ function setDocumentPermissionValue(permissionService, unitId, objectId, action, value) {
2017
+ const point = createDocumentPermissionPoint(unitId, objectId, action);
2018
+ if (!permissionService.getPermissionPoint(point.id)) permissionService.addPermissionPoint(point);
2019
+ permissionService.updatePermissionPoint(point.id, value);
2020
+ }
2021
+ function clearDocumentPermissionValuesForUnit(permissionService, unitId) {
2022
+ permissionService.getAllPermissionPoint().forEach((point$, id) => {
2023
+ point$.subscribe((point) => {
2024
+ if (DOCUMENT_PERMISSION_OBJECT_TYPES.has(point.type) && "unitId" in point && point.unitId === unitId) permissionService.deletePermissionPoint(id);
2025
+ }).unsubscribe();
2026
+ });
2027
+ }
2028
+ function canEditDocumentTargets(permissionService, unitId, objectIds) {
2029
+ if (!getDocumentPermissionValue(permissionService, unitId, unitId, UnitAction.Edit)) return false;
2030
+ for (const objectId of objectIds) if (!getDocumentPermissionValue(permissionService, unitId, objectId, UnitAction.Edit)) return false;
2031
+ return true;
2032
+ }
2033
+
2034
+ //#endregion
2035
+ //#region src/commands/commands/set-document-permission.command.ts
2036
+ const SetDocumentPermissionCommand = {
2037
+ type: CommandType.COMMAND,
2038
+ id: "doc.command.set-permission",
2039
+ async handler(accessor, params) {
2040
+ if (!params || !DOCUMENT_UNIT_PERMISSION_ACTIONS.includes(params.action)) return false;
2041
+ if (params.objectId !== params.unitId && params.action !== UnitAction.Edit) return false;
2042
+ if (params.policy && (![
2043
+ "all",
2044
+ "owner",
2045
+ "members"
2046
+ ].includes(params.policy.edit) || params.policy.strategies.some((strategy) => !DOCUMENT_UNIT_PERMISSION_ACTIONS.includes(strategy.action) || params.objectId !== params.unitId && strategy.action !== UnitAction.Edit))) return false;
2047
+ const point = createDocumentPermissionPoint(params.unitId, params.objectId, params.action);
2048
+ const target = {
2049
+ unitId: params.unitId,
2050
+ objectId: params.objectId,
2051
+ objectType: point.type
2052
+ };
2053
+ const service = accessor.get(ObjectPermissionService);
2054
+ if (params.remove) await service.remove(target);
2055
+ else if (params.policy) await service.save(target, params.policy);
2056
+ else await service.setPoint(target, point, params.value);
2057
+ return true;
2058
+ }
2059
+ };
2060
+
2061
+ //#endregion
2062
+ //#region src/commands/commands/set-document-permissions.command.ts
2063
+ const SetDocumentPermissionsCommand = {
2064
+ type: CommandType.COMMAND,
2065
+ id: "doc.command.set-permissions",
2066
+ async handler(accessor, params) {
2067
+ if (!(params === null || params === void 0 ? void 0 : params.unitId) || !Array.isArray(params.changes) || !params.changes.length || params.changes.some((change) => !(change === null || change === void 0 ? void 0 : change.objectId) || change.objectId === params.unitId || change.policy !== null && (!change.policy || ![
2068
+ "all",
2069
+ "owner",
2070
+ "members"
2071
+ ].includes(change.policy.edit) || !Array.isArray(change.policy.collaborators) || !Array.isArray(change.policy.strategies) || change.policy.strategies.some((strategy) => strategy.action !== UnitAction.Edit)))) throw new Error("Invalid object permission batch.");
2072
+ return accessor.get(ObjectPermissionService).saveMany(params.changes.map((change) => {
2073
+ const point = createDocumentPermissionPoint(params.unitId, change.objectId, UnitAction.Edit);
2074
+ return {
2075
+ unitId: params.unitId,
2076
+ objectId: change.objectId,
2077
+ objectType: point.type,
2078
+ policy: change.policy
2079
+ };
2080
+ }));
2081
+ }
2082
+ };
2083
+
792
2084
  //#endregion
793
2085
  //#region src/utils/sections.ts
794
2086
  /** Returns document-level section breaks, excluding table-cell and modern-column sentinels. */
@@ -1277,6 +2569,56 @@ function executeSectionTextX(commandService, documentDataModel, textX, trigger)
1277
2569
  }));
1278
2570
  }
1279
2571
 
2572
+ //#endregion
2573
+ //#region src/services/permission/document-permission-rule.model.ts
2574
+ /**
2575
+ * Copyright 2023-present DreamNum Co., Ltd.
2576
+ *
2577
+ * Licensed under the Apache License, Version 2.0 (the "License");
2578
+ * you may not use this file except in compliance with the License.
2579
+ * You may obtain a copy of the License at
2580
+ *
2581
+ * http://www.apache.org/licenses/LICENSE-2.0
2582
+ *
2583
+ * Unless required by applicable law or agreed to in writing, software
2584
+ * distributed under the License is distributed on an "AS IS" BASIS,
2585
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2586
+ * See the License for the specific language governing permissions and
2587
+ * limitations under the License.
2588
+ */
2589
+ let DocumentPermissionRuleModel = class DocumentPermissionRuleModel extends ObjectPermissionRuleModel {
2590
+ constructor(resources) {
2591
+ super(resources, "DOC_OBJECT_PERMISSION_PLUGIN", UniverInstanceType.UNIVER_DOC, [
2592
+ UnitObject.DocumentSection,
2593
+ UnitObject.DocumentParagraph,
2594
+ UnitObject.DocumentEntity
2595
+ ]);
2596
+ }
2597
+ };
2598
+ DocumentPermissionRuleModel = __decorate([__decorateParam(0, IResourceManagerService)], DocumentPermissionRuleModel);
2599
+
2600
+ //#endregion
2601
+ //#region src/commands/mutations/set-document-permission-rule.mutation.ts
2602
+ const SetDocumentPermissionRuleMutation = {
2603
+ id: "doc.mutation.set-permission-rule",
2604
+ type: CommandType.MUTATION,
2605
+ handler: (accessor, params) => {
2606
+ if (!params || !accessor.get(IUniverInstanceService).getUnit(params.unitId, UniverInstanceType.UNIVER_DOC)) return false;
2607
+ return accessor.get(DocumentPermissionRuleModel).setRule(params.unitId, params.objectType, params.objectId, params.rule);
2608
+ }
2609
+ };
2610
+
2611
+ //#endregion
2612
+ //#region src/commands/mutations/set-document-permission-rules.mutation.ts
2613
+ const SetDocumentPermissionRulesMutation = {
2614
+ id: "doc.mutation.set-permission-rules",
2615
+ type: CommandType.MUTATION,
2616
+ handler: (accessor, params) => {
2617
+ if (!params || !accessor.get(IUniverInstanceService).getUnit(params.unitId, UniverInstanceType.UNIVER_DOC)) return false;
2618
+ return accessor.get(DocumentPermissionRuleModel).setRules(params.unitId, params.rules);
2619
+ }
2620
+ };
2621
+
1280
2622
  //#endregion
1281
2623
  //#region src/embed-host-anchor.ts
1282
2624
  const EMBED_DOCS_CUSTOM_BLOCK_DEFAULT_COMPONENT_KEY = "UniverEmbedDocsCustomBlock";
@@ -1438,7 +2780,7 @@ function composeActions(actions) {
1438
2780
  //#endregion
1439
2781
  //#region package.json
1440
2782
  var name = "@univerjs/docs";
1441
- var version = "1.0.0-insiders.20260813-7c9aa50";
2783
+ var version = "1.0.0";
1442
2784
 
1443
2785
  //#endregion
1444
2786
  //#region src/config/config.ts
@@ -1458,7 +2800,7 @@ var version = "1.0.0-insiders.20260813-7c9aa50";
1458
2800
  * limitations under the License.
1459
2801
  */
1460
2802
  const DOCS_PLUGIN_CONFIG_KEY = "docs.config";
1461
- const configSymbol = Symbol(DOCS_PLUGIN_CONFIG_KEY);
2803
+ const configSymbol$1 = Symbol(DOCS_PLUGIN_CONFIG_KEY);
1462
2804
  const defaultPluginConfig = {};
1463
2805
 
1464
2806
  //#endregion
@@ -1494,25 +2836,492 @@ let DocCustomRangeController = class DocCustomRangeController extends Disposable
1494
2836
  collapsed: start === end
1495
2837
  };
1496
2838
  }
1497
- return selection;
2839
+ return selection;
2840
+ }
2841
+ _initSelectionChange() {
2842
+ this.disposeWithMe(this._commandService.onCommandExecuted((commandInfo) => {
2843
+ if (commandInfo.id === SetTextSelectionsOperation.id) {
2844
+ const { unitId, ranges, isEditing } = commandInfo.params;
2845
+ const doc = this._univerInstanceService.getUnit(unitId);
2846
+ if (!doc) return;
2847
+ const transformedRanges = ranges.map((range) => this._transformCustomRange(doc, range));
2848
+ if (transformedRanges.some((range, i) => ranges[i] !== range)) this._textSelectionManagerService.replaceDocRanges(transformedRanges, void 0, isEditing);
2849
+ }
2850
+ }));
2851
+ }
2852
+ };
2853
+ DocCustomRangeController = __decorate([
2854
+ __decorateParam(0, ICommandService),
2855
+ __decorateParam(1, Inject(DocSelectionManagerService)),
2856
+ __decorateParam(2, IUniverInstanceService)
2857
+ ], DocCustomRangeController);
2858
+
2859
+ //#endregion
2860
+ //#region src/services/permission/document-permission-resolver.ts
2861
+ const HEADER_FOOTER_REFERENCE_KEYS = [
2862
+ "defaultHeaderId",
2863
+ "defaultFooterId",
2864
+ "firstPageHeaderId",
2865
+ "firstPageFooterId",
2866
+ "evenPageHeaderId",
2867
+ "evenPageFooterId"
2868
+ ];
2869
+ const documentPermissionResolverIndexCache = /* @__PURE__ */ new WeakMap();
2870
+ function getDocumentPermissionResolverIndex(documentDataModel) {
2871
+ var _getMutationRevision, _snapshot$body;
2872
+ const mutationRevision = ((_getMutationRevision = documentDataModel.getMutationRevision) === null || _getMutationRevision === void 0 ? void 0 : _getMutationRevision.call(documentDataModel)) ?? NaN;
2873
+ const cached = documentPermissionResolverIndexCache.get(documentDataModel);
2874
+ if ((cached === null || cached === void 0 ? void 0 : cached.mutationRevision) === mutationRevision) return cached;
2875
+ const snapshot = documentDataModel.getSnapshot();
2876
+ const topLevelSections = snapshot.body ? getTopLevelSectionBreaks(snapshot.body) : [];
2877
+ const drawingSegmentIds = /* @__PURE__ */ new Map();
2878
+ const addDrawingSegments = (body, segmentId) => {
2879
+ for (const block of (body === null || body === void 0 ? void 0 : body.customBlocks) ?? []) if (!drawingSegmentIds.has(block.blockId)) drawingSegmentIds.set(block.blockId, segmentId);
2880
+ };
2881
+ addDrawingSegments(snapshot.body, "");
2882
+ Object.entries(snapshot.headers ?? {}).forEach(([segmentId, header]) => addDrawingSegments(header.body, segmentId));
2883
+ Object.entries(snapshot.footers ?? {}).forEach(([segmentId, footer]) => addDrawingSegments(footer.body, segmentId));
2884
+ Object.entries(snapshot.notes ?? {}).forEach(([segmentId, footnote]) => addDrawingSegments(footnote.body, segmentId));
2885
+ const footnoteReferenceRanges = /* @__PURE__ */ new Map();
2886
+ for (const reference of ((_snapshot$body = snapshot.body) === null || _snapshot$body === void 0 ? void 0 : _snapshot$body.customRanges) ?? []) {
2887
+ var _reference$properties;
2888
+ if ((reference.rangeType === CustomRangeType.FOOTNOTE || reference.rangeType === CustomRangeType.ENDNOTE) && typeof ((_reference$properties = reference.properties) === null || _reference$properties === void 0 ? void 0 : _reference$properties.noteId) === "string") footnoteReferenceRanges.set(reference.properties.noteId, {
2889
+ startOffset: reference.startIndex,
2890
+ endOffset: reference.endIndex + 1
2891
+ });
2892
+ }
2893
+ const index = {
2894
+ drawingSegmentIds,
2895
+ footnoteReferenceRanges,
2896
+ mutationRevision,
2897
+ segments: /* @__PURE__ */ new Map(),
2898
+ topLevelSections
2899
+ };
2900
+ documentPermissionResolverIndexCache.set(documentDataModel, index);
2901
+ return index;
2902
+ }
2903
+ function getDocumentPermissionSegmentIndex(documentDataModel, segmentId, resolverIndex = getDocumentPermissionResolverIndex(documentDataModel)) {
2904
+ var _documentDataModel$ge, _snapshot$notes;
2905
+ const cached = resolverIndex.segments.get(segmentId);
2906
+ if (cached) return cached;
2907
+ const body = ((_documentDataModel$ge = documentDataModel.getSelfOrHeaderFooterModel(segmentId)) === null || _documentDataModel$ge === void 0 ? void 0 : _documentDataModel$ge.getBody()) ?? null;
2908
+ const snapshot = documentDataModel.getSnapshot();
2909
+ const note = (_snapshot$notes = snapshot.notes) === null || _snapshot$notes === void 0 ? void 0 : _snapshot$notes[segmentId];
2910
+ const drawings = (note ? note.drawings : snapshot.drawings) ?? {};
2911
+ const entities = body == null ? [] : [
2912
+ ...(body.tables ?? []).map((item) => ({
2913
+ type: "table",
2914
+ id: item.tableId,
2915
+ startOffset: item.startIndex,
2916
+ endOffset: item.endIndex
2917
+ })),
2918
+ ...(body.customBlocks ?? []).map((item) => ({
2919
+ type: drawings[item.blockId] ? "drawing" : "custom-block",
2920
+ id: item.blockId,
2921
+ startOffset: item.startIndex,
2922
+ endOffset: item.startIndex + 1
2923
+ })),
2924
+ ...(body.blockRanges ?? []).map((item) => ({
2925
+ type: "block-range",
2926
+ id: item.blockId,
2927
+ startOffset: item.startIndex,
2928
+ endOffset: item.endIndex + 1
2929
+ })),
2930
+ ...(body.customRanges ?? []).map((item) => ({
2931
+ type: "custom-range",
2932
+ id: item.rangeId,
2933
+ startOffset: item.startIndex,
2934
+ endOffset: item.endIndex + 1
2935
+ })),
2936
+ ...(body.columnGroups ?? []).map((item) => ({
2937
+ type: "column-group",
2938
+ id: item.columnGroupId,
2939
+ startOffset: item.startIndex,
2940
+ endOffset: item.endIndex + 1
2941
+ }))
2942
+ ];
2943
+ const entityRanges = new Map(entities.map((entity) => [`${entity.type}:${entity.id}`, {
2944
+ startOffset: entity.startOffset,
2945
+ endOffset: entity.endOffset
2946
+ }]));
2947
+ for (const block of (body === null || body === void 0 ? void 0 : body.customBlocks) ?? []) {
2948
+ const range = {
2949
+ startOffset: block.startIndex,
2950
+ endOffset: block.startIndex + 1
2951
+ };
2952
+ entityRanges.set(`custom-block:${block.blockId}`, range);
2953
+ entityRanges.set(`drawing:${block.blockId}`, range);
2954
+ }
2955
+ const segmentIndex = {
2956
+ body,
2957
+ entities,
2958
+ entityRanges,
2959
+ paragraphContentStartOffsets: body == null ? /* @__PURE__ */ new Map() : getParagraphContentStartOffsets(body),
2960
+ paragraphsById: new Map(((body === null || body === void 0 ? void 0 : body.paragraphs) ?? []).filter((paragraph) => paragraph.paragraphId != null).map((paragraph) => [paragraph.paragraphId, paragraph]))
2961
+ };
2962
+ resolverIndex.segments.set(segmentId, segmentIndex);
2963
+ return segmentIndex;
2964
+ }
2965
+ function getDocumentDrawingSegmentId(documentDataModel, drawingId) {
2966
+ return getDocumentPermissionResolverIndex(documentDataModel).drawingSegmentIds.get(drawingId) ?? "";
2967
+ }
2968
+ function getDocumentEditTargetObjectIds(documentDataModel, segmentId, range) {
2969
+ const resolverIndex = getDocumentPermissionResolverIndex(documentDataModel);
2970
+ const segmentIndex = getDocumentPermissionSegmentIndex(documentDataModel, segmentId, resolverIndex);
2971
+ const { body } = segmentIndex;
2972
+ if (!body) return [];
2973
+ return [
2974
+ ...getSectionPermissionObjectIds(documentDataModel, segmentId, range, resolverIndex),
2975
+ ...getParagraphPermissionObjectIds(body, segmentId, range, segmentIndex),
2976
+ ...getEntityPermissionObjectIds(segmentId, range, segmentIndex)
2977
+ ];
2978
+ }
2979
+ function getDocumentEditTargetObjectIdsFromActions(documentDataModel, segmentId, actions) {
2980
+ const result = /* @__PURE__ */ new Set();
2981
+ if (actions == null) return [];
2982
+ const cursor = JSON1.type.readCursor(actions);
2983
+ cursor.traverse(null, () => {
2984
+ const path = cursor.getPath();
2985
+ const edit = cursor.getComponent();
2986
+ const scopedSegment = [
2987
+ "headers",
2988
+ "footers",
2989
+ "notes"
2990
+ ].includes(String(path[0])) && typeof path[1] === "string" ? path[1] : segmentId;
2991
+ if (path[0] === "notes" && typeof path[1] === "string") getSectionPermissionObjectIds(documentDataModel, path[1], {
2992
+ startOffset: 0,
2993
+ endOffset: 0
2994
+ }).forEach((objectId) => result.add(objectId));
2995
+ if (path[0] === "noteSettings") getDocumentPermissionResolverIndex(documentDataModel).topLevelSections.forEach((section) => {
2996
+ result.add(getDocumentSectionPermissionObjectId("", section.sectionId));
2997
+ });
2998
+ if (path[0] === "body" && path[1] === "sectionBreaks" && typeof path[2] === "number" && (path[3] === "noteProperties" || path[3] === "suppressEndnotes")) {
2999
+ var _documentDataModel$ge2;
3000
+ const sectionId = (_documentDataModel$ge2 = documentDataModel.getBody()) === null || _documentDataModel$ge2 === void 0 || (_documentDataModel$ge2 = _documentDataModel$ge2.sectionBreaks) === null || _documentDataModel$ge2 === void 0 || (_documentDataModel$ge2 = _documentDataModel$ge2[path[2]]) === null || _documentDataModel$ge2 === void 0 ? void 0 : _documentDataModel$ge2.sectionId;
3001
+ if (sectionId) result.add(getDocumentSectionPermissionObjectId("", sectionId));
3002
+ }
3003
+ if (path[path.length - 1] !== "body" || (edit === null || edit === void 0 ? void 0 : edit.et) !== TextX.id || !Array.isArray(edit.e) || !edit.e.every(isTextXAction)) return;
3004
+ const textActions = edit.e;
3005
+ let offset = 0;
3006
+ const addRange = (startOffset, endOffset) => {
3007
+ getDocumentEditTargetObjectIds(documentDataModel, scopedSegment, {
3008
+ startOffset,
3009
+ endOffset
3010
+ }).forEach((objectId) => result.add(objectId));
3011
+ };
3012
+ textActions.forEach((action) => {
3013
+ if (action.t === TextXActionType.INSERT) {
3014
+ addRange(offset, offset);
3015
+ return;
3016
+ }
3017
+ if (action.t === TextXActionType.DELETE) {
3018
+ addRange(offset, offset + action.len);
3019
+ offset += action.len;
3020
+ return;
3021
+ }
3022
+ if (action.body !== void 0 || action.oldBody !== void 0 || action.coverType !== void 0) addRange(offset, offset + action.len);
3023
+ offset += action.len;
3024
+ });
3025
+ });
3026
+ getDrawingIdsFromActions(actions).forEach((drawingId) => {
3027
+ var _documentDataModel$ge3, _body$customBlocks;
3028
+ result.add(getDocumentEntityPermissionObjectId(segmentId, "drawing", drawingId));
3029
+ const body = (_documentDataModel$ge3 = documentDataModel.getSelfOrHeaderFooterModel(segmentId)) === null || _documentDataModel$ge3 === void 0 ? void 0 : _documentDataModel$ge3.getBody();
3030
+ const block = body === null || body === void 0 || (_body$customBlocks = body.customBlocks) === null || _body$customBlocks === void 0 ? void 0 : _body$customBlocks.find((item) => item.blockId === drawingId);
3031
+ if (block) getSectionPermissionObjectIds(documentDataModel, segmentId, {
3032
+ startOffset: block.startIndex,
3033
+ endOffset: block.startIndex + 1
3034
+ }).forEach((objectId) => result.add(objectId));
3035
+ });
3036
+ return [...result];
3037
+ }
3038
+ function getDrawingIdsFromActions(actions) {
3039
+ const result = /* @__PURE__ */ new Set();
3040
+ const visit = (value) => {
3041
+ if (Array.isArray(value)) {
3042
+ value.forEach((item, index) => {
3043
+ if (item === "drawings" && typeof value[index + 1] === "string") result.add(value[index + 1]);
3044
+ visit(item);
3045
+ });
3046
+ return;
3047
+ }
3048
+ if (!isRecord$2(value)) return;
3049
+ Object.values(value).forEach(visit);
3050
+ };
3051
+ visit(actions);
3052
+ return [...result];
3053
+ }
3054
+ function isTextXAction(value) {
3055
+ if (!isRecord$2(value) || typeof value.len !== "number") return false;
3056
+ if (value.t === TextXActionType.INSERT) return isRecord$2(value.body);
3057
+ return value.t === TextXActionType.RETAIN || value.t === TextXActionType.DELETE;
3058
+ }
3059
+ function isRecord$2(value) {
3060
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3061
+ }
3062
+ function getDocumentEntityParentPermissionObjectIds(documentDataModel, segmentId, entityType, entityId) {
3063
+ const resolverIndex = getDocumentPermissionResolverIndex(documentDataModel);
3064
+ const range = getDocumentPermissionSegmentIndex(documentDataModel, segmentId, resolverIndex).entityRanges.get(`${entityType}:${entityId}`) ?? null;
3065
+ return range ? getSectionPermissionObjectIds(documentDataModel, segmentId, range, resolverIndex) : [];
3066
+ }
3067
+ function getDocumentParagraphParentPermissionObjectIds(documentDataModel, segmentId, paragraphId) {
3068
+ const resolverIndex = getDocumentPermissionResolverIndex(documentDataModel);
3069
+ const segmentIndex = getDocumentPermissionSegmentIndex(documentDataModel, segmentId, resolverIndex);
3070
+ const { body } = segmentIndex;
3071
+ const paragraph = segmentIndex.paragraphsById.get(paragraphId);
3072
+ if (!body || !paragraph) return [];
3073
+ return getSectionPermissionObjectIds(documentDataModel, segmentId, {
3074
+ startOffset: segmentIndex.paragraphContentStartOffsets.get(paragraph.startIndex) ?? getParagraphContentStartOffset(body, paragraph),
3075
+ endOffset: paragraph.startIndex + 1
3076
+ }, resolverIndex);
3077
+ }
3078
+ function getDocumentSectionPermissionObjectIdsByIds(sectionIds) {
3079
+ return Array.from(sectionIds, (sectionId) => getDocumentSectionPermissionObjectId("", sectionId));
3080
+ }
3081
+ function getDocumentSectionIdsAtOffset(body, offset) {
3082
+ return getSectionsIntersectingRange(getTopLevelSectionBreaks(body), {
3083
+ startOffset: offset,
3084
+ endOffset: offset
3085
+ }).map((section) => section.sectionId);
3086
+ }
3087
+ function getSectionPermissionObjectIds(documentDataModel, segmentId, range, resolverIndex = getDocumentPermissionResolverIndex(documentDataModel)) {
3088
+ if (segmentId) {
3089
+ const referenceRange = resolverIndex.footnoteReferenceRanges.get(segmentId);
3090
+ if (referenceRange) return getDocumentEditTargetObjectIds(documentDataModel, "", referenceRange);
3091
+ return getHeaderFooterOwnerSectionIds(documentDataModel, segmentId, resolverIndex.topLevelSections).map((sectionId) => getDocumentSectionPermissionObjectId("", sectionId));
3092
+ }
3093
+ return getSectionsIntersectingRange(resolverIndex.topLevelSections, range).map((section) => getDocumentSectionPermissionObjectId("", section.sectionId));
3094
+ }
3095
+ function getParagraphPermissionObjectIds(body, segmentId, range, segmentIndex) {
3096
+ const startOffset = Math.min(range.startOffset, range.endOffset);
3097
+ const endOffset = Math.max(range.startOffset, range.endOffset);
3098
+ return (body.paragraphs ?? []).filter((paragraph) => intersectsRange(segmentIndex.paragraphContentStartOffsets.get(paragraph.startIndex) ?? getParagraphContentStartOffset(body, paragraph), paragraph.startIndex + 1, startOffset, endOffset)).flatMap((paragraph) => paragraph.paragraphId ? [getDocumentParagraphPermissionObjectId(segmentId, paragraph.paragraphId)] : []);
3099
+ }
3100
+ function getEntityPermissionObjectIds(segmentId, range, segmentIndex) {
3101
+ const startOffset = Math.min(range.startOffset, range.endOffset);
3102
+ const endOffset = Math.max(range.startOffset, range.endOffset);
3103
+ return segmentIndex.entities.filter((item) => intersectsRange(item.startOffset, item.endOffset, startOffset, endOffset)).map((item) => getDocumentEntityPermissionObjectId(segmentId, item.type, item.id));
3104
+ }
3105
+ function getSectionsIntersectingRange(sections, range) {
3106
+ const startOffset = Math.min(range.startOffset, range.endOffset);
3107
+ const endOffset = Math.max(range.startOffset, range.endOffset);
3108
+ return sections.filter((section, index) => intersectsRange(index === 0 ? 0 : sections[index - 1].startIndex + 1, section.startIndex + 1, startOffset, endOffset));
3109
+ }
3110
+ function intersectsRange(targetStart, targetEnd, rangeStart, rangeEnd) {
3111
+ if (rangeStart === rangeEnd) return rangeStart >= targetStart && rangeStart < targetEnd;
3112
+ return rangeStart < targetEnd && rangeEnd > targetStart;
3113
+ }
3114
+ function getHeaderFooterOwnerSectionIds(documentDataModel, segmentId, sections = getDocumentPermissionResolverIndex(documentDataModel).topLevelSections) {
3115
+ const snapshot = documentDataModel.getSnapshot();
3116
+ const result = /* @__PURE__ */ new Set();
3117
+ sections.forEach((section, sectionIndex) => {
3118
+ if (HEADER_FOOTER_REFERENCE_KEYS.some((key) => resolveSectionHeaderFooterReference(snapshot.documentStyle, sections, sectionIndex, key).segmentId === segmentId)) result.add(section.sectionId);
3119
+ });
3120
+ return [...result];
3121
+ }
3122
+
3123
+ //#endregion
3124
+ //#region src/controllers/doc-permission.controller.ts
3125
+ const NON_EDIT_DOCUMENT_COMMAND_IDS = /* @__PURE__ */ new Set([
3126
+ SetDocumentPermissionCommand.id,
3127
+ SetDocumentPermissionsCommand.id,
3128
+ "doc.command.open-header-footer-panel",
3129
+ "doc.command.close-header-footer",
3130
+ "doc.command.select-all",
3131
+ "doc.command.set-zoom-ratio"
3132
+ ]);
3133
+ const DERIVED_DOCUMENT_MUTATION_IDS = /* @__PURE__ */ new Set(["docs-formula.mutation.set-last-values"]);
3134
+ let DocPermissionController = class DocPermissionController extends Disposable {
3135
+ constructor(_injector, _commandService, _permissionService, _univerInstanceService) {
3136
+ super();
3137
+ this._injector = _injector;
3138
+ this._commandService = _commandService;
3139
+ this._permissionService = _permissionService;
3140
+ this._univerInstanceService = _univerInstanceService;
3141
+ this._univerInstanceService.getAllUnitsForType(UniverInstanceType.UNIVER_DOC).forEach((unit) => this._registerUnitPermissionPoints(unit.getUnitId()));
3142
+ this.disposeWithMe(this._univerInstanceService.getTypeOfUnitAdded$(UniverInstanceType.UNIVER_DOC).subscribe(({ unit }) => this._registerUnitPermissionPoints(unit.getUnitId())));
3143
+ this.disposeWithMe(this._commandService.beforeCommandExecuted((commandInfo, options) => {
3144
+ this._check(commandInfo, options);
3145
+ }));
3146
+ this.disposeWithMe(this._univerInstanceService.getTypeOfUnitDisposed$(UniverInstanceType.UNIVER_DOC).subscribe((unit) => {
3147
+ this._injector.get(ObjectPermissionService).clearUnit(unit.getUnitId());
3148
+ clearDocumentPermissionValuesForUnit(this._permissionService, unit.getUnitId());
3149
+ }));
3150
+ }
3151
+ _registerUnitPermissionPoints(unitId) {
3152
+ DOCUMENT_UNIT_PERMISSION_ACTIONS.forEach((action) => {
3153
+ const point = createDocumentPermissionPoint(unitId, unitId, action);
3154
+ if (!this._permissionService.getPermissionPoint(point.id)) this._permissionService.addPermissionPoint(point);
3155
+ });
3156
+ this._injector.get(ObjectPermissionService).initializeUnit({
3157
+ unitId,
3158
+ objectId: unitId,
3159
+ objectType: UnitObject.Document
3160
+ });
3161
+ }
3162
+ _check(commandInfo, options) {
3163
+ var _this$_univerInstance;
3164
+ if ((options === null || options === void 0 ? void 0 : options.fromCollab) || (options === null || options === void 0 ? void 0 : options.fromChangeset)) return;
3165
+ const unitAction = getDocumentUnitAction(commandInfo.id);
3166
+ const unitId = getUnitId(commandInfo, options) ?? (unitAction ? (_this$_univerInstance = this._univerInstanceService.getCurrentUnitOfType(UniverInstanceType.UNIVER_DOC)) === null || _this$_univerInstance === void 0 ? void 0 : _this$_univerInstance.getUnitId() : void 0);
3167
+ if (!unitId || this._univerInstanceService.getUnitType(unitId) !== UniverInstanceType.UNIVER_DOC) return;
3168
+ if (unitAction) {
3169
+ if (!getDocumentPermissionValue(this._permissionService, unitId, unitId, unitAction)) {
3170
+ this._injector.get(ErrorService).emitPermissionDenied(unitId);
3171
+ throw new CustomCommandExecutionError(`Document ${UnitAction[unitAction]} permission denied.`);
3172
+ }
3173
+ return;
3174
+ }
3175
+ const documentDataModel = this._univerInstanceService.getUnit(unitId, UniverInstanceType.UNIVER_DOC);
3176
+ if (!documentDataModel) return;
3177
+ const targetObjectIds = this._resolveTargetObjectIds(documentDataModel, commandInfo);
3178
+ if (targetObjectIds === null) return;
3179
+ if (!canEditDocumentTargets(this._permissionService, unitId, targetObjectIds)) {
3180
+ this._injector.get(ErrorService).emitPermissionDenied(unitId, targetObjectIds);
3181
+ throw new CustomCommandExecutionError("Document edit permission denied.");
3182
+ }
3183
+ }
3184
+ _resolveTargetObjectIds(documentDataModel, commandInfo) {
3185
+ if (commandInfo.id === RichTextEditingMutation.id) {
3186
+ const params = commandInfo.params;
3187
+ return getDocumentEditTargetObjectIdsFromActions(documentDataModel, params.segmentId ?? "", params.actions);
3188
+ }
3189
+ if (commandInfo.id === DocsRenameMutation.id) return [];
3190
+ if (isDerivedDocumentMutation(commandInfo)) return null;
3191
+ const knownTargets = resolveTextTargets(documentDataModel, commandInfo) ?? resolveSectionTargets(documentDataModel, commandInfo);
3192
+ if (knownTargets) return knownTargets;
3193
+ const entityTargets = resolveEntityTargets(documentDataModel, commandInfo);
3194
+ if (entityTargets.length) return entityTargets;
3195
+ return isDocumentEditCommand(commandInfo.id) || isDocumentDataMutation(commandInfo) ? [] : null;
3196
+ }
3197
+ };
3198
+ DocPermissionController = __decorate([
3199
+ __decorateParam(0, Inject(Injector)),
3200
+ __decorateParam(1, ICommandService),
3201
+ __decorateParam(2, IPermissionService),
3202
+ __decorateParam(3, IUniverInstanceService)
3203
+ ], DocPermissionController);
3204
+ function isDocumentEditCommand(id) {
3205
+ if (NON_EDIT_DOCUMENT_COMMAND_IDS.has(id)) return false;
3206
+ return id.startsWith("doc.command.") || id.startsWith("docs.command.") || id.startsWith("doc.command-") || id.startsWith("doc.table.");
3207
+ }
3208
+ function isDocumentDataMutation(commandInfo) {
3209
+ const { id } = commandInfo;
3210
+ if (isDerivedDocumentMutation(commandInfo)) return false;
3211
+ return id.startsWith("doc.mutation.") || id.startsWith("docs.mutation.") || /^docs-[^.]+\.mutation\./.test(id);
3212
+ }
3213
+ function isDerivedDocumentMutation(commandInfo) {
3214
+ const params = isRecord$1(commandInfo.params) ? commandInfo.params : void 0;
3215
+ return DERIVED_DOCUMENT_MUTATION_IDS.has(commandInfo.id) || commandInfo.id === "doc.mutation.update-shape-data" && !!(params === null || params === void 0 ? void 0 : params.formulaLastValueGuard);
3216
+ }
3217
+ function resolveEntityTargets(documentDataModel, commandInfo) {
3218
+ const params = commandInfo.params;
3219
+ if (!isRecord$1(params)) return [];
3220
+ const result = /* @__PURE__ */ new Set();
3221
+ const add = (segmentId, entityType, entityId) => {
3222
+ result.add(getDocumentEntityPermissionObjectId(segmentId, entityType, entityId));
3223
+ getDocumentEntityParentPermissionObjectIds(documentDataModel, segmentId, entityType, entityId).forEach((objectId) => result.add(objectId));
3224
+ };
3225
+ collectEntityReferences(params).forEach(({ entityType, entityId }) => {
3226
+ const explicitSegmentId = params.segmentId;
3227
+ const segmentId = typeof explicitSegmentId === "string" ? explicitSegmentId : entityType === "drawing" || entityType === "custom-block" ? getDocumentDrawingSegmentId(documentDataModel, entityId) : "";
3228
+ add(segmentId, entityType, entityId);
3229
+ });
3230
+ return [...result];
3231
+ }
3232
+ function collectEntityReferences(value) {
3233
+ const result = /* @__PURE__ */ new Map();
3234
+ const typeByKey = {
3235
+ blockId: ["custom-block"],
3236
+ drawingId: ["drawing"],
3237
+ shapeId: ["drawing", "custom-block"],
3238
+ tableId: ["table"],
3239
+ rangeId: ["custom-range"],
3240
+ columnGroupId: ["column-group"]
3241
+ };
3242
+ const visit = (candidate, key = "") => {
3243
+ if (typeof candidate === "string") {
3244
+ var _typeByKey$key;
3245
+ (_typeByKey$key = typeByKey[key]) === null || _typeByKey$key === void 0 || _typeByKey$key.forEach((entityType) => {
3246
+ result.set(`${entityType}\u001F${candidate}`, {
3247
+ entityType,
3248
+ entityId: candidate
3249
+ });
3250
+ });
3251
+ return;
3252
+ }
3253
+ if (Array.isArray(candidate)) {
3254
+ const singularKey = key.endsWith("Ids") ? `${key.slice(0, -3)}Id` : key;
3255
+ candidate.forEach((item) => visit(item, singularKey));
3256
+ return;
3257
+ }
3258
+ if (!isRecord$1(candidate)) return;
3259
+ Object.entries(candidate).forEach(([childKey, child]) => visit(child, childKey));
3260
+ };
3261
+ visit(value);
3262
+ return [...result.values()];
3263
+ }
3264
+ function getUnitId(commandInfo, options) {
3265
+ const params = isRecord$1(commandInfo.params) ? commandInfo.params : void 0;
3266
+ return typeof (params === null || params === void 0 ? void 0 : params.unitId) === "string" ? params.unitId : typeof (options === null || options === void 0 ? void 0 : options.unitId) === "string" ? options.unitId : void 0;
3267
+ }
3268
+ function getDocumentUnitAction(commandId) {
3269
+ if (commandId === "doc.command.copy-current-paragraph" || commandId === "docs-table.command.copy-selection") return UnitAction.Copy;
3270
+ if (commandId === "docs.operation.print") return UnitAction.Print;
3271
+ if (commandId === "docs-exchange-client.operation.export-doc") return UnitAction.Export;
3272
+ if (commandId === "docs.operation.start-add-comment" || commandId === "docs.operation.add-drawing-comment" || commandId.startsWith("docs.command.") && commandId.includes("comment") || commandId.startsWith("thread-comment.command.") || commandId.startsWith("thread-comment.mutation.")) return UnitAction.Comment;
3273
+ }
3274
+ function resolveTextTargets(documentDataModel, commandInfo) {
3275
+ const { id } = commandInfo;
3276
+ if (id === InsertTextCommand.id || id === UpdateTextCommand.id) {
3277
+ const params = commandInfo.params;
3278
+ return getDocumentEditTargetObjectIds(documentDataModel, params.segmentId ?? "", params.range);
1498
3279
  }
1499
- _initSelectionChange() {
1500
- this.disposeWithMe(this._commandService.onCommandExecuted((commandInfo) => {
1501
- if (commandInfo.id === SetTextSelectionsOperation.id) {
1502
- const { unitId, ranges, isEditing } = commandInfo.params;
1503
- const doc = this._univerInstanceService.getUnit(unitId);
1504
- if (!doc) return;
1505
- const transformedRanges = ranges.map((range) => this._transformCustomRange(doc, range));
1506
- if (transformedRanges.some((range, i) => ranges[i] !== range)) this._textSelectionManagerService.replaceDocRanges(transformedRanges, void 0, isEditing);
1507
- }
1508
- }));
3280
+ if (id === DeleteTextCommand.id) {
3281
+ const params = commandInfo.params;
3282
+ const isDeleteLeft = params.direction === DeleteDirection.LEFT;
3283
+ return getDocumentEditTargetObjectIds(documentDataModel, params.segmentId ?? "", {
3284
+ startOffset: isDeleteLeft ? params.range.startOffset - (params.len ?? 1) : params.range.startOffset,
3285
+ endOffset: isDeleteLeft ? params.range.startOffset : params.range.startOffset + (params.len ?? 1)
3286
+ });
1509
3287
  }
1510
- };
1511
- DocCustomRangeController = __decorate([
1512
- __decorateParam(0, ICommandService),
1513
- __decorateParam(1, Inject(DocSelectionManagerService)),
1514
- __decorateParam(2, IUniverInstanceService)
1515
- ], DocCustomRangeController);
3288
+ if (id === UpdateDocumentParagraphStyleCommand.id) {
3289
+ const params = commandInfo.params;
3290
+ return getDocumentEditTargetObjectIds(documentDataModel, params.segmentId ?? "", params);
3291
+ }
3292
+ return null;
3293
+ }
3294
+ function resolveSectionTargets(documentDataModel, commandInfo) {
3295
+ const { id } = commandInfo;
3296
+ if (id === UpdateDocumentSectionCommand.id) {
3297
+ const params = commandInfo.params;
3298
+ return getDocumentSectionPermissionObjectIdsByIds(params.updates.map((update) => update.sectionId));
3299
+ }
3300
+ if (id === InsertDocumentSectionBreakCommand.id || id === InsertDocumentColumnBreakCommand.id) {
3301
+ const params = commandInfo.params;
3302
+ const body = documentDataModel.getBody();
3303
+ return body ? getDocumentSectionPermissionObjectIdsByIds(getDocumentSectionIdsAtOffset(body, params.offset)) : [];
3304
+ }
3305
+ if (id === DeleteDocumentSectionBreakCommand.id) {
3306
+ const params = commandInfo.params;
3307
+ const body = documentDataModel.getBody();
3308
+ const sections = body ? getTopLevelSectionBreaks(body) : [];
3309
+ const index = sections.findIndex((section) => section.sectionId === params.sectionId);
3310
+ return getDocumentSectionPermissionObjectIdsByIds([params.sectionId, ...index >= 0 && index + 1 < sections.length ? [sections[index + 1].sectionId] : []]);
3311
+ }
3312
+ if (id === SetSectionHeaderFooterLinkCommand.id) {
3313
+ const params = commandInfo.params;
3314
+ return [getDocumentSectionPermissionObjectId("", params.sectionId)];
3315
+ }
3316
+ if (id === CreateHeaderFooterCommand.id) {
3317
+ const params = commandInfo.params;
3318
+ return params.sectionId ? [getDocumentSectionPermissionObjectId("", params.sectionId)] : [];
3319
+ }
3320
+ return null;
3321
+ }
3322
+ function isRecord$1(value) {
3323
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3324
+ }
1516
3325
 
1517
3326
  //#endregion
1518
3327
  //#region src/services/doc-block-move-validator.service.ts
@@ -1599,6 +3408,20 @@ let DocStateChangeManagerService = class DocStateChangeManagerService extends Rx
1599
3408
  this._initialize();
1600
3409
  this._listenDocStateChange();
1601
3410
  }
3411
+ dispose() {
3412
+ if (this._historyTimer != null) {
3413
+ clearTimeout(this._historyTimer);
3414
+ this._historyTimer = null;
3415
+ }
3416
+ if (this._changeStateCacheTimer != null) {
3417
+ clearTimeout(this._changeStateCacheTimer);
3418
+ this._changeStateCacheTimer = null;
3419
+ }
3420
+ this._historyStateCache.clear();
3421
+ this._changeStateCache.clear();
3422
+ this._docStateChange$.complete();
3423
+ super.dispose();
3424
+ }
1602
3425
  getStateCache(unitId) {
1603
3426
  return {
1604
3427
  history: this._historyStateCache.get(unitId) ?? [],
@@ -1609,6 +3432,13 @@ let DocStateChangeManagerService = class DocStateChangeManagerService extends Rx
1609
3432
  this._historyStateCache.set(unitId, cache.history);
1610
3433
  this._changeStateCache.set(unitId, cache.collaboration);
1611
3434
  }
3435
+ clearHistory(unitId) {
3436
+ this._historyStateCache.delete(unitId);
3437
+ }
3438
+ flushPendingChanges(unitId) {
3439
+ this._pushHistory(unitId);
3440
+ this._emitChangeState(unitId);
3441
+ }
1612
3442
  _setChangeState(changeState) {
1613
3443
  this._cacheChangeState(changeState, "history");
1614
3444
  this._cacheChangeState(changeState, "collaboration");
@@ -1647,18 +3477,19 @@ let DocStateChangeManagerService = class DocStateChangeManagerService extends Rx
1647
3477
  const cacheStates = stateCache.get(unitId);
1648
3478
  cacheStates === null || cacheStates === void 0 || cacheStates.push(changeState);
1649
3479
  } else stateCache.set(unitId, [changeState]);
1650
- if (debounce) if (type === "history") {
1651
- if (this._historyTimer) clearTimeout(this._historyTimer);
1652
- this._historyTimer = setTimeout(() => {
1653
- cb(unitId);
1654
- }, DEBOUNCE_DELAY);
1655
- } else {
1656
- if (this._changeStateCacheTimer) clearTimeout(this._changeStateCacheTimer);
1657
- this._changeStateCacheTimer = setTimeout(() => {
1658
- cb(unitId);
1659
- }, DEBOUNCE_DELAY);
1660
- }
1661
- else cb(unitId);
3480
+ if (debounce) {
3481
+ if (type === "history") {
3482
+ if (this._historyTimer) clearTimeout(this._historyTimer);
3483
+ this._historyTimer = setTimeout(() => {
3484
+ cb(unitId);
3485
+ }, DEBOUNCE_DELAY);
3486
+ } else {
3487
+ if (this._changeStateCacheTimer) clearTimeout(this._changeStateCacheTimer);
3488
+ this._changeStateCacheTimer = setTimeout(() => {
3489
+ cb(unitId);
3490
+ }, DEBOUNCE_DELAY);
3491
+ }
3492
+ } else cb(unitId);
1662
3493
  }
1663
3494
  _pushHistory(unitId) {
1664
3495
  const undoRedoService = this._undoRedoService;
@@ -1670,6 +3501,8 @@ let DocStateChangeManagerService = class DocStateChangeManagerService extends Rx
1670
3501
  const lastState = cacheStates[len - 1];
1671
3502
  const redoParams = {
1672
3503
  unitId,
3504
+ trigger: RedoCommandId,
3505
+ ...lastState.redoState.layoutState === void 0 ? {} : { layoutState: lastState.redoState.layoutState },
1673
3506
  actions: cacheStates.reduce((acc, cur) => JSONX.compose(acc, cur.redoState.actions), null),
1674
3507
  textRanges: lastState.redoState.textRanges,
1675
3508
  segmentId: lastState.segmentId,
@@ -1678,6 +3511,8 @@ let DocStateChangeManagerService = class DocStateChangeManagerService extends Rx
1678
3511
  };
1679
3512
  const undoParams = {
1680
3513
  unitId,
3514
+ trigger: UndoCommandId,
3515
+ ...firstState.undoState.layoutState === void 0 ? {} : { layoutState: firstState.undoState.layoutState },
1681
3516
  actions: cacheStates.reverse().reduce((acc, cur) => JSONX.compose(acc, cur.undoState.actions), null),
1682
3517
  textRanges: firstState.undoState.textRanges,
1683
3518
  segmentId: firstState.segmentId,
@@ -1825,8 +3660,31 @@ let UniverDocsPlugin = class UniverDocsPlugin extends Plugin {
1825
3660
  this._configService.setConfig(DOCS_PLUGIN_CONFIG_KEY, rest);
1826
3661
  }
1827
3662
  onStarting() {
3663
+ this._injector.add([DocumentPermissionRuleModel]);
3664
+ this.disposeWithMe(this._injector.get(ICommandService).registerCommand(SetDocumentPermissionRuleMutation));
3665
+ this.disposeWithMe(this._injector.get(ICommandService).registerCommand(SetDocumentPermissionRulesMutation));
3666
+ this.disposeWithMe(this._injector.get(ObjectPermissionService).registerRuleModel(UnitObject.Document, this._injector.get(DocumentPermissionRuleModel), SetDocumentPermissionRuleMutation.id, SetDocumentPermissionRulesMutation.id));
1828
3667
  this._initializeDependencies();
1829
3668
  this._initializeCommands();
3669
+ this._initializeWordResources();
3670
+ }
3671
+ _initializeWordResources() {
3672
+ for (const pluginName of ["DOC_WORD_STYLES_PLUGIN", "DOC_NOTE_PLUGIN"]) {
3673
+ const resources = /* @__PURE__ */ new Map();
3674
+ this.disposeWithMe({ dispose: () => resources.clear() });
3675
+ this.disposeWithMe(this._injector.get(IResourceManagerService).registerPluginResource({
3676
+ pluginName,
3677
+ businesses: [UniverInstanceType.UNIVER_DOC],
3678
+ parseJson: (data) => data,
3679
+ onLoad: (unitId, data) => {
3680
+ resources.set(unitId, data);
3681
+ },
3682
+ onUnLoad: (unitId) => {
3683
+ resources.delete(unitId);
3684
+ },
3685
+ toJson: (unitId) => resources.get(unitId) ?? ""
3686
+ }));
3687
+ }
1830
3688
  }
1831
3689
  _initializeCommands() {
1832
3690
  [
@@ -1834,6 +3692,8 @@ let UniverDocsPlugin = class UniverDocsPlugin extends Plugin {
1834
3692
  DeleteTextCommand,
1835
3693
  UpdateTextCommand,
1836
3694
  CreateHeaderFooterCommand,
3695
+ SetDocumentPermissionCommand,
3696
+ SetDocumentPermissionsCommand,
1837
3697
  SetDocumentDefaultParagraphStyleCommand,
1838
3698
  SetDocumentNameCommand,
1839
3699
  SetSectionHeaderFooterLinkCommand,
@@ -1856,13 +3716,16 @@ let UniverDocsPlugin = class UniverDocsPlugin extends Plugin {
1856
3716
  [DocStateChangeManagerService],
1857
3717
  [DocBlockMoveValidatorService],
1858
3718
  [DocContentInsertService],
3719
+ [DocLayoutExecutorService],
1859
3720
  [DocTextResolverService],
1860
- [DocCustomRangeController]
3721
+ [DocCustomRangeController],
3722
+ [DocPermissionController]
1861
3723
  ].forEach((d) => this._injector.add(d));
1862
3724
  }
1863
3725
  onReady() {
1864
3726
  this._injector.get(DocStateChangeManagerService);
1865
3727
  this._injector.get(DocCustomRangeController);
3728
+ this._injector.get(DocPermissionController);
1866
3729
  }
1867
3730
  };
1868
3731
  _defineProperty(UniverDocsPlugin, "pluginName", "DOCS_PLUGIN");
@@ -1870,6 +3733,739 @@ _defineProperty(UniverDocsPlugin, "packageName", name);
1870
3733
  _defineProperty(UniverDocsPlugin, "version", version);
1871
3734
  UniverDocsPlugin = __decorate([__decorateParam(1, Inject(Injector)), __decorateParam(2, IConfigService)], UniverDocsPlugin);
1872
3735
 
3736
+ //#endregion
3737
+ //#region src/layout-worker/config/config.ts
3738
+ /**
3739
+ * Copyright 2023-present DreamNum Co., Ltd.
3740
+ *
3741
+ * Licensed under the Apache License, Version 2.0 (the "License");
3742
+ * you may not use this file except in compliance with the License.
3743
+ * You may obtain a copy of the License at
3744
+ *
3745
+ * http://www.apache.org/licenses/LICENSE-2.0
3746
+ *
3747
+ * Unless required by applicable law or agreed to in writing, software
3748
+ * distributed under the License is distributed on an "AS IS" BASIS,
3749
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3750
+ * See the License for the specific language governing permissions and
3751
+ * limitations under the License.
3752
+ */
3753
+ const DOCS_LAYOUT_WORKER_PLUGIN_CONFIG_KEY = "docs-layout-worker.config";
3754
+ const configSymbol = Symbol(DOCS_LAYOUT_WORKER_PLUGIN_CONFIG_KEY);
3755
+ const DEFAULT_DOCS_LAYOUT_WORKER_REQUEST_TIMEOUT_MS = 15e3;
3756
+ const defaultPluginDocsLayoutWorkerConfig = { requestTimeoutMs: DEFAULT_DOCS_LAYOUT_WORKER_REQUEST_TIMEOUT_MS };
3757
+
3758
+ //#endregion
3759
+ //#region src/layout-worker/document-font-metrics.ts
3760
+ /**
3761
+ * Copyright 2023-present DreamNum Co., Ltd.
3762
+ *
3763
+ * Licensed under the Apache License, Version 2.0 (the "License");
3764
+ * you may not use this file except in compliance with the License.
3765
+ * You may obtain a copy of the License at
3766
+ *
3767
+ * http://www.apache.org/licenses/LICENSE-2.0
3768
+ *
3769
+ * Unless required by applicable law or agreed to in writing, software
3770
+ * distributed under the License is distributed on an "AS IS" BASIS,
3771
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3772
+ * See the License for the specific language governing permissions and
3773
+ * limitations under the License.
3774
+ */
3775
+ /** Collect once from the snapshot, then only from mutation payloads (including JSON1 font replacements). */
3776
+ function collectDocumentFontFamilies(value, families) {
3777
+ if (value == null || typeof value !== "object") return;
3778
+ if (Array.isArray(value)) {
3779
+ for (let index = 0; index < value.length; index++) {
3780
+ const item = value[index];
3781
+ if (value[index - 1] === "ff" && item != null && typeof item === "object" && typeof item.i === "string") families.add(item.i);
3782
+ collectDocumentFontFamilies(item, families);
3783
+ }
3784
+ return;
3785
+ }
3786
+ const textStyle = value;
3787
+ if (typeof textStyle.eastAsiaFontFamily === "string" && textStyle.eastAsiaFontFamily.trim()) {
3788
+ const primary = typeof textStyle.ff === "string" && textStyle.ff.trim() ? textStyle.ff : DEFAULT_STYLES.ff;
3789
+ families.add(`${primary}, ${textStyle.eastAsiaFontFamily.trim()}`);
3790
+ families.add(`${textStyle.eastAsiaFontFamily.trim()}, ${primary}, ${textStyle.eastAsiaFontFamily.trim()}`);
3791
+ }
3792
+ for (const [key, item] of Object.entries(value)) if (key === "ff" && typeof item === "string") families.add(item);
3793
+ else collectDocumentFontFamilies(item, families);
3794
+ }
3795
+ function measureDocumentFontFamilies(families) {
3796
+ FontCache.getNormalLineHeight(getFontStyleString());
3797
+ for (const ff of ["", ...families]) for (const bl of [BooleanNumber.FALSE, BooleanNumber.TRUE]) for (const it of [BooleanNumber.FALSE, BooleanNumber.TRUE]) FontCache.getNormalLineHeight(getFontStyleString({
3798
+ ff,
3799
+ bl,
3800
+ it
3801
+ }));
3802
+ return FontCache.getNormalLineHeightCache();
3803
+ }
3804
+
3805
+ //#endregion
3806
+ //#region src/layout-worker/font-loader.ts
3807
+ /** Owns the same explicitly supplied font faces in the rendering and layout realms. */
3808
+ var DocsLayoutFontLoader = class extends Disposable {
3809
+ constructor(..._args) {
3810
+ super(..._args);
3811
+ _defineProperty(this, "_loading", null);
3812
+ _defineProperty(this, "_fontSet", void 0);
3813
+ _defineProperty(this, "_faces", []);
3814
+ }
3815
+ load(fonts) {
3816
+ this._loading ??= this._load(fonts);
3817
+ return this._loading;
3818
+ }
3819
+ dispose() {
3820
+ for (const face of this._faces) {
3821
+ var _this$_fontSet;
3822
+ (_this$_fontSet = this._fontSet) === null || _this$_fontSet === void 0 || _this$_fontSet.delete(face);
3823
+ }
3824
+ if (this._faces.length > 0) FontCache.invalidateMetrics(() => true);
3825
+ this._faces = [];
3826
+ this._fontSet = void 0;
3827
+ super.dispose();
3828
+ }
3829
+ async _load(fonts) {
3830
+ this.ensureNotDisposed();
3831
+ if (fonts.length === 0) return;
3832
+ const fontSet = typeof document === "undefined" ? globalThis.fonts : document.fonts;
3833
+ if (typeof FontFace === "undefined" || fontSet == null) throw new Error("Document layout requires FontFace support to load the configured fonts.");
3834
+ try {
3835
+ const faces = fonts.map(({ family, source, descriptors }) => {
3836
+ if (!family.trim() || (typeof source === "string" ? !source.trim() : source.byteLength === 0)) throw new TypeError("Document layout font family and source must not be empty.");
3837
+ return new FontFace(family, source, descriptors);
3838
+ });
3839
+ await Promise.all(faces.map((face) => face.load()));
3840
+ this.ensureNotDisposed();
3841
+ this._fontSet = fontSet;
3842
+ this._faces = faces;
3843
+ for (const face of faces) fontSet.add(face);
3844
+ FontCache.invalidateMetrics(() => true);
3845
+ } catch (error) {
3846
+ this.dispose();
3847
+ throw error;
3848
+ }
3849
+ }
3850
+ };
3851
+
3852
+ //#endregion
3853
+ //#region src/layout-worker/performance-tracker.ts
3854
+ const PERFORMANCE_SAMPLE_LIMIT = 2048;
3855
+ function isRecord(value) {
3856
+ return typeof value === "object" && value != null;
3857
+ }
3858
+ function getRequest(message) {
3859
+ if (!isRecord(message) || typeof message.method !== "string" || !Array.isArray(message.args)) return null;
3860
+ const request = message.args[0];
3861
+ return isRecord(request) ? {
3862
+ method: message.method,
3863
+ request
3864
+ } : null;
3865
+ }
3866
+ function getPublicationResult(message) {
3867
+ if (!isRecord(message) || !isRecord(message.data)) return null;
3868
+ const result = message.data;
3869
+ if (isRecord(result.step)) return result.step;
3870
+ return result;
3871
+ }
3872
+ function createTransferMetrics() {
3873
+ return {
3874
+ mutationTransferMs: [],
3875
+ patchTransferMs: [],
3876
+ snapshotTransferMs: []
3877
+ };
3878
+ }
3879
+ var DocsLayoutWorkerPerformanceTracker = class {
3880
+ constructor() {
3881
+ _defineProperty(this, "_metrics", /* @__PURE__ */ new Map());
3882
+ }
3883
+ recordRequest(message, durationMs) {
3884
+ const requestInfo = getRequest(message);
3885
+ const unitId = requestInfo === null || requestInfo === void 0 ? void 0 : requestInfo.request.unitId;
3886
+ if (requestInfo == null || typeof unitId !== "string") return;
3887
+ if (requestInfo.method === "createSession") {
3888
+ this._append(unitId, "snapshotTransferMs", durationMs);
3889
+ return;
3890
+ }
3891
+ if (requestInfo.method === "startLayout" && Array.isArray(requestInfo.request.mutations) && requestInfo.request.mutations.length > 0) this._append(unitId, "mutationTransferMs", durationMs);
3892
+ }
3893
+ recordResponse(message, durationMs) {
3894
+ const result = getPublicationResult(message);
3895
+ if (result == null || typeof result.unitId !== "string" || result.publication == null) return;
3896
+ this._append(result.unitId, "patchTransferMs", durationMs);
3897
+ }
3898
+ getMetrics(unitId) {
3899
+ const metrics = this._metrics.get(unitId) ?? createTransferMetrics();
3900
+ return {
3901
+ mutationTransferMs: [...metrics.mutationTransferMs],
3902
+ patchTransferMs: [...metrics.patchTransferMs],
3903
+ snapshotTransferMs: [...metrics.snapshotTransferMs]
3904
+ };
3905
+ }
3906
+ reset(unitId) {
3907
+ this._metrics.delete(unitId);
3908
+ }
3909
+ resetAll() {
3910
+ this._metrics.clear();
3911
+ }
3912
+ _append(unitId, metric, durationMs) {
3913
+ const metrics = this._metrics.get(unitId) ?? createTransferMetrics();
3914
+ const samples = metrics[metric];
3915
+ samples.push(durationMs);
3916
+ if (samples.length > PERFORMANCE_SAMPLE_LIMIT) samples.splice(0, samples.length - PERFORMANCE_SAMPLE_LIMIT);
3917
+ this._metrics.set(unitId, metrics);
3918
+ }
3919
+ };
3920
+
3921
+ //#endregion
3922
+ //#region src/layout-worker/protocol.ts
3923
+ const DOCS_LAYOUT_WORKER_CHANNEL = "univer.docs-layout-worker";
3924
+
3925
+ //#endregion
3926
+ //#region src/layout-worker/worker.ts
3927
+ const FONT_PROBE_CONTENT = "Univer 文档 Worker 0123456789";
3928
+ const FONT_PROBE_STYLE = "16px Arial";
3929
+ const WORKER_LAYOUT_INTERRUPT_INTERVAL = 8;
3930
+ function yieldForWorkerMessage() {
3931
+ return new Promise((resolve) => {
3932
+ requestImmediateMacroTask(() => resolve());
3933
+ });
3934
+ }
3935
+ function createDocsLayoutWorkerMessageProtocol$1(performanceTracker) {
3936
+ return {
3937
+ send(message) {
3938
+ const startedAt = Tools.now();
3939
+ postMessage(message);
3940
+ performanceTracker.recordResponse(message, Tools.now() - startedAt);
3941
+ },
3942
+ onMessage: new Observable((subscriber) => {
3943
+ const handler = (event) => subscriber.next(event.data);
3944
+ addEventListener("message", handler);
3945
+ return () => removeEventListener("message", handler);
3946
+ }).pipe(shareReplay({
3947
+ bufferSize: 1,
3948
+ refCount: true
3949
+ }))
3950
+ };
3951
+ }
3952
+ function getCustomRangePresentationKey(entry) {
3953
+ return JSON.stringify([entry.segmentId, entry.rangeId]);
3954
+ }
3955
+ function applyRecordPatch(target, patch) {
3956
+ if (patch == null) return;
3957
+ for (const key of patch.removals) delete target[key];
3958
+ Object.assign(target, patch.upserts);
3959
+ }
3960
+ var DocsLayoutWorkerRuntime = class {
3961
+ constructor(_performanceTracker = new DocsLayoutWorkerPerformanceTracker()) {
3962
+ this._performanceTracker = _performanceTracker;
3963
+ _defineProperty(this, "_sessions", /* @__PURE__ */ new Map());
3964
+ _defineProperty(this, "_fontLoader", new DocsLayoutFontLoader());
3965
+ _defineProperty(this, "type", "worker");
3966
+ }
3967
+ initialize(fontFaces = []) {
3968
+ return this._fontLoader.load(fontFaces);
3969
+ }
3970
+ async getCapabilities() {
3971
+ const metrics = FontCache.getMeasureText(FONT_PROBE_CONTENT, FONT_PROBE_STYLE);
3972
+ return {
3973
+ protocolVersion: 5,
3974
+ executor: "worker",
3975
+ offscreenCanvas: typeof OffscreenCanvas !== "undefined",
3976
+ structuredClone: typeof structuredClone === "function",
3977
+ fontProbe: {
3978
+ content: FONT_PROBE_CONTENT,
3979
+ font: FONT_PROBE_STYLE,
3980
+ width: metrics.width,
3981
+ actualBoundingBoxAscent: metrics.actualBoundingBoxAscent,
3982
+ actualBoundingBoxDescent: metrics.actualBoundingBoxDescent
3983
+ }
3984
+ };
3985
+ }
3986
+ async recover() {
3987
+ for (const session of this._sessions.values()) for (const mount of session.layoutSessions.values()) mount.layoutSession.dispose();
3988
+ this._sessions.clear();
3989
+ }
3990
+ async createSession(request) {
3991
+ if (typeof OffscreenCanvas === "undefined") throw new Error("Document layout Worker requires OffscreenCanvas text measurement support.");
3992
+ const currentSession = this._sessions.get(request.unitId);
3993
+ if (currentSession != null && request.sessionEpoch < currentSession.sessionEpoch) return;
3994
+ this._disposeSession(request.unitId);
3995
+ FontCache.setNormalLineHeightCache(request.normalFontLineHeights ?? {});
3996
+ const dataModel = new DocumentDataModel(request.snapshot);
3997
+ const localeService = new LocaleService();
3998
+ if (request.localeData != null) localeService.load({ [request.locale]: request.localeData });
3999
+ localeService.setLocale(request.locale);
4000
+ localeService.setDirection(request.direction);
4001
+ this._sessions.set(request.unitId, {
4002
+ sessionEpoch: request.sessionEpoch,
4003
+ dataModel,
4004
+ layoutSessions: /* @__PURE__ */ new Map(),
4005
+ localeService,
4006
+ modelRevision: request.modelRevision,
4007
+ customRangePresentations: /* @__PURE__ */ new Map()
4008
+ });
4009
+ }
4010
+ async startLayout(request) {
4011
+ const session = this._sessions.get(request.unitId);
4012
+ if (session == null) return { status: "not-found" };
4013
+ if (session.modelRevision !== request.baseRevision) return {
4014
+ status: "resnapshot-required",
4015
+ modelRevision: session.modelRevision
4016
+ };
4017
+ if (!this._applyMutations(session, request)) return {
4018
+ status: "resnapshot-required",
4019
+ modelRevision: session.modelRevision
4020
+ };
4021
+ FontCache.setNormalLineHeightCache(request.normalFontLineHeights ?? {});
4022
+ this._updateCustomRangePresentations(session, request);
4023
+ this._applyCustomRangePresentations(session.dataModel, Array.from(session.customRangePresentations.values()));
4024
+ let mount = session.layoutSessions.get(request.mountId);
4025
+ if (mount != null && (request.mountEpoch < mount.mountEpoch || request.mountEpoch === mount.mountEpoch && request.viewportEpoch < mount.viewportEpoch)) return { status: "superseded" };
4026
+ if (mount != null && request.mountEpoch > mount.mountEpoch) {
4027
+ mount.layoutSession.dispose();
4028
+ session.layoutSessions.delete(request.mountId);
4029
+ mount = void 0;
4030
+ }
4031
+ if (mount == null) {
4032
+ const customBlockViewports = { ...request.customBlockViewports };
4033
+ applyRecordPatch(customBlockViewports, request.customBlockViewportPatch);
4034
+ mount = {
4035
+ mountId: request.mountId,
4036
+ layoutSession: new DocumentLayoutSession(session.dataModel, session.localeService),
4037
+ customBlockViewports,
4038
+ metricsRevision: request.metricsRevision,
4039
+ mountEpoch: request.mountEpoch,
4040
+ viewportEpoch: request.viewportEpoch,
4041
+ activeGeneration: null,
4042
+ cancelledGeneration: null
4043
+ };
4044
+ session.layoutSessions.set(request.mountId, mount);
4045
+ } else {
4046
+ if (request.customBlockViewports != null) mount.customBlockViewports = { ...request.customBlockViewports };
4047
+ applyRecordPatch(mount.customBlockViewports, request.customBlockViewportPatch);
4048
+ mount.metricsRevision = request.metricsRevision;
4049
+ mount.viewportEpoch = request.viewportEpoch;
4050
+ }
4051
+ const generation = mount.layoutSession.start({
4052
+ waitForHyphenationPatterns: true,
4053
+ reason: request.reason,
4054
+ anchor: request.anchor,
4055
+ priorityAnchor: request.priorityAnchor,
4056
+ invalidation: request.invalidation,
4057
+ modernPageWidth: request.modernPageWidth,
4058
+ modernHorizontalMargin: request.modernHorizontalMargin
4059
+ });
4060
+ mount.activeGeneration = generation;
4061
+ mount.cancelledGeneration = null;
4062
+ return {
4063
+ status: "accepted",
4064
+ step: await this._runLayoutStep(session, mount, generation, request.budgetMs)
4065
+ };
4066
+ }
4067
+ _applyMutations(session, request) {
4068
+ for (const mutation of request.mutations) {
4069
+ if (mutation.baseRevision !== session.modelRevision || mutation.modelRevision !== mutation.baseRevision + 1) return false;
4070
+ session.dataModel.apply(mutation.actions);
4071
+ session.modelRevision = mutation.modelRevision;
4072
+ }
4073
+ if (session.modelRevision !== request.modelRevision) return false;
4074
+ if (request.mutations.length > 0) for (const mount of session.layoutSessions.values()) {
4075
+ mount.activeGeneration = null;
4076
+ mount.cancelledGeneration = null;
4077
+ mount.layoutSession.resetDataModel(session.dataModel);
4078
+ }
4079
+ return true;
4080
+ }
4081
+ _applyCustomRangePresentations(dataModel, presentations) {
4082
+ const rangeMaps = /* @__PURE__ */ new Map();
4083
+ const segmentModels = [
4084
+ ["", dataModel],
4085
+ ...dataModel.headerModelMap,
4086
+ ...dataModel.footerModelMap
4087
+ ];
4088
+ for (const [segmentId, segmentModel] of segmentModels) {
4089
+ var _segmentModel$getBody;
4090
+ const rangeMap = /* @__PURE__ */ new Map();
4091
+ for (const range of ((_segmentModel$getBody = segmentModel.getBody()) === null || _segmentModel$getBody === void 0 ? void 0 : _segmentModel$getBody.customRanges) ?? []) {
4092
+ const workerRange = range;
4093
+ delete workerRange.active;
4094
+ delete workerRange.glyphAscentEm;
4095
+ delete workerRange.glyphDescentEm;
4096
+ delete workerRange.glyphWidthEm;
4097
+ delete workerRange.show;
4098
+ if (!rangeMap.has(range.rangeId)) rangeMap.set(range.rangeId, workerRange);
4099
+ }
4100
+ rangeMaps.set(segmentId, rangeMap);
4101
+ }
4102
+ for (const { segmentId, rangeId, presentation } of presentations ?? []) {
4103
+ const rangeMap = rangeMaps.get(segmentId);
4104
+ const range = rangeMap === null || rangeMap === void 0 ? void 0 : rangeMap.get(rangeId);
4105
+ if (range != null) {
4106
+ Object.assign(range, presentation);
4107
+ rangeMap === null || rangeMap === void 0 || rangeMap.delete(rangeId);
4108
+ }
4109
+ }
4110
+ }
4111
+ _updateCustomRangePresentations(session, request) {
4112
+ var _request$customRangeP, _request$customRangeP2;
4113
+ if (request.customRangePresentations != null) {
4114
+ session.customRangePresentations.clear();
4115
+ for (const entry of request.customRangePresentations) {
4116
+ const key = getCustomRangePresentationKey(entry);
4117
+ if (!session.customRangePresentations.has(key)) session.customRangePresentations.set(key, entry);
4118
+ }
4119
+ }
4120
+ for (const removal of ((_request$customRangeP = request.customRangePresentationPatch) === null || _request$customRangeP === void 0 ? void 0 : _request$customRangeP.removals) ?? []) session.customRangePresentations.delete(getCustomRangePresentationKey(removal));
4121
+ for (const entry of ((_request$customRangeP2 = request.customRangePresentationPatch) === null || _request$customRangeP2 === void 0 ? void 0 : _request$customRangeP2.upserts) ?? []) session.customRangePresentations.set(getCustomRangePresentationKey(entry), entry);
4122
+ }
4123
+ async stepLayout(request) {
4124
+ const session = this._getSession(request.unitId);
4125
+ const mount = this._getLayoutMount(session, request);
4126
+ return await this._runLayoutStep(session, mount, request.generation, request.budgetMs);
4127
+ }
4128
+ async publishBacklog(request) {
4129
+ const session = this._getSession(request.unitId);
4130
+ const mount = this._getLayoutMount(session, request);
4131
+ return this._runWithCustomBlockViewports(session, mount, () => ({
4132
+ ...mount.layoutSession.publishBacklog(request.generation),
4133
+ unitId: request.unitId,
4134
+ mountId: request.mountId,
4135
+ mountEpoch: mount.mountEpoch,
4136
+ viewportEpoch: mount.viewportEpoch,
4137
+ modelRevision: session.modelRevision,
4138
+ metricsRevision: mount.metricsRevision
4139
+ }));
4140
+ }
4141
+ async getLayoutPage(request) {
4142
+ const session = this._getSession(request.unitId);
4143
+ const mount = this._getLayoutMount(session, request);
4144
+ return {
4145
+ ...request,
4146
+ modelRevision: session.modelRevision,
4147
+ metricsRevision: mount.metricsRevision,
4148
+ page: mount.layoutSession.getPage(request.pageIndex)
4149
+ };
4150
+ }
4151
+ async cancelLayout(request) {
4152
+ var _this$_sessions$get;
4153
+ const mount = (_this$_sessions$get = this._sessions.get(request.unitId)) === null || _this$_sessions$get === void 0 ? void 0 : _this$_sessions$get.layoutSessions.get(request.mountId);
4154
+ if ((mount === null || mount === void 0 ? void 0 : mount.mountEpoch) === request.mountEpoch && mount.viewportEpoch === request.viewportEpoch) {
4155
+ if (request.generation == null || request.generation === mount.activeGeneration) mount.cancelledGeneration = request.generation ?? mount.activeGeneration;
4156
+ mount.layoutSession.cancel(request.generation);
4157
+ }
4158
+ }
4159
+ async getPerformanceMetrics(unitId) {
4160
+ return this._performanceTracker.getMetrics(unitId);
4161
+ }
4162
+ async resetPerformanceMetrics(unitId) {
4163
+ this._performanceTracker.reset(unitId);
4164
+ }
4165
+ async disposeLayoutMount(request) {
4166
+ const session = this._sessions.get(request.unitId);
4167
+ const mount = session === null || session === void 0 ? void 0 : session.layoutSessions.get(request.mountId);
4168
+ if (session == null || mount == null || mount.mountEpoch !== request.mountEpoch || mount.viewportEpoch !== request.viewportEpoch) return;
4169
+ session.layoutSessions.delete(request.mountId);
4170
+ mount.activeGeneration = null;
4171
+ mount.layoutSession.dispose();
4172
+ }
4173
+ async disposeSession(request) {
4174
+ const session = this._sessions.get(request.unitId);
4175
+ if ((session === null || session === void 0 ? void 0 : session.sessionEpoch) !== request.sessionEpoch) return;
4176
+ this._disposeSession(request.unitId);
4177
+ this._performanceTracker.reset(request.unitId);
4178
+ }
4179
+ dispose() {
4180
+ this._fontLoader.dispose();
4181
+ for (const unitId of this._sessions.keys()) this._disposeSession(unitId);
4182
+ this._performanceTracker.resetAll();
4183
+ }
4184
+ _getSession(unitId) {
4185
+ const session = this._sessions.get(unitId);
4186
+ if (session == null) throw new Error(`Document layout Worker session not found for unit "${unitId}".`);
4187
+ return session;
4188
+ }
4189
+ _getLayoutMount(session, request) {
4190
+ const mount = session.layoutSessions.get(request.mountId);
4191
+ if (mount == null) throw new Error(`Document layout Worker mount not found: "${request.mountId}".`);
4192
+ if (mount.mountEpoch !== request.mountEpoch || mount.viewportEpoch !== request.viewportEpoch) throw new Error(`Document layout Worker rejected a stale mount publication: "${request.mountId}".`);
4193
+ return mount;
4194
+ }
4195
+ async _runLayoutStep(session, mount, generation, budgetMs) {
4196
+ const result = this._runWithCustomBlockViewports(session, mount, () => ({
4197
+ ...mount.layoutSession.step(generation, budgetMs, WORKER_LAYOUT_INTERRUPT_INTERVAL),
4198
+ unitId: session.dataModel.getUnitId(),
4199
+ mountId: mount.mountId,
4200
+ mountEpoch: mount.mountEpoch,
4201
+ viewportEpoch: mount.viewportEpoch,
4202
+ modelRevision: session.modelRevision,
4203
+ metricsRevision: mount.metricsRevision
4204
+ }));
4205
+ await yieldForWorkerMessage();
4206
+ if (mount.activeGeneration === generation && mount.cancelledGeneration !== generation) return result;
4207
+ return this._runWithCustomBlockViewports(session, mount, () => ({
4208
+ ...mount.layoutSession.step(generation, 0, 1),
4209
+ unitId: session.dataModel.getUnitId(),
4210
+ mountId: mount.mountId,
4211
+ mountEpoch: mount.mountEpoch,
4212
+ viewportEpoch: mount.viewportEpoch,
4213
+ modelRevision: session.modelRevision,
4214
+ metricsRevision: mount.metricsRevision
4215
+ }));
4216
+ }
4217
+ _runWithCustomBlockViewports(session, mount, run) {
4218
+ const unregister = setDocsCustomBlockRenderViewportProvider((unitId, blockId) => unitId === session.dataModel.getUnitId() ? mount.customBlockViewports[blockId] : null);
4219
+ try {
4220
+ return run();
4221
+ } finally {
4222
+ unregister();
4223
+ }
4224
+ }
4225
+ _disposeSession(unitId) {
4226
+ const session = this._sessions.get(unitId);
4227
+ if (session == null) return;
4228
+ this._sessions.delete(unitId);
4229
+ for (const mount of session.layoutSessions.values()) {
4230
+ mount.activeGeneration = null;
4231
+ mount.layoutSession.dispose();
4232
+ }
4233
+ session.layoutSessions.clear();
4234
+ session.dataModel.dispose();
4235
+ session.localeService.dispose();
4236
+ }
4237
+ };
4238
+ var DocsLayoutWorkerChannelService = class extends ChannelService {
4239
+ constructor(protocol, _runtime) {
4240
+ super(protocol);
4241
+ this._runtime = _runtime;
4242
+ }
4243
+ dispose() {
4244
+ this._runtime.dispose();
4245
+ super.dispose();
4246
+ }
4247
+ };
4248
+ function startDocsLayoutWorker() {
4249
+ const performanceTracker = new DocsLayoutWorkerPerformanceTracker();
4250
+ const runtime = new DocsLayoutWorkerRuntime(performanceTracker);
4251
+ const channelService = new DocsLayoutWorkerChannelService(createDocsLayoutWorkerMessageProtocol$1(performanceTracker), runtime);
4252
+ channelService.registerChannel(DOCS_LAYOUT_WORKER_CHANNEL, fromModule(runtime));
4253
+ return channelService;
4254
+ }
4255
+
4256
+ //#endregion
4257
+ //#region src/layout-worker/index.ts
4258
+ const FONT_METRICS_TOLERANCE = 1;
4259
+ var DocsLayoutWorkerCapabilityError = class extends Error {
4260
+ constructor(message) {
4261
+ super(message);
4262
+ this.name = "DocsLayoutWorkerCapabilityError";
4263
+ }
4264
+ };
4265
+ function createDocsLayoutWorkerMessageProtocol(worker, performanceTracker) {
4266
+ return {
4267
+ send(message) {
4268
+ const startedAt = Tools.now();
4269
+ worker.postMessage(message);
4270
+ performanceTracker.recordRequest(message, Tools.now() - startedAt);
4271
+ },
4272
+ onMessage: new Observable((subscriber) => {
4273
+ const handler = (event) => subscriber.next(event.data);
4274
+ worker.addEventListener("message", handler);
4275
+ return () => worker.removeEventListener("message", handler);
4276
+ }).pipe(shareReplay({
4277
+ bufferSize: 1,
4278
+ refCount: true
4279
+ }))
4280
+ };
4281
+ }
4282
+ var DocsLayoutWorkerClientService = class extends Disposable {
4283
+ constructor(_workerFactory, _requestTimeoutMs = DEFAULT_DOCS_LAYOUT_WORKER_REQUEST_TIMEOUT_MS, _fontFaces = []) {
4284
+ super();
4285
+ this._workerFactory = _workerFactory;
4286
+ this._requestTimeoutMs = _requestTimeoutMs;
4287
+ this._fontFaces = _fontFaces;
4288
+ _defineProperty(this, "type", "worker");
4289
+ _defineProperty(this, "_worker", null);
4290
+ _defineProperty(this, "_channelService", null);
4291
+ _defineProperty(this, "_runtime", null);
4292
+ _defineProperty(this, "_initialization", void 0);
4293
+ _defineProperty(this, "_initialized", false);
4294
+ _defineProperty(this, "_performanceTracker", new DocsLayoutWorkerPerformanceTracker());
4295
+ _defineProperty(this, "_fontFamilies", /* @__PURE__ */ new Map());
4296
+ _defineProperty(this, "_fontLoader", new DocsLayoutFontLoader());
4297
+ this.disposeWithMe(this._fontLoader);
4298
+ this._initialization = this._replaceRuntime();
4299
+ }
4300
+ initialize() {
4301
+ return this._initialization;
4302
+ }
4303
+ get renderingFontsReady() {
4304
+ return this._fontFaces.length > 0 ? this._initialization : void 0;
4305
+ }
4306
+ async getCapabilities() {
4307
+ await this._initialization;
4308
+ return this._getRuntime().getCapabilities();
4309
+ }
4310
+ async recover() {
4311
+ this._initialization = this._replaceRuntime();
4312
+ await this._initialization;
4313
+ }
4314
+ createSession(request) {
4315
+ const families = /* @__PURE__ */ new Set();
4316
+ collectDocumentFontFamilies(request.snapshot, families);
4317
+ this._fontFamilies.set(request.unitId, families);
4318
+ const measuredRequest = {
4319
+ ...request,
4320
+ normalFontLineHeights: measureDocumentFontFamilies(families)
4321
+ };
4322
+ if (this._initialized) return this._withTimeout(this._getRuntime().createSession(measuredRequest), "create session");
4323
+ const capturedRequest = Tools.deepClone(measuredRequest);
4324
+ return this._initialization.then(() => this._withTimeout(this._getRuntime().createSession(capturedRequest), "create session"));
4325
+ }
4326
+ async startLayout(request) {
4327
+ await this._initialization;
4328
+ const families = this._fontFamilies.get(request.unitId);
4329
+ if (families != null) collectDocumentFontFamilies(request.mutations, families);
4330
+ const measuredRequest = families == null ? request : {
4331
+ ...request,
4332
+ normalFontLineHeights: measureDocumentFontFamilies(families)
4333
+ };
4334
+ return this._withTimeout(this._getRuntime().startLayout(measuredRequest), "start layout");
4335
+ }
4336
+ async stepLayout(request) {
4337
+ await this._initialization;
4338
+ return this._withTimeout(this._getRuntime().stepLayout(request), "step layout");
4339
+ }
4340
+ async publishBacklog(request) {
4341
+ await this._initialization;
4342
+ return this._withTimeout(this._getRuntime().publishBacklog(request), "publish layout backlog");
4343
+ }
4344
+ async getLayoutPage(request) {
4345
+ await this._initialization;
4346
+ return this._withTimeout(this._getRuntime().getLayoutPage(request), "get layout page");
4347
+ }
4348
+ async cancelLayout(request) {
4349
+ await this._initialization;
4350
+ return this._withTimeout(this._getRuntime().cancelLayout(request), "cancel layout");
4351
+ }
4352
+ async getPerformanceMetrics(unitId) {
4353
+ await this._initialization;
4354
+ const workerMetrics = await this._withTimeout(this._getRuntime().getPerformanceMetrics(unitId), "read performance metrics");
4355
+ const clientMetrics = this._performanceTracker.getMetrics(unitId);
4356
+ return {
4357
+ mutationTransferMs: clientMetrics.mutationTransferMs,
4358
+ patchTransferMs: workerMetrics.patchTransferMs,
4359
+ snapshotTransferMs: clientMetrics.snapshotTransferMs
4360
+ };
4361
+ }
4362
+ async resetPerformanceMetrics(unitId) {
4363
+ await this._initialization;
4364
+ await this._withTimeout(this._getRuntime().resetPerformanceMetrics(unitId), "reset performance metrics");
4365
+ this._performanceTracker.reset(unitId);
4366
+ }
4367
+ async disposeLayoutMount(request) {
4368
+ await this._initialization;
4369
+ return this._withTimeout(this._getRuntime().disposeLayoutMount(request), "dispose layout mount");
4370
+ }
4371
+ async disposeSession(request) {
4372
+ await this._initialization;
4373
+ await this._withTimeout(this._getRuntime().disposeSession(request), "dispose session");
4374
+ this._performanceTracker.reset(request.unitId);
4375
+ this._fontFamilies.delete(request.unitId);
4376
+ }
4377
+ dispose() {
4378
+ this._disposeRuntime();
4379
+ super.dispose();
4380
+ }
4381
+ async _replaceRuntime() {
4382
+ this._initialized = false;
4383
+ this._disposeRuntime();
4384
+ await this._withTimeout(this._fontLoader.load(this._fontFaces), "load rendering fonts");
4385
+ this.ensureNotDisposed();
4386
+ const worker = this._workerFactory();
4387
+ const channelService = new ChannelService(createDocsLayoutWorkerMessageProtocol(worker, this._performanceTracker));
4388
+ this._worker = worker;
4389
+ this._channelService = channelService;
4390
+ this._runtime = toModule(channelService.requestChannel(DOCS_LAYOUT_WORKER_CHANNEL));
4391
+ try {
4392
+ await this._withTimeout(this._runtime.initialize(this._fontFaces), "load Worker fonts");
4393
+ await this._withTimeout(this._verifyCapabilities(), "verify capabilities");
4394
+ this.ensureNotDisposed();
4395
+ this._initialized = true;
4396
+ } catch (error) {
4397
+ if (this._worker === worker) this._disposeRuntime();
4398
+ throw error;
4399
+ }
4400
+ }
4401
+ _disposeRuntime() {
4402
+ var _this$_channelService, _this$_worker;
4403
+ this._fontFamilies.clear();
4404
+ this._initialized = false;
4405
+ (_this$_channelService = this._channelService) === null || _this$_channelService === void 0 || _this$_channelService.dispose();
4406
+ this._channelService = null;
4407
+ this._runtime = null;
4408
+ (_this$_worker = this._worker) === null || _this$_worker === void 0 || _this$_worker.terminate();
4409
+ this._worker = null;
4410
+ this._performanceTracker.resetAll();
4411
+ }
4412
+ _getRuntime() {
4413
+ if (this._runtime == null) throw new Error("Document layout Worker runtime is unavailable.");
4414
+ return this._runtime;
4415
+ }
4416
+ _withTimeout(task, operation) {
4417
+ let timeoutId = null;
4418
+ const timeout = new Promise((_resolve, reject) => {
4419
+ timeoutId = setTimeout(() => {
4420
+ reject(/* @__PURE__ */ new Error(`Document layout Worker timed out while attempting to ${operation}.`));
4421
+ }, this._requestTimeoutMs);
4422
+ });
4423
+ return Promise.race([task, timeout]).finally(() => {
4424
+ if (timeoutId != null) clearTimeout(timeoutId);
4425
+ });
4426
+ }
4427
+ async _verifyCapabilities() {
4428
+ const capabilities = await this._getRuntime().getCapabilities();
4429
+ if (capabilities.protocolVersion !== 5) throw new DocsLayoutWorkerCapabilityError("Document layout Worker protocol version mismatch.");
4430
+ if (!capabilities.offscreenCanvas || !capabilities.structuredClone) throw new DocsLayoutWorkerCapabilityError("Document layout Worker requires OffscreenCanvas and structured clone support.");
4431
+ const mainMetrics = FontCache.getMeasureText(capabilities.fontProbe.content, capabilities.fontProbe.font);
4432
+ if ([
4433
+ [mainMetrics.width, capabilities.fontProbe.width],
4434
+ [mainMetrics.actualBoundingBoxAscent, capabilities.fontProbe.actualBoundingBoxAscent],
4435
+ [mainMetrics.actualBoundingBoxDescent, capabilities.fontProbe.actualBoundingBoxDescent]
4436
+ ].some(([mainValue, workerValue]) => !Number.isFinite(mainValue) || !Number.isFinite(workerValue) || Math.abs(mainValue - workerValue) > FONT_METRICS_TOLERANCE)) throw new DocsLayoutWorkerCapabilityError("Document layout Worker font metrics differ from the main rendering context.");
4437
+ }
4438
+ };
4439
+ let UniverDocsLayoutWorkerPlugin = class UniverDocsLayoutWorkerPlugin extends Plugin {
4440
+ constructor(_config = defaultPluginDocsLayoutWorkerConfig, _injector, _layoutExecutorService, _configService) {
4441
+ super();
4442
+ this._config = _config;
4443
+ this._injector = _injector;
4444
+ this._layoutExecutorService = _layoutExecutorService;
4445
+ this._configService = _configService;
4446
+ const { ...rest } = merge({}, defaultPluginDocsLayoutWorkerConfig, this._config);
4447
+ this._configService.setConfig(DOCS_LAYOUT_WORKER_PLUGIN_CONFIG_KEY, rest);
4448
+ }
4449
+ onStarting() {
4450
+ if (typeof this._config.workerFactory !== "function") throw new TypeError("[UniverDocsLayoutWorkerPlugin]: workerFactory is required.");
4451
+ const requestTimeoutMs = this._config.requestTimeoutMs ?? 15e3;
4452
+ if (!Number.isFinite(requestTimeoutMs) || requestTimeoutMs <= 0) throw new RangeError("[UniverDocsLayoutWorkerPlugin]: requestTimeoutMs must be a positive finite number.");
4453
+ const client = new DocsLayoutWorkerClientService(this._config.workerFactory, requestTimeoutMs, this._config.fontFaces);
4454
+ this._injector.add([DocsLayoutWorkerClientService, { useValue: client }]);
4455
+ this.disposeWithMe(client);
4456
+ this.disposeWithMe(this._layoutExecutorService.register(client));
4457
+ }
4458
+ };
4459
+ _defineProperty(UniverDocsLayoutWorkerPlugin, "pluginName", "DOCS_LAYOUT_WORKER_PLUGIN");
4460
+ _defineProperty(UniverDocsLayoutWorkerPlugin, "packageName", name);
4461
+ _defineProperty(UniverDocsLayoutWorkerPlugin, "version", version);
4462
+ UniverDocsLayoutWorkerPlugin = __decorate([
4463
+ DependentOn(UniverDocsPlugin),
4464
+ __decorateParam(1, Inject(Injector)),
4465
+ __decorateParam(2, Inject(DocLayoutExecutorService)),
4466
+ __decorateParam(3, IConfigService)
4467
+ ], UniverDocsLayoutWorkerPlugin);
4468
+
1873
4469
  //#endregion
1874
4470
  //#region src/services/doc-interceptor/interceptor-const.ts
1875
4471
  const CUSTOM_RANGE = createInterceptorKey("CUSTOM_RANGE");
@@ -1917,7 +4513,8 @@ let DocInterceptorService = class DocInterceptorService extends Disposable {
1917
4513
  }
1918
4514
  fetchThroughInterceptors(name) {
1919
4515
  const key = name;
1920
- return composeInterceptors(this._interceptorsByName.get(key) || []);
4516
+ const interceptors = this._interceptorsByName.get(key);
4517
+ return composeInterceptors(interceptors || []);
1921
4518
  }
1922
4519
  interceptDocumentViewModel(viewModel) {
1923
4520
  const disposableCollection = new DisposableCollection();
@@ -1926,6 +4523,7 @@ let DocInterceptorService = class DocInterceptorService extends Disposable {
1926
4523
  return this.fetchThroughInterceptors(DOC_INTERCEPTOR_POINT.CUSTOM_RANGE)(viewModel.getCustomRangeRaw(index), {
1927
4524
  index,
1928
4525
  unitId: viewModel.getDataModel().getUnitId(),
4526
+ body: viewModel.getDataModel().getBody(),
1929
4527
  customRanges: viewModel.getDataModel().getCustomRanges() ?? []
1930
4528
  });
1931
4529
  },
@@ -2187,4 +4785,4 @@ function normalizeTextRange(textRange) {
2187
4785
  }
2188
4786
 
2189
4787
  //#endregion
2190
- export { CreateHeaderFooterCommand, DOC_INTERCEPTOR_POINT, DOC_SELECTION_OPTION_PRESERVE_CARET, DeleteDocumentSectionBreakCommand, DeleteTextCommand, DocBlockMoveValidatorService, DocContentInsertService, DocInterceptorService, DocSelectionManagerService, DocSkeletonManagerService, DocStateChangeManagerService, DocStateEmitService, DocTextResolverService, EMBED_DOCS_CUSTOM_BLOCK_DEFAULT_COMPONENT_KEY, HeaderFooterType, IDocStateChangeInterceptorService, InsertDocumentColumnBreakCommand, InsertDocumentSectionBreakCommand, InsertTextCommand, RichTextEditingMutation, SetDocumentDefaultParagraphStyleCommand, SetDocumentNameCommand, SetSectionHeaderFooterLinkCommand, SetTextSelectionsOperation, UniverDocsPlugin, UpdateDocumentParagraphStyleCommand, UpdateDocumentSectionCommand, UpdateTextCommand, addCustomRangeBySelectionFactory, addCustomRangeFactory, buildDocTransform, consumeContentInsertRange, createDocsCustomBlockDrawing, createDocsCustomBlockInsertMutation, createDocsCustomBlockRemoveMutation, createEmbedDocsCustomBlockData, createInsertCustomBlockActions, createRemoveCustomBlockActions, createSectionColumnProperties, deleteCustomRangeFactory, docDrawingPositionToTransform, generateParagraphs, getContentInsertRange, getEffectiveSectionPageSetup, getSectionContentWidth, getTopLevelSectionBreaks, isEmbedDocsCustomBlockData, isHeaderFooterSelection, isSheetLikeDocsCustomBlockChildType, normalizeTextRange, replaceSelectionFactory, resolveDocsCustomBlockSize, shouldUseInlineTextSelectionForDocsCustomBlockDrawing, transformToDocDrawingPosition };
4788
+ export { CreateHeaderFooterCommand, DOCUMENT_UNIT_PERMISSION_ACTIONS, DOC_INTERCEPTOR_POINT, DOC_SELECTION_OPTION_PRESERVE_CARET, DeleteDocumentSectionBreakCommand, DeleteTextCommand, DocBlockMoveValidatorService, DocContentInsertService, DocHistoryAction, DocInterceptorService, DocLayoutExecutorService, DocLayoutExecutorState, DocLayoutExecutorType, DocLayoutSessionStatus, DocSelectionManagerService, DocSkeletonManagerService, DocStateChangeManagerService, DocStateEmitService, DocTextResolverService, DocsLayoutWorkerCapabilityError, DocsLayoutWorkerClientService, EMBED_DOCS_CUSTOM_BLOCK_DEFAULT_COMPONENT_KEY, HeaderFooterType, IDocStateChangeInterceptorService, InsertDocumentColumnBreakCommand, InsertDocumentSectionBreakCommand, InsertTextCommand, RichTextEditingMutation, SetDocumentDefaultParagraphStyleCommand, SetDocumentNameCommand, SetDocumentPermissionCommand, SetDocumentPermissionRuleMutation, SetDocumentPermissionRulesMutation, SetDocumentPermissionsCommand, SetSectionHeaderFooterLinkCommand, SetTextSelectionsOperation, UniverDocsLayoutWorkerPlugin, UniverDocsPlugin, UpdateDocumentParagraphStyleCommand, UpdateDocumentSectionCommand, UpdateTextCommand, addCustomRangeBySelectionFactory, addCustomRangeFactory, buildDocTransform, canEditDocumentTargets, consumeContentInsertRange, createDocsCustomBlockDrawing, createDocsCustomBlockInsertMutation, createDocsCustomBlockRemoveMutation, createDocumentPermissionPoint, createEmbedDocsCustomBlockData, createInsertCustomBlockActions, createRemoveCustomBlockActions, createSectionColumnProperties, deleteCustomRangeFactory, docDrawingPositionToTransform, generateParagraphs, getContentInsertRange, getDocumentDrawingSegmentId, getDocumentEditTargetObjectIds, getDocumentEntityParentPermissionObjectIds, getDocumentEntityPermissionObjectId, getDocumentParagraphParentPermissionObjectIds, getDocumentParagraphPermissionObjectId, getDocumentPermissionValue, getDocumentSectionPermissionObjectId, getEffectiveSectionPageSetup, getSectionContentWidth, getTopLevelSectionBreaks, isEmbedDocsCustomBlockData, isHeaderFooterSelection, isSheetLikeDocsCustomBlockChildType, normalizeTextRange, replaceSelectionFactory, resolveDocsCustomBlockSize, setDocumentPermissionValue, shouldUseInlineTextSelectionForDocsCustomBlockDrawing, startDocsLayoutWorker, transformDocumentTextRanges, transformToDocDrawingPosition };