This commit is contained in:
Ben Gotow 2016-09-22 17:36:06 -07:00
parent 7379952a8f
commit ee49db6a09
10 changed files with 85 additions and 131 deletions

View file

@ -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 += "<br>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)
```

View file

@ -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

View file

@ -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!
}
}

View file

@ -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(`<root>${afterHTML}</root>`);
SpellcheckComposerExtension.applyTransformsToBody({fragment, draft});
expect(fragment.childNodes[0].innerHTML).toEqual(initialHTML);
});
});
});

View file

@ -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) =>
`<!-- ${match} -->`
);
return nextDraft;
}
static unapplyTransformsToDraft = ({draft}) => {
const nextDraft = draft.clone();
nextDraft.body = nextDraft.body.replace(/<!-- (<\/?code[^>]*>) -->/g, (match, node) =>
static unapplyTransformsToBody = ({fragment}) => {
const root = fragment.childNodes[0];
root.innerHTML = root.innerHTML.replace(/<!-- (<\/?code[^>]*>) -->/g, (match, node) =>
node
);
return nextDraft;
}
static onClick({editor, event}) {

View file

@ -180,7 +180,7 @@ describe "ComposerView", ->
describe "empty body warning", ->
it "warns if the body of the email is still the pristine body", ->
pristineBody = "<head></head><body><br><br></body>"
pristineBody = "<br><br>"
useDraft.call @,
to: [u1]

View file

@ -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 .*?data-overlay-id="(.*?)" data-component-props="(.*?)" data-component-key="(.*?)" data-style="(.*?)".*?>.*?<\/overlay>/gmi
}
static _serializedReplacerRe(id) {
return new RegExp(`<overlay .*?data-overlay-id="${id}".*?>.*?<\/overlay>`, 'gim')
}
// https://regex101.com/r/rK3uA3/1
static _anchorExtractRe() {
return /<img .*?data-overlay-id="(.*?)" data-component-props="(.*?)" data-component-key="(.*?)" style="(.*?)".*?>/gmi
}
static _anchorReplacerRe(id) {
return new RegExp(`<img .*?data-overlay-id="${id}".*?>`, 'gim')
}
static *overlayMatches(re, body) {
let result = re.exec(body);
while (result) {
let props = result[2];
props = JSON.parse(props.replace(/&quot;/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 = `<overlay data-overlay-id="${match.dataOverlayId}" data-component-props="${OverlaidComponents.propsToDOMAttr(match.dataComponentProps)}" data-component-key="${match.dataComponentKey}" data-style="${match.dataStyle}">${html}</overlay>`
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;
}
}

View file

@ -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

View file

@ -119,6 +119,9 @@ export function applyExtensionTransformsToBody(draft) {
const range = document.createRange();
const fragment = range.createContextualFragment(`<root>${draft.body}</root>`);
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)
))
}

@ -1 +1 @@
Subproject commit 45756b80645edcedfa7d015dcff8a26bcb521ec0
Subproject commit 3938f5e5834df59da5e9a4a1b6073ba57de51fb0