Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(docs-ui): cache menu style #3939

Merged
merged 4 commits into from
Nov 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions packages/core/src/docs/data-model/text-x/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import type { ICustomBlock, ICustomDecoration, ICustomRange, IDocumentBody, IPar
import type { IRetainAction } from './action-types';
import { UpdateDocsAttributeType } from '../../../shared/command-enum';
import { Tools } from '../../../shared/tools';
import { normalizeTextRuns } from './apply-utils/common';
import { coverTextRuns } from './apply-utils/update-apply';

export enum SliceBodyType {
Expand Down Expand Up @@ -65,14 +66,16 @@ export function getBodySlice(
}
}

docBody.textRuns = newTextRuns.map((tr) => {
const { st, ed } = tr;
return {
...tr,
st: st - startOffset,
ed: ed - startOffset,
};
});
docBody.textRuns = normalizeTextRuns(
newTextRuns.map((tr) => {
const { st, ed } = tr;
return {
...tr,
st: st - startOffset,
ed: ed - startOffset,
};
})
);
} else if (returnEmptyArray) {
// In the case of no style before, add the style, removeTextRuns will be empty,
// in this case, you need to add an empty textRun for undo.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ describe('Test inline format commands', () => {
startOffset: 0,
endOffset: 5,
collapsed: false,
isActive: true,
},
]);

Expand Down
184 changes: 122 additions & 62 deletions packages/docs-ui/src/commands/commands/inline-format.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,19 @@
import type {
DocumentDataModel,
ICommand, IDocumentBody, IMutationInfo, IStyleBase, ITextDecoration, ITextRun,
ITextStyle,
Nullable,
} from '@univerjs/core';
import type { IRichTextEditingMutationParams } from '@univerjs/docs';
import type { ITextRangeWithStyle } from '@univerjs/engine-render';
import {
BaselineOffset, BooleanNumber, CommandType,
DOC_RANGE_TYPE,
getBodySlice,
ICommandService, IUniverInstanceService,
JSONX, MemoryCursor,
TextX, TextXActionType,
Tools,
UniverInstanceType,
} from '@univerjs/core';
import { DocSelectionManagerService, RichTextEditingMutation } from '@univerjs/docs';
Expand Down Expand Up @@ -231,7 +235,7 @@ const COMMAND_ID_TO_FORMAT_KEY_MAP: Record<string, keyof IStyleBase> = {
export const SetInlineFormatCommand: ICommand<ISetInlineFormatCommandParams> = {
id: 'doc.command.set-inline-format',
type: CommandType.COMMAND,
// eslint-disable-next-line max-lines-per-function
// eslint-disable-next-line max-lines-per-function, complexity
handler: async (accessor, params: ISetInlineFormatCommandParams) => {
const { value, preCommandId } = params;
const commandService = accessor.get(ICommandService);
Expand All @@ -240,8 +244,9 @@ export const SetInlineFormatCommand: ICommand<ISetInlineFormatCommandParams> = {
const docMenuStyleService = accessor.get(DocMenuStyleService);

const docRanges = docSelectionManagerService.getDocRanges();
const activeTextRange = docSelectionManagerService.getActiveTextRange();

if (docRanges.length === 0) {
if (docRanges.length === 0 || activeTextRange == null) {
return false;
}

Expand All @@ -252,6 +257,12 @@ export const SetInlineFormatCommand: ICommand<ISetInlineFormatCommandParams> = {
return false;
}

const body = docDataModel.getSelfOrHeaderFooterModel(segmentId).getBody();

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

const unitId = docDataModel.getUnitId();

let formatValue;
Expand All @@ -263,10 +274,16 @@ export const SetInlineFormatCommand: ICommand<ISetInlineFormatCommandParams> = {
case SetInlineFormatStrikethroughCommand.id: // fallthrough
case SetInlineFormatSubscriptCommand.id: // fallthrough
case SetInlineFormatSuperscriptCommand.id: {
const defaultStyle = docMenuStyleService.getDefaultStyle();
const curTextStyle = getStyleInTextRange(
body,
activeTextRange,
defaultStyle
);

formatValue = getReverseFormatValueInSelection(
docDataModel.getSelfOrHeaderFooterModel(segmentId).getBody()!.textRuns!,
preCommandId,
docRanges
curTextStyle,
preCommandId
);

break;
Expand Down Expand Up @@ -327,9 +344,18 @@ export const SetInlineFormatCommand: ICommand<ISetInlineFormatCommandParams> = {

if (startOffset === endOffset) {
// Cache the menu style for next input.
const cacheStyle = docMenuStyleService.getStyleCache();
const key = COMMAND_ID_TO_FORMAT_KEY_MAP[preCommandId];

docMenuStyleService.setStyleCache(
{
[COMMAND_ID_TO_FORMAT_KEY_MAP[preCommandId]]: formatValue,
[key]: cacheStyle?.[key] !== undefined
? getReverseFormatValue(
cacheStyle,
key,
preCommandId
)
: formatValue,
}
);
continue;
Expand Down Expand Up @@ -383,71 +409,105 @@ function isTextDecoration(value: unknown | ITextDecoration): value is ITextDecor
return value !== null && typeof value === 'object';
}

/**
* When clicking on a Bold menu item, you should un-bold if there is bold in the selections,
* or bold if there is no bold text. This method is used to get the reverse style value calculated
* from textRuns in the selection
*/
// eslint-disable-next-line complexity
function getReverseFormatValueInSelection(
textRuns: ITextRun[],
preCommandId: string,
docRanges: ITextRangeWithStyle[]
): BooleanNumber | ITextDecoration | BaselineOffset {
let ti = 0;
let si = 0;
const key: keyof IStyleBase = COMMAND_ID_TO_FORMAT_KEY_MAP[preCommandId];

while (ti !== textRuns.length && si !== docRanges.length) {
const { startOffset, endOffset } = docRanges[si];
function getReverseFormatValue(ts: Nullable<ITextStyle>, key: keyof IStyleBase, preCommandId: string) {
if (/bl|it/.test(key)) {
return ts?.[key] === BooleanNumber.TRUE ? BooleanNumber.FALSE : BooleanNumber.TRUE;
}

// TODO: @jocs handle sid in textRun
const { st, ed, ts } = textRuns[ti];
if (/ul|st/.test(key)) {
return isTextDecoration(ts?.[key]) && (ts?.[key] as ITextDecoration).s === BooleanNumber.TRUE
? {
s: BooleanNumber.FALSE,
}
: {
s: BooleanNumber.TRUE,
};
}

if (endOffset! <= st) {
si++;
} else if (ed <= startOffset!) {
ti++;
if (/va/.test(key)) {
if (preCommandId === SetInlineFormatSubscriptCommand.id) {
return ts?.[key] === BaselineOffset.SUBSCRIPT
? BaselineOffset.NORMAL
: BaselineOffset.SUBSCRIPT;
} else {
if (/bl|it/.test(key)) {
return ts?.[key] === BooleanNumber.TRUE ? BooleanNumber.FALSE : BooleanNumber.TRUE;
}
return ts?.[key] === BaselineOffset.SUPERSCRIPT
? BaselineOffset.NORMAL
: BaselineOffset.SUPERSCRIPT;
}
}
}

if (/ul|st/.test(key)) {
return isTextDecoration(ts?.[key]) && (ts?.[key] as ITextDecoration).s === BooleanNumber.TRUE
? {
s: BooleanNumber.FALSE,
}
: {
s: BooleanNumber.TRUE,
};
// eslint-disable-next-line complexity
export function getStyleInTextRange(
body: IDocumentBody,
textRange: ITextRangeWithStyle,
defaultStyle: ITextStyle
): ITextStyle {
const { startOffset, endOffset, collapsed } = textRange;

if (collapsed) {
const textRuns = body.textRuns ?? [];
let textRun: Nullable<ITextRun> = null;

for (let i = textRuns.length - 1; i >= 0; i--) {
const curTextRun = textRuns[i];
if (curTextRun.st < startOffset && startOffset <= curTextRun.ed) {
textRun = curTextRun;
break;
}
}

return textRun?.ts ? { ...defaultStyle, ...textRun.ts } : defaultStyle;
}

const { textRuns = [] } = getBodySlice(body, startOffset, endOffset);

const style = Tools.deepClone(defaultStyle);

if (/va/.test(key)) {
if (preCommandId === SetInlineFormatSubscriptCommand.id) {
return ts?.[key] === BaselineOffset.SUBSCRIPT
? BaselineOffset.NORMAL
: BaselineOffset.SUBSCRIPT;
} else {
return ts?.[key] === BaselineOffset.SUPERSCRIPT
? BaselineOffset.NORMAL
: BaselineOffset.SUPERSCRIPT;
}
// Get the min font size in range.
style.fs = Math.max(style.fs!, ...textRuns.map((t) => t?.ts?.fs ?? style.fs!));
style.ff = textRuns.find((t) => t.ts?.ff != null)?.ts?.ff ?? style.ff;
style.it = textRuns.length && textRuns.every((t) => t.ts?.it === BooleanNumber.TRUE) ? BooleanNumber.TRUE : BooleanNumber.FALSE;
style.bl = textRuns.length && textRuns.every((t) => t.ts?.bl === BooleanNumber.TRUE) ? BooleanNumber.TRUE : BooleanNumber.FALSE;
style.ul = textRuns.length && textRuns.every((t) => t.ts?.ul?.s === BooleanNumber.TRUE) ? textRuns[0].ts?.ul : style.ul;
style.st = textRuns.length && textRuns.every((t) => t.ts?.st?.s === BooleanNumber.TRUE) ? textRuns[0].ts?.st : style.st;
style.bg = textRuns.find((t) => t.ts?.bg != null)?.ts?.bg ?? style.bg;
style.cl = textRuns.find((t) => t.ts?.cl != null)?.ts?.cl ?? style.cl;

const vas = textRuns.filter((t) => t?.ts?.va != null);

if (vas.length > 0 && vas.length === textRuns.length) {
const va = vas[0].ts?.va;
let isSame = true;

for (let i = 1; i < vas.length; i++) {
if (vas[i].ts?.va !== va) {
isSame = false;
break;
}
}

ti++;
if (isSame) {
style.va = va;
}
}

if (/bl|it/.test(key)) {
return BooleanNumber.TRUE;
} else if (/ul|st/.test(key)) {
return {
s: BooleanNumber.TRUE,
};
} else {
return preCommandId === SetInlineFormatSubscriptCommand.id
? BaselineOffset.SUBSCRIPT
: BaselineOffset.SUPERSCRIPT;
}
return style;
}

/**
* When clicking on a Bold menu item, you should un-bold if there is bold in the selections,
* or bold if there is no bold text. This method is used to get the reverse style value calculated
* from textRuns in the selection
*/

function getReverseFormatValueInSelection(
textStyle: ITextStyle,
preCommandId: string
): BooleanNumber | ITextDecoration | BaselineOffset {
const key: keyof IStyleBase = COMMAND_ID_TO_FORMAT_KEY_MAP[preCommandId];

const reverseValue = getReverseFormatValue(textStyle, key, preCommandId)!;

return reverseValue;
}
24 changes: 6 additions & 18 deletions packages/docs-ui/src/controllers/menu/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import {

import { combineLatest, map, Observable } from 'rxjs';
import { OpenHeaderFooterPanelCommand } from '../../commands/commands/doc-header-footer.command';
import { ResetInlineFormatTextBackgroundColorCommand, SetInlineFormatBoldCommand, SetInlineFormatCommand, SetInlineFormatFontFamilyCommand, SetInlineFormatFontSizeCommand, SetInlineFormatItalicCommand, SetInlineFormatStrikethroughCommand, SetInlineFormatSubscriptCommand, SetInlineFormatSuperscriptCommand, SetInlineFormatTextBackgroundColorCommand, SetInlineFormatTextColorCommand, SetInlineFormatUnderlineCommand } from '../../commands/commands/inline-format.command';
import { getStyleInTextRange, ResetInlineFormatTextBackgroundColorCommand, SetInlineFormatBoldCommand, SetInlineFormatCommand, SetInlineFormatFontFamilyCommand, SetInlineFormatFontSizeCommand, SetInlineFormatItalicCommand, SetInlineFormatStrikethroughCommand, SetInlineFormatSubscriptCommand, SetInlineFormatSuperscriptCommand, SetInlineFormatTextBackgroundColorCommand, SetInlineFormatTextColorCommand, SetInlineFormatUnderlineCommand } from '../../commands/commands/inline-format.command';
import { BulletListCommand, CheckListCommand, getParagraphsInRange, OrderListCommand } from '../../commands/commands/list.command';
import { AlignCenterCommand, AlignJustifyCommand, AlignLeftCommand, AlignOperationCommand, AlignRightCommand } from '../../commands/commands/paragraph-align.command';
import { SwitchDocModeCommand } from '../../commands/commands/switch-doc-mode.command';
Expand Down Expand Up @@ -939,11 +939,10 @@ function getFontStyleAtCursor(accessor: IAccessor) {
};
}

const { startOffset, segmentId } = activeTextRange;

const textRuns = docDataModel.getSelfOrHeaderFooterModel(segmentId).getBody()?.textRuns;
const { segmentId } = activeTextRange;
const body = docDataModel.getSelfOrHeaderFooterModel(segmentId).getBody();

if (textRuns == null) {
if (body == null) {
return {
ts: {
...defaultTextStyle,
Expand All @@ -952,22 +951,11 @@ function getFontStyleAtCursor(accessor: IAccessor) {
};
}

let textRun;

for (let i = textRuns.length - 1; i >= 0; i--) {
const curTextRun = textRuns[i];

if (curTextRun.st < startOffset && startOffset <= curTextRun.ed) {
textRun = curTextRun;
break;
}
}
const curTextStyle = getStyleInTextRange(body, activeTextRange, defaultTextStyle);

return {
...textRun,
ts: {
...defaultTextStyle,
...textRun?.ts,
...curTextStyle,
...cacheStyle,
},
};
Expand Down
Loading