Skip to content

Commit 61fcc77

Browse files
authored
[Markdown Plugin] Support auto-conversion on native paste and add undoConversion option (#3381)
1 parent b4bfffa commit 61fcc77

16 files changed

Lines changed: 389 additions & 60 deletions

File tree

demo/scripts/controlsV2/sidePane/editorOptions/EditorOptionsPlugin.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ const initialState: OptionState = {
6262
},
6363
markdownPasteOptions: {
6464
autoConversion: false,
65+
undoConversion: false,
6566
},
6667
editPluginOptions: {
6768
handleTabKey: {

demo/scripts/controlsV2/sidePane/editorOptions/Plugins.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ export class Plugins extends PluginsBase<keyof BuildInPluginList> {
119119
private markdownStrikethrough = React.createRef<HTMLInputElement>();
120120
private markdownCode = React.createRef<HTMLInputElement>();
121121
private markdownPasteAutoConversion = React.createRef<HTMLInputElement>();
122+
private markdownPasteUndoConversion = React.createRef<HTMLInputElement>();
122123
private linkTitle = React.createRef<HTMLInputElement>();
123124
private disableSideResize = React.createRef<HTMLInputElement>();
124125

@@ -337,6 +338,13 @@ export class Plugins extends PluginsBase<keyof BuildInPluginList> {
337338
(state, value) =>
338339
(state.markdownPasteOptions.autoConversion = value)
339340
)}
341+
{this.renderCheckBox(
342+
'Undo auto-converted markdown',
343+
this.markdownPasteUndoConversion,
344+
this.props.state.markdownPasteOptions.undoConversion,
345+
(state, value) =>
346+
(state.markdownPasteOptions.undoConversion = value)
347+
)}
340348
</>
341349
)}
342350
{this.renderPluginItem(

demo/scripts/controlsV2/sidePane/editorOptions/codes/MarkdownPasteCode.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export class MarkdownPasteCode extends CodeElement {
99
getCode() {
1010
return `new roosterjs.MarkdownPastePlugin({
1111
autoConversion: ${this.markdownPasteOptions.autoConversion},
12+
undoConversion: ${this.markdownPasteOptions.undoConversion},
1213
})`;
1314
}
1415
}

demo/scripts/utils/readClipboardData.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ export async function readClipboardData(doc: Document): Promise<ClipboardData |
2121
try {
2222
const clipboardItems = await clipboard.read();
2323
const dataTransferItems = await Promise.all(createDataTransferItems(clipboardItems));
24-
return await extractClipboardItems(dataTransferItems);
24+
return await extractClipboardItems(
25+
dataTransferItems,
26+
undefined /* allowedCustomPasteType */
27+
);
2528
} catch {
2629
return null;
2730
}

packages/roosterjs-content-model-core/lib/command/paste/mergePasteContent.ts

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,37 +2,24 @@ import { createDomToModelContextForSanitizing } from '../createModelFromHtml/cre
22
import {
33
ChangeSource,
44
EmptySegmentFormat,
5-
cloneModel,
65
domToContentModel,
76
getSegmentTextFormat,
87
getSelectedSegments,
98
mergeModel,
9+
cloneModelForPaste,
1010
} from 'roosterjs-content-model-dom';
1111
import type {
1212
BeforePasteEvent,
13-
CloneModelOptions,
1413
ContentModelDocument,
1514
ContentModelSegmentFormat,
1615
IEditor,
1716
MergeModelOption,
1817
PasteType,
19-
ReadonlyContentModelDocument,
2018
ShallowMutableContentModelDocument,
2119
} from 'roosterjs-content-model-types';
2220

2321
const BlackColor = 'rgb(0,0,0)';
2422

25-
const CloneOption: CloneModelOptions = {
26-
includeCachedElement: (node, type) => (type == 'cache' ? undefined : node),
27-
};
28-
29-
/**
30-
* @internal
31-
*/
32-
export function cloneModelForPaste(model: ReadonlyContentModelDocument) {
33-
return cloneModel(model, CloneOption);
34-
}
35-
3623
/**
3724
* @internal
3825
*/

packages/roosterjs-content-model-core/lib/command/paste/paste.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { cleanHtmlComments } from './cleanHtmlComments';
2-
import { cloneModelForPaste, mergePasteContent } from './mergePasteContent';
2+
import { cloneModelForPaste } from 'roosterjs-content-model-dom';
33
import { convertInlineCss } from '../createModelFromHtml/convertInlineCss';
44
import { createPasteFragment } from './createPasteFragment';
55
import { generatePasteOptionFromPlugins } from './generatePasteOptionFromPlugins';
6+
import { mergePasteContent } from './mergePasteContent';
67
import { retrieveHtmlInfo } from './retrieveHtmlInfo';
78
import type {
89
PasteTypeOrGetter,

packages/roosterjs-content-model-core/lib/corePlugin/copyPaste/CopyPastePlugin.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,8 @@ class CopyPastePlugin implements PluginWithState<CopyPastePluginState> {
129129
event.preventDefault();
130130
extractClipboardItems(
131131
toArray(dataTransfer!.items),
132-
this.state.allowedCustomPasteType
132+
this.state.allowedCustomPasteType,
133+
true /* isPasteNative */
133134
).then((clipboardData: ClipboardData) => {
134135
if (!editor.isDisposed()) {
135136
paste(editor, clipboardData, this.state.defaultPasteType);

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

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1890,7 +1890,7 @@ describe('mergePasteContent', () => {
18901890
document.body
18911891
);
18921892
const div = document.createElement('div');
1893-
const cloneModelSpy = spyOn(cloneModel, 'cloneModel').and.callThrough();
1893+
const cloneModelSpy = spyOn(cloneModel, 'cloneModelForPaste').and.callThrough();
18941894

18951895
sourceModel = {
18961896
blockGroupType: 'Document',
@@ -1973,7 +1973,7 @@ describe('mergePasteContent', () => {
19731973
document.body
19741974
);
19751975
const div = document.createElement('div');
1976-
const cloneModelSpy = spyOn(cloneModel, 'cloneModel').and.callThrough();
1976+
const cloneModelSpy = spyOn(cloneModel, 'cloneModelForPaste').and.callThrough();
19771977

19781978
sourceModel = {
19791979
blockGroupType: 'Document',
@@ -2042,8 +2042,5 @@ describe('mergePasteContent', () => {
20422042
format: {},
20432043
});
20442044
expect(cloneModelSpy).toHaveBeenCalledTimes(1);
2045-
expect(cloneModelSpy).toHaveBeenCalledWith(modelBeforePaste, {
2046-
includeCachedElement: jasmine.anything(),
2047-
} as any);
20482045
});
20492046
});

packages/roosterjs-content-model-core/test/corePlugin/copyPaste/CopyPastePluginTest.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -995,7 +995,8 @@ describe('CopyPastePlugin |', () => {
995995
expect(pasteSpy).toHaveBeenCalledWith(editor, clipboardData, undefined);
996996
expect(extractClipboardItemsFile.extractClipboardItems).toHaveBeenCalledWith(
997997
Array.from(clipboardEvent.clipboardData!.items),
998-
allowedCustomPasteType
998+
allowedCustomPasteType,
999+
true
9991000
);
10001001
expect(preventDefaultSpy).toHaveBeenCalledTimes(1);
10011002
});
@@ -1025,7 +1026,8 @@ describe('CopyPastePlugin |', () => {
10251026
expect(pasteSpy).toHaveBeenCalledWith(editor, clipboardData, undefined);
10261027
expect(extractClipboardItemsFile.extractClipboardItems).toHaveBeenCalledWith(
10271028
Array.from(clipboardEvent.clipboardData!.items),
1028-
allowedCustomPasteType
1029+
allowedCustomPasteType,
1030+
true
10291031
);
10301032
expect(preventDefaultSpy).toHaveBeenCalledTimes(1);
10311033
});
@@ -1056,7 +1058,8 @@ describe('CopyPastePlugin |', () => {
10561058
expect(pasteSpy).toHaveBeenCalledWith(editor, clipboardData, 'mergeFormat');
10571059
expect(extractClipboardItemsFile.extractClipboardItems).toHaveBeenCalledWith(
10581060
Array.from(clipboardEvent.clipboardData!.items),
1059-
allowedCustomPasteType
1061+
allowedCustomPasteType,
1062+
true
10601063
);
10611064
expect(preventDefaultSpy).toHaveBeenCalledTimes(1);
10621065
});
@@ -1087,7 +1090,8 @@ describe('CopyPastePlugin |', () => {
10871090
expect(pasteSpy).toHaveBeenCalledWith(editor, clipboardData, 'asImage');
10881091
expect(extractClipboardItemsFile.extractClipboardItems).toHaveBeenCalledWith(
10891092
Array.from(clipboardEvent.clipboardData!.items),
1090-
allowedCustomPasteType
1093+
allowedCustomPasteType,
1094+
true
10911095
);
10921096
expect(preventDefaultSpy).toHaveBeenCalledTimes(1);
10931097
});
@@ -1118,7 +1122,8 @@ describe('CopyPastePlugin |', () => {
11181122
expect(pasteSpy).toHaveBeenCalledWith(editor, clipboardData, 'asPlainText');
11191123
expect(extractClipboardItemsFile.extractClipboardItems).toHaveBeenCalledWith(
11201124
Array.from(clipboardEvent.clipboardData!.items),
1121-
allowedCustomPasteType
1125+
allowedCustomPasteType,
1126+
true
11221127
);
11231128
expect(preventDefaultSpy).toHaveBeenCalledTimes(1);
11241129
});
@@ -1149,7 +1154,8 @@ describe('CopyPastePlugin |', () => {
11491154
expect(pasteSpy).toHaveBeenCalledWith(editor, clipboardData, 'normal');
11501155
expect(extractClipboardItemsFile.extractClipboardItems).toHaveBeenCalledWith(
11511156
Array.from(clipboardEvent.clipboardData!.items),
1152-
allowedCustomPasteType
1157+
allowedCustomPasteType,
1158+
true
11531159
);
11541160
expect(preventDefaultSpy).toHaveBeenCalledTimes(1);
11551161
});
@@ -1179,7 +1185,8 @@ describe('CopyPastePlugin |', () => {
11791185
expect(pasteSpy).not.toHaveBeenCalled();
11801186
expect(extractClipboardItemsFile.extractClipboardItems).toHaveBeenCalledWith(
11811187
Array.from(clipboardEvent.clipboardData!.items),
1182-
allowedCustomPasteType
1188+
allowedCustomPasteType,
1189+
true
11831190
);
11841191
expect(preventDefaultSpy).toHaveBeenCalledTimes(1);
11851192
});
@@ -1210,7 +1217,8 @@ describe('CopyPastePlugin |', () => {
12101217
expect(pasteSpy).toHaveBeenCalledWith(editor, clipboardData, cb);
12111218
expect(extractClipboardItemsFile.extractClipboardItems).toHaveBeenCalledWith(
12121219
Array.from(clipboardEvent.clipboardData!.items),
1213-
allowedCustomPasteType
1220+
allowedCustomPasteType,
1221+
true
12141222
);
12151223
expect(preventDefaultSpy).toHaveBeenCalledTimes(1);
12161224
});

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ const ContentHandlers: {
1919
*/
2020
export function extractClipboardItems(
2121
items: DataTransferItem[],
22-
allowedCustomPasteType?: string[]
22+
allowedCustomPasteType?: string[],
23+
isPasteNative?: boolean
2324
): Promise<ClipboardData> {
2425
const data: ClipboardData = {
2526
types: [],
@@ -28,7 +29,7 @@ export function extractClipboardItems(
2829
files: [],
2930
rawHtml: null,
3031
customValues: {},
31-
pasteNativeEvent: true,
32+
pasteNativeEvent: !!isPasteNative,
3233
};
3334

3435
return Promise.all(

0 commit comments

Comments
 (0)