diff --git a/packages/core/src/docs/data-model/text-x/build-utils/__tests__/drawings.spec.ts b/packages/core/src/docs/data-model/text-x/build-utils/__tests__/drawings.spec.ts index fd2a6c0413..db8304c63c 100644 --- a/packages/core/src/docs/data-model/text-x/build-utils/__tests__/drawings.spec.ts +++ b/packages/core/src/docs/data-model/text-x/build-utils/__tests__/drawings.spec.ts @@ -15,13 +15,72 @@ */ import { describe, expect, it } from 'vitest'; +import { PositionedObjectLayoutType } from '../../../../../types/interfaces/i-document-data'; import { DrawingTypeEnum } from '../../../../../types/interfaces/i-drawing'; import { DocumentDataModel } from '../../../document-data-model'; +import { JSONX } from '../../../json-x/json-x'; import { DataStreamTreeTokenType } from '../../../types'; import { getRichTextEditPath } from '../../utils'; -import { addDrawing, getCustomBlockIdsInSelections } from '../drawings'; +import { addDrawing, getCustomBlockIdsInSelections, removeDrawingReferences } from '../drawings'; describe('drawing build utils', () => { + it('removes drawing references in reverse order for a structural text deletion', () => { + const doc = new DocumentDataModel({ + id: 'doc-remove-drawings', + body: { + dataStream: '\bA\bB\b\r\n', + customBlocks: [ + { startIndex: 0, blockId: 'drawing-1' }, + { startIndex: 2, blockId: 'drawing-2' }, + { startIndex: 4, blockId: 'stale-drawing' }, + ], + }, + drawings: { + 'drawing-1': { + drawingId: 'drawing-1', + drawingType: DrawingTypeEnum.DRAWING_IMAGE, + docTransform: { + angle: 0, + positionH: { posOffset: 0, relativeFrom: 0 }, + positionV: { posOffset: 0, relativeFrom: 0 }, + size: { height: 40, width: 80 }, + }, + layoutType: PositionedObjectLayoutType.INLINE, + subUnitId: 'doc-remove-drawings', + unitId: 'doc-remove-drawings', + }, + 'drawing-2': { + drawingId: 'drawing-2', + drawingType: DrawingTypeEnum.DRAWING_SHAPE, + docTransform: { + angle: 0, + positionH: { posOffset: 0, relativeFrom: 0 }, + positionV: { posOffset: 0, relativeFrom: 0 }, + size: { height: 40, width: 80 }, + }, + layoutType: PositionedObjectLayoutType.WRAP_SQUARE, + subUnitId: 'doc-remove-drawings', + unitId: 'doc-remove-drawings', + }, + }, + drawingsOrder: ['drawing-1', 'stale-drawing', 'drawing-2'], + }); + + const actions = removeDrawingReferences(doc.getSnapshot(), [ + { startOffset: 0, endOffset: 5, collapsed: false }, + { startOffset: 2, endOffset: 5, collapsed: false }, + ]); + let composedActions = actions[0]; + for (const action of actions.slice(1)) { + composedActions = JSONX.compose(composedActions, action); + } + if (!composedActions) throw new Error('Expected drawing removal actions'); + doc.apply(composedActions); + + expect(doc.getDrawings()).toEqual({}); + expect(doc.getDrawingsOrder()).toEqual([]); + }); + it('anchors a drawing in the current table-cell paragraph at the cell structural tail', () => { const T = DataStreamTreeTokenType; const tableStream = `${T.TABLE_START}${T.TABLE_ROW_START}${T.TABLE_CELL_START}Cell${T.PARAGRAPH}${T.SECTION_BREAK}${T.TABLE_CELL_END}${T.TABLE_ROW_END}${T.TABLE_END}`; diff --git a/packages/core/src/docs/data-model/text-x/build-utils/drawings.ts b/packages/core/src/docs/data-model/text-x/build-utils/drawings.ts index 9c6baca4b3..cade1b03e0 100644 --- a/packages/core/src/docs/data-model/text-x/build-utils/drawings.ts +++ b/packages/core/src/docs/data-model/text-x/build-utils/drawings.ts @@ -15,7 +15,7 @@ */ import type { ITextRange, ITextRangeParam } from '../../../../sheets/typedef'; -import type { IDocumentBody, IDrawingParam } from '../../../../types/interfaces'; +import type { IDocumentBody, IDocumentData, IDrawingParam } from '../../../../types/interfaces'; import type { DocumentDataModel } from '../../document-data-model'; import type { JSONXActions } from '../../json-x/json-x'; import { createParagraphId } from '../../../paragraph-id'; @@ -55,6 +55,43 @@ export function getCustomBlockIdsInSelections(body: IDocumentBody, selections: I return customBlockIds; } +export function removeDrawingReferences( + documentData: Pick, + selections: ITextRange[], + body: IDocumentBody | undefined = documentData.body +): JSONXActions[] { + if (!body) { + return []; + } + + const drawings = documentData.drawings ?? {}; + const drawingOrder = documentData.drawingsOrder ?? []; + const blockIds = [...new Set(getCustomBlockIdsInSelections(body, selections))] + .sort((left, right) => drawingOrder.indexOf(right) - drawingOrder.indexOf(left)); + const jsonX = JSONX.getInstance(); + const actions: JSONXActions[] = []; + + for (const blockId of blockIds) { + const drawing = drawings[blockId]; + if (drawing != null) { + const removeDrawingAction = jsonX.removeOp(['drawings', blockId], drawing); + if (removeDrawingAction) { + actions.push(removeDrawingAction); + } + } + + const drawingIndex = drawingOrder.indexOf(blockId); + if (drawingIndex >= 0) { + const removeDrawingOrderAction = jsonX.removeOp(['drawingsOrder', drawingIndex], blockId); + if (removeDrawingOrderAction) { + actions.push(removeDrawingOrderAction); + } + } + } + + return actions; +} + // eslint-disable-next-line max-lines-per-function export const addDrawing = (param: IAddDrawingParam) => { const { selection, documentDataModel, drawings } = param; diff --git a/packages/core/src/docs/data-model/text-x/build-utils/index.ts b/packages/core/src/docs/data-model/text-x/build-utils/index.ts index 77957296e9..90b999139f 100644 --- a/packages/core/src/docs/data-model/text-x/build-utils/index.ts +++ b/packages/core/src/docs/data-model/text-x/build-utils/index.ts @@ -16,7 +16,7 @@ import { addCustomDecorationTextX, deleteCustomDecorationTextX } from './custom-decoration'; import { copyCustomRange, getCustomRangesInterestsWithSelection, isIntersecting } from './custom-range'; -import { addDrawing } from './drawings'; +import { addDrawing, removeDrawingReferences } from './drawings'; import { changeParagraphBulletNestLevel, setParagraphBullet, @@ -96,6 +96,7 @@ export class BuildTextUtils { static drawing = { add: addDrawing, + remove: removeDrawingReferences, }; } diff --git a/packages/docs/src/commands/commands/__tests__/core-editing.command.spec.ts b/packages/docs/src/commands/commands/__tests__/core-editing.command.spec.ts index 1b51dfa74b..4cbb80a182 100644 --- a/packages/docs/src/commands/commands/__tests__/core-editing.command.spec.ts +++ b/packages/docs/src/commands/commands/__tests__/core-editing.command.spec.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { DeleteDirection, getRichTextEditPath, ICommandService, JSONX, TextXActionType } from '@univerjs/core'; +import { BlockType, DeleteDirection, DrawingTypeEnum, getRichTextEditPath, ICommandService, IUndoRedoService, JSONX, PositionedObjectLayoutType, TextXActionType } from '@univerjs/core'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { createTestBed } from '../../../facade/__tests__/create-test-bed'; import { RichTextEditingMutation } from '../../mutations/core-editing.mutation'; @@ -48,6 +48,62 @@ describe('core editing commands', () => { expect(testBed.univerAPI.getActiveDocument()?.save().body?.dataStream).toBe('Hello\r\n'); }); + it('deletes a drawing with backspace and restores its references through history', () => { + testBed.univer.dispose(); + testBed = createTestBed({ + id: 'test', + body: { + dataStream: '\b\r\n', + paragraphs: [{ startIndex: 1, paragraphId: 'paragraph-drawing' }], + customBlocks: [{ blockId: 'drawing-1', blockType: BlockType.DRAWING, startIndex: 0 }], + }, + drawings: { + 'drawing-1': { + drawingId: 'drawing-1', + drawingType: DrawingTypeEnum.DRAWING_SHAPE, + docTransform: { + angle: 0, + positionH: { posOffset: 0, relativeFrom: 0 }, + positionV: { posOffset: 0, relativeFrom: 0 }, + size: { height: 40, width: 80 }, + }, + layoutType: PositionedObjectLayoutType.WRAP_SQUARE, + subUnitId: 'test', + unitId: 'test', + }, + }, + drawingsOrder: ['drawing-1'], + documentStyle: {}, + }); + commandService = testBed.get(ICommandService); + testBed.get(IUndoRedoService); + + expect(commandService.syncExecuteCommand(DeleteTextCommand.id, { + unitId: 'test', + range: { startOffset: 1, endOffset: 1, collapsed: true }, + direction: DeleteDirection.LEFT, + })).toBe(true); + expect(testBed.doc.getSnapshot()).toMatchObject({ + body: { customBlocks: [] }, + drawings: {}, + drawingsOrder: [], + }); + + expect(testBed.univerAPI.getActiveDocument()?.undo()).toBe(true); + expect(testBed.doc.getSnapshot()).toMatchObject({ + body: { customBlocks: [{ blockId: 'drawing-1', startIndex: 0 }] }, + drawings: { 'drawing-1': { drawingId: 'drawing-1' } }, + drawingsOrder: ['drawing-1'], + }); + + expect(testBed.univerAPI.getActiveDocument()?.redo()).toBe(true); + expect(testBed.doc.getSnapshot()).toMatchObject({ + body: { customBlocks: [] }, + drawings: {}, + drawingsOrder: [], + }); + }); + it('does not delete the document minimum paragraph sentinel', () => { const result = commandService.syncExecuteCommand(DeleteTextCommand.id, { unitId: 'test', diff --git a/packages/docs/src/commands/commands/core-editing.command.ts b/packages/docs/src/commands/commands/core-editing.command.ts index 21d93fd7b9..09d744e170 100644 --- a/packages/docs/src/commands/commands/core-editing.command.ts +++ b/packages/docs/src/commands/commands/core-editing.command.ts @@ -14,15 +14,7 @@ * limitations under the License. */ -import type { - DocumentDataModel, - ICommand, - IDocumentBody, - IDocumentData, - IMutationInfo, - ITextRange, - UpdateDocsAttributeType, -} from '@univerjs/core'; +import type { DocumentDataModel, ICommand, IDocumentBody, IDocumentData, IMutationInfo, ITextRange, Nullable, UpdateDocsAttributeType } from '@univerjs/core'; import type { ITextRangeWithStyle } from '@univerjs/engine-render'; import type { IRichTextEditingMutationParams } from '../mutations/core-editing.mutation'; import { @@ -46,6 +38,10 @@ export interface IInsertTextCommandParams { range: ITextRange; segmentId?: string; cursorOffset?: number; + debounce?: boolean; + textRanges?: Nullable; + noNeedSetTextRange?: boolean; + isEditing?: boolean; } /** @@ -56,7 +52,17 @@ export const InsertTextCommand: ICommand = { type: CommandType.COMMAND, handler: (accessor, params: IInsertTextCommandParams) => { const commandService = accessor.get(ICommandService); - const { range, segmentId, body, unitId, cursorOffset } = params; + const { + range, + segmentId, + body, + unitId, + cursorOffset, + debounce = true, + textRanges, + noNeedSetTextRange, + isEditing, + } = params; const docSelectionManagerService = accessor.get(DocSelectionManagerService); const univerInstanceService = accessor.get(IUniverInstanceService); @@ -67,7 +73,7 @@ export const InsertTextCommand: ICommand = { } const activeRange = docSelectionManagerService.getActiveTextRange(); - const rangeSegmentId = 'segmentId' in range ? (range as ITextRange & { segmentId?: string }).segmentId : undefined; + const rangeSegmentId = 'segmentId' in range && typeof range.segmentId === 'string' ? range.segmentId : undefined; const targetSegmentId = segmentId ?? rangeSegmentId ?? activeRange?.segmentId ?? ''; const originBody = docDataModel.getSelfOrHeaderFooterModel(targetSegmentId)?.getBody(); @@ -77,54 +83,26 @@ export const InsertTextCommand: ICommand = { const { startOffset, collapsed } = range; const cursorMove = cursorOffset ?? body.dataStream.length; - const textRanges = [ - { - startOffset: startOffset + cursorMove, - endOffset: startOffset + cursorMove, - style: activeRange?.style, - collapsed, - }, - ]; - - const doMutation: IMutationInfo = { - id: RichTextEditingMutation.id, - params: { - unitId, - actions: [], - textRanges, - debounce: true, - }, - }; - - const textX = new TextX(); - const jsonX = JSONX.getInstance(); - - if (collapsed) { - if (startOffset > 0) { - textX.push({ - t: TextXActionType.RETAIN, - len: startOffset, - }); - } - - textX.push({ - t: TextXActionType.INSERT, - body, - len: body.dataStream.length, - }); - } else { - const dos = BuildTextUtils.selection.delete([range], originBody, 0, body); - textX.push(...dos); - } - - doMutation.params.textRanges = [{ + const mutationTextRanges = textRanges ?? [{ startOffset: startOffset + cursorMove, endOffset: startOffset + cursorMove, collapsed, }]; - const path = getRichTextEditPath(docDataModel, segmentId); - doMutation.params.actions = jsonX.editOp(textX.serialize(), path); + const doMutation: IMutationInfo = { + id: RichTextEditingMutation.id, + params: { + unitId, + segmentId: targetSegmentId, + actions: [], + textRanges: mutationTextRanges, + debounce, + noNeedSetTextRange, + isEditing, + }, + }; + + doMutation.params.actions = buildInsertTextActions(docDataModel, originBody, range, body, targetSegmentId); const result = commandService.syncExecuteCommand< IRichTextEditingMutationParams, @@ -135,6 +113,47 @@ export const InsertTextCommand: ICommand = { }, }; +function buildInsertTextActions( + documentDataModel: DocumentDataModel, + originBody: IDocumentBody, + range: ITextRange, + insertBody: IDocumentBody, + segmentId?: string +): IRichTextEditingMutationParams['actions'] { + const { startOffset, endOffset, collapsed } = range; + const textX = new TextX(); + if (collapsed) { + if (startOffset > 0) { + textX.push({ t: TextXActionType.RETAIN, len: startOffset }); + } + textX.push({ t: TextXActionType.INSERT, body: insertBody, len: insertBody.dataStream.length }); + } else { + textX.push(...BuildTextUtils.selection.delete([range], originBody, 0, insertBody)); + } + + const textActions = JSONX.getInstance().editOp(textX.serialize(), getRichTextEditPath(documentDataModel, segmentId)); + return collapsed + ? textActions + : appendRemovedDrawingActions(textActions, documentDataModel, originBody, startOffset, endOffset); +} + +function appendRemovedDrawingActions( + textActions: IRichTextEditingMutationParams['actions'], + documentDataModel: DocumentDataModel, + body: IDocumentBody, + startOffset: number, + endOffset: number +): IRichTextEditingMutationParams['actions'] { + const drawingActions = BuildTextUtils.drawing.remove(documentDataModel.getSnapshot(), [{ + startOffset, + endOffset, + collapsed: false, + }], body); + const rawActions = [textActions, ...drawingActions]; + + return rawActions.reduce((accumulator, action) => JSONX.compose(accumulator, action)); +} + export interface IDeleteTextCommandParams { unitId: string; range: ITextRange; @@ -175,6 +194,7 @@ export const DeleteTextCommand: ICommand = { id: RichTextEditingMutation.id, params: { unitId, + segmentId, actions: [], textRanges: [{ startOffset: start, @@ -196,7 +216,13 @@ export const DeleteTextCommand: ICommand = { }], body)); const path = getRichTextEditPath(docDataModel, segmentId); - doMutation.params.actions = jsonX.editOp(textX.serialize(), path); + doMutation.params.actions = appendRemovedDrawingActions( + jsonX.editOp(textX.serialize(), path), + docDataModel, + body, + start, + end + 1 + ); const result = commandService.syncExecuteCommand< IRichTextEditingMutationParams, diff --git a/packages/docs/src/facade/__tests__/f-document.spec.ts b/packages/docs/src/facade/__tests__/f-document.spec.ts index b4b8bc8ed8..75610ca4f0 100644 --- a/packages/docs/src/facade/__tests__/f-document.spec.ts +++ b/packages/docs/src/facade/__tests__/f-document.spec.ts @@ -16,9 +16,9 @@ import type { IDocumentData, Univer } from '@univerjs/core'; import type { FDocument } from '../f-document'; -import { ColumnSeparatorType, DataStreamTreeTokenType, DocumentFlavor, ICommandService, IResourceManagerService, IUndoRedoService, PageOrientType, SectionType, UniverInstanceType } from '@univerjs/core'; -import { InsertTextCommand } from '@univerjs/docs'; -import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { BlockType, ColumnSeparatorType, DataStreamTreeTokenType, DocumentFlavor, DrawingTypeEnum, ICommandService, IResourceManagerService, IUndoRedoService, PageOrientType, PositionedObjectLayoutType, SectionType, UniverInstanceType } from '@univerjs/core'; +import { DocSelectionManagerService, InsertTextCommand } from '@univerjs/docs'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { createDocumentData, createSimpleDocument, createTestBed } from './create-test-bed'; describe('FDocument', () => { @@ -187,6 +187,90 @@ describe('FDocument', () => { expect(document.getParagraphs()[0].getText()).toBe('Document title suffix'); }); + it('deletes drawing references atomically without changing the interactive selection', async () => { + univer.dispose(); + const data = createDocumentData('drawing-delete-doc', { + dataStream: '\b\r\n', + paragraphs: [{ startIndex: 1, paragraphId: 'para-drawing' }], + customBlocks: [{ blockId: 'drawing-1', blockType: BlockType.DRAWING, startIndex: 0 }], + }); + data.drawings = { 'drawing-1': createDrawingData('drawing-delete-doc', 'drawing-1') }; + data.drawingsOrder = ['drawing-1']; + createDocumentFacade(data); + get(IUndoRedoService); + const replaceDocRanges = vi.spyOn(get(DocSelectionManagerService), 'replaceDocRanges'); + + expect(document.deleteRange({ startOffset: 0, endOffset: 1 })).toBe(true); + await Promise.resolve(); + expect(replaceDocRanges).not.toHaveBeenCalled(); + expect(document.save()).toMatchObject({ + body: { customBlocks: [] }, + drawings: {}, + drawingsOrder: [], + }); + + expect(document.undo()).toBe(true); + expect(document.save()).toMatchObject({ + body: { customBlocks: [{ blockId: 'drawing-1', startIndex: 0 }] }, + drawings: { 'drawing-1': { drawingId: 'drawing-1' } }, + drawingsOrder: ['drawing-1'], + }); + + expect(document.redo()).toBe(true); + expect(document.save()).toMatchObject({ + body: { customBlocks: [] }, + drawings: {}, + drawingsOrder: [], + }); + }); + + it('deletes header drawing references in the header segment history', () => { + univer.dispose(); + const data = createDocumentData('header-drawing-delete-doc', { + dataStream: 'Body\r\n', + paragraphs: [{ startIndex: 4, paragraphId: 'body-paragraph' }], + }); + data.headers = { + 'header-drawing': { + headerId: 'header-drawing', + body: { + dataStream: '\b\r\n', + paragraphs: [{ startIndex: 1, paragraphId: 'header-paragraph' }], + customBlocks: [{ blockId: 'header-shape', blockType: BlockType.DRAWING, startIndex: 0 }], + }, + }, + }; + data.drawings = { 'header-shape': createDrawingData(data.id, 'header-shape') }; + data.drawingsOrder = ['header-shape']; + createDocumentFacade(data); + get(IUndoRedoService); + + expect(document.deleteRange({ startOffset: 0, endOffset: 1, segmentId: 'header-drawing' })).toBe(true); + expect(document.save()).toMatchObject({ + headers: { 'header-drawing': { body: { customBlocks: [] } } }, + drawings: {}, + drawingsOrder: [], + }); + + expect(document.undo()).toBe(true); + expect(document.save()).toMatchObject({ + headers: { + 'header-drawing': { + body: { customBlocks: [{ blockId: 'header-shape', startIndex: 0 }] }, + }, + }, + drawings: { 'header-shape': { drawingId: 'header-shape' } }, + drawingsOrder: ['header-shape'], + }); + + expect(document.redo()).toBe(true); + expect(document.save()).toMatchObject({ + headers: { 'header-drawing': { body: { customBlocks: [] } } }, + drawings: {}, + drawingsOrder: [], + }); + }); + it('ensures header and footer segments independently', () => { univer.dispose(); const documentData = createDocumentData('classic-doc', { @@ -542,3 +626,19 @@ describe('FDocument', () => { expect(rule?.getInfo().paragraph.paragraphStyle?.spaceBelow).toEqual({ v: 10 }); }); }); + +function createDrawingData(unitId: string, drawingId: string) { + return { + drawingId, + drawingType: DrawingTypeEnum.DRAWING_SHAPE, + docTransform: { + angle: 0, + positionH: { posOffset: 0, relativeFrom: 0 }, + positionV: { posOffset: 0, relativeFrom: 0 }, + size: { height: 40, width: 80 }, + }, + layoutType: PositionedObjectLayoutType.WRAP_SQUARE, + subUnitId: unitId, + unitId, + }; +} diff --git a/packages/docs/src/facade/f-document-paragraph.ts b/packages/docs/src/facade/f-document-paragraph.ts index 1886a35dd9..ad87a67f72 100644 --- a/packages/docs/src/facade/f-document-paragraph.ts +++ b/packages/docs/src/facade/f-document-paragraph.ts @@ -294,7 +294,7 @@ export class FDocumentParagraph extends FBaseInitialable { }, buildPlainTextInsertBody(text), this._document.getDocumentDataModel(), - this._injector + this._commandService ); } diff --git a/packages/docs/src/facade/f-document-text-range.ts b/packages/docs/src/facade/f-document-text-range.ts index e2d9727200..04ecb64c6c 100644 --- a/packages/docs/src/facade/f-document-text-range.ts +++ b/packages/docs/src/facade/f-document-text-range.ts @@ -17,7 +17,7 @@ import type { Injector, ITextStyle } from '@univerjs/core'; import type { FDocument } from './f-document'; import type { IFDocumentTextRange } from './utils'; -import { Tools, UpdateDocsAttributeType } from '@univerjs/core'; +import { ICommandService, Tools, UpdateDocsAttributeType } from '@univerjs/core'; import { FBaseInitialable } from '@univerjs/core/facade'; import { buildPlainTextInsertBody, replaceBodyRange, retainBodyRange } from './utils'; @@ -54,7 +54,8 @@ export class FDocumentTextRange extends FBaseInitialable { protected readonly _startOffset: number, protected readonly _endOffset: number, protected readonly _segmentId: string, - protected override readonly _injector: Injector + protected override readonly _injector: Injector, + @ICommandService private readonly _commandService: ICommandService ) { super(_injector); this._validateRange(); @@ -207,7 +208,7 @@ export class FDocumentTextRange extends FBaseInitialable { this.getRange(), buildPlainTextInsertBody(text), this._document.getDocumentDataModel(), - this._injector + this._commandService ); } diff --git a/packages/docs/src/facade/f-document.ts b/packages/docs/src/facade/f-document.ts index a90d21925b..bd3870f13b 100644 --- a/packages/docs/src/facade/f-document.ts +++ b/packages/docs/src/facade/f-document.ts @@ -384,7 +384,7 @@ export class FDocument extends FBaseInitialable { }, buildPlainTextInsertBody(text), this._documentDataModel, - this._injector + this._commandService ); } @@ -633,7 +633,7 @@ export class FDocument extends FBaseInitialable { paragraphs, }, this._documentDataModel, - this._injector + this._commandService ); return success ? this.getParagraph(paragraphId, segmentId) : null; @@ -766,7 +766,7 @@ export class FDocument extends FBaseInitialable { }, buildPlainTextInsertBody(`${text}\r`), this._documentDataModel, - this._injector + this._commandService ); if (!result) { @@ -828,7 +828,7 @@ export class FDocument extends FBaseInitialable { dataStream: '', }, this._documentDataModel, - this._injector + this._commandService ); } diff --git a/packages/docs/src/facade/utils.ts b/packages/docs/src/facade/utils.ts index 4ce73a3243..de21b8a9fd 100644 --- a/packages/docs/src/facade/utils.ts +++ b/packages/docs/src/facade/utils.ts @@ -16,8 +16,16 @@ import type { DocumentDataModel, IDocumentBody, Injector, IParagraphStyle, UpdateDocsAttributeType } from '@univerjs/core'; import type { IRichTextEditingMutationParams } from '@univerjs/docs'; -import { createParagraphId, DataStreamTreeTokenType, getRichTextEditPath, ICommandService, JSONX, TextX, TextXActionType } from '@univerjs/core'; -import { RichTextEditingMutation } from '@univerjs/docs'; +import { + createParagraphId, + DataStreamTreeTokenType, + getRichTextEditPath, + ICommandService, + JSONX, + TextX, + TextXActionType, +} from '@univerjs/core'; +import { InsertTextCommand, RichTextEditingMutation } from '@univerjs/docs'; export interface IBuildPlainTextInsertBodyOptions { paragraphStyle?: IParagraphStyle; @@ -114,43 +122,29 @@ export function replaceBodyRange( range: IFDocumentTextRange, insertBody: IDocumentBody, docDataModel: DocumentDataModel, - injector: Injector + commandService: ICommandService ): boolean { const { startOffset, endOffset, segmentId } = range; - const textX = new TextX(); - - if (startOffset > 0) { - textX.push({ t: TextXActionType.RETAIN, len: startOffset }); - } - - if (endOffset > startOffset) { - textX.push({ t: TextXActionType.DELETE, len: endOffset - startOffset }); - } - - if (insertBody.dataStream.length > 0) { - textX.push({ - t: TextXActionType.INSERT, - body: insertBody, - len: insertBody.dataStream.length, - }); - } - - const jsonX = JSONX.getInstance(); - const actions = jsonX.editOp(textX.serialize(), getRichTextEditPath(docDataModel, segmentId)); - - const commandService = injector.get(ICommandService); - const result = commandService.syncExecuteCommand( - RichTextEditingMutation.id, + const result = commandService.syncExecuteCommand( + InsertTextCommand.id, { unitId: docDataModel.getUnitId(), + body: insertBody, + range: { + startOffset, + endOffset, + collapsed: startOffset === endOffset, + segmentId, + }, segmentId, - actions, + debounce: false, textRanges: [], + noNeedSetTextRange: true, isEditing: false, } ); - return Boolean(result?.actions && result.actions.length > 0); + return Boolean(result); } export function retainBodyRange(