@wiris/mathtype-ckeditor5 8.9.2 → 8.10.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.
package/dist/index.js ADDED
@@ -0,0 +1,803 @@
1
+ import { Command, Plugin } from '@ckeditor/ckeditor5-core/dist/index.js';
2
+ import { ButtonView } from '@ckeditor/ckeditor5-ui/dist/index.js';
3
+ import { ClickObserver, XmlDataProcessor, UpcastWriter, HtmlDataProcessor } from '@ckeditor/ckeditor5-engine/dist/index.js';
4
+ import { Widget, viewToModelPositionOutsideModelElement, toWidget } from '@ckeditor/ckeditor5-widget/dist/index.js';
5
+ import IntegrationModel from '@wiris/mathtype-html-integration-devkit/src/integrationmodel.js';
6
+ import Core from '@wiris/mathtype-html-integration-devkit/src/core.src.js';
7
+ import Parser from '@wiris/mathtype-html-integration-devkit/src/parser.js';
8
+ import Util from '@wiris/mathtype-html-integration-devkit/src/util.js';
9
+ import Image from '@wiris/mathtype-html-integration-devkit/src/image.js';
10
+ import Configuration from '@wiris/mathtype-html-integration-devkit/src/configuration.js';
11
+ import Listeners from '@wiris/mathtype-html-integration-devkit/src/listeners.js';
12
+ import MathML from '@wiris/mathtype-html-integration-devkit/src/mathml.js';
13
+ import Latex from '@wiris/mathtype-html-integration-devkit/src/latex.js';
14
+ import StringManager from '@wiris/mathtype-html-integration-devkit/src/stringmanager.js';
15
+ import Telemeter from '@wiris/mathtype-html-integration-devkit/src/telemeter.js';
16
+
17
+ /**
18
+ * This class represents the MathType integration for CKEditor5.
19
+ * @extends {IntegrationModel}
20
+ */ class CKEditor5Integration extends IntegrationModel {
21
+ constructor(ckeditorIntegrationModelProperties){
22
+ const editor = ckeditorIntegrationModelProperties.editorObject;
23
+ if (typeof editor.config !== "undefined" && typeof editor.config.get("mathTypeParameters") !== "undefined") {
24
+ ckeditorIntegrationModelProperties.integrationParameters = editor.config.get("mathTypeParameters");
25
+ }
26
+ /**
27
+ * CKEditor5 Integration.
28
+ *
29
+ * @param {integrationModelProperties} integrationModelAttributes
30
+ */ super(ckeditorIntegrationModelProperties);
31
+ /**
32
+ * Folder name used for the integration inside CKEditor plugins folder.
33
+ */ this.integrationFolderName = "ckeditor_wiris";
34
+ }
35
+ /**
36
+ * @inheritdoc
37
+ * @returns {string} - The CKEditor instance language.
38
+ * @override
39
+ */ getLanguage() {
40
+ // Returns the CKEDitor instance language taking into account that the language can be an object.
41
+ // Try to get editorParameters.language, fail silently otherwise
42
+ try {
43
+ return this.editorParameters.language;
44
+ } catch (e) {}
45
+ const languageObject = this.editorObject.config.get("language");
46
+ if (languageObject != null) {
47
+ if (typeof languageObject === "object") {
48
+ // eslint-disable-next-line no-prototype-builtins
49
+ if (languageObject.hasOwnProperty("ui")) {
50
+ return languageObject.ui;
51
+ }
52
+ return languageObject;
53
+ }
54
+ return languageObject;
55
+ }
56
+ return super.getLanguage();
57
+ }
58
+ /**
59
+ * Adds callbacks to the following CKEditor listeners:
60
+ * - 'focus' - updates the current instance.
61
+ * - 'contentDom' - adds 'doubleclick' callback.
62
+ * - 'doubleclick' - sets to null data.dialog property to avoid modifications for MathType formulas.
63
+ * - 'setData' - parses the data converting MathML into images.
64
+ * - 'afterSetData' - adds an observer to MathType formulas to avoid modifications.
65
+ * - 'getData' - parses the data converting images into selected save mode (MathML by default).
66
+ * - 'mode' - recalculates the active element.
67
+ */ addEditorListeners() {
68
+ const editor = this.editorObject;
69
+ if (typeof editor.config.wirislistenersdisabled === "undefined" || !editor.config.wirislistenersdisabled) {
70
+ this.checkElement();
71
+ }
72
+ }
73
+ /**
74
+ * Checks the current container and assign events in case that it doesn't have them.
75
+ * CKEditor replaces several times the element element during its execution,
76
+ * so we must assign the events again to editor element.
77
+ */ checkElement() {
78
+ const editor = this.editorObject;
79
+ const newElement = editor.sourceElement;
80
+ // If the element wasn't treated, add the events.
81
+ if (!newElement.wirisActive) {
82
+ this.setTarget(newElement);
83
+ this.addEvents();
84
+ // Set the element as treated
85
+ newElement.wirisActive = true;
86
+ }
87
+ }
88
+ /**
89
+ * @inheritdoc
90
+ * @param {HTMLElement} element - HTMLElement target.
91
+ * @param {MouseEvent} event - event which trigger the handler.
92
+ */ doubleClickHandler(element, event) {
93
+ this.core.editionProperties.dbclick = true;
94
+ if (this.editorObject.isReadOnly === false) {
95
+ if (element.nodeName.toLowerCase() === "img") {
96
+ if (Util.containsClass(element, Configuration.get("imageClassName"))) {
97
+ // Some plugins (image2, image) open a dialog on Double-click. On formulas
98
+ // doubleclick event ends here.
99
+ if (typeof event.stopPropagation !== "undefined") {
100
+ // old I.E compatibility.
101
+ event.stopPropagation();
102
+ } else {
103
+ event.returnValue = false;
104
+ }
105
+ this.core.getCustomEditors().disable();
106
+ const customEditorAttr = element.getAttribute(Configuration.get("imageCustomEditorName"));
107
+ if (customEditorAttr) {
108
+ this.core.getCustomEditors().enable(customEditorAttr);
109
+ }
110
+ this.core.editionProperties.temporalImage = element;
111
+ this.openExistingFormulaEditor();
112
+ }
113
+ }
114
+ }
115
+ }
116
+ /** @inheritdoc */ static getCorePath() {
117
+ return null; // TODO
118
+ }
119
+ /** @inheritdoc */ callbackFunction() {
120
+ super.callbackFunction();
121
+ this.addEditorListeners();
122
+ }
123
+ openNewFormulaEditor() {
124
+ // Store the editor selection as it will be lost upon opening the modal
125
+ this.core.editionProperties.selection = this.editorObject.editing.view.document.selection;
126
+ return super.openNewFormulaEditor();
127
+ }
128
+ /**
129
+ * Replaces old formula with new MathML or inserts it in caret position if new
130
+ * @param {String} mathml MathML to update old one or insert
131
+ * @returns {module:engine/model/element~Element} The model element corresponding to the inserted image
132
+ */ insertMathml(mathml) {
133
+ // This returns the value returned by the callback function (writer => {...})
134
+ return this.editorObject.model.change((writer)=>{
135
+ const core = this.getCore();
136
+ const selection = this.editorObject.model.document.selection;
137
+ const modelElementNew = writer.createElement("mathml", {
138
+ formula: mathml,
139
+ ...Object.fromEntries(selection.getAttributes())
140
+ });
141
+ // Obtain the DOM <span><img ... /></span> object corresponding to the formula
142
+ if (core.editionProperties.isNewElement) {
143
+ // Don't bother inserting anything at all if the MathML is empty.
144
+ if (!mathml) return;
145
+ const viewSelection = this.core.editionProperties.selection || this.editorObject.editing.view.document.selection;
146
+ const modelPosition = this.editorObject.editing.mapper.toModelPosition(viewSelection.getLastPosition());
147
+ this.editorObject.model.insertObject(modelElementNew, modelPosition);
148
+ // Remove selection
149
+ if (!viewSelection.isCollapsed) {
150
+ for (const range of viewSelection.getRanges()){
151
+ writer.remove(this.editorObject.editing.mapper.toModelRange(range));
152
+ }
153
+ }
154
+ // Set carret after the formula
155
+ const position = this.editorObject.model.createPositionAfter(modelElementNew);
156
+ writer.setSelection(position);
157
+ } else {
158
+ const img = core.editionProperties.temporalImage;
159
+ const viewElement = this.editorObject.editing.view.domConverter.domToView(img).parent;
160
+ const modelElementOld = this.editorObject.editing.mapper.toModelElement(viewElement);
161
+ // Insert the new <mathml> and remove the old one
162
+ const position = this.editorObject.model.createPositionBefore(modelElementOld);
163
+ // If the given MathML is empty, don't insert a new formula.
164
+ if (mathml) {
165
+ this.editorObject.model.insertObject(modelElementNew, position);
166
+ }
167
+ writer.remove(modelElementOld);
168
+ }
169
+ // eslint-disable-next-line consistent-return
170
+ return modelElementNew;
171
+ });
172
+ }
173
+ /**
174
+ * Finds the text node corresponding to given DOM text element.
175
+ * @param {element} viewElement Element to find corresponding text node of.
176
+ * @returns {module:engine/model/text~Text|undefined} Text node corresponding to the given element or undefined if it doesn't exist.
177
+ */ findText(viewElement) {
178
+ // eslint-disable-line consistent-return
179
+ // mapper always converts text nodes to *new* model elements so we need to convert the text's parents and then come back down
180
+ let pivot = viewElement;
181
+ let element;
182
+ while(!element){
183
+ element = this.editorObject.editing.mapper.toModelElement(this.editorObject.editing.view.domConverter.domToView(pivot));
184
+ pivot = pivot.parentElement;
185
+ }
186
+ // Navigate through all the subtree under `pivot` in order to find the correct text node
187
+ const range = this.editorObject.model.createRangeIn(element);
188
+ const descendants = Array.from(range.getItems());
189
+ for (const node of descendants){
190
+ let viewElementData = viewElement.data;
191
+ if (viewElement.nodeType === 3) {
192
+ // Remove invisible white spaces
193
+ viewElementData = viewElementData.replaceAll(String.fromCharCode(8288), "");
194
+ }
195
+ if (node.is("textProxy") && node.data === viewElementData.replace(String.fromCharCode(160), " ")) {
196
+ return node.textNode;
197
+ }
198
+ }
199
+ }
200
+ /** @inheritdoc */ insertFormula(focusElement, windowTarget, mathml, wirisProperties) {
201
+ // eslint-disable-line no-unused-vars
202
+ const returnObject = {};
203
+ let mathmlOrigin;
204
+ if (!mathml) {
205
+ this.insertMathml("");
206
+ } else if (this.core.editMode === "latex") {
207
+ returnObject.latex = Latex.getLatexFromMathML(mathml);
208
+ returnObject.node = windowTarget.document.createTextNode(`$$${returnObject.latex}$$`);
209
+ this.editorObject.model.change((writer)=>{
210
+ const { latexRange } = this.core.editionProperties;
211
+ const startNode = this.findText(latexRange.startContainer);
212
+ const endNode = this.findText(latexRange.endContainer);
213
+ let startPosition = writer.createPositionAt(startNode.parent, startNode.startOffset + latexRange.startOffset);
214
+ let endPosition = writer.createPositionAt(endNode.parent, endNode.startOffset + latexRange.endOffset);
215
+ let range = writer.createRange(startPosition, endPosition);
216
+ // When Latex is next to image/formula.
217
+ if (latexRange.startContainer.nodeType === 3 && latexRange.startContainer.previousSibling?.nodeType === 1) {
218
+ // Get the position of the latex to be replaced.
219
+ let latexEdited = "$$" + Latex.getLatexFromMathML(MathML.safeXmlDecode(this.core.editionProperties.temporalImage.dataset.mathml)) + "$$";
220
+ let data = latexRange.startContainer.data;
221
+ // Remove invisible characters.
222
+ data = data.replaceAll(String.fromCharCode(8288), "");
223
+ // Get to the start of the latex we are editing.
224
+ let offset = data.indexOf(latexEdited);
225
+ let dataOffset = data.substring(offset);
226
+ let second$ = dataOffset.substring(2).indexOf("$$") + 4;
227
+ let substring = dataOffset.substr(0, second$);
228
+ data = data.replace(substring, "");
229
+ if (!data) {
230
+ startPosition = writer.createPositionBefore(startNode);
231
+ range = startNode;
232
+ } else {
233
+ startPosition = startPosition = writer.createPositionAt(startNode.parent, startNode.startOffset + offset);
234
+ endPosition = writer.createPositionAt(endNode.parent, endNode.startOffset + second$ + offset);
235
+ range = writer.createRange(startPosition, endPosition);
236
+ }
237
+ }
238
+ writer.remove(range);
239
+ writer.insertText(`$$${returnObject.latex}$$`, startNode.getAttributes(), startPosition);
240
+ });
241
+ } else {
242
+ mathmlOrigin = this.core.editionProperties.temporalImage?.dataset.mathml;
243
+ try {
244
+ returnObject.node = this.editorObject.editing.view.domConverter.viewToDom(this.editorObject.editing.mapper.toViewElement(this.insertMathml(mathml)), windowTarget.document);
245
+ } catch (e) {
246
+ const x = e.toString();
247
+ if (x.includes("CKEditorError: Cannot read property 'parent' of undefined")) {
248
+ this.core.modalDialog.cancelAction();
249
+ }
250
+ }
251
+ }
252
+ // Build the telemeter payload separated to delete null/undefined entries.
253
+ let payload = {
254
+ mathml_origin: mathmlOrigin ? MathML.safeXmlDecode(mathmlOrigin) : mathmlOrigin,
255
+ mathml: mathml ? MathML.safeXmlDecode(mathml) : mathml,
256
+ elapsed_time: Date.now() - this.core.editionProperties.editionStartTime,
257
+ editor_origin: null,
258
+ toolbar: this.core.modalDialog.contentManager.toolbar,
259
+ size: mathml?.length
260
+ };
261
+ // Remove desired null keys.
262
+ Object.keys(payload).forEach((key)=>{
263
+ if (key === "mathml_origin" || key === "editor_origin") !payload[key] ? delete payload[key] : {};
264
+ });
265
+ // Call Telemetry service to track the event.
266
+ try {
267
+ Telemeter.telemeter.track("INSERTED_FORMULA", {
268
+ ...payload
269
+ });
270
+ } catch (error) {
271
+ console.error("Error tracking INSERTED_FORMULA", error);
272
+ }
273
+ /* Due to PLUGINS-1329, we add the onChange event to the CK4 insertFormula.
274
+ We probably should add it here as well, but we should look further into how */ // this.editorObject.fire('change');
275
+ // Remove temporal image of inserted formula
276
+ this.core.editionProperties.temporalImage = null;
277
+ return returnObject;
278
+ }
279
+ /**
280
+ * Function called when the content submits an action.
281
+ */ notifyWindowClosed() {
282
+ this.editorObject.editing.view.focus();
283
+ }
284
+ }
285
+
286
+ /**
287
+ * Command for opening the MathType editor
288
+ */ class MathTypeCommand extends Command {
289
+ execute(options = {}) {
290
+ // Check we get a valid integration
291
+ // eslint-disable-next-line no-prototype-builtins
292
+ if (!options.hasOwnProperty("integration") || !(options.integration instanceof CKEditor5Integration)) {
293
+ throw 'Must pass a valid CKEditor5Integration instance as attribute "integration" of options';
294
+ }
295
+ // Save the integration instance as a property of the command.
296
+ this.integration = options.integration;
297
+ // Set custom editor or disable it
298
+ this.setEditor();
299
+ // Open the editor
300
+ this.openEditor();
301
+ }
302
+ /**
303
+ * Sets the appropriate custom editor, if any, or disables them.
304
+ */ setEditor() {
305
+ // It's possible that a custom editor was last used.
306
+ // We need to disable it to avoid wrong behaviors.
307
+ this.integration.core.getCustomEditors().disable();
308
+ }
309
+ /**
310
+ * Checks whether we are editing an existing formula or a new one and opens the editor.
311
+ */ openEditor() {
312
+ this.integration.core.editionProperties.dbclick = false;
313
+ const image = this._getSelectedImage();
314
+ if (typeof image !== "undefined" && image !== null && image.classList.contains(WirisPlugin.Configuration.get("imageClassName"))) {
315
+ this.integration.core.editionProperties.temporalImage = image;
316
+ this.integration.openExistingFormulaEditor();
317
+ } else {
318
+ this.integration.openNewFormulaEditor();
319
+ }
320
+ }
321
+ /**
322
+ * Gets the currently selected formula image
323
+ * @returns {Element} selected image, if any, undefined otherwise
324
+ */ _getSelectedImage() {
325
+ const { selection } = this.editor.editing.view.document;
326
+ // If we can not extract the formula, fall back to default behavior.
327
+ if (selection.isCollapsed || selection.rangeCount !== 1) {
328
+ return;
329
+ }
330
+ // Look for the <span> wrapping the formula and then for the <img/> inside
331
+ const range = selection.getFirstRange();
332
+ let image;
333
+ for (const span of range){
334
+ if (span.item.name !== "span") {
335
+ return;
336
+ }
337
+ image = span.item.getChild(0);
338
+ break;
339
+ }
340
+ if (!image) {
341
+ return;
342
+ }
343
+ // eslint-disable-next-line consistent-return
344
+ return this.editor.editing.view.domConverter.mapViewToDom(image);
345
+ }
346
+ }
347
+ /**
348
+ * Command for opening the ChemType editor
349
+ */ class ChemTypeCommand extends MathTypeCommand {
350
+ setEditor() {
351
+ this.integration.core.getCustomEditors().enable("chemistry");
352
+ }
353
+ }
354
+
355
+ var mathIcon = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->\n<svg version=\"1.1\" id=\"Layer_1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\"\n\t viewBox=\"0 0 300 261.7\" style=\"enable-background:new 0 0 300 261.7;\" xml:space=\"preserve\">\n<style type=\"text/css\">\n\t.st0{fill:#FFFFFF;}\n\t.st1{fill:#EF4A5F;}\n\t.st2{fill:#C8202F;}\n</style>\n<path class=\"st0\" d=\"M300,32.8c0-16.4-13.4-29.7-29.9-29.7c-2.9,0-7.2,0.8-7.2,0.8c-37.9,9.1-71.3,14-112,14c-0.3,0-0.6,0-1,0\n\tc-16.5,0-29.9,13.3-29.9,29.7c0,16.4,13.4,29.7,29.9,29.7l0,0c45.3,0,83.1-5.3,125.3-15.3h0C289.3,59.5,300,47.4,300,32.8\"/>\n<path class=\"st0\" d=\"M90.2,257.7c-11.4,0-21.9-6.4-27-16.7l-60-119.9c-7.5-14.9-1.4-33.1,13.5-40.5c14.9-7.5,33.1-1.4,40.5,13.5\n\tl27.3,54.7L121.1,39c5.3-15.8,22.4-24.4,38.2-19.1c15.8,5.3,24.4,22.4,19.1,38.2l-59.6,179c-3.9,11.6-14.3,19.7-26.5,20.6\n\tC91.6,257.7,90.9,257.7,90.2,257.7\"/>\n<g>\n\t<g>\n\t\t<path class=\"st1\" d=\"M90.2,257.7c-11.4,0-21.9-6.4-27-16.7l-60-119.9c-7.5-14.9-1.4-33.1,13.5-40.5c14.9-7.5,33.1-1.4,40.5,13.5\n\t\t\tl27.3,54.7L121.1,39c5.3-15.8,22.4-24.4,38.2-19.1c15.8,5.3,24.4,22.4,19.1,38.2l-59.6,179c-3.9,11.6-14.3,19.7-26.5,20.6\n\t\t\tC91.6,257.7,90.9,257.7,90.2,257.7\"/>\n\t</g>\n</g>\n<g>\n\t<g>\n\t\t<path class=\"st2\" d=\"M300,32.8c0-16.4-13.4-29.7-29.9-29.7c-2.9,0-7.2,0.8-7.2,0.8c-37.9,9.1-71.3,14-112,14c-0.3,0-0.6,0-1,0\n\t\t\tc-16.5,0-29.9,13.3-29.9,29.7c0,16.4,13.4,29.7,29.9,29.7l0,0c45.3,0,83.1-5.3,125.3-15.3h0C289.3,59.5,300,47.4,300,32.8\"/>\n\t</g>\n</g>\n</svg>\n";
356
+
357
+ var chemIcon = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->\n<svg version=\"1.1\" id=\"Layer_1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\"\n\t viewBox=\"0 0 40.3 49.5\" style=\"enable-background:new 0 0 40.3 49.5;\" xml:space=\"preserve\">\n<style type=\"text/css\">\n\t.st0{fill:#A4CF61;}\n</style>\n<path class=\"st0\" d=\"M39.2,12.1c0-1.9-1.1-3.6-2.7-4.4L24.5,0.9l0,0c-0.7-0.4-1.5-0.6-2.4-0.6c-0.9,0-1.7,0.2-2.4,0.6l0,0L2.3,10.8\n\tl0,0C0.9,11.7,0,13.2,0,14.9h0v19.6h0c0,1.7,0.9,3.3,2.3,4.1l0,0l17.4,9.9l0,0c0.7,0.4,1.5,0.6,2.4,0.6c0.9,0,1.7-0.2,2.4-0.6l0,0\n\tl12.2-6.9h0c1.5-0.8,2.6-2.5,2.6-4.3c0-2.7-2.2-4.9-4.9-4.9c-0.9,0-1.8,0.3-2.5,0.7l0,0l-9.7,5.6l-12.3-7V17.8l12.3-7l9.9,5.7l0,0\n\tc0.7,0.4,1.5,0.6,2.4,0.6C37,17,39.2,14.8,39.2,12.1\"/>\n</svg>\n";
358
+
359
+ var name = "@wiris/mathtype-ckeditor5";
360
+ var version = "8.10.0";
361
+ var description = "MathType Web for CKEditor5 editor";
362
+ var keywords = [
363
+ "chem",
364
+ "chemistry",
365
+ "chemtype",
366
+ "ckeditor",
367
+ "ckeditor5",
368
+ "editor",
369
+ "equation",
370
+ "latex",
371
+ "math",
372
+ "mathml",
373
+ "maths",
374
+ "mathtype",
375
+ "wiris"
376
+ ];
377
+ var repository = "https://github.com/wiris/html-integrations/tree/stable/packages/mathtype-ckeditor5";
378
+ var homepage = "https://www.wiris.com/";
379
+ var bugs = {
380
+ email: "support@wiris.com"
381
+ };
382
+ var license = "MIT";
383
+ var author = "WIRIS Team (http://www.wiris.com)";
384
+ var files = [
385
+ "dist",
386
+ "src",
387
+ "icons",
388
+ "theme",
389
+ "lang"
390
+ ];
391
+ var main = "src/plugin.js";
392
+ var type = "module";
393
+ var exports = {
394
+ ".": "./src/plugin.js",
395
+ "./dist/*.css": "./dist/*.css",
396
+ "./dist/*": "./dist/*",
397
+ "./src/*": "./src/*",
398
+ "./theme/*": "./theme/*",
399
+ "./package.json": "./package.json"
400
+ };
401
+ var scripts = {
402
+ build: "node ./scripts/build-dist.mjs",
403
+ "build:dist": "node ./scripts/build-dist.mjs",
404
+ prepare: "npm run build:dist"
405
+ };
406
+ var dependencies = {
407
+ "@wiris/mathtype-html-integration-devkit": "1.17.3"
408
+ };
409
+ var devDependencies = {
410
+ "@ckeditor/ckeditor5-dev-build-tools": "^40.2.0",
411
+ ckeditor5: ">=42.0.0"
412
+ };
413
+ var peerDependencies = {
414
+ ckeditor5: ">=42.0.0"
415
+ };
416
+ var packageInfo = {
417
+ name: name,
418
+ version: version,
419
+ description: description,
420
+ keywords: keywords,
421
+ repository: repository,
422
+ homepage: homepage,
423
+ bugs: bugs,
424
+ license: license,
425
+ author: author,
426
+ files: files,
427
+ main: main,
428
+ type: type,
429
+ exports: exports,
430
+ scripts: scripts,
431
+ dependencies: dependencies,
432
+ devDependencies: devDependencies,
433
+ peerDependencies: peerDependencies
434
+ };
435
+
436
+ // CKEditor imports
437
+ let currentInstance = null; // eslint-disable-line import/no-mutable-exports
438
+ class MathType extends Plugin {
439
+ static get requires() {
440
+ return [
441
+ Widget
442
+ ];
443
+ }
444
+ static get pluginName() {
445
+ return "MathType";
446
+ }
447
+ init() {
448
+ // Create the MathType API Integration object
449
+ const integration = this._addIntegration();
450
+ currentInstance = integration;
451
+ // Add the MathType and ChemType commands to the editor
452
+ this._addCommands();
453
+ // Add the buttons for MathType and ChemType
454
+ this._addViews(integration);
455
+ // Registers the <mathml> element in the schema
456
+ this._addSchema();
457
+ // Add the downcast and upcast converters
458
+ this._addConverters(integration);
459
+ // Expose the WirisPlugin variable to the window
460
+ this._exposeWiris();
461
+ }
462
+ /**
463
+ * Inherited from Plugin class: Executed when CKEditor5 is destroyed
464
+ */ destroy() {
465
+ // eslint-disable-line class-methods-use-this
466
+ currentInstance.destroy();
467
+ }
468
+ /**
469
+ * Create the MathType API Integration object
470
+ * @returns {CKEditor5Integration} the integration object
471
+ */ _addIntegration() {
472
+ const { editor } = this;
473
+ /**
474
+ * Integration model constructor attributes.
475
+ * @type {integrationModelProperties}
476
+ */ const integrationProperties = {};
477
+ integrationProperties.environment = {};
478
+ integrationProperties.environment.editor = "CKEditor5";
479
+ integrationProperties.environment.editorVersion = "5.x";
480
+ integrationProperties.version = packageInfo.version;
481
+ integrationProperties.editorObject = editor;
482
+ integrationProperties.serviceProviderProperties = {};
483
+ integrationProperties.serviceProviderProperties.URI = "https://www.wiris.net/demo/plugins/app";
484
+ integrationProperties.serviceProviderProperties.server = "java";
485
+ integrationProperties.target = editor.sourceElement;
486
+ integrationProperties.scriptName = "bundle.js";
487
+ integrationProperties.managesLanguage = true;
488
+ // etc
489
+ // There are platforms like Drupal that initialize CKEditor but they hide or remove the container element.
490
+ // To avoid a wrong behaviour, this integration only starts if the workspace container exists.
491
+ let integration;
492
+ if (integrationProperties.target) {
493
+ // Instance of the integration associated to this editor instance
494
+ integration = new CKEditor5Integration(integrationProperties);
495
+ integration.init();
496
+ integration.listeners.fire("onTargetReady", {});
497
+ integration.checkElement();
498
+ this.listenTo(editor.editing.view.document, "click", (evt, data)=>{
499
+ // Is Double-click
500
+ if (data.domEvent.detail === 2) {
501
+ integration.doubleClickHandler(data.domTarget, data.domEvent);
502
+ evt.stop();
503
+ }
504
+ }, {
505
+ priority: "highest"
506
+ });
507
+ }
508
+ return integration;
509
+ }
510
+ /**
511
+ * Add the MathType and ChemType commands to the editor
512
+ */ _addCommands() {
513
+ const { editor } = this;
514
+ // Add command to open the formula editor
515
+ editor.commands.add("MathType", new MathTypeCommand(editor));
516
+ // Add command to open the chemistry formula editor
517
+ editor.commands.add("ChemType", new ChemTypeCommand(editor));
518
+ }
519
+ /**
520
+ * Add the buttons for MathType and ChemType
521
+ * @param {CKEditor5Integration} integration the integration object
522
+ */ _addViews(integration) {
523
+ const { editor } = this;
524
+ // Check if MathType editor is enabled
525
+ if (Configuration.get("editorEnabled")) {
526
+ // Add button for the formula editor
527
+ editor.ui.componentFactory.add("MathType", (locale)=>{
528
+ const view = new ButtonView(locale);
529
+ // View is enabled iff command is enabled
530
+ view.bind("isEnabled").to(editor.commands.get("MathType"), "isEnabled");
531
+ view.set({
532
+ label: StringManager.get("insert_math", integration.getLanguage()),
533
+ icon: mathIcon,
534
+ tooltip: true
535
+ });
536
+ // Callback executed once the image is clicked.
537
+ view.on("execute", ()=>{
538
+ editor.execute("MathType", {
539
+ integration
540
+ });
541
+ });
542
+ return view;
543
+ });
544
+ }
545
+ // Check if ChemType editor is enabled
546
+ if (Configuration.get("chemEnabled")) {
547
+ // Add button for the chemistry formula editor
548
+ editor.ui.componentFactory.add("ChemType", (locale)=>{
549
+ const view = new ButtonView(locale);
550
+ // View is enabled iff command is enabled
551
+ view.bind("isEnabled").to(editor.commands.get("ChemType"), "isEnabled");
552
+ view.set({
553
+ label: StringManager.get("insert_chem", integration.getLanguage()),
554
+ icon: chemIcon,
555
+ tooltip: true
556
+ });
557
+ // Callback executed once the image is clicked.
558
+ view.on("execute", ()=>{
559
+ editor.execute("ChemType", {
560
+ integration
561
+ });
562
+ });
563
+ return view;
564
+ });
565
+ }
566
+ // Observer for the Double-click event
567
+ editor.editing.view.addObserver(ClickObserver);
568
+ }
569
+ /**
570
+ * Registers the <mathml> element in the schema
571
+ */ _addSchema() {
572
+ const { schema } = this.editor.model;
573
+ schema.register("mathml", {
574
+ inheritAllFrom: "$inlineObject",
575
+ allowAttributes: [
576
+ "formula"
577
+ ]
578
+ });
579
+ }
580
+ /**
581
+ * Add the downcast and upcast converters
582
+ */ _addConverters(integration) {
583
+ const { editor } = this;
584
+ // Editing view -> Model
585
+ editor.conversion.for("upcast").elementToElement({
586
+ view: {
587
+ name: "span",
588
+ classes: "ck-math-widget"
589
+ },
590
+ model: (viewElement, { writer: modelWriter })=>{
591
+ const formula = MathML.safeXmlDecode(viewElement.getChild(0).getAttribute("data-mathml"));
592
+ return modelWriter.createElement("mathml", {
593
+ formula
594
+ });
595
+ }
596
+ });
597
+ // Data view -> Model
598
+ editor.data.upcastDispatcher.on("element:math", (evt, data, conversionApi)=>{
599
+ const { consumable, writer } = conversionApi;
600
+ const { viewItem } = data;
601
+ // When element was already consumed then skip it.
602
+ if (!consumable.test(viewItem, {
603
+ name: true
604
+ })) {
605
+ return;
606
+ }
607
+ // If we encounter any <math> with a LaTeX annotation inside,
608
+ // convert it into a "$$...$$" string.
609
+ const isLatex = mathIsLatex(viewItem); // eslint-disable-line no-use-before-define
610
+ // Get the formula of the <math> (which is all its children).
611
+ const processor = new XmlDataProcessor(editor.editing.view.document);
612
+ // Only god knows why the following line makes viewItem lose all of its children,
613
+ // so we obtain isLatex before doing this because we need viewItem's children for that.
614
+ const upcastWriter = new UpcastWriter(editor.editing.view.document);
615
+ const viewDocumentFragment = upcastWriter.createDocumentFragment(viewItem.getChildren());
616
+ // and obtain the attributes of <math> too!
617
+ const mathAttributes = [
618
+ ...viewItem.getAttributes()
619
+ ].map(([key, value])=>` ${key}="${value}"`).join("");
620
+ // We process the document fragment
621
+ let formula = processor.toData(viewDocumentFragment) || "";
622
+ // And obtain the complete formula
623
+ formula = Util.htmlSanitize(`<math${mathAttributes}>${formula}</math>`);
624
+ // Replaces the < & > characters to its HTMLEntity to avoid render issues.
625
+ formula = formula.replaceAll('"<"', '"&lt;"').replaceAll('">"', '"&gt;"').replaceAll("><<", ">&lt;<");
626
+ /* Model node that contains what's going to actually be inserted. This can be either:
627
+ - A <mathml> element with a formula attribute set to the given formula, or
628
+ - If the original <math> had a LaTeX annotation, then the annotation surrounded by "$$...$$" */ const modelNode = isLatex ? writer.createText(Parser.initParse(formula, integration.getLanguage())) : writer.createElement("mathml", {
629
+ formula
630
+ });
631
+ // Find allowed parent for element that we are going to insert.
632
+ // If current parent does not allow to insert element but one of the ancestors does
633
+ // then split nodes to allowed parent.
634
+ const splitResult = conversionApi.splitToAllowedParent(modelNode, data.modelCursor);
635
+ // When there is no split result it means that we can't insert element to model tree, so let's skip it.
636
+ if (!splitResult) {
637
+ return;
638
+ }
639
+ // Insert element on allowed position.
640
+ conversionApi.writer.insert(modelNode, splitResult.position);
641
+ // Consume appropriate value from consumable values list.
642
+ consumable.consume(viewItem, {
643
+ name: true
644
+ });
645
+ const parts = conversionApi.getSplitParts(modelNode);
646
+ // Set conversion result range.
647
+ data.modelRange = writer.createRange(conversionApi.writer.createPositionBefore(modelNode), conversionApi.writer.createPositionAfter(parts[parts.length - 1]));
648
+ // Now we need to check where the `modelCursor` should be.
649
+ if (splitResult.cursorParent) {
650
+ // If we split parent to insert our element then we want to continue conversion in the new part of the split parent.
651
+ //
652
+ // before: <allowed><notAllowed>foo[]</notAllowed></allowed>
653
+ // after: <allowed><notAllowed>foo</notAllowed><converted></converted><notAllowed>[]</notAllowed></allowed>
654
+ data.modelCursor = conversionApi.writer.createPositionAt(splitResult.cursorParent, 0);
655
+ } else {
656
+ // Otherwise just continue after inserted element.
657
+ data.modelCursor = data.modelRange.end;
658
+ }
659
+ });
660
+ /**
661
+ * Whether the given view <math> element has a LaTeX annotation element.
662
+ * @param {*} math
663
+ * @returns {bool}
664
+ */ function mathIsLatex(math) {
665
+ const semantics = math.getChild(0);
666
+ if (!semantics || semantics.name !== "semantics") return false;
667
+ for (const child of semantics.getChildren()){
668
+ if (child.name === "annotation" && child.getAttribute("encoding") === "LaTeX") {
669
+ return true;
670
+ }
671
+ }
672
+ return false;
673
+ }
674
+ function createViewWidget(modelItem, { writer: viewWriter }) {
675
+ const widgetElement = viewWriter.createContainerElement("span", {
676
+ class: "ck-math-widget"
677
+ });
678
+ const mathUIElement = createViewImage(modelItem, {
679
+ writer: viewWriter
680
+ }); // eslint-disable-line no-use-before-define
681
+ if (mathUIElement) {
682
+ viewWriter.insert(viewWriter.createPositionAt(widgetElement, 0), mathUIElement);
683
+ }
684
+ return toWidget(widgetElement, viewWriter);
685
+ }
686
+ function createViewImage(modelItem, { writer: viewWriter }) {
687
+ const htmlDataProcessor = new HtmlDataProcessor(viewWriter.document);
688
+ const mathString = modelItem.getAttribute("formula").replaceAll('ref="<"', 'ref="&lt;"');
689
+ const imgHtml = Parser.initParse(mathString, integration.getLanguage());
690
+ const imgElement = htmlDataProcessor.toView(imgHtml).getChild(0);
691
+ /* Although we use the HtmlDataProcessor to obtain the attributes,
692
+ we must create a new EmptyElement which is independent of the
693
+ DataProcessor being used by this editor instance */ if (imgElement) {
694
+ return viewWriter.createEmptyElement("img", imgElement.getAttributes(), {
695
+ renderUnsafeAttributes: [
696
+ "src"
697
+ ]
698
+ });
699
+ }
700
+ return null;
701
+ }
702
+ // Model -> Editing view
703
+ editor.conversion.for("editingDowncast").elementToElement({
704
+ model: "mathml",
705
+ view: createViewWidget
706
+ });
707
+ // Model -> Data view
708
+ editor.conversion.for("dataDowncast").elementToElement({
709
+ model: "mathml",
710
+ view: createDataString
711
+ });
712
+ /**
713
+ * Makes a copy of the given view node.
714
+ * @param {module:engine/view/node~Node} sourceNode Node to copy.
715
+ * @returns {module:engine/view/node~Node} Copy of the node.
716
+ */ function clone(viewWriter, sourceNode) {
717
+ if (sourceNode.is("text")) {
718
+ return viewWriter.createText(sourceNode.data);
719
+ }
720
+ if (sourceNode.is("element")) {
721
+ if (sourceNode.is("emptyElement")) {
722
+ return viewWriter.createEmptyElement(sourceNode.name, sourceNode.getAttributes());
723
+ }
724
+ const element = viewWriter.createContainerElement(sourceNode.name, sourceNode.getAttributes());
725
+ for (const child of sourceNode.getChildren()){
726
+ viewWriter.insert(viewWriter.createPositionAt(element, "end"), clone(viewWriter, child));
727
+ }
728
+ return element;
729
+ }
730
+ throw new Exception("Given node has unsupported type."); // eslint-disable-line no-undef
731
+ }
732
+ function createDataString(modelItem, { writer: viewWriter }) {
733
+ const htmlDataProcessor = new HtmlDataProcessor(viewWriter.document);
734
+ let mathString = Parser.endParseSaveMode(modelItem.getAttribute("formula"));
735
+ const sourceMathElement = htmlDataProcessor.toView(mathString).getChild(0);
736
+ return clone(viewWriter, sourceMathElement);
737
+ }
738
+ // This stops the view selection getting into the <span>s and messing up caret movement
739
+ editor.editing.mapper.on("viewToModelPosition", viewToModelPositionOutsideModelElement(editor.model, (viewElement)=>viewElement.hasClass("ck-math-widget")));
740
+ // Keep a reference to the original get and set function.
741
+ editor.data;
742
+ /**
743
+ * Hack to transform $$latex$$ into <math> in editor.getData()'s output.
744
+ */ editor.data.on("get", (e)=>{
745
+ let output = e.return;
746
+ // This line cleans all the semantics stuff, including the handwritten data points and returns the MathML IF there is any.
747
+ // For text or latex formulas, it returns the original output.
748
+ e.return = MathML.removeSemantics(output, "application/json");
749
+ }, {
750
+ priority: "low"
751
+ });
752
+ /**
753
+ * Hack to transform <math> with LaTeX into $$LaTeX$$ in editor.setData().
754
+ */ editor.data.on("set", (e, args)=>{
755
+ // Retrieve the data to be set on the CKEditor.
756
+ let modifiedData = args[0];
757
+ // Regex to find all mathml formulas.
758
+ const regexp = /<math(.*?)<\/math>/gm;
759
+ // Get all MathML formulas and store them in an array.
760
+ // Using the conditional operator on data.main because the data parameter has different types depending on:
761
+ // editor.data.set can be used directly or by the source editing plugin.
762
+ // With the source editor plugin, data is an object with the key `main` which contains the source code string.
763
+ // When using the editor.data.set method, the data is a string with the content to be set to the editor.
764
+ let formulas = Object.values(modifiedData)[0] ? [
765
+ ...Object.values(modifiedData)[0].matchAll(regexp)
766
+ ] : [
767
+ ...modifiedData.matchAll(regexp)
768
+ ];
769
+ // Loop to find LaTeX formulas and replace the MathML for the LaTeX notation.
770
+ formulas.forEach((formula)=>{
771
+ let mathml = formula[0];
772
+ if (mathml.includes('encoding="LaTeX"')) {
773
+ // LaTeX found.
774
+ let latex = "$$$" + Latex.getLatexFromMathML(mathml) + "$$$"; // We add $$$ instead of $$ because the replace function ignores one $.
775
+ modifiedData = modifiedData.replace(mathml, latex);
776
+ }
777
+ });
778
+ args[0] = modifiedData;
779
+ }, {
780
+ priority: "high"
781
+ });
782
+ }
783
+ /**
784
+ * Expose the WirisPlugin variable to the window
785
+ */ // eslint-disable-next-line class-methods-use-this
786
+ _exposeWiris() {
787
+ window.WirisPlugin = {
788
+ Core,
789
+ Parser,
790
+ Image,
791
+ MathML,
792
+ Util,
793
+ Configuration,
794
+ Listeners,
795
+ IntegrationModel,
796
+ currentInstance,
797
+ Latex
798
+ };
799
+ }
800
+ }
801
+
802
+ export { MathType as default };
803
+ //# sourceMappingURL=index.js.map