jodit 4.18.3 → 4.18.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 (187) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/es2015/jodit.css +60 -1
  3. package/es2015/jodit.fat.min.css +1 -1
  4. package/es2015/jodit.fat.min.js +35 -10
  5. package/es2015/jodit.js +968 -152
  6. package/es2015/jodit.min.css +1 -1
  7. package/es2015/jodit.min.js +41 -16
  8. package/es2015/plugins/debug/debug.css +1 -1
  9. package/es2015/plugins/debug/debug.js +1 -1
  10. package/es2015/plugins/debug/debug.min.js +1 -1
  11. package/es2015/plugins/speech-recognize/speech-recognize.css +1 -1
  12. package/es2015/plugins/speech-recognize/speech-recognize.js +1 -1
  13. package/es2015/plugins/speech-recognize/speech-recognize.min.js +1 -1
  14. package/es2018/jodit.fat.min.css +1 -1
  15. package/es2018/jodit.fat.min.js +33 -8
  16. package/es2018/jodit.min.css +1 -1
  17. package/es2018/jodit.min.js +33 -8
  18. package/es2018/plugins/debug/debug.min.js +1 -1
  19. package/es2018/plugins/speech-recognize/speech-recognize.min.js +1 -1
  20. package/es2021/jodit.css +60 -1
  21. package/es2021/jodit.fat.min.css +1 -1
  22. package/es2021/jodit.fat.min.js +36 -11
  23. package/es2021/jodit.js +958 -150
  24. package/es2021/jodit.min.css +1 -1
  25. package/es2021/jodit.min.js +36 -11
  26. package/es2021/plugins/debug/debug.css +1 -1
  27. package/es2021/plugins/debug/debug.js +1 -1
  28. package/es2021/plugins/debug/debug.min.js +1 -1
  29. package/es2021/plugins/speech-recognize/speech-recognize.css +1 -1
  30. package/es2021/plugins/speech-recognize/speech-recognize.js +1 -1
  31. package/es2021/plugins/speech-recognize/speech-recognize.min.js +1 -1
  32. package/es2021.en/jodit.css +60 -1
  33. package/es2021.en/jodit.fat.min.css +1 -1
  34. package/es2021.en/jodit.fat.min.js +36 -11
  35. package/es2021.en/jodit.js +932 -124
  36. package/es2021.en/jodit.min.css +1 -1
  37. package/es2021.en/jodit.min.js +51 -26
  38. package/es2021.en/plugins/debug/debug.css +1 -1
  39. package/es2021.en/plugins/debug/debug.js +1 -1
  40. package/es2021.en/plugins/debug/debug.min.js +1 -1
  41. package/es2021.en/plugins/speech-recognize/speech-recognize.css +1 -1
  42. package/es2021.en/plugins/speech-recognize/speech-recognize.js +1 -1
  43. package/es2021.en/plugins/speech-recognize/speech-recognize.min.js +1 -1
  44. package/es5/jodit.css +76 -2
  45. package/es5/jodit.fat.min.css +1 -1
  46. package/es5/jodit.fat.min.js +2 -2
  47. package/es5/jodit.js +1103 -155
  48. package/es5/jodit.min.css +3 -3
  49. package/es5/jodit.min.js +2 -2
  50. package/es5/plugins/debug/debug.css +1 -1
  51. package/es5/plugins/debug/debug.js +1 -1
  52. package/es5/plugins/debug/debug.min.js +1 -1
  53. package/es5/plugins/speech-recognize/speech-recognize.css +1 -1
  54. package/es5/plugins/speech-recognize/speech-recognize.js +1 -1
  55. package/es5/plugins/speech-recognize/speech-recognize.min.js +1 -1
  56. package/es5/polyfills.fat.min.js +1 -1
  57. package/es5/polyfills.js +1 -1
  58. package/es5/polyfills.min.js +1 -1
  59. package/esm/config.d.ts +25 -26
  60. package/esm/config.js +19 -21
  61. package/esm/core/constants.js +1 -1
  62. package/esm/core/helpers/utils/align.d.ts +1 -1
  63. package/esm/core/helpers/utils/align.js +2 -3
  64. package/esm/jodit.js +14 -4
  65. package/esm/langs/ar.d.ts +7 -0
  66. package/esm/langs/ar.js +8 -1
  67. package/esm/langs/az.d.ts +7 -0
  68. package/esm/langs/az.js +8 -1
  69. package/esm/langs/cs_cz.d.ts +7 -0
  70. package/esm/langs/cs_cz.js +9 -1
  71. package/esm/langs/de.d.ts +7 -0
  72. package/esm/langs/de.js +8 -1
  73. package/esm/langs/es.d.ts +7 -0
  74. package/esm/langs/es.js +8 -1
  75. package/esm/langs/fa.d.ts +7 -0
  76. package/esm/langs/fa.js +8 -1
  77. package/esm/langs/fi.d.ts +7 -0
  78. package/esm/langs/fi.js +8 -1
  79. package/esm/langs/fr.d.ts +7 -0
  80. package/esm/langs/fr.js +8 -1
  81. package/esm/langs/he.d.ts +7 -0
  82. package/esm/langs/he.js +8 -1
  83. package/esm/langs/hu.d.ts +7 -0
  84. package/esm/langs/hu.js +8 -1
  85. package/esm/langs/id.d.ts +7 -0
  86. package/esm/langs/id.js +8 -1
  87. package/esm/langs/it.d.ts +7 -0
  88. package/esm/langs/it.js +8 -1
  89. package/esm/langs/ja.d.ts +7 -0
  90. package/esm/langs/ja.js +8 -1
  91. package/esm/langs/ko.d.ts +7 -0
  92. package/esm/langs/ko.js +8 -1
  93. package/esm/langs/mn.d.ts +7 -0
  94. package/esm/langs/mn.js +8 -1
  95. package/esm/langs/nl.d.ts +7 -0
  96. package/esm/langs/nl.js +8 -1
  97. package/esm/langs/no.d.ts +7 -0
  98. package/esm/langs/no.js +8 -1
  99. package/esm/langs/pl.d.ts +7 -0
  100. package/esm/langs/pl.js +8 -1
  101. package/esm/langs/pt_br.d.ts +7 -0
  102. package/esm/langs/pt_br.js +8 -1
  103. package/esm/langs/ru.d.ts +7 -0
  104. package/esm/langs/ru.js +8 -1
  105. package/esm/langs/sk.d.ts +7 -0
  106. package/esm/langs/sk.js +8 -1
  107. package/esm/langs/sv.d.ts +7 -0
  108. package/esm/langs/sv.js +8 -1
  109. package/esm/langs/tr.d.ts +7 -0
  110. package/esm/langs/tr.js +8 -1
  111. package/esm/langs/ua.d.ts +7 -0
  112. package/esm/langs/ua.js +8 -1
  113. package/esm/langs/zh_cn.d.ts +7 -0
  114. package/esm/langs/zh_cn.js +8 -1
  115. package/esm/langs/zh_tw.d.ts +7 -0
  116. package/esm/langs/zh_tw.js +8 -1
  117. package/esm/plugins/all.d.ts +1 -0
  118. package/esm/plugins/all.js +1 -0
  119. package/esm/plugins/image-processor/image-processor.d.ts +4 -1
  120. package/esm/plugins/image-processor/image-processor.js +9 -3
  121. package/esm/plugins/image-properties/readers/align.d.ts +2 -2
  122. package/esm/plugins/index.d.ts +1 -0
  123. package/esm/plugins/inline-popup/config/items/iframe.d.ts +9 -1
  124. package/esm/plugins/inline-popup/config/items/iframe.js +15 -0
  125. package/esm/plugins/media/media.js +16 -7
  126. package/esm/plugins/resizer/config.d.ts +5 -3
  127. package/esm/plugins/resizer/config.js +8 -2
  128. package/esm/plugins/resizer/resizer.d.ts +10 -0
  129. package/esm/plugins/resizer/resizer.js +64 -15
  130. package/esm/plugins/video/config.d.ts +6 -0
  131. package/esm/plugins/video/config.js +77 -65
  132. package/esm/plugins/video-properties/interface.d.ts +31 -0
  133. package/esm/plugins/video-properties/interface.js +6 -0
  134. package/esm/plugins/video-properties/readers.d.ts +26 -0
  135. package/esm/plugins/video-properties/readers.js +65 -0
  136. package/esm/plugins/video-properties/replace.d.ts +15 -0
  137. package/esm/plugins/video-properties/replace.js +97 -0
  138. package/esm/plugins/video-properties/ui/ui-video-form.d.ts +41 -0
  139. package/esm/plugins/video-properties/ui/ui-video-form.js +228 -0
  140. package/esm/plugins/video-properties/video-properties.d.ts +37 -0
  141. package/esm/plugins/video-properties/video-properties.js +175 -0
  142. package/esm/plugins/video-properties/writers.d.ts +15 -0
  143. package/esm/plugins/video-properties/writers.js +73 -0
  144. package/esm/types/types.d.ts +1 -1
  145. package/package.json +1 -1
  146. package/types/config.d.ts +25 -26
  147. package/types/core/helpers/utils/align.d.ts +1 -1
  148. package/types/langs/ar.d.ts +7 -0
  149. package/types/langs/az.d.ts +7 -0
  150. package/types/langs/cs_cz.d.ts +7 -0
  151. package/types/langs/de.d.ts +7 -0
  152. package/types/langs/es.d.ts +7 -0
  153. package/types/langs/fa.d.ts +7 -0
  154. package/types/langs/fi.d.ts +7 -0
  155. package/types/langs/fr.d.ts +7 -0
  156. package/types/langs/he.d.ts +7 -0
  157. package/types/langs/hu.d.ts +7 -0
  158. package/types/langs/id.d.ts +7 -0
  159. package/types/langs/it.d.ts +7 -0
  160. package/types/langs/ja.d.ts +7 -0
  161. package/types/langs/ko.d.ts +7 -0
  162. package/types/langs/mn.d.ts +7 -0
  163. package/types/langs/nl.d.ts +7 -0
  164. package/types/langs/no.d.ts +7 -0
  165. package/types/langs/pl.d.ts +7 -0
  166. package/types/langs/pt_br.d.ts +7 -0
  167. package/types/langs/ru.d.ts +7 -0
  168. package/types/langs/sk.d.ts +7 -0
  169. package/types/langs/sv.d.ts +7 -0
  170. package/types/langs/tr.d.ts +7 -0
  171. package/types/langs/ua.d.ts +7 -0
  172. package/types/langs/zh_cn.d.ts +7 -0
  173. package/types/langs/zh_tw.d.ts +7 -0
  174. package/types/plugins/image-processor/image-processor.d.ts +4 -1
  175. package/types/plugins/image-properties/readers/align.d.ts +2 -2
  176. package/types/plugins/index.d.ts +1 -0
  177. package/types/plugins/inline-popup/config/items/iframe.d.ts +9 -1
  178. package/types/plugins/resizer/config.d.ts +5 -3
  179. package/types/plugins/resizer/resizer.d.ts +10 -0
  180. package/types/plugins/video/config.d.ts +6 -0
  181. package/types/plugins/video-properties/interface.d.ts +31 -0
  182. package/types/plugins/video-properties/readers.d.ts +26 -0
  183. package/types/plugins/video-properties/replace.d.ts +15 -0
  184. package/types/plugins/video-properties/ui/ui-video-form.d.ts +41 -0
  185. package/types/plugins/video-properties/video-properties.d.ts +37 -0
  186. package/types/plugins/video-properties/writers.d.ts +15 -0
  187. package/types/types/types.d.ts +1 -1
@@ -4,12 +4,18 @@
4
4
  * Copyright (c) 2013-2026 Valerii Chupurnov. All rights reserved. https://xdsoft.net
5
5
  */
6
6
  import { Config } from "../../config.js";
7
- Config.prototype.allowResizeTags = new Set(['img', 'iframe', 'table', 'jodit']);
7
+ Config.prototype.allowResizeTags = new Set([
8
+ 'img',
9
+ 'iframe',
10
+ 'table',
11
+ 'jodit',
12
+ 'video'
13
+ ]);
8
14
  Config.prototype.resizer = {
9
15
  showSize: true,
10
16
  hideSizeTimeout: 1000,
11
17
  forImageChangeAttributes: true,
12
18
  min_width: 10,
13
19
  min_height: 10,
14
- useAspectRatio: new Set(['img'])
20
+ useAspectRatio: new Set(['img', 'video'])
15
21
  };
@@ -26,6 +26,7 @@ export declare class resizer extends Plugin {
26
26
  private height;
27
27
  private ratio;
28
28
  private rect;
29
+ private layer;
29
30
  private sizeViewer;
30
31
  /** @override */
31
32
  protected afterInit(editor: IJodit): void;
@@ -33,6 +34,11 @@ export declare class resizer extends Plugin {
33
34
  * Click in the editor area
34
35
  */
35
36
  protected onEditorClick(e: MouseEvent): void;
37
+ /**
38
+ * What a click on `node` resizes: the media in it when it's a media
39
+ * wrapper (`jodit-media`, or one from `mediaWrappers`), or `node` itself
40
+ */
41
+ private __resizable;
36
42
  protected __afterInsertImage(image: HTMLImageElement): void;
37
43
  private addEventListeners;
38
44
  private onStartResizing;
@@ -46,6 +52,10 @@ export declare class resizer extends Plugin {
46
52
  private onClickOutside;
47
53
  private getWorkplacePosition;
48
54
  private applySize;
55
+ /**
56
+ * The framed element can go with a wrapper round it, keeping its parent
57
+ */
58
+ private get isElementInEditor();
49
59
  private onDelete;
50
60
  private __onChangeEditor;
51
61
  /**
@@ -18,7 +18,7 @@ import { IS_ES_NEXT, IS_IE, KEY_ALT } from "../../core/constants.js";
18
18
  import { autobind, watch } from "../../core/decorators/index.js";
19
19
  import { Dom } from "../../core/dom/dom.js";
20
20
  import { eventEmitter, pluginSystem } from "../../core/global.js";
21
- import { $$, attr, css, dataBind, innerWidth, markOwner, offset } from "../../core/helpers/index.js";
21
+ import { $$, attr, css, dataBind, getWrappedMedia, innerWidth, markOwner, offset } from "../../core/helpers/index.js";
22
22
  import { cssInline } from "../../core/helpers/utils/css.js";
23
23
  import { Plugin } from "../../core/plugin/plugin.js";
24
24
  import "./config.js";
@@ -36,6 +36,15 @@ function attrPixelSize(element, key) {
36
36
  }
37
37
  return parseFloat(value);
38
38
  }
39
+ /**
40
+ * A video with no height of its own takes it from its width and its ratio,
41
+ * so resizing it keeps the ratio without setting a height.
42
+ */
43
+ function heightFollowsWidth(element) {
44
+ return (Dom.isTag(element, 'video') &&
45
+ !attr(element, 'height') &&
46
+ !cssInline(element, 'height'));
47
+ }
39
48
  /**
40
49
  * The module creates a supporting frame for resizing of the elements img and table
41
50
  */
@@ -58,6 +67,8 @@ export class resizer extends Plugin {
58
67
  <div class="jodit-resizer__bottom-left"></div>
59
68
  <span>100x100</span>
60
69
  </div>`);
70
+ // Clips the frame to the workplace, without the frame making it scroll
71
+ this.layer = this.j.c.div('jodit-resizer-layer');
61
72
  this.sizeViewer = this.rect.getElementsByTagName('span')[0];
62
73
  this.pointerX = 0;
63
74
  this.pointerY = 0;
@@ -72,6 +83,10 @@ export class resizer extends Plugin {
72
83
  if (Dom.isTag(this.element, 'img') && !this.element.complete) {
73
84
  this.j.e.one(this.element, 'load', this.updateSize);
74
85
  }
86
+ if (Dom.isTag(this.element, 'video') &&
87
+ this.element.readyState < HTMLMediaElement.HAVE_METADATA) {
88
+ this.j.e.one(this.element, 'loadedmetadata', this.updateSize);
89
+ }
75
90
  }
76
91
  };
77
92
  this.updateSize = () => {
@@ -135,14 +150,33 @@ export class resizer extends Plugin {
135
150
  let node = e.target;
136
151
  const { editor, options: { allowResizeTags } } = this.j;
137
152
  while (node && node !== editor) {
138
- if (Dom.isTag(node, allowResizeTags)) {
139
- this.__bind(node);
140
- this.onClickElement(node);
153
+ const element = this.__resizable(node);
154
+ if (Dom.isTag(element, allowResizeTags)) {
155
+ this.__bind(element);
156
+ this.onClickElement(element);
141
157
  return;
142
158
  }
143
159
  node = node.parentNode;
144
160
  }
145
161
  }
162
+ /**
163
+ * What a click on `node` resizes: the media in it when it's a media
164
+ * wrapper (`jodit-media`, or one from `mediaWrappers`), or `node` itself
165
+ */
166
+ __resizable(node) {
167
+ var _a;
168
+ const media = (_a = getWrappedMedia(this.j, node)) !== null && _a !== void 0 ? _a : (Dom.isHTMLElement(node) && attr(node, 'data-jodit_fake_wrapper')
169
+ ? node.firstElementChild
170
+ : null);
171
+ if (!media) {
172
+ return node;
173
+ }
174
+ // An embed is resized through its own wrapper
175
+ return Dom.isHTMLElement(media.parentNode) &&
176
+ attr(media.parentNode, 'data-jodit_iframe_wrapper')
177
+ ? media.parentNode
178
+ : media;
179
+ }
146
180
  __afterInsertImage(image) {
147
181
  if (this.j.o.resizer.forImageChangeAttributes) {
148
182
  return;
@@ -169,17 +203,15 @@ export class resizer extends Plugin {
169
203
  })
170
204
  .on(editor.ow, 'resize.resizer', this.updateSize)
171
205
  .on('resize.resizer', this.updateSize)
172
- .on([editor.ow, editor.editor], 'scroll.resizer', () => {
173
- if (this.isShown && !this.isResizeMode) {
174
- this.hide();
175
- }
176
- })
206
+ // The frame is outside the content, so it's moved to follow the
207
+ // element as the content scrolls
208
+ .on([editor.ow, editor.editor], 'scroll.resizer', this.updateSize)
177
209
  .on(editor.ow, 'keydown.resizer', this.onKeyDown)
178
210
  .on(editor.ow, 'keyup.resizer', this.onKeyUp)
179
211
  .on(editor.ow, 'mouseup.resizer touchend.resizer', this.onClickOutside);
180
212
  }
181
213
  onStartResizing(resizeHandle, e) {
182
- if (!this.element || !this.element.parentNode) {
214
+ if (!this.element || !this.isElementInEditor) {
183
215
  this.hide();
184
216
  return false;
185
217
  }
@@ -233,8 +265,9 @@ export class resizer extends Plugin {
233
265
  const className = this.handle.className;
234
266
  let new_w, new_h;
235
267
  const uar = this.j.o.resizer.useAspectRatio;
236
- if (!this.isAltMode &&
237
- (uar === true || (uar && Dom.isTag(this.element, uar)))) {
268
+ const keepRatio = !this.isAltMode &&
269
+ (uar === true || Boolean(uar && Dom.isTag(this.element, uar)));
270
+ if (keepRatio) {
238
271
  if (diff_x) {
239
272
  new_w =
240
273
  this.width +
@@ -266,7 +299,8 @@ export class resizer extends Plugin {
266
299
  this.applySize(this.element, 'width', '100%');
267
300
  }
268
301
  }
269
- if (new_h > this.j.o.resizer.min_height) {
302
+ if (new_h > this.j.o.resizer.min_height &&
303
+ !(keepRatio && heightFollowsWidth(this.element))) {
270
304
  this.applySize(this.element, 'height', new_h);
271
305
  }
272
306
  this.updateSize();
@@ -312,10 +346,20 @@ export class resizer extends Plugin {
312
346
  css(element, key, value);
313
347
  }
314
348
  }
349
+ /**
350
+ * The framed element can go with a wrapper round it, keeping its parent
351
+ */
352
+ get isElementInEditor() {
353
+ return Boolean(this.element && this.j.editor.contains(this.element));
354
+ }
315
355
  onDelete(e) {
316
356
  if (!this.element) {
317
357
  return;
318
358
  }
359
+ if (!this.isElementInEditor) {
360
+ this.hide();
361
+ return;
362
+ }
319
363
  if (this.element.tagName !== 'JODIT') {
320
364
  this.j.s.select(this.element);
321
365
  }
@@ -327,7 +371,7 @@ export class resizer extends Plugin {
327
371
  }
328
372
  __onChangeEditor() {
329
373
  if (this.isShown) {
330
- if (!this.element || !this.element.parentNode) {
374
+ if (!this.isElementInEditor) {
331
375
  this.hide();
332
376
  }
333
377
  else {
@@ -439,9 +483,13 @@ export class resizer extends Plugin {
439
483
  return;
440
484
  }
441
485
  this.isShown = true;
486
+ if (this.layer.parentNode !== this.j.workplace) {
487
+ markOwner(this.j, this.layer);
488
+ Dom.append(this.j.workplace, this.layer);
489
+ }
442
490
  if (!this.rect.parentNode) {
443
491
  markOwner(this.j, this.rect);
444
- Dom.append(this.j.workplace, this.rect);
492
+ Dom.append(this.layer, this.rect);
445
493
  }
446
494
  if (this.j.isFullSize) {
447
495
  css(this.rect, 'zIndex', css(this.j.container, 'zIndex').toString());
@@ -462,6 +510,7 @@ export class resizer extends Plugin {
462
510
  }
463
511
  beforeDestruct(jodit) {
464
512
  this.hide();
513
+ Dom.safeRemove(this.layer);
465
514
  eventEmitter.off('hideHelpers', this.hide);
466
515
  jodit.e.off(this.j.ow, '.resizer').off('.resizer');
467
516
  }
@@ -53,3 +53,9 @@ declare module 'jodit/config' {
53
53
  };
54
54
  }
55
55
  }
56
+ /**
57
+ * The video popup's tabs: Upload, when the uploader is configured, then Link
58
+ * and Code. `insert` runs what a tab inserts, which by default goes in at the
59
+ * selection the popup was opened with.
60
+ */
61
+ export declare function videoTabs(jodit: IJodit, close: () => void, insert?: (run: () => void) => void): HTMLElement;
@@ -26,73 +26,85 @@ Config.prototype.video = {
26
26
  defaultHandlerSuccess: insertVideos
27
27
  };
28
28
  Icon.set('video', videoIcon);
29
- Config.prototype.controls.video = {
30
- popup: (jodit, current, close) => {
31
- const formLink = new UIForm(jodit, [
32
- new UIBlock(jodit, [
33
- new UIInput(jodit, {
34
- name: 'url',
35
- required: true,
36
- label: 'URL',
37
- placeholder: 'https://',
38
- validators: ['url']
39
- })
40
- ]),
41
- new UIBlock(jodit, [
42
- Button(jodit, '', 'Insert', 'primary').onAction(() => formLink.submit())
43
- ])
44
- ]), formCode = new UIForm(jodit, [
45
- new UIBlock(jodit, [
46
- new UITextArea(jodit, {
47
- name: 'code',
48
- required: true,
49
- label: 'Embed code'
50
- })
51
- ]),
52
- new UIBlock(jodit, [
53
- Button(jodit, '', 'Insert', 'primary').onAction(() => formCode.submit())
54
- ])
55
- ]), tabs = [], insertCode = (code) => {
56
- jodit.s.restore();
57
- jodit.s.insertHTML(prepareEmbedCode(jodit, code));
58
- close();
59
- };
29
+ /**
30
+ * The video popup's tabs: Upload, when the uploader is configured, then Link
31
+ * and Code. `insert` runs what a tab inserts, which by default goes in at the
32
+ * selection the popup was opened with.
33
+ */
34
+ export function videoTabs(jodit, close, insert) {
35
+ const run = insert !== null && insert !== void 0 ? insert : ((fn) => {
36
+ jodit.s.restore();
37
+ fn();
38
+ });
39
+ const formLink = new UIForm(jodit, [
40
+ new UIBlock(jodit, [
41
+ new UIInput(jodit, {
42
+ name: 'url',
43
+ required: true,
44
+ label: 'URL',
45
+ placeholder: 'https://',
46
+ validators: ['url']
47
+ })
48
+ ]),
49
+ new UIBlock(jodit, [
50
+ Button(jodit, '', 'Insert', 'primary').onAction(() => formLink.submit())
51
+ ])
52
+ ]), formCode = new UIForm(jodit, [
53
+ new UIBlock(jodit, [
54
+ new UITextArea(jodit, {
55
+ name: 'code',
56
+ required: true,
57
+ label: 'Embed code'
58
+ })
59
+ ]),
60
+ new UIBlock(jodit, [
61
+ Button(jodit, '', 'Insert', 'primary').onAction(() => formCode.submit())
62
+ ])
63
+ ]), tabs = [], insertCode = (code) => {
64
+ run(() => jodit.s.insertHTML(prepareEmbedCode(jodit, code)));
65
+ close();
66
+ };
67
+ if (!insert) {
60
68
  jodit.s.save();
61
- const { uploader } = jodit.o;
62
- // Base64 alone would put whole video files into the content
63
- if (uploader.showTabInFileSelector !== false &&
64
- (uploader.url || uploader.customUploadFunction)) {
65
- tabs.push(UploadTab(jodit, data => {
69
+ }
70
+ const { uploader } = jodit.o;
71
+ // Base64 alone would put whole video files into the content
72
+ if (uploader.showTabInFileSelector !== false &&
73
+ (uploader.url || uploader.customUploadFunction)) {
74
+ tabs.push(UploadTab(jodit, data => {
75
+ run(() => {
66
76
  var _a, _b;
67
- jodit.s.restore();
68
- ((_b = (_a = jodit.o.video) === null || _a === void 0 ? void 0 : _a.defaultHandlerSuccess) !== null && _b !== void 0 ? _b : insertVideos).call(jodit, data);
69
- }, { label: 'Drop file', accept: 'video/*' }));
70
- }
71
- tabs.push({
72
- icon: 'link',
73
- name: 'Link',
74
- content: formLink.container
75
- }, {
76
- icon: 'source',
77
- name: 'Code',
78
- content: formCode.container
79
- });
80
- formLink.onSubmit(data => {
81
- var _a, _b, _c, _d;
82
- insertCode(call((_b = (_a = jodit.o.video) === null || _a === void 0 ? void 0 : _a.parseUrlToVideoEmbed) !== null && _b !== void 0 ? _b : convertMediaUrlToVideoEmbed, data.url, {
83
- width: (_c = jodit.o.video) === null || _c === void 0 ? void 0 : _c.defaultWidth,
84
- height: (_d = jodit.o.video) === null || _d === void 0 ? void 0 : _d.defaultHeight
85
- }));
86
- });
87
- formCode.onSubmit(data => {
88
- insertCode(data.code);
89
- });
90
- const box = TabsWidget(jodit, tabs);
91
- // Its tabs are sized to their labels, as the file selector's are, since
92
- // "Upload" doesn't fit a third of the popup
93
- box.classList.add('jodit-file-selector');
94
- return box;
95
- },
77
+ return ((_b = (_a = jodit.o.video) === null || _a === void 0 ? void 0 : _a.defaultHandlerSuccess) !== null && _b !== void 0 ? _b : insertVideos).call(jodit, data);
78
+ });
79
+ }, { label: 'Drop file', accept: 'video/*' }));
80
+ }
81
+ tabs.push({
82
+ icon: 'link',
83
+ name: 'Link',
84
+ content: formLink.container
85
+ }, {
86
+ icon: 'source',
87
+ name: 'Code',
88
+ content: formCode.container
89
+ });
90
+ formLink.onSubmit(data => {
91
+ var _a, _b, _c, _d;
92
+ insertCode(call((_b = (_a = jodit.o.video) === null || _a === void 0 ? void 0 : _a.parseUrlToVideoEmbed) !== null && _b !== void 0 ? _b : convertMediaUrlToVideoEmbed, data.url, {
93
+ width: (_c = jodit.o.video) === null || _c === void 0 ? void 0 : _c.defaultWidth,
94
+ height: (_d = jodit.o.video) === null || _d === void 0 ? void 0 : _d.defaultHeight
95
+ }));
96
+ });
97
+ formCode.onSubmit(data => {
98
+ insertCode(data.code);
99
+ });
100
+ const box = TabsWidget(jodit, tabs);
101
+ // Its tabs are sized to their labels, as the file selector's are, since
102
+ // "Upload" doesn't fit a third of the popup
103
+ box.classList.add('jodit-file-selector');
104
+ return box;
105
+ }
106
+ Config.prototype.controls.video = {
107
+ popup: (jodit, current, close) => videoTabs(jodit, close),
96
108
  tags: ['iframe'],
97
109
  tooltip: 'Insert youtube/vimeo video'
98
110
  };
@@ -0,0 +1,31 @@
1
+ /*!
2
+ * Jodit Editor (https://xdsoft.net/jodit/)
3
+ * Released under MIT see LICENSE.txt in the project root for license information.
4
+ * Copyright (c) 2013-2026 Valerii Chupurnov. All rights reserved. https://xdsoft.net
5
+ */
6
+ /**
7
+ * @module plugins/video-properties
8
+ */
9
+ export type EditedMedia = HTMLVideoElement | HTMLAudioElement | HTMLIFrameElement;
10
+ export interface VideoPropertiesValues {
11
+ width: number | string;
12
+ height: number | string;
13
+ poster: string;
14
+ autoplay: boolean;
15
+ loop: boolean;
16
+ muted: boolean;
17
+ controls: boolean;
18
+ }
19
+ export interface VideoPropertiesState {
20
+ /**
21
+ * What the dialog was opened for: the media, or a wrapper round it
22
+ */
23
+ target: HTMLElement;
24
+ media: EditedMedia;
25
+ ratio: number;
26
+ sizeIsLocked: boolean;
27
+ values: VideoPropertiesValues;
28
+ }
29
+ export interface VideoPropertiesAPI {
30
+ openPosterPopup: (target: HTMLElement) => void;
31
+ }
@@ -0,0 +1,6 @@
1
+ /*!
2
+ * Jodit Editor (https://xdsoft.net/jodit/)
3
+ * Released under MIT see LICENSE.txt in the project root for license information.
4
+ * Copyright (c) 2013-2026 Valerii Chupurnov. All rights reserved. https://xdsoft.net
5
+ */
6
+ export {};
@@ -0,0 +1,26 @@
1
+ /*!
2
+ * Jodit Editor (https://xdsoft.net/jodit/)
3
+ * Released under MIT see LICENSE.txt in the project root for license information.
4
+ * Copyright (c) 2013-2026 Valerii Chupurnov. All rights reserved. https://xdsoft.net
5
+ */
6
+ /**
7
+ * @module plugins/video-properties
8
+ */
9
+ import type { Nullable } from "../../types/index";
10
+ import type { EditedMedia, VideoPropertiesState } from "./interface";
11
+ /**
12
+ * The media `target` is, or holds as its wrapper (`jodit-media`, the resizer's
13
+ * `jodit` for an embed, or one from `mediaWrappers`)
14
+ * @private
15
+ */
16
+ export declare function editedMedia(target: HTMLElement): Nullable<EditedMedia>;
17
+ /**
18
+ * The size `media` is given, inline or by its attribute
19
+ * @private
20
+ */
21
+ export declare function ownSize(media: EditedMedia, key: 'width' | 'height'): string;
22
+ /**
23
+ * Read the media's values into the state
24
+ * @private
25
+ */
26
+ export declare function readValues(state: VideoPropertiesState): void;
@@ -0,0 +1,65 @@
1
+ /*!
2
+ * Jodit Editor (https://xdsoft.net/jodit/)
3
+ * Released under MIT see LICENSE.txt in the project root for license information.
4
+ * Copyright (c) 2013-2026 Valerii Chupurnov. All rights reserved. https://xdsoft.net
5
+ */
6
+ import { Dom } from "../../core/dom/dom.js";
7
+ import { isNumeric } from "../../core/helpers/checker/is-numeric.js";
8
+ import { attr } from "../../core/helpers/utils/attr.js";
9
+ import { cssInline } from "../../core/helpers/utils/css.js";
10
+ import { normalSizeFromString } from "../image-properties/utils/utils.js";
11
+ const mediaSelector = 'video, audio, iframe';
12
+ /**
13
+ * The media `target` is, or holds as its wrapper (`jodit-media`, the resizer's
14
+ * `jodit` for an embed, or one from `mediaWrappers`)
15
+ * @private
16
+ */
17
+ export function editedMedia(target) {
18
+ return (target.matches(mediaSelector)
19
+ ? target
20
+ : target.querySelector(mediaSelector));
21
+ }
22
+ /**
23
+ * The size `media` is given, inline or by its attribute
24
+ * @private
25
+ */
26
+ export function ownSize(media, key) {
27
+ return cssInline(media, key) || attr(media, key) || '';
28
+ }
29
+ const toNumber = (value) => parseFloat(value.toString());
30
+ /**
31
+ * Read the media's values into the state
32
+ * @private
33
+ */
34
+ export function readValues(state) {
35
+ const { media, values } = state;
36
+ const width = ownSize(media, 'width');
37
+ const height = ownSize(media, 'height');
38
+ values.width = width ? normalSizeFromString(width) : media.offsetWidth;
39
+ values.height = height ? normalSizeFromString(height) : '';
40
+ if (Dom.isTag(media, 'video') && media.videoWidth) {
41
+ state.ratio = media.videoWidth / media.videoHeight;
42
+ }
43
+ else if (isNumeric(values.width) && isNumeric(values.height)) {
44
+ state.ratio = toNumber(values.width) / toNumber(values.height);
45
+ }
46
+ else {
47
+ state.ratio = media.offsetWidth / media.offsetHeight || 1;
48
+ }
49
+ // One with no height takes it from its width
50
+ if (!height && isNumeric(values.width)) {
51
+ values.height = Math.round(toNumber(values.width) / state.ratio);
52
+ }
53
+ state.sizeIsLocked =
54
+ isNumeric(values.width) &&
55
+ isNumeric(values.height) &&
56
+ (!height ||
57
+ Math.abs(toNumber(values.width) - toNumber(values.height) * state.ratio) < 1);
58
+ values.poster = attr(media, 'poster') || '';
59
+ values.autoplay = media.hasAttribute('autoplay');
60
+ values.loop = media.hasAttribute('loop');
61
+ // Autoplay's own: turning it off gives the sound back
62
+ values.muted = media.hasAttribute('muted') && !values.autoplay;
63
+ // Without Autoplay it keeps its controls, or it could never be played
64
+ values.controls = media.hasAttribute('controls') || !values.autoplay;
65
+ }
@@ -0,0 +1,15 @@
1
+ /*!
2
+ * Jodit Editor (https://xdsoft.net/jodit/)
3
+ * Released under MIT see LICENSE.txt in the project root for license information.
4
+ * Copyright (c) 2013-2026 Valerii Chupurnov. All rights reserved. https://xdsoft.net
5
+ */
6
+ /**
7
+ * @module plugins/video-properties
8
+ */
9
+ import type { IJodit } from "../../types/index";
10
+ /**
11
+ * Run `insert`, which inserts media at the cursor, with the cursor after
12
+ * `replaced`, and put what it inserts in place of `replaced`
13
+ * @private
14
+ */
15
+ export declare function replaceMedia(j: IJodit, replaced: HTMLElement, insert: () => void): void;
@@ -0,0 +1,97 @@
1
+ /*!
2
+ * Jodit Editor (https://xdsoft.net/jodit/)
3
+ * Released under MIT see LICENSE.txt in the project root for license information.
4
+ * Copyright (c) 2013-2026 Valerii Chupurnov. All rights reserved. https://xdsoft.net
5
+ */
6
+ import { Dom } from "../../core/dom/dom.js";
7
+ import { attr } from "../../core/helpers/utils/attr.js";
8
+ import { css } from "../../core/helpers/utils/css.js";
9
+ import { editedMedia, ownSize, readValues } from "./readers.js";
10
+ import { applyValues } from "./writers.js";
11
+ /**
12
+ * Give `media`, in `outer`, the classes, inline styles, size and playback
13
+ * settings of `old`, in `oldOuter`. It keeps its own source and poster.
14
+ */
15
+ function takeOver(j, oldOuter, old, outer, media) {
16
+ var _a;
17
+ const state = {
18
+ target: oldOuter,
19
+ media: old,
20
+ ratio: 1,
21
+ sizeIsLocked: true,
22
+ values: {
23
+ width: '',
24
+ height: '',
25
+ poster: '',
26
+ autoplay: false,
27
+ loop: false,
28
+ muted: false,
29
+ controls: true
30
+ }
31
+ };
32
+ readValues(state);
33
+ const pairs = [[old, media]];
34
+ // A wrapper kept in the content, which holds the alignment
35
+ if (oldOuter !== old &&
36
+ outer !== media &&
37
+ oldOuter.nodeName === outer.nodeName) {
38
+ pairs.push([oldOuter, outer]);
39
+ }
40
+ pairs.forEach(([from, to]) => {
41
+ ['class', 'style'].forEach(name => attr(to, name, attr(from, name)));
42
+ });
43
+ // A video's size is CSS and an embed's its attributes, so the writer sizes
44
+ // one that's a different element
45
+ if (old.nodeName !== media.nodeName) {
46
+ css(media, { width: null, height: null });
47
+ if (!((_a = attr(media, 'style')) === null || _a === void 0 ? void 0 : _a.trim())) {
48
+ attr(media, 'style', null);
49
+ }
50
+ }
51
+ state.media = media;
52
+ state.values.poster = attr(media, 'poster') || '';
53
+ applyValues(j, state, Boolean(ownSize(old, 'width') || ownSize(old, 'height')));
54
+ }
55
+ /**
56
+ * Run `insert`, which inserts media at the cursor, with the cursor after
57
+ * `replaced`, and put what it inserts in place of `replaced`
58
+ * @private
59
+ */
60
+ export function replaceMedia(j, replaced, insert) {
61
+ const inserted = [];
62
+ const collect = (node) => {
63
+ inserted.push(node);
64
+ };
65
+ j.history.snapshot.transaction(() => {
66
+ j.s.focus();
67
+ j.s.setCursorAfter(replaced);
68
+ j.e.on('afterInsertNode', collect);
69
+ try {
70
+ insert();
71
+ }
72
+ finally {
73
+ j.e.off('afterInsertNode', collect);
74
+ }
75
+ const [outer] = inserted;
76
+ if (!Dom.isHTMLElement(outer)) {
77
+ return;
78
+ }
79
+ const old = editedMedia(replaced);
80
+ const media = editedMedia(outer);
81
+ if (old && media) {
82
+ takeOver(j, replaced, old, outer, media);
83
+ }
84
+ // The resizer can have put an embed in its own wrapper already
85
+ const placed = Dom.isHTMLElement(outer.parentElement) &&
86
+ attr(outer.parentElement, 'data-jodit-temp')
87
+ ? outer.parentElement
88
+ : outer;
89
+ // Where the old one was, since media Jodit counts as blocks can be put
90
+ // after the paragraph
91
+ Dom.before(replaced, placed);
92
+ Dom.safeRemove(replaced);
93
+ j.s.setCursorAfter(placed);
94
+ });
95
+ j.synchronizeValues();
96
+ j.e.fire('closeAllPopups');
97
+ }