Mailspring/app/internal_packages/composer-templates/lib/template-editor.es6

71 lines
3 KiB
JavaScript

import {DOMUtils, ContenteditableExtension} from 'nylas-exports';
export default class TemplateEditor extends ContenteditableExtension {
static onContentChanged = ({editor}) => {
// Run through and remove all code nodes that are invalid
const codeNodes = editor.rootNode.querySelectorAll("code.var.empty");
for (let ii = 0; ii < codeNodes.length; ii++) {
const codeNode = codeNodes[ii];
// remove any style that was added by contenteditable
codeNode.removeAttribute("style");
// grab the text content and the indexable text content
const codeNodeText = codeNode.textContent;
const indexText = DOMUtils.getIndexedTextContent(codeNode).map(({text}) => text).join("");
// unwrap any code nodes that don't start/end with {{}}, and any with line breaks inside
if ((!codeNodeText.startsWith("{{")) || (!codeNodeText.endsWith("}}")) || (indexText.indexOf("\n") > -1)) {
editor.whilePreservingSelection(() => {
DOMUtils.unwrapNode(codeNode);
});
}
}
// Attempt to sanitize extra nodes that may have been created by contenteditable on certain text editing
// operations (insertion/deletion of line breaks, etc.). These are generally <span>, but can also be
// <font>, <b>, and possibly others. The extra nodes often grab CSS styles from neighboring elements
// as inline style, including the yellow text from <code> nodes that we insert. This is contenteditable
// trying to be "smart" and preserve styles, which is very undesirable for the <code> node styles. The
// below code is a hack to prevent yellow text from appearing.
const starNodes = editor.rootNode.querySelectorAll("*");
for (let ii = 0; ii < starNodes.length; ii++) {
const node = starNodes[ii];
if ((!node.className) && (node.style.color === "#c79b11")) {
editor.whilePreservingSelection(() => {
DOMUtils.unwrapNode(node);
});
}
}
const fontNodes = editor.rootNode.querySelectorAll("font");
for (let ii = 0; ii < fontNodes.length; ii++) {
const node = fontNodes[ii];
if (node.color === "#c79b11") {
editor.whilePreservingSelection(() => {
DOMUtils.unwrapNode(node);
});
}
}
// Find all {{}} and wrap them in code nodes if they aren't already
// Regex finds any {{ <contents> }} that doesn't contain {, }, or \n
// https://regex101.com/r/jF2oF4/1
for (const range of editor.regExpSelectorAll(/\{\{[^\n{}]*?\}\}/g)) {
if (!DOMUtils.isWrapped(range, "CODE")) {
// Preserve the selection based on text index within the range matched by the regex
const selIndex = editor.getSelectionTextIndex(range);
const codeNode = DOMUtils.wrap(range, "CODE");
codeNode.className = "var empty";
// Sets node contents to just its textContent, strips HTML
codeNode.textContent = codeNode.textContent;
if (selIndex != null) {
editor.restoreSelectionByTextIndex(codeNode, selIndex.startIndex, selIndex.endIndex);
}
}
}
}
}