Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Version bump to 9.20.0 #2954

Merged
merged 33 commits into from
Feb 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
ad542f4
remove margins on Autoformat plugin
juliaroldi Jan 16, 2025
9037191
fixes
juliaroldi Jan 21, 2025
02b7309
Merge branch 'master' into u/juliaroldi/list-sequence
juliaroldi Jan 22, 2025
5d5981a
fix
juliaroldi Jan 22, 2025
0c53904
Merge branch 'u/juliaroldi/list-sequence' of https://github.com/micro…
juliaroldi Jan 22, 2025
4578669
remove data-istrue
juliaroldi Jan 28, 2025
11c9e3e
Merge pull request #2928 from microsoft/u/juliaroldi/list-sequence
juliaroldi Jan 28, 2025
6a3be95
Merge branch 'master' into u/juliaroldi/auto-format-margins
JiuqingSong Jan 28, 2025
f960478
Merge pull request #2923 from microsoft/u/juliaroldi/auto-format-margins
juliaroldi Jan 28, 2025
685ff40
use content change
juliaroldi Jan 28, 2025
2b011f2
Merge branch 'master' into u/juliaroldi/undo-images
juliaroldi Jan 28, 2025
27c11ff
refactor
juliaroldi Jan 28, 2025
f969062
Merge branch 'u/juliaroldi/undo-images' of https://github.com/microso…
juliaroldi Jan 28, 2025
555c239
Merge pull request #2931 from microsoft/u/juliaroldi/undo-images
juliaroldi Jan 28, 2025
5f67c49
Allow skipping marking hasNewContent in `formatContentModel` (#2933)
JiuqingSong Jan 31, 2025
f46f90e
Fix bridge plugin to be able to handle new event (#2935)
JiuqingSong Feb 5, 2025
4b838c4
clean LegacySelectionPlugin
juliaroldi Feb 6, 2025
db813ad
Merge pull request #2938 from microsoft/u/juliaroldi/clean-code
juliaroldi Feb 6, 2025
aab0fda
Send anchor event in Auto Link (#2934)
juliaroldi Feb 6, 2025
9dbe329
Respect font weight in TH element (#2939)
JiuqingSong Feb 7, 2025
6388b93
Fix resize table with width (#2940)
JiuqingSong Feb 7, 2025
01531e2
Support text/uri-list when pasting (#2943)
haven2world Feb 11, 2025
bbe7d52
Fix #317607 (#2945)
JiuqingSong Feb 12, 2025
ce1afbe
Fix #2927 (#2944)
JiuqingSong Feb 12, 2025
e120cce
Skip unstable test (#2946)
JiuqingSong Feb 18, 2025
7ee7805
Let contentModelToText accepts readonly types (#2947)
JiuqingSong Feb 18, 2025
9b93cf4
select image with keyboard (#2951)
juliaroldi Feb 18, 2025
3ddc5b8
Bump serialize-javascript from 6.0.1 to 6.0.2 (#2952)
dependabot[bot] Feb 20, 2025
7beec85
Implement Excel non-native paste event handling and related utilities…
BryanValverdeU Feb 20, 2025
ebbf2a4
Merge branch 'master' into biwu/versionbumpup221
Feb 21, 2025
1d8ace4
version bumpup
Feb 21, 2025
2680252
version update
Feb 21, 2025
afe82ff
address comment
Feb 21, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading