@ckeditor/ckeditor5-clipboard 41.3.0-alpha.1 → 41.3.0-alpha.2

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.
Files changed (101) hide show
  1. package/dist/translations/ar.d.ts +8 -0
  2. package/dist/translations/ar.js +5 -0
  3. package/dist/translations/bg.d.ts +8 -0
  4. package/dist/translations/bg.js +5 -0
  5. package/dist/translations/bn.d.ts +8 -0
  6. package/dist/translations/bn.js +5 -0
  7. package/dist/translations/ca.d.ts +8 -0
  8. package/dist/translations/ca.js +5 -0
  9. package/dist/translations/cs.d.ts +8 -0
  10. package/dist/translations/cs.js +5 -0
  11. package/dist/translations/da.d.ts +8 -0
  12. package/dist/translations/da.js +5 -0
  13. package/dist/translations/de.d.ts +8 -0
  14. package/dist/translations/de.js +5 -0
  15. package/dist/translations/el.d.ts +8 -0
  16. package/dist/translations/el.js +5 -0
  17. package/dist/translations/en.d.ts +8 -0
  18. package/dist/translations/en.js +5 -0
  19. package/dist/translations/es.d.ts +8 -0
  20. package/dist/translations/es.js +5 -0
  21. package/dist/translations/et.d.ts +8 -0
  22. package/dist/translations/et.js +5 -0
  23. package/dist/translations/fi.d.ts +8 -0
  24. package/dist/translations/fi.js +5 -0
  25. package/dist/translations/fr.d.ts +8 -0
  26. package/dist/translations/fr.js +5 -0
  27. package/dist/translations/he.d.ts +8 -0
  28. package/dist/translations/he.js +5 -0
  29. package/dist/translations/hi.d.ts +8 -0
  30. package/dist/translations/hi.js +5 -0
  31. package/dist/translations/hr.d.ts +8 -0
  32. package/dist/translations/hr.js +5 -0
  33. package/dist/translations/hu.d.ts +8 -0
  34. package/dist/translations/hu.js +5 -0
  35. package/dist/translations/id.d.ts +8 -0
  36. package/dist/translations/id.js +5 -0
  37. package/dist/translations/it.d.ts +8 -0
  38. package/dist/translations/it.js +5 -0
  39. package/dist/translations/ja.d.ts +8 -0
  40. package/dist/translations/ja.js +5 -0
  41. package/dist/translations/ko.d.ts +8 -0
  42. package/dist/translations/ko.js +5 -0
  43. package/dist/translations/lt.d.ts +8 -0
  44. package/dist/translations/lt.js +5 -0
  45. package/dist/translations/lv.d.ts +8 -0
  46. package/dist/translations/lv.js +5 -0
  47. package/dist/translations/ms.d.ts +8 -0
  48. package/dist/translations/ms.js +5 -0
  49. package/dist/translations/nl.d.ts +8 -0
  50. package/dist/translations/nl.js +5 -0
  51. package/dist/translations/no.d.ts +8 -0
  52. package/dist/translations/no.js +5 -0
  53. package/dist/translations/pl.d.ts +8 -0
  54. package/dist/translations/pl.js +5 -0
  55. package/dist/translations/pt-br.d.ts +8 -0
  56. package/dist/translations/pt-br.js +5 -0
  57. package/dist/translations/pt.d.ts +8 -0
  58. package/dist/translations/pt.js +5 -0
  59. package/dist/translations/ro.d.ts +8 -0
  60. package/dist/translations/ro.js +5 -0
  61. package/dist/translations/ru.d.ts +8 -0
  62. package/dist/translations/ru.js +5 -0
  63. package/dist/translations/sk.d.ts +8 -0
  64. package/dist/translations/sk.js +5 -0
  65. package/dist/translations/sr.d.ts +8 -0
  66. package/dist/translations/sr.js +5 -0
  67. package/dist/translations/sv.d.ts +8 -0
  68. package/dist/translations/sv.js +5 -0
  69. package/dist/translations/th.d.ts +8 -0
  70. package/dist/translations/th.js +5 -0
  71. package/dist/translations/tr.d.ts +8 -0
  72. package/dist/translations/tr.js +5 -0
  73. package/dist/translations/uk.d.ts +8 -0
  74. package/dist/translations/uk.js +5 -0
  75. package/dist/translations/vi.d.ts +8 -0
  76. package/dist/translations/vi.js +5 -0
  77. package/dist/translations/zh-cn.d.ts +8 -0
  78. package/dist/translations/zh-cn.js +5 -0
  79. package/dist/translations/zh.d.ts +8 -0
  80. package/dist/translations/zh.js +5 -0
  81. package/dist/types/augmentation.d.ts +4 -0
  82. package/dist/types/clipboard.d.ts +4 -0
  83. package/dist/types/clipboardmarkersutils.d.ts +43 -25
  84. package/dist/types/clipboardobserver.d.ts +4 -0
  85. package/dist/types/clipboardpipeline.d.ts +4 -0
  86. package/dist/types/dragdrop.d.ts +4 -0
  87. package/dist/types/dragdropblocktoolbar.d.ts +4 -0
  88. package/dist/types/dragdroptarget.d.ts +4 -0
  89. package/dist/types/index.d.ts +5 -1
  90. package/dist/types/lineview.d.ts +4 -0
  91. package/dist/types/pasteplaintext.d.ts +4 -0
  92. package/dist/types/utils/normalizeclipboarddata.d.ts +4 -0
  93. package/dist/types/utils/plaintexttohtml.d.ts +4 -0
  94. package/dist/types/utils/viewtoplaintext.d.ts +4 -0
  95. package/package.json +6 -6
  96. package/src/clipboardmarkersutils.d.ts +39 -25
  97. package/src/clipboardmarkersutils.js +147 -72
  98. package/src/clipboardpipeline.js +8 -9
  99. package/src/index.d.ts +1 -1
  100. package/dist/index.js +0 -2175
  101. package/dist/index.js.map +0 -1
@@ -35,36 +35,11 @@ export default class ClipboardMarkersUtils extends Plugin {
35
35
  * Registers marker name as copyable in clipboard pipeline.
36
36
  *
37
37
  * @param markerName Name of marker that can be copied.
38
- * @param restrictions Preset or specified array of actions that can be performed on specified marker name.
38
+ * @param config Configuration that describes what can be performed on specified marker.
39
39
  * @internal
40
40
  */
41
- _registerMarkerToCopy(markerName, restrictions) {
42
- const allowedActions = Array.isArray(restrictions) ? restrictions : this._mapRestrictionPresetToActions(restrictions);
43
- if (allowedActions.length) {
44
- this._markersToCopy.set(markerName, allowedActions);
45
- }
46
- }
47
- /**
48
- * Maps preset into array of clipboard operations to be allowed on marker.
49
- *
50
- * @param preset Restrictions preset to be mapped to actions
51
- * @internal
52
- */
53
- _mapRestrictionPresetToActions(preset) {
54
- switch (preset) {
55
- case 'always':
56
- return ['copy', 'cut', 'dragstart'];
57
- case 'default':
58
- return ['cut', 'dragstart'];
59
- case 'never':
60
- return [];
61
- default: {
62
- // Skip unrecognized type.
63
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
64
- const unreachable = preset;
65
- return [];
66
- }
67
- }
41
+ _registerMarkerToCopy(markerName, config) {
42
+ this._markersToCopy.set(markerName, config);
68
43
  }
69
44
  /**
70
45
  * Performs copy markers on provided selection and paste it to fragment returned from `getCopiedFragment`.
@@ -133,6 +108,7 @@ export default class ClipboardMarkersUtils extends Plugin {
133
108
  *
134
109
  * * `markers` are inserted into the same element that must be later transformed inside `getPastedDocumentElement`.
135
110
  * * Fake marker elements inside `getPastedDocumentElement` can be cloned, but their ranges cannot overlap.
111
+ * * If `duplicateOnPaste` is `true` in marker config then associated marker ID is regenerated before pasting.
136
112
  *
137
113
  * @param action Type of clipboard action.
138
114
  * @param markers Object that maps marker name to corresponding range.
@@ -140,26 +116,47 @@ export default class ClipboardMarkersUtils extends Plugin {
140
116
  * @internal
141
117
  */
142
118
  _pasteMarkersIntoTransformedElement(markers, getPastedDocumentElement) {
143
- const copyableMarkers = this._getCopyableMarkersFromRangeMap(markers);
119
+ const pasteMarkers = this._getPasteMarkersFromRangeMap(markers);
144
120
  return this.editor.model.change(writer => {
145
- const sourceFragmentFakeMarkers = this._insertFakeMarkersElements(writer, copyableMarkers);
121
+ // Inserts fake markers into source fragment / element that is later transformed inside `getPastedDocumentElement`.
122
+ const sourceFragmentFakeMarkers = this._insertFakeMarkersElements(writer, pasteMarkers);
123
+ // Modifies document fragment (for example, cloning table cells) and then inserts it into the document.
146
124
  const transformedElement = getPastedDocumentElement(writer);
125
+ // Removes markers in pasted and transformed fragment in root document.
147
126
  const removedFakeMarkers = this._removeFakeMarkersInsideElement(writer, transformedElement);
148
- // Cleanup fake markers inserted into transformed element.
127
+ // Cleans up fake markers inserted into source fragment (that one before transformation which is not pasted).
149
128
  for (const element of Object.values(sourceFragmentFakeMarkers).flat()) {
150
129
  writer.remove(element);
151
130
  }
131
+ // Inserts to root document fake markers.
152
132
  for (const [markerName, range] of Object.entries(removedFakeMarkers)) {
153
- const uniqueName = writer.model.markers.has(markerName) ? this._getUniqueMarkerName(markerName) : markerName;
154
- writer.addMarker(uniqueName, {
155
- usingOperation: true,
156
- affectsData: true,
157
- range
158
- });
133
+ if (!writer.model.markers.has(markerName)) {
134
+ writer.addMarker(markerName, {
135
+ usingOperation: true,
136
+ affectsData: true,
137
+ range
138
+ });
139
+ }
159
140
  }
160
141
  return transformedElement;
161
142
  });
162
143
  }
144
+ /**
145
+ * Pastes document fragment with markers to document.
146
+ * If `duplicateOnPaste` is `true` in marker config then associated markers IDs
147
+ * are regenerated before pasting to avoid markers duplications in content.
148
+ *
149
+ * @param fragment Document fragment that should contain already processed by pipeline markers.
150
+ * @internal
151
+ */
152
+ _pasteFragmentWithMarkers(fragment) {
153
+ const pasteMarkers = this._getPasteMarkersFromRangeMap(fragment.markers);
154
+ fragment.markers.clear();
155
+ for (const copyableMarker of pasteMarkers) {
156
+ fragment.markers.set(copyableMarker.name, copyableMarker.range);
157
+ }
158
+ return this.editor.model.insertContent(fragment);
159
+ }
163
160
  /**
164
161
  * In some situations we have to perform copy on selected fragment with certain markers. This function allows to temporarily bypass
165
162
  * restrictions on markers that we want to copy.
@@ -169,11 +166,16 @@ export default class ClipboardMarkersUtils extends Plugin {
169
166
  *
170
167
  * @param markerName Which markers should be copied.
171
168
  * @param executor Callback executed.
169
+ * @param config Optional configuration flags used to copy (such like partial copy flag).
172
170
  * @internal
173
171
  */
174
- _forceMarkersCopy(markerName, executor) {
172
+ _forceMarkersCopy(markerName, executor, config = {
173
+ allowedActions: 'all',
174
+ copyPartiallySelected: true,
175
+ duplicateOnPaste: true
176
+ }) {
175
177
  const before = this._markersToCopy.get(markerName);
176
- this._markersToCopy.set(markerName, this._mapRestrictionPresetToActions('always'));
178
+ this._markersToCopy.set(markerName, config);
177
179
  executor();
178
180
  if (before) {
179
181
  this._markersToCopy.set(markerName, before);
@@ -189,26 +191,35 @@ export default class ClipboardMarkersUtils extends Plugin {
189
191
  * @param action Type of clipboard action. If null then checks only if marker is registered as copyable.
190
192
  * @internal
191
193
  */
192
- _canPerformMarkerClipboardAction(markerName, action) {
193
- const [markerNamePrefix] = markerName.split(':');
194
+ _isMarkerCopyable(markerName, action) {
195
+ const config = this._getMarkerClipboardConfig(markerName);
196
+ if (!config) {
197
+ return false;
198
+ }
199
+ // If there is no action provided then only presence of marker is checked.
194
200
  if (!action) {
195
- return this._markersToCopy.has(markerNamePrefix);
201
+ return true;
196
202
  }
197
- const possibleActions = this._markersToCopy.get(markerNamePrefix) || [];
198
- return possibleActions.includes(action);
203
+ const { allowedActions } = config;
204
+ return allowedActions === 'all' || allowedActions.includes(action);
205
+ }
206
+ /**
207
+ * Checks if marker has any clipboard copy behavior configuration.
208
+ *
209
+ * @param markerName Name of checked marker.
210
+ */
211
+ _hasMarkerConfiguration(markerName) {
212
+ return !!this._getMarkerClipboardConfig(markerName);
199
213
  }
200
214
  /**
201
- * Changes marker names for markers stored in given document fragment so that they are unique.
215
+ * Returns marker's configuration flags passed during registration.
202
216
  *
203
- * @param fragment
217
+ * @param markerName Name of marker that should be returned.
204
218
  * @internal
205
219
  */
206
- _setUniqueMarkerNamesInFragment(fragment) {
207
- const markers = Array.from(fragment.markers);
208
- fragment.markers.clear();
209
- for (const [name, range] of markers) {
210
- fragment.markers.set(this._getUniqueMarkerName(name), range);
211
- }
220
+ _getMarkerClipboardConfig(markerName) {
221
+ const [markerNamePrefix] = markerName.split(':');
222
+ return this._markersToCopy.get(markerNamePrefix) || null;
212
223
  }
213
224
  /**
214
225
  * First step of copying markers. It looks for markers intersecting with given selection and inserts `$marker` elements
@@ -226,34 +237,89 @@ export default class ClipboardMarkersUtils extends Plugin {
226
237
  /**
227
238
  * Returns array of markers that can be copied in specified selection.
228
239
  *
240
+ * If marker cannot be copied partially (according to `copyPartiallySelected` configuration flag) and
241
+ * is not present entirely in any selection range then it will be skipped.
242
+ *
229
243
  * @param writer An instance of the model writer.
230
244
  * @param selection Selection which will be checked.
231
245
  * @param action Type of clipboard action. If null then checks only if marker is registered as copyable.
232
246
  */
233
247
  _getCopyableMarkersFromSelection(writer, selection, action) {
248
+ const selectionRanges = Array.from(selection.getRanges());
249
+ // Picks all markers in provided ranges. Ensures that there are no duplications if
250
+ // there are multiple ranges that intersects with the same marker.
251
+ const markersInRanges = new Set(selectionRanges.flatMap(selectionRange => Array.from(writer.model.markers.getMarkersIntersectingRange(selectionRange))));
252
+ const isSelectionMarkerCopyable = (marker) => {
253
+ // Check if marker exists in configuration and provided action can be performed on it.
254
+ const isCopyable = this._isMarkerCopyable(marker.name, action);
255
+ if (!isCopyable) {
256
+ return false;
257
+ }
258
+ // Checks if configuration disallows to copy marker only if part of its content is selected.
259
+ //
260
+ // Example:
261
+ // <marker-a> Hello [ World ] </marker-a>
262
+ // ^ selection
263
+ //
264
+ // In this scenario `marker-a` won't be copied because selection doesn't overlap its content entirely.
265
+ const { copyPartiallySelected } = this._getMarkerClipboardConfig(marker.name);
266
+ if (!copyPartiallySelected) {
267
+ const markerRange = marker.getRange();
268
+ return selectionRanges.some(selectionRange => selectionRange.containsRange(markerRange, true));
269
+ }
270
+ return true;
271
+ };
234
272
  return Array
235
- .from(selection.getRanges())
236
- .flatMap(selectionRange => Array.from(writer.model.markers.getMarkersIntersectingRange(selectionRange)))
237
- .filter(marker => this._canPerformMarkerClipboardAction(marker.name, action))
238
- .map((marker) => ({
239
- name: marker.name,
240
- range: marker.getRange()
241
- }));
273
+ .from(markersInRanges)
274
+ .filter(isSelectionMarkerCopyable)
275
+ .map((copyableMarker) => {
276
+ // During `dragstart` event original marker is still present in tree.
277
+ // It is removed after the clipboard drop event, so none of the copied markers are inserted at the end.
278
+ // It happens because there already markers with specified `marker.name` when clipboard is trying to insert data
279
+ // and it aborts inserting.
280
+ const name = action === 'dragstart' ? this._getUniqueMarkerName(copyableMarker.name) : copyableMarker.name;
281
+ return {
282
+ name,
283
+ range: copyableMarker.getRange()
284
+ };
285
+ });
242
286
  }
243
287
  /**
244
- * Picks all markers from markers map that can be copied.
288
+ * Picks all markers from markers map that can be pasted.
289
+ * If `duplicateOnPaste` is `true`, it regenerates their IDs to ensure uniqueness.
290
+ * If marker is not registered, it will be kept in the array anyway.
245
291
  *
246
292
  * @param markers Object that maps marker name to corresponding range.
247
293
  * @param action Type of clipboard action. If null then checks only if marker is registered as copyable.
248
294
  */
249
- _getCopyableMarkersFromRangeMap(markers, action = null) {
295
+ _getPasteMarkersFromRangeMap(markers, action = null) {
296
+ const { model } = this.editor;
250
297
  const entries = markers instanceof Map ? Array.from(markers.entries()) : Object.entries(markers);
251
- return entries
252
- .map(([markerName, range]) => ({
253
- name: markerName,
254
- range
255
- }))
256
- .filter(marker => this._canPerformMarkerClipboardAction(marker.name, action));
298
+ return entries.flatMap(([markerName, range]) => {
299
+ if (!this._hasMarkerConfiguration(markerName)) {
300
+ return [
301
+ {
302
+ name: markerName,
303
+ range
304
+ }
305
+ ];
306
+ }
307
+ if (this._isMarkerCopyable(markerName, action)) {
308
+ const copyMarkerConfig = this._getMarkerClipboardConfig(markerName);
309
+ const isInGraveyard = model.markers.has(markerName) &&
310
+ model.markers.get(markerName).getRange().root.rootName === '$graveyard';
311
+ if (copyMarkerConfig.duplicateOnPaste || isInGraveyard) {
312
+ markerName = this._getUniqueMarkerName(markerName);
313
+ }
314
+ return [
315
+ {
316
+ name: markerName,
317
+ range
318
+ }
319
+ ];
320
+ }
321
+ return [];
322
+ });
257
323
  }
258
324
  /**
259
325
  * Inserts specified array of fake markers elements to document and assigns them `type` and `name` attributes.
@@ -313,14 +379,23 @@ export default class ClipboardMarkersUtils extends Plugin {
313
379
  //
314
380
  // The easiest way to bypass this issue is to rename already existing in map nodes and
315
381
  // set them new unique name.
382
+ let skipAssign = false;
316
383
  if (prevFakeMarker && prevFakeMarker.start && prevFakeMarker.end) {
317
- acc[this._getUniqueMarkerName(fakeMarker.name)] = acc[fakeMarker.name];
384
+ const config = this._getMarkerClipboardConfig(fakeMarker.name);
385
+ if (config.duplicateOnPaste) {
386
+ acc[this._getUniqueMarkerName(fakeMarker.name)] = acc[fakeMarker.name];
387
+ }
388
+ else {
389
+ skipAssign = true;
390
+ }
318
391
  prevFakeMarker = null;
319
392
  }
320
- acc[fakeMarker.name] = {
321
- ...prevFakeMarker,
322
- [fakeMarker.type]: position
323
- };
393
+ if (!skipAssign) {
394
+ acc[fakeMarker.name] = {
395
+ ...prevFakeMarker,
396
+ [fakeMarker.type]: position
397
+ };
398
+ }
324
399
  if (fakeMarker.markerElement) {
325
400
  writer.remove(fakeMarker.markerElement);
326
401
  }
@@ -147,11 +147,13 @@ export default class ClipboardPipeline extends Plugin {
147
147
  */
148
148
  _fireOutputTransformationEvent(dataTransfer, selection, method) {
149
149
  const clipboardMarkersUtils = this.editor.plugins.get('ClipboardMarkersUtils');
150
- const documentFragment = clipboardMarkersUtils._copySelectedFragmentWithMarkers(method, selection);
151
- this.fire('outputTransformation', {
152
- dataTransfer,
153
- content: documentFragment,
154
- method
150
+ this.editor.model.enqueueChange({ isUndoable: method === 'cut' }, () => {
151
+ const documentFragment = clipboardMarkersUtils._copySelectedFragmentWithMarkers(method, selection);
152
+ this.fire('outputTransformation', {
153
+ dataTransfer,
154
+ content: documentFragment,
155
+ method
156
+ });
155
157
  });
156
158
  }
157
159
  /**
@@ -227,10 +229,7 @@ export default class ClipboardPipeline extends Plugin {
227
229
  });
228
230
  }, { priority: 'low' });
229
231
  this.listenTo(this, 'contentInsertion', (evt, data) => {
230
- clipboardMarkersUtils._setUniqueMarkerNamesInFragment(data.content);
231
- }, { priority: 'highest' });
232
- this.listenTo(this, 'contentInsertion', (evt, data) => {
233
- data.resultRange = model.insertContent(data.content);
232
+ data.resultRange = clipboardMarkersUtils._pasteFragmentWithMarkers(data.content);
234
233
  }, { priority: 'low' });
235
234
  }
236
235
  /**
package/src/index.d.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  */
8
8
  export { default as Clipboard } from './clipboard.js';
9
9
  export { default as ClipboardPipeline, type ClipboardContentInsertionEvent, type ClipboardContentInsertionData, type ClipboardInputTransformationEvent, type ClipboardInputTransformationData, type ClipboardOutputTransformationEvent, type ClipboardOutputTransformationData, type ViewDocumentClipboardOutputEvent } from './clipboardpipeline.js';
10
- export { default as ClipboardMarkersUtils, type ClipboardMarkerRestrictionsPreset, type ClipboardMarkerRestrictedAction } from './clipboardmarkersutils.js';
10
+ export { default as ClipboardMarkersUtils, type ClipboardMarkerRestrictedAction, type ClipboardMarkerConfiguration } from './clipboardmarkersutils.js';
11
11
  export type { ClipboardEventData } from './clipboardobserver.js';
12
12
  export { default as DragDrop } from './dragdrop.js';
13
13
  export { default as PastePlainText } from './pasteplaintext.js';