@ckeditor/ckeditor5-clipboard 41.1.0 → 41.3.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.
Files changed (71) hide show
  1. package/dist/content-index.css +4 -0
  2. package/dist/editor-index.css +23 -0
  3. package/dist/index.css +42 -0
  4. package/dist/index.css.map +1 -0
  5. package/dist/index.js +2175 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/types/augmentation.d.ts +16 -0
  8. package/dist/types/clipboard.d.ts +36 -0
  9. package/dist/types/clipboardmarkersutils.d.ts +186 -0
  10. package/dist/types/clipboardobserver.d.ts +312 -0
  11. package/dist/types/clipboardpipeline.d.ts +265 -0
  12. package/dist/types/dragdrop.d.ts +102 -0
  13. package/dist/types/dragdropblocktoolbar.d.ts +47 -0
  14. package/dist/types/dragdroptarget.d.ts +94 -0
  15. package/dist/types/index.d.ts +17 -0
  16. package/dist/types/lineview.d.ts +45 -0
  17. package/dist/types/pasteplaintext.d.ts +28 -0
  18. package/dist/types/utils/normalizeclipboarddata.d.ts +15 -0
  19. package/dist/types/utils/plaintexttohtml.d.ts +14 -0
  20. package/dist/types/utils/viewtoplaintext.d.ts +15 -0
  21. package/lang/contexts.json +5 -0
  22. package/lang/translations/ar.po +30 -0
  23. package/lang/translations/bg.po +30 -0
  24. package/lang/translations/bn.po +30 -0
  25. package/lang/translations/ca.po +30 -0
  26. package/lang/translations/cs.po +30 -0
  27. package/lang/translations/da.po +30 -0
  28. package/lang/translations/de.po +30 -0
  29. package/lang/translations/el.po +30 -0
  30. package/lang/translations/en.po +30 -0
  31. package/lang/translations/es.po +30 -0
  32. package/lang/translations/et.po +30 -0
  33. package/lang/translations/fi.po +30 -0
  34. package/lang/translations/fr.po +30 -0
  35. package/lang/translations/he.po +30 -0
  36. package/lang/translations/hi.po +30 -0
  37. package/lang/translations/hr.po +30 -0
  38. package/lang/translations/hu.po +30 -0
  39. package/lang/translations/id.po +30 -0
  40. package/lang/translations/it.po +30 -0
  41. package/lang/translations/ja.po +30 -0
  42. package/lang/translations/ko.po +30 -0
  43. package/lang/translations/lt.po +30 -0
  44. package/lang/translations/lv.po +30 -0
  45. package/lang/translations/ms.po +30 -0
  46. package/lang/translations/nl.po +30 -0
  47. package/lang/translations/no.po +30 -0
  48. package/lang/translations/pl.po +30 -0
  49. package/lang/translations/pt-br.po +30 -0
  50. package/lang/translations/pt.po +30 -0
  51. package/lang/translations/ro.po +30 -0
  52. package/lang/translations/ru.po +30 -0
  53. package/lang/translations/sk.po +30 -0
  54. package/lang/translations/sr.po +30 -0
  55. package/lang/translations/sv.po +30 -0
  56. package/lang/translations/th.po +30 -0
  57. package/lang/translations/tr.po +30 -0
  58. package/lang/translations/uk.po +30 -0
  59. package/lang/translations/vi.po +30 -0
  60. package/lang/translations/zh-cn.po +30 -0
  61. package/lang/translations/zh.po +30 -0
  62. package/package.json +7 -6
  63. package/src/augmentation.d.ts +2 -1
  64. package/src/clipboard.d.ts +6 -1
  65. package/src/clipboard.js +26 -1
  66. package/src/clipboardmarkersutils.d.ts +186 -0
  67. package/src/clipboardmarkersutils.js +424 -0
  68. package/src/clipboardpipeline.d.ts +5 -0
  69. package/src/clipboardpipeline.js +14 -2
  70. package/src/index.d.ts +2 -1
  71. package/src/index.js +1 -0
@@ -0,0 +1,424 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+ /**
6
+ * @module clipboard/clipboardmarkersutils
7
+ */
8
+ import { mapValues } from 'lodash-es';
9
+ import { uid } from '@ckeditor/ckeditor5-utils';
10
+ import { Plugin } from '@ckeditor/ckeditor5-core';
11
+ import { Range } from '@ckeditor/ckeditor5-engine';
12
+ /**
13
+ * Part of the clipboard logic. Responsible for collecting markers from selected fragments
14
+ * and restoring them with proper positions in pasted elements.
15
+ *
16
+ * @internal
17
+ */
18
+ export default class ClipboardMarkersUtils extends Plugin {
19
+ constructor() {
20
+ super(...arguments);
21
+ /**
22
+ * Map of marker names that can be copied.
23
+ *
24
+ * @internal
25
+ */
26
+ this._markersToCopy = new Map();
27
+ }
28
+ /**
29
+ * @inheritDoc
30
+ */
31
+ static get pluginName() {
32
+ return 'ClipboardMarkersUtils';
33
+ }
34
+ /**
35
+ * Registers marker name as copyable in clipboard pipeline.
36
+ *
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.
39
+ * @internal
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
+ }
68
+ }
69
+ /**
70
+ * Performs copy markers on provided selection and paste it to fragment returned from `getCopiedFragment`.
71
+ *
72
+ * 1. Picks all markers in provided selection.
73
+ * 2. Inserts fake markers to document.
74
+ * 3. Gets copied selection fragment from document.
75
+ * 4. Removes fake elements from fragment and document.
76
+ * 5. Inserts markers in the place of removed fake markers.
77
+ *
78
+ * Due to selection modification, when inserting items, `getCopiedFragment` must *always* operate on `writer.model.document.selection'.
79
+ * Do not use any other custom selection object within callback, as this will lead to out-of-bounds exceptions in rare scenarios.
80
+ *
81
+ * @param action Type of clipboard action.
82
+ * @param writer An instance of the model writer.
83
+ * @param selection Selection to be checked.
84
+ * @param getCopiedFragment Callback that performs copy of selection and returns it as fragment.
85
+ * @internal
86
+ */
87
+ _copySelectedFragmentWithMarkers(action, selection, getCopiedFragment = writer => writer.model.getSelectedContent(writer.model.document.selection)) {
88
+ return this.editor.model.change(writer => {
89
+ const oldSelection = writer.model.document.selection;
90
+ // In some scenarios, such like in drag & drop, passed `selection` parameter is not actually
91
+ // the same `selection` as the `writer.model.document.selection` which means that `_insertFakeMarkersToSelection`
92
+ // is not affecting passed `selection` `start` and `end` positions but rather modifies `writer.model.document.selection`.
93
+ //
94
+ // It is critical due to fact that when we have selection that starts [ 0, 0 ] and ends at [ 1, 0 ]
95
+ // and after inserting fake marker it will point to such marker instead of new widget position at start: [ 1, 0 ] end: [2, 0 ].
96
+ // `writer.insert` modifies only original `writer.model.document.selection`.
97
+ writer.setSelection(selection);
98
+ const sourceSelectionInsertedMarkers = this._insertFakeMarkersIntoSelection(writer, writer.model.document.selection, action);
99
+ const fragment = getCopiedFragment(writer);
100
+ const fakeMarkersRangesInsideRange = this._removeFakeMarkersInsideElement(writer, fragment);
101
+ // <fake-marker> [Foo] Bar</fake-marker>
102
+ // ^ ^
103
+ // In `_insertFakeMarkersIntoSelection` call we inserted fake marker just before first element.
104
+ // The problem is that the first element can be start position of selection so insertion fake-marker
105
+ // before such element shifts selection (so selection that was at [0, 0] now is at [0, 1]).
106
+ // It means that inserted fake-marker is no longer present inside such selection and is orphaned.
107
+ // This function checks special case of such problem. Markers that are orphaned at the start position
108
+ // and end position in the same time. Basically it means that they overlaps whole element.
109
+ for (const [markerName, elements] of Object.entries(sourceSelectionInsertedMarkers)) {
110
+ fakeMarkersRangesInsideRange[markerName] || (fakeMarkersRangesInsideRange[markerName] = writer.createRangeIn(fragment));
111
+ for (const element of elements) {
112
+ writer.remove(element);
113
+ }
114
+ }
115
+ fragment.markers.clear();
116
+ for (const [markerName, range] of Object.entries(fakeMarkersRangesInsideRange)) {
117
+ fragment.markers.set(markerName, range);
118
+ }
119
+ // Revert back selection to previous one.
120
+ writer.setSelection(oldSelection);
121
+ return fragment;
122
+ });
123
+ }
124
+ /**
125
+ * Performs paste of markers on already pasted element.
126
+ *
127
+ * 1. Inserts fake markers that are present in fragment element (such fragment will be processed in `getPastedDocumentElement`).
128
+ * 2. Calls `getPastedDocumentElement` and gets element that is inserted into root model.
129
+ * 3. Removes all fake markers present in transformed element.
130
+ * 4. Inserts new markers with removed fake markers ranges into pasted fragment.
131
+ *
132
+ * There are multiple edge cases that have to be considered before calling this function:
133
+ *
134
+ * * `markers` are inserted into the same element that must be later transformed inside `getPastedDocumentElement`.
135
+ * * Fake marker elements inside `getPastedDocumentElement` can be cloned, but their ranges cannot overlap.
136
+ *
137
+ * @param action Type of clipboard action.
138
+ * @param markers Object that maps marker name to corresponding range.
139
+ * @param getPastedDocumentElement Getter used to get target markers element.
140
+ * @internal
141
+ */
142
+ _pasteMarkersIntoTransformedElement(markers, getPastedDocumentElement) {
143
+ const copyableMarkers = this._getCopyableMarkersFromRangeMap(markers);
144
+ return this.editor.model.change(writer => {
145
+ const sourceFragmentFakeMarkers = this._insertFakeMarkersElements(writer, copyableMarkers);
146
+ const transformedElement = getPastedDocumentElement(writer);
147
+ const removedFakeMarkers = this._removeFakeMarkersInsideElement(writer, transformedElement);
148
+ // Cleanup fake markers inserted into transformed element.
149
+ for (const element of Object.values(sourceFragmentFakeMarkers).flat()) {
150
+ writer.remove(element);
151
+ }
152
+ 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
+ });
159
+ }
160
+ return transformedElement;
161
+ });
162
+ }
163
+ /**
164
+ * In some situations we have to perform copy on selected fragment with certain markers. This function allows to temporarily bypass
165
+ * restrictions on markers that we want to copy.
166
+ *
167
+ * This function executes `executor()` callback. For the duration of the callback, if the clipboard pipeline is used to copy
168
+ * content, markers with the specified name will be copied to the clipboard as well.
169
+ *
170
+ * @param markerName Which markers should be copied.
171
+ * @param executor Callback executed.
172
+ * @internal
173
+ */
174
+ _forceMarkersCopy(markerName, executor) {
175
+ const before = this._markersToCopy.get(markerName);
176
+ this._markersToCopy.set(markerName, this._mapRestrictionPresetToActions('always'));
177
+ executor();
178
+ if (before) {
179
+ this._markersToCopy.set(markerName, before);
180
+ }
181
+ else {
182
+ this._markersToCopy.delete(markerName);
183
+ }
184
+ }
185
+ /**
186
+ * Checks if marker can be copied.
187
+ *
188
+ * @param markerName Name of checked marker.
189
+ * @param action Type of clipboard action. If null then checks only if marker is registered as copyable.
190
+ * @internal
191
+ */
192
+ _canPerformMarkerClipboardAction(markerName, action) {
193
+ const [markerNamePrefix] = markerName.split(':');
194
+ if (!action) {
195
+ return this._markersToCopy.has(markerNamePrefix);
196
+ }
197
+ const possibleActions = this._markersToCopy.get(markerNamePrefix) || [];
198
+ return possibleActions.includes(action);
199
+ }
200
+ /**
201
+ * Changes marker names for markers stored in given document fragment so that they are unique.
202
+ *
203
+ * @param fragment
204
+ * @internal
205
+ */
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
+ }
212
+ }
213
+ /**
214
+ * First step of copying markers. It looks for markers intersecting with given selection and inserts `$marker` elements
215
+ * at positions where document markers start or end. This way `$marker` elements can be easily copied together with
216
+ * the rest of the content of the selection.
217
+ *
218
+ * @param writer An instance of the model writer.
219
+ * @param selection Selection to be checked.
220
+ * @param action Type of clipboard action.
221
+ */
222
+ _insertFakeMarkersIntoSelection(writer, selection, action) {
223
+ const copyableMarkers = this._getCopyableMarkersFromSelection(writer, selection, action);
224
+ return this._insertFakeMarkersElements(writer, copyableMarkers);
225
+ }
226
+ /**
227
+ * Returns array of markers that can be copied in specified selection.
228
+ *
229
+ * @param writer An instance of the model writer.
230
+ * @param selection Selection which will be checked.
231
+ * @param action Type of clipboard action. If null then checks only if marker is registered as copyable.
232
+ */
233
+ _getCopyableMarkersFromSelection(writer, selection, action) {
234
+ 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
+ }));
242
+ }
243
+ /**
244
+ * Picks all markers from markers map that can be copied.
245
+ *
246
+ * @param markers Object that maps marker name to corresponding range.
247
+ * @param action Type of clipboard action. If null then checks only if marker is registered as copyable.
248
+ */
249
+ _getCopyableMarkersFromRangeMap(markers, action = null) {
250
+ 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));
257
+ }
258
+ /**
259
+ * Inserts specified array of fake markers elements to document and assigns them `type` and `name` attributes.
260
+ * Fake markers elements are used to calculate position of markers on pasted fragment that were transformed during
261
+ * steps between copy and paste.
262
+ *
263
+ * @param writer An instance of the model writer.
264
+ * @param markers Array of markers that will be inserted.
265
+ */
266
+ _insertFakeMarkersElements(writer, markers) {
267
+ const mappedMarkers = {};
268
+ const sortedMarkers = markers
269
+ .flatMap(marker => {
270
+ const { start, end } = marker.range;
271
+ return [
272
+ { position: start, marker, type: 'start' },
273
+ { position: end, marker, type: 'end' }
274
+ ];
275
+ })
276
+ // Markers position is sorted backwards to ensure that the insertion of fake markers will not change
277
+ // the position of the next markers.
278
+ .sort(({ position: posA }, { position: posB }) => posA.isBefore(posB) ? 1 : -1);
279
+ for (const { position, marker, type } of sortedMarkers) {
280
+ const fakeMarker = writer.createElement('$marker', {
281
+ 'data-name': marker.name,
282
+ 'data-type': type
283
+ });
284
+ if (!mappedMarkers[marker.name]) {
285
+ mappedMarkers[marker.name] = [];
286
+ }
287
+ mappedMarkers[marker.name].push(fakeMarker);
288
+ writer.insert(fakeMarker, position);
289
+ }
290
+ return mappedMarkers;
291
+ }
292
+ /**
293
+ * Removes all `$marker` elements from the given document fragment.
294
+ *
295
+ * Returns an object where keys are marker names, and values are ranges corresponding to positions
296
+ * where `$marker` elements were inserted.
297
+ *
298
+ * If the document fragment had only one `$marker` element for given marker (start or end) the other boundary is set automatically
299
+ * (to the end or start of the document fragment, respectively).
300
+ *
301
+ * @param writer An instance of the model writer.
302
+ * @param rootElement The element to be checked.
303
+ */
304
+ _removeFakeMarkersInsideElement(writer, rootElement) {
305
+ const fakeMarkersElements = this._getAllFakeMarkersFromElement(writer, rootElement);
306
+ const fakeMarkersRanges = fakeMarkersElements.reduce((acc, fakeMarker) => {
307
+ const position = fakeMarker.markerElement && writer.createPositionBefore(fakeMarker.markerElement);
308
+ let prevFakeMarker = acc[fakeMarker.name];
309
+ // Handle scenario when tables clone cells with the same fake node. Example:
310
+ //
311
+ // <cell><fake-marker-a></cell> <cell><fake-marker-a></cell> <cell><fake-marker-a></cell>
312
+ // ^ cloned ^ cloned
313
+ //
314
+ // The easiest way to bypass this issue is to rename already existing in map nodes and
315
+ // set them new unique name.
316
+ if (prevFakeMarker && prevFakeMarker.start && prevFakeMarker.end) {
317
+ acc[this._getUniqueMarkerName(fakeMarker.name)] = acc[fakeMarker.name];
318
+ prevFakeMarker = null;
319
+ }
320
+ acc[fakeMarker.name] = {
321
+ ...prevFakeMarker,
322
+ [fakeMarker.type]: position
323
+ };
324
+ if (fakeMarker.markerElement) {
325
+ writer.remove(fakeMarker.markerElement);
326
+ }
327
+ return acc;
328
+ }, {});
329
+ // We cannot construct ranges directly in previous reduce because element ranges can overlap.
330
+ // In other words lets assume we have such scenario:
331
+ // <fake-marker-start /> <paragraph /> <fake-marker-2-start /> <fake-marker-end /> <fake-marker-2-end />
332
+ //
333
+ // We have to remove `fake-marker-start` firstly and then remove `fake-marker-2-start`.
334
+ // Removal of `fake-marker-2-start` affects `fake-marker-end` position so we cannot create
335
+ // connection between `fake-marker-start` and `fake-marker-end` without iterating whole set firstly.
336
+ return mapValues(fakeMarkersRanges, range => new Range(range.start || writer.createPositionFromPath(rootElement, [0]), range.end || writer.createPositionAt(rootElement, 'end')));
337
+ }
338
+ /**
339
+ * Returns array that contains list of fake markers with corresponding `$marker` elements.
340
+ *
341
+ * For each marker, there can be two `$marker` elements or only one (if the document fragment contained
342
+ * only the beginning or only the end of a marker).
343
+ *
344
+ * @param writer An instance of the model writer.
345
+ * @param rootElement The element to be checked.
346
+ */
347
+ _getAllFakeMarkersFromElement(writer, rootElement) {
348
+ const foundFakeMarkers = Array
349
+ .from(writer.createRangeIn(rootElement))
350
+ .flatMap(({ item }) => {
351
+ if (!item.is('element', '$marker')) {
352
+ return [];
353
+ }
354
+ const name = item.getAttribute('data-name');
355
+ const type = item.getAttribute('data-type');
356
+ return [
357
+ {
358
+ markerElement: item,
359
+ name,
360
+ type
361
+ }
362
+ ];
363
+ });
364
+ const prependFakeMarkers = [];
365
+ const appendFakeMarkers = [];
366
+ for (const fakeMarker of foundFakeMarkers) {
367
+ if (fakeMarker.type === 'end') {
368
+ // <fake-marker> [ phrase</fake-marker> phrase ]
369
+ // ^
370
+ // Handle case when marker is just before start of selection.
371
+ // Only end marker is inside selection.
372
+ const hasMatchingStartMarker = foundFakeMarkers.some(otherFakeMarker => otherFakeMarker.name === fakeMarker.name && otherFakeMarker.type === 'start');
373
+ if (!hasMatchingStartMarker) {
374
+ prependFakeMarkers.push({
375
+ markerElement: null,
376
+ name: fakeMarker.name,
377
+ type: 'start'
378
+ });
379
+ }
380
+ }
381
+ if (fakeMarker.type === 'start') {
382
+ // [<fake-marker>phrase]</fake-marker>
383
+ // ^
384
+ // Handle case when fake marker is after selection.
385
+ // Only start marker is inside selection.
386
+ const hasMatchingEndMarker = foundFakeMarkers.some(otherFakeMarker => otherFakeMarker.name === fakeMarker.name && otherFakeMarker.type === 'end');
387
+ if (!hasMatchingEndMarker) {
388
+ appendFakeMarkers.unshift({
389
+ markerElement: null,
390
+ name: fakeMarker.name,
391
+ type: 'end'
392
+ });
393
+ }
394
+ }
395
+ }
396
+ return [
397
+ ...prependFakeMarkers,
398
+ ...foundFakeMarkers,
399
+ ...appendFakeMarkers
400
+ ];
401
+ }
402
+ /**
403
+ * When copy of markers occurs we have to make sure that pasted markers have different names
404
+ * than source markers. This functions helps with assigning unique part to marker name to
405
+ * prevent duplicated markers error.
406
+ *
407
+ * @param name Name of marker
408
+ */
409
+ _getUniqueMarkerName(name) {
410
+ const parts = name.split(':');
411
+ const newId = uid().substring(1, 6);
412
+ // It looks like the marker already is UID marker so in this scenario just swap
413
+ // last part of marker name and assign new UID.
414
+ //
415
+ // example: comment:{ threadId }:{ id } => comment:{ threadId }:{ newId }
416
+ if (parts.length === 3) {
417
+ return `${parts.slice(0, 2).join(':')}:${newId}`;
418
+ }
419
+ // Assign new segment to marker name with id.
420
+ //
421
+ // example: comment => comment:{ newId }
422
+ return `${parts.join(':')}:${newId}`;
423
+ }
424
+ }
@@ -7,6 +7,7 @@
7
7
  */
8
8
  import { Plugin } from '@ckeditor/ckeditor5-core';
9
9
  import type { DataTransfer, DocumentFragment, Range, ViewDocumentFragment, ViewRange, Selection, DocumentSelection } from '@ckeditor/ckeditor5-engine';
10
+ import ClipboardMarkersUtils from './clipboardmarkersutils.js';
10
11
  /**
11
12
  * The clipboard pipeline feature. It is responsible for intercepting the `paste` and `drop` events and
12
13
  * passing the pasted content through a series of events in order to insert it into the editor's content.
@@ -73,6 +74,10 @@ export default class ClipboardPipeline extends Plugin {
73
74
  * @inheritDoc
74
75
  */
75
76
  static get pluginName(): "ClipboardPipeline";
77
+ /**
78
+ * @inheritDoc
79
+ */
80
+ static get requires(): readonly [typeof ClipboardMarkersUtils];
76
81
  /**
77
82
  * @inheritDoc
78
83
  */
@@ -11,6 +11,7 @@ import ClipboardObserver from './clipboardobserver.js';
11
11
  import plainTextToHtml from './utils/plaintexttohtml.js';
12
12
  import normalizeClipboardHtml from './utils/normalizeclipboarddata.js';
13
13
  import viewToPlainText from './utils/viewtoplaintext.js';
14
+ import ClipboardMarkersUtils from './clipboardmarkersutils.js';
14
15
  // Input pipeline events overview:
15
16
  //
16
17
  // ┌──────────────────────┐ ┌──────────────────────┐
@@ -123,6 +124,12 @@ export default class ClipboardPipeline extends Plugin {
123
124
  static get pluginName() {
124
125
  return 'ClipboardPipeline';
125
126
  }
127
+ /**
128
+ * @inheritDoc
129
+ */
130
+ static get requires() {
131
+ return [ClipboardMarkersUtils];
132
+ }
126
133
  /**
127
134
  * @inheritDoc
128
135
  */
@@ -139,10 +146,11 @@ export default class ClipboardPipeline extends Plugin {
139
146
  * @internal
140
147
  */
141
148
  _fireOutputTransformationEvent(dataTransfer, selection, method) {
142
- const content = this.editor.model.getSelectedContent(selection);
149
+ const clipboardMarkersUtils = this.editor.plugins.get('ClipboardMarkersUtils');
150
+ const documentFragment = clipboardMarkersUtils._copySelectedFragmentWithMarkers(method, selection);
143
151
  this.fire('outputTransformation', {
144
152
  dataTransfer,
145
- content,
153
+ content: documentFragment,
146
154
  method
147
155
  });
148
156
  }
@@ -154,6 +162,7 @@ export default class ClipboardPipeline extends Plugin {
154
162
  const model = editor.model;
155
163
  const view = editor.editing.view;
156
164
  const viewDocument = view.document;
165
+ const clipboardMarkersUtils = this.editor.plugins.get('ClipboardMarkersUtils');
157
166
  // Pasting is disabled when selection is in non-editable place.
158
167
  // Dropping is disabled in drag and drop handler.
159
168
  this.listenTo(viewDocument, 'clipboardInput', (evt, data) => {
@@ -217,6 +226,9 @@ export default class ClipboardPipeline extends Plugin {
217
226
  });
218
227
  });
219
228
  }, { priority: 'low' });
229
+ this.listenTo(this, 'contentInsertion', (evt, data) => {
230
+ clipboardMarkersUtils._setUniqueMarkerNamesInFragment(data.content);
231
+ }, { priority: 'highest' });
220
232
  this.listenTo(this, 'contentInsertion', (evt, data) => {
221
233
  data.resultRange = model.insertContent(data.content);
222
234
  }, { priority: 'low' });
package/src/index.d.ts CHANGED
@@ -6,7 +6,8 @@
6
6
  * @module clipboard
7
7
  */
8
8
  export { default as Clipboard } from './clipboard.js';
9
- export { default as ClipboardPipeline, type ClipboardContentInsertionEvent, type ClipboardInputTransformationEvent, type ClipboardInputTransformationData, type ClipboardOutputTransformationEvent, type ClipboardOutputTransformationData, type ViewDocumentClipboardOutputEvent } from './clipboardpipeline.js';
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
11
  export type { ClipboardEventData } from './clipboardobserver.js';
11
12
  export { default as DragDrop } from './dragdrop.js';
12
13
  export { default as PastePlainText } from './pasteplaintext.js';
package/src/index.js CHANGED
@@ -7,6 +7,7 @@
7
7
  */
8
8
  export { default as Clipboard } from './clipboard.js';
9
9
  export { default as ClipboardPipeline } from './clipboardpipeline.js';
10
+ export { default as ClipboardMarkersUtils } from './clipboardmarkersutils.js';
10
11
  export { default as DragDrop } from './dragdrop.js';
11
12
  export { default as PastePlainText } from './pasteplaintext.js';
12
13
  export { default as DragDropTarget } from './dragdroptarget.js';