@ckeditor/ckeditor5-clipboard 39.0.2 → 40.1.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.
@@ -6,7 +6,7 @@
6
6
  * @module clipboard/dragdroptarget
7
7
  */
8
8
  import { Plugin } from '@ckeditor/ckeditor5-core';
9
- import { type Range, type ViewElement, type ViewRange } from '@ckeditor/ckeditor5-engine';
9
+ import { type Range, type LiveRange, type ViewElement, type ViewRange } from '@ckeditor/ckeditor5-engine';
10
10
  /**
11
11
  * Part of the Drag and Drop handling. Responsible for finding and displaying the drop target.
12
12
  *
@@ -56,13 +56,13 @@ export default class DragDropTarget extends Plugin {
56
56
  *
57
57
  * @internal
58
58
  */
59
- updateDropMarker(targetViewElement: ViewElement, targetViewRanges: Array<ViewRange> | null, clientX: number, clientY: number, blockMode: boolean): void;
59
+ updateDropMarker(targetViewElement: ViewElement, targetViewRanges: Array<ViewRange> | null, clientX: number, clientY: number, blockMode: boolean, draggedRange: LiveRange | null): void;
60
60
  /**
61
61
  * Finds the final drop target range.
62
62
  *
63
63
  * @internal
64
64
  */
65
- getFinalDropRange(targetViewElement: ViewElement, targetViewRanges: Array<ViewRange> | null, clientX: number, clientY: number, blockMode: boolean): Range | null;
65
+ getFinalDropRange(targetViewElement: ViewElement, targetViewRanges: Array<ViewRange> | null, clientX: number, clientY: number, blockMode: boolean, draggedRange: LiveRange | null): Range | null;
66
66
  /**
67
67
  * Removes the drop target marker.
68
68
  *
@@ -5,7 +5,6 @@
5
5
  /**
6
6
  * @module clipboard/dragdroptarget
7
7
  */
8
- /* istanbul ignore file -- @preserve */
9
8
  import { Plugin } from '@ckeditor/ckeditor5-core';
10
9
  import { global, Rect, DomEmitterMixin, delay, ResizeObserver } from '@ckeditor/ckeditor5-utils';
11
10
  import LineView from './lineview';
@@ -79,21 +78,26 @@ export default class DragDropTarget extends Plugin {
79
78
  *
80
79
  * @internal
81
80
  */
82
- updateDropMarker(targetViewElement, targetViewRanges, clientX, clientY, blockMode) {
81
+ updateDropMarker(targetViewElement, targetViewRanges, clientX, clientY, blockMode, draggedRange) {
83
82
  this.removeDropMarkerDelayed.cancel();
84
- const targetRange = findDropTargetRange(this.editor, targetViewElement, targetViewRanges, clientX, clientY, blockMode);
85
- /* istanbul ignore else -- @preserve */
86
- if (targetRange) {
87
- this._updateDropMarkerThrottled(targetRange);
83
+ const targetRange = findDropTargetRange(this.editor, targetViewElement, targetViewRanges, clientX, clientY, blockMode, draggedRange);
84
+ /* istanbul ignore next -- @preserve */
85
+ if (!targetRange) {
86
+ return;
88
87
  }
88
+ if (draggedRange && draggedRange.containsRange(targetRange)) {
89
+ // Target range is inside the dragged range.
90
+ return this.removeDropMarker();
91
+ }
92
+ this._updateDropMarkerThrottled(targetRange);
89
93
  }
90
94
  /**
91
95
  * Finds the final drop target range.
92
96
  *
93
97
  * @internal
94
98
  */
95
- getFinalDropRange(targetViewElement, targetViewRanges, clientX, clientY, blockMode) {
96
- const targetRange = findDropTargetRange(this.editor, targetViewElement, targetViewRanges, clientX, clientY, blockMode);
99
+ getFinalDropRange(targetViewElement, targetViewRanges, clientX, clientY, blockMode, draggedRange) {
100
+ const targetRange = findDropTargetRange(this.editor, targetViewElement, targetViewRanges, clientX, clientY, blockMode, draggedRange);
97
101
  // The dragging markers must be removed after searching for the target range because sometimes
98
102
  // the target lands on the marker itself.
99
103
  this.removeDropMarker();
@@ -247,7 +251,7 @@ export default class DragDropTarget extends Plugin {
247
251
  /**
248
252
  * Returns fixed selection range for given position and target element.
249
253
  */
250
- function findDropTargetRange(editor, targetViewElement, targetViewRanges, clientX, clientY, blockMode) {
254
+ function findDropTargetRange(editor, targetViewElement, targetViewRanges, clientX, clientY, blockMode, draggedRange) {
251
255
  const model = editor.model;
252
256
  const mapper = editor.editing.mapper;
253
257
  const targetModelElement = getClosestMappedModelElement(editor, targetViewElement);
@@ -255,15 +259,20 @@ function findDropTargetRange(editor, targetViewElement, targetViewRanges, client
255
259
  while (modelElement) {
256
260
  if (!blockMode) {
257
261
  if (model.schema.checkChild(modelElement, '$text')) {
258
- const targetViewPosition = targetViewRanges ? targetViewRanges[0].start : null;
259
- const targetModelPosition = targetViewPosition ? mapper.toModelPosition(targetViewPosition) : null;
260
- if (targetModelPosition) {
261
- if (model.schema.checkChild(targetModelPosition, '$text')) {
262
- return model.createRange(targetModelPosition);
263
- }
264
- else if (targetViewPosition) {
265
- // This is the case of dropping inside a span wrapper of an inline image.
266
- return findDropTargetRangeForElement(editor, getClosestMappedModelElement(editor, targetViewPosition.parent), clientX, clientY);
262
+ if (targetViewRanges) {
263
+ const targetViewPosition = targetViewRanges[0].start;
264
+ const targetModelPosition = mapper.toModelPosition(targetViewPosition);
265
+ const canDropOnPosition = !draggedRange || Array
266
+ .from(draggedRange.getItems())
267
+ .every(item => model.schema.checkChild(targetModelPosition, item));
268
+ if (canDropOnPosition) {
269
+ if (model.schema.checkChild(targetModelPosition, '$text')) {
270
+ return model.createRange(targetModelPosition);
271
+ }
272
+ else if (targetViewPosition) {
273
+ // This is the case of dropping inside a span wrapper of an inline image.
274
+ return findDropTargetRangeForElement(editor, getClosestMappedModelElement(editor, targetViewPosition.parent), clientX, clientY);
275
+ }
267
276
  }
268
277
  }
269
278
  }
@@ -279,6 +288,9 @@ function findDropTargetRange(editor, targetViewElement, targetViewRanges, client
279
288
  .filter((node) => node.is('element') && !isFloatingElement(editor, node));
280
289
  let startIndex = 0;
281
290
  let endIndex = childNodes.length;
291
+ if (endIndex == 0) {
292
+ return model.createRange(model.createPositionAt(modelElement, 'end'));
293
+ }
282
294
  while (startIndex < endIndex - 1) {
283
295
  const middleIndex = Math.floor((startIndex + endIndex) / 2);
284
296
  const side = findElementSide(editor, childNodes[middleIndex], clientX, clientY);
@@ -293,7 +305,6 @@ function findDropTargetRange(editor, targetViewElement, targetViewRanges, client
293
305
  }
294
306
  modelElement = modelElement.parent;
295
307
  }
296
- console.warn('none:', targetModelElement.name);
297
308
  return null;
298
309
  }
299
310
  /**
package/src/index.d.ts CHANGED
@@ -6,11 +6,10 @@
6
6
  * @module clipboard
7
7
  */
8
8
  export { default as Clipboard } from './clipboard';
9
- export { default as ClipboardPipeline, type ClipboardContentInsertionEvent, type ClipboardInputTransformationEvent, type ClipboardInputTransformationData, type ViewDocumentClipboardOutputEvent } from './clipboardpipeline';
9
+ export { default as ClipboardPipeline, type ClipboardContentInsertionEvent, type ClipboardInputTransformationEvent, type ClipboardInputTransformationData, type ClipboardOutputTransformationEvent, type ClipboardOutputTransformationData, type ViewDocumentClipboardOutputEvent } from './clipboardpipeline';
10
10
  export type { ClipboardEventData } from './clipboardobserver';
11
11
  export { default as DragDrop } from './dragdrop';
12
12
  export { default as PastePlainText } from './pasteplaintext';
13
- export { default as DragDropExperimental } from './dragdropexperimental';
14
13
  export { default as DragDropTarget } from './dragdroptarget';
15
14
  export { default as DragDropBlockToolbar } from './dragdropblocktoolbar';
16
15
  export type { ViewDocumentClipboardInputEvent, ViewDocumentCopyEvent, ViewDocumentCutEvent } from './clipboardobserver';
package/src/index.js CHANGED
@@ -9,7 +9,6 @@ export { default as Clipboard } from './clipboard';
9
9
  export { default as ClipboardPipeline } from './clipboardpipeline';
10
10
  export { default as DragDrop } from './dragdrop';
11
11
  export { default as PastePlainText } from './pasteplaintext';
12
- export { default as DragDropExperimental } from './dragdropexperimental';
13
12
  export { default as DragDropTarget } from './dragdroptarget';
14
13
  export { default as DragDropBlockToolbar } from './dragdropblocktoolbar';
15
14
  import './augmentation';
@@ -13,6 +13,8 @@
13
13
  */
14
14
  export default function plainTextToHtml(text) {
15
15
  text = text
16
+ // Encode &.
17
+ .replace(/&/g, '&amp;')
16
18
  // Encode <>.
17
19
  .replace(/</g, '&lt;')
18
20
  .replace(/>/g, '&gt;')
@@ -6,6 +6,7 @@
6
6
  // Most `view.ContainerElement` want to be separate by new-line, but some are creating one structure
7
7
  // together (like `<li>`) so it is better to separate them by only one "\n".
8
8
  const smallPaddingElements = ['figcaption', 'li'];
9
+ const listElements = ['ol', 'ul'];
9
10
  /**
10
11
  * Converts {@link module:engine/view/item~Item view item} and all of its children to plain text.
11
12
  *
@@ -13,38 +14,54 @@ const smallPaddingElements = ['figcaption', 'li'];
13
14
  * @returns Plain text representation of `viewItem`.
14
15
  */
15
16
  export default function viewToPlainText(viewItem) {
16
- let text = '';
17
17
  if (viewItem.is('$text') || viewItem.is('$textProxy')) {
18
- // If item is `Text` or `TextProxy` simple take its text data.
19
- text = viewItem.data;
20
- }
21
- else if (viewItem.is('element', 'img') && viewItem.hasAttribute('alt')) {
22
- // Special case for images - use alt attribute if it is provided.
23
- text = viewItem.getAttribute('alt');
24
- }
25
- else if (viewItem.is('element', 'br')) {
26
- // A soft break should be converted into a single line break (#8045).
27
- text = '\n';
28
- }
29
- else {
30
- // Other elements are document fragments, attribute elements or container elements.
31
- // They don't have their own text value, so convert their children.
32
- let prev = null;
33
- for (const child of viewItem.getChildren()) {
34
- const childText = viewToPlainText(child);
35
- // Separate container element children with one or more new-line characters.
36
- if (prev && (prev.is('containerElement') || child.is('containerElement'))) {
37
- if (smallPaddingElements.includes(prev.name) ||
38
- smallPaddingElements.includes(child.name)) {
39
- text += '\n';
40
- }
41
- else {
42
- text += '\n\n';
43
- }
44
- }
45
- text += childText;
46
- prev = child;
47
- }
18
+ return viewItem.data;
19
+ }
20
+ if (viewItem.is('element', 'img') && viewItem.hasAttribute('alt')) {
21
+ return viewItem.getAttribute('alt');
22
+ }
23
+ if (viewItem.is('element', 'br')) {
24
+ return '\n'; // Convert soft breaks to single line break (#8045).
25
+ }
26
+ /**
27
+ * Item is a document fragment, attribute element or container element. It doesn't
28
+ * have it's own text value, so we need to convert its children elements.
29
+ */
30
+ let text = '';
31
+ let prev = null;
32
+ for (const child of viewItem.getChildren()) {
33
+ text += newLinePadding(child, prev) + viewToPlainText(child);
34
+ prev = child;
48
35
  }
49
36
  return text;
50
37
  }
38
+ /**
39
+ * Returns new line padding to prefix the given elements with.
40
+ */
41
+ function newLinePadding(element, previous) {
42
+ if (!previous) {
43
+ // Don't add padding to first elements in a level.
44
+ return '';
45
+ }
46
+ if (element.is('element', 'li') && !element.isEmpty && element.getChild(0).is('containerElement')) {
47
+ // Separate document list items with empty lines.
48
+ return '\n\n';
49
+ }
50
+ if (listElements.includes(element.name) && listElements.includes(previous.name)) {
51
+ /**
52
+ * Because `<ul>` and `<ol>` are AttributeElements, two consecutive lists will not have any padding between
53
+ * them (see the `if` statement below). To fix this, we need to make an exception for this case.
54
+ */
55
+ return '\n\n';
56
+ }
57
+ if (!element.is('containerElement') && !previous.is('containerElement')) {
58
+ // Don't add padding between non-container elements.
59
+ return '';
60
+ }
61
+ if (smallPaddingElements.includes(element.name) || smallPaddingElements.includes(previous.name)) {
62
+ // Add small padding between selected container elements.
63
+ return '\n';
64
+ }
65
+ // Add empty lines between container elements.
66
+ return '\n\n';
67
+ }
@@ -1,101 +0,0 @@
1
- /**
2
- * @license Copyright (c) 2003-2023, 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/dragdropexperimental
7
- */
8
- import { Plugin } from '@ckeditor/ckeditor5-core';
9
- import { Widget } from '@ckeditor/ckeditor5-widget';
10
- import ClipboardPipeline from './clipboardpipeline';
11
- import DragDropTarget from './dragdroptarget';
12
- import '../theme/clipboard.css';
13
- /**
14
- * The drag and drop feature. It works on top of the {@link module:clipboard/clipboardpipeline~ClipboardPipeline}.
15
- *
16
- * Read more about the clipboard integration in the {@glink framework/deep-dive/clipboard clipboard deep-dive} guide.
17
- *
18
- * @internal
19
- */
20
- export default class DragDropExperimental extends Plugin {
21
- /**
22
- * The live range over the original content that is being dragged.
23
- */
24
- private _draggedRange;
25
- /**
26
- * The UID of current dragging that is used to verify if the drop started in the same editor as the drag start.
27
- *
28
- * **Note**: This is a workaround for broken 'dragend' events (they are not fired if the source text node got removed).
29
- */
30
- private _draggingUid;
31
- /**
32
- * The reference to the model element that currently has a `draggable` attribute set (it is set while dragging).
33
- */
34
- private _draggableElement;
35
- /**
36
- * A delayed callback removing draggable attributes.
37
- */
38
- private _clearDraggableAttributesDelayed;
39
- /**
40
- * Whether the dragged content can be dropped only in block context.
41
- */
42
- private _blockMode;
43
- /**
44
- * DOM Emitter.
45
- */
46
- private _domEmitter;
47
- /**
48
- * The DOM element used to generate dragged preview image.
49
- */
50
- private _previewContainer?;
51
- /**
52
- * @inheritDoc
53
- */
54
- static get pluginName(): "DragDropExperimental";
55
- /**
56
- * @inheritDoc
57
- */
58
- static get requires(): readonly [typeof ClipboardPipeline, typeof Widget, typeof DragDropTarget];
59
- /**
60
- * @inheritDoc
61
- */
62
- init(): void;
63
- /**
64
- * @inheritDoc
65
- */
66
- destroy(): void;
67
- /**
68
- * Drag and drop events handling.
69
- */
70
- private _setupDragging;
71
- /**
72
- * Integration with the `clipboardInput` event.
73
- */
74
- private _setupClipboardInputIntegration;
75
- /**
76
- * Integration with the `contentInsertion` event of the clipboard pipeline.
77
- */
78
- private _setupContentInsertionIntegration;
79
- /**
80
- * Adds listeners that add the `draggable` attribute to the elements while the mouse button is down so the dragging could start.
81
- */
82
- private _setupDraggableAttributeHandling;
83
- /**
84
- * Removes the `draggable` attribute from the element that was used for dragging.
85
- */
86
- private _clearDraggableAttributes;
87
- /**
88
- * Deletes the dragged content from its original range and clears the dragging state.
89
- *
90
- * @param moved Whether the move succeeded.
91
- */
92
- private _finalizeDragging;
93
- /**
94
- * Sets the dragged source range based on event target and document selection.
95
- */
96
- private _prepareDraggedRange;
97
- /**
98
- * Updates the dragged preview image.
99
- */
100
- private _updatePreview;
101
- }