mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-02-23 07:36:12 +08:00
Summary: Adds several new features to the templates plugin, fixes some existing bugs, and refactors existing code. New Plugin Features/Fixes: - Changes the templates editor in preferences to allow variables to be entered with `{{brackets}}`. Handles many contenteditable complexities to implement. - Better interaction for renaming and deleting of templates in the editor. - Changes tabbing behavior when using templates. Tabbing between variables now wraps around, and typing tab from outside a variable region highlights the closest region. - Prevents "Enter" key in the composer when inside a variable region, and strips all formatting/tags from within the region - this prevents major contenteditable issues that can result in inline CSS in the style of our variable regions, which will not be removed when sending. - Shows a warning when choosing a template if it will replace existing text in a draft. - Prevents invalid characters in template names (due to filenames, esp. on Windows), and shows an error message. Strips these characters from draft titles when making a template. - Fixes a bug where TemplateStore's initialization code was being called multiple times. New N1 code: - Several new methods in `DOMUtils` useful for working with contenteditable. - Implement some missing methods in `Editor` Refactor: - Major refactor/rewrite of template composer extension to use new DOMUtils methods and simplify the logic (while adding new functionality). Remaining issues: - `preferences-tempaltes.cjsx` and `template-editor.coffee` should be rewritten in ES6 for consistency - Need tests for new DOMUtils functions and for new Templates plugin code. Test Plan: manual, need to update specs Reviewers: evan, bengotow Reviewed By: evan, bengotow Subscribers: juan Differential Revision: https://phab.nylas.com/D2382
106 lines
2.9 KiB
JavaScript
106 lines
2.9 KiB
JavaScript
import {Actions, React} from 'nylas-exports';
|
|
import {Popover, Menu, RetinaImg} from 'nylas-component-kit';
|
|
import TemplateStore from './template-store';
|
|
|
|
class TemplatePicker extends React.Component {
|
|
static displayName = 'TemplatePicker';
|
|
|
|
static propTypes = {
|
|
draftClientId: React.PropTypes.string,
|
|
}
|
|
|
|
constructor() {
|
|
super();
|
|
this.state = {
|
|
searchValue: '',
|
|
templates: TemplateStore.items(),
|
|
};
|
|
}
|
|
|
|
componentDidMount() {
|
|
this.unsubscribe = TemplateStore.listen(this._onStoreChange.bind(this));
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
if (this.unsubscribe) this.unsubscribe();
|
|
}
|
|
|
|
static containerStyles = {order: 2};
|
|
|
|
_filteredTemplates(search = this.state.searchValue) {
|
|
const items = TemplateStore.items();
|
|
|
|
if (!search.length) { return items; }
|
|
|
|
return items.filter((t)=> {
|
|
return t.name.toLowerCase().indexOf(search.toLowerCase()) === 0;
|
|
});
|
|
}
|
|
|
|
_onStoreChange() {
|
|
return this.setState({
|
|
templates: this._filteredTemplates(),
|
|
});
|
|
}
|
|
|
|
_onSearchValueChange() {
|
|
const newSearch = event.target.value;
|
|
return this.setState({
|
|
searchValue: newSearch,
|
|
templates: this._filteredTemplates(newSearch),
|
|
});
|
|
}
|
|
|
|
_onChooseTemplate = (template) => {
|
|
Actions.insertTemplateId({templateId: template.id, draftClientId: this.props.draftClientId});
|
|
return this.refs.popover.close();
|
|
}
|
|
|
|
_onManageTemplates = () => {
|
|
return Actions.showTemplates();
|
|
}
|
|
|
|
_onNewTemplate = () => {
|
|
return Actions.createTemplate({draftClientId: this.props.draftClientId});
|
|
}
|
|
|
|
render() {
|
|
const button = (
|
|
<button className="btn btn-toolbar narrow">
|
|
<RetinaImg url="nylas://N1-Composer-Templates/assets/icon-composer-templates@2x.png" mode={RetinaImg.Mode.ContentIsMask}/>
|
|
|
|
<RetinaImg name="icon-composer-dropdown.png" mode={RetinaImg.Mode.ContentIsMask}/>
|
|
</button>
|
|
);
|
|
|
|
const headerComponents = [
|
|
<input type="text"
|
|
tabIndex="1"
|
|
key="textfield"
|
|
className="search"
|
|
value={this.state.searchValue}
|
|
onChange={this._onSearchValueChange}/>,
|
|
];
|
|
|
|
const footerComponents = [
|
|
<div className="item" key="new" onMouseDown={this._onNewTemplate}>Save Draft as Template...</div>,
|
|
<div className="item" key="manage" onMouseDown={this._onManageTemplates}>Open Templates Folder...</div>,
|
|
];
|
|
|
|
return (
|
|
<Popover ref="popover" className="template-picker pull-right" buttonComponent={button}>
|
|
<Menu ref="menu"
|
|
headerComponents={headerComponents}
|
|
footerComponents={footerComponents}
|
|
items={this.state.templates}
|
|
itemKey={ (item)=> item.id }
|
|
itemContent={ (item)=> item.name }
|
|
onSelect={this._onChooseTemplate.bind(this)}
|
|
/>
|
|
</Popover>
|
|
);
|
|
}
|
|
|
|
}
|
|
|
|
export default TemplatePicker;
|