feat: add a Copy Formula Only menu item to the right-click menu (#6287)

This commit is contained in:
wpxp123456
2025-12-12 12:03:29 +08:00
committed by GitHub
parent 44c935f066
commit e9fc08679f
45 changed files with 352 additions and 138 deletions
+1 -1
View File
@@ -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';
+14 -1
View File
@@ -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<T>(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,
};
}
@@ -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 (
@@ -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)) {
@@ -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'));
@@ -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,
});
},
};
@@ -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 = {
@@ -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<Worksheet> = 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<ICellDataWithSpanAndDisplay>,
matrixFragment: ObjectMatrix<ICellDataWithSpanInfo>,
plainMatrix: ObjectMatrix<ICellDataWithSpanAndDisplay>
) {
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<Workbook> {
if (unitId) {
return this._univerInstanceService.getUnit<Workbook>(unitId, UniverInstanceType.UNIVER_SHEET);
}
return this._univerInstanceService.getCurrentUnitOfType<Workbook>(UniverInstanceType.UNIVER_SHEET);
}
private _getWorksheet(unitId?: string, subUnitId?: string): Nullable<Worksheet> {
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<Workbook>(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);
}
@@ -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,
@@ -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,
@@ -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<boolea
);
}
// Right click menu - Copy Formula Only
export function CopyFormulaOnlyMenuItemFactory(accessor: IAccessor): IMenuItem {
return {
id: SheetCopyFormulaOnlyCommand.id,
type: MenuItemType.BUTTON,
title: 'formula.operation.copyFormulaOnly',
disabled$: getCurrentRangeDisable$(accessor, {
workbookTypes: [WorkbookCopyPermission],
worksheetTypes: [WorksheetCopyPermission],
rangeTypes: [RangeProtectionPermissionViewPoint],
}),
hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET),
};
}
// Right click menu - Paste Formula
export function PasteFormulaMenuItemFactory(accessor: IAccessor): IMenuItem {
return {
id: SheetOnlyPasteFormulaCommand.id,
@@ -220,5 +236,6 @@ export function PasteFormulaMenuItemFactory(accessor: IAccessor): IMenuItem {
})),
map(([d1, d2]) => d1 || d2)
),
hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET),
};
}
+1 -1
View File
@@ -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';
@@ -114,6 +114,7 @@ const locale: typeof enUS = {
syntax: 'SINTAXI',
},
operation: {
copyFormulaOnly: 'Copia només la fórmula',
pasteFormula: 'Enganxa la fórmula',
},
},
@@ -113,6 +113,7 @@ const locale = {
syntax: 'SYNTAX',
},
operation: {
copyFormulaOnly: 'Copy Formula Only',
pasteFormula: 'Paste Formula',
},
},
@@ -114,6 +114,7 @@ const locale: typeof enUS = {
syntax: 'SINTAXIS',
},
operation: {
copyFormulaOnly: 'Copiar solo fórmula',
pasteFormula: 'Pegar fórmula',
},
},
@@ -114,6 +114,7 @@ const locale: typeof enUS = {
syntax: 'سینتکس',
},
operation: {
copyFormulaOnly: 'کپی فقط فرمول',
pasteFormula: 'چسباندن فرمول',
},
},
@@ -114,6 +114,7 @@ const locale: typeof enUS = {
syntax: 'SYNTAXE',
},
operation: {
copyFormulaOnly: 'Copier uniquement la formule',
pasteFormula: 'Coller la formule',
},
},
@@ -114,6 +114,7 @@ const locale: typeof enUS = {
syntax: '構文',
},
operation: {
copyFormulaOnly: '数式のみをコピー',
pasteFormula: '数式を貼り付け',
},
},
@@ -114,6 +114,7 @@ const locale: typeof enUS = {
syntax: '구문',
},
operation: {
copyFormulaOnly: '수식만 복사',
pasteFormula: '수식 붙여넣기',
},
},
@@ -114,6 +114,7 @@ const locale: typeof enUS = {
syntax: 'СИНТАКСИС',
},
operation: {
copyFormulaOnly: 'Копировать только формулу',
pasteFormula: 'Вставить Формулу',
},
},
@@ -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',
},
},
@@ -118,6 +118,7 @@ const locale: typeof enUS = {
},
operation: {
copyFormulaOnly: '仅复制公式',
pasteFormula: '仅粘贴公式',
},
},
@@ -118,6 +118,7 @@ const locale: typeof enUS = {
},
operation: {
copyFormulaOnly: '僅複製公式',
pasteFormula: '僅貼上公式',
},
},
@@ -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)) {
@@ -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);
@@ -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<Workbook>(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',
},
@@ -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,
@@ -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 {
@@ -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<UnitAction, boolean> & { 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],
+3 -3
View File
@@ -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';
+1
View File
@@ -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',
+1
View File
@@ -186,6 +186,7 @@ const locale = {
copy: 'Copy',
cut: 'Cut',
paste: 'Paste',
copySpecial: 'Copy Special',
pasteSpecial: 'Paste Special',
pasteValue: 'Paste Value',
pasteFormat: 'Paste Format',
+1
View File
@@ -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',
+1
View File
@@ -188,6 +188,7 @@ const locale: typeof enUS = {
copy: 'کپی',
cut: 'برش',
paste: 'چسباندن',
copySpecial: 'کپی ویژه',
pasteSpecial: 'چسباندن ویژه',
pasteValue: 'چسباندن مقدار',
pasteFormat: 'چسباندن قالب',
+1
View File
@@ -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',
+1
View File
@@ -188,6 +188,7 @@ const locale: typeof enUS = {
copy: 'コピー',
cut: '切り取り',
paste: '貼り付け',
copySpecial: '選択的にコピー',
pasteSpecial: '形式を選択して貼り付け',
pasteValue: '値を貼り付け',
pasteFormat: '書式を貼り付け',
+1
View File
@@ -188,6 +188,7 @@ const locale: typeof enUS = {
copy: '복사',
cut: '자르기',
paste: '붙여넣기',
copySpecial: '선택하여 복사',
pasteSpecial: '선택하여 붙여넣기',
pasteValue: '값 붙여넣기',
pasteFormat: '서식 붙여넣기',
+1
View File
@@ -188,6 +188,7 @@ const locale: typeof enUS = {
copy: 'Копировать',
cut: 'Вырезать',
paste: 'Вставить',
copySpecial: 'Выборочная копия',
pasteSpecial: 'Специальная вставка',
pasteValue: 'Вставить значение',
pasteFormat: 'Вставить формат',
+1
View File
@@ -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',
+1
View File
@@ -189,6 +189,7 @@ const locale: typeof enUS = {
copy: '复制',
cut: '剪切',
paste: '粘贴',
copySpecial: '选择性复制',
pasteSpecial: '选择性粘贴',
pasteValue: '仅粘贴值',
pasteFormat: '仅粘贴格式',
+1
View File
@@ -189,6 +189,7 @@ const locale: typeof enUS = {
copy: '複製',
cut: '剪下',
paste: '貼上',
copySpecial: '選擇性複製',
pasteSpecial: '選擇性貼上',
pasteValue: '僅貼上值',
pasteFormat: '僅貼上格式',
@@ -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');
@@ -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<boolean>;
copy(options?: ICopyOptions): Promise<boolean>;
cut(): Promise<boolean>;
paste(item: ClipboardItem, pasteType?: string): Promise<boolean>; // get content from a ClipboardItem and paste it.
legacyPaste(html?: string, text?: string, files?: File[]): Promise<boolean>; // 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<ICopyContent>;
generateCopyContent(workbookId: string, worksheetId: string, range: IRange, options?: ICopyOptions): Nullable<ICopyContent>;
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<ICopyContent> {
const hooks = this._clipboardHooks;
generateCopyContent(workbookId: string, worksheetId: string, range: IRange, { copyType = COPY_TYPE.COPY, copyHookType = PREDEFINED_HOOK_NAME_COPY.DEFAULT_COPY } = {}): Nullable<ICopyContent> {
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<boolean> {
async copy({
copyType = COPY_TYPE.COPY,
copyHookType = PREDEFINED_HOOK_NAME_COPY.DEFAULT_COPY,
} = {}): Promise<boolean> {
const selection = this._selectionManagerService.getCurrentLastSelection();
if (!selection) {
return false; // maybe we should notify user that there is no selection
}
const workbook = this._univerInstanceService.getCurrentUnitForType<Workbook>(UniverInstanceType.UNIVER_SHEET)!;
const worksheet = workbook.getActiveSheet();
if (!worksheet) {
const workbook = this._univerInstanceService.getCurrentUnitForType<Workbook>(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<boolean> {
return this.copy(COPY_TYPE.CUT);
return this.copy({
copyType: COPY_TYPE.CUT,
});
}
async paste(item: ClipboardItem, pasteType = PREDEFINED_HOOK_NAME.DEFAULT_PASTE): Promise<boolean> {
async paste(item: ClipboardItem, pasteType = PREDEFINED_HOOK_NAME_PASTE.DEFAULT_PASTE): Promise<boolean> {
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<ICopyContent> {
private _generateCopyContent(unitId: string, subUnitId: string, range: IRange, hooks: ISheetClipboardHook[], copyHookType = PREDEFINED_HOOK_NAME_COPY.DEFAULT_COPY): Nullable<ICopyContent> {
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<number>());
@@ -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<ICellDataWithSpanAndDisplay>):
}
// #endregion
function getEmptyCell(): ICellData {
return {
p: null,
v: null,
s: null,
f: null,
si: null,
t: null,
};
}
@@ -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<ICellDataWithSpanAndDisplay>,
matrixFragment: ObjectMatrix<ICellDataWithSpanInfo>,
plainMatrix: ObjectMatrix<ICellDataWithSpanAndDisplay>
): 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<keyof typeof PREDEFINED_HOOK_NAME, 'default-copy'>;
export type IPasteHookValueTypeWithoutDefaultCopy = typeof PREDEFINED_HOOK_NAME[IPasteHookKeyType];
export type IPasteHookValueType = Exclude<IPasteHookValueTypeWithoutDefaultCopy, 'default-copy'>;
export type IPasteHookKeyType = keyof typeof PREDEFINED_HOOK_NAME_PASTE;
export type IPasteHookValueType = typeof PREDEFINED_HOOK_NAME_PASTE[IPasteHookKeyType];
@@ -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}