Skip to content

Commit

Permalink
refactor(docs): refactor zen editor to use base-editor apis (#3551)
Browse files Browse the repository at this point in the history
Co-authored-by: zhangw <zhang9748@foxmail.com>
  • Loading branch information
Jocs and weird94 authored Sep 29, 2024
1 parent ae2297d commit 72e43d6
Show file tree
Hide file tree
Showing 33 changed files with 621 additions and 493 deletions.
6 changes: 3 additions & 3 deletions packages/core/src/shared/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@
* limitations under the License.
*/

import type { IKeyValue } from './types';

import { customAlphabet, nanoid } from 'nanoid';
import { isLegalUrl, normalizeUrl } from '../common/url';

import type { IKeyValue } from './types';

const rmsPrefix = /^-ms-/;
const rDashAlpha = /-([a-z])/g;

Expand Down Expand Up @@ -269,7 +269,7 @@ export class Tools {
}

function diffArrays(oneArray: any[], towArray: any[]) {
if (one.length !== tow.length) {
if (oneArray.length !== towArray.length) {
return false;
}
for (let i = 0, len = oneArray.length; i < len; i++) {
Expand Down
189 changes: 168 additions & 21 deletions packages/docs-ui/src/commands/commands/replace-content.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,143 @@
* limitations under the License.
*/

import { BuildTextUtils, CommandType, ICommandService, IUndoRedoService, IUniverInstanceService, JSONX, TextX, TextXActionType } from '@univerjs/core';
import { BuildTextUtils, CommandType, ICommandService, IUndoRedoService, IUniverInstanceService, JSONX, TextX, TextXActionType, Tools, UniverInstanceType } from '@univerjs/core';
import { DocSelectionManagerService, RichTextEditingMutation } from '@univerjs/docs';
import type { DocumentDataModel, ICommand, IDocumentBody, IMutationInfo, ITextRange } from '@univerjs/core';
import type { DocumentDataModel, ICommand, IDocumentBody, IDocumentData, IMutationInfo, ITextRange, JSONXActions } from '@univerjs/core';
import type { IRichTextEditingMutationParams } from '@univerjs/docs';
import type { ITextRangeWithStyle } from '@univerjs/engine-render';
import { getRichTextEditPath } from '../util';

interface IReplaceSnapshotCommandParams {
unitId: string;
snapshot: IDocumentData;
textRanges: ITextRangeWithStyle[];
segmentId?: string;
options: { [key: string]: boolean };
}

export const ReplaceSnapshotCommand: ICommand<IReplaceSnapshotCommandParams> = {
id: 'doc.command-replace-snapshot',
type: CommandType.COMMAND,
// eslint-disable-next-line max-lines-per-function, complexity
handler: async (accessor, params: IReplaceSnapshotCommandParams) => {
const { unitId, snapshot, textRanges, segmentId = '', options } = params;
const univerInstanceService = accessor.get(IUniverInstanceService);
const commandService = accessor.get(ICommandService);
const docSelectionManagerService = accessor.get(DocSelectionManagerService);

const docDataModel = univerInstanceService.getUnit<DocumentDataModel>(unitId, UniverInstanceType.UNIVER_DOC);
const prevSnapshot = docDataModel?.getSelfOrHeaderFooterModel(segmentId).getSnapshot();

if (docDataModel == null || prevSnapshot == null) {
return false;
}

const { body, tableSource, footers, headers, lists, drawings, drawingsOrder } = snapshot;
const {
body: prevBody,
tableSource: prevTableSource,
footers: prevFooters,
headers: prevHeaders,
lists: prevLists,
drawings: prevDrawings,
drawingsOrder: prevDrawingsOrder,
} = prevSnapshot;

if (body == null || prevBody == null) {
return false;
}

// Handle body is equal to previous prevBody, only set the text ranges.
if (Tools.diffValue(body, prevBody) && textRanges) {
docSelectionManagerService.replaceDocRanges(textRanges, {
unitId,
subUnitId: unitId,
}, false);

return true;
}

const doMutation: IMutationInfo<IRichTextEditingMutationParams> = {
id: RichTextEditingMutation.id,
params: {
unitId,
actions: [],
textRanges,
},
};

if (options) {
doMutation.params.options = options;
}

const rawActions: JSONXActions = [];

const jsonX = JSONX.getInstance();

if (!Tools.diffValue(body, prevBody)) {
const actions = jsonX.replaceOp(['body'], prevBody, body);
if (actions != null) {
rawActions.push(actions);
}
}

if (!Tools.diffValue(tableSource, prevTableSource)) {
const actions = jsonX.replaceOp(['tableSource'], prevTableSource, tableSource);
if (actions != null) {
rawActions.push(actions);
}
}

if (!Tools.diffValue(footers, prevFooters)) {
const actions = jsonX.replaceOp(['footers'], prevFooters, footers);
if (actions != null) {
rawActions.push(actions);
}
}

if (!Tools.diffValue(headers, prevHeaders)) {
const actions = jsonX.replaceOp(['headers'], prevHeaders, headers);
if (actions != null) {
rawActions.push(actions);
}
}

if (!Tools.diffValue(lists, prevLists)) {
const actions = jsonX.replaceOp(['lists'], prevLists, lists);
if (actions != null) {
rawActions.push(actions);
}
}

if (!Tools.diffValue(drawings, prevDrawings)) {
const actions = jsonX.replaceOp(['drawings'], prevDrawings, drawings);
if (actions != null) {
rawActions.push(actions);
}
}

if (!Tools.diffValue(drawingsOrder, prevDrawingsOrder)) {
const actions = jsonX.replaceOp(['drawingsOrder'], prevDrawingsOrder, drawingsOrder);
if (actions != null) {
rawActions.push(actions);
}
}

doMutation.params.actions = rawActions.reduce((acc, cur) => {
return JSONX.compose(acc, cur as JSONXActions);
}, null as JSONXActions);

const result = commandService.syncExecuteCommand<
IRichTextEditingMutationParams,
IRichTextEditingMutationParams
>(doMutation.id, doMutation.params);

return Boolean(result);
},

};

interface IReplaceContentCommandParams {
unitId: string;
body: IDocumentBody; // Do not contain `\r\n` at the end.
Expand All @@ -30,6 +160,9 @@ interface IReplaceContentCommandParams {
}

// Replace all content with new body, and reserve undo/redo stack.
/**
* @deprecated please use ReplaceSnapshotCommand instead.
*/
export const ReplaceContentCommand: ICommand<IReplaceContentCommandParams> = {
id: 'doc.command-replace-content',

Expand All @@ -41,22 +174,29 @@ export const ReplaceContentCommand: ICommand<IReplaceContentCommandParams> = {
const commandService = accessor.get(ICommandService);
const docSelectionManagerService = accessor.get(DocSelectionManagerService);

const docDataModel = univerInstanceService.getUniverDocInstance(unitId);
const prevBody = docDataModel?.getSnapshot().body;
const selections = docSelectionManagerService.getTextRanges();
const docDataModel = univerInstanceService.getUnit<DocumentDataModel>(unitId, UniverInstanceType.UNIVER_DOC);
const prevBody = docDataModel?.getSelfOrHeaderFooterModel(segmentId).getSnapshot().body;

if (docDataModel == null || prevBody == null) {
return false;
}

if (!Array.isArray(selections) || selections.length === 0) {
return false;
}

const doMutation = getMutationParams(unitId, segmentId, docDataModel, prevBody, body);

doMutation.params.textRanges = textRanges;
options && (doMutation.params.options = options);
if (options) {
doMutation.params.options = options;
}

// Handle body is equal to prevBody.
if (doMutation.params.actions == null && textRanges) {
docSelectionManagerService.replaceDocRanges(textRanges, {
unitId,
subUnitId: unitId,
}, false);

return true;
}

const result = commandService.syncExecuteCommand<
IRichTextEditingMutationParams,
Expand Down Expand Up @@ -110,16 +250,7 @@ export const CoverContentCommand: ICommand<ICoverContentCommandParams> = {
},
};

function getMutationParams(unitId: string, segmentId: string, docDatModel: DocumentDataModel, prevBody: IDocumentBody, body: IDocumentBody) {
const doMutation: IMutationInfo<IRichTextEditingMutationParams> = {
id: RichTextEditingMutation.id,
params: {
unitId,
actions: [],
textRanges: [],
},
};

function getMutationActions(segmentId: string, docDatModel: DocumentDataModel, prevBody: IDocumentBody, body: IDocumentBody) {
const textX = new TextX();
const jsonX = JSONX.getInstance();

Expand All @@ -144,7 +275,23 @@ function getMutationParams(unitId: string, segmentId: string, docDatModel: Docum
}

const path = getRichTextEditPath(docDatModel, segmentId);
doMutation.params.actions = jsonX.editOp(textX.serialize(), path);

return jsonX.editOp(textX.serialize(), path);
}

function getMutationParams(unitId: string, segmentId: string, docDatModel: DocumentDataModel, prevBody: IDocumentBody, body: IDocumentBody) {
const doMutation: IMutationInfo<IRichTextEditingMutationParams> = {
id: RichTextEditingMutation.id,
params: {
unitId,
actions: [],
textRanges: [],
},
};

const actions = getMutationActions(segmentId, docDatModel, prevBody, body);

doMutation.params.actions = actions;

return doMutation;
}
Expand Down
13 changes: 10 additions & 3 deletions packages/docs-ui/src/components/editor/TextEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import type { IDocumentData, Nullable } from '@univerjs/core';
import { isElementVisible } from '../../basics/editor';
import { IEditorService } from '../../services/editor/editor-manager.service';
import styles from './index.module.less';
import { genSnapShotByValue } from './utils';
import type { Editor, IEditorCanvasStyle } from '../../services/editor/editor';

type MyComponentProps = React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
Expand Down Expand Up @@ -82,7 +83,7 @@ export interface ITextEditorProps {

placeholder?: string; // Placeholder text.
isValueValid?: boolean; // Whether the value is valid.
disbaled?: boolean;
disabled?: boolean;
}

/**
Expand Down Expand Up @@ -151,9 +152,15 @@ export function TextEditor(props: ITextEditorProps & Omit<MyComponentProps, 'onC

resizeObserver.observe(editorDom);

const initialSnapshot = snapshot ?? genSnapShotByValue(id, value);

if (initialSnapshot.id !== id) {
initialSnapshot.id = id;
}

const registerSubscription = editorService.register({
editorUnitId: id,
initialSnapshot: snapshot,
initialSnapshot,
cancelDefaultResizeListener,
isSheetEditor,
canvasStyle,
Expand All @@ -180,7 +187,7 @@ export function TextEditor(props: ITextEditorProps & Omit<MyComponentProps, 'onC
// !IMPORTANT: Set a delay of 160ms to ensure that the position is corrected after the sidebar animation ends @jikkai
const ANIMATION_DELAY = 160;
const valueChange = debounce((editor: Readonly<Editor>) => {
const unitId = editor.editorUnitId;
const unitId = editor.getEditorId();
const isLegality = editorService.checkValueLegality(unitId);

setTimeout(() => {
Expand Down
60 changes: 60 additions & 0 deletions packages/docs-ui/src/components/editor/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* Copyright 2023-present DreamNum Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { DEFAULT_EMPTY_DOCUMENT_VALUE, DocumentFlavor } from '@univerjs/core';
import type { IDocumentData } from '@univerjs/core';

/**
*
* @param value The initial value of the editor.
* @returns snapshot generated by value.
*/
export function genSnapShotByValue(id = '', value = '') {
const dataStream = `${value}${DEFAULT_EMPTY_DOCUMENT_VALUE}`;
const paragraphs = [];
const sectionBreaks = [];

for (let i = 0; i < dataStream.length; i++) {
if (dataStream[i] === '\r') {
paragraphs.push({
startIndex: i,
});
}

if (dataStream[i] === '\n') {
sectionBreaks.push({
startIndex: i,
});
}
}

const snapshot: IDocumentData = {
id,
body: {
dataStream,
tables: [],
textRuns: [],
paragraphs,
sectionBreaks,
},
tableSource: {},
documentStyle: {
documentFlavor: DocumentFlavor.MODERN,
},
};

return snapshot;
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export class DocBackScrollRenderController extends RxDisposable implements IRend

const viewportMain = scene.getViewport(VIEWPORT_KEY.VIEW_MAIN);

const isEditor = !!this._editorService.getEditor(unitId);
const editor = this._editorService.getEditor(unitId);

if (viewportMain == null) {
return;
Expand All @@ -104,7 +104,7 @@ export class DocBackScrollRenderController extends RxDisposable implements IRend
let offsetY = 0;
let offsetX = 0;

const delta = isEditor ? 0 : 100;
const delta = editor ? editor.params.backScrollOffset ?? 0 : 100;

if (top < boundTop) {
offsetY = top - boundTop - delta;
Expand Down
Loading

0 comments on commit 72e43d6

Please sign in to comment.