@wiris/mathtype-ckeditor5 8.11.0 → 8.12.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/src/plugin.js CHANGED
@@ -1,8 +1,8 @@
1
1
  // CKEditor imports
2
- import { Plugin } from 'ckeditor5/src/core.js';
3
- import { ButtonView } from 'ckeditor5/src/ui.js';
4
- import { ClickObserver, HtmlDataProcessor, XmlDataProcessor, UpcastWriter } from 'ckeditor5/src/engine.js';
5
- import { Widget, toWidget, viewToModelPositionOutsideModelElement } from 'ckeditor5/src/widget.js';
2
+ import { Plugin } from "ckeditor5/src/core.js";
3
+ import { ButtonView } from "ckeditor5/src/ui.js";
4
+ import { ClickObserver, HtmlDataProcessor, XmlDataProcessor, UpcastWriter } from "ckeditor5/src/engine.js";
5
+ import { Widget, toWidget, viewToModelPositionOutsideModelElement } from "ckeditor5/src/widget.js";
6
6
 
7
7
  // MathType API imports
8
8
  import IntegrationModel from "@wiris/mathtype-html-integration-devkit/src/integrationmodel.js";
@@ -91,7 +91,7 @@ export default class MathType extends Plugin {
91
91
  // etc
92
92
 
93
93
  // There are platforms like Drupal that initialize CKEditor but they hide or remove the container element.
94
- // To avoid a wrong behaviour, this integration only starts if the workspace container exists.
94
+ // To avoid a wrong behavior, this integration only starts if the workspace container exists.
95
95
  let integration;
96
96
  if (integrationProperties.target) {
97
97
  // Instance of the integration associated to this editor instance
@@ -201,7 +201,7 @@ export default class MathType extends Plugin {
201
201
 
202
202
  schema.register("mathml", {
203
203
  inheritAllFrom: "$inlineObject",
204
- allowAttributes: ["formula"],
204
+ allowAttributes: ["formula", "htmlContent"],
205
205
  });
206
206
  }
207
207
 
@@ -304,6 +304,59 @@ export default class MathType extends Plugin {
304
304
  }
305
305
  });
306
306
 
307
+ // Data view -> Model
308
+ editor.data.upcastDispatcher.on("element:img", (evt, data, conversionApi) => {
309
+ const { consumable, writer } = conversionApi;
310
+ const { viewItem } = data;
311
+
312
+ // Only upcast when is wiris formula
313
+ if (viewItem.getClassNames().next().value !== "Wirisformula") {
314
+ return;
315
+ }
316
+
317
+ const mathAttributes = [...viewItem.getAttributes()].map(([key, value]) => ` ${key}="${value}"`).join("");
318
+ let htmlContent = Util.htmlSanitize(`<img${mathAttributes}>`);
319
+
320
+ const modelNode = writer.createElement("mathml", { htmlContent });
321
+
322
+ // Find allowed parent for element that we are going to insert.
323
+ // If current parent does not allow to insert element but one of the ancestors does
324
+ // then split nodes to allowed parent.
325
+ const splitResult = conversionApi.splitToAllowedParent(modelNode, data.modelCursor);
326
+
327
+ // When there is no split result it means that we can't insert element to model tree, so let's skip it.
328
+ if (!splitResult) {
329
+ return;
330
+ }
331
+
332
+ // Insert element on allowed position.
333
+ conversionApi.writer.insert(modelNode, splitResult.position);
334
+
335
+ // Consume appropriate value from consumable values list.
336
+ consumable.consume(viewItem, { name: true });
337
+
338
+ const parts = conversionApi.getSplitParts(modelNode);
339
+
340
+ // Set conversion result range.
341
+ data.modelRange = writer.createRange(
342
+ conversionApi.writer.createPositionBefore(modelNode),
343
+ conversionApi.writer.createPositionAfter(parts[parts.length - 1]),
344
+ );
345
+
346
+ // Now we need to check where the `modelCursor` should be.
347
+ if (splitResult.cursorParent) {
348
+ // If we split parent to insert our element then we want to continue conversion in the new part of the split parent.
349
+ //
350
+ // before: <allowed><notAllowed>foo[]</notAllowed></allowed>
351
+ // after: <allowed><notAllowed>foo</notAllowed><converted></converted><notAllowed>[]</notAllowed></allowed>
352
+
353
+ data.modelCursor = conversionApi.writer.createPositionAt(splitResult.cursorParent, 0);
354
+ } else {
355
+ // Otherwise just continue after inserted element.
356
+ data.modelCursor = data.modelRange.end;
357
+ }
358
+ });
359
+
307
360
  /**
308
361
  * Whether the given view <math> element has a LaTeX annotation element.
309
362
  * @param {*} math
@@ -337,13 +390,31 @@ export default class MathType extends Plugin {
337
390
  function createViewImage(modelItem, { writer: viewWriter }) {
338
391
  const htmlDataProcessor = new HtmlDataProcessor(viewWriter.document);
339
392
 
340
- const mathString = modelItem.getAttribute("formula").replaceAll('ref="<"', 'ref="&lt;"');
341
- const imgHtml = Parser.initParse(mathString, integration.getLanguage());
342
- const imgElement = htmlDataProcessor.toView(imgHtml).getChild(0);
393
+ const formula = modelItem.getAttribute("formula");
394
+ const htmlContent = modelItem.getAttribute("htmlContent");
395
+
396
+ if (!formula && !htmlContent) {
397
+ return null;
398
+ }
399
+
400
+ let imgElement = null;
401
+
402
+ if (htmlContent) {
403
+ imgElement = htmlDataProcessor.toView(htmlContent).getChild(0);
404
+ } else if (formula) {
405
+ const mathString = formula.replaceAll('ref="<"', 'ref="&lt;"');
406
+
407
+ const imgHtml = Parser.initParse(mathString, integration.getLanguage());
408
+ imgElement = htmlDataProcessor.toView(imgHtml).getChild(0);
409
+
410
+ // Add HTML element (<img>) to model
411
+ viewWriter.setAttribute("htmlContent", imgHtml, modelItem);
412
+ }
343
413
 
344
414
  /* Although we use the HtmlDataProcessor to obtain the attributes,
345
- we must create a new EmptyElement which is independent of the
346
- DataProcessor being used by this editor instance */
415
+ * we must create a new EmptyElement which is independent of the
416
+ * DataProcessor being used by this editor instance
417
+ */
347
418
  if (imgElement) {
348
419
  return viewWriter.createEmptyElement("img", imgElement.getAttributes(), {
349
420
  renderUnsafeAttributes: ["src"],
@@ -391,7 +462,9 @@ export default class MathType extends Plugin {
391
462
  function createDataString(modelItem, { writer: viewWriter }) {
392
463
  const htmlDataProcessor = new HtmlDataProcessor(viewWriter.document);
393
464
 
394
- let mathString = Parser.endParseSaveMode(modelItem.getAttribute("formula"));
465
+ // Load img element
466
+ let mathString =
467
+ modelItem.getAttribute("htmlContent") || Parser.endParseSaveMode(modelItem.getAttribute("formula"));
395
468
 
396
469
  const sourceMathElement = htmlDataProcessor.toView(mathString).getChild(0);
397
470
 
@@ -414,15 +487,17 @@ export default class MathType extends Plugin {
414
487
  "get",
415
488
  (e) => {
416
489
  let output = e.return;
417
- // This line cleans all the semantics stuff, including the handwritten data points and returns the MathML IF there is any.
418
- // For text or latex formulas, it returns the original output.
419
- e.return = MathML.removeSemantics(output, "application/json");
490
+ const parsedResult = Parser.endParse(output);
491
+
492
+ // Cleans all the semantics tag for safexml
493
+ // including the handwritten data points
494
+ e.return = MathML.removeSafeXMLSemantics(parsedResult);
420
495
  },
421
496
  { priority: "low" },
422
497
  );
423
498
 
424
499
  /**
425
- * Hack to transform <math> with LaTeX into $$LaTeX$$ in editor.setData().
500
+ * Hack to transform <math> with LaTeX into $$LaTeX$$ and formula images in editor.setData().
426
501
  */
427
502
  editor.data.on(
428
503
  "set",
@@ -430,24 +505,29 @@ export default class MathType extends Plugin {
430
505
  // Retrieve the data to be set on the CKEditor.
431
506
  let modifiedData = args[0];
432
507
  // Regex to find all mathml formulas.
433
- const regexp = /<math(.*?)<\/math>/gm;
434
-
435
- // Get all MathML formulas and store them in an array.
436
- // Using the conditional operator on data.main because the data parameter has different types depending on:
437
- // editor.data.set can be used directly or by the source editing plugin.
438
- // With the source editor plugin, data is an object with the key `main` which contains the source code string.
439
- // When using the editor.data.set method, the data is a string with the content to be set to the editor.
440
- let formulas = Object.values(modifiedData)[0]
441
- ? [...Object.values(modifiedData)[0].matchAll(regexp)]
442
- : [...modifiedData.matchAll(regexp)];
443
-
444
- // Loop to find LaTeX formulas and replace the MathML for the LaTeX notation.
508
+ const regexp = /(<img\b[^>]*>)|(<math(.*?)<\/math>)/gm;
509
+ const formulas = [];
510
+ let formula;
511
+
512
+ // Both data.set from the source plugin and console command are taken into account as the data received is MathML or an image containing the MathML.
513
+ while ((formula = regexp.exec(modifiedData)) !== null) {
514
+ formulas.push(formula[0]);
515
+ }
516
+
517
+ // Loop to find LaTeX and formula images and replace the MathML for the both.
445
518
  formulas.forEach((formula) => {
446
- let mathml = formula[0];
447
- if (mathml.includes('encoding="LaTeX"')) {
519
+ if (formula.includes('encoding="LaTeX"')) {
448
520
  // LaTeX found.
449
- let latex = "$$$" + Latex.getLatexFromMathML(mathml) + "$$$"; // We add $$$ instead of $$ because the replace function ignores one $.
450
- modifiedData = modifiedData.replace(mathml, latex);
521
+ let latex = "$$$" + Latex.getLatexFromMathML(formula) + "$$$"; // We add $$$ instead of $$ because the replace function ignores one $.
522
+ modifiedData = modifiedData.replace(formula, latex);
523
+ } else if (formula.includes("<img")) {
524
+ // If we found a formula image, we should find MathML data, and then substitute the entire image.
525
+ const regexp = /«math\b[^»]*»(.*?)«\/math»/g;
526
+ const safexml = formula.match(regexp);
527
+ if (safexml !== null) {
528
+ let decodeXML = MathML.safeXmlDecode(safexml[0]);
529
+ modifiedData = modifiedData.replace(formula, decodeXML);
530
+ }
451
531
  }
452
532
  });
453
533