class EditMathSourceDialog extends XUI.Dialog {
static showDialog(mathNotation, mathSource, readOnly, reference=null) {
return new Promise((resolve, reject) => {
let dialog = new EditMathSourceDialog(mathNotation, mathSource,
readOnly, resolve);
dialog.open("center", reference);
});
}
constructor(mathNotation, mathSource, readOnly, resolve) {
super({ title: `Edit ${mathNotation}`,
movable: true, resizable: true, closeable: true,
template: EditMathSourceDialog.TEMPLATE,
buttons: [ { label: "Cancel", action: "cancelAction" },
{ label: "OK", action: "okAction" } ]});
this._mathSource = mathSource.trim();
this._resolve = resolve;
this._mathArea = this.contentPane.firstElementChild.firstElementChild;
this._mathArea.value = mathSource;
this._mathArea.setSelectionRange(0, 0);
if (readOnly) {
this._mathArea.readOnly = readOnly;
const okButton = this.buttons[1];
okButton.disabled = true;
okButton.classList.add("xui-control-disabled");
}
}
dialogClosed(mathSource) {
this._resolve(mathSource);
}
cancelAction() {
this.close(null);
}
okAction() {
let editedSource = this._mathArea.value.trim();
if (editedSource.length === 0) {
this._mathArea.focus();
return;
}
if (editedSource === this._mathSource) {
// Not changed by user.
editedSource = null;
}
this.close(editedSource);
}
}
EditMathSourceDialog.TEMPLATE = document.createElement("template");
EditMathSourceDialog.TEMPLATE.innerHTML = `<div class="xxe-edmath-pane">
<textarea class="xui-control xxe-edmath-source"
rows="20" cols="70" wrap="off"
autocomplete="off" spellcheck="false"></textarea>
</div>`;
// ===========================================================================
/**
* A custom control containing MathML or TeX/LaTeX math rendered by MathJax.
* <p>This custom control automatically loads
* <a href="https://www.mathjax.org/">MathJax</a> code when needed to.
*/
class MathJaxPane extends HTMLElement {
constructor() {
super();
this._xmlEditor = null;
this._mathNotation = null;
this._mathStartTag = null;
this._mathEndTag = null;
this._mathSource = null;
this._editMathSource = this.editMathSource.bind(this);
this.addEventListener("dblclick", this._editMathSource);
}
get contextualMenuItems() {
return [ { label: `Edit ${this._mathNotation}...`,
cmdName: "_editMathSource()",
enabled: true } ];
}
// -----------------------------------
// editMathSource
// -----------------------------------
editMathSource(event) {
event.preventDefault();
event.stopPropagation();
let view = NodeView.lookupView(this);
if (view === null) {
// Should not happen.
return;
}
const xmlEditor = this._xmlEditor;
const docView = xmlEditor.documentView;
docView.selectNode(view)
.then((selected) => {
if (!selected) {
// Not expected to happen.
return null;
} else {
return EditMathSourceDialog.showDialog(
this._mathNotation, this._mathSource,
!docView.canEdit(), xmlEditor);
}
})
.then((mathSource) => {
if (mathSource === null) {
return CommandResult.CANCELED;
} else {
let pasteParam = "to <?xml version='1.0'?>";
if (this._mathNotation === "MathML") {
// Remove whitespace between XML tags.
pasteParam += mathSource.replaceAll(/>\s+</g, "><");
} else {
pasteParam += this._mathStartTag;
pasteParam += DOMUtil.escapeXML(mathSource);
pasteParam += this._mathEndTag;
}
return docView.executeCommand(EXECUTE_HELPER,
"paste", pasteParam);
}
})
.then((result) => {
if (result === null ||
result.status === COMMAND_RESULT_FAILED ||
result.status === COMMAND_RESULT_STOPPED) {
let msg;
if (result === null) {
msg = `Could not change ${this._mathNotation}.`;
} else {
msg = `Failed to change ${this._mathNotation}:
${result}`;
}
XUI.Alert.showError(msg, xmlEditor);
}
});
// Catch not useful. DocumentView.executeCommand handles errors
// by returning a null Promise.
}
// -----------------------------------------------------------------------
// Custom element
// -----------------------------------------------------------------------
connectedCallback() {
this._xmlEditor = DOMUtil.lookupAncestorByTag(this, "xxe-client");
if (this._xmlEditor === null) {
// Should not happen.
return;
}
this._mathNotation = null;
this._mathStartTag = null;
this._mathEndTag = null;
this._mathSource = null;
const replacedSrc = this.getAttribute("replace");
if (replacedSrc) {
this.removeAttribute("replace"); // No longer useful.
if (replacedSrc.match(/^<([\w]+:)?math\s/)) {
this._mathNotation = "MathML";
this._mathSource = replacedSrc;
} else if (replacedSrc.startsWith("<")) {
this._mathNotation = "TeX/LaTeX math";
if (replacedSrc.endsWith("/>")) {
this._mathStartTag =
replacedSrc.substring(0, replacedSrc.length-2).trim() +
">";
let end = replacedSrc.indexOf(' ', 1);
if (end < 0) {
end = replacedSrc.indexOf('/', 1);
}
if (end > 1) {
this._mathEndTag =
"</" + replacedSrc.substring(1, end) + ">";
this._mathSource = "";
}
} else {
const start = replacedSrc.indexOf('>');
if (start > 0) {
const end = replacedSrc.lastIndexOf("</");
if (end >= start+1) {
this._mathSource = DOMUtil.unescapeXML(
replacedSrc.substring(start+1, end).trim());
this._mathStartTag =
replacedSrc.substring(0, start+1);
this._mathEndTag = replacedSrc.substring(end);
}
}
}
}
}
if (this._mathSource === null) {
// Should not happen.
return;
}
this.setAttribute("title",
`Double-click to edit ${this._mathNotation}.`);
// ---
let addMathJax = true;
if (document.getElementById("xxe-mathjax-script") !== null) {
addMathJax = false;
} else {
const head = document.head;
let child = head.firstElementChild;
while (child !== null) {
if (child.localName === "script") {
const src = child.getAttribute("src");
if (src && src.indexOf("mathjax") > 0) {
addMathJax = true;
break;
}
}
child = child.nextElementSibling;
}
if (addMathJax) {
let script = document.createElement("script");
script.setAttribute("type", "text/javascript");
script.textContent = `MathJax = {
options: { enableMenu: false },
loader: { load: ['[tex]/color'] },
tex: { packages: { '[+]': ['color'] } }
};`;
head.appendChild(script);
script = document.createElement("script");
script.setAttribute("id", "xxe-mathjax-script");
script.setAttribute("type", "text/javascript");
script.setAttribute("src",
"https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js");
head.appendChild(script);
}
}
if (!addMathJax &&
// Test needed because the document may initially contain
// several MathJaxPanes.
window.MathJax.typesetPromise) {
window.MathJax.typesetPromise();
}
}
}
window.customElements.define("xxe-mathjax-pane", MathJaxPane);