Skip to content

Commit

Permalink
fix(docs-ui): cache menu style (#3939)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jocs authored Nov 16, 2024
1 parent 6ee01cd commit 3be0982
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 88 deletions.
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

0 comments on commit 3be0982

Please sign in to comment.