suneditor 3.0.0-beta.2 → 3.0.0-beta.20
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.
- package/CONTRIBUTING.md +186 -184
- package/LICENSE +21 -21
- package/README.md +157 -180
- package/dist/suneditor.min.css +1 -1
- package/dist/suneditor.min.js +1 -1
- package/package.json +126 -123
- package/src/assets/design/color.css +131 -121
- package/src/assets/design/index.css +3 -3
- package/src/assets/design/size.css +37 -35
- package/src/assets/design/typography.css +37 -37
- package/src/assets/icons/defaultIcons.js +247 -232
- package/src/assets/suneditor-contents.css +779 -778
- package/src/assets/suneditor.css +43 -35
- package/src/core/base/eventHandlers/handler_toolbar.js +135 -135
- package/src/core/base/eventHandlers/handler_ww_clipboard.js +56 -56
- package/src/core/base/eventHandlers/handler_ww_dragDrop.js +115 -113
- package/src/core/base/eventHandlers/handler_ww_key_input.js +1200 -1200
- package/src/core/base/eventHandlers/handler_ww_mouse.js +194 -194
- package/src/core/base/eventManager.js +1550 -1484
- package/src/core/base/history.js +355 -355
- package/src/core/class/char.js +163 -162
- package/src/core/class/component.js +856 -842
- package/src/core/class/format.js +3433 -3422
- package/src/core/class/html.js +1927 -1890
- package/src/core/class/menu.js +357 -346
- package/src/core/class/nodeTransform.js +424 -424
- package/src/core/class/offset.js +858 -891
- package/src/core/class/selection.js +710 -620
- package/src/core/class/shortcuts.js +98 -98
- package/src/core/class/toolbar.js +438 -430
- package/src/core/class/ui.js +424 -422
- package/src/core/class/viewer.js +750 -750
- package/src/core/editor.js +1810 -1708
- package/src/core/section/actives.js +268 -241
- package/src/core/section/constructor.js +1348 -1661
- package/src/core/section/context.js +102 -102
- package/src/core/section/documentType.js +582 -561
- package/src/core/section/options.js +367 -0
- package/src/core/util/instanceCheck.js +59 -0
- package/src/editorInjector/_classes.js +36 -36
- package/src/editorInjector/_core.js +92 -92
- package/src/editorInjector/index.js +75 -75
- package/src/events.js +634 -622
- package/src/helper/clipboard.js +59 -59
- package/src/helper/converter.js +586 -564
- package/src/helper/dom/domCheck.js +304 -304
- package/src/helper/dom/domQuery.js +677 -669
- package/src/helper/dom/domUtils.js +618 -557
- package/src/helper/dom/index.js +12 -12
- package/src/helper/env.js +249 -240
- package/src/helper/index.js +25 -25
- package/src/helper/keyCodeMap.js +183 -183
- package/src/helper/numbers.js +72 -72
- package/src/helper/unicode.js +47 -47
- package/src/langs/ckb.js +231 -231
- package/src/langs/cs.js +231 -231
- package/src/langs/da.js +231 -231
- package/src/langs/de.js +231 -231
- package/src/langs/en.js +230 -230
- package/src/langs/es.js +231 -231
- package/src/langs/fa.js +231 -231
- package/src/langs/fr.js +231 -231
- package/src/langs/he.js +231 -231
- package/src/langs/hu.js +230 -230
- package/src/langs/index.js +28 -28
- package/src/langs/it.js +231 -231
- package/src/langs/ja.js +230 -230
- package/src/langs/km.js +230 -230
- package/src/langs/ko.js +230 -230
- package/src/langs/lv.js +231 -231
- package/src/langs/nl.js +231 -231
- package/src/langs/pl.js +231 -231
- package/src/langs/pt_br.js +231 -231
- package/src/langs/ro.js +231 -231
- package/src/langs/ru.js +231 -231
- package/src/langs/se.js +231 -231
- package/src/langs/tr.js +231 -231
- package/src/langs/uk.js +231 -231
- package/src/langs/ur.js +231 -231
- package/src/langs/zh_cn.js +231 -231
- package/src/modules/ApiManager.js +191 -191
- package/src/modules/Browser.js +669 -667
- package/src/modules/ColorPicker.js +364 -362
- package/src/modules/Controller.js +474 -454
- package/src/modules/Figure.js +1620 -1617
- package/src/modules/FileManager.js +359 -359
- package/src/modules/HueSlider.js +577 -565
- package/src/modules/Modal.js +346 -346
- package/src/modules/ModalAnchorEditor.js +643 -643
- package/src/modules/SelectMenu.js +549 -549
- package/src/modules/_DragHandle.js +17 -17
- package/src/modules/index.js +14 -14
- package/src/plugins/browser/audioGallery.js +83 -83
- package/src/plugins/browser/fileBrowser.js +103 -103
- package/src/plugins/browser/fileGallery.js +83 -83
- package/src/plugins/browser/imageGallery.js +81 -81
- package/src/plugins/browser/videoGallery.js +103 -103
- package/src/plugins/command/blockquote.js +61 -60
- package/src/plugins/command/exportPDF.js +134 -134
- package/src/plugins/command/fileUpload.js +456 -456
- package/src/plugins/command/list_bulleted.js +149 -148
- package/src/plugins/command/list_numbered.js +152 -151
- package/src/plugins/dropdown/align.js +157 -155
- package/src/plugins/dropdown/backgroundColor.js +108 -104
- package/src/plugins/dropdown/font.js +141 -137
- package/src/plugins/dropdown/fontColor.js +109 -105
- package/src/plugins/dropdown/formatBlock.js +170 -178
- package/src/plugins/dropdown/hr.js +152 -152
- package/src/plugins/dropdown/layout.js +83 -83
- package/src/plugins/dropdown/lineHeight.js +131 -130
- package/src/plugins/dropdown/list.js +123 -122
- package/src/plugins/dropdown/paragraphStyle.js +138 -138
- package/src/plugins/dropdown/table.js +4110 -4000
- package/src/plugins/dropdown/template.js +83 -83
- package/src/plugins/dropdown/textStyle.js +149 -149
- package/src/plugins/field/mention.js +242 -242
- package/src/plugins/index.js +120 -120
- package/src/plugins/input/fontSize.js +414 -410
- package/src/plugins/input/pageNavigator.js +71 -70
- package/src/plugins/modal/audio.js +677 -677
- package/src/plugins/modal/drawing.js +537 -531
- package/src/plugins/modal/embed.js +886 -886
- package/src/plugins/modal/image.js +1377 -1376
- package/src/plugins/modal/link.js +248 -240
- package/src/plugins/modal/math.js +563 -563
- package/src/plugins/modal/video.js +1226 -1226
- package/src/plugins/popup/anchor.js +224 -222
- package/src/suneditor.js +114 -107
- package/src/themes/dark.css +132 -122
- package/src/typedef.js +132 -130
- package/types/assets/icons/defaultIcons.d.ts +8 -0
- package/types/core/base/eventManager.d.ts +29 -4
- package/types/core/class/char.d.ts +2 -1
- package/types/core/class/component.d.ts +1 -2
- package/types/core/class/format.d.ts +8 -1
- package/types/core/class/html.d.ts +8 -0
- package/types/core/class/menu.d.ts +8 -0
- package/types/core/class/offset.d.ts +24 -26
- package/types/core/class/selection.d.ts +2 -0
- package/types/core/class/toolbar.d.ts +6 -0
- package/types/core/class/ui.d.ts +1 -1
- package/types/core/editor.d.ts +34 -12
- package/types/core/section/constructor.d.ts +5 -638
- package/types/core/section/documentType.d.ts +12 -2
- package/types/core/section/options.d.ts +740 -0
- package/types/core/util/instanceCheck.d.ts +50 -0
- package/types/editorInjector/_core.d.ts +5 -5
- package/types/editorInjector/index.d.ts +2 -2
- package/types/events.d.ts +2 -0
- package/types/helper/converter.d.ts +9 -0
- package/types/helper/dom/domQuery.d.ts +5 -5
- package/types/helper/dom/domUtils.d.ts +8 -0
- package/types/helper/env.d.ts +6 -1
- package/types/helper/index.d.ts +4 -1
- package/types/index.d.ts +122 -120
- package/types/langs/_Lang.d.ts +194 -194
- package/types/modules/ColorPicker.d.ts +5 -1
- package/types/modules/Controller.d.ts +8 -4
- package/types/modules/Figure.d.ts +2 -1
- package/types/modules/HueSlider.d.ts +4 -1
- package/types/modules/SelectMenu.d.ts +1 -1
- package/types/plugins/command/blockquote.d.ts +1 -0
- package/types/plugins/command/list_bulleted.d.ts +1 -0
- package/types/plugins/command/list_numbered.d.ts +1 -0
- package/types/plugins/dropdown/align.d.ts +1 -0
- package/types/plugins/dropdown/backgroundColor.d.ts +1 -0
- package/types/plugins/dropdown/font.d.ts +1 -0
- package/types/plugins/dropdown/fontColor.d.ts +1 -0
- package/types/plugins/dropdown/formatBlock.d.ts +3 -2
- package/types/plugins/dropdown/lineHeight.d.ts +1 -0
- package/types/plugins/dropdown/list.d.ts +1 -0
- package/types/plugins/dropdown/table.d.ts +6 -0
- package/types/plugins/input/fontSize.d.ts +1 -0
- package/types/plugins/modal/drawing.d.ts +4 -0
- package/types/plugins/modal/link.d.ts +32 -15
- package/types/suneditor.d.ts +13 -9
- package/types/typedef.d.ts +8 -0
|
@@ -1,563 +1,563 @@
|
|
|
1
|
-
import EditorInjector from '../../editorInjector';
|
|
2
|
-
import { Modal, Controller } from '../../modules';
|
|
3
|
-
import { dom, env, converter } from '../../helper';
|
|
4
|
-
|
|
5
|
-
const { _w } = env;
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* @typedef {Object} MathPluginOptions
|
|
9
|
-
* @property {boolean} [canResize=true] - Whether the math modal can be resized.
|
|
10
|
-
* @property {boolean} [autoHeight=false] - Whether to automatically adjust the height of the modal.
|
|
11
|
-
* @property {Array<object>} [fontSizeList] - A list of font size options for rendering math expressions.
|
|
12
|
-
* @property {(...args: *) => *} [onPaste] - A callback function to handle paste events in the math input area.
|
|
13
|
-
* @property {Object} [formSize={}] - An object specifying the dimensions for the math modal.
|
|
14
|
-
* @property {string} [formSize.width="460px"] - The default width of the math modal.
|
|
15
|
-
* @property {string} [formSize.height="14em"] - The default height of the math modal.
|
|
16
|
-
* @property {string} [formSize.maxWidth] - The maximum width of the math modal.
|
|
17
|
-
* @property {string} [formSize.maxHeight] - The maximum height of the math modal.
|
|
18
|
-
* @property {string} [formSize.minWidth="400px"] - The minimum width of the math modal.
|
|
19
|
-
* @property {string} [formSize.minHeight="40px"] - The minimum height of the math modal.
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* @class
|
|
24
|
-
* @description Math plugin.
|
|
25
|
-
* - This plugin provides support for rendering mathematical expressions using either the KaTeX or MathJax libraries.
|
|
26
|
-
* - If external library is provided, a warning is issued.
|
|
27
|
-
*/
|
|
28
|
-
class Math_ extends EditorInjector {
|
|
29
|
-
static key = 'math';
|
|
30
|
-
static type = 'modal';
|
|
31
|
-
static className = '';
|
|
32
|
-
/**
|
|
33
|
-
* @this {Math_}
|
|
34
|
-
* @param {HTMLElement} node - The node to check.
|
|
35
|
-
* @returns {HTMLElement|null} Returns a node if the node is a valid component.
|
|
36
|
-
*/
|
|
37
|
-
static component(node) {
|
|
38
|
-
return dom.utils.hasClass(node, 'se-math|katex') && dom.utils.hasClass(node, 'se-component') ? node : null;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* @constructor
|
|
43
|
-
* @param {__se__EditorCore} editor - The root editor instance
|
|
44
|
-
* @param {MathPluginOptions} pluginOptions
|
|
45
|
-
*/
|
|
46
|
-
constructor(editor, pluginOptions) {
|
|
47
|
-
// plugin basic properties
|
|
48
|
-
super(editor);
|
|
49
|
-
this.title = this.lang.math;
|
|
50
|
-
this.icon = 'math';
|
|
51
|
-
|
|
52
|
-
// external library
|
|
53
|
-
this.katex = null;
|
|
54
|
-
this.mathjax = null;
|
|
55
|
-
|
|
56
|
-
// exception
|
|
57
|
-
if (!(this.katex = this.#CheckKatex(editor.options.get('externalLibs').katex)) && !(this.mathjax = this.#CheckMathJax(editor.options.get('externalLibs').mathjax, editor))) {
|
|
58
|
-
console.warn('[SUNEDITOR.plugins.math.warn] The math plugin must need either "KaTeX" or "MathJax" library. Please add the katex or mathjax option.');
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
this.pluginOptions = {
|
|
62
|
-
formSize: {
|
|
63
|
-
width: '460px',
|
|
64
|
-
height: '14em',
|
|
65
|
-
maxWidth: '',
|
|
66
|
-
maxHeight: '',
|
|
67
|
-
minWidth: '400px',
|
|
68
|
-
minHeight: '40px',
|
|
69
|
-
...pluginOptions.formSize
|
|
70
|
-
},
|
|
71
|
-
canResize: pluginOptions.canResize ?? true,
|
|
72
|
-
autoHeight: !!pluginOptions.autoHeight,
|
|
73
|
-
fontSizeList: pluginOptions.fontSizeList || [
|
|
74
|
-
{
|
|
75
|
-
text: '1',
|
|
76
|
-
value: '1em'
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
text: '1.5',
|
|
80
|
-
value: '1.5em'
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
text: '2',
|
|
84
|
-
value: '2em'
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
text: '2.5',
|
|
88
|
-
value: '2.5em'
|
|
89
|
-
}
|
|
90
|
-
],
|
|
91
|
-
onPaste: typeof pluginOptions.onPaste === 'function' ? pluginOptions.onPaste : null
|
|
92
|
-
};
|
|
93
|
-
if (this.pluginOptions.autoHeight) {
|
|
94
|
-
this.pluginOptions.formSize.height = this.pluginOptions.formSize.minHeight;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// create HTML
|
|
98
|
-
this.defaultFontSize = null;
|
|
99
|
-
const modalEl = CreateHTML_modal(this);
|
|
100
|
-
const controllerEl = CreateHTML_controller(editor);
|
|
101
|
-
|
|
102
|
-
// modules
|
|
103
|
-
this.modal = new Modal(this, modalEl);
|
|
104
|
-
this.controller = new Controller(this, controllerEl, { position: 'bottom', disabled: true });
|
|
105
|
-
|
|
106
|
-
// members
|
|
107
|
-
/** @type {HTMLTextAreaElement} */
|
|
108
|
-
this.textArea = modalEl.querySelector('.se-math-exp');
|
|
109
|
-
/** @type {HTMLPreElement} */
|
|
110
|
-
this.previewElement = modalEl.querySelector('.se-math-preview');
|
|
111
|
-
/** @type {HTMLSelectElement} */
|
|
112
|
-
this.fontSizeElement = modalEl.querySelector('.se-math-size');
|
|
113
|
-
|
|
114
|
-
this.isUpdateState = false;
|
|
115
|
-
this._element = null;
|
|
116
|
-
|
|
117
|
-
// init
|
|
118
|
-
this.previewElement.style.fontSize = this.defaultFontSize;
|
|
119
|
-
this.eventManager.addEvent(this.textArea, 'input', this.#RenderMathExp.bind(this));
|
|
120
|
-
this.eventManager.addEvent(
|
|
121
|
-
this.fontSizeElement,
|
|
122
|
-
'change',
|
|
123
|
-
function (e) {
|
|
124
|
-
this.fontSize = e.target.value;
|
|
125
|
-
}.bind(this.previewElement.style)
|
|
126
|
-
);
|
|
127
|
-
if (this.pluginOptions.onPaste) {
|
|
128
|
-
this.eventManager.addEvent(this.textArea, 'paste', this.pluginOptions.onPaste.bind(this));
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* @editorMethod Editor.component
|
|
134
|
-
* @description Executes the method that is called when a component of a plugin is selected.
|
|
135
|
-
* @param {HTMLElement} target Target component element
|
|
136
|
-
*/
|
|
137
|
-
select(target) {
|
|
138
|
-
if (dom.utils.hasClass(target, 'se-math|katex') && getValue(target)) {
|
|
139
|
-
this._element = target;
|
|
140
|
-
this.controller.open(target, null, { isWWTarget: false, initMethod: null, addOffset: null });
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* @editorMethod Modules.Controller
|
|
147
|
-
* @description This function is called before the "controller" before it is closed.
|
|
148
|
-
*/
|
|
149
|
-
close() {
|
|
150
|
-
this._element = null;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* @editorMethod Editor.core
|
|
155
|
-
* @description This method is used to validate and preserve the format of the component within the editor.
|
|
156
|
-
* - It ensures that the structure and attributes of the element are maintained and secure.
|
|
157
|
-
* - The method checks if the element is already wrapped in a valid container and updates its attributes if necessary.
|
|
158
|
-
* - If the element isn't properly contained, a new container is created to retain the format.
|
|
159
|
-
* @returns {{query: string, method: (element: HTMLElement) => void}} The format retention object containing the query and method to process the element.
|
|
160
|
-
* - query: The selector query to identify the relevant elements (in this case, 'audio').
|
|
161
|
-
* - method:The function to execute on the element to validate and preserve its format.
|
|
162
|
-
* - The function takes the element as an argument, checks if it is contained correctly, and applies necessary adjustments.
|
|
163
|
-
*/
|
|
164
|
-
retainFormat() {
|
|
165
|
-
return {
|
|
166
|
-
query: '.se-math, .katex, .MathJax',
|
|
167
|
-
method: (element) => {
|
|
168
|
-
if (!this.katex && !this.mathjax) return;
|
|
169
|
-
|
|
170
|
-
const value = getValue(element);
|
|
171
|
-
if (!value) return;
|
|
172
|
-
|
|
173
|
-
const domParser = this._d.createRange().createContextualFragment(this._renderer(converter.entityToHTML(this._escapeBackslashes(value, true))));
|
|
174
|
-
element.innerHTML = domParser.querySelector('.se-math, .katex').innerHTML;
|
|
175
|
-
element.setAttribute('contenteditable', 'false');
|
|
176
|
-
dom.utils.addClass(element, 'se-component|se-inline-component|se-disable-pointer|se-math');
|
|
177
|
-
|
|
178
|
-
if (this.katex) {
|
|
179
|
-
dom.utils.addClass(element, 'katex');
|
|
180
|
-
} else {
|
|
181
|
-
dom.utils.removeClass(element, 'katex');
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (this.mathjax) {
|
|
185
|
-
this.#renderMathJax(this.mathjax);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* @editorMethod Modules.Modal
|
|
193
|
-
* @description Executes the method that is called when a "Modal" module's is opened.
|
|
194
|
-
*/
|
|
195
|
-
open() {
|
|
196
|
-
this.modal.open();
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* @editorMethod Modules.Modal
|
|
201
|
-
* @description Executes the method that is called when a plugin's modal is opened.
|
|
202
|
-
* @param {boolean} isUpdate "Indicates whether the modal is for editing an existing component (true) or registering a new one (false)."
|
|
203
|
-
*/
|
|
204
|
-
on(isUpdate) {
|
|
205
|
-
this.isUpdateState = isUpdate;
|
|
206
|
-
if (!isUpdate) {
|
|
207
|
-
this.init();
|
|
208
|
-
} else if (this.controller.currentTarget) {
|
|
209
|
-
const currentTarget = this.controller.currentTarget;
|
|
210
|
-
const exp = converter.entityToHTML(this._escapeBackslashes(getValue(currentTarget), true));
|
|
211
|
-
const fontSize = getType(currentTarget) || '1em';
|
|
212
|
-
this.textArea.value = exp;
|
|
213
|
-
this.fontSizeElement.value = fontSize;
|
|
214
|
-
this.previewElement.innerHTML = this._renderer(exp);
|
|
215
|
-
this.previewElement.style.fontSize = fontSize;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* @editorMethod Modules.Modal
|
|
221
|
-
* @description This function is called when a form within a modal window is "submit".
|
|
222
|
-
* @returns {boolean} Success or failure
|
|
223
|
-
*/
|
|
224
|
-
modalAction() {
|
|
225
|
-
if (this.textArea.value.trim().length === 0 || dom.utils.hasClass(this.textArea, 'se-error')) {
|
|
226
|
-
this.textArea.focus();
|
|
227
|
-
return false;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
const mathExp = this.textArea.value;
|
|
231
|
-
|
|
232
|
-
/** @type {HTMLSpanElement} */
|
|
233
|
-
const mathEl = this.previewElement.querySelector('.se-math, .katex');
|
|
234
|
-
|
|
235
|
-
if (!mathEl) return false;
|
|
236
|
-
dom.utils.addClass(mathEl, 'se-component|se-inline-component|se-disable-pointer|se-math');
|
|
237
|
-
mathEl.setAttribute('contenteditable', 'false');
|
|
238
|
-
mathEl.setAttribute('data-se-value', converter.htmlToEntity(this._escapeBackslashes(mathExp, false)));
|
|
239
|
-
mathEl.setAttribute('data-se-type', this.fontSizeElement.value);
|
|
240
|
-
mathEl.style.fontSize = this.fontSizeElement.value;
|
|
241
|
-
|
|
242
|
-
if (this.katex) {
|
|
243
|
-
dom.utils.addClass(mathEl, 'katex');
|
|
244
|
-
dom.utils.removeClass(mathEl, 'MathJax');
|
|
245
|
-
} else {
|
|
246
|
-
dom.utils.removeClass(mathEl, 'katex');
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
if (!this.isUpdateState) {
|
|
250
|
-
const selectedFormats = this.format.getLines();
|
|
251
|
-
|
|
252
|
-
if (selectedFormats.length > 1) {
|
|
253
|
-
const oFormat = dom.utils.createElement(selectedFormats[0].nodeName, null, mathEl);
|
|
254
|
-
this.component.insert(oFormat, { skipCharCount: false, skipSelection: true, skipHistory: false });
|
|
255
|
-
} else {
|
|
256
|
-
this.component.insert(mathEl, { skipCharCount: false, skipSelection: true, skipHistory: false });
|
|
257
|
-
}
|
|
258
|
-
} else {
|
|
259
|
-
const containerEl = dom.query.getParentElement(this.controller.currentTarget, '.se-component');
|
|
260
|
-
containerEl.replaceWith(mathEl);
|
|
261
|
-
const compInfo = this.component.get(mathEl);
|
|
262
|
-
this.component.select(compInfo.target, compInfo.pluginName);
|
|
263
|
-
return true;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
if (this.mathjax) {
|
|
267
|
-
this.#renderMathJax(this.mathjax);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
const r = this.selection.getNearRange(mathEl);
|
|
271
|
-
if (r) {
|
|
272
|
-
this.selection.setRange(r.container, r.offset, r.container, r.offset);
|
|
273
|
-
} else {
|
|
274
|
-
this.component.select(mathEl, Math_.key);
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
return true;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* @editorMethod Modules.Modal
|
|
282
|
-
* @description This function is called before the modal window is opened, but before it is closed.
|
|
283
|
-
*/
|
|
284
|
-
init() {
|
|
285
|
-
this.textArea.value = '';
|
|
286
|
-
this.previewElement.innerHTML = '';
|
|
287
|
-
dom.utils.removeClass(this.textArea, 'se-error');
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* @editorMethod Modules.Controller
|
|
292
|
-
* @description Executes the method that is called when a button is clicked in the "controller".
|
|
293
|
-
* @param {HTMLButtonElement} target Target button element
|
|
294
|
-
*/
|
|
295
|
-
controllerAction(target) {
|
|
296
|
-
const command = target.getAttribute('data-command');
|
|
297
|
-
switch (command) {
|
|
298
|
-
case 'update':
|
|
299
|
-
this.modal.open();
|
|
300
|
-
break;
|
|
301
|
-
case 'copy':
|
|
302
|
-
this.#copyTextToClipboard(this._element);
|
|
303
|
-
break;
|
|
304
|
-
case 'delete':
|
|
305
|
-
this.destroy(this.controller.currentTarget);
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
/**
|
|
310
|
-
* @editorMethod Editor.Component
|
|
311
|
-
* @description Method to delete a component of a plugin, called by the "FileManager", "Controller" module.
|
|
312
|
-
* @param {Node} target Target element
|
|
313
|
-
*/
|
|
314
|
-
destroy(target) {
|
|
315
|
-
dom.utils.removeItem(target);
|
|
316
|
-
this.controller.close();
|
|
317
|
-
this.editor.focus();
|
|
318
|
-
this.history.push(false);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
/**
|
|
322
|
-
* @private
|
|
323
|
-
* @description Renders the given math expression using KaTeX or MathJax.
|
|
324
|
-
* @param {string} exp - The math expression to render.
|
|
325
|
-
* @returns {string} - The rendered math expression as HTML.
|
|
326
|
-
*/
|
|
327
|
-
_renderer(exp) {
|
|
328
|
-
let result = '';
|
|
329
|
-
try {
|
|
330
|
-
dom.utils.removeClass(this.textArea, 'se-error');
|
|
331
|
-
if (this.katex) {
|
|
332
|
-
result = this.katex.src.renderToString(exp, { throwOnError: true, displayMode: true });
|
|
333
|
-
} else if (this.mathjax) {
|
|
334
|
-
result = this.mathjax.convert(exp).outerHTML;
|
|
335
|
-
if (/<mjx-merror/.test(result)) {
|
|
336
|
-
dom.utils.addClass(this.textArea, 'se-error');
|
|
337
|
-
result = `<span class="se-math-error">${result}</span>`;
|
|
338
|
-
} else {
|
|
339
|
-
result = `<span class="se-math">${result}</span>`;
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
} catch (error) {
|
|
343
|
-
dom.utils.addClass(this.textArea, 'se-error');
|
|
344
|
-
result = `<span class="se-math-error">Math syntax error. (Refer ${this.katex ? `<a href="${env.KATEX_WEBSITE}" target="_blank">KaTeX</a>` : `<a href="${env.MATHJAX_WEBSITE}" target="_blank">MathJax</a>`})</span>`;
|
|
345
|
-
console.warn('[SUNEDITOR.math.error] ', error.message);
|
|
346
|
-
}
|
|
347
|
-
return result;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
/**
|
|
351
|
-
* @private
|
|
352
|
-
* @description Escapes or unescapes backslashes in a given string.
|
|
353
|
-
* @param {string} str - The input string.
|
|
354
|
-
* @param {boolean} decode - If true, decodes escaped backslashes; otherwise, encodes them.
|
|
355
|
-
* @returns {string} - The processed string.
|
|
356
|
-
*/
|
|
357
|
-
_escapeBackslashes(str, decode) {
|
|
358
|
-
return str.replace(/\\{2}/g, decode ? '\\' : '\\\\');
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
/**
|
|
362
|
-
* @description Copies the math expression text to clipboard.
|
|
363
|
-
* @param {Node} element - The math expression element.
|
|
364
|
-
* @returns {Promise<void>}
|
|
365
|
-
*/
|
|
366
|
-
async #copyTextToClipboard(element) {
|
|
367
|
-
if (!navigator.clipboard || !element) return;
|
|
368
|
-
|
|
369
|
-
try {
|
|
370
|
-
const text = getValue(element);
|
|
371
|
-
await this.html.copy(text);
|
|
372
|
-
dom.utils.addClass(element, 'se-copy');
|
|
373
|
-
// copy effect
|
|
374
|
-
_w.setTimeout(() => {
|
|
375
|
-
dom.utils.removeClass(element, 'se-copy');
|
|
376
|
-
}, 120);
|
|
377
|
-
} catch (err) {
|
|
378
|
-
console.error('[SUNEDITOR.math.copy.fail]', err);
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
/**
|
|
383
|
-
* @description Handles rendering of math expressions in the preview.
|
|
384
|
-
* @param {InputEvent} e - The input event.
|
|
385
|
-
*/
|
|
386
|
-
#RenderMathExp(e) {
|
|
387
|
-
/** @type {HTMLInputElement} */
|
|
388
|
-
const eventTarget = dom.query.getEventTarget(e);
|
|
389
|
-
if (this.pluginOptions.autoHeight) {
|
|
390
|
-
eventTarget.style.height = '5px';
|
|
391
|
-
eventTarget.style.height = eventTarget.scrollHeight + 5 + 'px';
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
this.previewElement.innerHTML = this._renderer(eventTarget.value);
|
|
395
|
-
if (this.mathjax) this.#renderMathJax(this.mathjax);
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
/**
|
|
399
|
-
* @param {*} mathjax - The MathJax instance.
|
|
400
|
-
*/
|
|
401
|
-
#renderMathJax(mathjax) {
|
|
402
|
-
mathjax.clear();
|
|
403
|
-
mathjax.updateDocument();
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
/**
|
|
407
|
-
* @param {*} katex - The KaTeX instance.
|
|
408
|
-
* @returns {*} - The KaTeX instance or null if the instance is invalid.
|
|
409
|
-
*/
|
|
410
|
-
#CheckKatex(katex) {
|
|
411
|
-
if (!katex) return null;
|
|
412
|
-
if (!katex.src) {
|
|
413
|
-
console.warn('[SUNEDITOR.math.katex.fail] The katex option is set incorrectly.');
|
|
414
|
-
return null;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
const katexOptions = [
|
|
418
|
-
{
|
|
419
|
-
throwOnError: false
|
|
420
|
-
},
|
|
421
|
-
katex.options || {}
|
|
422
|
-
].reduce((init, option) => {
|
|
423
|
-
for (const key in option) {
|
|
424
|
-
init[key] = option[key];
|
|
425
|
-
}
|
|
426
|
-
return init;
|
|
427
|
-
}, {});
|
|
428
|
-
|
|
429
|
-
katex.options = katexOptions;
|
|
430
|
-
return katex;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
/**
|
|
434
|
-
* @param {*} mathjax - The MathJax instance.
|
|
435
|
-
* @param {__se__EditorCore} editor - The root editor instance.
|
|
436
|
-
* @returns {*}
|
|
437
|
-
*/
|
|
438
|
-
#CheckMathJax(mathjax, editor) {
|
|
439
|
-
if (!mathjax) return null;
|
|
440
|
-
if (editor.frameOptions.get('iframe')) {
|
|
441
|
-
console.warn('[SUNEDITOR.math.mathjax.fail] The MathJax option is not supported in the iframe.');
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
try {
|
|
445
|
-
const adaptor = mathjax.browserAdaptor();
|
|
446
|
-
mathjax.RegisterHTMLHandler(adaptor);
|
|
447
|
-
|
|
448
|
-
const tex = new mathjax.TeX();
|
|
449
|
-
const chtml = new mathjax.CHTML();
|
|
450
|
-
|
|
451
|
-
return mathjax.src.document(document, {
|
|
452
|
-
InputJax: tex,
|
|
453
|
-
OutputJax: chtml
|
|
454
|
-
});
|
|
455
|
-
} catch (error) {
|
|
456
|
-
console.warn('[SUNEDITOR.math.mathjax.fail] The MathJax option is set incorrectly.', error);
|
|
457
|
-
return null;
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
function CreateHTML_modal(inst) {
|
|
463
|
-
const { lang, icons, pluginOptions, katex } = inst;
|
|
464
|
-
const { formSize, fontSizeList, canResize, autoHeight } = pluginOptions;
|
|
465
|
-
const { width, height, maxWidth, maxHeight, minWidth, minHeight } = formSize;
|
|
466
|
-
const resizeType = !canResize ? 'none' : autoHeight ? 'horizontal' : 'auto';
|
|
467
|
-
|
|
468
|
-
let defaultFontSize = fontSizeList[0].value;
|
|
469
|
-
let html = /*html*/ `
|
|
470
|
-
<form>
|
|
471
|
-
<div class="se-modal-header">
|
|
472
|
-
<button type="button" data-command="close" class="se-btn se-close-btn" title="${lang.close}" aria-label="${lang.close}">
|
|
473
|
-
${icons.cancel}
|
|
474
|
-
</button>
|
|
475
|
-
<span class="se-modal-title">${lang.math_modal_title}</span>
|
|
476
|
-
</div>
|
|
477
|
-
<div class="se-modal-body">
|
|
478
|
-
<div class="se-modal-form">
|
|
479
|
-
<label>${lang.math_modal_inputLabel} ${katex ? `(<a href="${env.KATEX_WEBSITE}" target="_blank">KaTeX</a>)` : `(<a href="${env.MATHJAX_WEBSITE}" target="_blank">MathJax</a>)`}</label>
|
|
480
|
-
<textarea class="se-input-form se-math-exp se-modal-resize-form" type="text" data-focus style="width: ${width}; height: ${height}; min-width: ${minWidth}; min-height: ${minHeight}; resize: ${resizeType};"></textarea>
|
|
481
|
-
</div>
|
|
482
|
-
<div class="se-modal-form">
|
|
483
|
-
<label>${lang.math_modal_fontSizeLabel}</label>
|
|
484
|
-
<select class="se-input-select se-math-size">`;
|
|
485
|
-
|
|
486
|
-
for (let i = 0, len = fontSizeList.length, f; i < len; i++) {
|
|
487
|
-
f = fontSizeList[i];
|
|
488
|
-
if (f.default) defaultFontSize = f.value;
|
|
489
|
-
html += /*html*/ `<option value="${f.value}"${f.default ? ' selected' : ''}>${f.text}</option>`;
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
html += /*html*/ `</select>
|
|
493
|
-
</div>
|
|
494
|
-
<div class="se-modal-form">
|
|
495
|
-
<label>${lang.math_modal_previewLabel}</label>
|
|
496
|
-
<p class="se-math-preview"></p>
|
|
497
|
-
</div>
|
|
498
|
-
</div>
|
|
499
|
-
<div class="se-modal-footer">
|
|
500
|
-
<button type="submit" class="se-btn-primary" title="${lang.submitButton}" aria-label="${lang.submitButton}">
|
|
501
|
-
<span>${lang.submitButton}</span>
|
|
502
|
-
</button>
|
|
503
|
-
</div>
|
|
504
|
-
</form>`;
|
|
505
|
-
|
|
506
|
-
inst.defaultFontSize = defaultFontSize;
|
|
507
|
-
return dom.utils.createElement('DIV', { class: 'se-modal-content se-modal-responsive', style: `max-width: ${maxWidth}; max-height: ${maxHeight};` }, html);
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
function CreateHTML_controller({ lang, icons }) {
|
|
511
|
-
const html = /*html*/ `
|
|
512
|
-
<div class="se-arrow se-arrow-up"></div>
|
|
513
|
-
<div class="link-content">
|
|
514
|
-
<div class="se-btn-group">
|
|
515
|
-
<button type="button" data-command="update" tabindex="-1" class="se-btn se-tooltip">
|
|
516
|
-
${icons.edit}
|
|
517
|
-
<span class="se-tooltip-inner">
|
|
518
|
-
<span class="se-tooltip-text">${lang.edit}</span>
|
|
519
|
-
</span>
|
|
520
|
-
</button>
|
|
521
|
-
<button type="button" data-command="copy" tabindex="-1" class="se-btn se-tooltip">
|
|
522
|
-
${icons.copy}
|
|
523
|
-
<span class="se-tooltip-inner">
|
|
524
|
-
<span class="se-tooltip-text">${lang.copy}</span>
|
|
525
|
-
</span>
|
|
526
|
-
</button>
|
|
527
|
-
<button type="button" data-command="delete" tabindex="-1" class="se-btn se-tooltip">
|
|
528
|
-
${icons.delete}
|
|
529
|
-
<span class="se-tooltip-inner">
|
|
530
|
-
<span class="se-tooltip-text">${lang.remove}</span>
|
|
531
|
-
</span>
|
|
532
|
-
</button>
|
|
533
|
-
</div>
|
|
534
|
-
</div>`;
|
|
535
|
-
|
|
536
|
-
return dom.utils.createElement('DIV', { class: 'se-controller se-controller-link' }, html);
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
function getValue(element) {
|
|
540
|
-
const seAttr = element.getAttribute('data-se-value');
|
|
541
|
-
if (seAttr) return seAttr;
|
|
542
|
-
|
|
543
|
-
// v2-migration
|
|
544
|
-
const v2SeAttr = element.getAttribute(`data-exp`);
|
|
545
|
-
if (!v2SeAttr) return null;
|
|
546
|
-
element.removeAttribute(`data-exp`);
|
|
547
|
-
element.setAttribute(`data-se-value`, v2SeAttr);
|
|
548
|
-
return v2SeAttr;
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
function getType(element) {
|
|
552
|
-
const seAttr = element.getAttribute('data-se-type');
|
|
553
|
-
if (seAttr) return seAttr;
|
|
554
|
-
|
|
555
|
-
// v2-migration
|
|
556
|
-
const v2SeAttr = element.getAttribute(`data-exp`);
|
|
557
|
-
if (!v2SeAttr) return null;
|
|
558
|
-
element.removeAttribute(`data-font-size`);
|
|
559
|
-
element.setAttribute(`data-se-type`, v2SeAttr);
|
|
560
|
-
return v2SeAttr;
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
export default Math_;
|
|
1
|
+
import EditorInjector from '../../editorInjector';
|
|
2
|
+
import { Modal, Controller } from '../../modules';
|
|
3
|
+
import { dom, env, converter } from '../../helper';
|
|
4
|
+
|
|
5
|
+
const { _w } = env;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {Object} MathPluginOptions
|
|
9
|
+
* @property {boolean} [canResize=true] - Whether the math modal can be resized.
|
|
10
|
+
* @property {boolean} [autoHeight=false] - Whether to automatically adjust the height of the modal.
|
|
11
|
+
* @property {Array<object>} [fontSizeList] - A list of font size options for rendering math expressions.
|
|
12
|
+
* @property {(...args: *) => *} [onPaste] - A callback function to handle paste events in the math input area.
|
|
13
|
+
* @property {Object} [formSize={}] - An object specifying the dimensions for the math modal.
|
|
14
|
+
* @property {string} [formSize.width="460px"] - The default width of the math modal.
|
|
15
|
+
* @property {string} [formSize.height="14em"] - The default height of the math modal.
|
|
16
|
+
* @property {string} [formSize.maxWidth] - The maximum width of the math modal.
|
|
17
|
+
* @property {string} [formSize.maxHeight] - The maximum height of the math modal.
|
|
18
|
+
* @property {string} [formSize.minWidth="400px"] - The minimum width of the math modal.
|
|
19
|
+
* @property {string} [formSize.minHeight="40px"] - The minimum height of the math modal.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @class
|
|
24
|
+
* @description Math plugin.
|
|
25
|
+
* - This plugin provides support for rendering mathematical expressions using either the KaTeX or MathJax libraries.
|
|
26
|
+
* - If external library is provided, a warning is issued.
|
|
27
|
+
*/
|
|
28
|
+
class Math_ extends EditorInjector {
|
|
29
|
+
static key = 'math';
|
|
30
|
+
static type = 'modal';
|
|
31
|
+
static className = '';
|
|
32
|
+
/**
|
|
33
|
+
* @this {Math_}
|
|
34
|
+
* @param {HTMLElement} node - The node to check.
|
|
35
|
+
* @returns {HTMLElement|null} Returns a node if the node is a valid component.
|
|
36
|
+
*/
|
|
37
|
+
static component(node) {
|
|
38
|
+
return dom.utils.hasClass(node, 'se-math|katex') && dom.utils.hasClass(node, 'se-component') ? node : null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @constructor
|
|
43
|
+
* @param {__se__EditorCore} editor - The root editor instance
|
|
44
|
+
* @param {MathPluginOptions} pluginOptions
|
|
45
|
+
*/
|
|
46
|
+
constructor(editor, pluginOptions) {
|
|
47
|
+
// plugin basic properties
|
|
48
|
+
super(editor);
|
|
49
|
+
this.title = this.lang.math;
|
|
50
|
+
this.icon = 'math';
|
|
51
|
+
|
|
52
|
+
// external library
|
|
53
|
+
this.katex = null;
|
|
54
|
+
this.mathjax = null;
|
|
55
|
+
|
|
56
|
+
// exception
|
|
57
|
+
if (!(this.katex = this.#CheckKatex(editor.options.get('externalLibs').katex)) && !(this.mathjax = this.#CheckMathJax(editor.options.get('externalLibs').mathjax, editor))) {
|
|
58
|
+
console.warn('[SUNEDITOR.plugins.math.warn] The math plugin must need either "KaTeX" or "MathJax" library. Please add the katex or mathjax option.');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
this.pluginOptions = {
|
|
62
|
+
formSize: {
|
|
63
|
+
width: '460px',
|
|
64
|
+
height: '14em',
|
|
65
|
+
maxWidth: '',
|
|
66
|
+
maxHeight: '',
|
|
67
|
+
minWidth: '400px',
|
|
68
|
+
minHeight: '40px',
|
|
69
|
+
...pluginOptions.formSize
|
|
70
|
+
},
|
|
71
|
+
canResize: pluginOptions.canResize ?? true,
|
|
72
|
+
autoHeight: !!pluginOptions.autoHeight,
|
|
73
|
+
fontSizeList: pluginOptions.fontSizeList || [
|
|
74
|
+
{
|
|
75
|
+
text: '1',
|
|
76
|
+
value: '1em'
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
text: '1.5',
|
|
80
|
+
value: '1.5em'
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
text: '2',
|
|
84
|
+
value: '2em'
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
text: '2.5',
|
|
88
|
+
value: '2.5em'
|
|
89
|
+
}
|
|
90
|
+
],
|
|
91
|
+
onPaste: typeof pluginOptions.onPaste === 'function' ? pluginOptions.onPaste : null
|
|
92
|
+
};
|
|
93
|
+
if (this.pluginOptions.autoHeight) {
|
|
94
|
+
this.pluginOptions.formSize.height = this.pluginOptions.formSize.minHeight;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// create HTML
|
|
98
|
+
this.defaultFontSize = null;
|
|
99
|
+
const modalEl = CreateHTML_modal(this);
|
|
100
|
+
const controllerEl = CreateHTML_controller(editor);
|
|
101
|
+
|
|
102
|
+
// modules
|
|
103
|
+
this.modal = new Modal(this, modalEl);
|
|
104
|
+
this.controller = new Controller(this, controllerEl, { position: 'bottom', disabled: true });
|
|
105
|
+
|
|
106
|
+
// members
|
|
107
|
+
/** @type {HTMLTextAreaElement} */
|
|
108
|
+
this.textArea = modalEl.querySelector('.se-math-exp');
|
|
109
|
+
/** @type {HTMLPreElement} */
|
|
110
|
+
this.previewElement = modalEl.querySelector('.se-math-preview');
|
|
111
|
+
/** @type {HTMLSelectElement} */
|
|
112
|
+
this.fontSizeElement = modalEl.querySelector('.se-math-size');
|
|
113
|
+
|
|
114
|
+
this.isUpdateState = false;
|
|
115
|
+
this._element = null;
|
|
116
|
+
|
|
117
|
+
// init
|
|
118
|
+
this.previewElement.style.fontSize = this.defaultFontSize;
|
|
119
|
+
this.eventManager.addEvent(this.textArea, 'input', this.#RenderMathExp.bind(this));
|
|
120
|
+
this.eventManager.addEvent(
|
|
121
|
+
this.fontSizeElement,
|
|
122
|
+
'change',
|
|
123
|
+
function (e) {
|
|
124
|
+
this.fontSize = e.target.value;
|
|
125
|
+
}.bind(this.previewElement.style)
|
|
126
|
+
);
|
|
127
|
+
if (this.pluginOptions.onPaste) {
|
|
128
|
+
this.eventManager.addEvent(this.textArea, 'paste', this.pluginOptions.onPaste.bind(this));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* @editorMethod Editor.component
|
|
134
|
+
* @description Executes the method that is called when a component of a plugin is selected.
|
|
135
|
+
* @param {HTMLElement} target Target component element
|
|
136
|
+
*/
|
|
137
|
+
select(target) {
|
|
138
|
+
if (dom.utils.hasClass(target, 'se-math|katex') && getValue(target)) {
|
|
139
|
+
this._element = target;
|
|
140
|
+
this.controller.open(target, null, { isWWTarget: false, initMethod: null, addOffset: null });
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* @editorMethod Modules.Controller
|
|
147
|
+
* @description This function is called before the "controller" before it is closed.
|
|
148
|
+
*/
|
|
149
|
+
close() {
|
|
150
|
+
this._element = null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* @editorMethod Editor.core
|
|
155
|
+
* @description This method is used to validate and preserve the format of the component within the editor.
|
|
156
|
+
* - It ensures that the structure and attributes of the element are maintained and secure.
|
|
157
|
+
* - The method checks if the element is already wrapped in a valid container and updates its attributes if necessary.
|
|
158
|
+
* - If the element isn't properly contained, a new container is created to retain the format.
|
|
159
|
+
* @returns {{query: string, method: (element: HTMLElement) => void}} The format retention object containing the query and method to process the element.
|
|
160
|
+
* - query: The selector query to identify the relevant elements (in this case, 'audio').
|
|
161
|
+
* - method:The function to execute on the element to validate and preserve its format.
|
|
162
|
+
* - The function takes the element as an argument, checks if it is contained correctly, and applies necessary adjustments.
|
|
163
|
+
*/
|
|
164
|
+
retainFormat() {
|
|
165
|
+
return {
|
|
166
|
+
query: '.se-math, .katex, .MathJax',
|
|
167
|
+
method: (element) => {
|
|
168
|
+
if (!this.katex && !this.mathjax) return;
|
|
169
|
+
|
|
170
|
+
const value = getValue(element);
|
|
171
|
+
if (!value) return;
|
|
172
|
+
|
|
173
|
+
const domParser = this._d.createRange().createContextualFragment(this._renderer(converter.entityToHTML(this._escapeBackslashes(value, true))));
|
|
174
|
+
element.innerHTML = domParser.querySelector('.se-math, .katex').innerHTML;
|
|
175
|
+
element.setAttribute('contenteditable', 'false');
|
|
176
|
+
dom.utils.addClass(element, 'se-component|se-inline-component|se-disable-pointer|se-math');
|
|
177
|
+
|
|
178
|
+
if (this.katex) {
|
|
179
|
+
dom.utils.addClass(element, 'katex');
|
|
180
|
+
} else {
|
|
181
|
+
dom.utils.removeClass(element, 'katex');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (this.mathjax) {
|
|
185
|
+
this.#renderMathJax(this.mathjax);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* @editorMethod Modules.Modal
|
|
193
|
+
* @description Executes the method that is called when a "Modal" module's is opened.
|
|
194
|
+
*/
|
|
195
|
+
open() {
|
|
196
|
+
this.modal.open();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* @editorMethod Modules.Modal
|
|
201
|
+
* @description Executes the method that is called when a plugin's modal is opened.
|
|
202
|
+
* @param {boolean} isUpdate "Indicates whether the modal is for editing an existing component (true) or registering a new one (false)."
|
|
203
|
+
*/
|
|
204
|
+
on(isUpdate) {
|
|
205
|
+
this.isUpdateState = isUpdate;
|
|
206
|
+
if (!isUpdate) {
|
|
207
|
+
this.init();
|
|
208
|
+
} else if (this.controller.currentTarget) {
|
|
209
|
+
const currentTarget = this.controller.currentTarget;
|
|
210
|
+
const exp = converter.entityToHTML(this._escapeBackslashes(getValue(currentTarget), true));
|
|
211
|
+
const fontSize = getType(currentTarget) || '1em';
|
|
212
|
+
this.textArea.value = exp;
|
|
213
|
+
this.fontSizeElement.value = fontSize;
|
|
214
|
+
this.previewElement.innerHTML = this._renderer(exp);
|
|
215
|
+
this.previewElement.style.fontSize = fontSize;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* @editorMethod Modules.Modal
|
|
221
|
+
* @description This function is called when a form within a modal window is "submit".
|
|
222
|
+
* @returns {boolean} Success or failure
|
|
223
|
+
*/
|
|
224
|
+
modalAction() {
|
|
225
|
+
if (this.textArea.value.trim().length === 0 || dom.utils.hasClass(this.textArea, 'se-error')) {
|
|
226
|
+
this.textArea.focus();
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const mathExp = this.textArea.value;
|
|
231
|
+
|
|
232
|
+
/** @type {HTMLSpanElement} */
|
|
233
|
+
const mathEl = this.previewElement.querySelector('.se-math, .katex');
|
|
234
|
+
|
|
235
|
+
if (!mathEl) return false;
|
|
236
|
+
dom.utils.addClass(mathEl, 'se-component|se-inline-component|se-disable-pointer|se-math');
|
|
237
|
+
mathEl.setAttribute('contenteditable', 'false');
|
|
238
|
+
mathEl.setAttribute('data-se-value', converter.htmlToEntity(this._escapeBackslashes(mathExp, false)));
|
|
239
|
+
mathEl.setAttribute('data-se-type', this.fontSizeElement.value);
|
|
240
|
+
mathEl.style.fontSize = this.fontSizeElement.value;
|
|
241
|
+
|
|
242
|
+
if (this.katex) {
|
|
243
|
+
dom.utils.addClass(mathEl, 'katex');
|
|
244
|
+
dom.utils.removeClass(mathEl, 'MathJax');
|
|
245
|
+
} else {
|
|
246
|
+
dom.utils.removeClass(mathEl, 'katex');
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (!this.isUpdateState) {
|
|
250
|
+
const selectedFormats = this.format.getLines();
|
|
251
|
+
|
|
252
|
+
if (selectedFormats.length > 1) {
|
|
253
|
+
const oFormat = dom.utils.createElement(selectedFormats[0].nodeName, null, mathEl);
|
|
254
|
+
this.component.insert(oFormat, { skipCharCount: false, skipSelection: true, skipHistory: false });
|
|
255
|
+
} else {
|
|
256
|
+
this.component.insert(mathEl, { skipCharCount: false, skipSelection: true, skipHistory: false });
|
|
257
|
+
}
|
|
258
|
+
} else {
|
|
259
|
+
const containerEl = dom.query.getParentElement(this.controller.currentTarget, '.se-component');
|
|
260
|
+
containerEl.replaceWith(mathEl);
|
|
261
|
+
const compInfo = this.component.get(mathEl);
|
|
262
|
+
this.component.select(compInfo.target, compInfo.pluginName);
|
|
263
|
+
return true;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (this.mathjax) {
|
|
267
|
+
this.#renderMathJax(this.mathjax);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const r = this.selection.getNearRange(mathEl);
|
|
271
|
+
if (r) {
|
|
272
|
+
this.selection.setRange(r.container, r.offset, r.container, r.offset);
|
|
273
|
+
} else {
|
|
274
|
+
this.component.select(mathEl, Math_.key);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return true;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* @editorMethod Modules.Modal
|
|
282
|
+
* @description This function is called before the modal window is opened, but before it is closed.
|
|
283
|
+
*/
|
|
284
|
+
init() {
|
|
285
|
+
this.textArea.value = '';
|
|
286
|
+
this.previewElement.innerHTML = '';
|
|
287
|
+
dom.utils.removeClass(this.textArea, 'se-error');
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* @editorMethod Modules.Controller
|
|
292
|
+
* @description Executes the method that is called when a button is clicked in the "controller".
|
|
293
|
+
* @param {HTMLButtonElement} target Target button element
|
|
294
|
+
*/
|
|
295
|
+
controllerAction(target) {
|
|
296
|
+
const command = target.getAttribute('data-command');
|
|
297
|
+
switch (command) {
|
|
298
|
+
case 'update':
|
|
299
|
+
this.modal.open();
|
|
300
|
+
break;
|
|
301
|
+
case 'copy':
|
|
302
|
+
this.#copyTextToClipboard(this._element);
|
|
303
|
+
break;
|
|
304
|
+
case 'delete':
|
|
305
|
+
this.destroy(this.controller.currentTarget);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* @editorMethod Editor.Component
|
|
311
|
+
* @description Method to delete a component of a plugin, called by the "FileManager", "Controller" module.
|
|
312
|
+
* @param {Node} target Target element
|
|
313
|
+
*/
|
|
314
|
+
destroy(target) {
|
|
315
|
+
dom.utils.removeItem(target);
|
|
316
|
+
this.controller.close();
|
|
317
|
+
this.editor.focus();
|
|
318
|
+
this.history.push(false);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* @private
|
|
323
|
+
* @description Renders the given math expression using KaTeX or MathJax.
|
|
324
|
+
* @param {string} exp - The math expression to render.
|
|
325
|
+
* @returns {string} - The rendered math expression as HTML.
|
|
326
|
+
*/
|
|
327
|
+
_renderer(exp) {
|
|
328
|
+
let result = '';
|
|
329
|
+
try {
|
|
330
|
+
dom.utils.removeClass(this.textArea, 'se-error');
|
|
331
|
+
if (this.katex) {
|
|
332
|
+
result = this.katex.src.renderToString(exp, { throwOnError: true, displayMode: true });
|
|
333
|
+
} else if (this.mathjax) {
|
|
334
|
+
result = this.mathjax.convert(exp).outerHTML;
|
|
335
|
+
if (/<mjx-merror/.test(result)) {
|
|
336
|
+
dom.utils.addClass(this.textArea, 'se-error');
|
|
337
|
+
result = `<span class="se-math-error">${result}</span>`;
|
|
338
|
+
} else {
|
|
339
|
+
result = `<span class="se-math">${result}</span>`;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
} catch (error) {
|
|
343
|
+
dom.utils.addClass(this.textArea, 'se-error');
|
|
344
|
+
result = `<span class="se-math-error">Math syntax error. (Refer ${this.katex ? `<a href="${env.KATEX_WEBSITE}" target="_blank">KaTeX</a>` : `<a href="${env.MATHJAX_WEBSITE}" target="_blank">MathJax</a>`})</span>`;
|
|
345
|
+
console.warn('[SUNEDITOR.math.error] ', error.message);
|
|
346
|
+
}
|
|
347
|
+
return result;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* @private
|
|
352
|
+
* @description Escapes or unescapes backslashes in a given string.
|
|
353
|
+
* @param {string} str - The input string.
|
|
354
|
+
* @param {boolean} decode - If true, decodes escaped backslashes; otherwise, encodes them.
|
|
355
|
+
* @returns {string} - The processed string.
|
|
356
|
+
*/
|
|
357
|
+
_escapeBackslashes(str, decode) {
|
|
358
|
+
return str.replace(/\\{2}/g, decode ? '\\' : '\\\\');
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* @description Copies the math expression text to clipboard.
|
|
363
|
+
* @param {Node} element - The math expression element.
|
|
364
|
+
* @returns {Promise<void>}
|
|
365
|
+
*/
|
|
366
|
+
async #copyTextToClipboard(element) {
|
|
367
|
+
if (!navigator.clipboard || !element) return;
|
|
368
|
+
|
|
369
|
+
try {
|
|
370
|
+
const text = getValue(element);
|
|
371
|
+
await this.html.copy(text);
|
|
372
|
+
dom.utils.addClass(element, 'se-copy');
|
|
373
|
+
// copy effect
|
|
374
|
+
_w.setTimeout(() => {
|
|
375
|
+
dom.utils.removeClass(element, 'se-copy');
|
|
376
|
+
}, 120);
|
|
377
|
+
} catch (err) {
|
|
378
|
+
console.error('[SUNEDITOR.math.copy.fail]', err);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* @description Handles rendering of math expressions in the preview.
|
|
384
|
+
* @param {InputEvent} e - The input event.
|
|
385
|
+
*/
|
|
386
|
+
#RenderMathExp(e) {
|
|
387
|
+
/** @type {HTMLInputElement} */
|
|
388
|
+
const eventTarget = dom.query.getEventTarget(e);
|
|
389
|
+
if (this.pluginOptions.autoHeight) {
|
|
390
|
+
eventTarget.style.height = '5px';
|
|
391
|
+
eventTarget.style.height = eventTarget.scrollHeight + 5 + 'px';
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
this.previewElement.innerHTML = this._renderer(eventTarget.value);
|
|
395
|
+
if (this.mathjax) this.#renderMathJax(this.mathjax);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* @param {*} mathjax - The MathJax instance.
|
|
400
|
+
*/
|
|
401
|
+
#renderMathJax(mathjax) {
|
|
402
|
+
mathjax.clear();
|
|
403
|
+
mathjax.updateDocument();
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* @param {*} katex - The KaTeX instance.
|
|
408
|
+
* @returns {*} - The KaTeX instance or null if the instance is invalid.
|
|
409
|
+
*/
|
|
410
|
+
#CheckKatex(katex) {
|
|
411
|
+
if (!katex) return null;
|
|
412
|
+
if (!katex.src) {
|
|
413
|
+
console.warn('[SUNEDITOR.math.katex.fail] The katex option is set incorrectly.');
|
|
414
|
+
return null;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const katexOptions = [
|
|
418
|
+
{
|
|
419
|
+
throwOnError: false
|
|
420
|
+
},
|
|
421
|
+
katex.options || {}
|
|
422
|
+
].reduce((init, option) => {
|
|
423
|
+
for (const key in option) {
|
|
424
|
+
init[key] = option[key];
|
|
425
|
+
}
|
|
426
|
+
return init;
|
|
427
|
+
}, {});
|
|
428
|
+
|
|
429
|
+
katex.options = katexOptions;
|
|
430
|
+
return katex;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* @param {*} mathjax - The MathJax instance.
|
|
435
|
+
* @param {__se__EditorCore} editor - The root editor instance.
|
|
436
|
+
* @returns {*}
|
|
437
|
+
*/
|
|
438
|
+
#CheckMathJax(mathjax, editor) {
|
|
439
|
+
if (!mathjax) return null;
|
|
440
|
+
if (editor.frameOptions.get('iframe')) {
|
|
441
|
+
console.warn('[SUNEDITOR.math.mathjax.fail] The MathJax option is not supported in the iframe.');
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
try {
|
|
445
|
+
const adaptor = mathjax.browserAdaptor();
|
|
446
|
+
mathjax.RegisterHTMLHandler(adaptor);
|
|
447
|
+
|
|
448
|
+
const tex = new mathjax.TeX();
|
|
449
|
+
const chtml = new mathjax.CHTML();
|
|
450
|
+
|
|
451
|
+
return mathjax.src.document(document, {
|
|
452
|
+
InputJax: tex,
|
|
453
|
+
OutputJax: chtml
|
|
454
|
+
});
|
|
455
|
+
} catch (error) {
|
|
456
|
+
console.warn('[SUNEDITOR.math.mathjax.fail] The MathJax option is set incorrectly.', error);
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function CreateHTML_modal(inst) {
|
|
463
|
+
const { lang, icons, pluginOptions, katex } = inst;
|
|
464
|
+
const { formSize, fontSizeList, canResize, autoHeight } = pluginOptions;
|
|
465
|
+
const { width, height, maxWidth, maxHeight, minWidth, minHeight } = formSize;
|
|
466
|
+
const resizeType = !canResize ? 'none' : autoHeight ? 'horizontal' : 'auto';
|
|
467
|
+
|
|
468
|
+
let defaultFontSize = fontSizeList[0].value;
|
|
469
|
+
let html = /*html*/ `
|
|
470
|
+
<form>
|
|
471
|
+
<div class="se-modal-header">
|
|
472
|
+
<button type="button" data-command="close" class="se-btn se-close-btn" title="${lang.close}" aria-label="${lang.close}">
|
|
473
|
+
${icons.cancel}
|
|
474
|
+
</button>
|
|
475
|
+
<span class="se-modal-title">${lang.math_modal_title}</span>
|
|
476
|
+
</div>
|
|
477
|
+
<div class="se-modal-body">
|
|
478
|
+
<div class="se-modal-form">
|
|
479
|
+
<label>${lang.math_modal_inputLabel} ${katex ? `(<a href="${env.KATEX_WEBSITE}" target="_blank">KaTeX</a>)` : `(<a href="${env.MATHJAX_WEBSITE}" target="_blank">MathJax</a>)`}</label>
|
|
480
|
+
<textarea class="se-input-form se-math-exp se-modal-resize-form" type="text" data-focus style="width: ${width}; height: ${height}; min-width: ${minWidth}; min-height: ${minHeight}; resize: ${resizeType};"></textarea>
|
|
481
|
+
</div>
|
|
482
|
+
<div class="se-modal-form">
|
|
483
|
+
<label>${lang.math_modal_fontSizeLabel}</label>
|
|
484
|
+
<select class="se-input-select se-math-size">`;
|
|
485
|
+
|
|
486
|
+
for (let i = 0, len = fontSizeList.length, f; i < len; i++) {
|
|
487
|
+
f = fontSizeList[i];
|
|
488
|
+
if (f.default) defaultFontSize = f.value;
|
|
489
|
+
html += /*html*/ `<option value="${f.value}"${f.default ? ' selected' : ''}>${f.text}</option>`;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
html += /*html*/ `</select>
|
|
493
|
+
</div>
|
|
494
|
+
<div class="se-modal-form">
|
|
495
|
+
<label>${lang.math_modal_previewLabel}</label>
|
|
496
|
+
<p class="se-math-preview"></p>
|
|
497
|
+
</div>
|
|
498
|
+
</div>
|
|
499
|
+
<div class="se-modal-footer">
|
|
500
|
+
<button type="submit" class="se-btn-primary" title="${lang.submitButton}" aria-label="${lang.submitButton}">
|
|
501
|
+
<span>${lang.submitButton}</span>
|
|
502
|
+
</button>
|
|
503
|
+
</div>
|
|
504
|
+
</form>`;
|
|
505
|
+
|
|
506
|
+
inst.defaultFontSize = defaultFontSize;
|
|
507
|
+
return dom.utils.createElement('DIV', { class: 'se-modal-content se-modal-responsive', style: `max-width: ${maxWidth}; max-height: ${maxHeight};` }, html);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function CreateHTML_controller({ lang, icons }) {
|
|
511
|
+
const html = /*html*/ `
|
|
512
|
+
<div class="se-arrow se-arrow-up"></div>
|
|
513
|
+
<div class="link-content">
|
|
514
|
+
<div class="se-btn-group">
|
|
515
|
+
<button type="button" data-command="update" tabindex="-1" class="se-btn se-tooltip">
|
|
516
|
+
${icons.edit}
|
|
517
|
+
<span class="se-tooltip-inner">
|
|
518
|
+
<span class="se-tooltip-text">${lang.edit}</span>
|
|
519
|
+
</span>
|
|
520
|
+
</button>
|
|
521
|
+
<button type="button" data-command="copy" tabindex="-1" class="se-btn se-tooltip">
|
|
522
|
+
${icons.copy}
|
|
523
|
+
<span class="se-tooltip-inner">
|
|
524
|
+
<span class="se-tooltip-text">${lang.copy}</span>
|
|
525
|
+
</span>
|
|
526
|
+
</button>
|
|
527
|
+
<button type="button" data-command="delete" tabindex="-1" class="se-btn se-tooltip">
|
|
528
|
+
${icons.delete}
|
|
529
|
+
<span class="se-tooltip-inner">
|
|
530
|
+
<span class="se-tooltip-text">${lang.remove}</span>
|
|
531
|
+
</span>
|
|
532
|
+
</button>
|
|
533
|
+
</div>
|
|
534
|
+
</div>`;
|
|
535
|
+
|
|
536
|
+
return dom.utils.createElement('DIV', { class: 'se-controller se-controller-link' }, html);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function getValue(element) {
|
|
540
|
+
const seAttr = element.getAttribute('data-se-value');
|
|
541
|
+
if (seAttr) return seAttr;
|
|
542
|
+
|
|
543
|
+
// v2-migration
|
|
544
|
+
const v2SeAttr = element.getAttribute(`data-exp`);
|
|
545
|
+
if (!v2SeAttr) return null;
|
|
546
|
+
element.removeAttribute(`data-exp`);
|
|
547
|
+
element.setAttribute(`data-se-value`, v2SeAttr);
|
|
548
|
+
return v2SeAttr;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function getType(element) {
|
|
552
|
+
const seAttr = element.getAttribute('data-se-type');
|
|
553
|
+
if (seAttr) return seAttr;
|
|
554
|
+
|
|
555
|
+
// v2-migration
|
|
556
|
+
const v2SeAttr = element.getAttribute(`data-exp`);
|
|
557
|
+
if (!v2SeAttr) return null;
|
|
558
|
+
element.removeAttribute(`data-font-size`);
|
|
559
|
+
element.setAttribute(`data-se-type`, v2SeAttr);
|
|
560
|
+
return v2SeAttr;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
export default Math_;
|