@ckeditor/ckeditor5-widget 41.4.2 → 42.0.0-alpha.0
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +6 -0
- package/dist/index.js +581 -479
- package/dist/index.js.map +1 -1
- package/dist/translations/ar.js +1 -1
- package/dist/translations/ar.umd.js +1 -1
- package/dist/translations/az.js +1 -1
- package/dist/translations/az.umd.js +1 -1
- package/dist/translations/bg.js +1 -1
- package/dist/translations/bg.umd.js +1 -1
- package/dist/translations/bn.js +1 -1
- package/dist/translations/bn.umd.js +1 -1
- package/dist/translations/ca.js +1 -1
- package/dist/translations/ca.umd.js +1 -1
- package/dist/translations/cs.js +1 -1
- package/dist/translations/cs.umd.js +1 -1
- package/dist/translations/da.js +1 -1
- package/dist/translations/da.umd.js +1 -1
- package/dist/translations/de-ch.js +1 -1
- package/dist/translations/de-ch.umd.js +1 -1
- package/dist/translations/de.js +1 -1
- package/dist/translations/de.umd.js +1 -1
- package/dist/translations/el.js +1 -1
- package/dist/translations/el.umd.js +1 -1
- package/dist/translations/en-au.js +1 -1
- package/dist/translations/en-au.umd.js +1 -1
- package/dist/translations/en.js +1 -1
- package/dist/translations/en.umd.js +1 -1
- package/dist/translations/es.js +1 -1
- package/dist/translations/es.umd.js +1 -1
- package/dist/translations/et.js +1 -1
- package/dist/translations/et.umd.js +1 -1
- package/dist/translations/fa.js +1 -1
- package/dist/translations/fa.umd.js +1 -1
- package/dist/translations/fi.js +1 -1
- package/dist/translations/fi.umd.js +1 -1
- package/dist/translations/fr.js +1 -1
- package/dist/translations/fr.umd.js +1 -1
- package/dist/translations/gl.js +1 -1
- package/dist/translations/gl.umd.js +1 -1
- package/dist/translations/he.js +1 -1
- package/dist/translations/he.umd.js +1 -1
- package/dist/translations/hi.js +1 -1
- package/dist/translations/hi.umd.js +1 -1
- package/dist/translations/hr.js +1 -1
- package/dist/translations/hr.umd.js +1 -1
- package/dist/translations/hu.js +1 -1
- package/dist/translations/hu.umd.js +1 -1
- package/dist/translations/id.js +1 -1
- package/dist/translations/id.umd.js +1 -1
- package/dist/translations/it.js +1 -1
- package/dist/translations/it.umd.js +1 -1
- package/dist/translations/ja.js +1 -1
- package/dist/translations/ja.umd.js +1 -1
- package/dist/translations/ko.js +1 -1
- package/dist/translations/ko.umd.js +1 -1
- package/dist/translations/ku.js +1 -1
- package/dist/translations/ku.umd.js +1 -1
- package/dist/translations/lt.js +1 -1
- package/dist/translations/lt.umd.js +1 -1
- package/dist/translations/lv.js +1 -1
- package/dist/translations/lv.umd.js +1 -1
- package/dist/translations/ms.js +1 -1
- package/dist/translations/ms.umd.js +1 -1
- package/dist/translations/nl.js +1 -1
- package/dist/translations/nl.umd.js +1 -1
- package/dist/translations/no.js +1 -1
- package/dist/translations/no.umd.js +1 -1
- package/dist/translations/pl.js +1 -1
- package/dist/translations/pl.umd.js +1 -1
- package/dist/translations/pt-br.js +1 -1
- package/dist/translations/pt-br.umd.js +1 -1
- package/dist/translations/pt.js +1 -1
- package/dist/translations/pt.umd.js +1 -1
- package/dist/translations/ro.js +1 -1
- package/dist/translations/ro.umd.js +1 -1
- package/dist/translations/ru.js +1 -1
- package/dist/translations/ru.umd.js +1 -1
- package/dist/translations/sk.js +1 -1
- package/dist/translations/sk.umd.js +1 -1
- package/dist/translations/sq.js +1 -1
- package/dist/translations/sq.umd.js +1 -1
- package/dist/translations/sr-latn.js +1 -1
- package/dist/translations/sr-latn.umd.js +1 -1
- package/dist/translations/sr.js +1 -1
- package/dist/translations/sr.umd.js +1 -1
- package/dist/translations/sv.js +1 -1
- package/dist/translations/sv.umd.js +1 -1
- package/dist/translations/th.js +1 -1
- package/dist/translations/th.umd.js +1 -1
- package/dist/translations/tk.js +1 -1
- package/dist/translations/tk.umd.js +1 -1
- package/dist/translations/tr.js +1 -1
- package/dist/translations/tr.umd.js +1 -1
- package/dist/translations/uk.js +1 -1
- package/dist/translations/uk.umd.js +1 -1
- package/dist/translations/ur.js +1 -1
- package/dist/translations/ur.umd.js +1 -1
- package/dist/translations/uz.js +1 -1
- package/dist/translations/uz.umd.js +1 -1
- package/dist/translations/vi.js +1 -1
- package/dist/translations/vi.umd.js +1 -1
- package/dist/translations/zh-cn.js +1 -1
- package/dist/translations/zh-cn.umd.js +1 -1
- package/dist/translations/zh.js +1 -1
- package/dist/translations/zh.umd.js +1 -1
- package/dist/types/highlightstack.d.ts +1 -1
- package/dist/types/widgetresize/resizer.d.ts +1 -1
- package/dist/types/widgetresize/resizerstate.d.ts +1 -1
- package/lang/contexts.json +2 -1
- package/lang/translations/ar.po +4 -0
- package/lang/translations/az.po +4 -0
- package/lang/translations/bg.po +4 -0
- package/lang/translations/bn.po +4 -0
- package/lang/translations/ca.po +4 -0
- package/lang/translations/cs.po +4 -0
- package/lang/translations/da.po +4 -0
- package/lang/translations/de-ch.po +4 -0
- package/lang/translations/de.po +4 -0
- package/lang/translations/el.po +4 -0
- package/lang/translations/en-au.po +4 -0
- package/lang/translations/en.po +4 -0
- package/lang/translations/es.po +4 -0
- package/lang/translations/et.po +4 -0
- package/lang/translations/fa.po +4 -0
- package/lang/translations/fi.po +4 -0
- package/lang/translations/fr.po +4 -0
- package/lang/translations/gl.po +4 -0
- package/lang/translations/he.po +4 -0
- package/lang/translations/hi.po +4 -0
- package/lang/translations/hr.po +4 -0
- package/lang/translations/hu.po +4 -0
- package/lang/translations/id.po +4 -0
- package/lang/translations/it.po +4 -0
- package/lang/translations/ja.po +4 -0
- package/lang/translations/ko.po +4 -0
- package/lang/translations/ku.po +4 -0
- package/lang/translations/lt.po +4 -0
- package/lang/translations/lv.po +4 -0
- package/lang/translations/ms.po +4 -0
- package/lang/translations/nl.po +4 -0
- package/lang/translations/no.po +4 -0
- package/lang/translations/pl.po +4 -0
- package/lang/translations/pt-br.po +4 -0
- package/lang/translations/pt.po +4 -0
- package/lang/translations/ro.po +4 -0
- package/lang/translations/ru.po +4 -0
- package/lang/translations/sk.po +4 -0
- package/lang/translations/sq.po +4 -0
- package/lang/translations/sr-latn.po +4 -0
- package/lang/translations/sr.po +4 -0
- package/lang/translations/sv.po +4 -0
- package/lang/translations/th.po +4 -0
- package/lang/translations/tk.po +4 -0
- package/lang/translations/tr.po +4 -0
- package/lang/translations/uk.po +4 -0
- package/lang/translations/ur.po +4 -0
- package/lang/translations/uz.po +4 -0
- package/lang/translations/vi.po +4 -0
- package/lang/translations/zh-cn.po +4 -0
- package/lang/translations/zh.po +4 -0
- package/package.json +7 -7
- package/src/highlightstack.d.ts +1 -1
- package/src/highlightstack.js +1 -1
- package/src/widget.js +4 -0
- package/src/widgetresize/resizer.d.ts +1 -1
- package/src/widgetresize/resizer.js +1 -1
- package/src/widgetresize/resizerstate.d.ts +1 -1
- package/src/widgetresize/resizerstate.js +1 -1
package/dist/index.js
CHANGED
@@ -10,12 +10,23 @@ import { IconView, Template, ContextualBalloon, ToolbarView, BalloonPanelView, V
|
|
10
10
|
import { Enter } from '@ckeditor/ckeditor5-enter/dist/index.js';
|
11
11
|
import { throttle } from 'lodash-es';
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
13
|
+
/**
|
14
|
+
* Class used to handle the correct order of highlights on elements.
|
15
|
+
*
|
16
|
+
* When different highlights are applied to same element the correct order should be preserved:
|
17
|
+
*
|
18
|
+
* * highlight with highest priority should be applied,
|
19
|
+
* * if two highlights have same priority - sort by CSS class provided in
|
20
|
+
* {@link module:engine/conversion/downcasthelpers~HighlightDescriptor}.
|
21
|
+
*
|
22
|
+
* This way, highlight will be applied with the same rules it is applied on texts.
|
23
|
+
*/ class HighlightStack extends /* #__PURE__ */ EmitterMixin() {
|
24
|
+
_stack = [];
|
25
|
+
/**
|
26
|
+
* Adds highlight descriptor to the stack.
|
27
|
+
*
|
28
|
+
* @fires change:top
|
29
|
+
*/ add(descriptor, writer) {
|
19
30
|
const stack = this._stack;
|
20
31
|
// Save top descriptor and insert new one. If top is changed - fire event.
|
21
32
|
const oldTop = stack[0];
|
@@ -31,11 +42,11 @@ class HighlightStack extends EmitterMixin() {
|
|
31
42
|
}
|
32
43
|
}
|
33
44
|
/**
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
45
|
+
* Removes highlight descriptor from the stack.
|
46
|
+
*
|
47
|
+
* @fires change:top
|
48
|
+
* @param id Id of the descriptor to remove.
|
49
|
+
*/ remove(id, writer) {
|
39
50
|
const stack = this._stack;
|
40
51
|
const oldTop = stack[0];
|
41
52
|
this._removeDescriptor(id);
|
@@ -50,9 +61,9 @@ class HighlightStack extends EmitterMixin() {
|
|
50
61
|
}
|
51
62
|
}
|
52
63
|
/**
|
53
|
-
|
54
|
-
|
55
|
-
|
64
|
+
* Inserts a given descriptor in correct place in the stack. It also takes care about updating information
|
65
|
+
* when descriptor with same id is already present.
|
66
|
+
*/ _insertDescriptor(descriptor) {
|
56
67
|
const stack = this._stack;
|
57
68
|
const index = stack.findIndex((item)=>item.id === descriptor.id);
|
58
69
|
// Inserting exact same descriptor - do nothing.
|
@@ -72,10 +83,10 @@ class HighlightStack extends EmitterMixin() {
|
|
72
83
|
stack.splice(i, 0, descriptor);
|
73
84
|
}
|
74
85
|
/**
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
86
|
+
* Removes descriptor with given id from the stack.
|
87
|
+
*
|
88
|
+
* @param id Descriptor's id.
|
89
|
+
*/ _removeDescriptor(id) {
|
79
90
|
const stack = this._stack;
|
80
91
|
const index = stack.findIndex((item)=>item.id === id);
|
81
92
|
// If descriptor with same id is on the list - remove it.
|
@@ -83,10 +94,6 @@ class HighlightStack extends EmitterMixin() {
|
|
83
94
|
stack.splice(index, 1);
|
84
95
|
}
|
85
96
|
}
|
86
|
-
constructor(){
|
87
|
-
super(...arguments);
|
88
|
-
this._stack = [];
|
89
|
-
}
|
90
97
|
}
|
91
98
|
/**
|
92
99
|
* Compares two descriptors by checking their priority and class list.
|
@@ -177,12 +184,12 @@ var dragHandleIcon = "<svg viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/
|
|
177
184
|
*/ function toWidget(element, writer, options = {}) {
|
178
185
|
if (!element.is('containerElement')) {
|
179
186
|
/**
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
187
|
+
* The element passed to `toWidget()` must be a {@link module:engine/view/containerelement~ContainerElement}
|
188
|
+
* instance.
|
189
|
+
*
|
190
|
+
* @error widget-to-widget-wrong-element-type
|
191
|
+
* @param element The view element passed to `toWidget()`.
|
192
|
+
*/ throw new CKEditorError('widget-to-widget-wrong-element-type', null, {
|
186
193
|
element
|
187
194
|
});
|
188
195
|
}
|
@@ -538,23 +545,37 @@ const POSSIBLE_INSERTION_POSITIONS = [
|
|
538
545
|
// Do the SVG parsing once and then clone the result <svg> DOM element for each new button.
|
539
546
|
const RETURN_ARROW_ICON_ELEMENT = new DOMParser().parseFromString(returnIcon, 'image/svg+xml').firstChild;
|
540
547
|
const PLUGIN_DISABLED_EDITING_ROOT_CLASS = 'ck-widget__type-around_disabled';
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
548
|
+
/**
|
549
|
+
* A plugin that allows users to type around widgets where normally it is impossible to place the caret due
|
550
|
+
* to limitations of web browsers. These "tight spots" occur, for instance, before (or after) a widget being
|
551
|
+
* the first (or last) child of its parent or between two block widgets.
|
552
|
+
*
|
553
|
+
* This plugin extends the {@link module:widget/widget~Widget `Widget`} plugin and injects the user interface
|
554
|
+
* with two buttons into each widget instance in the editor. Each of the buttons can be clicked by the
|
555
|
+
* user if the widget is next to the "tight spot". Once clicked, a paragraph is created with the selection anchored
|
556
|
+
* in it so that users can type (or insert content, paste, etc.) straight away.
|
557
|
+
*/ class WidgetTypeAround extends Plugin {
|
558
|
+
/**
|
559
|
+
* A reference to the model widget element that has the fake caret active
|
560
|
+
* on either side of it. It is later used to remove CSS classes associated with the fake caret
|
561
|
+
* when the widget no longer needs it.
|
562
|
+
*/ _currentFakeCaretModelElement = null;
|
563
|
+
/**
|
564
|
+
* @inheritDoc
|
565
|
+
*/ static get pluginName() {
|
545
566
|
return 'WidgetTypeAround';
|
546
567
|
}
|
547
568
|
/**
|
548
|
-
|
549
|
-
|
569
|
+
* @inheritDoc
|
570
|
+
*/ static get requires() {
|
550
571
|
return [
|
551
572
|
Enter,
|
552
573
|
Delete
|
553
574
|
];
|
554
575
|
}
|
555
576
|
/**
|
556
|
-
|
557
|
-
|
577
|
+
* @inheritDoc
|
578
|
+
*/ init() {
|
558
579
|
const editor = this.editor;
|
559
580
|
const editingView = editor.editing.view;
|
560
581
|
// Set a CSS class on the view editing root when the plugin is disabled so all the buttons
|
@@ -586,20 +607,20 @@ class WidgetTypeAround extends Plugin {
|
|
586
607
|
this._enableDeleteContentIntegration();
|
587
608
|
}
|
588
609
|
/**
|
589
|
-
|
590
|
-
|
610
|
+
* @inheritDoc
|
611
|
+
*/ destroy() {
|
591
612
|
super.destroy();
|
592
613
|
this._currentFakeCaretModelElement = null;
|
593
614
|
}
|
594
615
|
/**
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
616
|
+
* Inserts a new paragraph next to a widget element with the selection anchored in it.
|
617
|
+
*
|
618
|
+
* **Note**: This method is heavily user-oriented and will both focus the editing view and scroll
|
619
|
+
* the viewport to the selection in the inserted paragraph.
|
620
|
+
*
|
621
|
+
* @param widgetModelElement The model widget element next to which a paragraph is inserted.
|
622
|
+
* @param position The position where the paragraph is inserted. Either `'before'` or `'after'` the widget.
|
623
|
+
*/ _insertParagraph(widgetModelElement, position) {
|
603
624
|
const editor = this.editor;
|
604
625
|
const editingView = editor.editing.view;
|
605
626
|
const attributesToCopy = editor.model.schema.getAttributesWithProperty(widgetModelElement, 'copyOnReplace', true);
|
@@ -611,16 +632,16 @@ class WidgetTypeAround extends Plugin {
|
|
611
632
|
editingView.scrollToTheSelection();
|
612
633
|
}
|
613
634
|
/**
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
635
|
+
* A wrapper for the {@link module:utils/emittermixin~Emitter#listenTo} method that executes the callbacks only
|
636
|
+
* when the plugin {@link #isEnabled is enabled}.
|
637
|
+
*
|
638
|
+
* @param emitter The object that fires the event.
|
639
|
+
* @param event The name of the event.
|
640
|
+
* @param callback The function to be called on event.
|
641
|
+
* @param options Additional options.
|
642
|
+
* @param options.priority The priority of this event callback. The higher the priority value the sooner
|
643
|
+
* the callback will be fired. Events having the same priority are called in the order they were added.
|
644
|
+
*/ _listenToIfEnabled(emitter, event, callback, options) {
|
624
645
|
this.listenTo(emitter, event, (...args)=>{
|
625
646
|
// Do not respond if the plugin is disabled.
|
626
647
|
if (this.isEnabled) {
|
@@ -629,16 +650,16 @@ class WidgetTypeAround extends Plugin {
|
|
629
650
|
}, options);
|
630
651
|
}
|
631
652
|
/**
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
653
|
+
* Similar to {@link #_insertParagraph}, this method inserts a paragraph except that it
|
654
|
+
* does not expect a position. Instead, it performs the insertion next to a selected widget
|
655
|
+
* according to the `widget-type-around` model selection attribute value (fake caret position).
|
656
|
+
*
|
657
|
+
* Because this method requires the `widget-type-around` attribute to be set,
|
658
|
+
* the insertion can only happen when the widget's fake caret is active (e.g. activated
|
659
|
+
* using the keyboard).
|
660
|
+
*
|
661
|
+
* @returns Returns `true` when the paragraph was inserted (the attribute was present) and `false` otherwise.
|
662
|
+
*/ _insertParagraphAccordingToFakeCaretPosition() {
|
642
663
|
const editor = this.editor;
|
643
664
|
const model = editor.model;
|
644
665
|
const modelSelection = model.document.selection;
|
@@ -656,12 +677,12 @@ class WidgetTypeAround extends Plugin {
|
|
656
677
|
return true;
|
657
678
|
}
|
658
679
|
/**
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
680
|
+
* Creates a listener in the editing conversion pipeline that injects the widget type around
|
681
|
+
* UI into every single widget instance created in the editor.
|
682
|
+
*
|
683
|
+
* The UI is delivered as a {@link module:engine/view/uielement~UIElement}
|
684
|
+
* wrapper which renders DOM buttons that users can use to insert paragraphs.
|
685
|
+
*/ _enableTypeAroundUIInjection() {
|
665
686
|
const editor = this.editor;
|
666
687
|
const schema = editor.model.schema;
|
667
688
|
const t = editor.locale.t;
|
@@ -687,30 +708,30 @@ class WidgetTypeAround extends Plugin {
|
|
687
708
|
});
|
688
709
|
}
|
689
710
|
/**
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
711
|
+
* Brings support for the fake caret that appears when either:
|
712
|
+
*
|
713
|
+
* * the selection moves to a widget from a position next to it using arrow keys,
|
714
|
+
* * the arrow key is pressed when the widget is already selected.
|
715
|
+
*
|
716
|
+
* The fake caret lets the user know that they can start typing or just press
|
717
|
+
* <kbd>Enter</kbd> to insert a paragraph at the position next to a widget as suggested by the fake caret.
|
718
|
+
*
|
719
|
+
* The fake caret disappears when the user changes the selection or the editor
|
720
|
+
* gets blurred.
|
721
|
+
*
|
722
|
+
* The whole idea is as follows:
|
723
|
+
*
|
724
|
+
* 1. A user does one of the 2 scenarios described at the beginning.
|
725
|
+
* 2. The "keydown" listener is executed and the decision is made whether to show or hide the fake caret.
|
726
|
+
* 3. If it should show up, the `widget-type-around` model selection attribute is set indicating
|
727
|
+
* on which side of the widget it should appear.
|
728
|
+
* 4. The selection dispatcher reacts to the selection attribute and sets CSS classes responsible for the
|
729
|
+
* fake caret on the view widget.
|
730
|
+
* 5. If the fake caret should disappear, the selection attribute is removed and the dispatcher
|
731
|
+
* does the CSS class clean-up in the view.
|
732
|
+
* 6. Additionally, `change:range` and `FocusTracker#isFocused` listeners also remove the selection
|
733
|
+
* attribute (the former also removes widget CSS classes).
|
734
|
+
*/ _enableTypeAroundFakeCaretActivationUsingKeyboardArrows() {
|
714
735
|
const editor = this.editor;
|
715
736
|
const model = editor.model;
|
716
737
|
const modelSelection = model.document.selection;
|
@@ -798,17 +819,17 @@ class WidgetTypeAround extends Plugin {
|
|
798
819
|
}
|
799
820
|
}
|
800
821
|
/**
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
|
822
|
+
* A listener executed on each "keydown" in the view document, a part of
|
823
|
+
* {@link #_enableTypeAroundFakeCaretActivationUsingKeyboardArrows}.
|
824
|
+
*
|
825
|
+
* It decides whether the arrow keypress should activate the fake caret or not (also whether it should
|
826
|
+
* be deactivated).
|
827
|
+
*
|
828
|
+
* The fake caret activation is done by setting the `widget-type-around` model selection attribute
|
829
|
+
* in this listener, and stopping and preventing the event that would normally be handled by the widget
|
830
|
+
* plugin that is responsible for the regular keyboard navigation near/across all widgets (that
|
831
|
+
* includes inline widgets, which are ignored by the widget type around plugin).
|
832
|
+
*/ _handleArrowKeyPress(evt, domEventData) {
|
812
833
|
const editor = this.editor;
|
813
834
|
const model = editor.model;
|
814
835
|
const modelSelection = model.document.selection;
|
@@ -833,15 +854,15 @@ class WidgetTypeAround extends Plugin {
|
|
833
854
|
}
|
834
855
|
}
|
835
856
|
/**
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
857
|
+
* Handles the keyboard navigation on "keydown" when a widget is currently selected and activates or deactivates
|
858
|
+
* the fake caret for that widget, depending on the current value of the `widget-type-around` model
|
859
|
+
* selection attribute and the direction of the pressed arrow key.
|
860
|
+
*
|
861
|
+
* @param isForward `true` when the pressed arrow key was responsible for the forward model selection movement
|
862
|
+
* as in {@link module:utils/keyboard~isForwardArrowKeyCode}.
|
863
|
+
* @returns Returns `true` when the keypress was handled and no other keydown listener of the editor should
|
864
|
+
* process the event any further. Returns `false` otherwise.
|
865
|
+
*/ _handleArrowKeyPressOnSelectedWidget(isForward) {
|
845
866
|
const editor = this.editor;
|
846
867
|
const model = editor.model;
|
847
868
|
const modelSelection = model.document.selection;
|
@@ -870,19 +891,19 @@ class WidgetTypeAround extends Plugin {
|
|
870
891
|
});
|
871
892
|
}
|
872
893
|
/**
|
873
|
-
|
874
|
-
|
875
|
-
|
876
|
-
|
877
|
-
|
878
|
-
|
879
|
-
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
|
894
|
+
* Handles the keyboard navigation on "keydown" when **no** widget is selected but the selection is **directly** next
|
895
|
+
* to one and upon the fake caret should become active for this widget upon arrow keypress
|
896
|
+
* (AKA entering/selecting the widget).
|
897
|
+
*
|
898
|
+
* **Note**: This code mirrors the implementation from the widget plugin but also adds the selection attribute.
|
899
|
+
* Unfortunately, there is no safe way to let the widget plugin do the selection part first and then just set the
|
900
|
+
* selection attribute here in the widget type around plugin. This is why this code must duplicate some from the widget plugin.
|
901
|
+
*
|
902
|
+
* @param isForward `true` when the pressed arrow key was responsible for the forward model selection movement
|
903
|
+
* as in {@link module:utils/keyboard~isForwardArrowKeyCode}.
|
904
|
+
* @returns Returns `true` when the keypress was handled and no other keydown listener of the editor should
|
905
|
+
* process the event any further. Returns `false` otherwise.
|
906
|
+
*/ _handleArrowKeyPressWhenSelectionNextToAWidget(isForward) {
|
886
907
|
const editor = this.editor;
|
887
908
|
const model = editor.model;
|
888
909
|
const schema = model.schema;
|
@@ -902,14 +923,14 @@ class WidgetTypeAround extends Plugin {
|
|
902
923
|
return false;
|
903
924
|
}
|
904
925
|
/**
|
905
|
-
|
906
|
-
|
907
|
-
|
908
|
-
|
909
|
-
|
910
|
-
|
911
|
-
|
912
|
-
|
926
|
+
* Handles the keyboard navigation on "keydown" when a widget is currently selected (together with some other content)
|
927
|
+
* and the widget is the first or last element in the selection. It activates or deactivates the fake caret for that widget.
|
928
|
+
*
|
929
|
+
* @param isForward `true` when the pressed arrow key was responsible for the forward model selection movement
|
930
|
+
* as in {@link module:utils/keyboard~isForwardArrowKeyCode}.
|
931
|
+
* @returns Returns `true` when the keypress was handled and no other keydown listener of the editor should
|
932
|
+
* process the event any further. Returns `false` otherwise.
|
933
|
+
*/ _handleArrowKeyPressWhenNonCollapsedSelection(isForward) {
|
913
934
|
const editor = this.editor;
|
914
935
|
const model = editor.model;
|
915
936
|
const schema = model.schema;
|
@@ -928,10 +949,10 @@ class WidgetTypeAround extends Plugin {
|
|
928
949
|
return false;
|
929
950
|
}
|
930
951
|
/**
|
931
|
-
|
932
|
-
|
933
|
-
|
934
|
-
|
952
|
+
* Registers a `mousedown` listener for the view document which intercepts events
|
953
|
+
* coming from the widget type around UI, which happens when a user clicks one of the buttons
|
954
|
+
* that insert a paragraph next to a widget.
|
955
|
+
*/ _enableInsertingParagraphsOnButtonClick() {
|
935
956
|
const editor = this.editor;
|
936
957
|
const editingView = editor.editing.view;
|
937
958
|
this._listenToIfEnabled(editingView.document, 'mousedown', (evt, domEventData)=>{
|
@@ -948,18 +969,18 @@ class WidgetTypeAround extends Plugin {
|
|
948
969
|
});
|
949
970
|
}
|
950
971
|
/**
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
957
|
-
|
958
|
-
|
959
|
-
|
960
|
-
|
961
|
-
|
962
|
-
|
972
|
+
* Creates the <kbd>Enter</kbd> key listener on the view document that allows the user to insert a paragraph
|
973
|
+
* near the widget when either:
|
974
|
+
*
|
975
|
+
* * The fake caret was first activated using the arrow keys,
|
976
|
+
* * The entire widget is selected in the model.
|
977
|
+
*
|
978
|
+
* In the first case, the new paragraph is inserted according to the `widget-type-around` selection
|
979
|
+
* attribute (see {@link #_handleArrowKeyPress}).
|
980
|
+
*
|
981
|
+
* In the second case, the new paragraph is inserted based on whether a soft (<kbd>Shift</kbd>+<kbd>Enter</kbd>) keystroke
|
982
|
+
* was pressed or not.
|
983
|
+
*/ _enableInsertingParagraphsOnEnterKeypress() {
|
963
984
|
const editor = this.editor;
|
964
985
|
const selection = editor.model.document.selection;
|
965
986
|
const editingView = editor.editing.view;
|
@@ -990,18 +1011,18 @@ class WidgetTypeAround extends Plugin {
|
|
990
1011
|
});
|
991
1012
|
}
|
992
1013
|
/**
|
993
|
-
|
994
|
-
|
995
|
-
|
996
|
-
|
997
|
-
|
998
|
-
|
999
|
-
|
1000
|
-
|
1001
|
-
|
1002
|
-
|
1003
|
-
|
1004
|
-
|
1014
|
+
* Similar to the {@link #_enableInsertingParagraphsOnEnterKeypress}, it allows the user
|
1015
|
+
* to insert a paragraph next to a widget when the fake caret was activated using arrow
|
1016
|
+
* keys but it responds to typing instead of <kbd>Enter</kbd>.
|
1017
|
+
*
|
1018
|
+
* Listener enabled by this method will insert a new paragraph according to the `widget-type-around`
|
1019
|
+
* model selection attribute as the user simply starts typing, which creates the impression that the fake caret
|
1020
|
+
* behaves like a real one rendered by the browser (AKA your text appears where the caret was).
|
1021
|
+
*
|
1022
|
+
* **Note**: At the moment this listener creates 2 undo steps: one for the `insertParagraph` command
|
1023
|
+
* and another one for actual typing. It is not a disaster but this may need to be fixed
|
1024
|
+
* sooner or later.
|
1025
|
+
*/ _enableInsertingParagraphsOnTypingKeystroke() {
|
1005
1026
|
const editor = this.editor;
|
1006
1027
|
const viewDocument = editor.editing.view.document;
|
1007
1028
|
// Note: The priority must precede the default Input plugin insertText handler.
|
@@ -1035,13 +1056,13 @@ class WidgetTypeAround extends Plugin {
|
|
1035
1056
|
}
|
1036
1057
|
}
|
1037
1058
|
/**
|
1038
|
-
|
1039
|
-
|
1040
|
-
|
1041
|
-
|
1042
|
-
|
1043
|
-
|
1044
|
-
|
1059
|
+
* It creates a "delete" event listener on the view document to handle cases when the <kbd>Delete</kbd> or <kbd>Backspace</kbd>
|
1060
|
+
* is pressed and the fake caret is currently active.
|
1061
|
+
*
|
1062
|
+
* The fake caret should create an illusion of a real browser caret so that when it appears before or after
|
1063
|
+
* a widget, pressing <kbd>Delete</kbd> or <kbd>Backspace</kbd> should remove a widget or delete the content
|
1064
|
+
* before or after a widget (depending on the content surrounding the widget).
|
1065
|
+
*/ _enableDeleteIntegration() {
|
1045
1066
|
const editor = this.editor;
|
1046
1067
|
const editingView = editor.editing.view;
|
1047
1068
|
const model = editor.model;
|
@@ -1106,11 +1127,11 @@ class WidgetTypeAround extends Plugin {
|
|
1106
1127
|
});
|
1107
1128
|
}
|
1108
1129
|
/**
|
1109
|
-
|
1110
|
-
|
1111
|
-
|
1112
|
-
|
1113
|
-
|
1130
|
+
* Attaches the {@link module:engine/model/model~Model#event:insertContent} event listener that, for instance, allows the user to paste
|
1131
|
+
* content near a widget when the fake caret is first activated using the arrow keys.
|
1132
|
+
*
|
1133
|
+
* The content is inserted according to the `widget-type-around` selection attribute (see {@link #_handleArrowKeyPress}).
|
1134
|
+
*/ _enableInsertContentIntegration() {
|
1114
1135
|
const editor = this.editor;
|
1115
1136
|
const model = this.editor.model;
|
1116
1137
|
const documentSelection = model.document.selection;
|
@@ -1136,12 +1157,12 @@ class WidgetTypeAround extends Plugin {
|
|
1136
1157
|
});
|
1137
1158
|
}
|
1138
1159
|
/**
|
1139
|
-
|
1140
|
-
|
1141
|
-
|
1142
|
-
|
1143
|
-
|
1144
|
-
|
1160
|
+
* Attaches the {@link module:engine/model/model~Model#event:insertObject} event listener that modifies the
|
1161
|
+
* `options.findOptimalPosition`parameter to position of fake caret in relation to selected element
|
1162
|
+
* to reflect user's intent of desired insertion position.
|
1163
|
+
*
|
1164
|
+
* The object is inserted according to the `widget-type-around` selection attribute (see {@link #_handleArrowKeyPress}).
|
1165
|
+
*/ _enableInsertObjectIntegration() {
|
1145
1166
|
const editor = this.editor;
|
1146
1167
|
const model = this.editor.model;
|
1147
1168
|
const documentSelection = model.document.selection;
|
@@ -1161,13 +1182,13 @@ class WidgetTypeAround extends Plugin {
|
|
1161
1182
|
});
|
1162
1183
|
}
|
1163
1184
|
/**
|
1164
|
-
|
1165
|
-
|
1166
|
-
|
1167
|
-
|
1168
|
-
|
1169
|
-
|
1170
|
-
|
1185
|
+
* Attaches the {@link module:engine/model/model~Model#event:deleteContent} event listener to block the event when the fake
|
1186
|
+
* caret is active.
|
1187
|
+
*
|
1188
|
+
* This is required for cases that trigger {@link module:engine/model/model~Model#deleteContent `model.deleteContent()`}
|
1189
|
+
* before calling {@link module:engine/model/model~Model#insertContent `model.insertContent()`} like, for instance,
|
1190
|
+
* plain text pasting.
|
1191
|
+
*/ _enableDeleteContentIntegration() {
|
1171
1192
|
const editor = this.editor;
|
1172
1193
|
const model = this.editor.model;
|
1173
1194
|
const documentSelection = model.document.selection;
|
@@ -1184,14 +1205,6 @@ class WidgetTypeAround extends Plugin {
|
|
1184
1205
|
priority: 'high'
|
1185
1206
|
});
|
1186
1207
|
}
|
1187
|
-
constructor(){
|
1188
|
-
super(...arguments);
|
1189
|
-
/**
|
1190
|
-
* A reference to the model widget element that has the fake caret active
|
1191
|
-
* on either side of it. It is later used to remove CSS classes associated with the fake caret
|
1192
|
-
* when the widget no longer needs it.
|
1193
|
-
*/ this._currentFakeCaretModelElement = null;
|
1194
|
-
}
|
1195
1208
|
}
|
1196
1209
|
/**
|
1197
1210
|
* Injects the type around UI into a view widget instance.
|
@@ -1450,23 +1463,38 @@ function selectionWillShrink(selection, isForward) {
|
|
1450
1463
|
return !selection.isCollapsed && selection.isBackward == isForward;
|
1451
1464
|
}
|
1452
1465
|
|
1453
|
-
|
1454
|
-
|
1455
|
-
|
1456
|
-
|
1466
|
+
/**
|
1467
|
+
* The widget plugin. It enables base support for widgets.
|
1468
|
+
*
|
1469
|
+
* See {@glink api/widget package page} for more details and documentation.
|
1470
|
+
*
|
1471
|
+
* This plugin enables multiple behaviors required by widgets:
|
1472
|
+
*
|
1473
|
+
* * The model to view selection converter for the editing pipeline (it handles widget custom selection rendering).
|
1474
|
+
* If a converted selection wraps around a widget element, that selection is marked as
|
1475
|
+
* {@link module:engine/view/selection~Selection#isFake fake}. Additionally, the `ck-widget_selected` CSS class
|
1476
|
+
* is added to indicate that widget has been selected.
|
1477
|
+
* * The mouse and keyboard events handling on and around widget elements.
|
1478
|
+
*/ class Widget extends Plugin {
|
1479
|
+
/**
|
1480
|
+
* Holds previously selected widgets.
|
1481
|
+
*/ _previouslySelected = new Set();
|
1482
|
+
/**
|
1483
|
+
* @inheritDoc
|
1484
|
+
*/ static get pluginName() {
|
1457
1485
|
return 'Widget';
|
1458
1486
|
}
|
1459
1487
|
/**
|
1460
|
-
|
1461
|
-
|
1488
|
+
* @inheritDoc
|
1489
|
+
*/ static get requires() {
|
1462
1490
|
return [
|
1463
1491
|
WidgetTypeAround,
|
1464
1492
|
Delete
|
1465
1493
|
];
|
1466
1494
|
}
|
1467
1495
|
/**
|
1468
|
-
|
1469
|
-
|
1496
|
+
* @inheritDoc
|
1497
|
+
*/ init() {
|
1470
1498
|
const editor = this.editor;
|
1471
1499
|
const view = editor.editing.view;
|
1472
1500
|
const viewDocument = view.document;
|
@@ -1623,6 +1651,10 @@ class Widget extends Plugin {
|
|
1623
1651
|
id: 'widget',
|
1624
1652
|
label: t('Keystrokes that can be used when a widget is selected (for example: image, table, etc.)'),
|
1625
1653
|
keystrokes: [
|
1654
|
+
{
|
1655
|
+
label: t('Move focus from an editable area back to the parent widget'),
|
1656
|
+
keystroke: 'Esc'
|
1657
|
+
},
|
1626
1658
|
{
|
1627
1659
|
label: t('Insert a new paragraph directly after a widget'),
|
1628
1660
|
keystroke: 'Enter'
|
@@ -1657,8 +1689,8 @@ class Widget extends Plugin {
|
|
1657
1689
|
});
|
1658
1690
|
}
|
1659
1691
|
/**
|
1660
|
-
|
1661
|
-
|
1692
|
+
* Handles {@link module:engine/view/document~Document#event:mousedown mousedown} events on widget elements.
|
1693
|
+
*/ _onMousedown(eventInfo, domEventData) {
|
1662
1694
|
const editor = this.editor;
|
1663
1695
|
const view = editor.editing.view;
|
1664
1696
|
const viewDocument = view.document;
|
@@ -1695,8 +1727,8 @@ class Widget extends Plugin {
|
|
1695
1727
|
this._setSelectionOverElement(modelElement);
|
1696
1728
|
}
|
1697
1729
|
/**
|
1698
|
-
|
1699
|
-
|
1730
|
+
* Selects entire block content, e.g. on triple click it selects entire paragraph.
|
1731
|
+
*/ _selectBlockContent(element) {
|
1700
1732
|
const editor = this.editor;
|
1701
1733
|
const model = editor.model;
|
1702
1734
|
const mapper = editor.editing.mapper;
|
@@ -1715,14 +1747,14 @@ class Widget extends Plugin {
|
|
1715
1747
|
return true;
|
1716
1748
|
}
|
1717
1749
|
/**
|
1718
|
-
|
1719
|
-
|
1720
|
-
|
1721
|
-
|
1722
|
-
|
1723
|
-
|
1724
|
-
|
1725
|
-
|
1750
|
+
* Handles {@link module:engine/view/document~Document#event:keydown keydown} events and changes
|
1751
|
+
* the model selection when:
|
1752
|
+
*
|
1753
|
+
* * arrow key is pressed when the widget is selected,
|
1754
|
+
* * the selection is next to a widget and the widget should become selected upon the arrow key press.
|
1755
|
+
*
|
1756
|
+
* See {@link #_preventDefaultOnArrowKeyPress}.
|
1757
|
+
*/ _handleSelectionChangeOnArrowKeyPress(eventInfo, domEventData) {
|
1726
1758
|
const keyCode = domEventData.keyCode;
|
1727
1759
|
const model = this.editor.model;
|
1728
1760
|
const schema = model.schema;
|
@@ -1777,12 +1809,12 @@ class Widget extends Plugin {
|
|
1777
1809
|
}
|
1778
1810
|
}
|
1779
1811
|
/**
|
1780
|
-
|
1781
|
-
|
1782
|
-
|
1783
|
-
|
1784
|
-
|
1785
|
-
|
1812
|
+
* Handles {@link module:engine/view/document~Document#event:keydown keydown} events and prevents
|
1813
|
+
* the default browser behavior to make sure the fake selection is not being moved from a fake selection
|
1814
|
+
* container.
|
1815
|
+
*
|
1816
|
+
* See {@link #_handleSelectionChangeOnArrowKeyPress}.
|
1817
|
+
*/ _preventDefaultOnArrowKeyPress(eventInfo, domEventData) {
|
1786
1818
|
const model = this.editor.model;
|
1787
1819
|
const schema = model.schema;
|
1788
1820
|
const objectElement = model.document.selection.getSelectedElement();
|
@@ -1793,11 +1825,11 @@ class Widget extends Plugin {
|
|
1793
1825
|
}
|
1794
1826
|
}
|
1795
1827
|
/**
|
1796
|
-
|
1797
|
-
|
1798
|
-
|
1799
|
-
|
1800
|
-
|
1828
|
+
* Handles delete keys: backspace and delete.
|
1829
|
+
*
|
1830
|
+
* @param isForward Set to true if delete was performed in forward direction.
|
1831
|
+
* @returns Returns `true` if keys were handled correctly.
|
1832
|
+
*/ _handleDelete(isForward) {
|
1801
1833
|
const modelDocument = this.editor.model.document;
|
1802
1834
|
const modelSelection = modelDocument.selection;
|
1803
1835
|
// Do nothing when the read only mode is enabled.
|
@@ -1824,22 +1856,22 @@ class Widget extends Plugin {
|
|
1824
1856
|
}
|
1825
1857
|
}
|
1826
1858
|
/**
|
1827
|
-
|
1828
|
-
|
1829
|
-
|
1830
|
-
|
1859
|
+
* Sets {@link module:engine/model/selection~Selection document's selection} over given element.
|
1860
|
+
*
|
1861
|
+
* @internal
|
1862
|
+
*/ _setSelectionOverElement(element) {
|
1831
1863
|
this.editor.model.change((writer)=>{
|
1832
1864
|
writer.setSelection(writer.createRangeOn(element));
|
1833
1865
|
});
|
1834
1866
|
}
|
1835
1867
|
/**
|
1836
|
-
|
1837
|
-
|
1838
|
-
|
1839
|
-
|
1840
|
-
|
1841
|
-
|
1842
|
-
|
1868
|
+
* Checks if {@link module:engine/model/element~Element element} placed next to the current
|
1869
|
+
* {@link module:engine/model/selection~Selection model selection} exists and is marked in
|
1870
|
+
* {@link module:engine/model/schema~Schema schema} as `object`.
|
1871
|
+
*
|
1872
|
+
* @internal
|
1873
|
+
* @param forward Direction of checking.
|
1874
|
+
*/ _getObjectElementNextToSelection(forward) {
|
1843
1875
|
const model = this.editor.model;
|
1844
1876
|
const schema = model.schema;
|
1845
1877
|
const modelSelection = model.document.selection;
|
@@ -1860,16 +1892,16 @@ class Widget extends Plugin {
|
|
1860
1892
|
return null;
|
1861
1893
|
}
|
1862
1894
|
/**
|
1863
|
-
|
1864
|
-
|
1895
|
+
* Removes CSS class from previously selected widgets.
|
1896
|
+
*/ _clearPreviouslySelectedWidgets(writer) {
|
1865
1897
|
for (const widget of this._previouslySelected){
|
1866
1898
|
writer.removeClass(WIDGET_SELECTED_CLASS_NAME, widget);
|
1867
1899
|
}
|
1868
1900
|
this._previouslySelected.clear();
|
1869
1901
|
}
|
1870
1902
|
/**
|
1871
|
-
|
1872
|
-
|
1903
|
+
* Moves the document selection into the first nested editable.
|
1904
|
+
*/ _selectFirstNestedEditable() {
|
1873
1905
|
const editor = this.editor;
|
1874
1906
|
const view = this.editor.editing.view;
|
1875
1907
|
const viewDocument = view.document;
|
@@ -1890,8 +1922,8 @@ class Widget extends Plugin {
|
|
1890
1922
|
return false;
|
1891
1923
|
}
|
1892
1924
|
/**
|
1893
|
-
|
1894
|
-
|
1925
|
+
* Updates the document selection so that it selects first ancestor widget.
|
1926
|
+
*/ _selectAncestorWidget() {
|
1895
1927
|
const editor = this.editor;
|
1896
1928
|
const mapper = editor.editing.mapper;
|
1897
1929
|
const selection = editor.editing.view.document.selection;
|
@@ -1910,12 +1942,6 @@ class Widget extends Plugin {
|
|
1910
1942
|
});
|
1911
1943
|
return true;
|
1912
1944
|
}
|
1913
|
-
constructor(){
|
1914
|
-
super(...arguments);
|
1915
|
-
/**
|
1916
|
-
* Holds previously selected widgets.
|
1917
|
-
*/ this._previouslySelected = new Set();
|
1918
|
-
}
|
1919
1945
|
}
|
1920
1946
|
/**
|
1921
1947
|
* Returns `true` when element is a nested editable or is placed inside one.
|
@@ -1978,22 +2004,51 @@ class Widget extends Plugin {
|
|
1978
2004
|
return null;
|
1979
2005
|
}
|
1980
2006
|
|
1981
|
-
|
2007
|
+
/**
|
2008
|
+
* Widget toolbar repository plugin. A central point for registering widget toolbars. This plugin handles the whole
|
2009
|
+
* toolbar rendering process and exposes a concise API.
|
2010
|
+
*
|
2011
|
+
* To add a toolbar for your widget use the {@link ~WidgetToolbarRepository#register `WidgetToolbarRepository#register()`} method.
|
2012
|
+
*
|
2013
|
+
* The following example comes from the {@link module:image/imagetoolbar~ImageToolbar} plugin:
|
2014
|
+
*
|
2015
|
+
* ```ts
|
2016
|
+
* class ImageToolbar extends Plugin {
|
2017
|
+
* static get requires() {
|
2018
|
+
* return [ WidgetToolbarRepository ];
|
2019
|
+
* }
|
2020
|
+
*
|
2021
|
+
* afterInit() {
|
2022
|
+
* const editor = this.editor;
|
2023
|
+
* const widgetToolbarRepository = editor.plugins.get( WidgetToolbarRepository );
|
2024
|
+
*
|
2025
|
+
* widgetToolbarRepository.register( 'image', {
|
2026
|
+
* items: editor.config.get( 'image.toolbar' ),
|
2027
|
+
* getRelatedElement: getClosestSelectedImageWidget
|
2028
|
+
* } );
|
2029
|
+
* }
|
2030
|
+
* }
|
2031
|
+
* ```
|
2032
|
+
*/ class WidgetToolbarRepository extends Plugin {
|
2033
|
+
/**
|
2034
|
+
* A map of toolbar definitions.
|
2035
|
+
*/ _toolbarDefinitions = new Map();
|
2036
|
+
_balloon;
|
1982
2037
|
/**
|
1983
|
-
|
1984
|
-
|
2038
|
+
* @inheritDoc
|
2039
|
+
*/ static get requires() {
|
1985
2040
|
return [
|
1986
2041
|
ContextualBalloon
|
1987
2042
|
];
|
1988
2043
|
}
|
1989
2044
|
/**
|
1990
|
-
|
1991
|
-
|
2045
|
+
* @inheritDoc
|
2046
|
+
*/ static get pluginName() {
|
1992
2047
|
return 'WidgetToolbarRepository';
|
1993
2048
|
}
|
1994
2049
|
/**
|
1995
|
-
|
1996
|
-
|
2050
|
+
* @inheritDoc
|
2051
|
+
*/ init() {
|
1997
2052
|
const editor = this.editor;
|
1998
2053
|
// Disables the default balloon toolbar for all widgets.
|
1999
2054
|
if (editor.plugins.has('BalloonToolbar')) {
|
@@ -2027,35 +2082,35 @@ class WidgetToolbarRepository extends Plugin {
|
|
2027
2082
|
}
|
2028
2083
|
}
|
2029
2084
|
/**
|
2030
|
-
|
2031
|
-
|
2032
|
-
|
2033
|
-
|
2034
|
-
|
2035
|
-
|
2036
|
-
|
2037
|
-
|
2038
|
-
|
2039
|
-
|
2040
|
-
|
2041
|
-
|
2042
|
-
|
2085
|
+
* Registers toolbar in the WidgetToolbarRepository. It renders it in the `ContextualBalloon` based on the value of the invoked
|
2086
|
+
* `getRelatedElement` function. Toolbar items are gathered from `items` array.
|
2087
|
+
* The balloon's CSS class is by default `ck-toolbar-container` and may be override with the `balloonClassName` option.
|
2088
|
+
*
|
2089
|
+
* Note: This method should be called in the {@link module:core/plugin~PluginInterface#afterInit `Plugin#afterInit()`}
|
2090
|
+
* callback (or later) to make sure that the given toolbar items were already registered by other plugins.
|
2091
|
+
*
|
2092
|
+
* @param toolbarId An id for the toolbar. Used to
|
2093
|
+
* @param options.ariaLabel Label used by assistive technologies to describe this toolbar element.
|
2094
|
+
* @param options.items Array of toolbar items.
|
2095
|
+
* @param options.getRelatedElement Callback which returns an element the toolbar should be attached to.
|
2096
|
+
* @param options.balloonClassName CSS class for the widget balloon.
|
2097
|
+
*/ register(toolbarId, { ariaLabel, items, getRelatedElement, balloonClassName = 'ck-toolbar-container' }) {
|
2043
2098
|
// Trying to register a toolbar without any item.
|
2044
2099
|
if (!items.length) {
|
2045
2100
|
/**
|
2046
|
-
|
2047
|
-
|
2048
|
-
|
2049
|
-
|
2050
|
-
|
2051
|
-
|
2052
|
-
|
2053
|
-
|
2054
|
-
|
2055
|
-
|
2056
|
-
|
2057
|
-
|
2058
|
-
|
2101
|
+
* When {@link module:widget/widgettoolbarrepository~WidgetToolbarRepository#register registering} a new widget toolbar, you
|
2102
|
+
* need to provide a non-empty array with the items that will be inserted into the toolbar.
|
2103
|
+
*
|
2104
|
+
* If you see this error when integrating the editor, you likely forgot to configure one of the widget toolbars.
|
2105
|
+
*
|
2106
|
+
* See for instance:
|
2107
|
+
*
|
2108
|
+
* * {@link module:table/tableconfig~TableConfig#contentToolbar `config.table.contentToolbar`}
|
2109
|
+
* * {@link module:image/imageconfig~ImageConfig#toolbar `config.image.toolbar`}
|
2110
|
+
*
|
2111
|
+
* @error widget-toolbar-no-items
|
2112
|
+
* @param toolbarId The id of the toolbar that has not been configured correctly.
|
2113
|
+
*/ logWarning('widget-toolbar-no-items', {
|
2059
2114
|
toolbarId
|
2060
2115
|
});
|
2061
2116
|
return;
|
@@ -2066,11 +2121,11 @@ class WidgetToolbarRepository extends Plugin {
|
|
2066
2121
|
toolbarView.ariaLabel = ariaLabel || t('Widget toolbar');
|
2067
2122
|
if (this._toolbarDefinitions.has(toolbarId)) {
|
2068
2123
|
/**
|
2069
|
-
|
2070
|
-
|
2071
|
-
|
2072
|
-
|
2073
|
-
|
2124
|
+
* Toolbar with the given id was already added.
|
2125
|
+
*
|
2126
|
+
* @error widget-toolbar-duplicated
|
2127
|
+
* @param toolbarId Toolbar id.
|
2128
|
+
*/ throw new CKEditorError('widget-toolbar-duplicated', this, {
|
2074
2129
|
toolbarId
|
2075
2130
|
});
|
2076
2131
|
}
|
@@ -2097,8 +2152,8 @@ class WidgetToolbarRepository extends Plugin {
|
|
2097
2152
|
this._toolbarDefinitions.set(toolbarId, toolbarDefinition);
|
2098
2153
|
}
|
2099
2154
|
/**
|
2100
|
-
|
2101
|
-
|
2155
|
+
* Iterates over stored toolbars and makes them visible or hidden.
|
2156
|
+
*/ _updateToolbarsVisibility() {
|
2102
2157
|
let maxRelatedElementDepth = 0;
|
2103
2158
|
let deepestRelatedElement = null;
|
2104
2159
|
let deepestToolbarDefinition = null;
|
@@ -2130,18 +2185,18 @@ class WidgetToolbarRepository extends Plugin {
|
|
2130
2185
|
}
|
2131
2186
|
}
|
2132
2187
|
/**
|
2133
|
-
|
2134
|
-
|
2188
|
+
* Hides the given toolbar.
|
2189
|
+
*/ _hideToolbar(toolbarDefinition) {
|
2135
2190
|
this._balloon.remove(toolbarDefinition.view);
|
2136
2191
|
this.stopListening(this._balloon, 'change:visibleView');
|
2137
2192
|
}
|
2138
2193
|
/**
|
2139
|
-
|
2140
|
-
|
2141
|
-
|
2142
|
-
|
2143
|
-
|
2144
|
-
|
2194
|
+
* Shows up the toolbar if the toolbar is not visible.
|
2195
|
+
* Otherwise, repositions the toolbar's balloon when toolbar's view is the most top view in balloon stack.
|
2196
|
+
*
|
2197
|
+
* It might happen here that the toolbar's view is under another view. Then do nothing as the other toolbar view
|
2198
|
+
* should be still visible after the {@link module:ui/editorui/editorui~EditorUI#event:update}.
|
2199
|
+
*/ _showToolbar(toolbarDefinition, relatedElement) {
|
2145
2200
|
if (this._isToolbarVisible(toolbarDefinition)) {
|
2146
2201
|
repositionContextualBalloon(this.editor, relatedElement);
|
2147
2202
|
} else if (!this._isToolbarInBalloon(toolbarDefinition)) {
|
@@ -2174,12 +2229,6 @@ class WidgetToolbarRepository extends Plugin {
|
|
2174
2229
|
_isToolbarInBalloon(toolbar) {
|
2175
2230
|
return this._balloon.hasView(toolbar.view);
|
2176
2231
|
}
|
2177
|
-
constructor(){
|
2178
|
-
super(...arguments);
|
2179
|
-
/**
|
2180
|
-
* A map of toolbar definitions.
|
2181
|
-
*/ this._toolbarDefinitions = new Map();
|
2182
|
-
}
|
2183
2232
|
}
|
2184
2233
|
function repositionContextualBalloon(editor, relatedElement) {
|
2185
2234
|
const balloon = editor.plugins.get('ContextualBalloon');
|
@@ -2207,31 +2256,76 @@ function isWidgetSelected(selection) {
|
|
2207
2256
|
return !!(viewElement && isWidget(viewElement));
|
2208
2257
|
}
|
2209
2258
|
|
2210
|
-
|
2259
|
+
/**
|
2260
|
+
* Stores the internal state of a single resizable object.
|
2261
|
+
*/ class ResizeState extends /* #__PURE__ */ ObservableMixin() {
|
2262
|
+
/**
|
2263
|
+
* The reference point of the resizer where the dragging started. It is used to measure the distance the user cursor
|
2264
|
+
* traveled, so how much the image should be enlarged.
|
2265
|
+
* This information is only known after the DOM was rendered, so it will be updated later.
|
2266
|
+
*
|
2267
|
+
* @internal
|
2268
|
+
*/ _referenceCoordinates;
|
2269
|
+
/**
|
2270
|
+
* Resizer options.
|
2271
|
+
*/ _options;
|
2272
|
+
/**
|
2273
|
+
* The original width (pixels) of the resized object when the resize process was started.
|
2274
|
+
*
|
2275
|
+
* @readonly
|
2276
|
+
*/ _originalWidth;
|
2277
|
+
/**
|
2278
|
+
* The original height (pixels) of the resized object when the resize process was started.
|
2279
|
+
*
|
2280
|
+
* @readonly
|
2281
|
+
*/ _originalHeight;
|
2282
|
+
/**
|
2283
|
+
* The original width (percents) of the resized object when the resize process was started.
|
2284
|
+
*
|
2285
|
+
* @readonly
|
2286
|
+
*/ _originalWidthPercents;
|
2287
|
+
/**
|
2288
|
+
* A width to height ratio of the resized image.
|
2289
|
+
*
|
2290
|
+
* @readonly
|
2291
|
+
*/ _aspectRatio;
|
2292
|
+
/**
|
2293
|
+
* @param options Resizer options.
|
2294
|
+
*/ constructor(options){
|
2295
|
+
super();
|
2296
|
+
this.set('activeHandlePosition', null);
|
2297
|
+
this.set('proposedWidthPercents', null);
|
2298
|
+
this.set('proposedWidth', null);
|
2299
|
+
this.set('proposedHeight', null);
|
2300
|
+
this.set('proposedHandleHostWidth', null);
|
2301
|
+
this.set('proposedHandleHostHeight', null);
|
2302
|
+
this._options = options;
|
2303
|
+
this._referenceCoordinates = null;
|
2304
|
+
}
|
2211
2305
|
/**
|
2212
|
-
|
2213
|
-
|
2306
|
+
* The original width (pixels) of the resized object when the resize process was started.
|
2307
|
+
*/ get originalWidth() {
|
2214
2308
|
return this._originalWidth;
|
2215
2309
|
}
|
2216
2310
|
/**
|
2217
|
-
|
2218
|
-
|
2311
|
+
* The original height (pixels) of the resized object when the resize process was started.
|
2312
|
+
*/ get originalHeight() {
|
2219
2313
|
return this._originalHeight;
|
2220
2314
|
}
|
2221
2315
|
/**
|
2222
|
-
|
2223
|
-
|
2316
|
+
* The original width (percents) of the resized object when the resize process was started.
|
2317
|
+
*/ get originalWidthPercents() {
|
2224
2318
|
return this._originalWidthPercents;
|
2225
2319
|
}
|
2226
2320
|
/**
|
2227
|
-
|
2228
|
-
|
2321
|
+
* A width to height ratio of the resized image.
|
2322
|
+
*/ get aspectRatio() {
|
2229
2323
|
return this._aspectRatio;
|
2230
2324
|
}
|
2231
2325
|
/**
|
2232
|
-
|
2233
|
-
|
2234
|
-
|
2326
|
+
*
|
2327
|
+
* @param domResizeHandle The handle used to calculate the reference point.
|
2328
|
+
*/ begin(domResizeHandle, domHandleHost, domResizeHost) {
|
2235
2329
|
const clientRect = new Rect(domHandleHost);
|
2236
2330
|
this.activeHandlePosition = getHandlePosition(domResizeHandle);
|
2237
2331
|
this._referenceCoordinates = getAbsoluteBoundaryPoint(domHandleHost, getOppositePosition(this.activeHandlePosition));
|
@@ -2252,19 +2346,6 @@ class ResizeState extends ObservableMixin() {
|
|
2252
2346
|
this.proposedHandleHostWidth = newSize.handleHostWidth;
|
2253
2347
|
this.proposedHandleHostHeight = newSize.handleHostHeight;
|
2254
2348
|
}
|
2255
|
-
/**
|
2256
|
-
* @param options Resizer options.
|
2257
|
-
*/ constructor(options){
|
2258
|
-
super();
|
2259
|
-
this.set('activeHandlePosition', null);
|
2260
|
-
this.set('proposedWidthPercents', null);
|
2261
|
-
this.set('proposedWidth', null);
|
2262
|
-
this.set('proposedHeight', null);
|
2263
|
-
this.set('proposedHandleHostWidth', null);
|
2264
|
-
this.set('proposedHandleHostHeight', null);
|
2265
|
-
this._options = options;
|
2266
|
-
this._referenceCoordinates = null;
|
2267
|
-
}
|
2268
2349
|
}
|
2269
2350
|
/**
|
2270
2351
|
* Returns coordinates of the top-left corner of an element, relative to the document's top-left corner.
|
@@ -2319,33 +2400,9 @@ class ResizeState extends ObservableMixin() {
|
|
2319
2400
|
return `${replacements[parts[0]]}-${replacements[parts[1]]}`;
|
2320
2401
|
}
|
2321
2402
|
|
2322
|
-
|
2323
|
-
|
2324
|
-
|
2325
|
-
*
|
2326
|
-
* @internal
|
2327
|
-
* @param options An object defining the resizer options, used for setting the proper size label.
|
2328
|
-
* @param resizeState The `ResizeState` class instance, used for keeping the `SizeView` state up to date.
|
2329
|
-
*/ _bindToState(options, resizeState) {
|
2330
|
-
this.bind('_isVisible').to(resizeState, 'proposedWidth', resizeState, 'proposedHeight', (width, height)=>width !== null && height !== null);
|
2331
|
-
this.bind('_label').to(resizeState, 'proposedHandleHostWidth', resizeState, 'proposedHandleHostHeight', resizeState, 'proposedWidthPercents', (width, height, widthPercents)=>{
|
2332
|
-
if (options.unit === 'px') {
|
2333
|
-
return `${width}×${height}`;
|
2334
|
-
} else {
|
2335
|
-
return `${widthPercents}%`;
|
2336
|
-
}
|
2337
|
-
});
|
2338
|
-
this.bind('_viewPosition').to(resizeState, 'activeHandlePosition', resizeState, 'proposedHandleHostWidth', resizeState, 'proposedHandleHostHeight', // If the widget is too small to contain the size label, display the label above.
|
2339
|
-
(position, width, height)=>width < 50 || height < 50 ? 'above-center' : position);
|
2340
|
-
}
|
2341
|
-
/**
|
2342
|
-
* A method used for cleaning up. It removes the bindings and hides the view.
|
2343
|
-
*
|
2344
|
-
* @internal
|
2345
|
-
*/ _dismiss() {
|
2346
|
-
this.unbind();
|
2347
|
-
this._isVisible = false;
|
2348
|
-
}
|
2403
|
+
/**
|
2404
|
+
* A view displaying the proposed new element size during the resizing.
|
2405
|
+
*/ class SizeView extends View {
|
2349
2406
|
constructor(){
|
2350
2407
|
super();
|
2351
2408
|
const bind = this.bindTemplate;
|
@@ -2368,35 +2425,103 @@ class SizeView extends View {
|
|
2368
2425
|
]
|
2369
2426
|
});
|
2370
2427
|
}
|
2428
|
+
/**
|
2429
|
+
* A method used for binding the `SizeView` instance properties to the `ResizeState` instance observable properties.
|
2430
|
+
*
|
2431
|
+
* @internal
|
2432
|
+
* @param options An object defining the resizer options, used for setting the proper size label.
|
2433
|
+
* @param resizeState The `ResizeState` class instance, used for keeping the `SizeView` state up to date.
|
2434
|
+
*/ _bindToState(options, resizeState) {
|
2435
|
+
this.bind('_isVisible').to(resizeState, 'proposedWidth', resizeState, 'proposedHeight', (width, height)=>width !== null && height !== null);
|
2436
|
+
this.bind('_label').to(resizeState, 'proposedHandleHostWidth', resizeState, 'proposedHandleHostHeight', resizeState, 'proposedWidthPercents', (width, height, widthPercents)=>{
|
2437
|
+
if (options.unit === 'px') {
|
2438
|
+
return `${width}×${height}`;
|
2439
|
+
} else {
|
2440
|
+
return `${widthPercents}%`;
|
2441
|
+
}
|
2442
|
+
});
|
2443
|
+
this.bind('_viewPosition').to(resizeState, 'activeHandlePosition', resizeState, 'proposedHandleHostWidth', resizeState, 'proposedHandleHostHeight', // If the widget is too small to contain the size label, display the label above.
|
2444
|
+
(position, width, height)=>width < 50 || height < 50 ? 'above-center' : position);
|
2445
|
+
}
|
2446
|
+
/**
|
2447
|
+
* A method used for cleaning up. It removes the bindings and hides the view.
|
2448
|
+
*
|
2449
|
+
* @internal
|
2450
|
+
*/ _dismiss() {
|
2451
|
+
this.unbind();
|
2452
|
+
this._isVisible = false;
|
2453
|
+
}
|
2371
2454
|
}
|
2372
2455
|
|
2373
|
-
|
2456
|
+
/**
|
2457
|
+
* Represents a resizer for a single resizable object.
|
2458
|
+
*/ class Resizer extends /* #__PURE__ */ ObservableMixin() {
|
2459
|
+
/**
|
2460
|
+
* Stores the state of the resizable host geometry, such as the original width, the currently proposed height, etc.
|
2461
|
+
*
|
2462
|
+
* Note that a new state is created for each resize transaction.
|
2463
|
+
*/ _state;
|
2464
|
+
/**
|
2465
|
+
* A view displaying the proposed new element size during the resizing.
|
2466
|
+
*/ _sizeView;
|
2467
|
+
/**
|
2468
|
+
* Options passed to the {@link #constructor}.
|
2469
|
+
*/ _options;
|
2470
|
+
/**
|
2471
|
+
* A wrapper that is controlled by the resizer. This is usually a widget element.
|
2472
|
+
*/ _viewResizerWrapper = null;
|
2473
|
+
/**
|
2474
|
+
* The width of the resized {@link module:widget/widgetresize~ResizerOptions#viewElement viewElement} before the resizing started.
|
2475
|
+
*/ _initialViewWidth;
|
2476
|
+
/**
|
2477
|
+
* @param options Resizer options.
|
2478
|
+
*/ constructor(options){
|
2479
|
+
super();
|
2480
|
+
this._options = options;
|
2481
|
+
this.set('isEnabled', true);
|
2482
|
+
this.set('isSelected', false);
|
2483
|
+
this.bind('isVisible').to(this, 'isEnabled', this, 'isSelected', (isEnabled, isSelected)=>isEnabled && isSelected);
|
2484
|
+
this.decorate('begin');
|
2485
|
+
this.decorate('cancel');
|
2486
|
+
this.decorate('commit');
|
2487
|
+
this.decorate('updateSize');
|
2488
|
+
this.on('commit', (event)=>{
|
2489
|
+
// State might not be initialized yet. In this case, prevent further handling and make sure that the resizer is
|
2490
|
+
// cleaned up (#5195).
|
2491
|
+
if (!this.state.proposedWidth && !this.state.proposedWidthPercents) {
|
2492
|
+
this._cleanup();
|
2493
|
+
event.stop();
|
2494
|
+
}
|
2495
|
+
}, {
|
2496
|
+
priority: 'high'
|
2497
|
+
});
|
2498
|
+
}
|
2374
2499
|
/**
|
2375
|
-
|
2376
|
-
|
2377
|
-
|
2378
|
-
|
2500
|
+
* Stores the state of the resizable host geometry, such as the original width, the currently proposed height, etc.
|
2501
|
+
*
|
2502
|
+
* Note that a new state is created for each resize transaction.
|
2503
|
+
*/ get state() {
|
2379
2504
|
return this._state;
|
2380
2505
|
}
|
2381
2506
|
/**
|
2382
|
-
|
2383
|
-
|
2507
|
+
* Makes resizer visible in the UI.
|
2508
|
+
*/ show() {
|
2384
2509
|
const editingView = this._options.editor.editing.view;
|
2385
2510
|
editingView.change((writer)=>{
|
2386
2511
|
writer.removeClass('ck-hidden', this._viewResizerWrapper);
|
2387
2512
|
});
|
2388
2513
|
}
|
2389
2514
|
/**
|
2390
|
-
|
2391
|
-
|
2515
|
+
* Hides resizer in the UI.
|
2516
|
+
*/ hide() {
|
2392
2517
|
const editingView = this._options.editor.editing.view;
|
2393
2518
|
editingView.change((writer)=>{
|
2394
2519
|
writer.addClass('ck-hidden', this._viewResizerWrapper);
|
2395
2520
|
});
|
2396
2521
|
}
|
2397
2522
|
/**
|
2398
|
-
|
2399
|
-
|
2523
|
+
* Attaches the resizer to the DOM.
|
2524
|
+
*/ attach() {
|
2400
2525
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
2401
2526
|
const that = this;
|
2402
2527
|
const widgetElement = this._options.viewElement;
|
@@ -2428,23 +2553,23 @@ class Resizer extends ObservableMixin() {
|
|
2428
2553
|
});
|
2429
2554
|
}
|
2430
2555
|
/**
|
2431
|
-
|
2432
|
-
|
2433
|
-
|
2434
|
-
|
2435
|
-
|
2436
|
-
|
2437
|
-
|
2556
|
+
* Starts the resizing process.
|
2557
|
+
*
|
2558
|
+
* Creates a new {@link #state} for the current process.
|
2559
|
+
*
|
2560
|
+
* @fires begin
|
2561
|
+
* @param domResizeHandle Clicked handle.
|
2562
|
+
*/ begin(domResizeHandle) {
|
2438
2563
|
this._state = new ResizeState(this._options);
|
2439
2564
|
this._sizeView._bindToState(this._options, this.state);
|
2440
2565
|
this._initialViewWidth = this._options.viewElement.getStyle('width');
|
2441
2566
|
this.state.begin(domResizeHandle, this._getHandleHost(), this._getResizeHost());
|
2442
2567
|
}
|
2443
2568
|
/**
|
2444
|
-
|
2445
|
-
|
2446
|
-
|
2447
|
-
|
2569
|
+
* Updates the proposed size based on `domEventData`.
|
2570
|
+
*
|
2571
|
+
* @fires updateSize
|
2572
|
+
*/ updateSize(domEventData) {
|
2448
2573
|
const newSize = this._proposeNewSize(domEventData);
|
2449
2574
|
const editingView = this._options.editor.editing.view;
|
2450
2575
|
editingView.change((writer)=>{
|
@@ -2471,10 +2596,10 @@ class Resizer extends ObservableMixin() {
|
|
2471
2596
|
});
|
2472
2597
|
}
|
2473
2598
|
/**
|
2474
|
-
|
2475
|
-
|
2476
|
-
|
2477
|
-
|
2599
|
+
* Applies the geometry proposed with the resizer.
|
2600
|
+
*
|
2601
|
+
* @fires commit
|
2602
|
+
*/ commit() {
|
2478
2603
|
const unit = this._options.unit || '%';
|
2479
2604
|
const newValue = (unit === '%' ? this.state.proposedWidthPercents : this.state.proposedWidth) + unit;
|
2480
2605
|
// Both cleanup and onCommit callback are very likely to make view changes. Ensure that it is made in a single step.
|
@@ -2484,22 +2609,22 @@ class Resizer extends ObservableMixin() {
|
|
2484
2609
|
});
|
2485
2610
|
}
|
2486
2611
|
/**
|
2487
|
-
|
2488
|
-
|
2489
|
-
|
2490
|
-
|
2612
|
+
* Cancels and rejects the proposed resize dimensions, hiding the UI.
|
2613
|
+
*
|
2614
|
+
* @fires cancel
|
2615
|
+
*/ cancel() {
|
2491
2616
|
this._cleanup();
|
2492
2617
|
}
|
2493
2618
|
/**
|
2494
|
-
|
2495
|
-
|
2619
|
+
* Destroys the resizer.
|
2620
|
+
*/ destroy() {
|
2496
2621
|
this.cancel();
|
2497
2622
|
}
|
2498
2623
|
/**
|
2499
|
-
|
2500
|
-
|
2501
|
-
|
2502
|
-
|
2624
|
+
* Redraws the resizer.
|
2625
|
+
*
|
2626
|
+
* @param handleHostRect Handle host rectangle might be given to improve performance.
|
2627
|
+
*/ redraw(handleHostRect) {
|
2503
2628
|
const domWrapper = this._domResizerWrapper;
|
2504
2629
|
// Refresh only if resizer exists in the DOM.
|
2505
2630
|
if (!existsInDom(domWrapper)) {
|
@@ -2554,8 +2679,8 @@ class Resizer extends ObservableMixin() {
|
|
2554
2679
|
return domElement.classList.contains('ck-widget__resizer__handle');
|
2555
2680
|
}
|
2556
2681
|
/**
|
2557
|
-
|
2558
|
-
|
2682
|
+
* Cleans up the context state.
|
2683
|
+
*/ _cleanup() {
|
2559
2684
|
this._sizeView._dismiss();
|
2560
2685
|
const editingView = this._options.editor.editing.view;
|
2561
2686
|
editingView.change((writer)=>{
|
@@ -2563,10 +2688,10 @@ class Resizer extends ObservableMixin() {
|
|
2563
2688
|
});
|
2564
2689
|
}
|
2565
2690
|
/**
|
2566
|
-
|
2567
|
-
|
2568
|
-
|
2569
|
-
|
2691
|
+
* Calculates the proposed size as the resize handles are dragged.
|
2692
|
+
*
|
2693
|
+
* @param domEventData Event data that caused the size update request. It should be used to calculate the proposed size.
|
2694
|
+
*/ _proposeNewSize(domEventData) {
|
2570
2695
|
const state = this.state;
|
2571
2696
|
const currentCoordinates = extractCoordinates(domEventData);
|
2572
2697
|
const isCentered = this._options.isCentered ? this._options.isCentered(this) : true;
|
@@ -2612,37 +2737,37 @@ class Resizer extends ObservableMixin() {
|
|
2612
2737
|
};
|
2613
2738
|
}
|
2614
2739
|
/**
|
2615
|
-
|
2616
|
-
|
2617
|
-
|
2618
|
-
|
2740
|
+
* Obtains the resize host.
|
2741
|
+
*
|
2742
|
+
* Resize host is an object that receives dimensions which are the result of resizing.
|
2743
|
+
*/ _getResizeHost() {
|
2619
2744
|
const widgetWrapper = this._domResizerWrapper.parentElement;
|
2620
2745
|
return this._options.getResizeHost(widgetWrapper);
|
2621
2746
|
}
|
2622
2747
|
/**
|
2623
|
-
|
2624
|
-
|
2625
|
-
|
2626
|
-
|
2627
|
-
|
2628
|
-
|
2629
|
-
|
2748
|
+
* Obtains the handle host.
|
2749
|
+
*
|
2750
|
+
* Handle host is an object that the handles are aligned to.
|
2751
|
+
*
|
2752
|
+
* Handle host will not always be an entire widget itself. Take an image as an example. The image widget
|
2753
|
+
* contains an image and a caption. Only the image should be surrounded with handles.
|
2754
|
+
*/ _getHandleHost() {
|
2630
2755
|
const widgetWrapper = this._domResizerWrapper.parentElement;
|
2631
2756
|
return this._options.getHandleHost(widgetWrapper);
|
2632
2757
|
}
|
2633
2758
|
/**
|
2634
|
-
|
2635
|
-
|
2636
|
-
|
2637
|
-
|
2638
|
-
|
2759
|
+
* DOM container of the entire resize UI.
|
2760
|
+
*
|
2761
|
+
* Note that this property will have a value only after the element bound with the resizer is rendered
|
2762
|
+
* (otherwise `null`).
|
2763
|
+
*/ get _domResizerWrapper() {
|
2639
2764
|
return this._options.editor.editing.view.domConverter.mapViewToDom(this._viewResizerWrapper);
|
2640
2765
|
}
|
2641
2766
|
/**
|
2642
|
-
|
2643
|
-
|
2644
|
-
|
2645
|
-
|
2767
|
+
* Renders the resize handles in the DOM.
|
2768
|
+
*
|
2769
|
+
* @param domElement The resizer wrapper.
|
2770
|
+
*/ _appendHandles(domElement) {
|
2646
2771
|
const resizerPositions = [
|
2647
2772
|
'top-left',
|
2648
2773
|
'top-right',
|
@@ -2659,39 +2784,13 @@ class Resizer extends ObservableMixin() {
|
|
2659
2784
|
}
|
2660
2785
|
}
|
2661
2786
|
/**
|
2662
|
-
|
2663
|
-
|
2787
|
+
* Sets up the {@link #_sizeView} property and adds it to the passed `domElement`.
|
2788
|
+
*/ _appendSizeUI(domElement) {
|
2664
2789
|
this._sizeView = new SizeView();
|
2665
2790
|
// Make sure icon#element is rendered before passing to appendChild().
|
2666
2791
|
this._sizeView.render();
|
2667
2792
|
domElement.appendChild(this._sizeView.element);
|
2668
2793
|
}
|
2669
|
-
/**
|
2670
|
-
* @param options Resizer options.
|
2671
|
-
*/ constructor(options){
|
2672
|
-
super();
|
2673
|
-
/**
|
2674
|
-
* A wrapper that is controlled by the resizer. This is usually a widget element.
|
2675
|
-
*/ this._viewResizerWrapper = null;
|
2676
|
-
this._options = options;
|
2677
|
-
this.set('isEnabled', true);
|
2678
|
-
this.set('isSelected', false);
|
2679
|
-
this.bind('isVisible').to(this, 'isEnabled', this, 'isSelected', (isEnabled, isSelected)=>isEnabled && isSelected);
|
2680
|
-
this.decorate('begin');
|
2681
|
-
this.decorate('cancel');
|
2682
|
-
this.decorate('commit');
|
2683
|
-
this.decorate('updateSize');
|
2684
|
-
this.on('commit', (event)=>{
|
2685
|
-
// State might not be initialized yet. In this case, prevent further handling and make sure that the resizer is
|
2686
|
-
// cleaned up (#5195).
|
2687
|
-
if (!this.state.proposedWidth && !this.state.proposedWidthPercents) {
|
2688
|
-
this._cleanup();
|
2689
|
-
event.stop();
|
2690
|
-
}
|
2691
|
-
}, {
|
2692
|
-
priority: 'high'
|
2693
|
-
});
|
2694
|
-
}
|
2695
2794
|
}
|
2696
2795
|
/**
|
2697
2796
|
* @param resizerPosition Expected resizer position like `"top-left"`, `"bottom-right"`.
|
@@ -2709,15 +2808,24 @@ function existsInDom(element) {
|
|
2709
2808
|
return element && element.ownerDocument && element.ownerDocument.contains(element);
|
2710
2809
|
}
|
2711
2810
|
|
2712
|
-
|
2811
|
+
/**
|
2812
|
+
* The widget resize feature plugin.
|
2813
|
+
*
|
2814
|
+
* Use the {@link module:widget/widgetresize~WidgetResize#attachTo} method to create a resizer for the specified widget.
|
2815
|
+
*/ class WidgetResize extends Plugin {
|
2713
2816
|
/**
|
2714
|
-
|
2715
|
-
|
2817
|
+
* A map of resizers created using this plugin instance.
|
2818
|
+
*/ _resizers = new Map();
|
2819
|
+
_observer;
|
2820
|
+
_redrawSelectedResizerThrottled;
|
2821
|
+
/**
|
2822
|
+
* @inheritDoc
|
2823
|
+
*/ static get pluginName() {
|
2716
2824
|
return 'WidgetResize';
|
2717
2825
|
}
|
2718
2826
|
/**
|
2719
|
-
|
2720
|
-
|
2827
|
+
* @inheritDoc
|
2828
|
+
*/ init() {
|
2721
2829
|
const editing = this.editor.editing;
|
2722
2830
|
const domDocument = global.window.document;
|
2723
2831
|
this.set('selectedResizer', null);
|
@@ -2759,15 +2867,15 @@ class WidgetResize extends Plugin {
|
|
2759
2867
|
});
|
2760
2868
|
}
|
2761
2869
|
/**
|
2762
|
-
|
2763
|
-
|
2870
|
+
* Redraws the selected resizer if there is any selected resizer and if it is visible.
|
2871
|
+
*/ redrawSelectedResizer() {
|
2764
2872
|
if (this.selectedResizer && this.selectedResizer.isVisible) {
|
2765
2873
|
this.selectedResizer.redraw();
|
2766
2874
|
}
|
2767
2875
|
}
|
2768
2876
|
/**
|
2769
|
-
|
2770
|
-
|
2877
|
+
* @inheritDoc
|
2878
|
+
*/ destroy() {
|
2771
2879
|
super.destroy();
|
2772
2880
|
this._observer.stopListening();
|
2773
2881
|
for (const resizer of this._resizers.values()){
|
@@ -2776,23 +2884,23 @@ class WidgetResize extends Plugin {
|
|
2776
2884
|
this._redrawSelectedResizerThrottled.cancel();
|
2777
2885
|
}
|
2778
2886
|
/**
|
2779
|
-
|
2780
|
-
|
2887
|
+
* Marks resizer as selected.
|
2888
|
+
*/ select(resizer) {
|
2781
2889
|
this.deselect();
|
2782
2890
|
this.selectedResizer = resizer;
|
2783
2891
|
this.selectedResizer.isSelected = true;
|
2784
2892
|
}
|
2785
2893
|
/**
|
2786
|
-
|
2787
|
-
|
2894
|
+
* Deselects currently set resizer.
|
2895
|
+
*/ deselect() {
|
2788
2896
|
if (this.selectedResizer) {
|
2789
2897
|
this.selectedResizer.isSelected = false;
|
2790
2898
|
}
|
2791
2899
|
this.selectedResizer = null;
|
2792
2900
|
}
|
2793
2901
|
/**
|
2794
|
-
|
2795
|
-
|
2902
|
+
* @param options Resizer options.
|
2903
|
+
*/ attachTo(options) {
|
2796
2904
|
const resizer = new Resizer(options);
|
2797
2905
|
const plugins = this.editor.plugins;
|
2798
2906
|
resizer.attach();
|
@@ -2826,15 +2934,15 @@ class WidgetResize extends Plugin {
|
|
2826
2934
|
return resizer;
|
2827
2935
|
}
|
2828
2936
|
/**
|
2829
|
-
|
2830
|
-
|
2831
|
-
|
2832
|
-
|
2937
|
+
* Returns a resizer created for a given view element (widget element).
|
2938
|
+
*
|
2939
|
+
* @param viewElement View element associated with the resizer.
|
2940
|
+
*/ getResizerByViewElement(viewElement) {
|
2833
2941
|
return this._resizers.get(viewElement);
|
2834
2942
|
}
|
2835
2943
|
/**
|
2836
|
-
|
2837
|
-
|
2944
|
+
* Returns a resizer that contains a given resize handle.
|
2945
|
+
*/ _getResizerByHandle(domResizeHandle) {
|
2838
2946
|
for (const resizer of this._resizers.values()){
|
2839
2947
|
if (resizer.containsHandle(domResizeHandle)) {
|
2840
2948
|
return resizer;
|
@@ -2842,8 +2950,8 @@ class WidgetResize extends Plugin {
|
|
2842
2950
|
}
|
2843
2951
|
}
|
2844
2952
|
/**
|
2845
|
-
|
2846
|
-
|
2953
|
+
* @param domEventData Native DOM event.
|
2954
|
+
*/ _mouseDownListener(event, domEventData) {
|
2847
2955
|
const resizeHandle = domEventData.domTarget;
|
2848
2956
|
if (!Resizer.isResizeHandle(resizeHandle)) {
|
2849
2957
|
return;
|
@@ -2857,8 +2965,8 @@ class WidgetResize extends Plugin {
|
|
2857
2965
|
}
|
2858
2966
|
}
|
2859
2967
|
/**
|
2860
|
-
|
2861
|
-
|
2968
|
+
* @param domEventData Native DOM event.
|
2969
|
+
*/ _mouseMoveListener(event, domEventData) {
|
2862
2970
|
if (this._activeResizer) {
|
2863
2971
|
this._activeResizer.updateSize(domEventData);
|
2864
2972
|
}
|
@@ -2869,12 +2977,6 @@ class WidgetResize extends Plugin {
|
|
2869
2977
|
this._activeResizer = null;
|
2870
2978
|
}
|
2871
2979
|
}
|
2872
|
-
constructor(){
|
2873
|
-
super(...arguments);
|
2874
|
-
/**
|
2875
|
-
* A map of resizers created using this plugin instance.
|
2876
|
-
*/ this._resizers = new Map();
|
2877
|
-
}
|
2878
2980
|
}
|
2879
2981
|
|
2880
2982
|
export { WIDGET_CLASS_NAME, WIDGET_SELECTED_CLASS_NAME, Widget, WidgetResize, WidgetToolbarRepository, WidgetTypeAround, calculateResizeHostAncestorWidth, calculateResizeHostPercentageWidth, findOptimalInsertionRange, getLabel, isWidget, setHighlightHandling, setLabel, toWidget, toWidgetEditable, viewToModelPositionOutsideModelElement };
|