@wiris/mathtype-froala
Version:
MathType Web for Froala Editor
418 lines (373 loc) • 15.9 kB
JavaScript
import IntegrationModel from "@wiris/mathtype-html-integration-devkit/src/integrationmodel";
import Configuration from "@wiris/mathtype-html-integration-devkit/src/configuration";
import Parser from "@wiris/mathtype-html-integration-devkit/src/parser";
import Constants from "@wiris/mathtype-html-integration-devkit/src/constants";
import MathML from "@wiris/mathtype-html-integration-devkit/src/mathml";
import StringManager from "@wiris/mathtype-html-integration-devkit/src/stringmanager";
import Image from "@wiris/mathtype-html-integration-devkit/src/image";
import packageInfo from "./package.json";
/**
* This property contains all Froala Integration instances.
* @type {Object}
*/
export const instances = {};
/**
* This property contains the current Froala integration instance,
* which is the instance of the active editor.
* @type {IntegrationModel}
*/
export const currentInstance = null;
/**
* Froala integration class.
*/
export class FroalaIntegration extends IntegrationModel {
/**
* IntegrationModel constructor.
* @param {IntegrationModelProperties} froalaModelProperties
*/
constructor(froalaModelProperties) {
super(froalaModelProperties);
/**
* Mode when Froala is instantiated on an image.
* @type {Boolean}
*/
this.initOnImageMode = froalaModelProperties.initOnImageMode;
}
/**
* Returns the language of the current Froala editor instance.
* When no language is set, Froala sets the toolbar to english.
*/
getLanguage() {
// Try to get editorParameters.language, fail silently otherwise
try {
return this.editorParameters.language;
} catch (e) {}
if (this.editorObject.opts.language != null) {
return this.editorObject.opts.language;
}
return super.getLanguage();
}
/** @inheritdoc */
init() {
// On focus in a Froala editor instance, is needed to update the
// Current FroalaIntegration instance.
this.editorObject.events.on(
"focus",
function () {
WirisPlugin.currentInstance = WirisPlugin.instances[this.editorObject.id];
}.bind(this, this.editorObject),
);
// Change the destroy behavior to also destroy the Mathtype instance.
this.editorObject.events.on("destroy", () => {
// Destroy the MathType plugin.
this.destroy();
});
/**
* Update editor parameters.
* The integration could contain an object with editor parameters.
* These parameters have preference over the backend parameters so we need to update them.
*/
const editor = this.editorObject;
if ("wiriseditorparameters" in editor.opts) {
Configuration.update("editorParameters", editor.opts.wiriseditorparameters);
}
// When the data of Froala is retrieved we need to Parse the content.
// So we modify the Froala event 'html.get' to make it parse the content, whenever you retrieve it.
editor.events.on("html.get", (e, editor, html) => Parser.endParse(e)); // eslint-disable-line no-unused-vars
// Adding parse MathML to images after command event to prevent
// lost image formulas.
editor.events.on("commands.after", (cmd) => {
if (cmd === "html") {
if (!editor.codeView.isActive()) {
// Froala transform entities like > to its equivalent ('<' in this case).
// so MathML properties need to be parsed.
const contentMathMLPropertiesEncoded = this.parseMathMLProperties(editor.html.get());
const parsedContent = Parser.initParse(contentMathMLPropertiesEncoded, editor.opts.language);
editor.html.set(parsedContent);
}
}
});
super.init();
// Create dynamic button id.
const mathTypeId = `wirisEditor-${editor.id}`;
const chemTypeId = `wirisChemistry-${editor.id}`;
// Hide MathType toolbar button if is disabled by config.
if (!Configuration.get("editorEnabled")) {
FroalaEditor.ICONS.wirisEditor = null;
FroalaEditor.COMMANDS.wirisEditor = null;
document.getElementById(mathTypeId).classList.add("fr-hidden");
} else {
// Translate the button title
document.getElementById(mathTypeId).title = StringManager.get("insert_math", editor.opts.language);
}
// Hide ChemType toolbar button if is disabled by config.
if (!Configuration.get("chemEnabled")) {
FroalaEditor.ICONS.wirisChemistry = null;
FroalaEditor.COMMANDS.wirisChemistry = null;
document.getElementById(chemTypeId).classList.add("fr-hidden");
} else {
// Translate the button title
document.getElementById(chemTypeId).title = StringManager.get("insert_chem", editor.opts.language);
}
}
/**
* Encodes html entities in mathml properties occurrences inside 'text'.
* @param {String} text - text that can contain mathml elements or not.
* @returns {String} - 'text' with all mathml properties html entity encoded.
*/
parseMathMLProperties(text) {
// eslint-disable-line class-methods-use-this
const mathTagStart = `${Constants.xmlCharacters.tagOpener}math`;
const mathTagEnd = `${Constants.xmlCharacters.tagOpener}/math${Constants.xmlCharacters.tagCloser}`;
let output = "";
let start = text.indexOf(mathTagStart);
let end = 0;
while (start !== -1) {
// Add all the content between the past found mathml and the actual.
output += text.substring(end, start);
// Current mathml end.
end = text.indexOf(mathTagEnd, start);
const mathml = text.substring(start, end);
const mathmlEntitiesParsed = MathML.encodeProperties(mathml);
output += mathmlEntitiesParsed;
// Next mathml first position.
start = text.indexOf(mathTagStart, end);
}
output += text.substring(end, text.length);
return output;
}
/** @inheritdoc */
callbackFunction() {
super.callbackFunction();
// Froala editor uses a clean up process to remove unwanted tags and attributes. We need to avoid this process for Wiris formulas. If we don't do this a loading pup-up will appear with the message "Uploading" that can be exited clicking away and also if the froala version is greater than 4.2.0 the formula will paste 2 times and make every paste behave like this.
this.editorObject.events.on("paste.beforeCleanup", (clipboard_html) => {
//* The regex pattern matches an image tag with the class attribute containing the 'Wirisformula' class.
const regex = /<img[^>]*class\s*=\s*["'].*?\bWirisformula\b.*?["'][^>]*>/g;
// If a Wiris formula is detected, return false to avoid the clean up process.
if (clipboard_html.match(regex)) {
return false;
}
});
this.editorObject.events.on("html.set", function () {
const images = this.el.getElementsByClassName("Wirisformula");
for (const image of images) {
// Froala removes the styles of images upon inserting them
// We need to add the alignment back
Image.fixAfterResize(image);
// Froala malforms data-uri in images.
// We need to rewrite them.
if (Configuration.get("imageFormat") === "svg" && image.src.substr(0, 10) === "data:image") {
const firstPart = image.src.substr(0, 33);
const secondPart = image.src.substr(33, image.src.length);
image.src = firstPart + encodeURIComponent(decodeURIComponent(secondPart));
}
}
});
// Froala editor can be instantiated in images.
if (this.target.tagName.toLowerCase() !== "img") {
const parsedContent = Parser.initParse(this.editorObject.html.get());
this.editorObject.html.set(parsedContent);
}
}
/**
* @inheritdoc
* @param {HTMLElement} element - DOM object target.
*/
doubleClickHandler(element) {
// If the editor is in readOnly mode, don't add the handler
if (this.editorObject.edit.isDisabled()) {
return;
}
super.doubleClickHandler(element);
}
/** @inheritdoc */
openExistingFormulaEditor() {
super.openExistingFormulaEditor();
}
/** @inheritdoc */
openNewFormulaEditor() {
this.core.editionProperties.dbclick = false;
// If it exists a temporal image saved, open the existing formula editor
const image = this.core.editionProperties.temporalImage;
if (
image !== null &&
typeof image !== "undefined" &&
image.classList.contains(WirisPlugin.Configuration.get("imageClassName"))
) {
this.openExistingFormulaEditor();
} else {
super.openNewFormulaEditor();
}
}
/**
* Hides the active Froala popups.
*/
hidePopups() {
this.editorObject.popups.hideAll();
}
/** @inheritdoc */
insertFormula(focusElement, windowTarget, mathml, wirisProperties) {
const obj = super.insertFormula(focusElement, windowTarget, mathml, wirisProperties);
// Due to insertFormula adds an image using pure JavaScript functions,
// it is needed to notify the editorObject that placeholder status has to be updated.
this.editorObject.placeholder.refresh();
this.editorObject.undo.saveStep();
return obj;
}
}
(function (FroalaEditor) {
/**
* This method creates an instance of FroalaIntegration object extending necessary methods
* to integrate the plugin into Froala editor.
* @param {Object} editor - Froala editor object.
*/
function createIntegrationModel(editor) {
// Select target: choose between iframe, div or image.
let target;
if (editor.opts.iframe) {
target = editor.$iframe[0];
} else {
// For div or image HTMLElement.
target = editor.el;
}
const callbackMethodArguments = {};
callbackMethodArguments.editor = editor;
/** @type {integrationModelProperties} */
const froalaIntegrationProperties = {};
froalaIntegrationProperties.target = target;
froalaIntegrationProperties.serviceProviderProperties = {
URI: process.env.SERVICE_PROVIDER_URI,
server: process.env.SERVICE_PROVIDER_SERVER,
};
froalaIntegrationProperties.version = packageInfo.version;
froalaIntegrationProperties.scriptName = "wiris.js";
froalaIntegrationProperties.environment = {};
froalaIntegrationProperties.environment.editor = `Froala${FroalaEditor.VERSION[0]}`;
froalaIntegrationProperties.environment.editorVersion = `${FroalaEditor.VERSION[0]}.x`;
froalaIntegrationProperties.callbackMethodArguments = callbackMethodArguments;
froalaIntegrationProperties.editorObject = editor;
froalaIntegrationProperties.initOnImageMode = target.nodeName.toLowerCase() === "img";
// Updating integration paths if context path is overwritten by editor javascript configuration.
if ("wiriscontextpath" in editor.opts) {
froalaIntegrationProperties.configurationService =
editor.opts.wiriscontextpath + froalaIntegrationProperties.configurationService;
console.warn(
"Deprecated property wiriscontextpath. Use mathTypeParameters on instead.",
editor.opts.wiriscontextpath,
);
}
// Overriding MathType integration parameters.
if ("mathTypeParameters" in editor.opts) {
froalaIntegrationProperties.integrationParameters = editor.opts.mathTypeParameters;
}
const froalaIntegrationInstance = new FroalaIntegration(froalaIntegrationProperties);
froalaIntegrationInstance.init();
froalaIntegrationInstance.listeners.fire("onTargetReady", {});
WirisPlugin.instances[froalaIntegrationInstance.editorObject.id] = froalaIntegrationInstance;
// The last instance as current instance.
WirisPlugin.currentInstance = froalaIntegrationInstance;
}
FroalaEditor.PLUGINS.wiris = function (editor) {
// Init method, here we create the instance of the FroalaIntegration class.
function _init() {
createIntegrationModel(editor);
}
return {
_init,
};
};
// Icon templates for MathType.
FroalaEditor.DefineIconTemplate("wirisplugin", '<i class="icon icon-[NAME]"></i>');
FroalaEditor.DefineIcon("wirisEditor", {
NAME: "mathtype-editor",
template: "wirisplugin",
});
// Register the command for MathType formulas.
FroalaEditor.RegisterCommand("wirisEditor", {
title: "Insert a math equation - MathType",
focus: true,
undo: true,
refreshAfterCallback: true,
callback(editorObject) {
// eslint-disable-line no-unused-vars
const currentFroalaIntegrationInstance = WirisPlugin.instances[this.id];
currentFroalaIntegrationInstance.hidePopups();
currentFroalaIntegrationInstance.core.getCustomEditors().disable();
const imageObject = currentFroalaIntegrationInstance.editorObject.image.get();
currentFroalaIntegrationInstance.core.editionProperties.dbclick = false;
if (
typeof imageObject !== "undefined" &&
imageObject !== null &&
imageObject[0].classList.contains(WirisPlugin.Configuration.get("imageClassName"))
) {
currentFroalaIntegrationInstance.core.editionProperties.temporalImage = imageObject[0];
currentFroalaIntegrationInstance.openExistingFormulaEditor();
} else {
currentFroalaIntegrationInstance.openNewFormulaEditor();
}
},
});
// Prevent Froala to add it's own classes to the images generated with MathType.
FroalaEditor.COMMANDS.wirisEditor.refresh = function ($btn) {
const selectedImage = this.image.get();
// Value can be undefined.
if (selectedImage) {
if (
($btn.parent()[0].hasAttribute("class") &&
$btn.parent()[0].getAttribute("class").indexOf("fr-buttons") === -1) ||
(selectedImage[0] &&
(selectedImage[0].classList.contains(Configuration.get("imageClassName")) ||
// Is a MathType image.
selectedImage.hasClass(Configuration.get("imageClassName"))))
) {
// Show MathType icons if previously were hidden.
$btn.removeClass("fr-hidden");
// Disable resize box.
if (!document.getElementById("wrs_style")) {
// eslint-disable-line no-undef
document
.getElementsByTagName("head")[0]
.append('<style id="wrs_style">.fr-image-resizer {pointer-events: none;}</style>');
}
} else {
// Is a non-MathType image.
// Hide MathType icons.
$btn.addClass("fr-hidden");
// Enable resize box (if it was configured).
if (document.getElementById("wrs_style")) {
document.getElementById("wrs_style").remove();
}
}
}
};
// Template for ChemType.
FroalaEditor.DefineIcon("wirisChemistry", {
NAME: "mathtype-chemistry",
template: "wirisplugin",
});
// Register the command for ChemType formulas.
FroalaEditor.RegisterCommand("wirisChemistry", {
title: "Insert a chemistry formula - ChemType",
focus: true,
undo: true,
refreshAfterCallback: true,
callback() {
const currentFroalaIntegrationInstance = WirisPlugin.instances[this.id];
currentFroalaIntegrationInstance.hidePopups();
currentFroalaIntegrationInstance.core.getCustomEditors().enable("chemistry");
const imageObject = currentFroalaIntegrationInstance.editorObject.image.get();
if (
typeof imageObject !== "undefined" &&
imageObject !== null &&
imageObject[0].classList.contains(WirisPlugin.Configuration.get("imageClassName"))
) {
currentFroalaIntegrationInstance.core.editionProperties.temporalImage = imageObject[0];
currentFroalaIntegrationInstance.openExistingFormulaEditor();
} else {
currentFroalaIntegrationInstance.openNewFormulaEditor();
}
},
});
// Prevent Froala to add it's own classes to the images generated with ChemType.
FroalaEditor.COMMANDS.wirisChemistry.refresh = FroalaEditor.COMMANDS.wirisEditor.refresh;
})(FroalaEditor);