diff --git a/docs/ComposerExtensions.md b/docs/ComposerExtensions.md
index 7ef6d625e..c607fbb4b 100644
--- a/docs/ComposerExtensions.md
+++ b/docs/ComposerExtensions.md
@@ -35,12 +35,9 @@ class ProductsExtension extends ComposerExtension
return ["with the word '#{word}'?"]
return []
- @applyTransformsToDraft: ({draft}) ->
+ @applyTransformsToBody: ({fragment, draft}) ->
if @warningsForSending({draft})
- updated = draft.clone()
- updated.body += "
This email \
- contains competitor's product names \
- or trademarks used in context."
- return updated
- return draft
+ el = document.createElement('p');
+ el.innerText = "This email contains competitor's product names or trademarks used in context."
+ fragment.childNodes[0].appendChild(el)
```
diff --git a/internal_packages/composer-markdown/lib/markdown-composer-extension.coffee b/internal_packages/composer-markdown/lib/markdown-composer-extension.coffee
index f08f00a4a..77245969c 100644
--- a/internal_packages/composer-markdown/lib/markdown-composer-extension.coffee
+++ b/internal_packages/composer-markdown/lib/markdown-composer-extension.coffee
@@ -2,20 +2,18 @@ marked = require 'marked'
Utils = require './utils'
{ComposerExtension} = require 'nylas-exports'
-
rawBodies = {}
class MarkdownComposerExtension extends ComposerExtension
- @applyTransformsToDraft: ({draft}) ->
- nextDraft = draft.clone()
- rawBodies[draft.clientId] = nextDraft.body
- nextDraft.body = marked(Utils.getTextFromHtml(draft.body))
- return nextDraft
+ @applyTransformsToDraft: ({fragment, draft}) ->
+ root = fragment.childNodes[0]
+ rawBodies[draft.clientId] = root.innerHTML
+ root.innerHTML = marked(root.innerText)
- @unapplyTransformsToDraft: ({draft}) ->
- nextDraft = draft.clone()
- nextDraft.body = rawBodies[nextDraft.clientId] ? nextDraft.body
- return nextDraft
+ @unapplyTransformsToDraft: ({fragment, draft}) ->
+ if rawBodies[draft.clientId]
+ root = fragment.childNodes[0]
+ root.innerHTML = rawBodies[draft.clientId]
module.exports = MarkdownComposerExtension
diff --git a/internal_packages/composer-spellcheck/lib/spellcheck-composer-extension.es6 b/internal_packages/composer-spellcheck/lib/spellcheck-composer-extension.es6
index 197bf9dc4..8b1bb6443 100644
--- a/internal_packages/composer-spellcheck/lib/spellcheck-composer-extension.es6
+++ b/internal_packages/composer-spellcheck/lib/spellcheck-composer-extension.es6
@@ -163,11 +163,19 @@ export default class SpellcheckComposerExtension extends ComposerExtension {
});
}
- static applyTransformsToDraft = ({draft}) => {
- const nextDraft = draft.clone();
- nextDraft.body = nextDraft.body.replace(/<\/?spelling[^>]*>/g, '');
- return nextDraft;
+ static applyTransformsToBody = ({fragment}) => {
+ const spellingEls = fragment.querySelectorAll('spelling');
+ for (const spellingEl of Array.from(spellingEls)) {
+ // move contents out of the spelling node, remove the node
+ const parent = spellingEl.parentNode;
+ while (spellingEl.firstChild) {
+ parent.insertBefore(spellingEl.firstChild, spellingEl);
+ }
+ parent.removeChild(spellingEl);
+ }
}
- static unapplyTransformsToDraft = () => 'unnecessary'
+ static unapplyTransformsToBody = () => {
+ // no need to put spelling nodes back!
+ }
}
diff --git a/internal_packages/composer-spellcheck/spec/spellcheck-composer-extension-spec.es6 b/internal_packages/composer-spellcheck/spec/spellcheck-composer-extension-spec.es6
index 79041afcb..7023d0ceb 100644
--- a/internal_packages/composer-spellcheck/spec/spellcheck-composer-extension-spec.es6
+++ b/internal_packages/composer-spellcheck/spec/spellcheck-composer-extension-spec.es6
@@ -8,8 +8,8 @@ import {NylasSpellchecker, Message} from 'nylas-exports';
const initialPath = path.join(__dirname, 'fixtures', 'california-with-misspellings-before.html');
const initialHTML = fs.readFileSync(initialPath).toString();
-const expectedPath = path.join(__dirname, 'fixtures', 'california-with-misspellings-after.html');
-const expectedHTML = fs.readFileSync(expectedPath).toString();
+const afterPath = path.join(__dirname, 'fixtures', 'california-with-misspellings-after.html');
+const afterHTML = fs.readFileSync(afterPath).toString();
describe('SpellcheckComposerExtension', function spellcheckComposerExtension() {
beforeEach(() => {
@@ -30,23 +30,17 @@ describe('SpellcheckComposerExtension', function spellcheckComposerExtension() {
};
SpellcheckComposerExtension.update(editor);
- expect(node.innerHTML).toEqual(expectedHTML);
+ expect(node.innerHTML).toEqual(afterHTML);
});
});
- describe("applyTransformsToDraft", () => {
+ describe("applyTransformsToBody", () => {
it("removes the spelling annotations it inserted", () => {
- const draft = new Message({ body: expectedHTML });
- const out = SpellcheckComposerExtension.applyTransformsToDraft({draft});
- expect(out.body).toEqual(initialHTML);
- });
- });
-
- describe("unapplyTransformsToDraft", () => {
- it("returns the magic no-op option", () => {
- const draft = new Message({ body: expectedHTML });
- const out = SpellcheckComposerExtension.unapplyTransformsToDraft({draft});
- expect(out).toEqual('unnecessary');
+ const draft = new Message({ body: afterHTML });
+ const range = document.createRange();
+ const fragment = range.createContextualFragment(`${afterHTML}`);
+ SpellcheckComposerExtension.applyTransformsToBody({fragment, draft});
+ expect(fragment.childNodes[0].innerHTML).toEqual(initialHTML);
});
});
});
diff --git a/internal_packages/composer-templates/lib/template-composer-extension.es6 b/internal_packages/composer-templates/lib/template-composer-extension.es6
index fad672444..e1bc5ed41 100644
--- a/internal_packages/composer-templates/lib/template-composer-extension.es6
+++ b/internal_packages/composer-templates/lib/template-composer-extension.es6
@@ -10,20 +10,18 @@ export default class TemplatesComposerExtension extends ComposerExtension {
return warnings;
}
- static applyTransformsToDraft = ({draft}) => {
- const nextDraft = draft.clone();
- nextDraft.body = nextDraft.body.replace(/<\/?code[^>]*>/g, (match) =>
+ static applyTransformsToBody = ({fragment}) => {
+ const root = fragment.childNodes[0];
+ root.innerHTML = root.innerHTML.replace(/<\/?code[^>]*>/g, (match) =>
``
);
- return nextDraft;
}
- static unapplyTransformsToDraft = ({draft}) => {
- const nextDraft = draft.clone();
- nextDraft.body = nextDraft.body.replace(//g, (match, node) =>
+ static unapplyTransformsToBody = ({fragment}) => {
+ const root = fragment.childNodes[0];
+ root.innerHTML = root.innerHTML.replace(//g, (match, node) =>
node
);
- return nextDraft;
}
static onClick({editor, event}) {
diff --git a/internal_packages/composer/spec/composer-view-spec.cjsx b/internal_packages/composer/spec/composer-view-spec.cjsx
index cebd6b6aa..7a3a12778 100644
--- a/internal_packages/composer/spec/composer-view-spec.cjsx
+++ b/internal_packages/composer/spec/composer-view-spec.cjsx
@@ -180,7 +180,7 @@ describe "ComposerView", ->
describe "empty body warning", ->
it "warns if the body of the email is still the pristine body", ->
- pristineBody = "
"
+ pristineBody = "
"
useDraft.call @,
to: [u1]
diff --git a/src/components/overlaid-components/overlaid-composer-extension.es6 b/src/components/overlaid-components/overlaid-composer-extension.es6
index e32c3b8a2..7b4ac935d 100644
--- a/src/components/overlaid-components/overlaid-composer-extension.es6
+++ b/src/components/overlaid-components/overlaid-composer-extension.es6
@@ -1,88 +1,37 @@
import React from 'react'
import ReactDOMServer from 'react-dom/server'
import ComposerExtension from '../../extensions/composer-extension'
-// import {ANCHOR_CLASS, IMG_SRC} from './anchor-constants'
import OverlaidComponents from './overlaid-components'
import CustomContenteditableComponents from './custom-contenteditable-components'
export default class OverlaidComposerExtension extends ComposerExtension {
- // https://regex101.com/r/fW6sV3/2
- static _serializedExtractRe() {
- return /.*?<\/overlay>/gmi
- }
-
- static _serializedReplacerRe(id) {
- return new RegExp(`.*?<\/overlay>`, 'gim')
- }
-
- // https://regex101.com/r/rK3uA3/1
- static _anchorExtractRe() {
- return //gmi
- }
-
- static _anchorReplacerRe(id) {
- return new RegExp(``, 'gim')
- }
-
- static *overlayMatches(re, body) {
- let result = re.exec(body);
- while (result) {
- let props = result[2];
- props = JSON.parse(props.replace(/"/g, `"`));
- const data = {
- dataOverlayId: result[1],
- dataComponentProps: props,
- dataComponentKey: result[3],
- dataStyle: result[4],
+ static applyTransformsToBody({fragment, draft}) {
+ const overlayImgEls = Array.from(fragment.querySelector('img[data-overlay-id]'));
+ for (const imgEl of overlayImgEls) {
+ const Component = CustomContenteditableComponents.get(imgEl.dataset.componentKey);
+ if (!Component) {
+ continue;
}
- yield data
- result = re.exec(body);
+
+ const props = Object.assign({draft, isPreview: true}, imgEl.dataset.componentProps);
+ const reactElement = React.createElement(Component, props);
+
+ const overlayEl = document.createElement('overlay');
+ overlayEl.innerHTML = ReactDOMServer.renderToStaticMarkup(reactElement);
+ Object.assign(overlayEl.dataset, imgEl.dataset);
+
+ imgEl.parentNode.replaceChild(overlayEl, imgEl);
}
- return
}
- static applyTransformsToDraft({draft}) {
- const self = OverlaidComposerExtension;
- const outDraft = draft.clone();
- let outBody = outDraft.body;
- const matcher = self.overlayMatches(self._anchorExtractRe(), outDraft.body)
-
- for (const match of matcher) {
- const component = CustomContenteditableComponents.get(match.dataComponentKey);
- if (!component) {
- continue
- }
- const props = Object.assign({draft, isPreview: true}, match.dataComponentProps);
- const el = React.createElement(component, props);
- let html = ReactDOMServer.renderToStaticMarkup(el);
-
- html = `${html}`
-
- outBody = outBody.replace(
- OverlaidComposerExtension._anchorReplacerRe(match.dataOverlayId),
- html
- )
+ static unapplyTransformsToDraft({fragment}) {
+ const overlayEls = Array.from(fragment.querySelector('overlay[data-overlay-id]'));
+ for (const overlayEl of overlayEls) {
+ const {componentKey, componentProps, overlayId, style} = overlayEl.dataset;
+ const {anchorTag} = OverlaidComponents.buildAnchorTag(componentKey, componentProps, overlayId, style);
+ const anchorFragment = document.createRange().createContextualFragment(anchorTag);
+ overlayEl.parentNode.replaceChild(anchorFragment, overlayEl);
}
-
- outDraft.body = outBody;
- return outDraft;
- }
-
- static unapplyTransformsToDraft({draft}) {
- const self = OverlaidComposerExtension;
- const outDraft = draft.clone();
- let outBody = outDraft.body
-
- const matcher = self.overlayMatches(self._serializedExtractRe(), outDraft.body);
-
- for (const match of matcher) {
- const {anchorTag} = OverlaidComponents.buildAnchorTag(match.dataComponentKey, match.dataComponentProps, match.dataOverlayId, match.dataStyle);
-
- outBody = outBody.replace(OverlaidComposerExtension._serializedReplacerRe(match.dataOverlayId), anchorTag)
- }
-
- outDraft.body = outBody;
- return outDraft;
}
}
diff --git a/src/extensions/composer-extension.coffee b/src/extensions/composer-extension.coffee
index 7af5b0c92..a68e4ec92 100644
--- a/src/extensions/composer-extension.coffee
+++ b/src/extensions/composer-extension.coffee
@@ -142,15 +142,15 @@ class ComposerExtension extends ContenteditableExtension
- `draft`: A {Message} the user is about to finish editing.
###
- @applyTransformsToDraft: ({draft}) ->
- return draft
+ @applyTransformsToBody: ({draft, fragment}) ->
+ return
###
Public: unapplyTransformsToDraft should revert the changes made in
`applyTransformsToDraft`. See the documentation for that method for more
information.
###
- @unapplyTransformsToDraft: ({draft}) ->
- return draft
+ @unapplyTransformsToBody: ({draft, fragment}) ->
+ return
module.exports = ComposerExtension
diff --git a/src/flux/stores/draft-helpers.es6 b/src/flux/stores/draft-helpers.es6
index ad386a72b..559cb7e7e 100644
--- a/src/flux/stores/draft-helpers.es6
+++ b/src/flux/stores/draft-helpers.es6
@@ -119,6 +119,9 @@ export function applyExtensionTransformsToBody(draft) {
const range = document.createRange();
const fragment = range.createContextualFragment(`${draft.body}`);
const extensions = ExtensionRegistry.Composer.extensions();
+ console.log('--BEFORE------------------------------');
+ console.log(draft.body);
+ console.log('--------------------------------');
return Promise.each(extensions, (ext) => {
const extApply = ext.applyTransformsToBody;
@@ -133,23 +136,30 @@ export function applyExtensionTransformsToBody(draft) {
});
}).then(() => {
draft.body = fragment.childNodes[0].innerHTML;
+ console.log('--AFTER------------------------------');
+ console.log(draft.body);
+ console.log('--------------------------------');
return draft;
});
}
export function prepareDraftForSyncback(session) {
return session.ensureCorrectAccount({noSyncback: true})
- .then(() => applyExtensionTransformsToDraft(session.draft()))
- .then((transformed) => applyExtensionTransformsToBody(transformed))
+ .then(() =>
+ applyExtensionTransformsToDraft(session.draft()))
+ .then((transformed) =>
+ applyExtensionTransformsToBody(transformed))
.then((transformed) => {
if (!transformed.replyToMessageId || !shouldAppendQuotedText(transformed)) {
- return Promise.resolve(transformed)
+ return Promise.resolve(transformed);
}
- return appendQuotedTextToDraft(transformed)
+ return appendQuotedTextToDraft(transformed);
})
.then((draft) => (
- DatabaseStore.inTransaction((t) => t.persistModel(draft))
- .then(() => Promise.resolve(queueDraftFileUploads(draft)))
- .thenReturn(draft)
+ DatabaseStore.inTransaction((t) =>
+ t.persistModel(draft)
+ ).then(() =>
+ Promise.resolve(queueDraftFileUploads(draft))
+ ).thenReturn(draft)
))
}
diff --git a/src/pro b/src/pro
index 45756b806..3938f5e58 160000
--- a/src/pro
+++ b/src/pro
@@ -1 +1 @@
-Subproject commit 45756b80645edcedfa7d015dcff8a26bcb521ec0
+Subproject commit 3938f5e5834df59da5e9a4a1b6073ba57de51fb0