diff --git a/build/config/eslint.json b/build/config/eslint.json index 0065fbe02..ee23d4ebf 100644 --- a/build/config/eslint.json +++ b/build/config/eslint.json @@ -4,7 +4,8 @@ "NylasEnv": false, "$n": false, "waitsForPromise": false, - "advanceClock": false + "advanceClock": false, + "TEST_ACCOUNT_ID": false }, "env": { "browser": true, diff --git a/internal_packages/composer-signature/lib/main.es6 b/internal_packages/composer-signature/lib/main.es6 index f8b9b876f..5aaa35a7e 100644 --- a/internal_packages/composer-signature/lib/main.es6 +++ b/internal_packages/composer-signature/lib/main.es6 @@ -13,15 +13,13 @@ export function activate() { ExtensionRegistry.Composer.register(SignatureComposerExtension); PreferencesUIStore.registerPreferencesTab(this.preferencesTab); - - this.signatureStore = new SignatureStore(); - this.signatureStore.activate(); + SignatureStore.activate(); } export function deactivate() { ExtensionRegistry.Composer.unregister(SignatureComposerExtension); PreferencesUIStore.unregisterPreferencesTab(this.preferencesTab.sectionId); - this.signatureStore.deactivate(); + SignatureStore.deactivate(); } export function serialize() { diff --git a/internal_packages/composer-signature/lib/preferences-signatures.cjsx b/internal_packages/composer-signature/lib/preferences-signatures.cjsx deleted file mode 100644 index 78f84cda1..000000000 --- a/internal_packages/composer-signature/lib/preferences-signatures.cjsx +++ /dev/null @@ -1,118 +0,0 @@ -React = require 'react' -_ = require 'underscore' -{Contenteditable, RetinaImg, Flexbox} = require 'nylas-component-kit' -{AccountStore, Utils} = require 'nylas-exports' - -class PreferencesSignatures extends React.Component - @displayName: 'PreferencesSignatures' - - constructor: (@props) -> - @_signatureSaveQueue = {} - - # TODO check initally selected account - selectedAccountId = AccountStore.accounts()[0].id - if selectedAccountId - key = @_configKey(selectedAccountId) - initialSig = @props.config.get(key) - else - initialSig = "" - - @state = - editAsHTML: false - accounts: AccountStore.accounts() - currentSignature: initialSig - selectedAccountId: selectedAccountId - - componentDidMount: -> - @usub = AccountStore.listen @_onChange - - componentWillUnmount: -> - @usub() - @_saveSignatureNow(@state.selectedAccountId, @state.currentSignature) - - _saveSignatureNow: (accountId, value) => - key = @_configKey(accountId) - @props.config.set(key, value) - - _saveSignatureSoon: (accountId, value) => - @_signatureSaveQueue[accountId] = value - @_saveSignaturesFromCache() - - __saveSignaturesFromCache: => - for accountId, value of @_signatureSaveQueue - @_saveSignatureNow(accountId, value) - - @_signatureSaveQueue = {} - - _saveSignaturesFromCache: _.debounce(PreferencesSignatures::__saveSignaturesFromCache, 500) - - _onChange: => - @setState @_getStateFromStores() - - _getStateFromStores: -> - accounts = AccountStore.accounts() - selectedAccountId = @state.selectedAccountId - currentSignature = @state.currentSignature - if not @state.selectedAccountId in _.pluck(accounts, "id") - selectedAccountId = null - currentSignature = "" - return {accounts, selectedAccountId, currentSignature} - - _renderAccountPicker: -> - options = @state.accounts.map (account) -> - {account.emailAddress} - - - {options} - - - _renderEditableSignature: -> - - - _renderHTMLSignature: -> - - - _onEditSignature: (event) => - html = event.target.value - @setState currentSignature: html - @_saveSignatureSoon(@state.selectedAccountId, html) - - _configKey: (accountId) -> - "nylas.account-#{accountId}.signature" - - _onSelectAccount: (event) => - @_saveSignatureNow(@state.selectedAccountId, @state.currentSignature) - selectedAccountId = event.target.value - key = @_configKey(selectedAccountId) - initialSig = @props.config.get(key) ? "" - @setState - currentSignature: initialSig - selectedAccountId: selectedAccountId - - _renderModeToggle: -> - if @state.editAsHTML - return @setState(editAsHTML: false); return}>Edit live preview - else - return @setState(editAsHTML: true); return}>Edit raw HTML - - render: => - rawText = if @state.editAsHTML then "Raw HTML " else "" - - Signatures - - {rawText}Signature for: {@_renderAccountPicker()} - - - {if @state.editAsHTML then @_renderHTMLSignature() else @_renderEditableSignature()} - - {@_renderModeToggle()} - - -module.exports = PreferencesSignatures diff --git a/internal_packages/composer-signature/lib/preferences-signatures.jsx b/internal_packages/composer-signature/lib/preferences-signatures.jsx new file mode 100644 index 000000000..5f50b11da --- /dev/null +++ b/internal_packages/composer-signature/lib/preferences-signatures.jsx @@ -0,0 +1,122 @@ +import React from 'react'; +import {Contenteditable} from 'nylas-component-kit'; +import {AccountStore} from 'nylas-exports'; +import SignatureStore from './signature-store'; +import SignatureActions from './signature-actions'; + +export default class PreferencesSignatures extends React.Component { + static displayName = 'PreferencesSignatures'; + + constructor(props) { + super(props); + this.state = this._getStateFromStores(); + } + + componentDidMount() { + this.usub = AccountStore.listen(this._onChange); + } + + componentWillUnmount() { + this.usub(); + } + + _onChange = () => { + this.setState(this._getStateFromStores()); + } + + _getStateFromStores() { + const accounts = AccountStore.accounts(); + const state = this.state || {}; + + let {currentAccountId} = state; + if (!accounts.find(acct => acct.id === currentAccountId)) { + currentAccountId = accounts[0].id; + } + return { + accounts, + currentAccountId, + currentSignature: SignatureStore.signatureForAccountId(currentAccountId), + editAsHTML: state.editAsHTML, + }; + } + + _renderAccountPicker() { + const options = this.state.accounts.map(account => + {account.emailAddress} + ); + + return ( + + {options} + + ); + } + + _renderEditableSignature() { + return ( + + ); + } + + _renderHTMLSignature() { + return ( + + ); + } + + _onEditSignature = (event) => { + const html = event.target.value; + this.setState({currentSignature: html}); + + SignatureActions.setSignatureForAccountId({ + accountId: this.state.currentAccountId, + signature: html, + }); + } + + _onSelectAccount = (event) => { + const accountId = event.target.value; + this.setState({ + currentSignature: SignatureStore.signatureForAccountId(accountId), + currentAccountId: accountId, + }); + } + + _renderModeToggle() { + const label = this.state.editAsHTML ? "Edit live preview" : "Edit raw HTML"; + const action = () => { + this.setState({editAsHTML: !this.state.editAsHTML}); + return; + }; + + return ( + {label} + ); + } + + render() { + const rawText = this.state.editAsHTML ? "Raw HTML " : ""; + return ( + + Signatures + + {rawText}Signature for: {this._renderAccountPicker()} + + + {this.state.editAsHTML ? this._renderHTMLSignature() : this._renderEditableSignature()} + + {this._renderModeToggle()} + + ) + } +} diff --git a/internal_packages/composer-signature/lib/signature-actions.es6 b/internal_packages/composer-signature/lib/signature-actions.es6 new file mode 100644 index 000000000..03578901b --- /dev/null +++ b/internal_packages/composer-signature/lib/signature-actions.es6 @@ -0,0 +1,12 @@ +import Reflux from 'reflux'; + +const ActionNames = [ + 'setSignatureForAccountId', +]; + +const Actions = Reflux.createActions(ActionNames); +ActionNames.forEach((name) => { + Actions[name].sync = true; +}); + +export default Actions; diff --git a/internal_packages/composer-signature/lib/signature-composer-extension.es6 b/internal_packages/composer-signature/lib/signature-composer-extension.es6 index 1d644b77f..044985208 100644 --- a/internal_packages/composer-signature/lib/signature-composer-extension.es6 +++ b/internal_packages/composer-signature/lib/signature-composer-extension.es6 @@ -1,10 +1,11 @@ import {ComposerExtension} from 'nylas-exports'; import SignatureUtils from './signature-utils'; +import SignatureStore from './signature-store'; export default class SignatureComposerExtension extends ComposerExtension { - static prepareNewDraft = ({draft})=> { + static prepareNewDraft = ({draft}) => { const accountId = draft.accountId; - const signature = NylasEnv.config.get(`nylas.account-${accountId}.signature`); + const signature = SignatureStore.signatureForAccountId(accountId); if (!signature) { return; } diff --git a/internal_packages/composer-signature/lib/signature-store.es6 b/internal_packages/composer-signature/lib/signature-store.es6 index f767c89d4..71c4ea28e 100644 --- a/internal_packages/composer-signature/lib/signature-store.es6 +++ b/internal_packages/composer-signature/lib/signature-store.es6 @@ -1,29 +1,57 @@ import {DraftStore, AccountStore, Actions} from 'nylas-exports'; import SignatureUtils from './signature-utils'; +import SignatureActions from './signature-actions'; -export default class SignatureStore { + +class SignatureStore { + + DefaultSignature = "Sent from Nylas N1, the extensible, open source mail client."; constructor() { - this.unsubscribe = ()=> {}; + this.unsubscribes = []; } activate() { - this.unsubscribe = Actions.draftParticipantsChanged.listen(this.onParticipantsChanged); + this.unsubscribes.push( + SignatureActions.setSignatureForAccountId.listen(this._onSetSignatureForAccountId) + ); + this.unsubscribes.push( + Actions.draftParticipantsChanged.listen(this._onParticipantsChanged) + ); } - onParticipantsChanged(draftClientId, changes) { + deactivate() { + this.unsubscribes.forEach(unsub => unsub()); + } + + signatureForAccountId(accountId) { + if (!accountId) { + return this.DefaultSignature; + } + const saved = NylasEnv.config.get(`nylas.account-${accountId}.signature`); + if (saved === undefined) { + return this.DefaultSignature; + } + return saved; + } + + _onParticipantsChanged = (draftClientId, changes) => { if (!changes.from) { return; } - DraftStore.sessionForClientId(draftClientId).then((session)=> { + + DraftStore.sessionForClientId(draftClientId).then((session) => { const draft = session.draft(); const {accountId} = AccountStore.accountForEmail(changes.from[0].email); - const signature = NylasEnv.config.get(`nylas.account-${accountId}.signature`) || ""; + const signature = this.signatureForAccountId(accountId); const body = SignatureUtils.applySignature(draft.body, signature); session.changes.add({body}); }); } - deactivate() { - this.unsubscribe(); + _onSetSignatureForAccountId = ({signature, accountId}) => { + // NylasEnv.config.set is internally debounced 100ms + NylasEnv.config.set(`nylas.account-${accountId}.signature`, signature) } } + +export default new SignatureStore(); diff --git a/internal_packages/composer-signature/spec/signature-composer-extension-spec.es6 b/internal_packages/composer-signature/spec/signature-composer-extension-spec.es6 index 3059c561f..464a9a947 100644 --- a/internal_packages/composer-signature/spec/signature-composer-extension-spec.es6 +++ b/internal_packages/composer-signature/spec/signature-composer-extension-spec.es6 @@ -1,5 +1,6 @@ import {Message} from 'nylas-exports'; import SignatureComposerExtension from '../lib/signature-composer-extension'; +import SignatureStore from '../lib/signature-store'; describe("SignatureComposerExtension", ()=> { describe("prepareNewDraft", ()=> { @@ -12,10 +13,12 @@ describe("SignatureComposerExtension", ()=> { it("should insert the signature at the end of the message or before the first blockquote and have a newline", ()=> { const a = new Message({ draft: true, + accountId: TEST_ACCOUNT_ID, body: 'This is a test! Hello world', }); const b = new Message({ draft: true, + accountId: TEST_ACCOUNT_ID, body: 'This is a another test.', }); @@ -50,25 +53,47 @@ describe("SignatureComposerExtension", ()=> { ] scenarios.forEach((scenario)=> { - const message = new Message({draft: true, body: scenario.body}) + const message = new Message({ + draft: true, + body: scenario.body, + accountId: TEST_ACCOUNT_ID, + }) SignatureComposerExtension.prepareNewDraft({draft: message}); expect(message.body).toEqual(scenario.expected) }) }); }); - describe("when a signature is not defined", ()=> { + describe("when no signature is present in the config file", ()=> { beforeEach(()=> { - spyOn(NylasEnv.config, 'get').andCallFake(()=> null); + spyOn(NylasEnv.config, 'get').andCallFake(()=> undefined); }); - it("should not do anything", ()=> { + it("should insert the default signature", ()=> { const a = new Message({ draft: true, + accountId: TEST_ACCOUNT_ID, body: 'This is a test! Hello world', }); SignatureComposerExtension.prepareNewDraft({draft: a}); - expect(a.body).toEqual('This is a test! Hello world'); + expect(a.body).toEqual(`This is a test! ${SignatureStore.DefaultSignature}Hello world`); + }); + }); + + + describe("when a blank signature is present in the config file", ()=> { + beforeEach(()=> { + spyOn(NylasEnv.config, 'get').andCallFake(()=> ""); + }); + + it("should insert nothing", ()=> { + const a = new Message({ + draft: true, + accountId: TEST_ACCOUNT_ID, + body: 'This is a test! Hello world', + }); + SignatureComposerExtension.prepareNewDraft({draft: a}); + expect(a.body).toEqual(`This is a test! Hello world`); }); }); });
Hello world