Skip to content
This repository has been archived by the owner on Mar 27, 2023. It is now read-only.

Commit

Permalink
Support rich text in EmailEditor (uses draft-js)
Browse files Browse the repository at this point in the history
  • Loading branch information
vincemtnz committed Apr 12, 2018
1 parent 8a55b9e commit ddd1382
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 29 deletions.
71 changes: 51 additions & 20 deletions app/javascript/components/EmailEditor/EmailEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,24 @@ import Input from '../SweetInput/SweetInput';
import FormGroup from '../Form/FormGroup';
import ErrorMessages from '../ErrorMessages';
import { FormattedMessage } from 'react-intl';
import { compact, get, template, isEqual } from 'lodash';
import { compact, debounce, get, template, isEqual } from 'lodash';
import classnames from 'classnames';
import type { ErrorMap } from '../../util/ChampaignClient/Base';
import {
ContentState,
CompositeDecorator,
convertFromHTML,
Editor,
EditorState,
} from 'draft-js';
import { stateFromHTML } from 'draft-js-import-html';
import { stateToHTML } from 'draft-js-export-html';
import './EmailEditor.scss';

export type EmailProps = {
subject: string,
body: string,
emailBody: string,
};

type Props = {
Expand All @@ -24,14 +34,34 @@ type Props = {
onUpdate: (email: EmailProps) => void,
};

type State = {
subject: string,
header: string,
footer: string,
editorState: EditorState,
};

function interpolateVars(templateString: ?string, templateVars: any): string {
if (!templateString) return '';
return template(templateString)(templateVars);
}
export default class EmailEditor extends Component {
props: Props;
state: EmailProps;
state: State;
constructor(props: Props) {
super(props);
this.state = {
subject: this.interpolateVars(this.props.subject),
body: this.interpolateVars(this.props.body),

this.state = EmailEditor.getDerivedStateFromProps(props);
}

static getDerivedStateFromProps(props: Props): State {
return {
subject: interpolateVars(props.subject, props.templateVars),
header: interpolateVars(props.header, props.templateVars),
footer: interpolateVars(props.footer, props.templateVars),
editorState: EditorState.createWithContent(
stateFromHTML(interpolateVars(props.body, props.templateVars))
),
};
}

Expand All @@ -43,14 +73,19 @@ export default class EmailEditor extends Component {
this.update();
}

shouldComponentUpdate(nextProps: Props) {
return !isEqual(nextProps, this.props);
shouldComponentUpdate(nextProps: Props, nextState: State) {
return (
!isEqual(nextProps.templateVars, this.props.templateVars) ||
this.state.editorState !== nextState.editorState
);
}

body() {
return compact([
this.parse(this.props.header),
this.state.body,
this.interpolateVars(
stateToHTML(this.state.editorState.getCurrentContent())
),
this.parse(this.props.footer),
]).join('\n\n');
}
Expand All @@ -66,24 +101,21 @@ export default class EmailEditor extends Component {
return template(templateString)(this.props.templateVars);
}

update = () => {
update = debounce(() => {
if (typeof this.props.onUpdate === 'function') {
this.props.onUpdate({
subject: this.state.subject,
body: this.body(),
});
}
};
}, 400);

updateSubject = (subject: string) => {
this.setState(s => ({ ...s, subject }), this.update);
};

updateBody = ({ target }: SyntheticEvent) => {
if (target instanceof HTMLTextAreaElement) {
const body = target.value;
this.setState(s => ({ ...s, body }), this.update);
}
onEditorChange = (editorState: EditorState) => {
this.setState({ editorState }, this.update);
};

render() {
Expand All @@ -92,6 +124,7 @@ export default class EmailEditor extends Component {
const bodyClassName = classnames({
'has-error': errors.body && errors.body.length > 0,
});

return (
<div className="EmailEditor">
<FormGroup>
Expand Down Expand Up @@ -121,11 +154,9 @@ export default class EmailEditor extends Component {
dangerouslySetInnerHTML={{ __html: this.parse(header) }}
/>
)}
<textarea
name="email_body"
defaultValue={this.state.body}
onChange={this.updateBody}
maxLength="9999"
<Editor
editorState={this.state.editorState}
onChange={this.onEditorChange}
/>
{footer && (
<div
Expand Down
8 changes: 4 additions & 4 deletions app/javascript/email_tool/EmailToolView.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ import './EmailToolView';
type ChampaignEmailPayload = any;

export interface EmailTarget {
id: string,
title?: string,
name: string,
email: string,
id: string;
title?: string;
name: string;
email: string;
}

type Props = {
Expand Down
11 changes: 7 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,21 @@
"classnames": "^2.2.5",
"clipboard": "^1.7.1",
"dotenv": "^4.0.0",
"draft-js": "^0.10.5",
"draft-js-export-html": "^1.2.0",
"draft-js-import-html": "^1.2.1",
"empty": "^0.10.1",
"enzyme": "^3.3.0",
"enzyme-adapter-react-16": "^1.1.1",
"enzyme-to-json": "^3.3.1",
"enzyme": "^3.3.0",
"faker": "^4.1.0",
"flow-bin": "^0.52.0",
"husky": "^0.14.3",
"ismobilejs": "^0.4.1",
"jest": "^22.3.0",
"jquery": "^3.3.1",
"jquery-ui": "^1.12.1",
"jquery-ujs": "^1.2.2",
"jquery": "^3.3.1",
"js-cookie": "^2.2.0",
"js-yaml": "^3.10.0",
"libphonenumber-js": "^1.0.7",
Expand All @@ -49,14 +52,14 @@
"prettier": "^1.10.2",
"prop-types": "^15.6.0",
"query-string": "^5.0.1",
"react": "^16.2.0",
"react-dom": "^16.2.0",
"react-intl": "^2.4.0",
"react-onclickoutside": "^6.7.1",
"react-redux": "^5.0.6",
"react-select": "^1.2.1",
"react": "^16.2.0",
"redux-thunk": "^2.2.0",
"redux": "^3.7.2",
"redux-thunk": "^2.2.0",
"selectize": "^0.12.4",
"speakingurl": "^14.0.1",
"urijs": "^1.19.0",
Expand Down
41 changes: 40 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2429,6 +2429,37 @@ dotenv@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-4.0.0.tgz#864ef1379aced55ce6f95debecdce179f7a0cd1d"

draft-js-export-html@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/draft-js-export-html/-/draft-js-export-html-1.2.0.tgz#1cbe2b78e1fed74fc29c7cdcbfd7540468eca209"
dependencies:
draft-js-utils "^1.2.0"

draft-js-import-element@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/draft-js-import-element/-/draft-js-import-element-1.2.1.tgz#9a6a56d74690d48d35d8d089564e6d710b4926eb"
dependencies:
draft-js-utils "^1.2.0"
synthetic-dom "^1.2.0"

draft-js-import-html@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/draft-js-import-html/-/draft-js-import-html-1.2.1.tgz#88adb8ce5dbe1a5a777663b1893cee6a35239eaa"
dependencies:
draft-js-import-element "^1.2.1"

draft-js-utils@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/draft-js-utils/-/draft-js-utils-1.2.0.tgz#f5cb23eb167325ffed3d79882fdc317721d2fd12"

draft-js@^0.10.5:
version "0.10.5"
resolved "https://registry.yarnpkg.com/draft-js/-/draft-js-0.10.5.tgz#bfa9beb018fe0533dbb08d6675c371a6b08fa742"
dependencies:
fbjs "^0.8.15"
immutable "~3.7.4"
object-assign "^4.1.0"

duplexify@^3.4.2, duplexify@^3.5.3:
version "3.5.3"
resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.5.3.tgz#8b5818800df92fd0125b27ab896491912858243e"
Expand Down Expand Up @@ -2955,7 +2986,7 @@ fb-watchman@^2.0.0:
dependencies:
bser "^2.0.0"

fbjs@^0.8.16:
fbjs@^0.8.15, fbjs@^0.8.16:
version "0.8.16"
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db"
dependencies:
Expand Down Expand Up @@ -3655,6 +3686,10 @@ iferr@^0.1.5:
version "0.1.5"
resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501"

immutable@~3.7.4:
version "3.7.6"
resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.7.6.tgz#13b4d3cb12befa15482a26fe1b2ebae640071e4b"

import-local@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/import-local/-/import-local-1.0.0.tgz#5e4ffdc03f4fe6c009c6729beb29631c2f8227bc"
Expand Down Expand Up @@ -7716,6 +7751,10 @@ symbol-tree@^3.2.2:
version "3.2.2"
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6"

synthetic-dom@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/synthetic-dom/-/synthetic-dom-1.2.0.tgz#f3589aafe2b5e299f337bb32973a9be42dd5625e"

tapable@^0.2.7:
version "0.2.8"
resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.8.tgz#99372a5c999bf2df160afc0d74bed4f47948cd22"
Expand Down

0 comments on commit ddd1382

Please sign in to comment.