suneditor 3.1.3 → 3.2.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.
Files changed (219) hide show
  1. package/dist/suneditor-contents.min.css +1 -1
  2. package/dist/suneditor.min.css +28 -1
  3. package/dist/suneditor.min.js +1 -1
  4. package/package.json +2 -1
  5. package/src/assets/design/color.css +12 -0
  6. package/src/assets/design/size.css +9 -2
  7. package/src/assets/icons/defaultIcons.js +96 -47
  8. package/src/assets/suneditor-contents.css +3 -2
  9. package/src/assets/suneditor.css +480 -31
  10. package/src/core/config/contextProvider.js +2 -1
  11. package/src/core/config/eventManager.js +41 -2
  12. package/src/core/config/optionProvider.js +23 -2
  13. package/src/core/editor.js +32 -14
  14. package/src/core/event/actions/index.js +76 -9
  15. package/src/core/event/effects/common.registry.js +2 -1
  16. package/src/core/event/effects/keydown.registry.js +169 -33
  17. package/src/core/event/effects/ruleHelpers.js +20 -6
  18. package/src/core/event/eventOrchestrator.js +136 -23
  19. package/src/core/event/handlers/handler_toolbar.js +4 -2
  20. package/src/core/event/handlers/handler_ww_dragDrop.js +10 -0
  21. package/src/core/event/handlers/handler_ww_input.js +9 -2
  22. package/src/core/event/handlers/handler_ww_key.js +86 -17
  23. package/src/core/event/handlers/handler_ww_mouse.js +45 -8
  24. package/src/core/event/ports.js +23 -5
  25. package/src/core/event/reducers/keydown.reducer.js +15 -2
  26. package/src/core/event/rules/keydown.rule.arrow.js +12 -2
  27. package/src/core/event/rules/keydown.rule.backspace.js +110 -15
  28. package/src/core/event/rules/keydown.rule.delete.js +62 -7
  29. package/src/core/event/rules/keydown.rule.enter.js +62 -31
  30. package/src/core/event/support/defaultLineManager.js +9 -2
  31. package/src/core/event/support/selectionState.js +13 -3
  32. package/src/core/kernel/coreKernel.js +1 -0
  33. package/src/core/logic/dom/char.js +13 -3
  34. package/src/core/logic/dom/format.js +171 -39
  35. package/src/core/logic/dom/html.js +357 -87
  36. package/src/core/logic/dom/inline.js +229 -34
  37. package/src/core/logic/dom/listFormat.js +111 -20
  38. package/src/core/logic/dom/nodeTransform.js +36 -11
  39. package/src/core/logic/dom/offset.js +116 -31
  40. package/src/core/logic/dom/selection.js +147 -29
  41. package/src/core/logic/panel/blockHandle.js +785 -0
  42. package/src/core/logic/panel/blockResolver.js +278 -0
  43. package/src/core/logic/panel/finder.js +16 -6
  44. package/src/core/logic/panel/menu.js +119 -18
  45. package/src/core/logic/panel/toolbar.js +75 -19
  46. package/src/core/logic/panel/viewer.js +76 -20
  47. package/src/core/logic/shell/_commandExecutor.js +44 -9
  48. package/src/core/logic/shell/commandDispatcher.js +46 -4
  49. package/src/core/logic/shell/component.js +102 -25
  50. package/src/core/logic/shell/focusManager.js +13 -3
  51. package/src/core/logic/shell/history.js +21 -6
  52. package/src/core/logic/shell/pluginManager.js +21 -13
  53. package/src/core/logic/shell/shortcuts.js +9 -1
  54. package/src/core/logic/shell/ui.js +139 -37
  55. package/src/core/schema/frameContext.js +34 -3
  56. package/src/core/schema/options.js +61 -19
  57. package/src/core/section/constructor.js +537 -103
  58. package/src/core/section/documentType.js +35 -6
  59. package/src/helper/converter.js +24 -7
  60. package/src/helper/dom/domCheck.js +25 -5
  61. package/src/helper/dom/domQuery.js +2 -1
  62. package/src/helper/dom/domUtils.js +17 -4
  63. package/src/helper/env.js +11 -2
  64. package/src/helper/keyCodeMap.js +6 -2
  65. package/src/helper/markdown.js +24 -5
  66. package/src/helper/msOffice.js +9 -2
  67. package/src/interfaces/plugins.js +1 -1
  68. package/src/langs/ckb.js +0 -1
  69. package/src/langs/cs.js +0 -1
  70. package/src/langs/da.js +0 -1
  71. package/src/langs/de.js +0 -1
  72. package/src/langs/en.js +0 -1
  73. package/src/langs/es.js +0 -1
  74. package/src/langs/fa.js +0 -1
  75. package/src/langs/fr.js +0 -1
  76. package/src/langs/he.js +0 -1
  77. package/src/langs/hu.js +0 -1
  78. package/src/langs/index.js +27 -1
  79. package/src/langs/it.js +0 -1
  80. package/src/langs/ja.js +0 -1
  81. package/src/langs/km.js +0 -1
  82. package/src/langs/ko.js +0 -1
  83. package/src/langs/lv.js +0 -1
  84. package/src/langs/nl.js +0 -1
  85. package/src/langs/pl.js +0 -1
  86. package/src/langs/pt_br.js +0 -1
  87. package/src/langs/ro.js +0 -1
  88. package/src/langs/ru.js +0 -1
  89. package/src/langs/se.js +0 -1
  90. package/src/langs/tr.js +0 -1
  91. package/src/langs/uk.js +0 -1
  92. package/src/langs/ur.js +0 -1
  93. package/src/langs/zh_cn.js +0 -1
  94. package/src/modules/contract/Browser.js +68 -23
  95. package/src/modules/contract/ColorPicker.js +12 -4
  96. package/src/modules/contract/Controller.js +37 -7
  97. package/src/modules/contract/Figure.js +159 -36
  98. package/src/modules/contract/HueSlider.js +27 -4
  99. package/src/modules/contract/Modal.js +62 -11
  100. package/src/modules/manager/FileManager.js +50 -8
  101. package/src/modules/ui/CommandMenu.js +597 -0
  102. package/src/modules/ui/ModalAnchorEditor.js +41 -9
  103. package/src/modules/ui/SelectMenu.js +481 -36
  104. package/src/modules/ui/index.js +1 -0
  105. package/src/plugins/browser/audioGallery.js +8 -1
  106. package/src/plugins/browser/fileBrowser.js +15 -2
  107. package/src/plugins/browser/fileGallery.js +8 -1
  108. package/src/plugins/browser/imageGallery.js +24 -3
  109. package/src/plugins/browser/videoGallery.js +19 -4
  110. package/src/plugins/command/blockquote.js +6 -1
  111. package/src/plugins/command/codeBlock.js +43 -5
  112. package/src/plugins/command/exportPDF.js +20 -4
  113. package/src/plugins/command/fileUpload.js +46 -11
  114. package/src/plugins/command/list_bulleted.js +6 -1
  115. package/src/plugins/command/list_numbered.js +6 -1
  116. package/src/plugins/dropdown/align.js +21 -33
  117. package/src/plugins/dropdown/backgroundColor.js +10 -2
  118. package/src/plugins/dropdown/blockStyle.js +17 -22
  119. package/src/plugins/dropdown/font.js +34 -34
  120. package/src/plugins/dropdown/hr.js +15 -43
  121. package/src/plugins/dropdown/layout.js +10 -27
  122. package/src/plugins/dropdown/lineHeight.js +10 -33
  123. package/src/plugins/dropdown/list.js +19 -25
  124. package/src/plugins/dropdown/paragraphStyle.js +16 -24
  125. package/src/plugins/dropdown/table/index.js +72 -17
  126. package/src/plugins/dropdown/table/render/table.html.js +3 -1
  127. package/src/plugins/dropdown/table/services/table.cell.js +33 -7
  128. package/src/plugins/dropdown/table/services/table.clipboard.js +5 -1
  129. package/src/plugins/dropdown/table/services/table.grid.js +40 -9
  130. package/src/plugins/dropdown/table/services/table.resize.js +55 -11
  131. package/src/plugins/dropdown/table/services/table.selection.js +27 -7
  132. package/src/plugins/dropdown/table/services/table.style.js +120 -20
  133. package/src/plugins/dropdown/template.js +13 -29
  134. package/src/plugins/dropdown/textStyle.js +22 -35
  135. package/src/plugins/field/autocomplete.js +8 -4
  136. package/src/plugins/field/slashCommand.js +297 -0
  137. package/src/plugins/index.js +3 -0
  138. package/src/plugins/input/fontSize.js +37 -8
  139. package/src/plugins/input/pageNavigator.js +7 -1
  140. package/src/plugins/modal/audio.js +71 -17
  141. package/src/plugins/modal/drawing.js +41 -11
  142. package/src/plugins/modal/embed.js +132 -22
  143. package/src/plugins/modal/image/index.js +115 -28
  144. package/src/plugins/modal/image/services/image.size.js +9 -2
  145. package/src/plugins/modal/image/services/image.upload.js +38 -4
  146. package/src/plugins/modal/link.js +9 -2
  147. package/src/plugins/modal/math.js +10 -2
  148. package/src/plugins/modal/video/index.js +112 -26
  149. package/src/plugins/modal/video/render/video.html.js +4 -1
  150. package/src/plugins/modal/video/services/video.size.js +15 -4
  151. package/src/plugins/modal/video/services/video.upload.js +10 -2
  152. package/src/plugins/popup/anchor.js +14 -2
  153. package/src/suneditor.js +8 -2
  154. package/src/themes/cobalt.css +12 -0
  155. package/src/themes/cream.css +12 -0
  156. package/src/themes/dark.css +12 -0
  157. package/src/themes/midnight.css +12 -0
  158. package/src/typedef.js +32 -21
  159. package/types/assets/icons/defaultIcons.d.ts +1 -0
  160. package/types/core/config/contextProvider.d.ts +7 -1
  161. package/types/core/config/eventManager.d.ts +37 -4
  162. package/types/core/config/optionProvider.d.ts +5 -1
  163. package/types/core/event/actions/index.d.ts +44 -6
  164. package/types/core/event/effects/keydown.registry.d.ts +37 -7
  165. package/types/core/event/effects/ruleHelpers.d.ts +6 -1
  166. package/types/core/event/eventOrchestrator.d.ts +6 -1
  167. package/types/core/event/handlers/handler_ww_clipboard.d.ts +15 -3
  168. package/types/core/event/handlers/handler_ww_dragDrop.d.ts +14 -2
  169. package/types/core/event/handlers/handler_ww_input.d.ts +10 -2
  170. package/types/core/event/handlers/handler_ww_key.d.ts +10 -2
  171. package/types/core/event/handlers/handler_ww_mouse.d.ts +25 -5
  172. package/types/core/event/ports.d.ts +7 -2
  173. package/types/core/event/rules/keydown.rule.backspace.d.ts +6 -1
  174. package/types/core/kernel/store.d.ts +4 -1
  175. package/types/core/logic/dom/format.d.ts +32 -14
  176. package/types/core/logic/dom/html.d.ts +11 -3
  177. package/types/core/logic/dom/nodeTransform.d.ts +6 -6
  178. package/types/core/logic/panel/blockHandle.d.ts +64 -0
  179. package/types/core/logic/panel/blockResolver.d.ts +50 -0
  180. package/types/core/logic/panel/menu.d.ts +72 -3
  181. package/types/core/logic/shell/commandDispatcher.d.ts +8 -0
  182. package/types/core/logic/shell/component.d.ts +5 -3
  183. package/types/core/logic/shell/shortcuts.d.ts +10 -1
  184. package/types/core/logic/shell/ui.d.ts +7 -1
  185. package/types/core/schema/context.d.ts +7 -1
  186. package/types/core/schema/frameContext.d.ts +36 -4
  187. package/types/core/schema/options.d.ts +110 -37
  188. package/types/core/section/constructor.d.ts +38 -1
  189. package/types/events.d.ts +208 -32
  190. package/types/helper/converter.d.ts +4 -2
  191. package/types/helper/dom/domCheck.d.ts +12 -2
  192. package/types/helper/dom/domQuery.d.ts +35 -7
  193. package/types/helper/dom/domUtils.d.ts +4 -1
  194. package/types/interfaces/plugins.d.ts +2 -2
  195. package/types/langs/_Lang.d.ts +0 -1
  196. package/types/modules/contract/Browser.d.ts +7 -7
  197. package/types/modules/contract/ColorPicker.d.ts +2 -2
  198. package/types/modules/contract/Controller.d.ts +7 -0
  199. package/types/modules/contract/Figure.d.ts +31 -4
  200. package/types/modules/ui/CommandMenu.d.ts +262 -0
  201. package/types/modules/ui/ModalAnchorEditor.d.ts +6 -1
  202. package/types/modules/ui/SelectMenu.d.ts +56 -3
  203. package/types/modules/ui/index.d.ts +1 -0
  204. package/types/plugins/browser/audioGallery.d.ts +17 -1
  205. package/types/plugins/browser/fileBrowser.d.ts +17 -1
  206. package/types/plugins/browser/fileGallery.d.ts +17 -1
  207. package/types/plugins/browser/imageGallery.d.ts +17 -1
  208. package/types/plugins/browser/videoGallery.d.ts +17 -1
  209. package/types/plugins/dropdown/align.d.ts +1 -1
  210. package/types/plugins/dropdown/blockStyle.d.ts +3 -1
  211. package/types/plugins/dropdown/table/index.d.ts +4 -1
  212. package/types/plugins/dropdown/table/services/table.clipboard.d.ts +5 -1
  213. package/types/plugins/dropdown/table/services/table.grid.d.ts +10 -2
  214. package/types/plugins/field/autocomplete.d.ts +0 -1
  215. package/types/plugins/field/slashCommand.d.ts +165 -0
  216. package/types/plugins/index.d.ts +3 -0
  217. package/types/plugins/modal/embed.d.ts +34 -0
  218. package/types/plugins/modal/link.d.ts +4 -1
  219. package/types/typedef.d.ts +48 -23
@@ -49,7 +49,9 @@ class Drawing extends PluginModal {
49
49
  this.pluginOptions = {
50
50
  outputFormat: pluginOptions.outputFormat || 'dataurl', // dataurl, svg
51
51
  useFormatType: pluginOptions.useFormatType ?? false,
52
- defaultFormatType: ['block', 'inline'].includes(pluginOptions.defaultFormatType) ? pluginOptions.defaultFormatType : 'block',
52
+ defaultFormatType: ['block', 'inline'].includes(pluginOptions.defaultFormatType)
53
+ ? pluginOptions.defaultFormatType
54
+ : 'block',
53
55
  keepFormatType: pluginOptions.keepFormatType ?? false,
54
56
  lineWidth: pluginOptions.lineWidth || 5,
55
57
  lineReconnect: !!pluginOptions.lineReconnect,
@@ -70,9 +72,13 @@ class Drawing extends PluginModal {
70
72
 
71
73
  // exception
72
74
  if (!this.$.plugins.image) {
73
- console.warn('[SUNEDITOR.plugins.drawing.warn] The drawing plugin must need either "image" plugin. Please add the "image" plugin.');
75
+ console.warn(
76
+ '[SUNEDITOR.plugins.drawing.warn] The drawing plugin must need either "image" plugin. Please add the "image" plugin.',
77
+ );
74
78
  } else if (this.pluginOptions.outputFormat === 'svg' && !this.$.plugins.image.pluginOptions.uploadUrl) {
75
- console.warn('[SUNEDITOR.plugins.drawing.warn] The drawing plugin must need the "image" plugin with the "uploadUrl" option. Please add the "image" plugin with the "uploadUrl" option.');
79
+ console.warn(
80
+ '[SUNEDITOR.plugins.drawing.warn] The drawing plugin must need the "image" plugin with the "uploadUrl" option. Please add the "image" plugin with the "uploadUrl" option.',
81
+ );
76
82
  }
77
83
 
78
84
  // create HTML
@@ -119,7 +125,11 @@ class Drawing extends PluginModal {
119
125
  };
120
126
 
121
127
  // init
122
- this.$.eventManager.addEvent(modalEl.querySelector('[data-command="remove"]'), 'click', this.#OnRemove.bind(this));
128
+ this.$.eventManager.addEvent(
129
+ modalEl.querySelector('[data-command="remove"]'),
130
+ 'click',
131
+ this.#OnRemove.bind(this),
132
+ );
123
133
  }
124
134
 
125
135
  /**
@@ -128,7 +138,9 @@ class Drawing extends PluginModal {
128
138
  */
129
139
  open() {
130
140
  if (this.pluginOptions.useFormatType) {
131
- this.#activeAsInline((this.pluginOptions.keepFormatType ? this.as : this.pluginOptions.defaultFormatType) === 'inline');
141
+ this.#activeAsInline(
142
+ (this.pluginOptions.keepFormatType ? this.as : this.pluginOptions.defaultFormatType) === 'inline',
143
+ );
132
144
  }
133
145
  this.modal.open();
134
146
  this.#initDrawing();
@@ -180,10 +192,26 @@ class Drawing extends PluginModal {
180
192
 
181
193
  this.#setCtx();
182
194
 
183
- this.#eventsRegister.touchstart = this.$.eventManager.addEvent(canvas, 'touchstart', this.#events.touchstart, { passive: false, capture: true });
184
- this.#eventsRegister.touchmove = this.$.eventManager.addEvent(canvas, 'touchmove', this.#events.touchmove, true);
185
- this.#eventsRegister.mousedown = this.$.eventManager.addEvent(canvas, 'mousedown', this.#events.mousedown, { passive: false, capture: true });
186
- this.#eventsRegister.mousemove = this.$.eventManager.addEvent(canvas, 'mousemove', this.#events.mousemove, true);
195
+ this.#eventsRegister.touchstart = this.$.eventManager.addEvent(canvas, 'touchstart', this.#events.touchstart, {
196
+ passive: false,
197
+ capture: true,
198
+ });
199
+ this.#eventsRegister.touchmove = this.$.eventManager.addEvent(
200
+ canvas,
201
+ 'touchmove',
202
+ this.#events.touchmove,
203
+ true,
204
+ );
205
+ this.#eventsRegister.mousedown = this.$.eventManager.addEvent(canvas, 'mousedown', this.#events.mousedown, {
206
+ passive: false,
207
+ capture: true,
208
+ });
209
+ this.#eventsRegister.mousemove = this.$.eventManager.addEvent(
210
+ canvas,
211
+ 'mousemove',
212
+ this.#events.mousemove,
213
+ true,
214
+ );
187
215
  this.#eventsRegister.mouseup = this.$.eventManager.addEvent(canvas, 'mouseup', this.#events.mouseup, true);
188
216
  this.#eventsRegister.mouseleave = this.$.eventManager.addEvent(canvas, 'mouseleave', this.#events.mouseleave);
189
217
  this.#eventsRegister.mouseenter = this.$.eventManager.addEvent(canvas, 'mouseenter', this.#events.mouseenter);
@@ -202,7 +230,8 @@ class Drawing extends PluginModal {
202
230
  canvas.width = newWidth;
203
231
  canvas.height = newHeight;
204
232
  if (prevWidth !== canvas.width || prevHeight !== canvas.height) {
205
- if (this.pluginOptions.maintainRatio) this.#adjustPathsToNewDimensions(prevWidth, prevHeight, newWidth, newHeight);
233
+ if (this.pluginOptions.maintainRatio)
234
+ this.#adjustPathsToNewDimensions(prevWidth, prevHeight, newWidth, newHeight);
206
235
  this.#drawAll();
207
236
  }
208
237
  });
@@ -241,7 +270,8 @@ class Drawing extends PluginModal {
241
270
  #setCtx() {
242
271
  this.ctx.lineWidth = this.pluginOptions.lineWidth;
243
272
  this.ctx.lineCap = this.pluginOptions.lineCap;
244
- this.ctx.strokeStyle = this.pluginOptions.lineColor || _w.getComputedStyle(this.$.contextProvider.carrierWrapper).color;
273
+ this.ctx.strokeStyle =
274
+ this.pluginOptions.lineColor || _w.getComputedStyle(this.$.contextProvider.carrierWrapper).color;
245
275
  }
246
276
 
247
277
  /**
@@ -39,6 +39,17 @@ const { _w, NO_EVENT } = env;
39
39
  * { query_vimeo: 'autoplay=1' }
40
40
  * ```
41
41
  * @property {Array<RegExp>} [urlPatterns] - Additional URL patterns to recognize as embeddable content.
42
+ * @property {Array<RegExp|string>} [scriptSrcWhitelist] - Allowed `<script src=...>` patterns for raw embed HTML
43
+ * - (e.g. Twitter blockquote + `widgets.js`). Each entry is a `RegExp` (tested against the full src) or a `string` (matched via `startsWith`).
44
+ * - Defaults to `[]` — all script tags are rejected.** Inline scripts (no `src`) are always rejected.
45
+ * ```js
46
+ * {
47
+ * scriptSrcWhitelist: [
48
+ * /^https:\/\/platform\.x\.com\/widgets\.js$/,
49
+ * /^https:\/\/www\.instagram\.com\/embed\.js$/,
50
+ * ]
51
+ * }
52
+ * ```
42
53
  * @property {Object<string, {pattern: RegExp, action: (url: string) => string, tag: string}>} [embedQuery] - Custom embed service definitions.
43
54
  * Each key is a service name, with `pattern` to match the URL, `action` to transform it into an embed URL, and `tag` for the output element.
44
55
  * ```js
@@ -125,6 +136,18 @@ class Embed extends PluginModal {
125
136
  return false;
126
137
  }
127
138
 
139
+ /**
140
+ * @description Validate a `<script src>` against the configured whitelist for raw embed HTML.
141
+ * Inline scripts (no `src`) are always rejected.
142
+ * @param {string} src
143
+ * @param {Array<RegExp|string>} whitelist
144
+ * @returns {boolean}
145
+ */
146
+ static #isAllowedScriptSrc(src, whitelist) {
147
+ if (!src) return false;
148
+ return whitelist.some((p) => (p instanceof RegExp ? p.test(src) : src.startsWith(p)));
149
+ }
150
+
128
151
  /** @type {Array<RegExp>} */
129
152
  static #urlPatterns = null;
130
153
 
@@ -158,8 +181,18 @@ class Embed extends PluginModal {
158
181
  this.pluginOptions = {
159
182
  canResize: pluginOptions.canResize === undefined ? true : pluginOptions.canResize,
160
183
  showHeightInput: pluginOptions.showHeightInput === undefined ? true : !!pluginOptions.showHeightInput,
161
- defaultWidth: !pluginOptions.defaultWidth || !numbers.get(pluginOptions.defaultWidth, 0) ? '' : numbers.is(pluginOptions.defaultWidth) ? pluginOptions.defaultWidth + 'px' : pluginOptions.defaultWidth,
162
- defaultHeight: !pluginOptions.defaultHeight || !numbers.get(pluginOptions.defaultHeight, 0) ? '' : numbers.is(pluginOptions.defaultHeight) ? pluginOptions.defaultHeight + 'px' : pluginOptions.defaultHeight,
184
+ defaultWidth:
185
+ !pluginOptions.defaultWidth || !numbers.get(pluginOptions.defaultWidth, 0)
186
+ ? ''
187
+ : numbers.is(pluginOptions.defaultWidth)
188
+ ? pluginOptions.defaultWidth + 'px'
189
+ : pluginOptions.defaultWidth,
190
+ defaultHeight:
191
+ !pluginOptions.defaultHeight || !numbers.get(pluginOptions.defaultHeight, 0)
192
+ ? ''
193
+ : numbers.is(pluginOptions.defaultHeight)
194
+ ? pluginOptions.defaultHeight + 'px'
195
+ : pluginOptions.defaultHeight,
163
196
  percentageOnlySize: !!pluginOptions.percentageOnlySize,
164
197
  uploadUrl: typeof pluginOptions.uploadUrl === 'string' ? pluginOptions.uploadUrl : null,
165
198
  uploadHeaders: pluginOptions.uploadHeaders || null,
@@ -168,16 +201,22 @@ class Embed extends PluginModal {
168
201
  iframeTagAttributes: pluginOptions.iframeTagAttributes || null,
169
202
  query_youtube: pluginOptions.query_youtube || '',
170
203
  query_vimeo: pluginOptions.query_vimeo || '',
204
+ scriptSrcWhitelist: Array.isArray(pluginOptions.scriptSrcWhitelist) ? pluginOptions.scriptSrcWhitelist : [],
171
205
  insertBehavior: pluginOptions.insertBehavior,
172
206
  };
173
207
 
174
208
  // create HTML
175
209
  const sizeUnit = this.pluginOptions.percentageOnlySize ? '%' : 'px';
176
210
  const modalEl = CreateHTML_modal(this.$, this.pluginOptions);
177
- const figureControls = pluginOptions.controls || (!this.pluginOptions.canResize ? [['align', 'edit', 'copy', 'remove']] : [['resize_auto,75,50', 'align', 'edit', 'revert', 'copy', 'remove']]);
211
+ const figureControls =
212
+ pluginOptions.controls ||
213
+ (!this.pluginOptions.canResize
214
+ ? [['align', 'edit', 'copy', 'remove']]
215
+ : [['resize_auto,75,50', 'align', 'edit', 'revert', 'copy', 'remove']]);
178
216
 
179
217
  // show align
180
- if (!figureControls.some((subArray) => subArray.includes('align'))) modalEl.figureAlignBtn.style.display = 'none';
218
+ if (!figureControls.some((subArray) => subArray.includes('align')))
219
+ modalEl.figureAlignBtn.style.display = 'none';
181
220
 
182
221
  // modules
183
222
  this.modal = new Modal(this, this.$, modalEl.html);
@@ -210,7 +249,8 @@ class Embed extends PluginModal {
210
249
  tag: 'iframe',
211
250
  },
212
251
  twitter: {
213
- pattern: /^(?:https?:\/\/)?(?:(?:www\.)?(?:twitter\.com|x\.com)\/(?:[^/?#]+\/)?status\/\d+(?:[/?#]|$)|platform\.twitter\.com\/embed\/Tweet\.html(?:[?#].*)?$)/i,
252
+ pattern:
253
+ /^(?:https?:\/\/)?(?:(?:www\.)?(?:twitter\.com|x\.com)\/(?:[^/?#]+\/)?status\/\d+(?:[/?#]|$)|platform\.twitter\.com\/embed\/Tweet\.html(?:[?#].*)?$)/i,
214
254
  action: (url) => {
215
255
  return `https://platform.twitter.com/embed/Tweet.html?url=${encodeURIComponent(url)}`;
216
256
  },
@@ -319,11 +359,16 @@ class Embed extends PluginModal {
319
359
  */
320
360
  modalOn(isUpdate) {
321
361
  if (!isUpdate && this.#resizing) {
322
- this.inputX.value = this.#origin_w = this.pluginOptions.defaultWidth === 'auto' ? '' : this.pluginOptions.defaultWidth;
323
- this.inputY.value = this.#origin_h = this.pluginOptions.defaultHeight === 'auto' ? '' : this.pluginOptions.defaultHeight;
362
+ this.inputX.value = this.#origin_w =
363
+ this.pluginOptions.defaultWidth === 'auto' ? '' : this.pluginOptions.defaultWidth;
364
+ this.inputY.value = this.#origin_h =
365
+ this.pluginOptions.defaultHeight === 'auto' ? '' : this.pluginOptions.defaultHeight;
324
366
  this.proportion.disabled = true;
325
367
  } else if (isUpdate) {
326
- this.#linkValue = this.previewSrc.textContent = this.embedInput.value = this.#cover.getAttribute('data-se-origin') || '';
368
+ this.#linkValue =
369
+ this.previewSrc.textContent =
370
+ this.embedInput.value =
371
+ this.#cover.getAttribute('data-se-origin') || '';
327
372
  }
328
373
  }
329
374
 
@@ -332,7 +377,9 @@ class Embed extends PluginModal {
332
377
  * @type {SunEditor.Hook.Modal.Action}
333
378
  */
334
379
  async modalAction() {
335
- this.#align = /** @type {HTMLInputElement} */ (this.modal.form.querySelector('input[name="suneditor_embed_radio"]:checked')).value;
380
+ this.#align = /** @type {HTMLInputElement} */ (
381
+ this.modal.form.querySelector('input[name="suneditor_embed_radio"]:checked')
382
+ ).value;
336
383
 
337
384
  let result = false;
338
385
  if (this.#linkValue.length > 0) {
@@ -352,13 +399,17 @@ class Embed extends PluginModal {
352
399
  Modal.OnChangeFile(this.fileModalWrapper, []);
353
400
  this.#linkValue = this.previewSrc.textContent = this.embedInput.value = '';
354
401
 
355
- /** @type {HTMLInputElement} */ (this.modal.form.querySelector('input[name="suneditor_embed_radio"][value="none"]')).checked = true;
402
+ /** @type {HTMLInputElement} */ (
403
+ this.modal.form.querySelector('input[name="suneditor_embed_radio"][value="none"]')
404
+ ).checked = true;
356
405
  this.#ratio = { w: 0, h: 0 };
357
406
  this.#nonResizing = false;
358
407
 
359
408
  if (this.#resizing) {
360
- this.inputX.value = this.pluginOptions.defaultWidth === this.#defaultSizeX ? '' : this.pluginOptions.defaultWidth;
361
- this.inputY.value = this.pluginOptions.defaultHeight === this.#defaultSizeY ? '' : this.pluginOptions.defaultHeight;
409
+ this.inputX.value =
410
+ this.pluginOptions.defaultWidth === this.#defaultSizeX ? '' : this.pluginOptions.defaultWidth;
411
+ this.inputY.value =
412
+ this.pluginOptions.defaultHeight === this.#defaultSizeY ? '' : this.pluginOptions.defaultHeight;
362
413
  this.proportion.checked = false;
363
414
  this.proportion.disabled = true;
364
415
  }
@@ -390,7 +441,12 @@ class Embed extends PluginModal {
390
441
  const focusEl = container.previousElementSibling || container.nextElementSibling;
391
442
  const emptyDiv = container.parentNode;
392
443
 
393
- const message = await this.$.eventManager.triggerEvent('onEmbedDeleteBefore', { element: targetEl, container, align: this.#align, url: this.#linkValue });
444
+ const message = await this.$.eventManager.triggerEvent('onEmbedDeleteBefore', {
445
+ element: targetEl,
446
+ container,
447
+ align: this.#align,
448
+ url: this.#linkValue,
449
+ });
394
450
  if (message === false) return;
395
451
 
396
452
  dom.utils.removeItem(container);
@@ -446,6 +502,20 @@ class Embed extends PluginModal {
446
502
  if (/^<iframe\s|^<blockquote\s/i.test(src)) {
447
503
  const embedDOM = new DOMParser().parseFromString(src, 'text/html').body.children;
448
504
  if (embedDOM.length === 0) return false;
505
+
506
+ // Validate every iframe in the raw HTML against the URL whitelist.
507
+ for (let i = 0; i < embedDOM.length; i++) {
508
+ const node = /** @type {Element} */ (embedDOM[i]);
509
+ if (/^iframe$/i.test(node.nodeName) && !Embed.#checkContentType(node.getAttribute('src') || ''))
510
+ return false;
511
+ const nested = node.querySelectorAll?.('iframe');
512
+ if (nested) {
513
+ for (let j = 0; j < nested.length; j++) {
514
+ if (!Embed.#checkContentType(nested[j].getAttribute('src') || '')) return false;
515
+ }
516
+ }
517
+ }
518
+
449
519
  embedInfo = { children: embedDOM, ...this.#getInfo(), process: null };
450
520
  } else {
451
521
  const processUrl = this.findProcessUrl(src);
@@ -461,7 +531,16 @@ class Embed extends PluginModal {
461
531
 
462
532
  const handler = function (uploadCallback, infos, newInfos) {
463
533
  infos = newInfos || infos;
464
- uploadCallback(src, infos.process, infos.url, infos.children, infos.inputWidth, infos.inputHeight, infos.align, infos.isUpdate);
534
+ uploadCallback(
535
+ src,
536
+ infos.process,
537
+ infos.url,
538
+ infos.children,
539
+ infos.inputWidth,
540
+ infos.inputHeight,
541
+ infos.align,
542
+ infos.isUpdate,
543
+ );
465
544
  }.bind(this, this.#create.bind(this), embedInfo);
466
545
 
467
546
  const result = await this.$.eventManager.triggerEvent('onEmbedInputBefore', {
@@ -487,7 +566,13 @@ class Embed extends PluginModal {
487
566
  */
488
567
  #ready(target, infoOnly = false) {
489
568
  if (!target) return;
490
- const figureInfo = this.figure.open(target, { nonResizing: this.#nonResizing, nonSizeInfo: false, nonBorder: false, figureTarget: false, infoOnly });
569
+ const figureInfo = this.figure.open(target, {
570
+ nonResizing: this.#nonResizing,
571
+ nonSizeInfo: false,
572
+ nonBorder: false,
573
+ figureTarget: false,
574
+ infoOnly,
575
+ });
491
576
 
492
577
  this.#element = target;
493
578
  this.#cover = figureInfo.cover;
@@ -508,7 +593,9 @@ class Embed extends PluginModal {
508
593
  this.#origin_h = String(figureInfo.originHeight || figureInfo.h || '');
509
594
 
510
595
  /** @type {HTMLInputElement} */
511
- const activeAlign = this.modal.form.querySelector('input[name="suneditor_embed_radio"][value="' + this.#align + '"]') || this.modal.form.querySelector('input[name="suneditor_embed_radio"][value="none"]');
596
+ const activeAlign =
597
+ this.modal.form.querySelector('input[name="suneditor_embed_radio"][value="' + this.#align + '"]') ||
598
+ this.modal.form.querySelector('input[name="suneditor_embed_radio"][value="none"]');
512
599
  activeAlign.checked = true;
513
600
 
514
601
  if (!this.#resizing) return;
@@ -604,9 +691,12 @@ class Embed extends PluginModal {
604
691
  container = figure.container;
605
692
 
606
693
  const childNodes = Array.from(children);
694
+ const scriptWhitelist = this.pluginOptions.scriptSrcWhitelist;
607
695
  for (const chd of childNodes) {
608
696
  if (/^script$/i.test(chd.nodeName)) {
609
- scriptTag = dom.utils.createElement('script', { src: /** @type {Element} */ (chd).getAttribute('src'), async: 'true' }, null);
697
+ const scriptSrc = /** @type {Element} */ (chd).getAttribute('src') || '';
698
+ if (!Embed.#isAllowedScriptSrc(scriptSrc, scriptWhitelist)) continue;
699
+ scriptTag = dom.utils.createElement('script', { src: scriptSrc, async: 'true' }, null);
610
700
  continue;
611
701
  }
612
702
  cover.appendChild(chd);
@@ -618,7 +708,13 @@ class Embed extends PluginModal {
618
708
  this.#element = oFrame;
619
709
  this.#cover = cover;
620
710
  this.#container = container;
621
- this.figure.open(oFrame, { nonResizing: this.#nonResizing, nonSizeInfo: false, nonBorder: false, figureTarget: false, infoOnly: true });
711
+ this.figure.open(oFrame, {
712
+ nonResizing: this.#nonResizing,
713
+ nonSizeInfo: false,
714
+ nonBorder: false,
715
+ figureTarget: false,
716
+ infoOnly: true,
717
+ });
622
718
 
623
719
  width ||= this.#defaultSizeX;
624
720
  height ||= this.#defaultSizeY;
@@ -639,7 +735,11 @@ class Embed extends PluginModal {
639
735
  cover.setAttribute('data-se-origin', originSrc);
640
736
 
641
737
  if (!isUpdate) {
642
- this.$.component.insert(container, { skipHistory: true, scrollTo: false, insertBehavior: this.pluginOptions.insertBehavior });
738
+ this.$.component.insert(container, {
739
+ skipHistory: true,
740
+ scrollTo: false,
741
+ insertBehavior: this.pluginOptions.insertBehavior,
742
+ });
643
743
 
644
744
  if (scriptTag) {
645
745
  try {
@@ -681,7 +781,8 @@ class Embed extends PluginModal {
681
781
  return;
682
782
  }
683
783
 
684
- if (!this.#resizing || !changeSize || !this.figure.isVertical) this.figure.setTransform(oFrame, width, height, 0);
784
+ if (!this.#resizing || !changeSize || !this.figure.isVertical)
785
+ this.figure.setTransform(oFrame, width, height, 0);
685
786
  if (!scriptTag) this.$.history.push(false);
686
787
  }
687
788
 
@@ -700,7 +801,13 @@ class Embed extends PluginModal {
700
801
  const container = figure.container;
701
802
 
702
803
  // size
703
- this.figure.open(oFrame, { nonResizing: this.#nonResizing, nonSizeInfo: false, nonBorder: false, figureTarget: false, infoOnly: true });
804
+ this.figure.open(oFrame, {
805
+ nonResizing: this.#nonResizing,
806
+ nonSizeInfo: false,
807
+ nonBorder: false,
808
+ figureTarget: false,
809
+ infoOnly: true,
810
+ });
704
811
  const size = (oFrame.getAttribute('data-se-size') || ',').split(',');
705
812
 
706
813
  const width = size[0] || prevFrame.width || '';
@@ -860,7 +967,10 @@ function CreateHTML_modal({ lang, icons }, pluginOptions) {
860
967
  const onlyPercentDisplay = onlyPercentage ? ' style="display: none !important;"' : '';
861
968
  const heightDisplay = !pluginOptions.showHeightInput ? ' style="display: none !important;"' : '';
862
969
  const ratioDisplay = !pluginOptions.showRatioOption ? ' style="display: none !important;"' : '';
863
- const onlyWidthDisplay = !onlyPercentage && !pluginOptions.showHeightInput && !pluginOptions.showRatioOption ? ' style="display: none !important;"' : '';
970
+ const onlyWidthDisplay =
971
+ !onlyPercentage && !pluginOptions.showHeightInput && !pluginOptions.showRatioOption
972
+ ? ' style="display: none !important;"'
973
+ : '';
864
974
  html += /*html*/ `
865
975
  <div class="se-modal-form">
866
976
  <div class="se-modal-size-text">