@ckeditor/ckeditor5-paste-from-office 48.1.1 → 48.2.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/filters/list.d.ts +3 -1
- package/dist/filters/space.d.ts +3 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +208 -86
- package/dist/index.js.map +1 -1
- package/dist/normalizer.d.ts +1 -6
- package/dist/normalizers/googledocsnormalizer.d.ts +3 -2
- package/dist/normalizers/googlesheetsnormalizer.d.ts +3 -2
- package/dist/normalizers/mswordnormalizer.d.ts +8 -3
- package/dist/pastefromoffice.d.ts +10 -0
- package/package.json +5 -4
package/dist/filters/list.d.ts
CHANGED
|
@@ -18,9 +18,11 @@ import { ViewUpcastWriter, type ViewDocumentFragment } from '@ckeditor/ckeditor5
|
|
|
18
18
|
*
|
|
19
19
|
* @param documentFragment The view structure to be transformed.
|
|
20
20
|
* @param stylesString Styles from which list-like elements styling will be extracted.
|
|
21
|
+
* @param hasMultiLevelListPlugin Whether the editor has the multi-level list plugin enabled.
|
|
22
|
+
* @param enableSkipLevelLists Whether to enable skip-level lists.
|
|
21
23
|
* @internal
|
|
22
24
|
*/
|
|
23
|
-
export declare function transformListItemLikeElementsIntoLists(documentFragment: ViewDocumentFragment, stylesString: string, hasMultiLevelListPlugin: boolean): void;
|
|
25
|
+
export declare function transformListItemLikeElementsIntoLists(documentFragment: ViewDocumentFragment, stylesString: string, hasMultiLevelListPlugin: boolean, enableSkipLevelLists?: boolean): void;
|
|
24
26
|
/**
|
|
25
27
|
* Removes paragraph wrapping content inside a list item.
|
|
26
28
|
*
|
package/dist/filters/space.d.ts
CHANGED
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
* Replaces last space preceding elements closing tag with ` `. Such operation prevents spaces from being removed
|
|
10
10
|
* during further DOM/View processing (see especially {@link module:engine/view/domconverter~ViewDomConverter#_processDomInlineNodes}).
|
|
11
11
|
* This method also takes into account Word specific `<o:p></o:p>` empty tags.
|
|
12
|
-
* Additionally multiline sequences of spaces and new lines between tags are removed (see
|
|
12
|
+
* Additionally multiline sequences of spaces and new lines between tags are removed (see
|
|
13
|
+
* https://github.com/ckeditor/ckeditor5-paste-from-office/issues/39
|
|
14
|
+
* and https://github.com/ckeditor/ckeditor5-paste-from-office/issues/40).
|
|
13
15
|
*
|
|
14
16
|
* @param htmlString HTML string in which spacing should be normalized.
|
|
15
17
|
* @returns Input HTML with spaces normalized.
|
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* @module paste-from-office
|
|
7
7
|
*/
|
|
8
8
|
export { PasteFromOffice } from './pastefromoffice.js';
|
|
9
|
-
export type { PasteFromOfficeNormalizer
|
|
9
|
+
export type { PasteFromOfficeNormalizer } from './normalizer.js';
|
|
10
10
|
export { PasteFromOfficeMSWordNormalizer } from './normalizers/mswordnormalizer.js';
|
|
11
11
|
export { parsePasteOfficeHtml, type PasteOfficeHtmlParseResult } from './filters/parse.js';
|
|
12
12
|
export { transformBookmarks as _transformPasteOfficeBookmarks } from './filters/bookmark.js';
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
4
4
|
*/
|
|
5
5
|
import { Plugin } from '@ckeditor/ckeditor5-core/dist/index.js';
|
|
6
|
+
import { priorities, insertToPriorityArray } from '@ckeditor/ckeditor5-utils/dist/index.js';
|
|
6
7
|
import { ClipboardPipeline } from '@ckeditor/ckeditor5-clipboard/dist/index.js';
|
|
7
8
|
import { ViewUpcastWriter, Matcher, ViewDocument, ViewDomConverter } from '@ckeditor/ckeditor5-engine/dist/index.js';
|
|
8
9
|
|
|
@@ -103,8 +104,10 @@ import { ViewUpcastWriter, Matcher, ViewDocument, ViewDomConverter } from '@cked
|
|
|
103
104
|
*
|
|
104
105
|
* @param documentFragment The view structure to be transformed.
|
|
105
106
|
* @param stylesString Styles from which list-like elements styling will be extracted.
|
|
107
|
+
* @param hasMultiLevelListPlugin Whether the editor has the multi-level list plugin enabled.
|
|
108
|
+
* @param enableSkipLevelLists Whether to enable skip-level lists.
|
|
106
109
|
* @internal
|
|
107
|
-
*/ function transformListItemLikeElementsIntoLists(documentFragment, stylesString, hasMultiLevelListPlugin) {
|
|
110
|
+
*/ function transformListItemLikeElementsIntoLists(documentFragment, stylesString, hasMultiLevelListPlugin, enableSkipLevelLists = false) {
|
|
108
111
|
if (!documentFragment.childCount) {
|
|
109
112
|
return;
|
|
110
113
|
}
|
|
@@ -139,10 +142,25 @@ import { ViewUpcastWriter, Matcher, ViewDocument, ViewDomConverter } from '@cked
|
|
|
139
142
|
// Combines the list id and level so that two different lists at the same indent
|
|
140
143
|
// level (e.g. first an <ol>, then a <ul> after a paragraph break) don't share a counter.
|
|
141
144
|
const originalListId = `${itemLikeElement.id}:${itemLikeElement.indent}`;
|
|
142
|
-
//
|
|
143
|
-
|
|
145
|
+
// When the editor opts into skip-level lists, preserve Word indent gaps (the fill loop below
|
|
146
|
+
// inserts `<li style="list-style-type:none">` wrappers for them). Otherwise clamp to one
|
|
147
|
+
// level below the current stack top — the original pre-skip-level behavior — so the editor's
|
|
148
|
+
// list post-fixer doesn't have to bridge the gap with empty filler paragraphs.
|
|
149
|
+
const indent = enableSkipLevelLists ? itemLikeElement.indent - 1 : Math.min(itemLikeElement.indent - 1, stack.length);
|
|
144
150
|
// Trimming of the list stack on list ID change.
|
|
145
151
|
if (indent < stack.length && stack[indent].id !== itemLikeElement.id) {
|
|
152
|
+
// A different list at the top level starts here. `isListContinuation` returned true
|
|
153
|
+
// (the previous sibling in the DOM is the prior list's `<ol>/<ul>`), so the outer reset
|
|
154
|
+
// path didn't run. Flush the prior list's accumulated margin onto its own `<ol>/<ul>`
|
|
155
|
+
// BEFORE the stack reference is replaced — otherwise a later flush would hoist that
|
|
156
|
+
// margin onto the wrong (interrupting) list and strip it from the original list's
|
|
157
|
+
// `<li>`s. The flush is a no-op unless the prior list actually had uniform-margin
|
|
158
|
+
// items pushed, so single-item / mixed-margin lists keep their pre-existing
|
|
159
|
+
// per-`<li>` margin semantics.
|
|
160
|
+
if (indent == 0 && topLevelListInfo.canApplyMarginOnList && topLevelListInfo.topLevelListItemElements.length > 0) {
|
|
161
|
+
applyIndentationToTopLevelList(writer, stack, topLevelListInfo);
|
|
162
|
+
topLevelListInfo = createTopLevelListInfo();
|
|
163
|
+
}
|
|
146
164
|
// A different list started at this indent level — counters for this level and deeper
|
|
147
165
|
// belong to the previous list context and must not carry over.
|
|
148
166
|
encounteredLists.length = indent;
|
|
@@ -153,40 +171,99 @@ import { ViewUpcastWriter, Matcher, ViewDocument, ViewDomConverter } from '@cked
|
|
|
153
171
|
// We jumped back to a shallower indent — any counters deeper than the new top are stale.
|
|
154
172
|
encounteredLists.length = indent + 1;
|
|
155
173
|
stack.length = indent + 1;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
+
}
|
|
175
|
+
const listStyle = detectListStyle(itemLikeElement, stylesString);
|
|
176
|
+
// Word can jump indent levels (e.g. from level 1 directly to level 3) without producing
|
|
177
|
+
// items for the in-between levels. Fill the missing levels with intermediate wrappers —
|
|
178
|
+
// an `<ol>`/`<ul>` of the deepest item's type containing a single `<li style="list-style-type:none">` —
|
|
179
|
+
// so the resulting view matches the shape the skip-level upcast (`listItemSkipLevelConsumer`) expects.
|
|
180
|
+
while(stack.length < indent){
|
|
181
|
+
const intermediateList = writer.createElement(listStyle.type);
|
|
182
|
+
const intermediateListItem = writer.createElement('li');
|
|
183
|
+
writer.setStyle('list-style-type', 'none', intermediateListItem);
|
|
184
|
+
if (stack.length == 0) {
|
|
185
|
+
const parent = itemLikeElement.element.parent;
|
|
186
|
+
const index = parent.getChildIndex(itemLikeElement.element) + 1;
|
|
187
|
+
writer.insertChild(index, intermediateList, parent);
|
|
188
|
+
} else {
|
|
189
|
+
const parentListItems = stack[stack.length - 1].listItemElements;
|
|
190
|
+
writer.appendChild(intermediateList, parentListItems[parentListItems.length - 1]);
|
|
191
|
+
}
|
|
192
|
+
writer.appendChild(intermediateListItem, intermediateList);
|
|
193
|
+
stack.push({
|
|
194
|
+
...itemLikeElement,
|
|
195
|
+
listElement: intermediateList,
|
|
196
|
+
listItemElements: [
|
|
197
|
+
intermediateListItem
|
|
198
|
+
],
|
|
199
|
+
isIntermediate: true,
|
|
200
|
+
// Intermediate wrappers hold no real list item, so they must not pretend to "own" the
|
|
201
|
+
// deep item's `margin-left` — otherwise `stack.find` (matching non-list multi-block
|
|
202
|
+
// continuations by margin) returns the shallower intermediate before the real item.
|
|
203
|
+
marginLeft: undefined
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
// Create a new OL/UL if required (greater indent or different list type).
|
|
207
|
+
if (indent > stack.length - 1 || stack[indent].listElement.name != listStyle.type) {
|
|
208
|
+
// If this list was seen before at this indent (i.e. it was interrupted by a non-list block
|
|
209
|
+
// and is now resuming), set `start` so the numbering continues from where it left off.
|
|
210
|
+
if (listStyle.type == 'ol' && itemLikeElement.id !== undefined && encounteredLists[indent] && encounteredLists[indent][originalListId]) {
|
|
211
|
+
listStyle.startIndex = encounteredLists[indent][originalListId];
|
|
212
|
+
}
|
|
213
|
+
const listElement = createNewEmptyList(listStyle, writer, hasMultiLevelListPlugin);
|
|
214
|
+
// Insert the new OL/UL.
|
|
215
|
+
if (stack.length == 0) {
|
|
216
|
+
const parent = itemLikeElement.element.parent;
|
|
217
|
+
const index = parent.getChildIndex(itemLikeElement.element) + 1;
|
|
218
|
+
writer.insertChild(index, listElement, parent);
|
|
219
|
+
} else if (indent == 0) {
|
|
220
|
+
// A real list at root indent while a skip-level intermediate of a different type
|
|
221
|
+
// already sits there — insert the new list as a sibling of the intermediate in the
|
|
222
|
+
// same parent (can't merge two lists of different types).
|
|
223
|
+
const existingList = stack[0].listElement;
|
|
224
|
+
const listParent = existingList.parent;
|
|
225
|
+
const insertIndex = listParent.getChildIndex(existingList) + 1;
|
|
226
|
+
writer.insertChild(insertIndex, listElement, listParent);
|
|
227
|
+
} else {
|
|
228
|
+
const parentListItems = stack[indent - 1].listItemElements;
|
|
229
|
+
writer.appendChild(listElement, parentListItems[parentListItems.length - 1]);
|
|
230
|
+
}
|
|
231
|
+
// Update the list stack for other items to reference.
|
|
232
|
+
stack[indent] = {
|
|
233
|
+
...itemLikeElement,
|
|
234
|
+
listElement,
|
|
235
|
+
listItemElements: []
|
|
236
|
+
};
|
|
237
|
+
// Record the starting value for this list so that if it is interrupted and resumed later,
|
|
238
|
+
// the continuation list can pick up numbering from the right value.
|
|
239
|
+
// For a fresh list `listStyle.startIndex` is undefined, so we fall back to 1.
|
|
240
|
+
if (itemLikeElement.id !== undefined) {
|
|
241
|
+
if (!encounteredLists[indent]) {
|
|
242
|
+
encounteredLists[indent] = {};
|
|
174
243
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
244
|
+
encounteredLists[indent][originalListId] = listStyle.startIndex || 1;
|
|
245
|
+
}
|
|
246
|
+
} else if (stack[indent].isIntermediate) {
|
|
247
|
+
// Same type as the intermediate — reuse its `<ol>`/`<ul>`. The intermediate was created
|
|
248
|
+
// without `list-style-type`, `start`, or the `legal-list` class (the fill loop only sets
|
|
249
|
+
// the tag name); apply them now from the claiming item so the reused element no longer
|
|
250
|
+
// looks like a styleless wrapper.
|
|
251
|
+
applyListStyleToElement(stack[indent].listElement, listStyle, writer, hasMultiLevelListPlugin);
|
|
252
|
+
// Update the wrapper to represent the claiming item (id, marginLeft, …) so later lookups
|
|
253
|
+
// don't see stale data from the deep item that originally seeded the intermediate.
|
|
254
|
+
stack[indent] = {
|
|
255
|
+
...itemLikeElement,
|
|
256
|
+
listElement: stack[indent].listElement,
|
|
257
|
+
listItemElements: stack[indent].listItemElements
|
|
258
|
+
};
|
|
259
|
+
// Same as the create-new path: track this list so that if it is interrupted (e.g. by a
|
|
260
|
+
// multi-block paragraph matched against an ancestor frame) and later resumed via a fresh
|
|
261
|
+
// `<ol>`, the continuation can set the correct `start` attribute instead of restarting from 1.
|
|
262
|
+
if (itemLikeElement.id !== undefined) {
|
|
263
|
+
if (!encounteredLists[indent]) {
|
|
264
|
+
encounteredLists[indent] = {};
|
|
189
265
|
}
|
|
266
|
+
encounteredLists[indent][originalListId] = listStyle.startIndex || 1;
|
|
190
267
|
}
|
|
191
268
|
}
|
|
192
269
|
// Use LI if it is already it or create a new LI element.
|
|
@@ -244,19 +321,20 @@ function applyListItemMarginLeftAndUpdateTopLevelInfo(writer, stack, topLevelLis
|
|
|
244
321
|
}
|
|
245
322
|
const listItemBlockMarginLeft = parseFloat(itemLikeElement.marginLeft);
|
|
246
323
|
let currentListBlockIndent = 0;
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
324
|
+
// Sum the relative `margin-left` of the last `<li>` in every ancestor stack frame.
|
|
325
|
+
// Browser nesting cumulates: each ancestor `<li>`'s margin pushes its descendants further right,
|
|
326
|
+
// so to convert Word's absolute `margin-left` into the editor's relative value we have to subtract
|
|
327
|
+
// every ancestor's contribution — not only the immediate parent. Skip-level intermediate wrappers
|
|
328
|
+
// contribute 0 (no margin set) and so naturally drop out of the sum.
|
|
329
|
+
for(let ancestorIndex = 0; ancestorIndex < stack.length - 1; ancestorIndex++){
|
|
330
|
+
const ancestorListItems = stack[ancestorIndex].listItemElements;
|
|
331
|
+
const ancestorMargin = ancestorListItems[ancestorListItems.length - 1].getStyle('margin-left');
|
|
332
|
+
if (ancestorMargin !== undefined) {
|
|
333
|
+
currentListBlockIndent += parseFloat(ancestorMargin);
|
|
255
334
|
}
|
|
256
335
|
}
|
|
257
336
|
// Add 40px for each indent level because by default HTML lists have 40px indentation (padding-inline-start: 40px).
|
|
258
337
|
// So every nested list is indented by another 40px.
|
|
259
|
-
// Additionally, the nested list itself may be placed in a list item with margin-left style.
|
|
260
338
|
currentListBlockIndent += stack.length * 40;
|
|
261
339
|
// Calculate relative list item indentation to the list it is in.
|
|
262
340
|
const adjustedListItemIndent = listItemBlockMarginLeft - currentListBlockIndent;
|
|
@@ -354,16 +432,31 @@ function createTopLevelListInfo() {
|
|
|
354
432
|
* Whether the given element is possibly a list continuation. Previous element was wrapped into a list
|
|
355
433
|
* or the current element already is inside a list.
|
|
356
434
|
*/ function isListContinuation(currentItem) {
|
|
357
|
-
|
|
435
|
+
let previousSibling = currentItem.element.previousSibling;
|
|
436
|
+
// Skip past stray inline markers that Word leaves between list paragraphs (e.g. empty
|
|
437
|
+
// `<span style='mso-bookmark:...'></span>` or empty `<o:p></o:p>`). They have no visual effect
|
|
438
|
+
// but would otherwise break list continuity here — which matters most for nested items pasted
|
|
439
|
+
// right after their parent, where breaking the chain causes PFO to clamp the deeper item to
|
|
440
|
+
// indent 0 instead of nesting it.
|
|
441
|
+
while(previousSibling && isStrayInlineMarker(previousSibling)){
|
|
442
|
+
previousSibling = previousSibling.previousSibling;
|
|
443
|
+
}
|
|
358
444
|
if (!previousSibling) {
|
|
359
445
|
const parent = currentItem.element.parent;
|
|
360
446
|
// If it's a li inside ul or ol like in here: https://github.com/ckeditor/ckeditor5/issues/15964.
|
|
361
447
|
// If the parent has previous sibling, which is not a list, then it is not a continuation.
|
|
362
448
|
return isList(parent) && (!parent.previousSibling || isList(parent.previousSibling));
|
|
363
449
|
}
|
|
364
|
-
// Even with the same id the list does not have to be continuous (
|
|
450
|
+
// Even with the same id the list does not have to be continuous (https://github.com/ckeditor/ckeditor5/issues/43).
|
|
365
451
|
return isList(previousSibling);
|
|
366
452
|
}
|
|
453
|
+
/**
|
|
454
|
+
* True for empty inline elements Word emits as residue between paragraphs (`<span>`, `<a>`, `<o:p>`).
|
|
455
|
+
* Used by `isListContinuation` to look past these when checking whether the prior block is a list —
|
|
456
|
+
* they're layout artefacts, not real content.
|
|
457
|
+
*/ function isStrayInlineMarker(node) {
|
|
458
|
+
return node.is('element') && node.childCount === 0 && /^(?:span|a|o:p)$/.test(node.name);
|
|
459
|
+
}
|
|
367
460
|
function isList(element) {
|
|
368
461
|
return element.is('element', 'ol') || element.is('element', 'ul');
|
|
369
462
|
}
|
|
@@ -515,6 +608,14 @@ function isList(element) {
|
|
|
515
608
|
* Creates a new list OL/UL element.
|
|
516
609
|
*/ function createNewEmptyList(listStyle, writer, hasMultiLevelListPlugin) {
|
|
517
610
|
const list = writer.createElement(listStyle.type);
|
|
611
|
+
applyListStyleToElement(list, listStyle, writer, hasMultiLevelListPlugin);
|
|
612
|
+
return list;
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Applies `list-style-type`, `start`, and the `legal-list` class to a list element based on the detected
|
|
616
|
+
* list style. Used both when creating a fresh list and when a real item claims a previously-intermediate
|
|
617
|
+
* wrapper (which was created without any of these).
|
|
618
|
+
*/ function applyListStyleToElement(list, listStyle, writer, hasMultiLevelListPlugin) {
|
|
518
619
|
// We do not support modifying the marker for a particular list item.
|
|
519
620
|
// Set the value for the `list-style-type` property directly to the list container.
|
|
520
621
|
if (listStyle.style) {
|
|
@@ -526,7 +627,6 @@ function isList(element) {
|
|
|
526
627
|
if (listStyle.isLegalStyleList && hasMultiLevelListPlugin) {
|
|
527
628
|
writer.addClass('legal-list', list);
|
|
528
629
|
}
|
|
529
|
-
return list;
|
|
530
630
|
}
|
|
531
631
|
/**
|
|
532
632
|
* Extracts list item information from Word specific list-like element style:
|
|
@@ -538,7 +638,7 @@ function isList(element) {
|
|
|
538
638
|
* where:
|
|
539
639
|
*
|
|
540
640
|
* ```
|
|
541
|
-
* * `l1` is a list id (however it does not mean this is a continuous list - see
|
|
641
|
+
* * `l1` is a list id (however it does not mean this is a continuous list - see https://github.com/ckeditor/ckeditor5/issues/43),
|
|
542
642
|
* * `level1` is a list item indentation level,
|
|
543
643
|
* * `lfo1` is a list insertion order in a document.
|
|
544
644
|
* ```
|
|
@@ -1176,14 +1276,16 @@ const msWordMatch2 = /xmlns:o="urn:schemas-microsoft-com/i;
|
|
|
1176
1276
|
document;
|
|
1177
1277
|
hasMultiLevelListPlugin;
|
|
1178
1278
|
hasTablePropertiesPlugin;
|
|
1279
|
+
enableSkipLevelLists;
|
|
1179
1280
|
/**
|
|
1180
1281
|
* Creates a new `PasteFromOfficeMSWordNormalizer` instance.
|
|
1181
1282
|
*
|
|
1182
1283
|
* @param document View document.
|
|
1183
|
-
*/ constructor(document, hasMultiLevelListPlugin = false, hasTablePropertiesPlugin = false){
|
|
1284
|
+
*/ constructor(document, hasMultiLevelListPlugin = false, hasTablePropertiesPlugin = false, enableSkipLevelLists = false){
|
|
1184
1285
|
this.document = document;
|
|
1185
1286
|
this.hasMultiLevelListPlugin = hasMultiLevelListPlugin;
|
|
1186
1287
|
this.hasTablePropertiesPlugin = hasTablePropertiesPlugin;
|
|
1288
|
+
this.enableSkipLevelLists = enableSkipLevelLists;
|
|
1187
1289
|
}
|
|
1188
1290
|
/**
|
|
1189
1291
|
* @inheritDoc
|
|
@@ -1194,15 +1296,14 @@ const msWordMatch2 = /xmlns:o="urn:schemas-microsoft-com/i;
|
|
|
1194
1296
|
* @inheritDoc
|
|
1195
1297
|
*/ execute(data) {
|
|
1196
1298
|
const writer = new ViewUpcastWriter(this.document);
|
|
1197
|
-
const
|
|
1198
|
-
transformBookmarks(
|
|
1199
|
-
transformListItemLikeElementsIntoLists(
|
|
1200
|
-
replaceImagesSourceWithBase64(
|
|
1201
|
-
transformTables(
|
|
1202
|
-
removeInvalidTableWidth(
|
|
1203
|
-
replaceMSFootnotes(
|
|
1204
|
-
removeMSAttributes(
|
|
1205
|
-
data.content = documentFragment;
|
|
1299
|
+
const stylesString = data.extraContent.stylesString;
|
|
1300
|
+
transformBookmarks(data.content, writer);
|
|
1301
|
+
transformListItemLikeElementsIntoLists(data.content, stylesString, this.hasMultiLevelListPlugin, this.enableSkipLevelLists);
|
|
1302
|
+
replaceImagesSourceWithBase64(data.content, data.dataTransfer.getData('text/rtf'));
|
|
1303
|
+
transformTables(data.content, writer, this.hasTablePropertiesPlugin);
|
|
1304
|
+
removeInvalidTableWidth(data.content, writer);
|
|
1305
|
+
replaceMSFootnotes(data.content, writer);
|
|
1306
|
+
removeMSAttributes(data.content);
|
|
1206
1307
|
}
|
|
1207
1308
|
}
|
|
1208
1309
|
|
|
@@ -1374,12 +1475,10 @@ const googleDocsMatch = /id=("|')docs-internal-guid-[-0-9a-f]+("|')/i;
|
|
|
1374
1475
|
* @inheritDoc
|
|
1375
1476
|
*/ execute(data) {
|
|
1376
1477
|
const writer = new ViewUpcastWriter(this.document);
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
replaceTabsWithinPreWithSpaces(documentFragment, writer, 8);
|
|
1382
|
-
data.content = documentFragment;
|
|
1478
|
+
removeBoldWrapper(data.content, writer);
|
|
1479
|
+
unwrapParagraphInListItem(data.content, writer);
|
|
1480
|
+
transformBlockBrsToParagraphs(data.content, writer);
|
|
1481
|
+
replaceTabsWithinPreWithSpaces(data.content, writer, 8);
|
|
1383
1482
|
}
|
|
1384
1483
|
}
|
|
1385
1484
|
|
|
@@ -1462,12 +1561,10 @@ const googleSheetsMatch = /<google-sheets-html-origin/i;
|
|
|
1462
1561
|
* @inheritDoc
|
|
1463
1562
|
*/ execute(data) {
|
|
1464
1563
|
const writer = new ViewUpcastWriter(this.document);
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
removeStyleBlock(documentFragment, writer);
|
|
1470
|
-
data.content = documentFragment;
|
|
1564
|
+
removeGoogleSheetsTag(data.content, writer);
|
|
1565
|
+
removeXmlns(data.content, writer);
|
|
1566
|
+
removeInvalidTableWidth(data.content, writer);
|
|
1567
|
+
removeStyleBlock(data.content, writer);
|
|
1471
1568
|
}
|
|
1472
1569
|
}
|
|
1473
1570
|
|
|
@@ -1480,7 +1577,9 @@ const googleSheetsMatch = /<google-sheets-html-origin/i;
|
|
|
1480
1577
|
* Replaces last space preceding elements closing tag with ` `. Such operation prevents spaces from being removed
|
|
1481
1578
|
* during further DOM/View processing (see especially {@link module:engine/view/domconverter~ViewDomConverter#_processDomInlineNodes}).
|
|
1482
1579
|
* This method also takes into account Word specific `<o:p></o:p>` empty tags.
|
|
1483
|
-
* Additionally multiline sequences of spaces and new lines between tags are removed (see
|
|
1580
|
+
* Additionally multiline sequences of spaces and new lines between tags are removed (see
|
|
1581
|
+
* https://github.com/ckeditor/ckeditor5-paste-from-office/issues/39
|
|
1582
|
+
* and https://github.com/ckeditor/ckeditor5-paste-from-office/issues/40).
|
|
1484
1583
|
*
|
|
1485
1584
|
* @param htmlString HTML string in which spacing should be normalized.
|
|
1486
1585
|
* @returns Input HTML with spaces normalized.
|
|
@@ -1622,6 +1721,9 @@ const googleSheetsMatch = /<google-sheets-html-origin/i;
|
|
|
1622
1721
|
*
|
|
1623
1722
|
* For more information about this feature check the {@glink api/paste-from-office package page}.
|
|
1624
1723
|
*/ class PasteFromOffice extends Plugin {
|
|
1724
|
+
/**
|
|
1725
|
+
* The priority array of registered normalizers.
|
|
1726
|
+
*/ _normalizers = [];
|
|
1625
1727
|
/**
|
|
1626
1728
|
* @inheritDoc
|
|
1627
1729
|
*/ static get pluginName() {
|
|
@@ -1656,33 +1758,53 @@ const googleSheetsMatch = /<google-sheets-html-origin/i;
|
|
|
1656
1758
|
const editor = this.editor;
|
|
1657
1759
|
const clipboardPipeline = editor.plugins.get('ClipboardPipeline');
|
|
1658
1760
|
const viewDocument = editor.editing.view.document;
|
|
1659
|
-
const normalizers = [];
|
|
1660
1761
|
const hasMultiLevelListPlugin = this.editor.plugins.has('MultiLevelListEditing');
|
|
1661
1762
|
const hasTablePropertiesPlugin = this.editor.plugins.has('TablePropertiesEditing');
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1763
|
+
const enableSkipLevelLists = !!this.editor.config.get('list.enableSkipLevelLists');
|
|
1764
|
+
this.registerNormalizer(new PasteFromOfficeMSWordNormalizer(viewDocument, hasMultiLevelListPlugin, hasTablePropertiesPlugin, enableSkipLevelLists));
|
|
1765
|
+
this.registerNormalizer(new GoogleDocsNormalizer(viewDocument));
|
|
1766
|
+
this.registerNormalizer(new GoogleSheetsNormalizer(viewDocument));
|
|
1767
|
+
viewDocument.on('clipboardInput', (evt, data)=>{
|
|
1768
|
+
if (typeof data.content != 'string') {
|
|
1667
1769
|
return;
|
|
1668
1770
|
}
|
|
1669
|
-
|
|
1670
|
-
|
|
1771
|
+
// The `htmlString` is used only to detect (match) the active normalizer.
|
|
1772
|
+
// The actual content processing is happening on `data.content` below.
|
|
1773
|
+
const htmlString = data.dataTransfer.getData('text/html');
|
|
1774
|
+
const activeNormalizer = this._normalizers.find(({ normalizer })=>normalizer.isActive(htmlString));
|
|
1775
|
+
if (activeNormalizer) {
|
|
1776
|
+
const parsedData = parsePasteOfficeHtml(data.content, viewDocument.stylesProcessor);
|
|
1777
|
+
data.content = parsedData.body;
|
|
1778
|
+
data.extraContent = {
|
|
1779
|
+
...parsedData,
|
|
1780
|
+
isTransformedWithPasteFromOffice: true
|
|
1781
|
+
};
|
|
1782
|
+
}
|
|
1783
|
+
}, {
|
|
1784
|
+
priority: priorities.low + 10
|
|
1785
|
+
});
|
|
1786
|
+
clipboardPipeline.on('inputTransformation', (evt, data)=>{
|
|
1787
|
+
if (!data.extraContent || !data.extraContent.isTransformedWithPasteFromOffice) {
|
|
1671
1788
|
return;
|
|
1672
1789
|
}
|
|
1790
|
+
// The `htmlString` is used only to detect (match) the active normalizers, not for processing.
|
|
1673
1791
|
const htmlString = data.dataTransfer.getData('text/html');
|
|
1674
|
-
const
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
data._parsedData = parsePasteOfficeHtml(htmlString, viewDocument.stylesProcessor);
|
|
1678
|
-
}
|
|
1679
|
-
activeNormalizer.execute(data);
|
|
1680
|
-
data._isTransformedWithPasteFromOffice = true;
|
|
1792
|
+
const normalizers = this._normalizers.filter(({ normalizer })=>normalizer.isActive(htmlString));
|
|
1793
|
+
for (const { normalizer } of normalizers){
|
|
1794
|
+
normalizer.execute(data);
|
|
1681
1795
|
}
|
|
1682
1796
|
}, {
|
|
1683
1797
|
priority: 'high'
|
|
1684
1798
|
});
|
|
1685
1799
|
}
|
|
1800
|
+
/**
|
|
1801
|
+
* Registers a normalizer with the given priority.
|
|
1802
|
+
*/ registerNormalizer(normalizer, priority) {
|
|
1803
|
+
insertToPriorityArray(this._normalizers, {
|
|
1804
|
+
normalizer,
|
|
1805
|
+
priority: priorities.get(priority)
|
|
1806
|
+
});
|
|
1807
|
+
}
|
|
1686
1808
|
}
|
|
1687
1809
|
|
|
1688
1810
|
export { PasteFromOffice, GoogleDocsNormalizer as PasteFromOfficeGoogleDocsNormalizer, GoogleSheetsNormalizer as PasteFromOfficeGoogleSheetsNormalizer, PasteFromOfficeMSWordNormalizer, _convertHexToBase64, convertCssLengthToPx as _convertPasteOfficeCssLengthToPx, isPx as _isPasteOfficePxValue, normalizeSpacerunSpans as _normalizePasteOfficeSpaceRunSpans, normalizeSpacing as _normalizePasteOfficeSpacing, removeGoogleSheetsTag as _removePasteGoogleOfficeSheetsTag, removeMSAttributes as _removePasteMSOfficeAttributes, removeBoldWrapper as _removePasteOfficeBoldWrapper, removeInvalidTableWidth as _removePasteOfficeInvalidTableWidths, removeStyleBlock as _removePasteOfficeStyleBlock, removeXmlns as _removePasteOfficeXmlnsAttributes, replaceImagesSourceWithBase64 as _replacePasteOfficeImagesSourceWithBase64, toPx as _toPasteOfficePxValue, transformBlockBrsToParagraphs as _transformPasteOfficeBlockBrsToParagraphs, transformBookmarks as _transformPasteOfficeBookmarks, transformListItemLikeElementsIntoLists as _transformPasteOfficeListItemLikeElementsIntoLists, transformTables as _transformPasteOfficeTables, unwrapParagraphInListItem as _unwrapPasteOfficeParagraphInListItem, parsePasteOfficeHtml };
|