-
Notifications
You must be signed in to change notification settings - Fork 148
/
PastePlugin.ts
163 lines (149 loc) · 6.02 KB
/
PastePlugin.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
import { addParser } from './utils/addParser';
import { BorderKeys } from 'roosterjs-content-model-dom';
import { deprecatedBorderColorParser } from './utils/deprecatedColorParser';
import { getPasteSource } from './pasteSourceValidations/getPasteSource';
import { parseLink } from './utils/linkParser';
import { PastePropertyNames } from './pasteSourceValidations/constants';
import { processPastedContentFromExcel } from './Excel/processPastedContentFromExcel';
import { processPastedContentFromPowerPoint } from './PowerPoint/processPastedContentFromPowerPoint';
import { processPastedContentFromWordDesktop } from './WordDesktop/processPastedContentFromWordDesktop';
import { processPastedContentWacComponents } from './WacComponents/processPastedContentWacComponents';
import type {
BorderFormat,
ContentModelBlockFormat,
ContentModelTableCellFormat,
EditorPlugin,
FormatParser,
IEditor,
PluginEvent,
} from 'roosterjs-content-model-types';
/**
* Paste plugin, handles BeforePaste event and reformat some special content, including:
* 1. Content copied from Word
* 2. Content copied from Excel
* 3. Content copied from Word Online or OneNote Online
* 4. Content copied from Power Point
*/
export class PastePlugin implements EditorPlugin {
private editor: IEditor | null = null;
/**
* Construct a new instance of Paste class
* @param unknownTagReplacement Replace solution of unknown tags, default behavior is to replace with SPAN
* @param allowExcelNoBorderTable Allow table copied from Excel without border
*/
constructor(private allowExcelNoBorderTable?: boolean) {}
/**
* Get name of this plugin
*/
getName() {
return 'Paste';
}
/**
* The first method that editor will call to a plugin when editor is initializing.
* It will pass in the editor instance, plugin should take this chance to save the
* editor reference so that it can call to any editor method or format API later.
* @param editor The editor object
*/
initialize(editor: IEditor) {
this.editor = editor;
}
/**
* The last method that editor will call to a plugin before it is disposed.
* Plugin can take this chance to clear the reference to editor. After this method is
* called, plugin should not call to any editor method since it will result in error.
*/
dispose() {
this.editor = null;
}
/**
* Core method for a plugin. Once an event happens in editor, editor will call this
* method of each plugin to handle the event as long as the event is not handled
* exclusively by another plugin.
* @param event The event to handle:
*/
onPluginEvent(event: PluginEvent) {
if (!this.editor || event.eventType != 'beforePaste') {
return;
}
if (!event.domToModelOption) {
return;
}
const pasteSource = getPasteSource(event, false);
const pasteType = event.pasteType;
switch (pasteSource) {
case 'wordDesktop':
processPastedContentFromWordDesktop(event, this.editor.getTrustedHTMLHandler());
break;
case 'wacComponents':
processPastedContentWacComponents(event);
break;
case 'excelOnline':
case 'excelDesktop':
if (pasteType === 'normal' || pasteType === 'mergeFormat') {
// Handle HTML copied from Excel
processPastedContentFromExcel(
event,
this.editor.getTrustedHTMLHandler(),
this.allowExcelNoBorderTable
);
}
break;
case 'googleSheets':
event.domToModelOption.additionalAllowedTags.push(
PastePropertyNames.GOOGLE_SHEET_NODE_NAME as Lowercase<string>
);
break;
case 'powerPointDesktop':
processPastedContentFromPowerPoint(event, this.editor.getTrustedHTMLHandler());
break;
}
addParser(event.domToModelOption, 'link', parseLink);
addParser(event.domToModelOption, 'tableCell', deprecatedBorderColorParser);
addParser(event.domToModelOption, 'tableCell', tableBorderParser);
addParser(event.domToModelOption, 'table', deprecatedBorderColorParser);
if (pasteType === 'mergeFormat') {
addParser(event.domToModelOption, 'block', blockElementParser);
addParser(event.domToModelOption, 'listLevel', blockElementParser);
}
}
}
/**
* For block elements that have background color style, remove the background color when user selects the merge current format
* paste option
*/
const blockElementParser: FormatParser<ContentModelBlockFormat> = (
format: ContentModelBlockFormat,
element: HTMLElement
) => {
if (element.style.backgroundColor) {
delete format.backgroundColor;
}
};
const ElementBorderKeys = new Map<
keyof BorderFormat,
{
c: keyof CSSStyleDeclaration;
s: keyof CSSStyleDeclaration;
w: keyof CSSStyleDeclaration;
}
>([
['borderTop', { w: 'borderTopWidth', s: 'borderTopStyle', c: 'borderTopColor' }],
['borderRight', { w: 'borderRightWidth', s: 'borderRightStyle', c: 'borderRightColor' }],
['borderBottom', { w: 'borderBottomWidth', s: 'borderBottomStyle', c: 'borderBottomColor' }],
['borderLeft', { w: 'borderLeftWidth', s: 'borderLeftStyle', c: 'borderLeftColor' }],
]);
function tableBorderParser(format: ContentModelTableCellFormat, element: HTMLElement): void {
BorderKeys.forEach(key => {
if (!format[key]) {
const styleSet = ElementBorderKeys.get(key);
if (
styleSet &&
element.style[styleSet.w] &&
element.style[styleSet.s] &&
!element.style[styleSet.c]
) {
format[key] = `${element.style[styleSet.w]} ${element.style[styleSet.s]}`;
}
}
});
}