From cac679b119b92e6fa947a699fb43033dfabfecf0 Mon Sep 17 00:00:00 2001 From: Juan Tejada Date: Thu, 12 May 2016 10:47:22 -0700 Subject: [PATCH] feat(mail-merge): Add ability to drop tokens in subject Summary: Adds ability to drop tokens in subject via a custom rendered subject field which renders a contenteditable instead of an input. Decided to completely replace the subject field via injected components for a few resons: - That's the way we are currently extending the functionality of the participant fields, so it keeps the plugin code consistent (at the cost of potentially more code) - Completely replacing the subject for a contenteditable means we hace to do extra work to clean up the html before sending. - Reusing our Contenteditable.cjsx class for the subject is overkill, but using a vanilla contenteditable meant duplicating a bunch of the code in that class if we want to add Test Plan: Unit tests Reviewers: bengotow, evan Reviewed By: evan Differential Revision: https://phab.nylas.com/D2949 --- .../composer/lib/composer-header.jsx | 34 ++++++++-------- .../composer/lib/composer-view.jsx | 1 + .../composer/lib/subject-text-field.jsx | 39 +++++++++++++++++++ .../composer/stylesheets/composer.less | 2 +- .../contenteditable/contenteditable.cjsx | 4 +- .../decorators/listens-to-flux-store.jsx | 4 +- src/components/participants-text-field.jsx | 1 + src/global/nylas-exports.coffee | 1 + src/pro | 2 +- 9 files changed, 67 insertions(+), 21 deletions(-) create mode 100644 internal_packages/composer/lib/subject-text-field.jsx diff --git a/internal_packages/composer/lib/composer-header.jsx b/internal_packages/composer/lib/composer-header.jsx index 7d0dc753f..a23fad8cb 100644 --- a/internal_packages/composer/lib/composer-header.jsx +++ b/internal_packages/composer/lib/composer-header.jsx @@ -3,10 +3,11 @@ import React from 'react'; import ReactDOM from 'react-dom'; import AccountContactField from './account-contact-field'; import {Utils, Actions, AccountStore} from 'nylas-exports'; -import {KeyCommandsRegion, ParticipantsTextField, ListensToFluxStore} from 'nylas-component-kit'; +import {InjectedComponent, KeyCommandsRegion, ParticipantsTextField, ListensToFluxStore} from 'nylas-component-kit'; import CollapsedParticipants from './collapsed-participants'; import ComposerHeaderActions from './composer-header-actions'; +import SubjectTextField from './subject-text-field'; import Fields from './fields'; @@ -118,8 +119,8 @@ export default class ComposerHeader extends React.Component { Actions.draftParticipantsChanged(this.props.draft.clientId, changes); } - _onChangeSubject = (event) => { - this.props.session.changes.add({subject: event.target.value}); + _onSubjectChange = (value) => { + this.props.session.changes.add({subject: value}); } _onFocusInParticipants = () => { @@ -192,21 +193,22 @@ export default class ComposerHeader extends React.Component { if (!this.state.enabledFields.includes(Fields.Subject)) { return false; } + const {draft} = this.props return ( -
- -
- ); + matching={{role: 'Composer:SubjectTextField'}} + exposedProps={{ + draft, + value: draft.subject, + draftClientId: draft.clientId, + onSubjectChange: this._onSubjectChange, + }} + requiredMethods={['focus']} + fallback={SubjectTextField} + /> + ) } _renderFields = () => { diff --git a/internal_packages/composer/lib/composer-view.jsx b/internal_packages/composer/lib/composer-view.jsx index 28c1059e0..7f280a393 100644 --- a/internal_packages/composer/lib/composer-view.jsx +++ b/internal_packages/composer/lib/composer-view.jsx @@ -192,6 +192,7 @@ export default class ComposerView extends React.Component { return ( { + this.props.onSubjectChange(value) + } + + focus() { + findDOMNode(this.refs.input).focus() + } + + render() { + const {value} = this.props + + return ( +
+ +
+ ); + } +} diff --git a/internal_packages/composer/stylesheets/composer.less b/internal_packages/composer/stylesheets/composer.less index 71b74c537..be36b0045 100644 --- a/internal_packages/composer/stylesheets/composer.less +++ b/internal_packages/composer/stylesheets/composer.less @@ -253,7 +253,7 @@ body.platform-win32 { } } - .compose-subject-wrap { + .composer-subject { position: relative; margin: 0 23px; border-bottom: 1px solid @border-color-divider; diff --git a/src/components/contenteditable/contenteditable.cjsx b/src/components/contenteditable/contenteditable.cjsx index d703e57d4..669206dec 100644 --- a/src/components/contenteditable/contenteditable.cjsx +++ b/src/components/contenteditable/contenteditable.cjsx @@ -100,7 +100,7 @@ class Contenteditable extends React.Component editor = new EditorAPI(@_editableNode()) - if not editor.currentSelection().isInScope() + if not editor.currentSelection().isInScope() and extraArgsObj.methodName isnt 'onBlur' @_restoreSelection() argsObj = _.extend(extraArgsObj, {editor}) @@ -202,6 +202,7 @@ class Contenteditable extends React.Component ref="contenteditable" contentEditable spellCheck={false} + placeholder={@props.placeholder} dangerouslySetInnerHTML={__html: @props.value} {...@_eventHandlers()}> @@ -439,6 +440,7 @@ class Contenteditable extends React.Component return if @_inCompositionEvent return if not extension[method]? editingFunction = extension[method].bind(extension) + argsObj = _.extend(argsObj, {methodName: method}) @atomicEdit(editingFunction, argsObj) diff --git a/src/components/decorators/listens-to-flux-store.jsx b/src/components/decorators/listens-to-flux-store.jsx index 52daa001b..32810ac9b 100644 --- a/src/components/decorators/listens-to-flux-store.jsx +++ b/src/components/decorators/listens-to-flux-store.jsx @@ -2,10 +2,10 @@ import React from 'react'; function ListensToFluxStore(ComposedComponent, {stores, getStateFromStores}) { return class extends ComposedComponent { - static displayName = ComposedComponent.displayName; - static containerRequired = false; + static propTypes = {} + constructor(props) { super(props); this._unlisteners = []; diff --git a/src/components/participants-text-field.jsx b/src/components/participants-text-field.jsx index ed1eebf71..fbb73b8ed 100644 --- a/src/components/participants-text-field.jsx +++ b/src/components/participants-text-field.jsx @@ -193,6 +193,7 @@ export default class ParticipantsTextField extends React.Component { ref="textField" matching={{role: 'Composer:ParticipantsTextField'}} fallback={TokenizingTextField} + requiredMethods={['focus']} exposedProps={{ tokens: this.props.participants[this.props.field], tokenKey: (p) => p.email, diff --git a/src/global/nylas-exports.coffee b/src/global/nylas-exports.coffee index 6f62e1c00..cc47b7e34 100644 --- a/src/global/nylas-exports.coffee +++ b/src/global/nylas-exports.coffee @@ -156,6 +156,7 @@ class NylasExports @lazyLoad "DeprecateUtils", 'deprecate-utils' @lazyLoad "VirtualDOMUtils", 'virtual-dom-utils' @lazyLoad "NylasSpellchecker", 'nylas-spellchecker' + @lazyLoad "EditorAPI", 'components/contenteditable/editor-api' # Services @lazyLoad "UndoManager", 'undo-manager' diff --git a/src/pro b/src/pro index 17af8764f..8a3b33001 160000 --- a/src/pro +++ b/src/pro @@ -1 +1 @@ -Subproject commit 17af8764f710a944344d39a9f834006fd7a0f3d5 +Subproject commit 8a3b33001749f74933bff9d800080279935cb768