mirror of
https://github.com/Foundry376/Mailspring.git
synced 2024-12-29 20:04:59 +08:00
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
This commit is contained in:
parent
c762cef4e6
commit
cac679b119
9 changed files with 67 additions and 21 deletions
|
@ -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 (
|
||||
<div
|
||||
<InjectedComponent
|
||||
ref={Fields.Subject}
|
||||
key="subject-wrap"
|
||||
className="compose-subject-wrap"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
name="subject"
|
||||
ref={Fields.Subject}
|
||||
placeholder="Subject"
|
||||
value={this.props.draft.subject}
|
||||
onChange={this._onChangeSubject}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
matching={{role: 'Composer:SubjectTextField'}}
|
||||
exposedProps={{
|
||||
draft,
|
||||
value: draft.subject,
|
||||
draftClientId: draft.clientId,
|
||||
onSubjectChange: this._onSubjectChange,
|
||||
}}
|
||||
requiredMethods={['focus']}
|
||||
fallback={SubjectTextField}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
_renderFields = () => {
|
||||
|
|
|
@ -192,6 +192,7 @@ export default class ComposerView extends React.Component {
|
|||
return (
|
||||
<InjectedComponent
|
||||
ref={Fields.Body}
|
||||
className="body-field"
|
||||
matching={{role: "Composer:Editor"}}
|
||||
fallback={ComposerEditor}
|
||||
requiredMethods={[
|
||||
|
|
39
internal_packages/composer/lib/subject-text-field.jsx
Normal file
39
internal_packages/composer/lib/subject-text-field.jsx
Normal file
|
@ -0,0 +1,39 @@
|
|||
import React, {Component, PropTypes} from 'react'
|
||||
import {findDOMNode} from 'react-dom'
|
||||
|
||||
|
||||
export default class SubjectTextField extends Component {
|
||||
static displayName = 'SubjectTextField'
|
||||
|
||||
static containerRequired = false
|
||||
|
||||
static propTypes = {
|
||||
value: PropTypes.string,
|
||||
onSubjectChange: PropTypes.func,
|
||||
}
|
||||
|
||||
onInputChange = ({target: {value}}) => {
|
||||
this.props.onSubjectChange(value)
|
||||
}
|
||||
|
||||
focus() {
|
||||
findDOMNode(this.refs.input).focus()
|
||||
}
|
||||
|
||||
render() {
|
||||
const {value} = this.props
|
||||
|
||||
return (
|
||||
<div className="composer-subject subject-field">
|
||||
<input
|
||||
ref="input"
|
||||
type="text"
|
||||
name="subject"
|
||||
placeholder="Subject"
|
||||
value={value}
|
||||
onChange={this.onInputChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -253,7 +253,7 @@ body.platform-win32 {
|
|||
}
|
||||
}
|
||||
|
||||
.compose-subject-wrap {
|
||||
.composer-subject {
|
||||
position: relative;
|
||||
margin: 0 23px;
|
||||
border-bottom: 1px solid @border-color-divider;
|
||||
|
|
|
@ -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()}></div>
|
||||
</KeyCommandsRegion>
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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 = [];
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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'
|
||||
|
|
2
src/pro
2
src/pro
|
@ -1 +1 @@
|
|||
Subproject commit 17af8764f710a944344d39a9f834006fd7a0f3d5
|
||||
Subproject commit 8a3b33001749f74933bff9d800080279935cb768
|
Loading…
Reference in a new issue