@ckeditor/ckeditor5-media-embed 0.0.0-nightly-20240602.0 → 0.0.0-nightly-20240604.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.

Potentially problematic release.


This version of @ckeditor/ckeditor5-media-embed might be problematic. Click here for more details.

@@ -10,7 +10,6 @@
10
10
  * @module media-embed/mediaembedui
11
11
  */
12
12
  import { Plugin } from 'ckeditor5/src/core.js';
13
- import { Dialog } from 'ckeditor5/src/ui.js';
14
13
  import MediaEmbedEditing from './mediaembedediting.js';
15
14
  /**
16
15
  * The media embed UI plugin.
@@ -19,19 +18,14 @@ export default class MediaEmbedUI extends Plugin {
19
18
  /**
20
19
  * @inheritDoc
21
20
  */
22
- static get requires(): readonly [typeof MediaEmbedEditing, typeof Dialog];
21
+ static get requires(): readonly [typeof MediaEmbedEditing];
23
22
  /**
24
23
  * @inheritDoc
25
24
  */
26
25
  static get pluginName(): "MediaEmbedUI";
27
- private _formView;
28
26
  /**
29
27
  * @inheritDoc
30
28
  */
31
29
  init(): void;
32
- /**
33
- * Creates a button for menu bar that will show media embed dialog.
34
- */
35
- private _createDialogButton;
36
- private _showDialog;
30
+ private _setUpDropdown;
37
31
  }
@@ -9,7 +9,7 @@
9
9
  /**
10
10
  * @module media-embed/ui/mediaformview
11
11
  */
12
- import { type InputTextView, LabeledFieldView, View } from 'ckeditor5/src/ui.js';
12
+ import { type InputTextView, ButtonView, LabeledFieldView, View } from 'ckeditor5/src/ui.js';
13
13
  import { FocusTracker, KeystrokeHandler, type Locale } from 'ckeditor5/src/utils.js';
14
14
  import '@ckeditor/ckeditor5-ui/theme/components/responsive-form/responsiveform.css';
15
15
  import '../../theme/mediaform.css';
@@ -35,6 +35,22 @@ export default class MediaFormView extends View {
35
35
  * The URL input view.
36
36
  */
37
37
  urlInputView: LabeledFieldView<InputTextView>;
38
+ /**
39
+ * The Save button view.
40
+ */
41
+ saveButtonView: ButtonView;
42
+ /**
43
+ * The Cancel button view.
44
+ */
45
+ cancelButtonView: ButtonView;
46
+ /**
47
+ * A collection of views that can be focused in the form.
48
+ */
49
+ private readonly _focusables;
50
+ /**
51
+ * Helps cycling over {@link #_focusables} in the form.
52
+ */
53
+ private readonly _focusCycler;
38
54
  /**
39
55
  * An array of form validators used by {@link #isValid}.
40
56
  */
@@ -62,7 +78,7 @@ export default class MediaFormView extends View {
62
78
  */
63
79
  destroy(): void;
64
80
  /**
65
- * Focuses the {@link #urlInputView}.
81
+ * Focuses the fist {@link #_focusables} in the form.
66
82
  */
67
83
  focus(): void;
68
84
  /**
@@ -90,4 +106,14 @@ export default class MediaFormView extends View {
90
106
  * @returns Labeled input view instance.
91
107
  */
92
108
  private _createUrlInput;
109
+ /**
110
+ * Creates a button view.
111
+ *
112
+ * @param label The button label.
113
+ * @param icon The button icon.
114
+ * @param className The additional button CSS class name.
115
+ * @param eventName An event name that the `ButtonView#execute` event will be delegated to.
116
+ * @returns The button view instance.
117
+ */
118
+ private _createButton;
93
119
  }
@@ -6,7 +6,6 @@
6
6
  "The URL must not be empty.": "An error message that informs about an empty value in the URL input.",
7
7
  "This media URL is not supported.": "An error message that informs about unsupported media URL.",
8
8
  "Insert media": "Toolbar button tooltip for the Media Embed feature.",
9
- "Media": "Label describing type of the inserted content (e.g. 'insert media').",
10
9
  "Media toolbar": "The label used by assistive technologies describing an image toolbar attached to an image widget.",
11
10
  "Open media in new tab": "A tooltip displayed when the user hovers a non-previewable media URL in the editor content."
12
11
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ckeditor/ckeditor5-media-embed",
3
- "version": "0.0.0-nightly-20240602.0",
3
+ "version": "0.0.0-nightly-20240604.0",
4
4
  "description": "Media embed feature for CKEditor 5.",
5
5
  "keywords": [
6
6
  "ckeditor",
@@ -13,8 +13,8 @@
13
13
  "type": "module",
14
14
  "main": "src/index.js",
15
15
  "dependencies": {
16
- "@ckeditor/ckeditor5-ui": "0.0.0-nightly-20240602.0",
17
- "ckeditor5": "0.0.0-nightly-20240602.0"
16
+ "@ckeditor/ckeditor5-ui": "0.0.0-nightly-20240604.0",
17
+ "ckeditor5": "0.0.0-nightly-20240604.0"
18
18
  },
19
19
  "author": "CKSource (http://cksource.com/)",
20
20
  "license": "GPL-2.0-or-later",
@@ -6,7 +6,6 @@
6
6
  * @module media-embed/mediaembedui
7
7
  */
8
8
  import { Plugin } from 'ckeditor5/src/core.js';
9
- import { Dialog } from 'ckeditor5/src/ui.js';
10
9
  import MediaEmbedEditing from './mediaembedediting.js';
11
10
  /**
12
11
  * The media embed UI plugin.
@@ -15,19 +14,14 @@ export default class MediaEmbedUI extends Plugin {
15
14
  /**
16
15
  * @inheritDoc
17
16
  */
18
- static get requires(): readonly [typeof MediaEmbedEditing, typeof Dialog];
17
+ static get requires(): readonly [typeof MediaEmbedEditing];
19
18
  /**
20
19
  * @inheritDoc
21
20
  */
22
21
  static get pluginName(): "MediaEmbedUI";
23
- private _formView;
24
22
  /**
25
23
  * @inheritDoc
26
24
  */
27
25
  init(): void;
28
- /**
29
- * Creates a button for menu bar that will show media embed dialog.
30
- */
31
- private _createDialogButton;
32
- private _showDialog;
26
+ private _setUpDropdown;
33
27
  }
@@ -6,7 +6,7 @@
6
6
  * @module media-embed/mediaembedui
7
7
  */
8
8
  import { Plugin } from 'ckeditor5/src/core.js';
9
- import { ButtonView, CssTransitionDisablerMixin, MenuBarMenuListItemButtonView, Dialog } from 'ckeditor5/src/ui.js';
9
+ import { createDropdown, CssTransitionDisablerMixin } from 'ckeditor5/src/ui.js';
10
10
  import MediaFormView from './ui/mediaformview.js';
11
11
  import MediaEmbedEditing from './mediaembedediting.js';
12
12
  import mediaIcon from '../theme/icons/media.svg';
@@ -18,7 +18,7 @@ export default class MediaEmbedUI extends Plugin {
18
18
  * @inheritDoc
19
19
  */
20
20
  static get requires() {
21
- return [MediaEmbedEditing, Dialog];
21
+ return [MediaEmbedEditing];
22
22
  }
23
23
  /**
24
24
  * @inheritDoc
@@ -31,78 +31,59 @@ export default class MediaEmbedUI extends Plugin {
31
31
  */
32
32
  init() {
33
33
  const editor = this.editor;
34
- editor.ui.componentFactory.add('mediaEmbed', () => {
35
- const t = this.editor.locale.t;
36
- const button = this._createDialogButton(ButtonView);
37
- button.tooltip = true;
38
- button.label = t('Insert media');
39
- return button;
40
- });
41
- editor.ui.componentFactory.add('menuBar:mediaEmbed', () => {
42
- const t = this.editor.locale.t;
43
- const button = this._createDialogButton(MenuBarMenuListItemButtonView);
44
- button.label = t('Media');
45
- return button;
46
- });
47
- }
48
- /**
49
- * Creates a button for menu bar that will show media embed dialog.
50
- */
51
- _createDialogButton(ButtonClass) {
52
- const editor = this.editor;
53
- const buttonView = new ButtonClass(editor.locale);
54
34
  const command = editor.commands.get('mediaEmbed');
55
- const dialogPlugin = this.editor.plugins.get('Dialog');
56
- buttonView.icon = mediaIcon;
57
- buttonView.bind('isEnabled').to(command, 'isEnabled');
58
- buttonView.on('execute', () => {
59
- if (dialogPlugin.id === 'mediaEmbed') {
60
- dialogPlugin.hide();
61
- }
62
- else {
63
- this._showDialog();
64
- }
35
+ editor.ui.componentFactory.add('mediaEmbed', locale => {
36
+ const dropdown = createDropdown(locale);
37
+ this._setUpDropdown(dropdown, command);
38
+ return dropdown;
65
39
  });
66
- return buttonView;
67
40
  }
68
- _showDialog() {
41
+ _setUpDropdown(dropdown, command) {
69
42
  const editor = this.editor;
70
- const dialog = editor.plugins.get('Dialog');
71
- const command = editor.commands.get('mediaEmbed');
72
- const t = editor.locale.t;
73
- if (!this._formView) {
74
- const registry = editor.plugins.get(MediaEmbedEditing).registry;
75
- this._formView = new (CssTransitionDisablerMixin(MediaFormView))(getFormValidators(editor.t, registry), editor.locale);
76
- }
77
- dialog.show({
78
- id: 'mediaEmbed',
79
- title: t('Insert media'),
80
- content: this._formView,
81
- isModal: true,
82
- onShow: () => {
83
- this._formView.url = command.value || '';
84
- this._formView.resetFormStatus();
85
- this._formView.urlInputView.fieldView.select();
86
- },
87
- actionButtons: [
88
- {
89
- label: t('Cancel'),
90
- withText: true,
91
- onExecute: () => dialog.hide()
92
- },
93
- {
94
- label: t('Accept'),
95
- class: 'ck-button-action',
96
- withText: true,
97
- onExecute: () => {
98
- if (this._formView.isValid()) {
99
- editor.execute('mediaEmbed', this._formView.url);
100
- dialog.hide();
101
- editor.editing.view.focus();
102
- }
103
- }
43
+ const t = editor.t;
44
+ const button = dropdown.buttonView;
45
+ const registry = editor.plugins.get(MediaEmbedEditing).registry;
46
+ dropdown.once('change:isOpen', () => {
47
+ const form = new (CssTransitionDisablerMixin(MediaFormView))(getFormValidators(editor.t, registry), editor.locale);
48
+ dropdown.panelView.children.add(form);
49
+ // Note: Use the low priority to make sure the following listener starts working after the
50
+ // default action of the drop-down is executed (i.e. the panel showed up). Otherwise, the
51
+ // invisible form/input cannot be focused/selected.
52
+ button.on('open', () => {
53
+ form.disableCssTransitions();
54
+ // Make sure that each time the panel shows up, the URL field remains in sync with the value of
55
+ // the command. If the user typed in the input, then canceled (`urlInputView#fieldView#value` stays
56
+ // unaltered) and re-opened it without changing the value of the media command (e.g. because they
57
+ // didn't change the selection), they would see the old value instead of the actual value of the
58
+ // command.
59
+ form.url = command.value || '';
60
+ form.urlInputView.fieldView.select();
61
+ form.enableCssTransitions();
62
+ }, { priority: 'low' });
63
+ dropdown.on('submit', () => {
64
+ if (form.isValid()) {
65
+ editor.execute('mediaEmbed', form.url);
66
+ editor.editing.view.focus();
104
67
  }
105
- ]
68
+ });
69
+ dropdown.on('change:isOpen', () => form.resetFormStatus());
70
+ dropdown.on('cancel', () => {
71
+ editor.editing.view.focus();
72
+ });
73
+ form.delegate('submit', 'cancel').to(dropdown);
74
+ form.urlInputView.fieldView.bind('value').to(command, 'value');
75
+ // Update balloon position when form error changes.
76
+ form.urlInputView.on('change:errorText', () => {
77
+ editor.ui.update();
78
+ });
79
+ // Form elements should be read-only when corresponding commands are disabled.
80
+ form.urlInputView.bind('isEnabled').to(command, 'isEnabled');
81
+ });
82
+ dropdown.bind('isEnabled').to(command);
83
+ button.set({
84
+ label: t('Insert media'),
85
+ icon: mediaIcon,
86
+ tooltip: true
106
87
  });
107
88
  }
108
89
  }
@@ -5,7 +5,7 @@
5
5
  /**
6
6
  * @module media-embed/ui/mediaformview
7
7
  */
8
- import { type InputTextView, LabeledFieldView, View } from 'ckeditor5/src/ui.js';
8
+ import { type InputTextView, ButtonView, LabeledFieldView, View } from 'ckeditor5/src/ui.js';
9
9
  import { FocusTracker, KeystrokeHandler, type Locale } from 'ckeditor5/src/utils.js';
10
10
  import '@ckeditor/ckeditor5-ui/theme/components/responsive-form/responsiveform.css';
11
11
  import '../../theme/mediaform.css';
@@ -31,6 +31,22 @@ export default class MediaFormView extends View {
31
31
  * The URL input view.
32
32
  */
33
33
  urlInputView: LabeledFieldView<InputTextView>;
34
+ /**
35
+ * The Save button view.
36
+ */
37
+ saveButtonView: ButtonView;
38
+ /**
39
+ * The Cancel button view.
40
+ */
41
+ cancelButtonView: ButtonView;
42
+ /**
43
+ * A collection of views that can be focused in the form.
44
+ */
45
+ private readonly _focusables;
46
+ /**
47
+ * Helps cycling over {@link #_focusables} in the form.
48
+ */
49
+ private readonly _focusCycler;
34
50
  /**
35
51
  * An array of form validators used by {@link #isValid}.
36
52
  */
@@ -58,7 +74,7 @@ export default class MediaFormView extends View {
58
74
  */
59
75
  destroy(): void;
60
76
  /**
61
- * Focuses the {@link #urlInputView}.
77
+ * Focuses the fist {@link #_focusables} in the form.
62
78
  */
63
79
  focus(): void;
64
80
  /**
@@ -86,4 +102,14 @@ export default class MediaFormView extends View {
86
102
  * @returns Labeled input view instance.
87
103
  */
88
104
  private _createUrlInput;
105
+ /**
106
+ * Creates a button view.
107
+ *
108
+ * @param label The button label.
109
+ * @param icon The button icon.
110
+ * @param className The additional button CSS class name.
111
+ * @param eventName An event name that the `ButtonView#execute` event will be delegated to.
112
+ * @returns The button view instance.
113
+ */
114
+ private _createButton;
89
115
  }
@@ -5,8 +5,9 @@
5
5
  /**
6
6
  * @module media-embed/ui/mediaformview
7
7
  */
8
- import { LabeledFieldView, View, createLabeledInputText, submitHandler } from 'ckeditor5/src/ui.js';
8
+ import { ButtonView, FocusCycler, LabeledFieldView, View, ViewCollection, createLabeledInputText, submitHandler } from 'ckeditor5/src/ui.js';
9
9
  import { FocusTracker, KeystrokeHandler } from 'ckeditor5/src/utils.js';
10
+ import { icons } from 'ckeditor5/src/core.js';
10
11
  // See: #8833.
11
12
  // eslint-disable-next-line ckeditor5-rules/ckeditor-imports
12
13
  import '@ckeditor/ckeditor5-ui/theme/components/responsive-form/responsiveform.css';
@@ -23,10 +24,26 @@ export default class MediaFormView extends View {
23
24
  */
24
25
  constructor(validators, locale) {
25
26
  super(locale);
27
+ const t = locale.t;
26
28
  this.focusTracker = new FocusTracker();
27
29
  this.keystrokes = new KeystrokeHandler();
28
30
  this.set('mediaURLInputValue', '');
29
31
  this.urlInputView = this._createUrlInput();
32
+ this.saveButtonView = this._createButton(t('Save'), icons.check, 'ck-button-save');
33
+ this.saveButtonView.type = 'submit';
34
+ this.cancelButtonView = this._createButton(t('Cancel'), icons.cancel, 'ck-button-cancel', 'cancel');
35
+ this._focusables = new ViewCollection();
36
+ this._focusCycler = new FocusCycler({
37
+ focusables: this._focusables,
38
+ focusTracker: this.focusTracker,
39
+ keystrokeHandler: this.keystrokes,
40
+ actions: {
41
+ // Navigate form fields backwards using the <kbd>Shift</kbd> + <kbd>Tab</kbd> keystroke.
42
+ focusPrevious: 'shift + tab',
43
+ // Navigate form fields forwards using the <kbd>Tab</kbd> key.
44
+ focusNext: 'tab'
45
+ }
46
+ });
30
47
  this._validators = validators;
31
48
  this.setTemplate({
32
49
  tag: 'form',
@@ -39,7 +56,9 @@ export default class MediaFormView extends View {
39
56
  tabindex: '-1'
40
57
  },
41
58
  children: [
42
- this.urlInputView
59
+ this.urlInputView,
60
+ this.saveButtonView,
61
+ this.cancelButtonView
43
62
  ]
44
63
  });
45
64
  }
@@ -51,10 +70,27 @@ export default class MediaFormView extends View {
51
70
  submitHandler({
52
71
  view: this
53
72
  });
54
- // Register the view in the focus tracker.
55
- this.focusTracker.add(this.urlInputView.element);
73
+ const childViews = [
74
+ this.urlInputView,
75
+ this.saveButtonView,
76
+ this.cancelButtonView
77
+ ];
78
+ childViews.forEach(v => {
79
+ // Register the view as focusable.
80
+ this._focusables.add(v);
81
+ // Register the view in the focus tracker.
82
+ this.focusTracker.add(v.element);
83
+ });
56
84
  // Start listening for the keystrokes coming from #element.
57
85
  this.keystrokes.listenTo(this.element);
86
+ const stopPropagation = (data) => data.stopPropagation();
87
+ // Since the form is in the dropdown panel which is a child of the toolbar, the toolbar's
88
+ // keystroke handler would take over the key management in the URL input. We need to prevent
89
+ // this ASAP. Otherwise, the basic caret movement using the arrow keys will be impossible.
90
+ this.keystrokes.set('arrowright', stopPropagation);
91
+ this.keystrokes.set('arrowleft', stopPropagation);
92
+ this.keystrokes.set('arrowup', stopPropagation);
93
+ this.keystrokes.set('arrowdown', stopPropagation);
58
94
  }
59
95
  /**
60
96
  * @inheritDoc
@@ -65,10 +101,10 @@ export default class MediaFormView extends View {
65
101
  this.keystrokes.destroy();
66
102
  }
67
103
  /**
68
- * Focuses the {@link #urlInputView}.
104
+ * Focuses the fist {@link #_focusables} in the form.
69
105
  */
70
106
  focus() {
71
- this.urlInputView.focus();
107
+ this._focusCycler.focusFirst();
72
108
  }
73
109
  /**
74
110
  * The native DOM `value` of the {@link #urlInputView} element.
@@ -80,7 +116,7 @@ export default class MediaFormView extends View {
80
116
  return this.urlInputView.fieldView.element.value.trim();
81
117
  }
82
118
  set url(url) {
83
- this.urlInputView.fieldView.value = url.trim();
119
+ this.urlInputView.fieldView.element.value = url.trim();
84
120
  }
85
121
  /**
86
122
  * Validates the form and returns `false` when some fields are invalid.
@@ -121,7 +157,6 @@ export default class MediaFormView extends View {
121
157
  this._urlInputViewInfoTip = t('Tip: Paste the URL into the content to embed faster.');
122
158
  labeledInput.label = t('Media URL');
123
159
  labeledInput.infoText = this._urlInputViewInfoDefault;
124
- inputField.inputMode = 'url';
125
160
  inputField.on('input', () => {
126
161
  // Display the tip text only when there is some value. Otherwise fall back to the default info text.
127
162
  labeledInput.infoText = inputField.element.value ? this._urlInputViewInfoTip : this._urlInputViewInfoDefault;
@@ -129,4 +164,30 @@ export default class MediaFormView extends View {
129
164
  });
130
165
  return labeledInput;
131
166
  }
167
+ /**
168
+ * Creates a button view.
169
+ *
170
+ * @param label The button label.
171
+ * @param icon The button icon.
172
+ * @param className The additional button CSS class name.
173
+ * @param eventName An event name that the `ButtonView#execute` event will be delegated to.
174
+ * @returns The button view instance.
175
+ */
176
+ _createButton(label, icon, className, eventName) {
177
+ const button = new ButtonView(this.locale);
178
+ button.set({
179
+ label,
180
+ icon,
181
+ tooltip: true
182
+ });
183
+ button.extendTemplate({
184
+ attributes: {
185
+ class: className
186
+ }
187
+ });
188
+ if (eventName) {
189
+ button.delegate('execute').to(this, eventName);
190
+ }
191
+ return button;
192
+ }
132
193
  }
@@ -10,21 +10,15 @@
10
10
  align-items: flex-start;
11
11
  flex-direction: row;
12
12
  flex-wrap: nowrap;
13
- width: 400px;
14
13
 
15
14
  & .ck-labeled-field-view {
16
15
  display: inline-block;
17
- width: 100%;
18
16
  }
19
17
 
20
18
  & .ck-label {
21
19
  display: none;
22
20
  }
23
21
 
24
- & .ck-input {
25
- width: 100%;
26
- }
27
-
28
22
  @mixin ck-media-phone {
29
23
  flex-wrap: wrap;
30
24