mirror of
https://github.com/Foundry376/Mailspring.git
synced 2024-11-14 21:57:55 +08:00
18d294c42f
Summary: We originally didn't do this because creating a DOM tree was loading images. Using range.createContextualFragment seems to do it without the tree ever being attached. Accompanying changes to src/pro are here: https://phab.nylas.com/D3300 https://github.com/nylas/edgehill/compare/bengotow/draft-dom-transformations?expand=1 Also rename applyTransformsToDraft => applyTransformsForSending. Needed a new name because the function signature has changed. AFAIK there are no open source plugins using the old functions. Test Plan: All specs updated Reviewers: evan, juan Reviewed By: evan, juan Differential Revision: https://phab.nylas.com/D3299
113 lines
4.1 KiB
JavaScript
113 lines
4.1 KiB
JavaScript
import {DOMUtils, ComposerExtension} from 'nylas-exports';
|
|
|
|
export default class TemplatesComposerExtension extends ComposerExtension {
|
|
|
|
static warningsForSending({draft}) {
|
|
const warnings = [];
|
|
if (draft.body.search(/<code[^>]*empty[^>]*>/i) > 0) {
|
|
warnings.push('with an empty template area');
|
|
}
|
|
return warnings;
|
|
}
|
|
|
|
static applyTransformsForSending = ({draftBodyRootNode}) => {
|
|
draftBodyRootNode.innerHTML = draftBodyRootNode.innerHTML.replace(/<\/?code[^>]*>/g, (match) =>
|
|
`<!-- ${match} -->`
|
|
);
|
|
}
|
|
|
|
static unapplyTransformsForSending = ({draftBodyRootNode}) => {
|
|
draftBodyRootNode.innerHTML = draftBodyRootNode.innerHTML.replace(/<!-- (<\/?code[^>]*>) -->/g, (match, node) =>
|
|
node
|
|
);
|
|
}
|
|
|
|
static onClick({editor, event}) {
|
|
const node = event.target;
|
|
if (node.nodeName === 'CODE' && node.classList.contains('var') && node.classList.contains('empty')) {
|
|
editor.selectAllChildren(node);
|
|
}
|
|
}
|
|
|
|
static onKeyDown({editor, event}) {
|
|
const editableNode = editor.rootNode;
|
|
if (event.key === 'Tab') {
|
|
const nodes = editableNode.querySelectorAll('code.var');
|
|
if (nodes.length > 0) {
|
|
const sel = editor.currentSelection();
|
|
let found = false;
|
|
|
|
// First, try to find a <code> that the selection is within. If found,
|
|
// select the next/prev node if the selection ends at the end of the
|
|
// <code>'s text, otherwise select the <code>'s contents.
|
|
for (let i = 0; i < nodes.length; i++) {
|
|
const node = nodes[i];
|
|
if (DOMUtils.selectionIsWithin(node)) {
|
|
const selIndex = editor.getSelectionTextIndex(node);
|
|
const length = DOMUtils.getIndexedTextContent(node).slice(-1)[0].end;
|
|
let nextIndex = i;
|
|
if (selIndex.endIndex === length) {
|
|
nextIndex = event.shiftKey ? i - 1 : i + 1;
|
|
}
|
|
nextIndex = (nextIndex + nodes.length) % nodes.length; // allow wraparound in both directions
|
|
sel.selectAllChildren(nodes[nextIndex]);
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If we failed to find a <code> that the selection is within, select the
|
|
// nearest <code> before/after the selection (depending on shift).
|
|
if (!found) {
|
|
const treeWalker = document.createTreeWalker(editableNode, NodeFilter.SHOW_ELEMENT + NodeFilter.SHOW_TEXT);
|
|
let curIndex = 0;
|
|
let nextIndex = null;
|
|
let node = treeWalker.nextNode();
|
|
while (node) {
|
|
if (sel.anchorNode === node || sel.focusNode === node) break;
|
|
if (node.nodeName === 'CODE' && node.classList.contains('var')) curIndex++;
|
|
node = treeWalker.nextNode();
|
|
}
|
|
nextIndex = event.shiftKey ? curIndex - 1 : curIndex;
|
|
nextIndex = (nextIndex + nodes.length) % nodes.length; // allow wraparound in both directions
|
|
sel.selectAllChildren(nodes[nextIndex]);
|
|
}
|
|
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
}
|
|
} else if (event.key === 'Enter') {
|
|
const nodes = editableNode.querySelectorAll('code.var');
|
|
for (let i = 0; i < nodes.length; i++) {
|
|
if (DOMUtils.selectionStartsOrEndsIn(nodes[i])) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static onContentChanged({editor}) {
|
|
const editableNode = editor.rootNode;
|
|
const selection = editor.currentSelection().rawSelection;
|
|
const isWithinNode = (node) => {
|
|
let test = selection.baseNode;
|
|
while (test !== editableNode) {
|
|
if (test === node) { return true; }
|
|
test = test.parentNode;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
const codeTags = editableNode.querySelectorAll('code.var.empty');
|
|
for (let i = 0, codeTag; i < codeTags.length; i++) {
|
|
codeTag = codeTags[i];
|
|
// sets node contents to just its textContent, strips HTML
|
|
codeTag.textContent = codeTag.textContent;
|
|
if (selection.containsNode(codeTag) || isWithinNode(codeTag)) {
|
|
codeTag.classList.remove('empty');
|
|
}
|
|
}
|
|
}
|
|
}
|