ckeditor5-elements-actions 0.0.0-stage → 49.0.0-alpha.4

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 (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +50 -2
  3. package/dist/augmentation.d.ts +19 -0
  4. package/dist/augmentation.d.ts.map +1 -0
  5. package/dist/augmentation.js +2 -0
  6. package/dist/augmentation.js.map +1 -0
  7. package/dist/elementActions.d.ts +18 -0
  8. package/dist/elementActions.d.ts.map +1 -0
  9. package/dist/elementActions.js +29 -0
  10. package/dist/elementActions.js.map +1 -0
  11. package/dist/elementClipboard.d.ts +18 -0
  12. package/dist/elementClipboard.d.ts.map +1 -0
  13. package/dist/elementClipboard.js +32 -0
  14. package/dist/elementClipboard.js.map +1 -0
  15. package/dist/elementCopyCommand.d.ts +30 -0
  16. package/dist/elementCopyCommand.d.ts.map +1 -0
  17. package/dist/elementCopyCommand.js +64 -0
  18. package/dist/elementCopyCommand.js.map +1 -0
  19. package/dist/elementDeleteCommand.d.ts +19 -0
  20. package/dist/elementDeleteCommand.d.ts.map +1 -0
  21. package/dist/elementDeleteCommand.js +47 -0
  22. package/dist/elementDeleteCommand.js.map +1 -0
  23. package/dist/elementDuplicateCommand.d.ts +22 -0
  24. package/dist/elementDuplicateCommand.d.ts.map +1 -0
  25. package/dist/elementDuplicateCommand.js +49 -0
  26. package/dist/elementDuplicateCommand.js.map +1 -0
  27. package/dist/elementMoveCommand.d.ts +32 -0
  28. package/dist/elementMoveCommand.d.ts.map +1 -0
  29. package/dist/elementMoveCommand.js +59 -0
  30. package/dist/elementMoveCommand.js.map +1 -0
  31. package/dist/elementPasteCommand.d.ts +38 -0
  32. package/dist/elementPasteCommand.d.ts.map +1 -0
  33. package/dist/elementPasteCommand.js +100 -0
  34. package/dist/elementPasteCommand.js.map +1 -0
  35. package/dist/icons.d.ts +40 -0
  36. package/dist/icons.d.ts.map +1 -0
  37. package/dist/icons.js +57 -0
  38. package/dist/icons.js.map +1 -0
  39. package/dist/index.d.ts +16 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +19 -0
  42. package/dist/index.js.map +1 -0
  43. package/package.json +45 -3
  44. package/src/augmentation.ts +28 -0
  45. package/src/elementActions.ts +31 -0
  46. package/src/elementClipboard.ts +35 -0
  47. package/src/elementCopyCommand.ts +75 -0
  48. package/src/elementDeleteCommand.ts +58 -0
  49. package/src/elementDuplicateCommand.ts +64 -0
  50. package/src/elementMoveCommand.ts +74 -0
  51. package/src/elementPasteCommand.ts +129 -0
  52. package/src/icons.ts +73 -0
  53. package/src/index.ts +32 -0
@@ -0,0 +1,74 @@
1
+ import { type Editor, type ModelElement } from 'ckeditor5';
2
+ import { ElementOrPositionCommand, isStandalone, type ElementOrPosition } from 'ckeditor5-elements-core';
3
+ import { moveDownIcon, moveUpIcon } from './icons.js';
4
+
5
+ /**
6
+ * Where the element moves.
7
+ */
8
+ export type ElementMoveDirection = 'up' | 'down';
9
+
10
+ /**
11
+ * Moves an element up or down: swaps it with the element before or after it, in the same parent. The selection stays
12
+ * in the element.
13
+ *
14
+ * The element is the given one, the parent of the given position, or the selected element or the parent of the
15
+ * selection (see {@link ElementOrPositionCommand}). The command is disabled when there is no element before it (up) or
16
+ * after it (down), e.g. for the first element of a note, and for parts of a structure, such as table cells.
17
+ */
18
+ export default class ElementMoveCommand extends ElementOrPositionCommand {
19
+ /**
20
+ * Where the element moves.
21
+ */
22
+ public readonly direction: ElementMoveDirection;
23
+
24
+ constructor(editor: Editor, direction: ElementMoveDirection) {
25
+ super(editor);
26
+
27
+ this.direction = direction;
28
+ this.title = direction === 'up' ? editor.t('Move up') : editor.t('Move down');
29
+ this.icon = direction === 'up' ? moveUpIcon : moveDownIcon;
30
+ }
31
+
32
+ /**
33
+ * Moves the element up or down.
34
+ */
35
+ public override execute(elementOrPosition?: ElementOrPosition): void {
36
+ const element = this._getElement(elementOrPosition);
37
+ const sibling = element && this._getSibling(element);
38
+
39
+ if (!element || !sibling || !this._canMove(element, sibling)) {
40
+ return;
41
+ }
42
+
43
+ this.editor.model.change(writer => {
44
+ // Either way, the selection stays in the element: it moves with the element, or the sibling moves past it.
45
+ if (this.direction === 'up') {
46
+ writer.move(writer.createRangeOn(element), writer.createPositionBefore(sibling));
47
+ } else {
48
+ writer.move(writer.createRangeOn(sibling), writer.createPositionBefore(element));
49
+ }
50
+ });
51
+ }
52
+
53
+ protected override _isEnabledFor(elementOrPosition?: ElementOrPosition): boolean {
54
+ const element = this._getElement(elementOrPosition);
55
+ const sibling = element && this._getSibling(element);
56
+
57
+ return !!element && !!sibling && this._canMove(element, sibling);
58
+ }
59
+
60
+ /**
61
+ * Returns the element the element swaps with: the one before it (up) or after it (down), if it is an element.
62
+ */
63
+ private _getSibling(element: ModelElement): ModelElement | null {
64
+ const sibling = this.direction === 'up' ? element.previousSibling : element.nextSibling;
65
+
66
+ return sibling?.is('element') ? sibling : null;
67
+ }
68
+
69
+ private _canMove(element: ModelElement, sibling: ModelElement): boolean {
70
+ const model = this.editor.model;
71
+
72
+ return isStandalone(model.schema, element) && model.canEditAt(element) && model.canEditAt(sibling);
73
+ }
74
+ }
@@ -0,0 +1,129 @@
1
+ import { ViewDataTransfer, plainTextToHtml, type ClipboardInputEventData, type Editor } from 'ckeditor5';
2
+ import { ElementOrPositionCommand, isInDocument, isStandalone, type ElementOrPosition } from 'ckeditor5-elements-core';
3
+ import { pasteAfterIcon, pasteBeforeIcon, pasteIcon } from './icons.js';
4
+
5
+ /**
6
+ * Where the pasted content goes, relative to the element:
7
+ *
8
+ * * `'replace'` – replaces the element,
9
+ * * `'before'` – before the element,
10
+ * * `'after'` – after the element.
11
+ */
12
+ export type ElementPastePlacement = 'replace' | 'before' | 'after';
13
+
14
+ /**
15
+ * Pastes the system clipboard content relative to an element.
16
+ *
17
+ * The element is the given one, the parent of the given position, or the selected element or the parent of the
18
+ * selection (see {@link ElementOrPositionCommand}). Parts of a structure, such as table cells, rows and captions, cannot
19
+ * be replaced, nor have content pasted next to them, so the command is disabled for them.
20
+ *
21
+ * The content is pasted as with Ctrl+V: it goes through the clipboard pipeline (the `clipboardInput` event of the
22
+ * editing view document), so other plugins process it, e.g. paste from Office cleans up content from Word, and image
23
+ * upload uploads the images on the clipboard. The selection is moved to the pasted content.
24
+ *
25
+ * Requires a secure context (HTTPS or `localhost`), as it uses the asynchronous Clipboard API.
26
+ * The browser may ask the user for permission.
27
+ */
28
+ export default class ElementPasteCommand extends ElementOrPositionCommand {
29
+ /**
30
+ * Where the pasted content goes, relative to the element.
31
+ */
32
+ public readonly placement: ElementPastePlacement;
33
+
34
+ constructor(editor: Editor, placement: ElementPastePlacement) {
35
+ super(editor);
36
+
37
+ this.placement = placement;
38
+ this.title = {
39
+ replace: editor.t('Paste'),
40
+ before: editor.t('Paste before'),
41
+ after: editor.t('Paste after')
42
+ }[placement];
43
+ this.icon = { replace: pasteIcon, before: pasteBeforeIcon, after: pasteAfterIcon }[placement];
44
+ }
45
+
46
+ protected override _isEnabledFor(elementOrPosition?: ElementOrPosition): boolean {
47
+ const element = this._getElement(elementOrPosition);
48
+
49
+ return window.isSecureContext && !!element && isStandalone(this.editor.model.schema, element);
50
+ }
51
+
52
+ /**
53
+ * Pastes the clipboard content relative to the element. Resolves when the content has been pasted; rejects when the
54
+ * browser does not allow reading the clipboard.
55
+ */
56
+ public override async execute(elementOrPosition?: ElementOrPosition): Promise<void> {
57
+ // Resolve the element before reading the clipboard, as the selection may change in the meantime.
58
+ const element = this._getElement(elementOrPosition);
59
+
60
+ if (!element || !isStandalone(this.editor.model.schema, element)) {
61
+ return;
62
+ }
63
+
64
+ const dataTransfer = await readClipboard();
65
+
66
+ if (!dataTransfer || !isInDocument(element)) {
67
+ return;
68
+ }
69
+
70
+ const editing = this.editor.editing;
71
+ const model = this.editor.model;
72
+ const viewElement = editing.mapper.toViewElement(element);
73
+
74
+ if (!viewElement) {
75
+ return;
76
+ }
77
+
78
+ model.change(writer => {
79
+ const target =
80
+ this.placement === 'before'
81
+ ? writer.createRange(writer.createPositionBefore(element))
82
+ : this.placement === 'after'
83
+ ? writer.createRange(writer.createPositionAfter(element))
84
+ : writer.createRangeOn(element);
85
+
86
+ if (!model.canEditAt(target)) {
87
+ return;
88
+ }
89
+
90
+ // The same data as `ClipboardObserver` fires for Ctrl+V. The pipeline pastes at the selection.
91
+ const data: ClipboardInputEventData = {
92
+ dataTransfer,
93
+ content:
94
+ dataTransfer.getData('text/html') || plainTextToHtmlOrEmpty(dataTransfer.getData('text/plain')),
95
+ method: 'paste',
96
+ target: viewElement,
97
+ targetRanges: [editing.mapper.toViewRange(target)]
98
+ };
99
+
100
+ writer.setSelection(target);
101
+ editing.view.document.fire('clipboardInput', data);
102
+ });
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Reads the system clipboard into a data transfer: HTML, plain text and images (as files).
108
+ */
109
+ async function readClipboard(): Promise<ViewDataTransfer | null> {
110
+ const nativeDataTransfer = new DataTransfer();
111
+
112
+ for (const item of await navigator.clipboard.read()) {
113
+ for (const type of item.types) {
114
+ const blob = await item.getType(type);
115
+
116
+ if (type === 'text/html' || type === 'text/plain') {
117
+ nativeDataTransfer.setData(type, await blob.text());
118
+ } else if (type.startsWith('image/')) {
119
+ nativeDataTransfer.items.add(new File([blob], `image.${type.slice('image/'.length)}`, { type }));
120
+ }
121
+ }
122
+ }
123
+
124
+ return nativeDataTransfer.types.length ? new ViewDataTransfer(nativeDataTransfer, { cacheFiles: true }) : null;
125
+ }
126
+
127
+ function plainTextToHtmlOrEmpty(text: string): string {
128
+ return text ? plainTextToHtml(text) : '';
129
+ }
package/src/icons.ts ADDED
@@ -0,0 +1,73 @@
1
+ import { IconCopy, IconRemove } from 'ckeditor5';
2
+
3
+ /**
4
+ * The icons of the element actions and of the clipboard commands, in the style of the CKEditor 5 icons (20×20).
5
+ */
6
+
7
+ /**
8
+ * Move up: an arrow up, above a block.
9
+ */
10
+ export const moveUpIcon =
11
+ '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path fill-rule="evenodd" d="' +
12
+ 'M10 1.5l3.75 3.75-1.05 1.05-1.95-1.95V8.5h-1.5V4.35L7.3 6.3 6.25 5.25zM3.5 11h13a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1h-13a1 ' +
13
+ '1 0 0 1-1-1v-5a1 1 0 0 1 1-1zm.5 1.5v4h12v-4z"/></svg>';
14
+
15
+ /**
16
+ * Move down: a block, above an arrow down.
17
+ */
18
+ export const moveDownIcon =
19
+ '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path fill-rule="evenodd" d="' +
20
+ 'M3.5 2h13a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1h-13a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1zm.5 1.5v4h12v-4zM10 18.5l-3.75-3.75 ' +
21
+ '1.05-1.05 1.95 1.95V11.5h1.5v4.15l1.95-1.95 1.05 1.05z"/></svg>';
22
+
23
+ /**
24
+ * Duplicate: a block, above a new block.
25
+ */
26
+ export const duplicateIcon =
27
+ '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path fill-rule="evenodd" d="' +
28
+ 'M3.5 2h13a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1h-13a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1zm.5 1.5v4h12v-4zM3.5 11h13a1 1 0 0 1 1 ' +
29
+ '1v5a1 1 0 0 1-1 1h-13a1 1 0 0 1-1-1v-5a1 1 0 0 1 1-1zm.5 1.5v4h12v-4zM9.25 13h1.5v.75h.75v1.5h-.75V16h-1.5v-.75H8.5' +
30
+ 'v-1.5h.75z"/></svg>';
31
+
32
+ /**
33
+ * Delete: CKEditor's own remove icon.
34
+ */
35
+ export const deleteIcon = IconRemove;
36
+
37
+ /**
38
+ * Copy: CKEditor's own copy icon.
39
+ */
40
+ export const copyIcon = IconCopy;
41
+
42
+ /**
43
+ * Cut: scissors.
44
+ */
45
+ export const cutIcon =
46
+ '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path fill-rule="evenodd" d="' +
47
+ 'M5.3 2.9 6.6 2.1l6.2 10.3a3 3 0 1 1-1.3.8L10 10.7 8.5 13.2a3 3 0 1 1-1.3-.8l2-3.3zm7.4 11.1a1.5 1.5 0 1 0 2.1 2.1 ' +
48
+ '1.5 1.5 0 0 0-2.1-2.1zm-7.4 0a1.5 1.5 0 1 0 2.1 2.1 1.5 1.5 0 0 0-2.1-2.1zm5.4-5.3 3.9-6.6 1.3.8-4.4 7.3z"/></svg>';
49
+
50
+ /**
51
+ * Paste: a clipboard.
52
+ */
53
+ export const pasteIcon =
54
+ '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path fill-rule="evenodd" d="' +
55
+ 'M7.5 2h5a1 1 0 0 1 1 1v.5h1.7a1.3 1.3 0 0 1 1.3 1.3v11.9a1.3 1.3 0 0 1-1.3 1.3H4.8a1.3 1.3 0 0 1-1.3-1.3V4.8a1.3 ' +
56
+ '1.3 0 0 1 1.3-1.3h1.7V3a1 1 0 0 1 1-1zM8 3.5V5h4V3.5zM5 5v11.5h10V5h-1.5v.5a1 1 0 0 1-1 1h-5a1 1 0 0 1-1-1V5zm2 ' +
57
+ '4h6v1.5H7zm0 3h4v1.5H7z"/></svg>';
58
+
59
+ /**
60
+ * Paste before: a small clipboard above a block.
61
+ */
62
+ export const pasteBeforeIcon =
63
+ '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path fill-rule="evenodd" d="' +
64
+ 'M8.5 1.5h3v1h1.4a.6.6 0 0 1 .6.6v5.3a.6.6 0 0 1-.6.6H7.1a.6.6 0 0 1-.6-.6V3.1a.6.6 0 0 1 .6-.6h1.4zM8 4v3.5h4V4zM3.5 ' +
65
+ '11h13a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1h-13a1 1 0 0 1-1-1v-5a1 1 0 0 1 1-1zm.5 1.5v4h12v-4z"/></svg>';
66
+
67
+ /**
68
+ * Paste after: a block above a small clipboard.
69
+ */
70
+ export const pasteAfterIcon =
71
+ '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path fill-rule="evenodd" d="' +
72
+ 'M3.5 2h13a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1h-13a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1zm.5 1.5v4h12v-4zM8.5 10.5h3v1h1.4a.6.6 0 0 ' +
73
+ '1 .6.6v5.3a.6.6 0 0 1-.6.6H7.1a.6.6 0 0 1-.6-.6v-5.3a.6.6 0 0 1 .6-.6h1.4zM8 13v3.5h4V13z"/></svg>';
package/src/index.ts ADDED
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Element actions: move an element up or down, duplicate it, or delete it; and the clipboard commands that act on an
3
+ * element: copy, cut, paste, paste before and paste after.
4
+ *
5
+ * @module ckeditor5-elements-actions
6
+ */
7
+
8
+ // Plugins.
9
+ export { default as ElementActions } from './elementActions.js';
10
+ export { default as ElementClipboard } from './elementClipboard.js';
11
+
12
+ // Commands.
13
+ export { default as ElementMoveCommand, type ElementMoveDirection } from './elementMoveCommand.js';
14
+ export { default as ElementDuplicateCommand } from './elementDuplicateCommand.js';
15
+ export { default as ElementDeleteCommand } from './elementDeleteCommand.js';
16
+ export { default as ElementCopyCommand } from './elementCopyCommand.js';
17
+ export { default as ElementPasteCommand, type ElementPastePlacement } from './elementPasteCommand.js';
18
+
19
+ // Icons.
20
+ export {
21
+ copyIcon,
22
+ cutIcon,
23
+ deleteIcon,
24
+ duplicateIcon,
25
+ moveDownIcon,
26
+ moveUpIcon,
27
+ pasteAfterIcon,
28
+ pasteBeforeIcon,
29
+ pasteIcon
30
+ } from './icons.js';
31
+
32
+ import './augmentation.js';