@ckeditor/ckeditor5-paste-from-office 48.0.1-alpha.1 → 48.1.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/bookmark.d.ts +1 -1
- package/dist/index.js +64 -12
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
/**
|
|
6
6
|
* @module paste-from-office/filters/bookmark
|
|
7
7
|
*/
|
|
8
|
-
import {
|
|
8
|
+
import type { ViewUpcastWriter, ViewDocumentFragment } from '@ckeditor/ckeditor5-engine';
|
|
9
9
|
/**
|
|
10
10
|
* Transforms `<a>` elements which are bookmarks by moving their children after the element.
|
|
11
11
|
*
|
package/dist/index.js
CHANGED
|
@@ -27,8 +27,25 @@ import { ViewUpcastWriter, Matcher, ViewDocument, ViewDomConverter } from '@cked
|
|
|
27
27
|
const index = element.parent.getChildIndex(element) + 1;
|
|
28
28
|
const children = element.getChildren();
|
|
29
29
|
writer.insertChild(index, children, element.parent);
|
|
30
|
+
if (isHiddenBookmarkAnchor(element)) {
|
|
31
|
+
writer.remove(element);
|
|
32
|
+
}
|
|
30
33
|
}
|
|
31
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Checks whether the given element is a hidden or auto-generated bookmark anchor.
|
|
37
|
+
*
|
|
38
|
+
* Editors like MS Word and Google Docs use the `name` attribute (rather than `id`)
|
|
39
|
+
* for bookmarks. Furthermore, they reserve `_`-prefixed bookmark names for
|
|
40
|
+
* auto-generated anchors (e.g., Table of Contents or internal hyperlinks) and
|
|
41
|
+
* do not allow users to manually create custom bookmarks starting with an underscore.
|
|
42
|
+
*
|
|
43
|
+
* @param element The element to check.
|
|
44
|
+
* @returns True if the element is a hidden bookmark anchor, false otherwise.
|
|
45
|
+
*/ function isHiddenBookmarkAnchor(element) {
|
|
46
|
+
const name = element.getAttribute('name');
|
|
47
|
+
return !!name && name.startsWith('_');
|
|
48
|
+
}
|
|
32
49
|
|
|
33
50
|
/**
|
|
34
51
|
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
@@ -96,7 +113,13 @@ import { ViewUpcastWriter, Matcher, ViewDocument, ViewDomConverter } from '@cked
|
|
|
96
113
|
if (!itemLikeElements.length) {
|
|
97
114
|
return;
|
|
98
115
|
}
|
|
99
|
-
|
|
116
|
+
// Tracks how many items have been added to each encountered list, keyed by indent level and list ID.
|
|
117
|
+
// Used to set the `start` attribute on a new <ol> when a list at a given indent is interrupted by
|
|
118
|
+
// a non-list block (e.g. a paragraph) and then resumed.
|
|
119
|
+
// Structure: [ { [listId:level]: itemCount } ] (array index is the indent level)
|
|
120
|
+
// Example: [ { '1:1': 3 }, { '0:2': 2 } ] means the top-level list (id=1) has 3 items,
|
|
121
|
+
// and the nested list (id=0) has 2 items so the next continuation should start at 3.
|
|
122
|
+
const encounteredLists = [];
|
|
100
123
|
const stack = [];
|
|
101
124
|
let topLevelListInfo = createTopLevelListInfo();
|
|
102
125
|
for (const itemLikeElement of itemLikeElements){
|
|
@@ -104,26 +127,40 @@ import { ViewUpcastWriter, Matcher, ViewDocument, ViewDomConverter } from '@cked
|
|
|
104
127
|
if (!isListContinuation(itemLikeElement)) {
|
|
105
128
|
applyIndentationToTopLevelList(writer, stack, topLevelListInfo);
|
|
106
129
|
topLevelListInfo = createTopLevelListInfo();
|
|
130
|
+
// Clear counters for nested levels only. The top-level counter (index 0) must survive
|
|
131
|
+
// so that a resumed top-level list (same id, interrupted by a paragraph) can still
|
|
132
|
+
// receive the correct `start` attribute. Nested counters must be cleared because
|
|
133
|
+
// a sibling top-level list item should not inherit the nested list counts from
|
|
134
|
+
// a previous top-level list item.
|
|
135
|
+
encounteredLists.length = 1;
|
|
107
136
|
stack.length = 0;
|
|
108
137
|
}
|
|
109
|
-
//
|
|
138
|
+
// Key used to look up this list inside `encounteredLists[indent]`.
|
|
139
|
+
// Combines the list id and level so that two different lists at the same indent
|
|
140
|
+
// level (e.g. first an <ol>, then a <ul> after a paragraph break) don't share a counter.
|
|
110
141
|
const originalListId = `${itemLikeElement.id}:${itemLikeElement.indent}`;
|
|
111
142
|
// Normalized list item indentation.
|
|
112
143
|
const indent = Math.min(itemLikeElement.indent - 1, stack.length);
|
|
113
144
|
// Trimming of the list stack on list ID change.
|
|
114
145
|
if (indent < stack.length && stack[indent].id !== itemLikeElement.id) {
|
|
146
|
+
// A different list started at this indent level — counters for this level and deeper
|
|
147
|
+
// belong to the previous list context and must not carry over.
|
|
148
|
+
encounteredLists.length = indent;
|
|
115
149
|
stack.length = indent;
|
|
116
150
|
}
|
|
117
151
|
// Trimming of the list stack on lower indent list encountered.
|
|
118
152
|
if (indent < stack.length - 1) {
|
|
153
|
+
// We jumped back to a shallower indent — any counters deeper than the new top are stale.
|
|
154
|
+
encounteredLists.length = indent + 1;
|
|
119
155
|
stack.length = indent + 1;
|
|
120
156
|
} else {
|
|
121
157
|
const listStyle = detectListStyle(itemLikeElement, stylesString);
|
|
122
158
|
// Create a new OL/UL if required (greater indent or different list type).
|
|
123
159
|
if (indent > stack.length - 1 || stack[indent].listElement.name != listStyle.type) {
|
|
124
|
-
//
|
|
125
|
-
|
|
126
|
-
|
|
160
|
+
// If this list was seen before at this indent (i.e. it was interrupted by a non-list block
|
|
161
|
+
// and is now resuming), set `start` so the numbering continues from where it left off.
|
|
162
|
+
if (listStyle.type == 'ol' && itemLikeElement.id !== undefined && encounteredLists[indent] && encounteredLists[indent][originalListId]) {
|
|
163
|
+
listStyle.startIndex = encounteredLists[indent][originalListId];
|
|
127
164
|
}
|
|
128
165
|
const listElement = createNewEmptyList(listStyle, writer, hasMultiLevelListPlugin);
|
|
129
166
|
// Insert the new OL/UL.
|
|
@@ -141,9 +178,14 @@ import { ViewUpcastWriter, Matcher, ViewDocument, ViewDomConverter } from '@cked
|
|
|
141
178
|
listElement,
|
|
142
179
|
listItemElements: []
|
|
143
180
|
};
|
|
144
|
-
//
|
|
145
|
-
|
|
146
|
-
|
|
181
|
+
// Record the starting value for this list so that if it is interrupted and resumed later,
|
|
182
|
+
// the continuation list can pick up numbering from the right value.
|
|
183
|
+
// For a fresh list `listStyle.startIndex` is undefined, so we fall back to 1.
|
|
184
|
+
if (itemLikeElement.id !== undefined) {
|
|
185
|
+
if (!encounteredLists[indent]) {
|
|
186
|
+
encounteredLists[indent] = {};
|
|
187
|
+
}
|
|
188
|
+
encounteredLists[indent][originalListId] = listStyle.startIndex || 1;
|
|
147
189
|
}
|
|
148
190
|
}
|
|
149
191
|
}
|
|
@@ -154,9 +196,9 @@ import { ViewUpcastWriter, Matcher, ViewDocument, ViewDomConverter } from '@cked
|
|
|
154
196
|
// Append the LI to OL/UL.
|
|
155
197
|
writer.appendChild(listItem, stack[indent].listElement);
|
|
156
198
|
stack[indent].listItemElements.push(listItem);
|
|
157
|
-
//
|
|
158
|
-
if (
|
|
159
|
-
encounteredLists[originalListId]++;
|
|
199
|
+
// Count the item so that `encounteredLists` always holds the value the *next* continuation list should start at.
|
|
200
|
+
if (itemLikeElement.id !== undefined && encounteredLists[indent]) {
|
|
201
|
+
encounteredLists[indent][originalListId]++;
|
|
160
202
|
}
|
|
161
203
|
// Append list block to LI.
|
|
162
204
|
if (itemLikeElement.element != listItem) {
|
|
@@ -169,12 +211,22 @@ import { ViewUpcastWriter, Matcher, ViewDocument, ViewDomConverter } from '@cked
|
|
|
169
211
|
} else {
|
|
170
212
|
// Other blocks in a list item.
|
|
171
213
|
const stackItem = stack.find((stackItem)=>stackItem.marginLeft == itemLikeElement.marginLeft);
|
|
172
|
-
//
|
|
214
|
+
// A non-list block (e.g. a plain paragraph) whose margin-left matches one of the active list items.
|
|
215
|
+
// The match is done by margin-left value — nested list items sometimes have no explicit margin-left,
|
|
216
|
+
// so the match typically resolves to an ancestor <li> rather than the deepest one.
|
|
173
217
|
if (stackItem) {
|
|
174
218
|
const listItems = stackItem.listItemElements;
|
|
175
219
|
// Append block to LI.
|
|
176
220
|
writer.appendChild(itemLikeElement.element, listItems[listItems.length - 1]);
|
|
177
221
|
writer.removeStyle('margin-left', itemLikeElement.element);
|
|
222
|
+
// Trim the stack to the matched level. Without this, the next nested list item would
|
|
223
|
+
// be appended to the existing nested <ol>/<ul> that appears *before* this paragraph
|
|
224
|
+
// in the DOM, instead of creating a new one *after* it.
|
|
225
|
+
stack.length = stack.indexOf(stackItem) + 1;
|
|
226
|
+
// Clear counters only for levels deeper than the direct children of the matched <li>.
|
|
227
|
+
// The counter at `stack.length` must survive so the next nested list can continue
|
|
228
|
+
// numbering from where it left off (e.g. <ol start="3">).
|
|
229
|
+
encounteredLists.length = stack.length + 1;
|
|
178
230
|
} else {
|
|
179
231
|
stack.length = 0;
|
|
180
232
|
}
|