From e9fc08679f70ee54451c14863732746b18b69042 Mon Sep 17 00:00:00 2001 From: wpxp123456 <2677556700@qq.com> Date: Fri, 12 Dec 2025 12:03:29 +0800 Subject: [PATCH] feat: add a `Copy Formula Only` menu item to the right-click menu (#6287) --- packages/core/src/index.ts | 2 +- packages/core/src/sheets/util.ts | 15 ++- .../controllers/cf.copy-paste.controller.ts | 8 +- .../controllers/dv-copy-paste.controller.ts | 10 +- .../sheet-drawing-copy-paste.controller.ts | 14 +-- .../commands/formula-clipboard.command.ts | 15 ++- .../formula-clipboard.controller.spec.ts | 6 +- .../formula-clipboard.controller.ts | 116 ++++++++++++++++-- .../src/controllers/formula-ui.controller.ts | 3 +- .../src/controllers/menu.schema.ts | 11 +- .../sheets-formula-ui/src/controllers/menu.ts | 21 +++- packages/sheets-formula-ui/src/index.ts | 2 +- .../sheets-formula-ui/src/locale/ca-ES.ts | 1 + .../sheets-formula-ui/src/locale/en-US.ts | 1 + .../sheets-formula-ui/src/locale/es-ES.ts | 1 + .../sheets-formula-ui/src/locale/fa-IR.ts | 1 + .../sheets-formula-ui/src/locale/fr-FR.ts | 1 + .../sheets-formula-ui/src/locale/ja-JP.ts | 1 + .../sheets-formula-ui/src/locale/ko-KR.ts | 1 + .../sheets-formula-ui/src/locale/ru-RU.ts | 1 + .../sheets-formula-ui/src/locale/vi-VN.ts | 1 + .../sheets-formula-ui/src/locale/zh-CN.ts | 1 + .../sheets-formula-ui/src/locale/zh-TW.ts | 1 + .../src/controllers/copy-paste.controller.ts | 10 +- .../commands/commands/clipboard.command.ts | 16 +-- .../clipboard/clipboard.controller.ts | 24 ++-- .../sheets-ui/src/controllers/menu.schema.ts | 15 ++- .../sheets-ui/src/controllers/menu/menu.ts | 13 ++ .../sheet-permission-check-ui.controller.ts | 6 +- packages/sheets-ui/src/index.ts | 6 +- packages/sheets-ui/src/locale/ca-ES.ts | 1 + packages/sheets-ui/src/locale/en-US.ts | 1 + packages/sheets-ui/src/locale/es-ES.ts | 1 + packages/sheets-ui/src/locale/fa-IR.ts | 1 + packages/sheets-ui/src/locale/fr-FR.ts | 1 + packages/sheets-ui/src/locale/ja-JP.ts | 1 + packages/sheets-ui/src/locale/ko-KR.ts | 1 + packages/sheets-ui/src/locale/ru-RU.ts | 1 + packages/sheets-ui/src/locale/vi-VN.ts | 1 + packages/sheets-ui/src/locale/zh-CN.ts | 1 + packages/sheets-ui/src/locale/zh-TW.ts | 1 + .../__tests__/clipboard-service.spec.ts | 4 +- .../services/clipboard/clipboard.service.ts | 115 ++++++++--------- .../sheets-ui/src/services/clipboard/type.ts | 32 +++-- .../views/clipboard/ClipboardPopupMenu.tsx | 4 +- 45 files changed, 352 insertions(+), 138 deletions(-) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 443a1d65c2..88c967b80a 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -173,7 +173,7 @@ export { Styles } from './sheets/styles'; export * from './sheets/typedef'; export type { IPosition } from './sheets/typedef'; -export { addLinkToDocumentModel, isNotNullOrUndefined, isRangesEqual, isUnitRangesEqual } from './sheets/util'; +export { addLinkToDocumentModel, getEmptyCell, isNotNullOrUndefined, isRangesEqual, isUnitRangesEqual } from './sheets/util'; export { createDocumentModelWithStyle } from './sheets/util'; export { SheetViewModel } from './sheets/view-model'; export { getWorksheetUID, Workbook } from './sheets/workbook'; diff --git a/packages/core/src/sheets/util.ts b/packages/core/src/sheets/util.ts index c7e84a2cee..50c137c962 100644 --- a/packages/core/src/sheets/util.ts +++ b/packages/core/src/sheets/util.ts @@ -17,7 +17,7 @@ import type { Nullable } from '../shared'; import type { CellValueType, TextDirection } from '../types/enum'; import type { IDocumentData, IPaddingData, IStyleBase, IStyleData, ITextRotation, ITextStyle } from '../types/interfaces'; -import type { IRange, IUnitRange } from './typedef'; +import type { ICellData, IRange, IUnitRange } from './typedef'; import { DEFAULT_EMPTY_DOCUMENT_VALUE } from '../common/const'; import { BuildTextUtils, DocumentDataModel } from '../docs'; import { TextX } from '../docs/data-model/text-x/text-x'; @@ -215,3 +215,16 @@ export function addLinkToDocumentModel(documentModel: DocumentDataModel, linkUrl export function isNotNullOrUndefined(value: T | null | undefined): value is T { return value !== null && value !== undefined; } + +export function getEmptyCell(): ICellData { + return { + p: null, + s: null, + v: null, + t: null, + f: null, + si: null, + ref: null, + xf: null, + }; +} diff --git a/packages/sheets-conditional-formatting-ui/src/controllers/cf.copy-paste.controller.ts b/packages/sheets-conditional-formatting-ui/src/controllers/cf.copy-paste.controller.ts index 71d4b35c06..cfccc6fe5a 100644 --- a/packages/sheets-conditional-formatting-ui/src/controllers/cf.copy-paste.controller.ts +++ b/packages/sheets-conditional-formatting-ui/src/controllers/cf.copy-paste.controller.ts @@ -34,7 +34,7 @@ import { rangeToDiscreteRange, } from '@univerjs/sheets'; import { AddConditionalRuleMutation, AddConditionalRuleMutationUndoFactory, ConditionalFormattingRuleModel, ConditionalFormattingViewModel, DeleteConditionalRuleMutation, DeleteConditionalRuleMutationUndoFactory, SetConditionalRuleMutation, setConditionalRuleMutationUndoFactory, SHEET_CONDITIONAL_FORMATTING_PLUGIN } from '@univerjs/sheets-conditional-formatting'; -import { COPY_TYPE, getRepeatRange, ISheetClipboardService, PREDEFINED_HOOK_NAME, virtualizeDiscreteRanges } from '@univerjs/sheets-ui'; +import { COPY_TYPE, getRepeatRange, ISheetClipboardService, PREDEFINED_HOOK_NAME_PASTE, virtualizeDiscreteRanges } from '@univerjs/sheets-ui'; export class ConditionalFormattingCopyPasteController extends Disposable { private _copyInfo: Nullable<{ @@ -138,9 +138,9 @@ export class ConditionalFormattingCopyPasteController extends Disposable { } const specialPastes: IPasteHookValueType[] = [ - PREDEFINED_HOOK_NAME.SPECIAL_PASTE_FORMAT, - PREDEFINED_HOOK_NAME.DEFAULT_PASTE, - PREDEFINED_HOOK_NAME.SPECIAL_PASTE_BESIDES_BORDER, + PREDEFINED_HOOK_NAME_PASTE.SPECIAL_PASTE_FORMAT, + PREDEFINED_HOOK_NAME_PASTE.DEFAULT_PASTE, + PREDEFINED_HOOK_NAME_PASTE.SPECIAL_PASTE_BESIDES_BORDER, ]; if ( diff --git a/packages/sheets-data-validation-ui/src/controllers/dv-copy-paste.controller.ts b/packages/sheets-data-validation-ui/src/controllers/dv-copy-paste.controller.ts index 4c10666466..7d3a8af883 100644 --- a/packages/sheets-data-validation-ui/src/controllers/dv-copy-paste.controller.ts +++ b/packages/sheets-data-validation-ui/src/controllers/dv-copy-paste.controller.ts @@ -19,7 +19,7 @@ import type { IDiscreteRange, IPasteHookValueType } from '@univerjs/sheets-ui'; import { Disposable, Inject, Injector, ObjectMatrix, queryObjectMatrix, Rectangle } from '@univerjs/core'; import { rangeToDiscreteRange } from '@univerjs/sheets'; import { DATA_VALIDATION_PLUGIN_NAME, getDataValidationDiffMutations, SheetDataValidationModel } from '@univerjs/sheets-data-validation'; -import { COPY_TYPE, getRepeatRange, ISheetClipboardService, PREDEFINED_HOOK_NAME, virtualizeDiscreteRanges } from '@univerjs/sheets-ui'; +import { COPY_TYPE, getRepeatRange, ISheetClipboardService, PREDEFINED_HOOK_NAME_PASTE, virtualizeDiscreteRanges } from '@univerjs/sheets-ui'; export class DataValidationCopyPasteController extends Disposable { private _copyInfo: Nullable<{ @@ -96,10 +96,10 @@ export class DataValidationCopyPasteController extends Disposable { } const specialPastes: IPasteHookValueType[] = [ - PREDEFINED_HOOK_NAME.SPECIAL_PASTE_COL_WIDTH, - PREDEFINED_HOOK_NAME.SPECIAL_PASTE_VALUE, - PREDEFINED_HOOK_NAME.SPECIAL_PASTE_FORMAT, - PREDEFINED_HOOK_NAME.SPECIAL_PASTE_FORMULA, + PREDEFINED_HOOK_NAME_PASTE.SPECIAL_PASTE_COL_WIDTH, + PREDEFINED_HOOK_NAME_PASTE.SPECIAL_PASTE_VALUE, + PREDEFINED_HOOK_NAME_PASTE.SPECIAL_PASTE_FORMAT, + PREDEFINED_HOOK_NAME_PASTE.SPECIAL_PASTE_FORMULA, ]; if (specialPastes.includes(copyInfo.pasteType)) { diff --git a/packages/sheets-drawing-ui/src/controllers/sheet-drawing-copy-paste.controller.ts b/packages/sheets-drawing-ui/src/controllers/sheet-drawing-copy-paste.controller.ts index 278fdc3e1a..f5dd746ec8 100644 --- a/packages/sheets-drawing-ui/src/controllers/sheet-drawing-copy-paste.controller.ts +++ b/packages/sheets-drawing-ui/src/controllers/sheet-drawing-copy-paste.controller.ts @@ -28,7 +28,7 @@ import { discreteRangeToRange, ISheetClipboardService, ISheetSelectionRenderService, - PREDEFINED_HOOK_NAME, + PREDEFINED_HOOK_NAME_PASTE, SheetSkeletonManagerService, virtualizeDiscreteRanges, } from '@univerjs/sheets-ui'; @@ -85,10 +85,10 @@ function focusDocument() { } const specialPastes: IPasteHookValueType[] = [ - PREDEFINED_HOOK_NAME.SPECIAL_PASTE_COL_WIDTH, - PREDEFINED_HOOK_NAME.SPECIAL_PASTE_VALUE, - PREDEFINED_HOOK_NAME.SPECIAL_PASTE_FORMAT, - PREDEFINED_HOOK_NAME.SPECIAL_PASTE_FORMULA, + PREDEFINED_HOOK_NAME_PASTE.SPECIAL_PASTE_COL_WIDTH, + PREDEFINED_HOOK_NAME_PASTE.SPECIAL_PASTE_VALUE, + PREDEFINED_HOOK_NAME_PASTE.SPECIAL_PASTE_FORMAT, + PREDEFINED_HOOK_NAME_PASTE.SPECIAL_PASTE_FORMULA, ]; export class SheetsDrawingCopyPasteController extends Disposable { @@ -188,7 +188,7 @@ export class SheetsDrawingCopyPasteController extends Disposable { onPasteUnrecognized: (pasteTo: ISheetDiscreteRangeLocation) => { if (this._copyInfo) { - return this._generateSingleDrawingPasteMutations({ pasteTo, pasteType: PREDEFINED_HOOK_NAME.DEFAULT_PASTE }, COPY_TYPE.COPY); + return this._generateSingleDrawingPasteMutations({ pasteTo, pasteType: PREDEFINED_HOOK_NAME_PASTE.DEFAULT_PASTE }, COPY_TYPE.COPY); } else { return { undos: [], redos: [] }; } @@ -196,7 +196,7 @@ export class SheetsDrawingCopyPasteController extends Disposable { onPasteFiles: (pasteTo: ISheetDiscreteRangeLocation, files) => { if (this._copyInfo) { - return this._generateSingleDrawingPasteMutations({ pasteTo, pasteType: PREDEFINED_HOOK_NAME.DEFAULT_PASTE }, COPY_TYPE.COPY); + return this._generateSingleDrawingPasteMutations({ pasteTo, pasteType: PREDEFINED_HOOK_NAME_PASTE.DEFAULT_PASTE }, COPY_TYPE.COPY); } else { // Paste image from external const images = files.filter((file) => file.type.includes('image')); diff --git a/packages/sheets-formula-ui/src/commands/commands/formula-clipboard.command.ts b/packages/sheets-formula-ui/src/commands/commands/formula-clipboard.command.ts index 5ec78b51be..ab45f11f01 100644 --- a/packages/sheets-formula-ui/src/commands/commands/formula-clipboard.command.ts +++ b/packages/sheets-formula-ui/src/commands/commands/formula-clipboard.command.ts @@ -16,7 +16,18 @@ import type { ICommand } from '@univerjs/core'; import { CommandType, ICommandService } from '@univerjs/core'; -import { PREDEFINED_HOOK_NAME, SheetPasteCommand } from '@univerjs/sheets-ui'; +import { ISheetClipboardService, PREDEFINED_HOOK_NAME_COPY, PREDEFINED_HOOK_NAME_PASTE, SheetPasteCommand } from '@univerjs/sheets-ui'; + +export const SheetCopyFormulaOnlyCommand: ICommand = { + id: 'sheet.command.copy-formula-only', + type: CommandType.COMMAND, + handler: async (accessor) => { + const sheetClipboardService = accessor.get(ISheetClipboardService); + return sheetClipboardService.copy({ + copyHookType: PREDEFINED_HOOK_NAME_COPY.SPECIAL_COPY_FORMULA_ONLY, + }); + }, +}; export const SheetOnlyPasteFormulaCommand: ICommand = { id: 'sheet.command.paste-formula', @@ -24,7 +35,7 @@ export const SheetOnlyPasteFormulaCommand: ICommand = { handler: async (accessor) => { const commandService = accessor.get(ICommandService); return commandService.executeCommand(SheetPasteCommand.id, { - value: PREDEFINED_HOOK_NAME.SPECIAL_PASTE_FORMULA, + value: PREDEFINED_HOOK_NAME_PASTE.SPECIAL_PASTE_FORMULA, }); }, }; diff --git a/packages/sheets-formula-ui/src/controllers/__tests__/formula-clipboard.controller.spec.ts b/packages/sheets-formula-ui/src/controllers/__tests__/formula-clipboard.controller.spec.ts index 17c49248e7..c6eaab56c8 100644 --- a/packages/sheets-formula-ui/src/controllers/__tests__/formula-clipboard.controller.spec.ts +++ b/packages/sheets-formula-ui/src/controllers/__tests__/formula-clipboard.controller.spec.ts @@ -20,7 +20,7 @@ import type { ICellDataWithSpanInfo } from '@univerjs/sheets-ui'; import { ICommandService, IUniverInstanceService, ObjectMatrix } from '@univerjs/core'; import { FormulaDataModel, LexerTreeBuilder } from '@univerjs/engine-formula'; import { SetRangeValuesMutation } from '@univerjs/sheets'; -import { COPY_TYPE, ISheetSelectionRenderService, PREDEFINED_HOOK_NAME, SheetSelectionRenderService } from '@univerjs/sheets-ui'; +import { COPY_TYPE, ISheetSelectionRenderService, PREDEFINED_HOOK_NAME_PASTE, SheetSelectionRenderService } from '@univerjs/sheets-ui'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { getSetCellFormulaMutations } from '../formula-clipboard.controller'; import { createCommandTestBed } from './create-command-test-bed'; @@ -99,7 +99,7 @@ describe('Test paste with formula', () => { cols: [2, 3], }, copyType: COPY_TYPE.COPY, - pasteType: PREDEFINED_HOOK_NAME.DEFAULT_PASTE, + pasteType: PREDEFINED_HOOK_NAME_PASTE.DEFAULT_PASTE, }; const pasteFrom = { @@ -329,7 +329,7 @@ describe('Test paste with formula', () => { rows: [0, 1, 2, 3], cols: [5, 6, 7, 8], }, - pasteType: PREDEFINED_HOOK_NAME.DEFAULT_PASTE, + pasteType: PREDEFINED_HOOK_NAME_PASTE.DEFAULT_PASTE, }; const pasteFrom = { diff --git a/packages/sheets-formula-ui/src/controllers/formula-clipboard.controller.ts b/packages/sheets-formula-ui/src/controllers/formula-clipboard.controller.ts index 4ad25efe53..3d03ed2582 100644 --- a/packages/sheets-formula-ui/src/controllers/formula-clipboard.controller.ts +++ b/packages/sheets-formula-ui/src/controllers/formula-clipboard.controller.ts @@ -14,13 +14,14 @@ * limitations under the License. */ -import type { IAccessor, ICellData, IMutationInfo, Workbook } from '@univerjs/core'; +import type { IAccessor, ICellData, ICellDataWithSpanAndDisplay, IMutationInfo, IRange, Nullable, Workbook, Worksheet } from '@univerjs/core'; import type { ISetRangeValuesMutationParams } from '@univerjs/sheets'; import type { ICellDataWithSpanInfo, ICopyPastePayload, IDiscreteRange, IPasteHookValueType, ISheetClipboardHook, ISheetDiscreteRangeLocation } from '@univerjs/sheets-ui'; import { DEFAULT_EMPTY_DOCUMENT_VALUE, Disposable, generateRandomId, + getEmptyCell, Inject, Injector, isFormulaId, @@ -31,13 +32,13 @@ import { } from '@univerjs/core'; import { FormulaDataModel, LexerTreeBuilder } from '@univerjs/engine-formula'; import { SetRangeValuesMutation, SetRangeValuesUndoMutationFactory } from '@univerjs/sheets'; -import { COPY_TYPE, ISheetClipboardService, PREDEFINED_HOOK_NAME } from '@univerjs/sheets-ui'; +import { COPY_TYPE, ISheetClipboardService, PREDEFINED_HOOK_NAME_COPY, PREDEFINED_HOOK_NAME_PASTE } from '@univerjs/sheets-ui'; export const DEFAULT_PASTE_FORMULA = 'default-paste-formula'; export class FormulaClipboardController extends Disposable { constructor( - @IUniverInstanceService private readonly _currentUniverSheet: IUniverInstanceService, + @IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService, @Inject(LexerTreeBuilder) private readonly _lexerTreeBuilder: LexerTreeBuilder, @ISheetClipboardService private readonly _sheetClipboardService: ISheetClipboardService, @Inject(Injector) private readonly _injector: Injector, @@ -53,13 +54,88 @@ export class FormulaClipboardController extends Disposable { } private _registerClipboardHook() { + this.disposeWithMe(this._sheetClipboardService.addClipboardHook(this._copyFormulaOnlyHook())); this.disposeWithMe(this._sheetClipboardService.addClipboardHook(this._pasteFormulaHook())); this.disposeWithMe(this._sheetClipboardService.addClipboardHook(this._pasteWithFormulaHook())); } + private _copyFormulaOnlyHook(): ISheetClipboardHook { + const self = this; + let currentSheet: Nullable = null; + + return { + id: PREDEFINED_HOOK_NAME_COPY.SPECIAL_COPY_FORMULA_ONLY, + priority: 10, + onBeforeCopy(unitId, subUnitId) { + currentSheet = self._getWorksheet(unitId, subUnitId); + }, + onCopyCellContent(row: number, col: number): string { + if (!currentSheet) return ''; + + const cell = currentSheet.getCellRaw(row, col); + if (!cell) return ''; + + if (isFormulaString(cell.f)) return cell.f as string; + if (isFormulaId(cell.si)) { + const formulaDataModel = self._formulaDataModel; + const formulaString = formulaDataModel.getFormulaStringByCell(row, col, currentSheet.getSheetId(), currentSheet.getUnitId()); + return formulaString || ''; + } + + return ''; + }, + onAfterCopy() { + currentSheet = null; + }, + getFilteredOutRows(unitId: string, subUnitId: string, range: IRange) { + const worksheet = self._getWorksheet(unitId, subUnitId); + if (!worksheet) return []; + + const { startRow, endRow } = range; + const res: number[] = []; + + for (let r = startRow; r <= endRow; r++) { + if (worksheet.getRowFiltered(r)) { + res.push(r); + } + } + return res; + }, + handleMatrixOnCell( + row: number, + column: number, + rowIndexInMatrix: number, + columnIndexInMatrix: number, + matrix: ObjectMatrix, + matrixFragment: ObjectMatrix, + plainMatrix: ObjectMatrix + ) { + const cellData = matrix.getValue(row, column); + if (currentSheet && cellData && (isFormulaString(cellData.f) || isFormulaId(cellData.si))) { + const formulaString = isFormulaString(cellData.f) + ? (cellData.f as string) + : self._formulaDataModel.getFormulaStringByCell(row, column, currentSheet.getSheetId(), currentSheet.getUnitId()) as string; + matrixFragment.setValue(rowIndexInMatrix, columnIndexInMatrix, { + ...getEmptyCell(), + f: formulaString, + }); + plainMatrix.setValue(rowIndexInMatrix, columnIndexInMatrix, { + ...getEmptyCell(), + f: formulaString, + displayV: formulaString, + }); + } else { + matrix.setValue(row, column, getEmptyCell()); + matrixFragment.setValue(rowIndexInMatrix, columnIndexInMatrix, getEmptyCell()); + plainMatrix.setValue(rowIndexInMatrix, columnIndexInMatrix, getEmptyCell()); + } + }, + }; + } + private _pasteFormulaHook(): ISheetClipboardHook { return { - id: PREDEFINED_HOOK_NAME.SPECIAL_PASTE_FORMULA, + id: PREDEFINED_HOOK_NAME_PASTE.SPECIAL_PASTE_FORMULA, priority: 10, specialPasteInfo: { label: 'specialPaste.formula' }, onPasteCells: (pasteFrom, pasteTo, data, payload) => this._onPasteCells(pasteFrom, pasteTo, data, payload, true), @@ -74,6 +150,24 @@ export class FormulaClipboardController extends Disposable { }; } + private _getWorkbook(unitId?: string): Nullable { + if (unitId) { + return this._univerInstanceService.getUnit(unitId, UniverInstanceType.UNIVER_SHEET); + } + + return this._univerInstanceService.getCurrentUnitOfType(UniverInstanceType.UNIVER_SHEET); + } + + private _getWorksheet(unitId?: string, subUnitId?: string): Nullable { + const workbook = this._getWorkbook(unitId); + + if (subUnitId) { + return workbook?.getSheetBySheetId(subUnitId); + } + + return workbook?.getActiveSheet(); + } + private _onPasteCells( pasteFrom: ISheetDiscreteRangeLocation | null, pasteTo: ISheetDiscreteRangeLocation, @@ -83,8 +177,8 @@ export class FormulaClipboardController extends Disposable { ) { // Intercept scenarios where formulas do not need to be processed, and only process default paste and paste formulas only const specialPastes: IPasteHookValueType[] = [ - PREDEFINED_HOOK_NAME.SPECIAL_PASTE_FORMAT, - PREDEFINED_HOOK_NAME.SPECIAL_PASTE_COL_WIDTH, + PREDEFINED_HOOK_NAME_PASTE.SPECIAL_PASTE_FORMAT, + PREDEFINED_HOOK_NAME_PASTE.SPECIAL_PASTE_COL_WIDTH, ]; if (specialPastes.includes(payload.pasteType)) { return { @@ -93,9 +187,9 @@ export class FormulaClipboardController extends Disposable { }; } - const workbook = this._currentUniverSheet.getCurrentUnitForType(UniverInstanceType.UNIVER_SHEET)!; - const unitId = pasteTo.unitId || workbook.getUnitId(); - const subUnitId = pasteTo.subUnitId || workbook.getActiveSheet()?.getSheetId(); + const workbook = this._getWorkbook(); + const unitId = pasteTo.unitId || workbook?.getUnitId(); + const subUnitId = pasteTo.subUnitId || workbook?.getActiveSheet()?.getSheetId(); if (!unitId || !subUnitId) { return { @@ -201,11 +295,11 @@ function getValueMatrix( return getValueMatrixOfPasteFromIsNull(unitId, subUnitId, range, matrix, formulaDataModel); } - if (copyInfo.pasteType === PREDEFINED_HOOK_NAME.SPECIAL_PASTE_VALUE) { + if (copyInfo.pasteType === PREDEFINED_HOOK_NAME_PASTE.SPECIAL_PASTE_VALUE) { return getSpecialPasteValueValueMatrix(unitId, subUnitId, range, matrix, formulaDataModel, pasteFrom); } - if (copyInfo.pasteType === PREDEFINED_HOOK_NAME.SPECIAL_PASTE_FORMULA) { + if (copyInfo.pasteType === PREDEFINED_HOOK_NAME_PASTE.SPECIAL_PASTE_FORMULA) { return getSpecialPasteFormulaValueMatrix(unitId, subUnitId, range, matrix, lexerTreeBuilder, formulaDataModel, pasteFrom); } diff --git a/packages/sheets-formula-ui/src/controllers/formula-ui.controller.ts b/packages/sheets-formula-ui/src/controllers/formula-ui.controller.ts index 0c6960c049..d164ac3871 100644 --- a/packages/sheets-formula-ui/src/controllers/formula-ui.controller.ts +++ b/packages/sheets-formula-ui/src/controllers/formula-ui.controller.ts @@ -20,7 +20,7 @@ import { IRenderManagerService } from '@univerjs/engine-render'; import { SheetsUIPart } from '@univerjs/sheets-ui'; import { ComponentManager, connectInjector, IMenuManagerService, IShortcutService, IUIPartsService } from '@univerjs/ui'; -import { SheetOnlyPasteFormulaCommand } from '../commands/commands/formula-clipboard.command'; +import { SheetCopyFormulaOnlyCommand, SheetOnlyPasteFormulaCommand } from '../commands/commands/formula-clipboard.command'; import { SelectEditorFormulaOperation } from '../commands/operations/editor-formula.operation'; import { HelpFunctionOperation } from '../commands/operations/help-function.operation'; import { InsertFunctionOperation } from '../commands/operations/insert-function.operation'; @@ -71,6 +71,7 @@ export class FormulaUIController extends Disposable { private _registerCommands(): void { [ + SheetCopyFormulaOnlyCommand, SheetOnlyPasteFormulaCommand, InsertFunctionOperation, MoreFunctionsOperation, diff --git a/packages/sheets-formula-ui/src/controllers/menu.schema.ts b/packages/sheets-formula-ui/src/controllers/menu.schema.ts index 77aee31169..aa0d67372c 100644 --- a/packages/sheets-formula-ui/src/controllers/menu.schema.ts +++ b/packages/sheets-formula-ui/src/controllers/menu.schema.ts @@ -15,12 +15,13 @@ */ import type { MenuSchemaType } from '@univerjs/ui'; -import { PASTE_SPECIAL_MENU_ID } from '@univerjs/sheets-ui'; +import { COPY_SPECIAL_MENU_ID, PASTE_SPECIAL_MENU_ID } from '@univerjs/sheets-ui'; import { RibbonFormulasGroup } from '@univerjs/ui'; -import { SheetOnlyPasteFormulaCommand } from '../commands/commands/formula-clipboard.command'; +import { SheetCopyFormulaOnlyCommand, SheetOnlyPasteFormulaCommand } from '../commands/commands/formula-clipboard.command'; import { InsertFunctionOperation } from '../commands/operations/insert-function.operation'; import { MoreFunctionsOperation } from '../commands/operations/more-functions.operation'; import { + CopyFormulaOnlyMenuItemFactory, InsertAVERAGEFunctionMenuItemFactory, InsertCOUNTFunctionMenuItemFactory, InsertMAXFunctionMenuItemFactory, @@ -59,6 +60,12 @@ export const menuSchema: MenuSchemaType = { menuItemFactory: MoreFunctionsMenuItemFactory, }, }, + [COPY_SPECIAL_MENU_ID]: { + [SheetCopyFormulaOnlyCommand.id]: { + order: 0, + menuItemFactory: CopyFormulaOnlyMenuItemFactory, + }, + }, [PASTE_SPECIAL_MENU_ID]: { [SheetOnlyPasteFormulaCommand.id]: { order: 4, diff --git a/packages/sheets-formula-ui/src/controllers/menu.ts b/packages/sheets-formula-ui/src/controllers/menu.ts index 4a6fb068ca..b6ead24be1 100644 --- a/packages/sheets-formula-ui/src/controllers/menu.ts +++ b/packages/sheets-formula-ui/src/controllers/menu.ts @@ -17,11 +17,11 @@ import type { IAccessor, Workbook } from '@univerjs/core'; import type { IMenuItem } from '@univerjs/ui'; import { IUniverInstanceService, UniverInstanceType } from '@univerjs/core'; -import { RangeProtectionPermissionEditPoint, WorkbookEditablePermission, WorksheetEditPermission, WorksheetSetCellValuePermission } from '@univerjs/sheets'; +import { RangeProtectionPermissionEditPoint, RangeProtectionPermissionViewPoint, WorkbookCopyPermission, WorkbookEditablePermission, WorksheetCopyPermission, WorksheetEditPermission, WorksheetSetCellValuePermission } from '@univerjs/sheets'; import { getCurrentRangeDisable$ } from '@univerjs/sheets-ui'; import { getMenuHiddenObservable, IClipboardInterfaceService, MenuItemType } from '@univerjs/ui'; import { combineLatestWith, map, Observable, of, switchMap } from 'rxjs'; -import { SheetOnlyPasteFormulaCommand } from '../commands/commands/formula-clipboard.command'; +import { SheetCopyFormulaOnlyCommand, SheetOnlyPasteFormulaCommand } from '../commands/commands/formula-clipboard.command'; import { InsertFunctionOperation } from '../commands/operations/insert-function.operation'; import { MoreFunctionsOperation } from '../commands/operations/more-functions.operation'; @@ -207,6 +207,22 @@ function menuClipboardDisabledObservable(injector: IAccessor): Observable d1 || d2) ), + hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET), }; } diff --git a/packages/sheets-formula-ui/src/index.ts b/packages/sheets-formula-ui/src/index.ts index 802a05f16d..2343bd3ed1 100644 --- a/packages/sheets-formula-ui/src/index.ts +++ b/packages/sheets-formula-ui/src/index.ts @@ -16,7 +16,7 @@ import './global.css'; -export { SheetOnlyPasteFormulaCommand } from './commands/commands/formula-clipboard.command'; +export { SheetCopyFormulaOnlyCommand, SheetOnlyPasteFormulaCommand } from './commands/commands/formula-clipboard.command'; export { SelectEditorFormulaOperation } from './commands/operations/editor-formula.operation'; export { HelpFunctionOperation } from './commands/operations/help-function.operation'; export { InsertFunctionOperation } from './commands/operations/insert-function.operation'; diff --git a/packages/sheets-formula-ui/src/locale/ca-ES.ts b/packages/sheets-formula-ui/src/locale/ca-ES.ts index a7f79fe19a..f6c7caff41 100644 --- a/packages/sheets-formula-ui/src/locale/ca-ES.ts +++ b/packages/sheets-formula-ui/src/locale/ca-ES.ts @@ -114,6 +114,7 @@ const locale: typeof enUS = { syntax: 'SINTAXI', }, operation: { + copyFormulaOnly: 'Copia només la fórmula', pasteFormula: 'Enganxa la fórmula', }, }, diff --git a/packages/sheets-formula-ui/src/locale/en-US.ts b/packages/sheets-formula-ui/src/locale/en-US.ts index 078ba996f1..eaaee14ed4 100644 --- a/packages/sheets-formula-ui/src/locale/en-US.ts +++ b/packages/sheets-formula-ui/src/locale/en-US.ts @@ -113,6 +113,7 @@ const locale = { syntax: 'SYNTAX', }, operation: { + copyFormulaOnly: 'Copy Formula Only', pasteFormula: 'Paste Formula', }, }, diff --git a/packages/sheets-formula-ui/src/locale/es-ES.ts b/packages/sheets-formula-ui/src/locale/es-ES.ts index 36324a9973..8c10bd3ec1 100644 --- a/packages/sheets-formula-ui/src/locale/es-ES.ts +++ b/packages/sheets-formula-ui/src/locale/es-ES.ts @@ -114,6 +114,7 @@ const locale: typeof enUS = { syntax: 'SINTAXIS', }, operation: { + copyFormulaOnly: 'Copiar solo fórmula', pasteFormula: 'Pegar fórmula', }, }, diff --git a/packages/sheets-formula-ui/src/locale/fa-IR.ts b/packages/sheets-formula-ui/src/locale/fa-IR.ts index fd919ee35a..c3dd2bac47 100644 --- a/packages/sheets-formula-ui/src/locale/fa-IR.ts +++ b/packages/sheets-formula-ui/src/locale/fa-IR.ts @@ -114,6 +114,7 @@ const locale: typeof enUS = { syntax: 'سینتکس', }, operation: { + copyFormulaOnly: 'کپی فقط فرمول', pasteFormula: 'چسباندن فرمول', }, }, diff --git a/packages/sheets-formula-ui/src/locale/fr-FR.ts b/packages/sheets-formula-ui/src/locale/fr-FR.ts index cee0666516..da0f416ab5 100644 --- a/packages/sheets-formula-ui/src/locale/fr-FR.ts +++ b/packages/sheets-formula-ui/src/locale/fr-FR.ts @@ -114,6 +114,7 @@ const locale: typeof enUS = { syntax: 'SYNTAXE', }, operation: { + copyFormulaOnly: 'Copier uniquement la formule', pasteFormula: 'Coller la formule', }, }, diff --git a/packages/sheets-formula-ui/src/locale/ja-JP.ts b/packages/sheets-formula-ui/src/locale/ja-JP.ts index 58177ea5c3..d6c95c5f72 100644 --- a/packages/sheets-formula-ui/src/locale/ja-JP.ts +++ b/packages/sheets-formula-ui/src/locale/ja-JP.ts @@ -114,6 +114,7 @@ const locale: typeof enUS = { syntax: '構文', }, operation: { + copyFormulaOnly: '数式のみをコピー', pasteFormula: '数式を貼り付け', }, }, diff --git a/packages/sheets-formula-ui/src/locale/ko-KR.ts b/packages/sheets-formula-ui/src/locale/ko-KR.ts index 626e3b952c..f95359e8b0 100644 --- a/packages/sheets-formula-ui/src/locale/ko-KR.ts +++ b/packages/sheets-formula-ui/src/locale/ko-KR.ts @@ -114,6 +114,7 @@ const locale: typeof enUS = { syntax: '구문', }, operation: { + copyFormulaOnly: '수식만 복사', pasteFormula: '수식 붙여넣기', }, }, diff --git a/packages/sheets-formula-ui/src/locale/ru-RU.ts b/packages/sheets-formula-ui/src/locale/ru-RU.ts index 4daf1cf916..bb0fbffc7b 100644 --- a/packages/sheets-formula-ui/src/locale/ru-RU.ts +++ b/packages/sheets-formula-ui/src/locale/ru-RU.ts @@ -114,6 +114,7 @@ const locale: typeof enUS = { syntax: 'СИНТАКСИС', }, operation: { + copyFormulaOnly: 'Копировать только формулу', pasteFormula: 'Вставить Формулу', }, }, diff --git a/packages/sheets-formula-ui/src/locale/vi-VN.ts b/packages/sheets-formula-ui/src/locale/vi-VN.ts index bb03d388ba..1a5e581629 100644 --- a/packages/sheets-formula-ui/src/locale/vi-VN.ts +++ b/packages/sheets-formula-ui/src/locale/vi-VN.ts @@ -118,6 +118,7 @@ const locale: typeof enUS = { }, operation: { + copyFormulaOnly: 'Chỉ sao chép công thức', pasteFormula: 'Chỉ dán công thức', }, }, diff --git a/packages/sheets-formula-ui/src/locale/zh-CN.ts b/packages/sheets-formula-ui/src/locale/zh-CN.ts index 507fdab4f7..51cf70e0f0 100644 --- a/packages/sheets-formula-ui/src/locale/zh-CN.ts +++ b/packages/sheets-formula-ui/src/locale/zh-CN.ts @@ -118,6 +118,7 @@ const locale: typeof enUS = { }, operation: { + copyFormulaOnly: '仅复制公式', pasteFormula: '仅粘贴公式', }, }, diff --git a/packages/sheets-formula-ui/src/locale/zh-TW.ts b/packages/sheets-formula-ui/src/locale/zh-TW.ts index ba13d199ba..44aee0aaaf 100644 --- a/packages/sheets-formula-ui/src/locale/zh-TW.ts +++ b/packages/sheets-formula-ui/src/locale/zh-TW.ts @@ -118,6 +118,7 @@ const locale: typeof enUS = { }, operation: { + copyFormulaOnly: '僅複製公式', pasteFormula: '僅貼上公式', }, }, diff --git a/packages/sheets-hyper-link-ui/src/controllers/copy-paste.controller.ts b/packages/sheets-hyper-link-ui/src/controllers/copy-paste.controller.ts index cb9372d16a..8f899cce84 100644 --- a/packages/sheets-hyper-link-ui/src/controllers/copy-paste.controller.ts +++ b/packages/sheets-hyper-link-ui/src/controllers/copy-paste.controller.ts @@ -19,7 +19,7 @@ import type { IDiscreteRange, IPasteHookValueType, ISheetDiscreteRangeLocation } import { Disposable, generateRandomId, Inject, Injector, ObjectMatrix, Range, Rectangle } from '@univerjs/core'; import { rangeToDiscreteRange } from '@univerjs/sheets'; import { AddHyperLinkMutation, HyperLinkModel, RemoveHyperLinkMutation } from '@univerjs/sheets-hyper-link'; -import { COPY_TYPE, getRepeatRange, ISheetClipboardService, PREDEFINED_HOOK_NAME, virtualizeDiscreteRanges } from '@univerjs/sheets-ui'; +import { COPY_TYPE, getRepeatRange, ISheetClipboardService, PREDEFINED_HOOK_NAME_PASTE, virtualizeDiscreteRanges } from '@univerjs/sheets-ui'; import { isLegalLink } from '../common/util'; import { SheetsHyperLinkResolverService } from '../services/resolver.service'; import { SHEET_HYPER_LINK_UI_PLUGIN } from '../types/const'; @@ -154,10 +154,10 @@ export class SheetsHyperLinkCopyPasteController extends Disposable { } const specialPastes: IPasteHookValueType[] = [ - PREDEFINED_HOOK_NAME.SPECIAL_PASTE_COL_WIDTH, - PREDEFINED_HOOK_NAME.SPECIAL_PASTE_VALUE, - PREDEFINED_HOOK_NAME.SPECIAL_PASTE_FORMAT, - PREDEFINED_HOOK_NAME.SPECIAL_PASTE_FORMULA, + PREDEFINED_HOOK_NAME_PASTE.SPECIAL_PASTE_COL_WIDTH, + PREDEFINED_HOOK_NAME_PASTE.SPECIAL_PASTE_VALUE, + PREDEFINED_HOOK_NAME_PASTE.SPECIAL_PASTE_FORMAT, + PREDEFINED_HOOK_NAME_PASTE.SPECIAL_PASTE_FORMULA, ]; if (specialPastes.includes(copyInfo.pasteType)) { diff --git a/packages/sheets-ui/src/commands/commands/clipboard.command.ts b/packages/sheets-ui/src/commands/commands/clipboard.command.ts index 35360ac04f..54e9586225 100644 --- a/packages/sheets-ui/src/commands/commands/clipboard.command.ts +++ b/packages/sheets-ui/src/commands/commands/clipboard.command.ts @@ -15,11 +15,11 @@ */ import type { IAccessor, ICommand, IMultiCommand } from '@univerjs/core'; +import type { IPasteHookKeyType } from '../../services/clipboard/type'; import { CommandType, ICommandService } from '@univerjs/core'; import { CopyCommand, CutCommand, IClipboardInterfaceService, PasteCommand, SheetPasteShortKeyCommandName } from '@univerjs/ui'; - import { whenSheetFocused } from '../../controllers/shortcuts/utils'; -import { ISheetClipboardService, PREDEFINED_HOOK_NAME } from '../../services/clipboard/clipboard.service'; +import { ISheetClipboardService, PREDEFINED_HOOK_NAME_PASTE } from '../../services/clipboard/clipboard.service'; const SHEET_CLIPBOARD_PRIORITY = 998; @@ -101,7 +101,9 @@ export const SheetPasteValueCommand: ICommand = { type: CommandType.COMMAND, handler: async (accessor) => { const commandService = accessor.get(ICommandService); - return commandService.executeCommand(SheetPasteCommand.id, { value: PREDEFINED_HOOK_NAME.SPECIAL_PASTE_VALUE }); + return commandService.executeCommand(SheetPasteCommand.id, { + value: PREDEFINED_HOOK_NAME_PASTE.SPECIAL_PASTE_VALUE, + }); }, }; @@ -111,7 +113,7 @@ export const SheetPasteFormatCommand: ICommand = { handler: async (accessor) => { const commandService = accessor.get(ICommandService); return commandService.executeCommand(SheetPasteCommand.id, { - value: PREDEFINED_HOOK_NAME.SPECIAL_PASTE_FORMAT, + value: PREDEFINED_HOOK_NAME_PASTE.SPECIAL_PASTE_FORMAT, }); }, }; @@ -122,7 +124,7 @@ export const SheetPasteColWidthCommand: ICommand = { handler: async (accessor) => { const commandService = accessor.get(ICommandService); return commandService.executeCommand(SheetPasteCommand.id, { - value: PREDEFINED_HOOK_NAME.SPECIAL_PASTE_COL_WIDTH, + value: PREDEFINED_HOOK_NAME_PASTE.SPECIAL_PASTE_COL_WIDTH, }); }, }; @@ -133,7 +135,7 @@ export const SheetPasteBesidesBorderCommand: ICommand = { handler: async (accessor) => { const commandService = accessor.get(ICommandService); return commandService.executeCommand(SheetPasteCommand.id, { - value: PREDEFINED_HOOK_NAME.SPECIAL_PASTE_BESIDES_BORDER, + value: PREDEFINED_HOOK_NAME_PASTE.SPECIAL_PASTE_BESIDES_BORDER, }); }, }; @@ -141,7 +143,7 @@ export const SheetPasteBesidesBorderCommand: ICommand = { export const SheetOptionalPasteCommand: ICommand = { id: 'sheet.command.optional-paste', type: CommandType.COMMAND, - handler: async (accessor, { type }: { type: keyof typeof PREDEFINED_HOOK_NAME }) => { + handler: async (accessor, { type }: { type: IPasteHookKeyType }) => { const clipboardService = accessor.get(ISheetClipboardService); return clipboardService.rePasteWithPasteType(type); diff --git a/packages/sheets-ui/src/controllers/clipboard/clipboard.controller.ts b/packages/sheets-ui/src/controllers/clipboard/clipboard.controller.ts index 2939554978..499b147610 100644 --- a/packages/sheets-ui/src/controllers/clipboard/clipboard.controller.ts +++ b/packages/sheets-ui/src/controllers/clipboard/clipboard.controller.ts @@ -97,7 +97,7 @@ import { SheetPasteValueCommand, } from '../../commands/commands/clipboard.command'; import { SetScrollOperation } from '../../commands/operations/scroll.operation'; -import { ISheetClipboardService, PREDEFINED_HOOK_NAME } from '../../services/clipboard/clipboard.service'; +import { ISheetClipboardService, PREDEFINED_HOOK_NAME_COPY, PREDEFINED_HOOK_NAME_PASTE } from '../../services/clipboard/clipboard.service'; import { SheetSkeletonManagerService } from '../../services/sheet-skeleton-manager.service'; import { ClipboardPopupMenu } from '../../views/clipboard/ClipboardPopupMenu'; import { SHEETS_UI_PLUGIN_CONFIG_KEY } from '../config.schema'; @@ -239,7 +239,7 @@ export class SheetClipboardController extends RxDisposable { const self = this; let currentSheet: Worksheet | null = null; return { - id: PREDEFINED_HOOK_NAME.DEFAULT_COPY, + id: PREDEFINED_HOOK_NAME_COPY.DEFAULT_COPY, isDefaultHook: true, onBeforeCopy(unitId, subUnitId) { currentSheet = self._getWorksheet(unitId, subUnitId); @@ -308,13 +308,13 @@ export class SheetClipboardController extends RxDisposable { onAfterCopy() { currentSheet = null; }, - getFilteredOutRows(range: IRange) { + getFilteredOutRows(unitId: string, subUnitId: string, range: IRange) { + const worksheet = self._getWorksheet(unitId, subUnitId); + if (!worksheet) return []; + const { startRow, endRow } = range; - const worksheet = self._instanceService.getCurrentUnitForType(UniverInstanceType.UNIVER_SHEET)?.getActiveSheet(); const res: number[] = []; - if (!worksheet) { - return res; - } + for (let r = startRow; r <= endRow; r++) { if (worksheet.getRowFiltered(r)) { res.push(r); @@ -334,7 +334,7 @@ export class SheetClipboardController extends RxDisposable { let currentSheet: Worksheet | null = null; return { - id: PREDEFINED_HOOK_NAME.DEFAULT_PASTE, + id: PREDEFINED_HOOK_NAME_PASTE.DEFAULT_PASTE, isDefaultHook: true, onBeforePaste({ unitId: unitId_, subUnitId: subUnitId_, range }) { currentSheet = self._getWorksheet(unitId_, subUnitId_); @@ -700,7 +700,7 @@ export class SheetClipboardController extends RxDisposable { const self = this; const specialPasteValueHook: ISheetClipboardHook = { - id: PREDEFINED_HOOK_NAME.SPECIAL_PASTE_VALUE, + id: PREDEFINED_HOOK_NAME_PASTE.SPECIAL_PASTE_VALUE, specialPasteInfo: { label: 'specialPaste.value', }, @@ -711,7 +711,7 @@ export class SheetClipboardController extends RxDisposable { }, }; const specialPasteFormatHook: ISheetClipboardHook = { - id: PREDEFINED_HOOK_NAME.SPECIAL_PASTE_FORMAT, + id: PREDEFINED_HOOK_NAME_PASTE.SPECIAL_PASTE_FORMAT, specialPasteInfo: { label: 'specialPaste.format', }, @@ -757,7 +757,7 @@ export class SheetClipboardController extends RxDisposable { }; const specialPasteColWidthHook: ISheetClipboardHook = { - id: PREDEFINED_HOOK_NAME.SPECIAL_PASTE_COL_WIDTH, + id: PREDEFINED_HOOK_NAME_PASTE.SPECIAL_PASTE_COL_WIDTH, specialPasteInfo: { label: 'specialPaste.colWidth', }, @@ -841,7 +841,7 @@ export class SheetClipboardController extends RxDisposable { }; const specialPasteBesidesBorder: ISheetClipboardHook = { - id: PREDEFINED_HOOK_NAME.SPECIAL_PASTE_BESIDES_BORDER, + id: PREDEFINED_HOOK_NAME_PASTE.SPECIAL_PASTE_BESIDES_BORDER, specialPasteInfo: { label: 'specialPaste.besidesBorder', }, diff --git a/packages/sheets-ui/src/controllers/menu.schema.ts b/packages/sheets-ui/src/controllers/menu.schema.ts index 299ed2f8c4..e2ff966df6 100644 --- a/packages/sheets-ui/src/controllers/menu.schema.ts +++ b/packages/sheets-ui/src/controllers/menu.schema.ts @@ -117,7 +117,9 @@ import { BoldMenuItemFactory, CancelFrozenMenuItemFactory, ColAutoWidthMenuItemFactory, + COPY_SPECIAL_MENU_ID, CopyMenuItemFactory, + CopySpacialMenuItemFactory, CutMenuItemFactory, FitContentMenuItemFactory, FontFamilySelectorMenuItemFactory, @@ -132,7 +134,6 @@ import { PASTE_SPECIAL_MENU_ID, PasteBesidesBorderMenuItemFactory, PasteColWidthMenuItemFactory, - // PasteColWidthMenuItemFactory, PasteFormatMenuItemFactory, PasteMenuItemFactory, PasteSpacialMenuItemFactory, @@ -302,6 +303,10 @@ export const menuSchema: MenuSchemaType = { order: 2, menuItemFactory: PasteMenuItemFactory, }, + [COPY_SPECIAL_MENU_ID]: { + order: 2.5, + menuItemFactory: CopySpacialMenuItemFactory, + }, [PASTE_SPECIAL_MENU_ID]: { order: 3, menuItemFactory: PasteSpacialMenuItemFactory, @@ -443,6 +448,10 @@ export const menuSchema: MenuSchemaType = { order: 2, menuItemFactory: PasteMenuItemFactory, }, + [COPY_SPECIAL_MENU_ID]: { + order: 2.5, + menuItemFactory: CopySpacialMenuItemFactory, + }, [PASTE_SPECIAL_MENU_ID]: { order: 3, menuItemFactory: PasteSpacialMenuItemFactory, @@ -564,6 +573,10 @@ export const menuSchema: MenuSchemaType = { order: 2, menuItemFactory: PasteMenuItemFactory, }, + [COPY_SPECIAL_MENU_ID]: { + order: 2.5, + menuItemFactory: CopySpacialMenuItemFactory, + }, [PASTE_SPECIAL_MENU_ID]: { order: 3, menuItemFactory: PasteSpacialMenuItemFactory, diff --git a/packages/sheets-ui/src/controllers/menu/menu.ts b/packages/sheets-ui/src/controllers/menu/menu.ts index fbb3ce260f..1d5864834f 100644 --- a/packages/sheets-ui/src/controllers/menu/menu.ts +++ b/packages/sheets-ui/src/controllers/menu/menu.ts @@ -941,6 +941,19 @@ export function PasteMenuItemFactory(accessor: IAccessor): IMenuButtonItem { }; } +// Right click menu - Copy Special +export const COPY_SPECIAL_MENU_ID = 'sheet.menu.copy-special'; +export function CopySpacialMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { + return { + id: COPY_SPECIAL_MENU_ID, + type: MenuItemType.SUBITEMS, + icon: 'CopyDoubleIcon', + title: 'rightClick.copySpecial', + hidden$: getObservableWithExclusiveRange$(accessor, getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET)), + }; +} + +// Right click menu - Paste Special export const PASTE_SPECIAL_MENU_ID = 'sheet.menu.paste-special'; export function PasteSpacialMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { return { diff --git a/packages/sheets-ui/src/controllers/permission/sheet-permission-check-ui.controller.ts b/packages/sheets-ui/src/controllers/permission/sheet-permission-check-ui.controller.ts index 6c6debaa90..c5ebb1c8e7 100644 --- a/packages/sheets-ui/src/controllers/permission/sheet-permission-check-ui.controller.ts +++ b/packages/sheets-ui/src/controllers/permission/sheet-permission-check-ui.controller.ts @@ -33,7 +33,7 @@ import { SetRangeBoldCommand, SetRangeItalicCommand, SetRangeStrickThroughComman import { ApplyFormatPainterCommand } from '../../commands/commands/set-format-painter.command'; import { SetCellEditVisibleOperation } from '../../commands/operations/cell-edit.operation'; import { IAutoFillService } from '../../services/auto-fill/auto-fill.service'; -import { PREDEFINED_HOOK_NAME } from '../../services/clipboard/clipboard.service'; +import { PREDEFINED_HOOK_NAME_PASTE } from '../../services/clipboard/clipboard.service'; import { UNIVER_SHEET_PERMISSION_ALERT_DIALOG, UNIVER_SHEET_PERMISSION_ALERT_DIALOG_ID } from '../../views/permission/error-msg-dialog/interface'; type ICellPermission = Record & { ruleId?: string; ranges?: IRange[] }; @@ -202,13 +202,13 @@ export class SheetPermissionCheckUIController extends Disposable { } private _permissionCheckByPaste(params: ISheetPasteParams) { - if (params.value === PREDEFINED_HOOK_NAME.SPECIAL_PASTE_VALUE || params.value === PREDEFINED_HOOK_NAME.SPECIAL_PASTE_FORMULA) { + if (params.value === PREDEFINED_HOOK_NAME_PASTE.SPECIAL_PASTE_VALUE || params.value === PREDEFINED_HOOK_NAME_PASTE.SPECIAL_PASTE_FORMULA) { return this._sheetPermissionCheckController.permissionCheckWithRanges({ workbookTypes: [WorkbookEditablePermission], rangeTypes: [RangeProtectionPermissionEditPoint], worksheetTypes: [WorksheetSetCellStylePermission, WorksheetEditPermission], }); - } else if (params.value === PREDEFINED_HOOK_NAME.SPECIAL_PASTE_FORMAT) { + } else if (params.value === PREDEFINED_HOOK_NAME_PASTE.SPECIAL_PASTE_FORMAT) { return this._sheetPermissionCheckController.permissionCheckWithRanges({ workbookTypes: [WorkbookEditablePermission], rangeTypes: [RangeProtectionPermissionEditPoint], diff --git a/packages/sheets-ui/src/index.ts b/packages/sheets-ui/src/index.ts index 415aa697b4..3e629d6add 100644 --- a/packages/sheets-ui/src/index.ts +++ b/packages/sheets-ui/src/index.ts @@ -106,7 +106,7 @@ export { EMBEDDING_FORMULA_EDITOR, isEmbeddingFormulaEditor } from './controller export { isRangeSelector, RANGE_SELECTOR_SYMBOLS } from './controllers/editor/utils/is-range-selector'; export { HoverRenderController } from './controllers/hover-render.controller'; export { menuSchema as SheetsUIMenuSchema } from './controllers/menu.schema'; -export { PASTE_SPECIAL_MENU_ID, SheetMenuPosition } from './controllers/menu/menu'; +export { COPY_SPECIAL_MENU_ID, PASTE_SPECIAL_MENU_ID, SheetMenuPosition } from './controllers/menu/menu'; export { deriveStateFromActiveSheet$, getCurrentExclusiveRangeInterest$, getCurrentRangeDisable$, getObservableWithExclusiveRange$ } from './controllers/menu/menu-util'; export { SheetPermissionCheckUIController } from './controllers/permission/sheet-permission-check-ui.controller'; export { HeaderFreezeRenderController } from './controllers/render-controllers/freeze.render-controller'; @@ -132,9 +132,9 @@ export { type ICanvasPopup, SheetCanvasPopManagerService } from './services/canv export { CellAlertManagerService, CellAlertType, type ICellAlert } from './services/cell-alert-manager.service'; export { type IDropdownParam, ISheetCellDropdownManagerService, SheetCellDropdownManagerService } from './services/cell-dropdown-manager.service'; export { CellPopupManagerService } from './services/cell-popup-manager.service'; -export { getMatrixPlainText, ISheetClipboardService, PREDEFINED_HOOK_NAME, SheetClipboardService } from './services/clipboard/clipboard.service'; +export { getMatrixPlainText, ISheetClipboardService, PREDEFINED_HOOK_NAME, PREDEFINED_HOOK_NAME_COPY, PREDEFINED_HOOK_NAME_PASTE, SheetClipboardService } from './services/clipboard/clipboard.service'; export { COPY_TYPE } from './services/clipboard/type'; -export type { ICellDataWithSpanInfo, ICopyPastePayload, IPasteHookValueType, ISheetClipboardHook, ISheetDiscreteRangeLocation } from './services/clipboard/type'; +export type { ICellDataWithSpanInfo, ICopyHookKeyType, ICopyHookValueType, ICopyPastePayload, IPasteHookKeyType, IPasteHookValueType, ISheetClipboardHook, ISheetDiscreteRangeLocation } from './services/clipboard/type'; export { getRepeatRange, mergeSetRangeValues } from './services/clipboard/utils'; export { DragManagerService, type IDragCellPosition } from './services/drag-manager.service'; export { EditorBridgeService, IEditorBridgeService, type IEditorBridgeServiceVisibleParam } from './services/editor-bridge.service'; diff --git a/packages/sheets-ui/src/locale/ca-ES.ts b/packages/sheets-ui/src/locale/ca-ES.ts index c891c5ff04..8ce7c48a60 100644 --- a/packages/sheets-ui/src/locale/ca-ES.ts +++ b/packages/sheets-ui/src/locale/ca-ES.ts @@ -188,6 +188,7 @@ const locale: typeof enUS = { copy: 'Copiar', cut: 'Retallar', paste: 'Enganxar', + copySpecial: 'Còpia especial', pasteSpecial: 'Enganxat especial', pasteValue: 'Enganxar només els valors', pasteFormat: 'Enganxar només el format', diff --git a/packages/sheets-ui/src/locale/en-US.ts b/packages/sheets-ui/src/locale/en-US.ts index bfa3cfce3f..7b966d1146 100644 --- a/packages/sheets-ui/src/locale/en-US.ts +++ b/packages/sheets-ui/src/locale/en-US.ts @@ -186,6 +186,7 @@ const locale = { copy: 'Copy', cut: 'Cut', paste: 'Paste', + copySpecial: 'Copy Special', pasteSpecial: 'Paste Special', pasteValue: 'Paste Value', pasteFormat: 'Paste Format', diff --git a/packages/sheets-ui/src/locale/es-ES.ts b/packages/sheets-ui/src/locale/es-ES.ts index 82ab8e55ea..5bcf06e918 100644 --- a/packages/sheets-ui/src/locale/es-ES.ts +++ b/packages/sheets-ui/src/locale/es-ES.ts @@ -188,6 +188,7 @@ const locale: typeof enUS = { copy: 'Copiar', cut: 'Cortar', paste: 'Pegar', + copySpecial: 'Copiar especial', pasteSpecial: 'Pegado especial', pasteValue: 'Pegar solo valores', pasteFormat: 'Pegar solo formato', diff --git a/packages/sheets-ui/src/locale/fa-IR.ts b/packages/sheets-ui/src/locale/fa-IR.ts index a428a15a12..d7edc9f384 100644 --- a/packages/sheets-ui/src/locale/fa-IR.ts +++ b/packages/sheets-ui/src/locale/fa-IR.ts @@ -188,6 +188,7 @@ const locale: typeof enUS = { copy: 'کپی', cut: 'برش', paste: 'چسباندن', + copySpecial: 'کپی ویژه', pasteSpecial: 'چسباندن ویژه', pasteValue: 'چسباندن مقدار', pasteFormat: 'چسباندن قالب', diff --git a/packages/sheets-ui/src/locale/fr-FR.ts b/packages/sheets-ui/src/locale/fr-FR.ts index 5f4a29aa08..34ca2b3977 100644 --- a/packages/sheets-ui/src/locale/fr-FR.ts +++ b/packages/sheets-ui/src/locale/fr-FR.ts @@ -188,6 +188,7 @@ const locale: typeof enUS = { copy: 'Copier', cut: 'Couper', paste: 'Coller', + copySpecial: 'Collage spécial', pasteSpecial: 'Collage spécial', pasteValue: 'Coller la valeur', pasteFormat: 'Coller le format', diff --git a/packages/sheets-ui/src/locale/ja-JP.ts b/packages/sheets-ui/src/locale/ja-JP.ts index de438db1fe..a72dd834e8 100644 --- a/packages/sheets-ui/src/locale/ja-JP.ts +++ b/packages/sheets-ui/src/locale/ja-JP.ts @@ -188,6 +188,7 @@ const locale: typeof enUS = { copy: 'コピー', cut: '切り取り', paste: '貼り付け', + copySpecial: '選択的にコピー', pasteSpecial: '形式を選択して貼り付け', pasteValue: '値を貼り付け', pasteFormat: '書式を貼り付け', diff --git a/packages/sheets-ui/src/locale/ko-KR.ts b/packages/sheets-ui/src/locale/ko-KR.ts index 63ee9c6fcc..981f7465ee 100644 --- a/packages/sheets-ui/src/locale/ko-KR.ts +++ b/packages/sheets-ui/src/locale/ko-KR.ts @@ -188,6 +188,7 @@ const locale: typeof enUS = { copy: '복사', cut: '자르기', paste: '붙여넣기', + copySpecial: '선택하여 복사', pasteSpecial: '선택하여 붙여넣기', pasteValue: '값 붙여넣기', pasteFormat: '서식 붙여넣기', diff --git a/packages/sheets-ui/src/locale/ru-RU.ts b/packages/sheets-ui/src/locale/ru-RU.ts index 1b61620963..06bdf8bb3c 100644 --- a/packages/sheets-ui/src/locale/ru-RU.ts +++ b/packages/sheets-ui/src/locale/ru-RU.ts @@ -188,6 +188,7 @@ const locale: typeof enUS = { copy: 'Копировать', cut: 'Вырезать', paste: 'Вставить', + copySpecial: 'Выборочная копия', pasteSpecial: 'Специальная вставка', pasteValue: 'Вставить значение', pasteFormat: 'Вставить формат', diff --git a/packages/sheets-ui/src/locale/vi-VN.ts b/packages/sheets-ui/src/locale/vi-VN.ts index 86d37dc705..3b8d39a1d2 100644 --- a/packages/sheets-ui/src/locale/vi-VN.ts +++ b/packages/sheets-ui/src/locale/vi-VN.ts @@ -188,6 +188,7 @@ const locale: typeof enUS = { copy: 'Sao chép', cut: 'Cắt', paste: 'Dán', + copySpecial: 'Sao chép đặc biệt', pasteSpecial: 'Dán đặc biệt', pasteValue: 'Chỉ dán giá trị', pasteFormat: 'Chỉ dán định dạng', diff --git a/packages/sheets-ui/src/locale/zh-CN.ts b/packages/sheets-ui/src/locale/zh-CN.ts index 656d76e102..ca8bdd3419 100644 --- a/packages/sheets-ui/src/locale/zh-CN.ts +++ b/packages/sheets-ui/src/locale/zh-CN.ts @@ -189,6 +189,7 @@ const locale: typeof enUS = { copy: '复制', cut: '剪切', paste: '粘贴', + copySpecial: '选择性复制', pasteSpecial: '选择性粘贴', pasteValue: '仅粘贴值', pasteFormat: '仅粘贴格式', diff --git a/packages/sheets-ui/src/locale/zh-TW.ts b/packages/sheets-ui/src/locale/zh-TW.ts index ab3f4122aa..19491a0372 100644 --- a/packages/sheets-ui/src/locale/zh-TW.ts +++ b/packages/sheets-ui/src/locale/zh-TW.ts @@ -189,6 +189,7 @@ const locale: typeof enUS = { copy: '複製', cut: '剪下', paste: '貼上', + copySpecial: '選擇性複製', pasteSpecial: '選擇性貼上', pasteValue: '僅貼上值', pasteFormat: '僅貼上格式', diff --git a/packages/sheets-ui/src/services/clipboard/__tests__/clipboard-service.spec.ts b/packages/sheets-ui/src/services/clipboard/__tests__/clipboard-service.spec.ts index f9722d1a69..ed61e616e7 100644 --- a/packages/sheets-ui/src/services/clipboard/__tests__/clipboard-service.spec.ts +++ b/packages/sheets-ui/src/services/clipboard/__tests__/clipboard-service.spec.ts @@ -29,7 +29,7 @@ import { } from '@univerjs/sheets'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { discreteRangeToRange } from '../../../controllers/utils/range-tools'; -import { ISheetClipboardService, PREDEFINED_HOOK_NAME } from '../clipboard.service'; +import { ISheetClipboardService, PREDEFINED_HOOK_NAME_PASTE } from '../clipboard.service'; import { COPY_TYPE } from '../type'; import { clipboardTestBed } from './clipboard-test-bed'; import { MockClipboard } from './mock-clipboard'; @@ -1136,7 +1136,7 @@ describe('Test clipboard', () => { }, ]); - (sheetClipboardService as any)._pasteInternal(copyId, PREDEFINED_HOOK_NAME.DEFAULT_PASTE); + (sheetClipboardService as any)._pasteInternal(copyId, PREDEFINED_HOOK_NAME_PASTE.DEFAULT_PASTE); expect(getValues(24, 0, 24, 0)![0][0]).toBe(null); expect(getValues(24, 1, 24, 1)![0][0]!.v).toBe('A25'); diff --git a/packages/sheets-ui/src/services/clipboard/clipboard.service.ts b/packages/sheets-ui/src/services/clipboard/clipboard.service.ts index ceef6496a4..f77c12a3b5 100644 --- a/packages/sheets-ui/src/services/clipboard/clipboard.service.ts +++ b/packages/sheets-ui/src/services/clipboard/clipboard.service.ts @@ -15,7 +15,6 @@ */ import type { - ICellData, ICellDataWithSpanAndDisplay, IDisposable, IMutationInfo, @@ -30,6 +29,7 @@ import type { IDiscreteRange } from '../../controllers/utils/range-tools'; import type { ICellDataWithSpanInfo, IClipboardPropertyItem, + ICopyOptions, IPasteHookKeyType, IPasteHookValueType, IPasteOptionCache, @@ -45,6 +45,7 @@ import { Disposable, ErrorService, extractPureTextFromCell, + getEmptyCell, ICommandService, ILogService, Inject, @@ -60,7 +61,6 @@ import { UniverInstanceType, } from '@univerjs/core'; import { IRenderManagerService, withCurrentTypeOfRenderer } from '@univerjs/engine-render'; - import { getPrimaryForRange, rangeToDiscreteRange, @@ -70,13 +70,11 @@ import { SheetsSelectionsService, } from '@univerjs/sheets'; import { FILE__BMP_CLIPBOARD_MIME_TYPE, FILE__JPEG_CLIPBOARD_MIME_TYPE, FILE__WEBP_CLIPBOARD_MIME_TYPE, FILE_PNG_CLIPBOARD_MIME_TYPE, HTML_CLIPBOARD_MIME_TYPE, IClipboardInterfaceService, imageMimeTypeSet, INotificationService, IPlatformService, PLAIN_TEXT_CLIPBOARD_MIME_TYPE } from '@univerjs/ui'; - import { BehaviorSubject } from 'rxjs'; import { virtualizeDiscreteRanges } from '../../controllers/utils/range-tools'; import { IMarkSelectionService } from '../mark-selection/mark-selection.service'; import { SheetSkeletonManagerService } from '../sheet-skeleton-manager.service'; import { createCopyPasteSelectionStyle } from '../utils/selection-util'; - import { cloneCellDataWithSpanInfo } from './clone'; import { CopyContentCache, extractId, genId } from './copy-content-cache'; import { HtmlToUSMService } from './html-to-usm/converter'; @@ -87,8 +85,11 @@ import { COPY_TYPE } from './type'; import { USMToHtmlService } from './usm-to-html/convertor'; import { convertTextToTable, discreteRangeContainsRange, htmlContainsImage, htmlIsFromExcel, mergeSetRangeValues, rangeIntersectWithDiscreteRange, spilitLargeSetRangeValuesMutations } from './utils'; -export const PREDEFINED_HOOK_NAME = { +export const PREDEFINED_HOOK_NAME_COPY = { DEFAULT_COPY: 'default-copy', + SPECIAL_COPY_FORMULA_ONLY: 'special-copy-formula-only', +} as const; +export const PREDEFINED_HOOK_NAME_PASTE = { DEFAULT_PASTE: 'default-paste', SPECIAL_PASTE_VALUE: 'special-paste-value', SPECIAL_PASTE_FORMAT: 'special-paste-format', @@ -96,6 +97,10 @@ export const PREDEFINED_HOOK_NAME = { SPECIAL_PASTE_BESIDES_BORDER: 'special-paste-besides-border', SPECIAL_PASTE_FORMULA: 'special-paste-formula', } as const; +export const PREDEFINED_HOOK_NAME = { + ...PREDEFINED_HOOK_NAME_COPY, + ...PREDEFINED_HOOK_NAME_PASTE, +} as const; const IMAGE_MIME_TO_EXTENSION = { [FILE_PNG_CLIPBOARD_MIME_TYPE]: 'png', @@ -129,7 +134,7 @@ export interface ISheetClipboardService { getPasteOptionsCache: () => IPasteOptionCache | null; updatePasteOptionsCache(cache: IPasteOptionCache | null): void; - copy(): Promise; + copy(options?: ICopyOptions): Promise; cut(): Promise; paste(item: ClipboardItem, pasteType?: string): Promise; // get content from a ClipboardItem and paste it. legacyPaste(html?: string, text?: string, files?: File[]): Promise; // paste a HTML string or plain text directly. @@ -137,7 +142,7 @@ export interface ISheetClipboardService { rePasteWithPasteType(type: IPasteHookKeyType): boolean; disposePasteOptionsCache(): void; - generateCopyContent(workbookId: string, worksheetId: string, range: IRange, copyType?: COPY_TYPE): Nullable; + generateCopyContent(workbookId: string, worksheetId: string, range: IRange, options?: ICopyOptions): Nullable; copyContentCache(): CopyContentCache; // return the cache content for inner copy/cut/paste. addClipboardHook(hook: ISheetClipboardHook): IDisposable; // add a hook to the clipboard service getClipboardHooks(): ISheetClipboardHook[]; // get all hooks @@ -217,30 +222,33 @@ export class SheetClipboardService extends Disposable implements ISheetClipboard return this._copyContentCache; } - generateCopyContent(workbookId: string, worksheetId: string, range: IRange, copyType: COPY_TYPE = COPY_TYPE.COPY): Nullable { - const hooks = this._clipboardHooks; + generateCopyContent(workbookId: string, worksheetId: string, range: IRange, { copyType = COPY_TYPE.COPY, copyHookType = PREDEFINED_HOOK_NAME_COPY.DEFAULT_COPY } = {}): Nullable { + const hooks = this._clipboardHooks.filter((h) => h.id === copyHookType); hooks.forEach((h) => h.onBeforeCopy?.(workbookId, worksheetId, range, copyType)); - const copyContent = this._generateCopyContent(workbookId, worksheetId, range, this._clipboardHooks); + const copyContent = this._generateCopyContent(workbookId, worksheetId, range, hooks, copyHookType); hooks.forEach((h) => h.onAfterCopy?.()); - return copyContent; } - async copy(copyType = COPY_TYPE.COPY): Promise { + async copy({ + copyType = COPY_TYPE.COPY, + copyHookType = PREDEFINED_HOOK_NAME_COPY.DEFAULT_COPY, + } = {}): Promise { const selection = this._selectionManagerService.getCurrentLastSelection(); if (!selection) { return false; // maybe we should notify user that there is no selection } - const workbook = this._univerInstanceService.getCurrentUnitForType(UniverInstanceType.UNIVER_SHEET)!; - const worksheet = workbook.getActiveSheet(); - if (!worksheet) { + const workbook = this._univerInstanceService.getCurrentUnitForType(UniverInstanceType.UNIVER_SHEET); + const worksheet = workbook?.getActiveSheet(); + if (!workbook || !worksheet) { return false; } + const workbookId = workbook.getUnitId(); const worksheetId = worksheet.getSheetId(); - const copyContent = this.generateCopyContent(workbookId, worksheetId, selection.range, copyType); + const copyContent = this.generateCopyContent(workbookId, worksheetId, selection.range, { copyType, copyHookType }); if (!copyContent) { return false; @@ -271,10 +279,12 @@ export class SheetClipboardService extends Disposable implements ISheetClipboard } async cut(): Promise { - return this.copy(COPY_TYPE.CUT); + return this.copy({ + copyType: COPY_TYPE.CUT, + }); } - async paste(item: ClipboardItem, pasteType = PREDEFINED_HOOK_NAME.DEFAULT_PASTE): Promise { + async paste(item: ClipboardItem, pasteType = PREDEFINED_HOOK_NAME_PASTE.DEFAULT_PASTE): Promise { const types = item.types; const text = types.indexOf(PLAIN_TEXT_CLIPBOARD_MIME_TYPE) !== -1 @@ -333,15 +343,15 @@ export class SheetClipboardService extends Disposable implements ISheetClipboard const isFromExcel = htmlIsFromExcel(html ?? ''); if (files && !isFromExcel) { - return this._pasteFiles(files, PREDEFINED_HOOK_NAME.DEFAULT_PASTE); + return this._pasteFiles(files, PREDEFINED_HOOK_NAME_PASTE.DEFAULT_PASTE); } else if (html) { - return this._pasteHTML(html, PREDEFINED_HOOK_NAME.DEFAULT_PASTE); + return this._pasteHTML(html, PREDEFINED_HOOK_NAME_PASTE.DEFAULT_PASTE); } else if (text) { // Converts text with tabs and newlines into an HTML table if (/[\n\t]/.test(text)) { - return this._pasteHTML(convertTextToTable(text), PREDEFINED_HOOK_NAME.DEFAULT_PASTE); + return this._pasteHTML(convertTextToTable(text), PREDEFINED_HOOK_NAME_PASTE.DEFAULT_PASTE); } else { - return this._pastePlainText(text, PREDEFINED_HOOK_NAME.DEFAULT_PASTE); + return this._pastePlainText(text, PREDEFINED_HOOK_NAME_PASTE.DEFAULT_PASTE); } } else { return this._pasteUnrecognized(); @@ -369,7 +379,7 @@ export class SheetClipboardService extends Disposable implements ISheetClipboard cellMatrix, colProperties, rowProperties, - }, target, PREDEFINED_HOOK_NAME[type] as IPasteHookValueType, source); + }, target, PREDEFINED_HOOK_NAME_PASTE[type], source); return true; } @@ -407,7 +417,7 @@ export class SheetClipboardService extends Disposable implements ISheetClipboard return this._clipboardHooks; } - private _generateCopyContent(unitId: string, subUnitId: string, range: IRange, hooks: ISheetClipboardHook[]): Nullable { + private _generateCopyContent(unitId: string, subUnitId: string, range: IRange, hooks: ISheetClipboardHook[], copyHookType = PREDEFINED_HOOK_NAME_COPY.DEFAULT_COPY): Nullable { const workbook = this._univerInstanceService.getUniverSheetInstance(unitId); const worksheet = workbook?.getSheetBySheetId(subUnitId); @@ -417,7 +427,7 @@ export class SheetClipboardService extends Disposable implements ISheetClipboard // get filtered out rows those are filtered out by plugins (e.g. filter feature) const filteredRows = hooks.reduce((acc, cur) => { - const rows = cur.getFilteredOutRows?.(range); + const rows = cur.getFilteredOutRows?.(unitId, subUnitId, range); rows?.forEach((r) => acc.add(r)); return acc; }, new Set()); @@ -438,23 +448,27 @@ export class SheetClipboardService extends Disposable implements ISheetClipboard } discreteRange.rows.push(r); for (let c = startColumn; c <= endColumn; c++) { - const cellData = matrix.getValue(r, c); - if (cellData) { - const newCellData = cloneCellDataWithSpanAndDisplay(cellData)!; - plainMatrix.setValue(rowIndex - startRow, c - startColumn, { - ...getEmptyCell(), - ...newCellData, - }); - - delete newCellData.displayV; - matrixFragment.setValue(rowIndex - startRow, c - startColumn, { - ...getEmptyCell(), - ...newCellData, - }); + if (copyHookType !== PREDEFINED_HOOK_NAME_COPY.DEFAULT_COPY) { + hooks.forEach((h) => h.handleMatrixOnCell?.(r, c, rowIndex - startRow, c - startColumn, matrix, matrixFragment, plainMatrix)); } else { - plainMatrix.setValue(rowIndex - startRow, c - startColumn, getEmptyCell()); - matrixFragment.setValue(rowIndex - startRow, c - startColumn, getEmptyCell()); - matrix.setValue(r, c, getEmptyCell()); + const cellData = matrix.getValue(r, c); + if (cellData) { + const newCellData = cloneCellDataWithSpanAndDisplay(cellData)!; + plainMatrix.setValue(rowIndex - startRow, c - startColumn, { + ...getEmptyCell(), + ...newCellData, + }); + + delete newCellData.displayV; + matrixFragment.setValue(rowIndex - startRow, c - startColumn, { + ...getEmptyCell(), + ...newCellData, + }); + } else { + plainMatrix.setValue(rowIndex - startRow, c - startColumn, getEmptyCell()); + matrixFragment.setValue(rowIndex - startRow, c - startColumn, getEmptyCell()); + matrix.setValue(r, c, getEmptyCell()); + } } } rowIndex += 1; @@ -774,7 +788,7 @@ export class SheetClipboardService extends Disposable implements ISheetClipboard // if hooks are not special or default, it will be executed in any case. // other hooks will be executed only when the paste type is the same as the hook name, including the default one const filteredHooks: ISheetClipboardHook[] = enabledHooks.filter( - (h) => (!h.specialPasteInfo && h.id !== PREDEFINED_HOOK_NAME.DEFAULT_PASTE) || pasteType === h.id + (h) => (!h.specialPasteInfo && h.id !== PREDEFINED_HOOK_NAME_PASTE.DEFAULT_PASTE) || pasteType === h.id ); filteredHooks.forEach((h) => { if (rowProperties) { @@ -899,9 +913,9 @@ export class SheetClipboardService extends Disposable implements ISheetClipboard const rowSpan = mainCell?.rowSpan || 1; const colSpan = mainCell?.colSpan || 1; - const shouldExpandPrimary = pasteType === PREDEFINED_HOOK_NAME.DEFAULT_PASTE - || pasteType === PREDEFINED_HOOK_NAME.SPECIAL_PASTE_BESIDES_BORDER - || pasteType === PREDEFINED_HOOK_NAME.SPECIAL_PASTE_FORMAT; + const shouldExpandPrimary = pasteType === PREDEFINED_HOOK_NAME_PASTE.DEFAULT_PASTE + || pasteType === PREDEFINED_HOOK_NAME_PASTE.SPECIAL_PASTE_BESIDES_BORDER + || pasteType === PREDEFINED_HOOK_NAME_PASTE.SPECIAL_PASTE_FORMAT; if (shouldExpandPrimary && (rowSpan > 1 || colSpan > 1)) { const mergeRange = { @@ -1357,14 +1371,3 @@ function isMultipleCells(cellMatrix: ObjectMatrix): } // #endregion - -function getEmptyCell(): ICellData { - return { - p: null, - v: null, - s: null, - f: null, - si: null, - t: null, - }; -} diff --git a/packages/sheets-ui/src/services/clipboard/type.ts b/packages/sheets-ui/src/services/clipboard/type.ts index e62d00b025..25947fb292 100644 --- a/packages/sheets-ui/src/services/clipboard/type.ts +++ b/packages/sheets-ui/src/services/clipboard/type.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -import type { ICellData, IDocumentData, IMutationInfo, IRange, ObjectMatrix } from '@univerjs/core'; +import type { ICellData, ICellDataWithSpanAndDisplay, IDocumentData, IMutationInfo, IRange, ObjectMatrix } from '@univerjs/core'; import type { IDiscreteRange } from '../../controllers/utils/range-tools'; -import type { PREDEFINED_HOOK_NAME } from './clipboard.service'; +import type { PREDEFINED_HOOK_NAME_COPY, PREDEFINED_HOOK_NAME_PASTE } from './clipboard.service'; export enum COPY_TYPE { COPY = 'COPY', @@ -255,9 +255,29 @@ export interface ISheetClipboardHook { * The callback would be called before the clipboard service decides what region need to be copied from or pasted to. * It would jump over these filtered rows when copying or pasting. */ - getFilteredOutRows?(range: IRange): number[]; + getFilteredOutRows?(unitId: string, subUnitId: string, range: IRange): number[]; + + /** + * Handle matrix on each cell of copy operation. + */ + handleMatrixOnCell?( + row: number, + column: number, + rowIndexInMatrix: number, + columnIndexInMatrix: number, + matrix: ObjectMatrix, + matrixFragment: ObjectMatrix, + plainMatrix: ObjectMatrix + ): void; } +export interface ICopyOptions { + copyType?: COPY_TYPE; + copyHookType?: ICopyHookValueType; +} +export type ICopyHookKeyType = keyof typeof PREDEFINED_HOOK_NAME_COPY; +export type ICopyHookValueType = typeof PREDEFINED_HOOK_NAME_COPY[ICopyHookKeyType]; + export interface IPasteOptionCache { target: IPasteTarget; source?: IPasteSource; @@ -266,8 +286,6 @@ export interface IPasteOptionCache { colProperties?: IClipboardPropertyItem[]; pasteType: IPasteHookValueType; } - export type IPasteSource = ISheetDiscreteRangeLocation & { copyId: string; copyType: COPY_TYPE }; -export type IPasteHookKeyType = Exclude; -export type IPasteHookValueTypeWithoutDefaultCopy = typeof PREDEFINED_HOOK_NAME[IPasteHookKeyType]; -export type IPasteHookValueType = Exclude; +export type IPasteHookKeyType = keyof typeof PREDEFINED_HOOK_NAME_PASTE; +export type IPasteHookValueType = typeof PREDEFINED_HOOK_NAME_PASTE[IPasteHookKeyType]; diff --git a/packages/sheets-ui/src/views/clipboard/ClipboardPopupMenu.tsx b/packages/sheets-ui/src/views/clipboard/ClipboardPopupMenu.tsx index daa68c34cb..8560e8582c 100644 --- a/packages/sheets-ui/src/views/clipboard/ClipboardPopupMenu.tsx +++ b/packages/sheets-ui/src/views/clipboard/ClipboardPopupMenu.tsx @@ -25,7 +25,7 @@ import { useState } from 'react'; import { SheetOptionalPasteCommand } from '../../commands/commands/clipboard.command'; import { useActiveWorkbook } from '../../components/hook'; import { getSheetObject } from '../../controllers/utils/component-tools'; -import { ISheetClipboardService, PREDEFINED_HOOK_NAME } from '../../services/clipboard/clipboard.service'; +import { ISheetClipboardService, PREDEFINED_HOOK_NAME_PASTE } from '../../services/clipboard/clipboard.service'; import { ISheetSelectionRenderService } from '../../services/selection/base-selection-render.service'; import { SheetSkeletonManagerService } from '../../services/sheet-skeleton-manager.service'; @@ -151,7 +151,7 @@ export const ClipboardPopupMenu = () => { type: 'checkbox', value: item.value, label: localeService.t(item.label), - checked: pasteOptionsCache?.pasteType === PREDEFINED_HOOK_NAME[item.value as IPasteHookKeyType], + checked: pasteOptionsCache?.pasteType === PREDEFINED_HOOK_NAME_PASTE[item.value as IPasteHookKeyType], onSelect: () => handleClick(item.value), }))} open={visible}