Skip to content

Commit 92bcb4f

Browse files
Merge pull request #2954 from flyingbee2012/biwu/versionbumpup221
Version bump to 9.19.1
2 parents 5bac841 + afe82ff commit 92bcb4f

File tree

30 files changed

+423
-154
lines changed

30 files changed

+423
-154
lines changed

demo/scripts/controlsV2/demoButtons/pasteButton.ts

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,19 +45,14 @@ const createDataTransfer = (
4545

4646
const createDataTransferItems = (data: ClipboardItems) => {
4747
const isTEXT = (type: string) => type.startsWith('text/');
48-
const isIMAGE = (type: string) => type.startsWith('image/');
4948
const dataTransferItems: Promise<DataTransferItem>[] = [];
5049
data.forEach(item => {
5150
item.types.forEach(type => {
52-
if (isTEXT(type) || isIMAGE(type)) {
53-
dataTransferItems.push(
54-
item
55-
.getType(type)
56-
.then(blob =>
57-
createDataTransfer(isTEXT(type) ? 'string' : 'file', type, blob)
58-
)
59-
);
60-
}
51+
dataTransferItems.push(
52+
item
53+
.getType(type)
54+
.then(blob => createDataTransfer(isTEXT(type) ? 'string' : 'file', type, blob))
55+
);
6156
});
6257
});
6358
return dataTransferItems;

packages/roosterjs-content-model-api/lib/publicApi/segment/toggleUnderline.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { adjustTrailingSpaceSelection } from '../../modelApi/selection/adjustTrailingSpaceSelection';
21
import { formatSegmentWithContentModel } from '../utils/formatSegmentWithContentModel';
32
import type { IEditor } from 'roosterjs-content-model-types';
43

@@ -20,7 +19,6 @@ export function toggleUnderline(editor: IEditor) {
2019
}
2120
},
2221
(format, segment) => !!format.underline || !!segment?.link?.format?.underline,
23-
false /*includingFormatHolder*/,
24-
adjustTrailingSpaceSelection
22+
false /*includingFormatHolder*/
2523
);
2624
}

packages/roosterjs-content-model-api/test/publicApi/segment/toggleUnderlineTest.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -491,18 +491,12 @@ describe('toggleUnderline', () => {
491491
segments: [
492492
{
493493
segmentType: 'Text',
494-
text: 'Test',
494+
text: 'Test ',
495495
format: {
496496
underline: true,
497497
},
498498
isSelected: true,
499499
},
500-
{
501-
segmentType: 'Text',
502-
text: ' ',
503-
format: {},
504-
isSelected: true,
505-
},
506500
],
507501
format: {},
508502
},

packages/roosterjs-content-model-core/lib/corePlugin/selection/SelectionPlugin.ts

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -646,21 +646,23 @@ class SelectionPlugin implements PluginWithState<SelectionPluginState> {
646646

647647
//If am image selection changed to a wider range due a keyboard event, we should update the selection
648648
const selection = this.editor.getDocument().getSelection();
649-
650-
if (
651-
newSelection?.type == 'image' &&
652-
selection &&
653-
selection.focusNode &&
654-
!isSingleImageInSelection(selection)
655-
) {
656-
const range = selection.getRangeAt(0);
657-
this.editor.setDOMSelection({
658-
type: 'range',
659-
range,
660-
isReverted:
661-
selection.focusNode != range.endContainer ||
662-
selection.focusOffset != range.endOffset,
663-
});
649+
if (selection && selection.focusNode) {
650+
const image = isSingleImageInSelection(selection);
651+
if (newSelection?.type == 'image' && !image) {
652+
const range = selection.getRangeAt(0);
653+
this.editor.setDOMSelection({
654+
type: 'range',
655+
range,
656+
isReverted:
657+
selection.focusNode != range.endContainer ||
658+
selection.focusOffset != range.endOffset,
659+
});
660+
} else if (newSelection?.type !== 'image' && image) {
661+
this.editor.setDOMSelection({
662+
type: 'image',
663+
image,
664+
});
665+
}
664666
}
665667

666668
// Safari has problem to handle onBlur event. When blur, we cannot get the original selection from editor.

packages/roosterjs-content-model-core/test/command/paste/pasteTest.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,7 @@ describe('Paste with clipboardData', () => {
396396
expect(mergePasteContentSpy.calls.argsFor(0)[2]).toBeTrue();
397397
});
398398

399-
it('Second paste', () => {
399+
xit('Second paste', () => {
400400
clipboardData.rawHtml = '';
401401
clipboardData.modelBeforePaste = {
402402
blockGroupType: 'Document',

packages/roosterjs-content-model-core/test/corePlugin/selection/SelectionPluginTest.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2857,4 +2857,39 @@ describe('SelectionPlugin selectionChange on image selected', () => {
28572857
expect(setDOMSelectionSpy).not.toHaveBeenCalled();
28582858
expect(getDOMSelectionSpy).toHaveBeenCalledTimes(1);
28592859
});
2860+
2861+
it('onSelectionChange on image | 4', () => {
2862+
const image = document.createElement('img');
2863+
spyOn(isSingleImageInSelection, 'isSingleImageInSelection').and.returnValue(image);
2864+
2865+
const plugin = createSelectionPlugin({});
2866+
const state = plugin.getState();
2867+
const mockedOldSelection = {
2868+
type: 'image',
2869+
image: {} as any,
2870+
} as DOMSelection;
2871+
2872+
state.selection = mockedOldSelection;
2873+
2874+
plugin.initialize(editor);
2875+
2876+
const onSelectionChange = addEventListenerSpy.calls.argsFor(0)[1] as Function;
2877+
const mockedNewSelection = {
2878+
type: 'range',
2879+
range: {} as any,
2880+
} as any;
2881+
2882+
hasFocusSpy.and.returnValue(true);
2883+
isInShadowEditSpy.and.returnValue(false);
2884+
getDOMSelectionSpy.and.returnValue(mockedNewSelection);
2885+
getRangeAtSpy.and.returnValue({ startContainer: {} });
2886+
2887+
onSelectionChange();
2888+
2889+
expect(setDOMSelectionSpy).toHaveBeenCalledWith({
2890+
type: 'image',
2891+
image,
2892+
});
2893+
expect(getDOMSelectionSpy).toHaveBeenCalledTimes(1);
2894+
});
28602895
});

packages/roosterjs-content-model-dom/lib/domUtils/event/extractClipboardItems.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const ContentHandlers: {
88
['text/plain']: (data, value) => (data.text = value),
99
['text/*']: (data, value, type?) => !!type && (data.customValues[type] = value),
1010
['text/link-preview']: tryParseLinkPreview,
11+
['text/uri-list']: (data, value) => (data.text = value),
1112
};
1213

1314
/**

packages/roosterjs-content-model-dom/lib/modelToText/contentModelToText.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type {
2-
ContentModelBlockGroup,
3-
ContentModelDocument,
42
ModelToTextCallbacks,
3+
ReadonlyContentModelBlockGroup,
4+
ReadonlyContentModelDocument,
55
} from 'roosterjs-content-model-types';
66

77
const TextForHR = '________________________________________';
@@ -24,7 +24,7 @@ const defaultCallbacks: Required<ModelToTextCallbacks> = {
2424
* @param callbacks Callbacks to customize the behavior of contentModelToText function
2525
*/
2626
export function contentModelToText(
27-
model: ContentModelDocument,
27+
model: ReadonlyContentModelDocument,
2828
separator: string = '\r\n',
2929
callbacks?: ModelToTextCallbacks
3030
): string {
@@ -37,7 +37,7 @@ export function contentModelToText(
3737
}
3838

3939
function contentModelToTextArray(
40-
group: ContentModelBlockGroup,
40+
group: ReadonlyContentModelBlockGroup,
4141
textArray: string[],
4242
callbacks: Required<ModelToTextCallbacks>
4343
) {

packages/roosterjs-content-model-dom/test/domUtils/event/extractClipboardItemsTest.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,4 +292,20 @@ describe('extractClipboardItems', () => {
292292
pasteNativeEvent: true,
293293
});
294294
});
295+
296+
it('input with text/uri-list', async () => {
297+
const text = 'https://example.com';
298+
const clipboardData = await extractClipboardItems([
299+
createStringItem('text/uri-list', text),
300+
]);
301+
expect(clipboardData).toEqual({
302+
types: ['text/uri-list'],
303+
text: text,
304+
image: null,
305+
files: [],
306+
rawHtml: null,
307+
customValues: {},
308+
pasteNativeEvent: true,
309+
});
310+
});
295311
});

packages/roosterjs-content-model-plugins/lib/paste/Excel/processPastedContentFromExcel.ts

Lines changed: 62 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,29 @@ const LAST_TD_END_REGEX = /<\/\s*td\s*>((?!<\/\s*tr\s*>)[\s\S])*$/i;
1212
const LAST_TR_END_REGEX = /<\/\s*tr\s*>((?!<\/\s*table\s*>)[\s\S])*$/i;
1313
const LAST_TR_REGEX = /<tr[^>]*>[^<]*/i;
1414
const LAST_TABLE_REGEX = /<table[^>]*>[^<]*/i;
15-
const DEFAULT_BORDER_STYLE = 'solid 1px #d4d4d4';
1615
const TABLE_SELECTOR = 'table';
16+
const DEFAULT_BORDER_STYLE = 'solid 1px #d4d4d4';
1717

1818
/**
1919
* @internal
2020
* Convert pasted content from Excel, add borders when source doc doesn't have a border
2121
* @param event The BeforePaste event
22+
* @param domCreator The DOM creator
23+
* @param allowExcelNoBorderTable Allow table copied from Excel without border
24+
* @param isNativeEvent Whether the event is native event
2225
*/
23-
2426
export function processPastedContentFromExcel(
2527
event: BeforePasteEvent,
2628
domCreator: DOMCreator,
27-
allowExcelNoBorderTable?: boolean
29+
allowExcelNoBorderTable: boolean,
30+
isNativeEvent: boolean
2831
) {
2932
const { fragment, htmlBefore, htmlAfter, clipboardData } = event;
3033

31-
validateExcelFragment(fragment, domCreator, htmlBefore, clipboardData, htmlAfter);
34+
// For non native event we already validated that the content contains a table
35+
if (isNativeEvent) {
36+
validateExcelFragment(fragment, domCreator, htmlBefore, clipboardData, htmlAfter);
37+
}
3238

3339
// For Excel Online
3440
const firstChild = fragment.firstChild;
@@ -54,40 +60,13 @@ export function processPastedContentFromExcel(
5460
}
5561
}
5662

57-
addParser(event.domToModelOption, 'tableCell', (format, element) => {
58-
if (!allowExcelNoBorderTable && element.style.borderStyle === 'none') {
59-
format.borderBottom = DEFAULT_BORDER_STYLE;
60-
format.borderLeft = DEFAULT_BORDER_STYLE;
61-
format.borderRight = DEFAULT_BORDER_STYLE;
62-
format.borderTop = DEFAULT_BORDER_STYLE;
63-
}
64-
});
65-
66-
setProcessor(event.domToModelOption, 'child', childProcessor);
63+
setupExcelTableHandlers(
64+
event,
65+
allowExcelNoBorderTable,
66+
isNativeEvent /* handleForNativeEvent */
67+
);
6768
}
6869

69-
/**
70-
* @internal
71-
* Exported only for unit test
72-
*/
73-
export const childProcessor: ElementProcessor<ParentNode> = (group, element, context) => {
74-
const segmentFormat = { ...context.segmentFormat };
75-
if (
76-
group.blockGroupType === 'TableCell' &&
77-
group.format.textColor &&
78-
!context.segmentFormat.textColor
79-
) {
80-
context.segmentFormat.textColor = group.format.textColor;
81-
}
82-
83-
context.defaultElementProcessors.child(group, element, context);
84-
85-
if (group.blockGroupType === 'TableCell' && group.format.textColor) {
86-
context.segmentFormat = segmentFormat;
87-
delete group.format.textColor;
88-
}
89-
};
90-
9170
/**
9271
* @internal
9372
* Exported only for unit test
@@ -148,3 +127,50 @@ export function excelHandler(html: string, htmlBefore: string): string {
148127
return html;
149128
}
150129
}
130+
131+
/**
132+
* @internal
133+
* Exported only for unit test
134+
*/
135+
export function setupExcelTableHandlers(
136+
event: BeforePasteEvent,
137+
allowExcelNoBorderTable: boolean | undefined,
138+
isNativeEvent: boolean
139+
) {
140+
addParser(event.domToModelOption, 'tableCell', (format, element) => {
141+
if (
142+
!allowExcelNoBorderTable &&
143+
(element.style.borderStyle === 'none' ||
144+
(!isNativeEvent && element.style.borderStyle == ''))
145+
) {
146+
format.borderBottom = DEFAULT_BORDER_STYLE;
147+
format.borderLeft = DEFAULT_BORDER_STYLE;
148+
format.borderRight = DEFAULT_BORDER_STYLE;
149+
format.borderTop = DEFAULT_BORDER_STYLE;
150+
}
151+
});
152+
153+
setProcessor(event.domToModelOption, 'child', childProcessor);
154+
}
155+
156+
/**
157+
* @internal
158+
* Exported only for unit test
159+
*/
160+
export const childProcessor: ElementProcessor<ParentNode> = (group, element, context) => {
161+
const segmentFormat = { ...context.segmentFormat };
162+
if (
163+
group.blockGroupType === 'TableCell' &&
164+
group.format.textColor &&
165+
!context.segmentFormat.textColor
166+
) {
167+
context.segmentFormat.textColor = group.format.textColor;
168+
}
169+
170+
context.defaultElementProcessors.child(group, element, context);
171+
172+
if (group.blockGroupType === 'TableCell' && group.format.textColor) {
173+
context.segmentFormat = segmentFormat;
174+
delete group.format.textColor;
175+
}
176+
};

0 commit comments

Comments
 (0)