Skip to content

Commit

Permalink
Merge pull request #2954 from flyingbee2012/biwu/versionbumpup221
Browse files Browse the repository at this point in the history
Version bump to 9.19.1
  • Loading branch information
flyingbee2012 authored Feb 21, 2025
2 parents 5bac841 + afe82ff commit 92bcb4f
Show file tree
Hide file tree
Showing 30 changed files with 423 additions and 154 deletions.
15 changes: 5 additions & 10 deletions demo/scripts/controlsV2/demoButtons/pasteButton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,14 @@ const createDataTransfer = (

const createDataTransferItems = (data: ClipboardItems) => {
const isTEXT = (type: string) => type.startsWith('text/');
const isIMAGE = (type: string) => type.startsWith('image/');
const dataTransferItems: Promise<DataTransferItem>[] = [];
data.forEach(item => {
item.types.forEach(type => {
if (isTEXT(type) || isIMAGE(type)) {
dataTransferItems.push(
item
.getType(type)
.then(blob =>
createDataTransfer(isTEXT(type) ? 'string' : 'file', type, blob)
)
);
}
dataTransferItems.push(
item
.getType(type)
.then(blob => createDataTransfer(isTEXT(type) ? 'string' : 'file', type, blob))
);
});
});
return dataTransferItems;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { adjustTrailingSpaceSelection } from '../../modelApi/selection/adjustTrailingSpaceSelection';
import { formatSegmentWithContentModel } from '../utils/formatSegmentWithContentModel';
import type { IEditor } from 'roosterjs-content-model-types';

Expand All @@ -20,7 +19,6 @@ export function toggleUnderline(editor: IEditor) {
}
},
(format, segment) => !!format.underline || !!segment?.link?.format?.underline,
false /*includingFormatHolder*/,
adjustTrailingSpaceSelection
false /*includingFormatHolder*/
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -491,18 +491,12 @@ describe('toggleUnderline', () => {
segments: [
{
segmentType: 'Text',
text: 'Test',
text: 'Test ',
format: {
underline: true,
},
isSelected: true,
},
{
segmentType: 'Text',
text: ' ',
format: {},
isSelected: true,
},
],
format: {},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -646,21 +646,23 @@ class SelectionPlugin implements PluginWithState<SelectionPluginState> {

//If am image selection changed to a wider range due a keyboard event, we should update the selection
const selection = this.editor.getDocument().getSelection();

if (
newSelection?.type == 'image' &&
selection &&
selection.focusNode &&
!isSingleImageInSelection(selection)
) {
const range = selection.getRangeAt(0);
this.editor.setDOMSelection({
type: 'range',
range,
isReverted:
selection.focusNode != range.endContainer ||
selection.focusOffset != range.endOffset,
});
if (selection && selection.focusNode) {
const image = isSingleImageInSelection(selection);
if (newSelection?.type == 'image' && !image) {
const range = selection.getRangeAt(0);
this.editor.setDOMSelection({
type: 'range',
range,
isReverted:
selection.focusNode != range.endContainer ||
selection.focusOffset != range.endOffset,
});
} else if (newSelection?.type !== 'image' && image) {
this.editor.setDOMSelection({
type: 'image',
image,
});
}
}

// Safari has problem to handle onBlur event. When blur, we cannot get the original selection from editor.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ describe('Paste with clipboardData', () => {
expect(mergePasteContentSpy.calls.argsFor(0)[2]).toBeTrue();
});

it('Second paste', () => {
xit('Second paste', () => {
clipboardData.rawHtml = '';
clipboardData.modelBeforePaste = {
blockGroupType: 'Document',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2857,4 +2857,39 @@ describe('SelectionPlugin selectionChange on image selected', () => {
expect(setDOMSelectionSpy).not.toHaveBeenCalled();
expect(getDOMSelectionSpy).toHaveBeenCalledTimes(1);
});

it('onSelectionChange on image | 4', () => {
const image = document.createElement('img');
spyOn(isSingleImageInSelection, 'isSingleImageInSelection').and.returnValue(image);

const plugin = createSelectionPlugin({});
const state = plugin.getState();
const mockedOldSelection = {
type: 'image',
image: {} as any,
} as DOMSelection;

state.selection = mockedOldSelection;

plugin.initialize(editor);

const onSelectionChange = addEventListenerSpy.calls.argsFor(0)[1] as Function;
const mockedNewSelection = {
type: 'range',
range: {} as any,
} as any;

hasFocusSpy.and.returnValue(true);
isInShadowEditSpy.and.returnValue(false);
getDOMSelectionSpy.and.returnValue(mockedNewSelection);
getRangeAtSpy.and.returnValue({ startContainer: {} });

onSelectionChange();

expect(setDOMSelectionSpy).toHaveBeenCalledWith({
type: 'image',
image,
});
expect(getDOMSelectionSpy).toHaveBeenCalledTimes(1);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const ContentHandlers: {
['text/plain']: (data, value) => (data.text = value),
['text/*']: (data, value, type?) => !!type && (data.customValues[type] = value),
['text/link-preview']: tryParseLinkPreview,
['text/uri-list']: (data, value) => (data.text = value),
};

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type {
ContentModelBlockGroup,
ContentModelDocument,
ModelToTextCallbacks,
ReadonlyContentModelBlockGroup,
ReadonlyContentModelDocument,
} from 'roosterjs-content-model-types';

const TextForHR = '________________________________________';
Expand All @@ -24,7 +24,7 @@ const defaultCallbacks: Required<ModelToTextCallbacks> = {
* @param callbacks Callbacks to customize the behavior of contentModelToText function
*/
export function contentModelToText(
model: ContentModelDocument,
model: ReadonlyContentModelDocument,
separator: string = '\r\n',
callbacks?: ModelToTextCallbacks
): string {
Expand All @@ -37,7 +37,7 @@ export function contentModelToText(
}

function contentModelToTextArray(
group: ContentModelBlockGroup,
group: ReadonlyContentModelBlockGroup,
textArray: string[],
callbacks: Required<ModelToTextCallbacks>
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -292,4 +292,20 @@ describe('extractClipboardItems', () => {
pasteNativeEvent: true,
});
});

it('input with text/uri-list', async () => {
const text = 'https://example.com';
const clipboardData = await extractClipboardItems([
createStringItem('text/uri-list', text),
]);
expect(clipboardData).toEqual({
types: ['text/uri-list'],
text: text,
image: null,
files: [],
rawHtml: null,
customValues: {},
pasteNativeEvent: true,
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,29 @@ const LAST_TD_END_REGEX = /<\/\s*td\s*>((?!<\/\s*tr\s*>)[\s\S])*$/i;
const LAST_TR_END_REGEX = /<\/\s*tr\s*>((?!<\/\s*table\s*>)[\s\S])*$/i;
const LAST_TR_REGEX = /<tr[^>]*>[^<]*/i;
const LAST_TABLE_REGEX = /<table[^>]*>[^<]*/i;
const DEFAULT_BORDER_STYLE = 'solid 1px #d4d4d4';
const TABLE_SELECTOR = 'table';
const DEFAULT_BORDER_STYLE = 'solid 1px #d4d4d4';

/**
* @internal
* Convert pasted content from Excel, add borders when source doc doesn't have a border
* @param event The BeforePaste event
* @param domCreator The DOM creator
* @param allowExcelNoBorderTable Allow table copied from Excel without border
* @param isNativeEvent Whether the event is native event
*/

export function processPastedContentFromExcel(
event: BeforePasteEvent,
domCreator: DOMCreator,
allowExcelNoBorderTable?: boolean
allowExcelNoBorderTable: boolean,
isNativeEvent: boolean
) {
const { fragment, htmlBefore, htmlAfter, clipboardData } = event;

validateExcelFragment(fragment, domCreator, htmlBefore, clipboardData, htmlAfter);
// For non native event we already validated that the content contains a table
if (isNativeEvent) {
validateExcelFragment(fragment, domCreator, htmlBefore, clipboardData, htmlAfter);
}

// For Excel Online
const firstChild = fragment.firstChild;
Expand All @@ -54,40 +60,13 @@ export function processPastedContentFromExcel(
}
}

addParser(event.domToModelOption, 'tableCell', (format, element) => {
if (!allowExcelNoBorderTable && element.style.borderStyle === 'none') {
format.borderBottom = DEFAULT_BORDER_STYLE;
format.borderLeft = DEFAULT_BORDER_STYLE;
format.borderRight = DEFAULT_BORDER_STYLE;
format.borderTop = DEFAULT_BORDER_STYLE;
}
});

setProcessor(event.domToModelOption, 'child', childProcessor);
setupExcelTableHandlers(
event,
allowExcelNoBorderTable,
isNativeEvent /* handleForNativeEvent */
);
}

/**
* @internal
* Exported only for unit test
*/
export const childProcessor: ElementProcessor<ParentNode> = (group, element, context) => {
const segmentFormat = { ...context.segmentFormat };
if (
group.blockGroupType === 'TableCell' &&
group.format.textColor &&
!context.segmentFormat.textColor
) {
context.segmentFormat.textColor = group.format.textColor;
}

context.defaultElementProcessors.child(group, element, context);

if (group.blockGroupType === 'TableCell' && group.format.textColor) {
context.segmentFormat = segmentFormat;
delete group.format.textColor;
}
};

/**
* @internal
* Exported only for unit test
Expand Down Expand Up @@ -148,3 +127,50 @@ export function excelHandler(html: string, htmlBefore: string): string {
return html;
}
}

/**
* @internal
* Exported only for unit test
*/
export function setupExcelTableHandlers(
event: BeforePasteEvent,
allowExcelNoBorderTable: boolean | undefined,
isNativeEvent: boolean
) {
addParser(event.domToModelOption, 'tableCell', (format, element) => {
if (
!allowExcelNoBorderTable &&
(element.style.borderStyle === 'none' ||
(!isNativeEvent && element.style.borderStyle == ''))
) {
format.borderBottom = DEFAULT_BORDER_STYLE;
format.borderLeft = DEFAULT_BORDER_STYLE;
format.borderRight = DEFAULT_BORDER_STYLE;
format.borderTop = DEFAULT_BORDER_STYLE;
}
});

setProcessor(event.domToModelOption, 'child', childProcessor);
}

/**
* @internal
* Exported only for unit test
*/
export const childProcessor: ElementProcessor<ParentNode> = (group, element, context) => {
const segmentFormat = { ...context.segmentFormat };
if (
group.blockGroupType === 'TableCell' &&
group.format.textColor &&
!context.segmentFormat.textColor
) {
context.segmentFormat.textColor = group.format.textColor;
}

context.defaultElementProcessors.child(group, element, context);

if (group.blockGroupType === 'TableCell' && group.format.textColor) {
context.segmentFormat = segmentFormat;
delete group.format.textColor;
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,14 @@ export class PastePlugin implements EditorPlugin {
break;
case 'excelOnline':
case 'excelDesktop':
case 'excelNonNativeEvent':
if (pasteType === 'normal' || pasteType === 'mergeFormat') {
// Handle HTML copied from Excel
processPastedContentFromExcel(
event,
this.editor.getDOMCreator(),
this.allowExcelNoBorderTable
!!this.allowExcelNoBorderTable,
pasteSource != 'excelNonNativeEvent' /* isNativeEvent */
);
}
break;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { documentContainWacElements } from './documentContainWacElements';
import { isExcelDesktopDocument } from './isExcelDesktopDocument';
import { isExcelNotNativeEvent } from './isExcelNonNativeEvent';
import { isExcelOnlineDocument } from './isExcelOnlineDocument';
import { isGoogleSheetDocument } from './isGoogleSheetDocument';
import { isPowerPointDesktopDocument } from './isPowerPointDesktopDocument';
Expand Down Expand Up @@ -29,7 +30,8 @@ export type KnownPasteSourceType =
| 'googleSheets'
| 'wacComponents'
| 'default'
| 'singleImage';
| 'singleImage'
| 'excelNonNativeEvent';

/**
* @internal
Expand All @@ -44,6 +46,7 @@ const getSourceFunctions = new Map<KnownPasteSourceType, GetSourceFunction>([
['wacComponents', documentContainWacElements],
['googleSheets', isGoogleSheetDocument],
['singleImage', shouldConvertToSingleImage],
['excelNonNativeEvent', isExcelNotNativeEvent],
]);

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { GetSourceFunction, GetSourceInputParams } from './getPasteSource';

const ShadowWorkbookClipboardType = 'web data/shadow-workbook';

/**
* @internal
* When the clipboard content is retrieved programatically, the clipboard html does not contain the usual
* attributes we use to determine if the content is from Excel. This function is used to handle that case.
*/
export const isExcelNotNativeEvent: GetSourceFunction = (props: GetSourceInputParams) => {
const { clipboardData } = props;

return (
clipboardData.types.includes(ShadowWorkbookClipboardType) &&
clipboardData.htmlFirstLevelChildTags?.length == 1 &&
clipboardData.htmlFirstLevelChildTags[0] == 'TABLE'
);
};
Loading

0 comments on commit 92bcb4f

Please sign in to comment.