biso24-editor 1.3.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 (104) hide show
  1. package/CKEditor4/.eslintrc.json +4 -0
  2. package/CKEditor4/CHANGES.md +2182 -0
  3. package/CKEditor4/LICENSE.md +1436 -0
  4. package/CKEditor4/README.md +39 -0
  5. package/CKEditor4/SECURITY.md +10 -0
  6. package/CKEditor4/adapters/jquery.js +158 -0
  7. package/CKEditor4/bender-runner.config.json +16 -0
  8. package/CKEditor4/build-config.js +100 -0
  9. package/CKEditor4/ckeditor.js +30065 -0
  10. package/CKEditor4/config.js +39 -0
  11. package/CKEditor4/contents.css +161 -0
  12. package/CKEditor4/lang/vi.js +475 -0
  13. package/CKEditor4/plugins/a11yhelp/dialogs/a11yhelp.js +143 -0
  14. package/CKEditor4/plugins/a11yhelp/dialogs/lang/vi.js +122 -0
  15. package/CKEditor4/plugins/ajax/plugin.js +241 -0
  16. package/CKEditor4/plugins/autocomplete/plugin.js +1655 -0
  17. package/CKEditor4/plugins/autocomplete/skins/default.css +38 -0
  18. package/CKEditor4/plugins/custompaste/plugin.js +57 -0
  19. package/CKEditor4/plugins/dialog/dialogDefinition.js +4 -0
  20. package/CKEditor4/plugins/dialog/styles/dialog.css +18 -0
  21. package/CKEditor4/plugins/font/lang/vi.js +14 -0
  22. package/CKEditor4/plugins/font/plugin.js +535 -0
  23. package/CKEditor4/plugins/icons.png +0 -0
  24. package/CKEditor4/plugins/icons_hidpi.png +0 -0
  25. package/CKEditor4/plugins/image2/dialogs/image2.js +558 -0
  26. package/CKEditor4/plugins/image2/icons/hidpi/image.png +0 -0
  27. package/CKEditor4/plugins/image2/icons/image.png +0 -0
  28. package/CKEditor4/plugins/image2/lang/vi.js +21 -0
  29. package/CKEditor4/plugins/image2/plugin.js +1708 -0
  30. package/CKEditor4/plugins/justify/icons/hidpi/justifyblock.png +0 -0
  31. package/CKEditor4/plugins/justify/icons/hidpi/justifycenter.png +0 -0
  32. package/CKEditor4/plugins/justify/icons/hidpi/justifyleft.png +0 -0
  33. package/CKEditor4/plugins/justify/icons/hidpi/justifyright.png +0 -0
  34. package/CKEditor4/plugins/justify/icons/justifyblock.png +0 -0
  35. package/CKEditor4/plugins/justify/icons/justifycenter.png +0 -0
  36. package/CKEditor4/plugins/justify/icons/justifyleft.png +0 -0
  37. package/CKEditor4/plugins/justify/icons/justifyright.png +0 -0
  38. package/CKEditor4/plugins/justify/plugin.js +266 -0
  39. package/CKEditor4/plugins/justify_group/icons/hidpi/justifycenter.png +0 -0
  40. package/CKEditor4/plugins/justify_group/icons/justifycenter.png +0 -0
  41. package/CKEditor4/plugins/justify_group/plugin.js +64 -0
  42. package/CKEditor4/plugins/lineheight/LICENSE +22 -0
  43. package/CKEditor4/plugins/lineheight/README.md +2 -0
  44. package/CKEditor4/plugins/lineheight/lang/vi.js +3 -0
  45. package/CKEditor4/plugins/lineheight/plugin.js +99 -0
  46. package/CKEditor4/plugins/lineheight/readme.txt +30 -0
  47. package/CKEditor4/plugins/link/dialogs/anchor.js +82 -0
  48. package/CKEditor4/plugins/link/dialogs/link.js +777 -0
  49. package/CKEditor4/plugins/link/images/anchor.png +0 -0
  50. package/CKEditor4/plugins/link/images/hidpi/anchor.png +0 -0
  51. package/CKEditor4/plugins/magicline/images/hidpi/icon-rtl.png +0 -0
  52. package/CKEditor4/plugins/magicline/images/hidpi/icon.png +0 -0
  53. package/CKEditor4/plugins/magicline/images/icon-rtl.png +0 -0
  54. package/CKEditor4/plugins/magicline/images/icon.png +0 -0
  55. package/CKEditor4/plugins/mentions/plugin.js +471 -0
  56. package/CKEditor4/plugins/menubutton/plugin.js +99 -0
  57. package/CKEditor4/plugins/pastefromword/filter/default.js +849 -0
  58. package/CKEditor4/plugins/pastetools/filter/common.js +445 -0
  59. package/CKEditor4/plugins/pastetools/filter/image.js +163 -0
  60. package/CKEditor4/plugins/pdffile/icons/exporttopdf.png +0 -0
  61. package/CKEditor4/plugins/pdffile/icons/hidpi/exporttopdf.png +0 -0
  62. package/CKEditor4/plugins/pdffile/plugin.js +36 -0
  63. package/CKEditor4/plugins/placeholder_button/plugin.js +31 -0
  64. package/CKEditor4/plugins/sourcedialog/dialogs/sourcedialog.js +88 -0
  65. package/CKEditor4/plugins/sourcedialog/icons/hidpi/sourcedialog-rtl.png +0 -0
  66. package/CKEditor4/plugins/sourcedialog/icons/hidpi/sourcedialog.png +0 -0
  67. package/CKEditor4/plugins/sourcedialog/icons/sourcedialog-rtl.png +0 -0
  68. package/CKEditor4/plugins/sourcedialog/icons/sourcedialog.png +0 -0
  69. package/CKEditor4/plugins/sourcedialog/lang/vi.js +9 -0
  70. package/CKEditor4/plugins/sourcedialog/plugin.js +30 -0
  71. package/CKEditor4/plugins/textmatch/plugin.js +331 -0
  72. package/CKEditor4/plugins/textwatcher/plugin.js +295 -0
  73. package/CKEditor4/plugins/widget/images/handle.png +0 -0
  74. package/CKEditor4/plugins/wordfile/icons/exporttoword.png +0 -0
  75. package/CKEditor4/plugins/wordfile/icons/hidpi/exporttoword.png +0 -0
  76. package/CKEditor4/plugins/wordfile/icons/hidpi/importfromword.png +0 -0
  77. package/CKEditor4/plugins/wordfile/icons/importfromword.png +0 -0
  78. package/CKEditor4/plugins/wordfile/plugin.js +161 -0
  79. package/CKEditor4/skins/moono-lisa/dialog.css +685 -0
  80. package/CKEditor4/skins/moono-lisa/dialog_ie.css +721 -0
  81. package/CKEditor4/skins/moono-lisa/dialog_ie8.css +746 -0
  82. package/CKEditor4/skins/moono-lisa/dialog_iequirks.css +724 -0
  83. package/CKEditor4/skins/moono-lisa/editor.css +1551 -0
  84. package/CKEditor4/skins/moono-lisa/editor_gecko.css +1556 -0
  85. package/CKEditor4/skins/moono-lisa/editor_ie.css +1588 -0
  86. package/CKEditor4/skins/moono-lisa/editor_ie8.css +1639 -0
  87. package/CKEditor4/skins/moono-lisa/editor_iequirks.css +1632 -0
  88. package/CKEditor4/skins/moono-lisa/icons.png +0 -0
  89. package/CKEditor4/skins/moono-lisa/icons_hidpi.png +0 -0
  90. package/CKEditor4/skins/moono-lisa/images/arrow.png +0 -0
  91. package/CKEditor4/skins/moono-lisa/images/close.png +0 -0
  92. package/CKEditor4/skins/moono-lisa/images/hidpi/close.png +0 -0
  93. package/CKEditor4/skins/moono-lisa/images/hidpi/lock-open.png +0 -0
  94. package/CKEditor4/skins/moono-lisa/images/hidpi/lock.png +0 -0
  95. package/CKEditor4/skins/moono-lisa/images/hidpi/refresh.png +0 -0
  96. package/CKEditor4/skins/moono-lisa/images/lock-open.png +0 -0
  97. package/CKEditor4/skins/moono-lisa/images/lock.png +0 -0
  98. package/CKEditor4/skins/moono-lisa/images/refresh.png +0 -0
  99. package/CKEditor4/skins/moono-lisa/images/spinner.gif +0 -0
  100. package/CKEditor4/skins/moono-lisa/readme.md +46 -0
  101. package/CKEditor4/styles.js +136 -0
  102. package/CKEditor4/vendor/promise.js +385 -0
  103. package/README.md +3 -0
  104. package/package.json +9 -0
@@ -0,0 +1,295 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2022, 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
+ 'use strict';
7
+
8
+ ( function() {
9
+
10
+ CKEDITOR.plugins.add( 'textwatcher', {} );
11
+
12
+ /**
13
+ * API exposed by the [Text Watcher](https://ckeditor.com/cke4/addon/textwatcher) plugin.
14
+ *
15
+ * Class implementing the text watcher — a base for features like
16
+ * autocomplete. It fires the {@link #matched} and {@link #unmatched} events
17
+ * based on changes in the text and the position of the caret in the editor.
18
+ *
19
+ * To check whether the text matches some criteria, the text watcher uses
20
+ * a callback function which should return the matching text and a {@link CKEDITOR.dom.range}
21
+ * for that text.
22
+ *
23
+ * Since the text watcher works on the DOM where searching for text
24
+ * is pretty complicated, it is usually recommended to use the {@link CKEDITOR.plugins.textMatch#match}
25
+ * function.
26
+ *
27
+ * Example:
28
+ *
29
+ * ```javascript
30
+ * function textTestCallback( range ) {
31
+ * // You do not want to autocomplete a non-empty selection.
32
+ * if ( !range.collapsed ) {
33
+ * return null;
34
+ * }
35
+ *
36
+ * // Use the text match plugin which does the tricky job of doing
37
+ * // a text search in the DOM. The matchCallback function should return
38
+ * // a matching fragment of the text.
39
+ * return CKEDITOR.plugins.textMatch.match( range, matchCallback );
40
+ * }
41
+ *
42
+ * function matchCallback( text, offset ) {
43
+ * // Get the text before the caret.
44
+ * var left = text.slice( 0, offset ),
45
+ * // Will look for an '@' character followed by word characters.
46
+ * match = left.match( /@\w*$/ );
47
+ *
48
+ * if ( !match ) {
49
+ * return null;
50
+ * }
51
+ * return { start: match.index, end: offset };
52
+ * }
53
+ *
54
+ * // Initialize the text watcher.
55
+ * var textWatcher = new CKEDITOR.plugins.textWatcher( editor, textTestCallback );
56
+ * // Start listening.
57
+ * textWatcher.attach();
58
+ *
59
+ * // Handle text matching.
60
+ * textWatcher.on( 'matched', function( evt ) {
61
+ * autocomplete.setQuery( evt.data.text );
62
+ * } );
63
+ * ```
64
+ *
65
+ * @class CKEDITOR.plugins.textWatcher
66
+ * @since 4.10.0
67
+ * @mixins CKEDITOR.event
68
+ * @constructor Creates the text watcher instance.
69
+ * @param {CKEDITOR.editor} editor The editor instance to watch in.
70
+ * @param {Function} callback Callback executed when the text watcher
71
+ * thinks that something might have changed.
72
+ * @param {Number} [throttle=0] Throttle interval, see {@link #throttle}.
73
+ * @param {CKEDITOR.dom.range} callback.range The range representing the caret position.
74
+ * @param {Object} [callback.return=null] Matching text data (`null` if nothing matches).
75
+ * @param {String} callback.return.text The matching text.
76
+ * @param {CKEDITOR.dom.range} callback.return.range A range in the DOM for the text that matches.
77
+ */
78
+ function TextWatcher( editor, callback, throttle ) {
79
+ /**
80
+ * The editor instance which the text watcher watches.
81
+ *
82
+ * @readonly
83
+ * @property {CKEDITOR.editor}
84
+ */
85
+ this.editor = editor;
86
+
87
+ /**
88
+ * The last matched text.
89
+ *
90
+ * @readonly
91
+ * @property {String}
92
+ */
93
+ this.lastMatched = null;
94
+
95
+ /**
96
+ * Whether the next check should be ignored. See the {@link #consumeNext} method.
97
+ *
98
+ * @readonly
99
+ */
100
+ this.ignoreNext = false;
101
+
102
+ /**
103
+ * The callback passed to the {@link CKEDITOR.plugins.textWatcher} constructor.
104
+ *
105
+ * @readonly
106
+ * @property {Function}
107
+ */
108
+ this.callback = callback;
109
+
110
+ /**
111
+ * Keys that should be ignored by the {@link #check} method.
112
+ *
113
+ * @readonly
114
+ * @property {Number[]}
115
+ */
116
+ this.ignoredKeys = [
117
+ 16, // Shift
118
+ 17, // Ctrl
119
+ 18, // Alt
120
+ 91, // Cmd
121
+ 35, // End
122
+ 36, // Home
123
+ 37, // Left
124
+ 38, // Up
125
+ 39, // Right
126
+ 40, // Down
127
+ 33, // PageUp
128
+ 34 // PageUp
129
+ ];
130
+
131
+ /**
132
+ * Listeners registered by this text watcher.
133
+ *
134
+ * @private
135
+ */
136
+ this._listeners = [];
137
+
138
+ /**
139
+ * Indicates throttle threshold mitigating text checks.
140
+ *
141
+ * Higher levels of the throttle threshold will create a delay for text watcher checks
142
+ * but also improve its performance.
143
+ *
144
+ * See the {@link CKEDITOR.tools#throttle throttle} feature for more information.
145
+ *
146
+ * @readonly
147
+ * @property {Number} [throttle=0]
148
+ */
149
+ this.throttle = throttle || 0;
150
+
151
+ /**
152
+ * The {@link CKEDITOR.tools#throttle throttle buffer} used to mitigate text checks.
153
+ *
154
+ * @private
155
+ */
156
+ this._buffer = CKEDITOR.tools.throttle( this.throttle, testTextMatch, this );
157
+
158
+ /**
159
+ * Event fired when the text is no longer matching.
160
+ *
161
+ * @event matched
162
+ * @param {Object} data The value returned by the {@link #callback}.
163
+ * @param {String} data.text
164
+ * @param {CKEDITOR.dom.range} data.range
165
+ */
166
+
167
+ /**
168
+ * Event fired when the text stops matching.
169
+ *
170
+ * @event unmatched
171
+ */
172
+
173
+ function testTextMatch( selectionRange ) {
174
+ var matched = this.callback( selectionRange );
175
+
176
+ if ( matched ) {
177
+ if ( matched.text == this.lastMatched ) {
178
+ return;
179
+ }
180
+
181
+ this.lastMatched = matched.text;
182
+ this.fire( 'matched', matched );
183
+ } else if ( this.lastMatched ) {
184
+ this.unmatch();
185
+ }
186
+ }
187
+ }
188
+
189
+ TextWatcher.prototype = {
190
+ /**
191
+ * Attaches the text watcher to the {@link #editor}.
192
+ *
193
+ * @chainable
194
+ */
195
+ attach: function() {
196
+ var editor = this.editor;
197
+
198
+ this._listeners.push( editor.on( 'contentDom', onContentDom, this ) );
199
+ this._listeners.push( editor.on( 'blur', unmatch, this ) );
200
+ this._listeners.push( editor.on( 'beforeModeUnload', unmatch, this ) );
201
+ this._listeners.push( editor.on( 'setData', unmatch, this ) );
202
+ this._listeners.push( editor.on( 'afterCommandExec', unmatch, this ) );
203
+
204
+ // Attach if editor is already initialized.
205
+ if ( editor.editable() ) {
206
+ onContentDom.call( this );
207
+ }
208
+
209
+ return this;
210
+
211
+ function onContentDom() {
212
+ var editable = editor.editable();
213
+
214
+ this._listeners.push( editable.attachListener( editable, 'keyup', check, this ) );
215
+ }
216
+
217
+ // CKEditor's event system has a limitation that one function (in this case this.check)
218
+ // cannot be used as listener for the same event more than once. Hence, wrapper function.
219
+ function check( evt ) {
220
+ this.check( evt );
221
+ }
222
+
223
+ function unmatch() {
224
+ this.unmatch();
225
+ }
226
+ },
227
+
228
+ /**
229
+ * Triggers a text check. Fires the {@link #matched} and {@link #unmatched} events.
230
+ * The {@link #matched} event will not be fired twice in a row for the same text
231
+ * unless the text watcher is {@link #unmatch reset}.
232
+ *
233
+ * @param {CKEDITOR.dom.event/CKEDITOR.eventInfo} [evt]
234
+ */
235
+ check: function( evt ) {
236
+ if ( this.ignoreNext ) {
237
+ this.ignoreNext = false;
238
+ return;
239
+ }
240
+
241
+ // Ignore control keys, so they don't trigger the check.
242
+ if ( evt && evt.name == 'keyup' && ( CKEDITOR.tools.array.indexOf( this.ignoredKeys, evt.data.getKey() ) != -1 ) ) {
243
+ return;
244
+ }
245
+
246
+ var sel = this.editor.getSelection();
247
+ if ( !sel ) {
248
+ return;
249
+ }
250
+
251
+ var selectionRange = sel.getRanges()[ 0 ];
252
+ if ( !selectionRange ) {
253
+ return;
254
+ }
255
+
256
+ this._buffer.input( selectionRange );
257
+ },
258
+
259
+ /**
260
+ * Ignores the next {@link #check}.
261
+ *
262
+ * @chainable
263
+ */
264
+ consumeNext: function() {
265
+ this.ignoreNext = true;
266
+ return this;
267
+ },
268
+
269
+ /**
270
+ * Resets the state and fires the {@link #unmatched} event.
271
+ *
272
+ * @chainable
273
+ */
274
+ unmatch: function() {
275
+ this.lastMatched = null;
276
+ this.fire( 'unmatched' );
277
+ return this;
278
+ },
279
+
280
+ /**
281
+ * Destroys the text watcher instance. The DOM event listeners will be cleaned up.
282
+ */
283
+ destroy: function() {
284
+ CKEDITOR.tools.array.forEach( this._listeners, function( obj ) {
285
+ obj.removeListener();
286
+ } );
287
+ this._listeners = [];
288
+ }
289
+ };
290
+
291
+ CKEDITOR.event.implementOn( TextWatcher.prototype );
292
+
293
+ CKEDITOR.plugins.textWatcher = TextWatcher;
294
+
295
+ } )();
@@ -0,0 +1,161 @@
1
+ /**
2
+ * A plugin to enable placeholder tokens to be inserted into the CKEditor message. Use on its own or with teh placeholder plugin.
3
+ * The default format is compatible with the placeholders syntex
4
+ *
5
+ * @version 0.1
6
+ * @Author Troy Lutton
7
+ * @license MIT
8
+ *
9
+ * This is a pure modification for the placeholders plugin. All credit goes to Stuart Sillitoe for creating the original (stuartsillitoe.co.uk)
10
+ *
11
+ */
12
+ const INSERTHTMLFILE_ALLOWED_EXTENSIONS = ['docx', 'txt']; //, 'txt', 'htm', 'html'
13
+
14
+ CKEDITOR.plugins.add('wordfile', {
15
+ icons: 'exporttoword,importfromword',
16
+ hidpi: true,
17
+ init: function (editor) {
18
+ // add the menu to the editor
19
+
20
+ editor.addCommand('exporttoword', {
21
+ exec: function (editor) {
22
+ const preHtml = `
23
+ <html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:w="urn:schemas-microsoft-com:office:word" xmlns="http://www.w3.org/TR/REC-html40">
24
+ <head><meta charset="utf-8"><title></title></head><body>`;
25
+ const postHtml = '</body></html>';
26
+ const html = preHtml + (editor.getData() || '').toString() + postHtml;
27
+
28
+ const blob = new Blob(['\ufeff', html], {
29
+ type: 'application/msword',
30
+ });
31
+
32
+ const link = document.createElement('a');
33
+ link.href = URL.createObjectURL(blob);
34
+ link.download = 'document.doc';
35
+ link.click();
36
+ },
37
+ });
38
+
39
+ // editor.ui.addButton('exporttoword', {
40
+ // label: 'Xuất tệp Word',
41
+ // command: 'exporttoword',
42
+ // className: 'wordfile',
43
+ // toolbar: 'others',
44
+ // });
45
+
46
+ const inputElement = document.createElement('input');
47
+ inputElement.type = 'file';
48
+ inputElement.style.display = 'none';
49
+ inputElement.id = 'importfromword-input';
50
+ inputElement.setAttribute('accept', INSERTHTMLFILE_ALLOWED_EXTENSIONS.map((item) => `.${item}`).toString());
51
+ inputElement.onchange = async (event) => {
52
+ var file = event.target.files[0];
53
+ var fileUrl = URL.createObjectURL(file);
54
+ var extension = file.name.split('.').pop();
55
+ var rawFile = new XMLHttpRequest();
56
+
57
+ if (INSERTHTMLFILE_ALLOWED_EXTENSIONS.indexOf(extension) == -1) {
58
+ var extList = '';
59
+ for (var i in INSERTHTMLFILE_ALLOWED_EXTENSIONS) {
60
+ if (extList != '') {
61
+ extList = extList + ', ';
62
+ }
63
+ extList = extList + INSERTHTMLFILE_ALLOWED_EXTENSIONS[i];
64
+ }
65
+ alert(lang.extensionError + ' ' + extList + '');
66
+ return;
67
+ }
68
+
69
+ if (['docx', 'doc'].includes(extension) && editor.getCommand('docx')) {
70
+ const docx = editor.getCommand('docx');
71
+ const tempContainer = document.createElement('div');
72
+ docx
73
+ .renderAsync(await (await fetch(fileUrl)).arrayBuffer(), tempContainer, null, {
74
+ className: '',
75
+ useBase64URL: true,
76
+ breakPages: true,
77
+ inWrapper: false,
78
+ ignoreWidth: true,
79
+ ignoreHeight: true,
80
+ renderHeaders: false,
81
+ renderFooters: false,
82
+ trimXmlDeclaration: true,
83
+ })
84
+ .then((x) => {
85
+ const spans = tempContainer.querySelectorAll('span');
86
+
87
+ spans.forEach((span) => {
88
+ if (span.querySelector('div') && span.children.length === 1) {
89
+ const div = document.createElement('div');
90
+
91
+ for (let i = 0; i < span.attributes.length; i++) {
92
+ div.setAttribute(span.attributes[i].name, span.attributes[i].value);
93
+ }
94
+
95
+ while (span.firstChild) {
96
+ div.appendChild(span.firstChild);
97
+ }
98
+
99
+ span.parentNode.replaceChild(div, span);
100
+ }
101
+ });
102
+ const pTags = tempContainer.querySelectorAll('p');
103
+
104
+ pTags.forEach((pTag) => {
105
+ if (pTag.querySelector('div') && pTag.children.length === 1) {
106
+ const div = document.createElement('div');
107
+
108
+ for (let i = 0; i < pTag.attributes.length; i++) {
109
+ div.setAttribute(pTag.attributes[i].name, pTag.attributes[i].value);
110
+ }
111
+
112
+ while (pTag.firstChild) {
113
+ div.appendChild(pTag.firstChild);
114
+ }
115
+
116
+ pTag.parentNode.replaceChild(div, pTag);
117
+ }
118
+ });
119
+
120
+ const htmlValue = tempContainer.innerHTML;
121
+ editor.setData(htmlValue.replaceAll('application/octet-stream;', 'image/png;'));
122
+ });
123
+ } else {
124
+ rawFile.open('GET', fileUrl, false);
125
+ rawFile.onreadystatechange = function () {
126
+ if (rawFile.readyState === 4) {
127
+ if (rawFile.status === 200 || (rawFile.status == 0 && rawFile.responseText.length > 0)) {
128
+ editor.insertHtml(rawFile.responseText);
129
+ }
130
+ }
131
+ };
132
+ rawFile.send(null);
133
+ }
134
+ };
135
+
136
+ editor.addCommand('importfromword', {
137
+ exec: function (editor) {
138
+ const element = editor.element.$.querySelector('#importfromword-label');
139
+ if (element) {
140
+ editor.element.$.appendChild(inputElement);
141
+ element.click();
142
+ editor.element.$.removeChild(inputElement);
143
+ }
144
+ },
145
+ });
146
+ editor.ui.addButton('importfromword', {
147
+ label: 'Nhập tệp Word',
148
+ className: 'wordfile',
149
+ toolbar: 'others',
150
+ command: 'importfromword',
151
+ });
152
+ },
153
+
154
+ afterInit: function (editor) {
155
+ const labelElement = document.createElement('label');
156
+ labelElement.setAttribute('for', 'importfromword-input');
157
+ labelElement.id = 'importfromword-label';
158
+
159
+ editor.element.$.appendChild(labelElement);
160
+ },
161
+ });