@ckeditor/ckeditor5-html-embed 0.0.0-internal-20241017.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 (284) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/LICENSE.md +17 -0
  3. package/README.md +26 -0
  4. package/build/html-embed.js +5 -0
  5. package/build/translations/ar.js +1 -0
  6. package/build/translations/bg.js +1 -0
  7. package/build/translations/bn.js +1 -0
  8. package/build/translations/bs.js +1 -0
  9. package/build/translations/ca.js +1 -0
  10. package/build/translations/cs.js +1 -0
  11. package/build/translations/da.js +1 -0
  12. package/build/translations/de-ch.js +1 -0
  13. package/build/translations/de.js +1 -0
  14. package/build/translations/el.js +1 -0
  15. package/build/translations/en-au.js +1 -0
  16. package/build/translations/es.js +1 -0
  17. package/build/translations/et.js +1 -0
  18. package/build/translations/fa.js +1 -0
  19. package/build/translations/fi.js +1 -0
  20. package/build/translations/fr.js +1 -0
  21. package/build/translations/gl.js +1 -0
  22. package/build/translations/he.js +1 -0
  23. package/build/translations/hi.js +1 -0
  24. package/build/translations/hr.js +1 -0
  25. package/build/translations/hu.js +1 -0
  26. package/build/translations/id.js +1 -0
  27. package/build/translations/it.js +1 -0
  28. package/build/translations/ja.js +1 -0
  29. package/build/translations/jv.js +1 -0
  30. package/build/translations/ko.js +1 -0
  31. package/build/translations/lt.js +1 -0
  32. package/build/translations/lv.js +1 -0
  33. package/build/translations/ms.js +1 -0
  34. package/build/translations/nl.js +1 -0
  35. package/build/translations/no.js +1 -0
  36. package/build/translations/pl.js +1 -0
  37. package/build/translations/pt-br.js +1 -0
  38. package/build/translations/pt.js +1 -0
  39. package/build/translations/ro.js +1 -0
  40. package/build/translations/ru.js +1 -0
  41. package/build/translations/sk.js +1 -0
  42. package/build/translations/sl.js +1 -0
  43. package/build/translations/sr-latn.js +1 -0
  44. package/build/translations/sr.js +1 -0
  45. package/build/translations/sv.js +1 -0
  46. package/build/translations/th.js +1 -0
  47. package/build/translations/tr.js +1 -0
  48. package/build/translations/uk.js +1 -0
  49. package/build/translations/ur.js +1 -0
  50. package/build/translations/uz.js +1 -0
  51. package/build/translations/vi.js +1 -0
  52. package/build/translations/zh-cn.js +1 -0
  53. package/build/translations/zh.js +1 -0
  54. package/ckeditor5-metadata.json +31 -0
  55. package/dist/augmentation.d.ts +28 -0
  56. package/dist/htmlembed.d.ts +36 -0
  57. package/dist/htmlembedcommand.d.ts +48 -0
  58. package/dist/htmlembedconfig.d.ts +90 -0
  59. package/dist/htmlembedediting.d.ts +48 -0
  60. package/dist/htmlembedui.d.ts +33 -0
  61. package/dist/index-content.css +4 -0
  62. package/dist/index-editor.css +44 -0
  63. package/dist/index.css +68 -0
  64. package/dist/index.css.map +1 -0
  65. package/dist/index.d.ts +17 -0
  66. package/dist/index.js +530 -0
  67. package/dist/index.js.map +1 -0
  68. package/dist/translations/ar.d.ts +8 -0
  69. package/dist/translations/ar.js +5 -0
  70. package/dist/translations/ar.umd.js +11 -0
  71. package/dist/translations/bg.d.ts +8 -0
  72. package/dist/translations/bg.js +5 -0
  73. package/dist/translations/bg.umd.js +11 -0
  74. package/dist/translations/bn.d.ts +8 -0
  75. package/dist/translations/bn.js +5 -0
  76. package/dist/translations/bn.umd.js +11 -0
  77. package/dist/translations/bs.d.ts +8 -0
  78. package/dist/translations/bs.js +5 -0
  79. package/dist/translations/bs.umd.js +11 -0
  80. package/dist/translations/ca.d.ts +8 -0
  81. package/dist/translations/ca.js +5 -0
  82. package/dist/translations/ca.umd.js +11 -0
  83. package/dist/translations/cs.d.ts +8 -0
  84. package/dist/translations/cs.js +5 -0
  85. package/dist/translations/cs.umd.js +11 -0
  86. package/dist/translations/da.d.ts +8 -0
  87. package/dist/translations/da.js +5 -0
  88. package/dist/translations/da.umd.js +11 -0
  89. package/dist/translations/de-ch.d.ts +8 -0
  90. package/dist/translations/de-ch.js +5 -0
  91. package/dist/translations/de-ch.umd.js +11 -0
  92. package/dist/translations/de.d.ts +8 -0
  93. package/dist/translations/de.js +5 -0
  94. package/dist/translations/de.umd.js +11 -0
  95. package/dist/translations/el.d.ts +8 -0
  96. package/dist/translations/el.js +5 -0
  97. package/dist/translations/el.umd.js +11 -0
  98. package/dist/translations/en-au.d.ts +8 -0
  99. package/dist/translations/en-au.js +5 -0
  100. package/dist/translations/en-au.umd.js +11 -0
  101. package/dist/translations/en.d.ts +8 -0
  102. package/dist/translations/en.js +5 -0
  103. package/dist/translations/en.umd.js +11 -0
  104. package/dist/translations/es.d.ts +8 -0
  105. package/dist/translations/es.js +5 -0
  106. package/dist/translations/es.umd.js +11 -0
  107. package/dist/translations/et.d.ts +8 -0
  108. package/dist/translations/et.js +5 -0
  109. package/dist/translations/et.umd.js +11 -0
  110. package/dist/translations/fa.d.ts +8 -0
  111. package/dist/translations/fa.js +5 -0
  112. package/dist/translations/fa.umd.js +11 -0
  113. package/dist/translations/fi.d.ts +8 -0
  114. package/dist/translations/fi.js +5 -0
  115. package/dist/translations/fi.umd.js +11 -0
  116. package/dist/translations/fr.d.ts +8 -0
  117. package/dist/translations/fr.js +5 -0
  118. package/dist/translations/fr.umd.js +11 -0
  119. package/dist/translations/gl.d.ts +8 -0
  120. package/dist/translations/gl.js +5 -0
  121. package/dist/translations/gl.umd.js +11 -0
  122. package/dist/translations/he.d.ts +8 -0
  123. package/dist/translations/he.js +5 -0
  124. package/dist/translations/he.umd.js +11 -0
  125. package/dist/translations/hi.d.ts +8 -0
  126. package/dist/translations/hi.js +5 -0
  127. package/dist/translations/hi.umd.js +11 -0
  128. package/dist/translations/hr.d.ts +8 -0
  129. package/dist/translations/hr.js +5 -0
  130. package/dist/translations/hr.umd.js +11 -0
  131. package/dist/translations/hu.d.ts +8 -0
  132. package/dist/translations/hu.js +5 -0
  133. package/dist/translations/hu.umd.js +11 -0
  134. package/dist/translations/id.d.ts +8 -0
  135. package/dist/translations/id.js +5 -0
  136. package/dist/translations/id.umd.js +11 -0
  137. package/dist/translations/it.d.ts +8 -0
  138. package/dist/translations/it.js +5 -0
  139. package/dist/translations/it.umd.js +11 -0
  140. package/dist/translations/ja.d.ts +8 -0
  141. package/dist/translations/ja.js +5 -0
  142. package/dist/translations/ja.umd.js +11 -0
  143. package/dist/translations/jv.d.ts +8 -0
  144. package/dist/translations/jv.js +5 -0
  145. package/dist/translations/jv.umd.js +11 -0
  146. package/dist/translations/ko.d.ts +8 -0
  147. package/dist/translations/ko.js +5 -0
  148. package/dist/translations/ko.umd.js +11 -0
  149. package/dist/translations/lt.d.ts +8 -0
  150. package/dist/translations/lt.js +5 -0
  151. package/dist/translations/lt.umd.js +11 -0
  152. package/dist/translations/lv.d.ts +8 -0
  153. package/dist/translations/lv.js +5 -0
  154. package/dist/translations/lv.umd.js +11 -0
  155. package/dist/translations/ms.d.ts +8 -0
  156. package/dist/translations/ms.js +5 -0
  157. package/dist/translations/ms.umd.js +11 -0
  158. package/dist/translations/nl.d.ts +8 -0
  159. package/dist/translations/nl.js +5 -0
  160. package/dist/translations/nl.umd.js +11 -0
  161. package/dist/translations/no.d.ts +8 -0
  162. package/dist/translations/no.js +5 -0
  163. package/dist/translations/no.umd.js +11 -0
  164. package/dist/translations/pl.d.ts +8 -0
  165. package/dist/translations/pl.js +5 -0
  166. package/dist/translations/pl.umd.js +11 -0
  167. package/dist/translations/pt-br.d.ts +8 -0
  168. package/dist/translations/pt-br.js +5 -0
  169. package/dist/translations/pt-br.umd.js +11 -0
  170. package/dist/translations/pt.d.ts +8 -0
  171. package/dist/translations/pt.js +5 -0
  172. package/dist/translations/pt.umd.js +11 -0
  173. package/dist/translations/ro.d.ts +8 -0
  174. package/dist/translations/ro.js +5 -0
  175. package/dist/translations/ro.umd.js +11 -0
  176. package/dist/translations/ru.d.ts +8 -0
  177. package/dist/translations/ru.js +5 -0
  178. package/dist/translations/ru.umd.js +11 -0
  179. package/dist/translations/sk.d.ts +8 -0
  180. package/dist/translations/sk.js +5 -0
  181. package/dist/translations/sk.umd.js +11 -0
  182. package/dist/translations/sl.d.ts +8 -0
  183. package/dist/translations/sl.js +5 -0
  184. package/dist/translations/sl.umd.js +11 -0
  185. package/dist/translations/sr-latn.d.ts +8 -0
  186. package/dist/translations/sr-latn.js +5 -0
  187. package/dist/translations/sr-latn.umd.js +11 -0
  188. package/dist/translations/sr.d.ts +8 -0
  189. package/dist/translations/sr.js +5 -0
  190. package/dist/translations/sr.umd.js +11 -0
  191. package/dist/translations/sv.d.ts +8 -0
  192. package/dist/translations/sv.js +5 -0
  193. package/dist/translations/sv.umd.js +11 -0
  194. package/dist/translations/th.d.ts +8 -0
  195. package/dist/translations/th.js +5 -0
  196. package/dist/translations/th.umd.js +11 -0
  197. package/dist/translations/tr.d.ts +8 -0
  198. package/dist/translations/tr.js +5 -0
  199. package/dist/translations/tr.umd.js +11 -0
  200. package/dist/translations/uk.d.ts +8 -0
  201. package/dist/translations/uk.js +5 -0
  202. package/dist/translations/uk.umd.js +11 -0
  203. package/dist/translations/ur.d.ts +8 -0
  204. package/dist/translations/ur.js +5 -0
  205. package/dist/translations/ur.umd.js +11 -0
  206. package/dist/translations/uz.d.ts +8 -0
  207. package/dist/translations/uz.js +5 -0
  208. package/dist/translations/uz.umd.js +11 -0
  209. package/dist/translations/vi.d.ts +8 -0
  210. package/dist/translations/vi.js +5 -0
  211. package/dist/translations/vi.umd.js +11 -0
  212. package/dist/translations/zh-cn.d.ts +8 -0
  213. package/dist/translations/zh-cn.js +5 -0
  214. package/dist/translations/zh-cn.umd.js +11 -0
  215. package/dist/translations/zh.d.ts +8 -0
  216. package/dist/translations/zh.js +5 -0
  217. package/dist/translations/zh.umd.js +11 -0
  218. package/lang/contexts.json +9 -0
  219. package/lang/translations/ar.po +46 -0
  220. package/lang/translations/bg.po +46 -0
  221. package/lang/translations/bn.po +46 -0
  222. package/lang/translations/bs.po +46 -0
  223. package/lang/translations/ca.po +46 -0
  224. package/lang/translations/cs.po +46 -0
  225. package/lang/translations/da.po +46 -0
  226. package/lang/translations/de-ch.po +46 -0
  227. package/lang/translations/de.po +46 -0
  228. package/lang/translations/el.po +46 -0
  229. package/lang/translations/en-au.po +46 -0
  230. package/lang/translations/en.po +46 -0
  231. package/lang/translations/es.po +46 -0
  232. package/lang/translations/et.po +46 -0
  233. package/lang/translations/fa.po +46 -0
  234. package/lang/translations/fi.po +46 -0
  235. package/lang/translations/fr.po +46 -0
  236. package/lang/translations/gl.po +46 -0
  237. package/lang/translations/he.po +46 -0
  238. package/lang/translations/hi.po +46 -0
  239. package/lang/translations/hr.po +46 -0
  240. package/lang/translations/hu.po +46 -0
  241. package/lang/translations/id.po +46 -0
  242. package/lang/translations/it.po +46 -0
  243. package/lang/translations/ja.po +46 -0
  244. package/lang/translations/jv.po +46 -0
  245. package/lang/translations/ko.po +46 -0
  246. package/lang/translations/lt.po +46 -0
  247. package/lang/translations/lv.po +46 -0
  248. package/lang/translations/ms.po +46 -0
  249. package/lang/translations/nl.po +46 -0
  250. package/lang/translations/no.po +46 -0
  251. package/lang/translations/pl.po +46 -0
  252. package/lang/translations/pt-br.po +46 -0
  253. package/lang/translations/pt.po +46 -0
  254. package/lang/translations/ro.po +46 -0
  255. package/lang/translations/ru.po +46 -0
  256. package/lang/translations/sk.po +46 -0
  257. package/lang/translations/sl.po +46 -0
  258. package/lang/translations/sr-latn.po +46 -0
  259. package/lang/translations/sr.po +46 -0
  260. package/lang/translations/sv.po +46 -0
  261. package/lang/translations/th.po +46 -0
  262. package/lang/translations/tr.po +46 -0
  263. package/lang/translations/uk.po +46 -0
  264. package/lang/translations/ur.po +46 -0
  265. package/lang/translations/uz.po +46 -0
  266. package/lang/translations/vi.po +46 -0
  267. package/lang/translations/zh-cn.po +46 -0
  268. package/lang/translations/zh.po +46 -0
  269. package/package.json +42 -0
  270. package/src/augmentation.d.ts +24 -0
  271. package/src/augmentation.js +5 -0
  272. package/src/htmlembed.d.ts +32 -0
  273. package/src/htmlembed.js +38 -0
  274. package/src/htmlembedcommand.d.ts +44 -0
  275. package/src/htmlembedcommand.js +95 -0
  276. package/src/htmlembedconfig.d.ts +86 -0
  277. package/src/htmlembedconfig.js +5 -0
  278. package/src/htmlembedediting.d.ts +44 -0
  279. package/src/htmlembedediting.js +333 -0
  280. package/src/htmlembedui.d.ts +29 -0
  281. package/src/htmlembedui.js +72 -0
  282. package/src/index.d.ts +13 -0
  283. package/src/index.js +11 -0
  284. package/theme/htmlembed.css +68 -0
@@ -0,0 +1,95 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+ import { Command } from 'ckeditor5/src/core.js';
6
+ import { findOptimalInsertionRange } from 'ckeditor5/src/widget.js';
7
+ /**
8
+ * The insert HTML embed element command.
9
+ *
10
+ * The command is registered by {@link module:html-embed/htmlembedediting~HtmlEmbedEditing} as `'htmlEmbed'`.
11
+ *
12
+ * To insert an empty HTML embed element at the current selection, execute the command:
13
+ *
14
+ * ```ts
15
+ * editor.execute( 'htmlEmbed' );
16
+ * ```
17
+ *
18
+ * You can specify the initial content of a new HTML embed in the argument:
19
+ *
20
+ * ```ts
21
+ * editor.execute( 'htmlEmbed', '<b>Initial content.</b>' );
22
+ * ```
23
+ *
24
+ * To update the content of the HTML embed, select it in the model and pass the content in the argument:
25
+ *
26
+ * ```ts
27
+ * editor.execute( 'htmlEmbed', '<b>New content of an existing embed.</b>' );
28
+ * ```
29
+ */
30
+ export default class HtmlEmbedCommand extends Command {
31
+ /**
32
+ * @inheritDoc
33
+ */
34
+ refresh() {
35
+ const model = this.editor.model;
36
+ const schema = model.schema;
37
+ const selection = model.document.selection;
38
+ const selectedRawHtmlElement = getSelectedRawHtmlModelWidget(selection);
39
+ this.isEnabled = isHtmlEmbedAllowedInParent(selection, schema, model);
40
+ this.value = selectedRawHtmlElement ? selectedRawHtmlElement.getAttribute('value') || '' : null;
41
+ }
42
+ /**
43
+ * Executes the command, which either:
44
+ *
45
+ * * creates and inserts a new HTML embed element if none was selected,
46
+ * * updates the content of the HTML embed if one was selected.
47
+ *
48
+ * @fires execute
49
+ * @param value When passed, the value (content) will be set on a new embed or a selected one.
50
+ */
51
+ execute(value) {
52
+ const model = this.editor.model;
53
+ const selection = model.document.selection;
54
+ model.change(writer => {
55
+ let htmlEmbedElement;
56
+ // If the command has a non-null value, there must be some HTML embed selected in the model.
57
+ if (this.value !== null) {
58
+ htmlEmbedElement = getSelectedRawHtmlModelWidget(selection);
59
+ }
60
+ else {
61
+ htmlEmbedElement = writer.createElement('rawHtml');
62
+ model.insertObject(htmlEmbedElement, null, null, { setSelection: 'on' });
63
+ }
64
+ writer.setAttribute('value', value, htmlEmbedElement);
65
+ });
66
+ }
67
+ }
68
+ /**
69
+ * Checks if an HTML embed is allowed by the schema in the optimal insertion parent.
70
+ */
71
+ function isHtmlEmbedAllowedInParent(selection, schema, model) {
72
+ const parent = getInsertHtmlEmbedParent(selection, model);
73
+ return schema.checkChild(parent, 'rawHtml');
74
+ }
75
+ /**
76
+ * Returns a node that will be used to insert a html embed with `model.insertContent` to check if a html embed element can be placed there.
77
+ */
78
+ function getInsertHtmlEmbedParent(selection, model) {
79
+ const insertionRange = findOptimalInsertionRange(selection, model);
80
+ const parent = insertionRange.start.parent;
81
+ if (parent.isEmpty && !parent.is('rootElement')) {
82
+ return parent.parent;
83
+ }
84
+ return parent;
85
+ }
86
+ /**
87
+ * Returns the selected HTML embed element in the model, if any.
88
+ */
89
+ function getSelectedRawHtmlModelWidget(selection) {
90
+ const selectedElement = selection.getSelectedElement();
91
+ if (selectedElement && selectedElement.is('element', 'rawHtml')) {
92
+ return selectedElement;
93
+ }
94
+ return null;
95
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+ /**
6
+ * @module html-embed/htmlembedconfig
7
+ */
8
+ /**
9
+ * The configuration of the HTML embed feature.
10
+ *
11
+ * ```ts
12
+ * ClassicEditor
13
+ * .create( editorElement, {
14
+ * htmlEmbed: ... // HTML embed feature options.
15
+ * } )
16
+ * .then( ... )
17
+ * .catch( ... );
18
+ * ```
19
+ *
20
+ * See {@link module:core/editor/editorconfig~EditorConfig all editor options}.
21
+ */
22
+ export interface HtmlEmbedConfig {
23
+ /**
24
+ * Whether the feature should render previews of the embedded HTML.
25
+ *
26
+ * When set to `true`, the feature will produce a preview of the inserted HTML based on a sanitized
27
+ * version of the HTML provided by the user.
28
+ *
29
+ * The function responsible for sanitizing the HTML needs to be specified in
30
+ * {@link module:html-embed/htmlembedconfig~HtmlEmbedConfig#sanitizeHtml `config.htmlEmbed.sanitizeHtml()`}.
31
+ *
32
+ * Read more about the security aspect of this feature in the {@glink features/html/html-embed#security "Security"} section of
33
+ * the {@glink features/html/html-embed HTML embed} feature guide.
34
+ */
35
+ showPreviews?: boolean;
36
+ /**
37
+ * Callback used to sanitize the HTML provided by the user in HTML embed widget when it is previewed inside the editor.
38
+ *
39
+ * We strongly recommend overwriting the default function to avoid XSS vulnerabilities.
40
+ *
41
+ * Read more about the security aspect of this feature in the {@glink features/html/html-embed#security "Security"} section of
42
+ * the {@glink features/html/html-embed HTML embed} feature guide.
43
+ *
44
+ * The function receives the input HTML (as a string), and should return an object
45
+ * that matches the {@link module:html-embed/htmlembedconfig~HtmlEmbedSanitizeOutput} interface.
46
+ *
47
+ * ```ts
48
+ * ClassicEditor
49
+ * .create( editorElement, {
50
+ * htmlEmbed: {
51
+ * showPreviews: true,
52
+ * sanitizeHtml( inputHtml ) {
53
+ * // Strip unsafe elements and attributes, e.g.:
54
+ * // the `<script>` elements and `on*` attributes.
55
+ * const outputHtml = sanitize( inputHtml );
56
+ *
57
+ * return {
58
+ * html: outputHtml,
59
+ * // true or false depending on whether the sanitizer stripped anything.
60
+ * hasChanged: ...
61
+ * };
62
+ * },
63
+ * }
64
+ * } )
65
+ * .then( ... )
66
+ * .catch( ... );
67
+ * ```
68
+ *
69
+ * **Note:** The function is used only when the feature
70
+ * {@link module:html-embed/htmlembedconfig~HtmlEmbedConfig#showPreviews is configured to render previews}.
71
+ */
72
+ sanitizeHtml?: (html: string) => HtmlEmbedSanitizeOutput;
73
+ }
74
+ /**
75
+ * An object returned by the {@link module:html-embed/htmlembedconfig~HtmlEmbedConfig#sanitizeHtml} function.
76
+ */
77
+ export interface HtmlEmbedSanitizeOutput {
78
+ /**
79
+ * An output (safe) HTML that will be inserted into the {@glink framework/architecture/editing-engine editing view}.
80
+ */
81
+ html: string;
82
+ /**
83
+ * A flag that indicates whether the output HTML is different than the input value.
84
+ */
85
+ hasChanged: boolean;
86
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+ export {};
@@ -0,0 +1,44 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+ /**
6
+ * @module html-embed/htmlembedediting
7
+ */
8
+ import { Plugin, type Editor } from 'ckeditor5/src/core.js';
9
+ import '../theme/htmlembed.css';
10
+ /**
11
+ * The HTML embed editing feature.
12
+ */
13
+ export default class HtmlEmbedEditing extends Plugin {
14
+ /**
15
+ * Keeps references to {@link module:ui/button/buttonview~ButtonView edit, save, and cancel} button instances created for
16
+ * each widget so they can be destroyed if they are no longer in DOM after the editing view was re-rendered.
17
+ */
18
+ private _widgetButtonViewReferences;
19
+ /**
20
+ * @inheritDoc
21
+ */
22
+ static get pluginName(): "HtmlEmbedEditing";
23
+ /**
24
+ * @inheritDoc
25
+ */
26
+ static get isOfficialPlugin(): true;
27
+ /**
28
+ * @inheritDoc
29
+ */
30
+ constructor(editor: Editor);
31
+ /**
32
+ * @inheritDoc
33
+ */
34
+ init(): void;
35
+ /**
36
+ * Prepares converters for the feature.
37
+ */
38
+ private _setupConversion;
39
+ }
40
+ export interface RawHtmlApi {
41
+ makeEditable(): void;
42
+ save(newValue: string): void;
43
+ cancel(): void;
44
+ }
@@ -0,0 +1,333 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+ /**
6
+ * @module html-embed/htmlembedediting
7
+ */
8
+ import { Plugin, icons } from 'ckeditor5/src/core.js';
9
+ import { ButtonView } from 'ckeditor5/src/ui.js';
10
+ import { toWidget } from 'ckeditor5/src/widget.js';
11
+ import { logWarning, createElement } from 'ckeditor5/src/utils.js';
12
+ import HtmlEmbedCommand from './htmlembedcommand.js';
13
+ import '../theme/htmlembed.css';
14
+ /**
15
+ * The HTML embed editing feature.
16
+ */
17
+ export default class HtmlEmbedEditing extends Plugin {
18
+ /**
19
+ * @inheritDoc
20
+ */
21
+ static get pluginName() {
22
+ return 'HtmlEmbedEditing';
23
+ }
24
+ /**
25
+ * @inheritDoc
26
+ */
27
+ static get isOfficialPlugin() {
28
+ return true;
29
+ }
30
+ /**
31
+ * @inheritDoc
32
+ */
33
+ constructor(editor) {
34
+ super(editor);
35
+ /**
36
+ * Keeps references to {@link module:ui/button/buttonview~ButtonView edit, save, and cancel} button instances created for
37
+ * each widget so they can be destroyed if they are no longer in DOM after the editing view was re-rendered.
38
+ */
39
+ this._widgetButtonViewReferences = new Set();
40
+ editor.config.define('htmlEmbed', {
41
+ showPreviews: false,
42
+ sanitizeHtml: rawHtml => {
43
+ /**
44
+ * When using the HTML embed feature with the `config.htmlEmbed.showPreviews` set to `true`, it is strongly recommended to
45
+ * define a sanitize function that will clean up the input HTML in order to avoid XSS vulnerability.
46
+ *
47
+ * For a detailed overview, check the {@glink features/html/html-embed HTML embed feature} documentation.
48
+ *
49
+ * @error html-embed-provide-sanitize-function
50
+ */
51
+ logWarning('html-embed-provide-sanitize-function');
52
+ return {
53
+ html: rawHtml,
54
+ hasChanged: false
55
+ };
56
+ }
57
+ });
58
+ }
59
+ /**
60
+ * @inheritDoc
61
+ */
62
+ init() {
63
+ const editor = this.editor;
64
+ const schema = editor.model.schema;
65
+ schema.register('rawHtml', {
66
+ inheritAllFrom: '$blockObject',
67
+ allowAttributes: ['value']
68
+ });
69
+ editor.commands.add('htmlEmbed', new HtmlEmbedCommand(editor));
70
+ this._setupConversion();
71
+ }
72
+ /**
73
+ * Prepares converters for the feature.
74
+ */
75
+ _setupConversion() {
76
+ const editor = this.editor;
77
+ const t = editor.t;
78
+ const view = editor.editing.view;
79
+ const widgetButtonViewReferences = this._widgetButtonViewReferences;
80
+ const htmlEmbedConfig = editor.config.get('htmlEmbed');
81
+ // Destroy UI buttons created for widgets that have been removed from the view document (e.g. in the previous conversion).
82
+ // This prevents unexpected memory leaks from UI views.
83
+ this.editor.editing.view.on('render', () => {
84
+ for (const buttonView of widgetButtonViewReferences) {
85
+ if (buttonView.element && buttonView.element.isConnected) {
86
+ return;
87
+ }
88
+ buttonView.destroy();
89
+ widgetButtonViewReferences.delete(buttonView);
90
+ }
91
+ }, { priority: 'lowest' });
92
+ // Register div.raw-html-embed as a raw content element so all of it's content will be provided
93
+ // as a view element's custom property while data upcasting.
94
+ editor.data.registerRawContentMatcher({
95
+ name: 'div',
96
+ classes: 'raw-html-embed'
97
+ });
98
+ editor.conversion.for('upcast').elementToElement({
99
+ view: {
100
+ name: 'div',
101
+ classes: 'raw-html-embed'
102
+ },
103
+ model: (viewElement, { writer }) => {
104
+ // The div.raw-html-embed is registered as a raw content element,
105
+ // so all it's content is available in a custom property.
106
+ return writer.createElement('rawHtml', {
107
+ value: viewElement.getCustomProperty('$rawContent')
108
+ });
109
+ }
110
+ });
111
+ editor.conversion.for('dataDowncast').elementToElement({
112
+ model: 'rawHtml',
113
+ view: (modelElement, { writer }) => {
114
+ return writer.createRawElement('div', { class: 'raw-html-embed' }, function (domElement) {
115
+ domElement.innerHTML = modelElement.getAttribute('value') || '';
116
+ });
117
+ }
118
+ });
119
+ editor.conversion.for('editingDowncast').elementToStructure({
120
+ model: { name: 'rawHtml', attributes: ['value'] },
121
+ view: (modelElement, { writer }) => {
122
+ let domContentWrapper;
123
+ let state;
124
+ let props;
125
+ const viewContentWrapper = writer.createRawElement('div', {
126
+ class: 'raw-html-embed__content-wrapper'
127
+ }, function (domElement) {
128
+ domContentWrapper = domElement;
129
+ renderContent({ editor, domElement, state, props });
130
+ // Since there is a `data-cke-ignore-events` attribute set on the wrapper element in the editable mode,
131
+ // the explicit `mousedown` handler on the `capture` phase is needed to move the selection onto the whole
132
+ // HTML embed widget.
133
+ domContentWrapper.addEventListener('mousedown', () => {
134
+ if (state.isEditable) {
135
+ const model = editor.model;
136
+ const selectedElement = model.document.selection.getSelectedElement();
137
+ // Move the selection onto the whole HTML embed widget if it's currently not selected.
138
+ if (selectedElement !== modelElement) {
139
+ model.change(writer => writer.setSelection(modelElement, 'on'));
140
+ }
141
+ }
142
+ }, true);
143
+ });
144
+ // API exposed on each raw HTML embed widget so other features can control a particular widget.
145
+ const rawHtmlApi = {
146
+ makeEditable() {
147
+ state = Object.assign({}, state, {
148
+ isEditable: true
149
+ });
150
+ renderContent({ domElement: domContentWrapper, editor, state, props });
151
+ view.change(writer => {
152
+ writer.setAttribute('data-cke-ignore-events', 'true', viewContentWrapper);
153
+ });
154
+ // This could be potentially pulled to a separate method called focusTextarea().
155
+ domContentWrapper.querySelector('textarea').focus();
156
+ },
157
+ save(newValue) {
158
+ // If the value didn't change, we just cancel. If it changed,
159
+ // it's enough to update the model – the entire widget will be reconverted.
160
+ if (newValue !== state.getRawHtmlValue()) {
161
+ editor.execute('htmlEmbed', newValue);
162
+ editor.editing.view.focus();
163
+ }
164
+ else {
165
+ this.cancel();
166
+ }
167
+ },
168
+ cancel() {
169
+ state = Object.assign({}, state, {
170
+ isEditable: false
171
+ });
172
+ renderContent({ domElement: domContentWrapper, editor, state, props });
173
+ editor.editing.view.focus();
174
+ view.change(writer => {
175
+ writer.removeAttribute('data-cke-ignore-events', viewContentWrapper);
176
+ });
177
+ }
178
+ };
179
+ state = {
180
+ showPreviews: htmlEmbedConfig.showPreviews,
181
+ isEditable: false,
182
+ getRawHtmlValue: () => modelElement.getAttribute('value') || ''
183
+ };
184
+ props = {
185
+ sanitizeHtml: htmlEmbedConfig.sanitizeHtml,
186
+ textareaPlaceholder: t('Paste raw HTML here...'),
187
+ onEditClick() {
188
+ rawHtmlApi.makeEditable();
189
+ },
190
+ onSaveClick(newValue) {
191
+ rawHtmlApi.save(newValue);
192
+ },
193
+ onCancelClick() {
194
+ rawHtmlApi.cancel();
195
+ }
196
+ };
197
+ const viewContainer = writer.createContainerElement('div', {
198
+ class: 'raw-html-embed',
199
+ 'data-html-embed-label': t('HTML snippet'),
200
+ dir: editor.locale.uiLanguageDirection
201
+ }, viewContentWrapper);
202
+ writer.setCustomProperty('rawHtmlApi', rawHtmlApi, viewContainer);
203
+ writer.setCustomProperty('rawHtml', true, viewContainer);
204
+ return toWidget(viewContainer, writer, {
205
+ label: t('HTML snippet'),
206
+ hasSelectionHandle: true
207
+ });
208
+ }
209
+ });
210
+ function renderContent({ editor, domElement, state, props }) {
211
+ // Remove all children;
212
+ domElement.textContent = '';
213
+ const domDocument = domElement.ownerDocument;
214
+ let domTextarea;
215
+ if (state.isEditable) {
216
+ const textareaProps = {
217
+ isDisabled: false,
218
+ placeholder: props.textareaPlaceholder
219
+ };
220
+ domTextarea = createDomTextarea({ domDocument, state, props: textareaProps });
221
+ domElement.append(domTextarea);
222
+ }
223
+ else if (state.showPreviews) {
224
+ const previewContainerProps = {
225
+ sanitizeHtml: props.sanitizeHtml
226
+ };
227
+ domElement.append(createPreviewContainer({ domDocument, state, props: previewContainerProps, editor }));
228
+ }
229
+ else {
230
+ const textareaProps = {
231
+ isDisabled: true,
232
+ placeholder: props.textareaPlaceholder
233
+ };
234
+ domElement.append(createDomTextarea({ domDocument, state, props: textareaProps }));
235
+ }
236
+ const buttonsWrapperProps = {
237
+ onEditClick: props.onEditClick,
238
+ onSaveClick: () => {
239
+ props.onSaveClick(domTextarea.value);
240
+ },
241
+ onCancelClick: props.onCancelClick
242
+ };
243
+ domElement.prepend(createDomButtonsWrapper({ editor, domDocument, state, props: buttonsWrapperProps }));
244
+ }
245
+ function createDomButtonsWrapper({ editor, domDocument, state, props }) {
246
+ const domButtonsWrapper = createElement(domDocument, 'div', {
247
+ class: 'raw-html-embed__buttons-wrapper'
248
+ });
249
+ if (state.isEditable) {
250
+ const saveButtonView = createUIButton(editor, 'save', props.onSaveClick);
251
+ const cancelButtonView = createUIButton(editor, 'cancel', props.onCancelClick);
252
+ domButtonsWrapper.append(saveButtonView.element, cancelButtonView.element);
253
+ widgetButtonViewReferences.add(saveButtonView).add(cancelButtonView);
254
+ }
255
+ else {
256
+ const editButtonView = createUIButton(editor, 'edit', props.onEditClick);
257
+ domButtonsWrapper.append(editButtonView.element);
258
+ widgetButtonViewReferences.add(editButtonView);
259
+ }
260
+ return domButtonsWrapper;
261
+ }
262
+ function createDomTextarea({ domDocument, state, props }) {
263
+ const domTextarea = createElement(domDocument, 'textarea', {
264
+ placeholder: props.placeholder,
265
+ class: 'ck ck-reset ck-input ck-input-text raw-html-embed__source'
266
+ });
267
+ domTextarea.disabled = props.isDisabled;
268
+ domTextarea.value = state.getRawHtmlValue();
269
+ return domTextarea;
270
+ }
271
+ function createPreviewContainer({ editor, domDocument, state, props }) {
272
+ const sanitizedOutput = props.sanitizeHtml(state.getRawHtmlValue());
273
+ const placeholderText = state.getRawHtmlValue().length > 0 ?
274
+ t('No preview available') :
275
+ t('Empty snippet content');
276
+ const domPreviewPlaceholder = createElement(domDocument, 'div', {
277
+ class: 'ck ck-reset_all raw-html-embed__preview-placeholder'
278
+ }, placeholderText);
279
+ const domPreviewContent = createElement(domDocument, 'div', {
280
+ class: 'raw-html-embed__preview-content',
281
+ dir: editor.locale.contentLanguageDirection
282
+ });
283
+ // Creating a contextual document fragment allows executing scripts when inserting into the preview element.
284
+ // See: #8326.
285
+ const domRange = domDocument.createRange();
286
+ const domDocumentFragment = domRange.createContextualFragment(sanitizedOutput.html);
287
+ domPreviewContent.appendChild(domDocumentFragment);
288
+ const domPreviewContainer = createElement(domDocument, 'div', {
289
+ class: 'raw-html-embed__preview'
290
+ }, [
291
+ domPreviewPlaceholder, domPreviewContent
292
+ ]);
293
+ return domPreviewContainer;
294
+ }
295
+ }
296
+ }
297
+ /**
298
+ * Returns a UI button view that can be used in conversion.
299
+ */
300
+ function createUIButton(editor, type, onClick) {
301
+ const { t } = editor.locale;
302
+ const buttonView = new ButtonView(editor.locale);
303
+ const command = editor.commands.get('htmlEmbed');
304
+ buttonView.set({
305
+ class: `raw-html-embed__${type}-button`,
306
+ icon: icons.pencil,
307
+ tooltip: true,
308
+ tooltipPosition: editor.locale.uiLanguageDirection === 'rtl' ? 'e' : 'w'
309
+ });
310
+ buttonView.render();
311
+ if (type === 'edit') {
312
+ buttonView.set({
313
+ icon: icons.pencil,
314
+ label: t('Edit source')
315
+ });
316
+ buttonView.bind('isEnabled').to(command);
317
+ }
318
+ else if (type === 'save') {
319
+ buttonView.set({
320
+ icon: icons.check,
321
+ label: t('Save changes')
322
+ });
323
+ buttonView.bind('isEnabled').to(command);
324
+ }
325
+ else {
326
+ buttonView.set({
327
+ icon: icons.cancel,
328
+ label: t('Cancel')
329
+ });
330
+ }
331
+ buttonView.on('execute', onClick);
332
+ return buttonView;
333
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+ /**
6
+ * @module html-embed/htmlembedui
7
+ */
8
+ import { Plugin } from 'ckeditor5/src/core.js';
9
+ /**
10
+ * The HTML embed UI plugin.
11
+ */
12
+ export default class HtmlEmbedUI extends Plugin {
13
+ /**
14
+ * @inheritDoc
15
+ */
16
+ static get pluginName(): "HtmlEmbedUI";
17
+ /**
18
+ * @inheritDoc
19
+ */
20
+ static get isOfficialPlugin(): true;
21
+ /**
22
+ * @inheritDoc
23
+ */
24
+ init(): void;
25
+ /**
26
+ * Creates a button for html embed command to use either in toolbar or in menu bar.
27
+ */
28
+ private _createButton;
29
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+ /**
6
+ * @module html-embed/htmlembedui
7
+ */
8
+ import { icons, Plugin } from 'ckeditor5/src/core.js';
9
+ import { ButtonView, MenuBarMenuListItemButtonView } from 'ckeditor5/src/ui.js';
10
+ /**
11
+ * The HTML embed UI plugin.
12
+ */
13
+ export default class HtmlEmbedUI extends Plugin {
14
+ /**
15
+ * @inheritDoc
16
+ */
17
+ static get pluginName() {
18
+ return 'HtmlEmbedUI';
19
+ }
20
+ /**
21
+ * @inheritDoc
22
+ */
23
+ static get isOfficialPlugin() {
24
+ return true;
25
+ }
26
+ /**
27
+ * @inheritDoc
28
+ */
29
+ init() {
30
+ const editor = this.editor;
31
+ const locale = editor.locale;
32
+ const t = locale.t;
33
+ // Add the `htmlEmbed` button to feature components.
34
+ editor.ui.componentFactory.add('htmlEmbed', () => {
35
+ const buttonView = this._createButton(ButtonView);
36
+ buttonView.set({
37
+ tooltip: true,
38
+ label: t('Insert HTML')
39
+ });
40
+ return buttonView;
41
+ });
42
+ editor.ui.componentFactory.add('menuBar:htmlEmbed', () => {
43
+ const buttonView = this._createButton(MenuBarMenuListItemButtonView);
44
+ buttonView.set({
45
+ label: t('HTML snippet')
46
+ });
47
+ return buttonView;
48
+ });
49
+ }
50
+ /**
51
+ * Creates a button for html embed command to use either in toolbar or in menu bar.
52
+ */
53
+ _createButton(ButtonClass) {
54
+ const editor = this.editor;
55
+ const command = editor.commands.get('htmlEmbed');
56
+ const view = new ButtonClass(editor.locale);
57
+ view.set({
58
+ icon: icons.html
59
+ });
60
+ view.bind('isEnabled').to(command, 'isEnabled');
61
+ // Execute the command.
62
+ this.listenTo(view, 'execute', () => {
63
+ editor.execute('htmlEmbed');
64
+ editor.editing.view.focus();
65
+ const rawHtmlApi = editor.editing.view.document.selection
66
+ .getSelectedElement()
67
+ .getCustomProperty('rawHtmlApi');
68
+ rawHtmlApi.makeEditable();
69
+ });
70
+ return view;
71
+ }
72
+ }
package/src/index.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+ /**
6
+ * @module html-embed
7
+ */
8
+ export { default as HtmlEmbed } from './htmlembed.js';
9
+ export { default as HtmlEmbedEditing } from './htmlembedediting.js';
10
+ export { default as HtmlEmbedUI } from './htmlembedui.js';
11
+ export type { default as HtmlEmbedCommand } from './htmlembedcommand.js';
12
+ export type { HtmlEmbedConfig } from './htmlembedconfig.js';
13
+ import './augmentation.js';