diff --git a/packages/core/src/docs/data-model/text-x/utils.ts b/packages/core/src/docs/data-model/text-x/utils.ts index 201f6f11dc77..9b083e5a5914 100644 --- a/packages/core/src/docs/data-model/text-x/utils.ts +++ b/packages/core/src/docs/data-model/text-x/utils.ts @@ -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 { @@ -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. diff --git a/packages/docs-ui/src/commands/commands/inline-format.command.ts b/packages/docs-ui/src/commands/commands/inline-format.command.ts index 5af9b3a876d2..9636d4fe331a 100644 --- a/packages/docs-ui/src/commands/commands/inline-format.command.ts +++ b/packages/docs-ui/src/commands/commands/inline-format.command.ts @@ -25,9 +25,11 @@ 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'; @@ -255,6 +257,12 @@ export const SetInlineFormatCommand: ICommand = { return false; } + const body = docDataModel.getSelfOrHeaderFooterModel(segmentId).getBody(); + + if (body == null) { + return false; + } + const unitId = docDataModel.getUnitId(); let formatValue; @@ -266,10 +274,16 @@ export const SetInlineFormatCommand: ICommand = { 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, - activeTextRange + curTextStyle, + preCommandId ); break; @@ -423,55 +437,77 @@ function getReverseFormatValue(ts: Nullable, key: keyof IStyleBase, } } -/** - * 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 +export function getStyleInTextRange( + body: IDocumentBody, + textRange: ITextRangeWithStyle, + defaultStyle: ITextStyle +): ITextStyle { + const { startOffset, endOffset, collapsed } = textRange; -function getReverseFormatValueInSelection( - textRuns: ITextRun[], - preCommandId: string, - activeTextRange: ITextRangeWithStyle -): BooleanNumber | ITextDecoration | BaselineOffset { - const key: keyof IStyleBase = COMMAND_ID_TO_FORMAT_KEY_MAP[preCommandId]; - const { startOffset, endOffset, collapsed } = activeTextRange; + if (collapsed) { + const textRuns = body.textRuns ?? []; + let textRun: Nullable = null; - let textRun; - - for (let i = textRuns.length - 1; i >= 0; i--) { - const curTextRun = textRuns[i]; - if (collapsed) { + for (let i = textRuns.length - 1; i >= 0; i--) { + const curTextRun = textRuns[i]; if (curTextRun.st < startOffset && startOffset <= curTextRun.ed) { textRun = curTextRun; break; } - } else { - if (curTextRun.st <= startOffset && endOffset <= curTextRun.ed) { - textRun = curTextRun; - break; - } } + + return textRun?.ts ? { ...defaultStyle, ...textRun.ts } : defaultStyle; } - if (textRun) { - const { ts } = textRun; - const reverseValue = getReverseFormatValue(ts, key, preCommandId); + const { textRuns = [] } = getBodySlice(body, startOffset, endOffset); + + const style = Tools.deepClone(defaultStyle); + + // 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; - if (reverseValue !== undefined) { - return reverseValue; + 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; + } } - } - 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; + if (isSame) { + style.va = va; + } } + + 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; } diff --git a/packages/docs-ui/src/controllers/menu/menu.ts b/packages/docs-ui/src/controllers/menu/menu.ts index 094deb13c72a..6cc1f817905e 100644 --- a/packages/docs-ui/src/controllers/menu/menu.ts +++ b/packages/docs-ui/src/controllers/menu/menu.ts @@ -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'; @@ -939,11 +939,10 @@ function getFontStyleAtCursor(accessor: IAccessor) { }; } - const { startOffset, endOffset, collapsed, segmentId } = activeTextRange; + const { segmentId } = activeTextRange; + const body = docDataModel.getSelfOrHeaderFooterModel(segmentId).getBody(); - const textRuns = docDataModel.getSelfOrHeaderFooterModel(segmentId).getBody()?.textRuns; - - if (textRuns == null) { + if (body == null) { return { ts: { ...defaultTextStyle, @@ -952,28 +951,11 @@ function getFontStyleAtCursor(accessor: IAccessor) { }; } - let textRun; - - for (let i = textRuns.length - 1; i >= 0; i--) { - const curTextRun = textRuns[i]; - if (collapsed) { - if (curTextRun.st < startOffset && startOffset <= curTextRun.ed) { - textRun = curTextRun; - break; - } - } else { - if (curTextRun.st <= startOffset && endOffset <= curTextRun.ed) { - textRun = curTextRun; - break; - } - } - } + const curTextStyle = getStyleInTextRange(body, activeTextRange, defaultTextStyle); return { - ...textRun, ts: { - ...defaultTextStyle, - ...textRun?.ts, + ...curTextStyle, ...cacheStyle, }, };