Skip to content

Commit

Permalink
fix(sheet): fix some copy/paste bugs (#1754)
Browse files Browse the repository at this point in the history
* fix(sheet): fix some copy/paste bugs

* fix: fix type

* feat: add rich text parsing logic

* fix: add test and fix bug

* fix: fix ts

* fix: delete useless function

* fix: fix copy

* fix: the paste area overlaps with merged cells, so pasting is not allowed

* fix: fix test

* fix: fix test

* fix: pasting to interpret number formats

* fix: fix lint

* fix: pasting to interpret formulas

* fix: lint

* fix: add some test

* fix: paste add underline

* fix: fix paste with merge and insert col

* fix: parse fontsize pt to px

* fix: add paste cell rotate

* fix: add paste test

* fix: fix error parse by rbga

* fix: fix ts

* fix: fix parse col width

* fix: modify the way of get worksheet in clipboard service
  • Loading branch information
ybzky authored Apr 13, 2024
1 parent 8ae91b7 commit 496dcb8
Show file tree
Hide file tree
Showing 25 changed files with 2,192 additions and 396 deletions.
166 changes: 166 additions & 0 deletions packages/core/src/shared/clipboard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/**
* Copyright 2023-present DreamNum Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { BaselineOffset, BooleanNumber } from '../types/enum';
import type { IDocumentBody, ITextRun } from '../types/interfaces';
import { Tools } from './tools';

export function getBodySliceHtml(body: IDocumentBody, startIndex: number, endIndex: number) {
const { dataStream, textRuns = [] } = body;
let cursorIndex = startIndex;
const spanList: string[] = [];

for (const textRun of textRuns) {
const { st, ed } = textRun;
if (Tools.hasIntersectionBetweenTwoRanges(startIndex, endIndex, st, ed)) {
if (st > cursorIndex) {
spanList.push(dataStream.slice(cursorIndex, st));

spanList.push(covertTextRunToHtml(dataStream, {
...textRun,
ed: Math.min(ed, endIndex),
}));
} else {
spanList.push(covertTextRunToHtml(dataStream, {
...textRun,
st: cursorIndex,
ed: Math.min(ed, endIndex),
}));
}
}

cursorIndex = Math.max(startIndex, Math.min(ed, endIndex));
}

if (cursorIndex !== endIndex) {
spanList.push(dataStream.slice(cursorIndex, endIndex));
}

return spanList.join('');
}

export function convertBodyToHtml(body: IDocumentBody, withParagraphInfo: boolean = true): string {
if (withParagraphInfo && body.paragraphs?.length) {
const { dataStream, paragraphs = [] } = body;
let result = '';
let cursorIndex = -1;
for (const paragraph of paragraphs) {
const { startIndex, paragraphStyle = {} } = paragraph;
const { spaceAbove, spaceBelow, lineSpacing } = paragraphStyle;
const style = [];

if (spaceAbove != null) {
if (typeof spaceAbove === 'number') {
style.push(`margin-top: ${spaceAbove}px`);
} else {
style.push(`margin-top: ${spaceAbove.v}px`);
}
}

if (spaceBelow != null) {
if (typeof spaceBelow === 'number') {
style.push(`margin-bottom: ${spaceBelow}px`);
} else {
style.push(`margin-bottom: ${spaceBelow.v}px`);
}
}

if (lineSpacing != null) {
style.push(`line-height: ${lineSpacing}`);
}

if (startIndex > cursorIndex + 1) {
result += `<p class="UniverNormal" ${style.length ? `style="${style.join('; ')};"` : ''
}>${getBodySliceHtml(body, cursorIndex + 1, startIndex)}</p>`;
} else {
result += `<p class="UniverNormal" ${style.length ? `style="${style.join('; ')};"` : ''
}></p>`;
}

cursorIndex = startIndex;
}

if (cursorIndex !== dataStream.length) {
result += getBodySliceHtml(body, cursorIndex, dataStream.length);
}

return result;
} else {
return getBodySliceHtml(body, 0, body.dataStream.length);
}
}

export function covertTextRunToHtml(dataStream: string, textRun: ITextRun): string {
const { st: start, ed, ts = {} } = textRun;
const { ff, fs, it, bl, ul, st, ol, bg, cl, va } = ts;

let html = dataStream.slice(start, ed);
const style: string[] = [];

// italic
if (it === BooleanNumber.TRUE) {
html = `<i>${html}</i>`;
}

// subscript and superscript
if (va === BaselineOffset.SUPERSCRIPT) {
html = `<sup>${html}</sup>`;
} else if (va === BaselineOffset.SUBSCRIPT) {
html = `<sub>${html}</sub>`;
}

// underline
if (ul?.s === BooleanNumber.TRUE) {
html = `<u>${html}</u>`;
}

// strick-through
if (st?.s === BooleanNumber.TRUE) {
html = `<s>${html}</s>`;
}

// bold
if (bl === BooleanNumber.TRUE) {
html = `<strong>${html}</strong>`;
}

// font family
if (ff) {
style.push(`font-family: ${ff}`);
}

// font color
if (cl) {
style.push(`color: ${cl.rgb}`);
}

// font size
if (fs) {
style.push(`font-size: ${fs}pt`);
}

// overline
if (ol) {
style.push('text-decoration: overline');
}

// background color
if (bg) {
style.push(`background: ${bg.rgb}`);
}

return style.length ? `<span style="${style.join('; ')};">${html}</span>` : html;
}
2 changes: 2 additions & 0 deletions packages/core/src/shared/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,8 @@ export function getBorderStyleType(type: string) {
str = BorderStyleTypes.MEDIUM_DASH_DOT_DOT;
} else if (type === '1.5pt solid') {
str = BorderStyleTypes.THICK;
} else if (!type.includes('none')) {
str = BorderStyleTypes.THIN;
} else {
return BorderStyleTypes.NONE;
}
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/shared/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,6 @@ export * from './sort-rules';
export * from './tools';
export * from './types';
export * from './debounce';
export * from './clipboard';
export { queryObjectMatrix } from './object-matrix-query';
export { moveRangeByOffset } from './range';
2 changes: 1 addition & 1 deletion packages/engine-numfmt/src/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ type LocaleTag =
| 'th'
| 'tr';

interface ParsedReturnType {
export interface ParsedReturnType {
/**
* The parsed value. For dates, this will be an Excel style serial date unless the nativeDate option is used.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
* limitations under the License.
*/

import type { BorderStyleTypes, IRange, IScale, ObjectMatrix } from '@univerjs/core';
import type { IRange, IScale, ObjectMatrix } from '@univerjs/core';
import { BorderStyleTypes } from '@univerjs/core';

import { BORDER_TYPE, COLOR_BLACK_RGB, FIX_ONE_PIXEL_BLUR_OFFSET } from '../../../basics/const';
import { drawDiagonalLineByBorderType, drawLineByBorderType, getLineWidth, setLineType } from '../../../basics/draw';
Expand Down Expand Up @@ -95,6 +96,10 @@ export class Border extends SheetExtension {
for (const key in borderCaches) {
const { type, style, color } = borderCaches[key] as BorderCacheItem;

if (style === BorderStyleTypes.NONE) {
continue;
}

let startY = cellStartY;
let endY = cellEndY;
let startX = cellStartX;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { ICommandService, IUniverInstanceService, ObjectMatrix } from '@univerjs
import { Lexer } from '@univerjs/engine-formula';
import { type ISetRangeValuesMutationParams, SetRangeValuesMutation } from '@univerjs/sheets';
import type { ICellDataWithSpanInfo } from '@univerjs/sheets-ui';
import { COPY_TYPE, ISelectionRenderService, SelectionRenderService } from '@univerjs/sheets-ui';
import { COPY_TYPE, ISelectionRenderService, PREDEFINED_HOOK_NAME, SelectionRenderService } from '@univerjs/sheets-ui';
import type { Injector } from '@wendellhu/redi';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';

Expand Down Expand Up @@ -101,6 +101,7 @@ describe('Test paste with formula', () => {
rangeType: 0,
},
copyType: COPY_TYPE.COPY,
pasteType: PREDEFINED_HOOK_NAME.DEFAULT_PASTE,
};

const result = {
Expand Down Expand Up @@ -331,6 +332,7 @@ describe('Test paste with formula', () => {
endColumn: 8,
rangeType: 0,
},
pasteType: PREDEFINED_HOOK_NAME.DEFAULT_PASTE,
};

const result = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import { Lexer } from '@univerjs/engine-formula';
import type { ISetRangeValuesMutationParams } from '@univerjs/sheets';
import { SetRangeValuesMutation, SetRangeValuesUndoMutationFactory } from '@univerjs/sheets';
import type { ICellDataWithSpanInfo, ICopyPastePayload, ISheetClipboardHook, ISheetRangeLocation } from '@univerjs/sheets-ui';
import { COPY_TYPE, ISheetClipboardService } from '@univerjs/sheets-ui';
import { COPY_TYPE, ISheetClipboardService, PREDEFINED_HOOK_NAME } from '@univerjs/sheets-ui';
import type { IAccessor } from '@wendellhu/redi';
import { Inject, Injector } from '@wendellhu/redi';

Expand Down Expand Up @@ -86,6 +86,7 @@ export class FormulaClipboardController extends Disposable {
const copyInfo = {
copyType: payload.copyType || COPY_TYPE.COPY,
copyRange: pasteFrom?.range,
pasteType: payload.pasteType,
};
const pastedRange = pasteTo.range;
const matrix = data;
Expand Down Expand Up @@ -115,6 +116,7 @@ export function getSetCellFormulaMutations(
copyInfo: {
copyType: COPY_TYPE;
copyRange?: IRange;
pasteType: string;
},
lexer: Lexer,
isSpecialPaste = false
Expand Down Expand Up @@ -162,7 +164,7 @@ export function getSetCellFormulaMutations(
valueObject.f = null;
valueObject.v = null;
valueObject.p = null;
} else if (isFormulaString(originalFormula)) {
} else if (isFormulaString(originalFormula) && copyInfo.pasteType === PREDEFINED_HOOK_NAME.DEFAULT_PASTE) {
const rowIndex = row % copyRowLength;
const colIndex = col % copyColumnLength;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,11 @@ import {
SetNumfmtMutation,
transformCellsToRange,
} from '@univerjs/sheets';
import type { ICellDataWithSpanInfo } from '@univerjs/sheets-ui';
import { COPY_TYPE, getRepeatRange, ISheetClipboardService, PREDEFINED_HOOK_NAME } from '@univerjs/sheets-ui';
import { Inject, Injector } from '@wendellhu/redi';

import numfmt from '@univerjs/engine-numfmt';
import { SHEET_NUMFMT_PLUGIN } from '../base/const/PLUGIN_NAME';
import { mergeNumfmtMutations } from '../utils/mutation';

Expand Down Expand Up @@ -69,7 +71,8 @@ export class NumfmtCopyPasteController extends Disposable {
const { copyType = COPY_TYPE.COPY, pasteType } = payload;
const { range: copyRange } = pasteFrom || {};
const { range: pastedRange } = pasteTo;
return this._generateNumfmtMutations(pastedRange, { copyType, pasteType, copyRange });
const res = this._generateNumfmtMutations(pastedRange, { copyType, pasteType, copyRange, data });
return res;
},
})
);
Expand Down Expand Up @@ -115,19 +118,66 @@ export class NumfmtCopyPasteController extends Disposable {
copyType: COPY_TYPE;
copyRange?: IRange;
pasteType: string;
data: ObjectMatrix<ICellDataWithSpanInfo>;
}
) {
const workbook = this._univerInstanceService.getCurrentUniverSheetInstance()!;
const sheet = workbook.getActiveSheet();
const unitId = workbook.getUnitId();
const subUnitId = sheet.getSheetId();
const numfmtModel = this._numfmtService.getModel(unitId, subUnitId);
if (copyInfo.copyType === COPY_TYPE.CUT) {
// This do not need to deal with clipping.
// move range had handle this case .
// to see numfmt.ref-range.controller.ts
this._copyInfo = null;
return { redos: [], undos: [] };
}
if (!copyInfo.copyRange && copyInfo.data) {
const removeRedos: IRemoveNumfmtMutationParams = { unitId, subUnitId, ranges: [] };
const cells: ISetCellsNumfmt = [];
Range.foreach(pastedRange, (row, col) => {
if (this._numfmtService.getValue(unitId, subUnitId, row, col, numfmtModel!)) {
removeRedos.ranges.push({ startRow: row, startColumn: col, endRow: row, endColumn: col });
}
});
copyInfo.data.forValue((row, col, value) => {
const content = String(value.v);

const dateInfo = numfmt.parseDate(content) || numfmt.parseTime(content) || numfmt.parseNumber(content);
const isTranslateDate = !!dateInfo;
if (isTranslateDate) {
if (dateInfo && dateInfo.z) {
cells.push({
row: pastedRange.startRow + row,
col: pastedRange.startColumn + col,
pattern: dateInfo.z || '',
type: 'date',
});
}
}
});
const setRedos = transformCellsToRange(unitId, subUnitId, cells);
Object.keys(setRedos.values).forEach((key) => {
const v = setRedos.values[key];
v.ranges = rangeMerge(v.ranges);
});

removeRedos.ranges = rangeMerge(removeRedos.ranges);
const undos = [
...factorySetNumfmtUndoMutation(this._injector, setRedos),
...factoryRemoveNumfmtUndoMutation(this._injector, removeRedos),
];

return {
redos: [
{ id: RemoveNumfmtMutation.id, params: removeRedos },
{ id: SetNumfmtMutation.id, params: setRedos },
],
undos: mergeNumfmtMutations(undos),
};
}

if (!this._copyInfo || !this._copyInfo.matrix.getSizeOf() || !copyInfo.copyRange) {
return { redos: [], undos: [] };
}
Expand All @@ -139,10 +189,11 @@ export class NumfmtCopyPasteController extends Disposable {
) {
return { redos: [], undos: [] };
}

const repeatRange = getRepeatRange(copyInfo.copyRange, pastedRange, true);
const cells: ISetCellsNumfmt = [];
const removeRedos: IRemoveNumfmtMutationParams = { unitId, subUnitId, ranges: [] };
const numfmtModel = this._numfmtService.getModel(unitId, subUnitId);

// Clears the destination area data format
Range.foreach(pastedRange, (row, col) => {
if (this._numfmtService.getValue(unitId, subUnitId, row, col, numfmtModel!)) {
Expand Down
Loading

0 comments on commit 496dcb8

Please sign in to comment.