mirror of
https://github.com/Foundry376/Mailspring.git
synced 2024-11-13 21:24:58 +08:00
394 lines
11 KiB
JavaScript
394 lines
11 KiB
JavaScript
import _ from 'underscore';
|
|
import { Contenteditable, RetinaImg } from 'mailspring-component-kit';
|
|
import { React } from 'mailspring-exports';
|
|
|
|
import TemplateStore from './template-store';
|
|
import TemplateEditor from './template-editor';
|
|
|
|
class PreferencesTemplates extends React.Component {
|
|
static displayName = 'PreferencesTemplates';
|
|
|
|
constructor() {
|
|
super();
|
|
this._templateSaveQueue = {};
|
|
|
|
const { templates, selectedTemplate, selectedTemplateName } = this._getStateFromStores();
|
|
this.state = {
|
|
editAsHTML: false,
|
|
editState: templates.length === 0 ? 'new' : null,
|
|
templates: templates,
|
|
selectedTemplate: selectedTemplate,
|
|
selectedTemplateName: selectedTemplateName,
|
|
contents: null,
|
|
};
|
|
}
|
|
|
|
componentDidMount() {
|
|
this.unsub = TemplateStore.listen(this._onChange);
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
this.unsub();
|
|
if (this.state.selectedTemplate) {
|
|
this._saveTemplateNow(this.state.selectedTemplate.name, this.state.contents);
|
|
}
|
|
}
|
|
|
|
// SAVING AND LOADING TEMPLATES
|
|
_loadTemplateContents = template => {
|
|
if (template) {
|
|
TemplateStore.getTemplateContents(template.id, contents => {
|
|
this.setState({ contents: contents });
|
|
});
|
|
}
|
|
};
|
|
|
|
_saveTemplateNow(name, contents, callback) {
|
|
TemplateStore.saveTemplate(name, contents, callback);
|
|
}
|
|
|
|
_saveTemplateSoon(name, contents) {
|
|
this._templateSaveQueue[name] = contents;
|
|
this._saveTemplatesFromCache();
|
|
}
|
|
|
|
__saveTemplatesFromCache() {
|
|
for (const name of Object.keys(this._templateSaveQueue)) {
|
|
this._saveTemplateNow(name, this._templateSaveQueue[name]);
|
|
}
|
|
this._templateSaveQueue = {};
|
|
}
|
|
|
|
_saveTemplatesFromCache = _.debounce(
|
|
PreferencesTemplates.prototype.__saveTemplatesFromCache,
|
|
500
|
|
);
|
|
|
|
// OVERALL STATE HANDLING
|
|
_onChange = () => {
|
|
this.setState(this._getStateFromStores());
|
|
};
|
|
|
|
_getStateFromStores() {
|
|
const templates = TemplateStore.items();
|
|
let selectedTemplate = this.state ? this.state.selectedTemplate : null;
|
|
if (selectedTemplate && !_.pluck(templates, 'id').includes(selectedTemplate.id)) {
|
|
selectedTemplate = null;
|
|
} else if (!selectedTemplate) {
|
|
selectedTemplate = templates.length > 0 ? templates[0] : null;
|
|
}
|
|
this._loadTemplateContents(selectedTemplate);
|
|
let selectedTemplateName = null;
|
|
if (selectedTemplate) {
|
|
selectedTemplateName = this.state ? this.state.selectedTemplateName : selectedTemplate.name;
|
|
}
|
|
return { templates, selectedTemplate, selectedTemplateName };
|
|
}
|
|
|
|
// TEMPLATE CONTENT EDITING
|
|
_onEditTemplate = event => {
|
|
const html = event.target.value;
|
|
this.setState({ contents: html });
|
|
if (this.state.selectedTemplate) {
|
|
this._saveTemplateSoon(this.state.selectedTemplate.name, html);
|
|
}
|
|
};
|
|
|
|
_onSelectTemplate = event => {
|
|
if (this.state.selectedTemplate) {
|
|
this._saveTemplateNow(this.state.selectedTemplate.name, this.state.contents);
|
|
}
|
|
|
|
const selectedId = event.target.value;
|
|
const selectedTemplate = this.state.templates.find(template => template.id === selectedId);
|
|
|
|
this.setState({
|
|
selectedTemplate: selectedTemplate,
|
|
selectedTemplateName: selectedTemplate ? selectedTemplate.name : null,
|
|
contents: null,
|
|
});
|
|
this._loadTemplateContents(selectedTemplate);
|
|
};
|
|
|
|
_renderTemplatePicker() {
|
|
const options = this.state.templates.map(template => {
|
|
return (
|
|
<option value={template.id} key={template.id}>
|
|
{template.name}
|
|
</option>
|
|
);
|
|
});
|
|
|
|
return (
|
|
<select
|
|
value={this.state.selectedTemplate ? this.state.selectedTemplate.id : null}
|
|
onChange={this._onSelectTemplate}
|
|
>
|
|
{options}
|
|
</select>
|
|
);
|
|
}
|
|
|
|
_renderEditableTemplate() {
|
|
return (
|
|
<Contenteditable
|
|
value={this.state.contents || ''}
|
|
onChange={this._onEditTemplate}
|
|
extensions={[TemplateEditor]}
|
|
spellcheck={false}
|
|
/>
|
|
);
|
|
}
|
|
|
|
_renderHTMLTemplate() {
|
|
return <textarea value={this.state.contents || ''} onChange={this._onEditTemplate} />;
|
|
}
|
|
|
|
_renderModeToggle() {
|
|
if (this.state.editAsHTML) {
|
|
return (
|
|
<a
|
|
onClick={() => {
|
|
this.setState({ editAsHTML: false });
|
|
}}
|
|
>
|
|
Edit live preview
|
|
</a>
|
|
);
|
|
}
|
|
return (
|
|
<a
|
|
onClick={() => {
|
|
this.setState({ editAsHTML: true });
|
|
}}
|
|
>
|
|
Edit raw HTML
|
|
</a>
|
|
);
|
|
}
|
|
|
|
_onEnter(action) {
|
|
return event => {
|
|
if (event.key === 'Enter') {
|
|
action();
|
|
}
|
|
};
|
|
}
|
|
|
|
// TEMPLATE NAME EDITING
|
|
_renderEditName() {
|
|
return (
|
|
<div className="section-title">
|
|
Template Name:{' '}
|
|
<input
|
|
type="text"
|
|
className="template-name-input"
|
|
value={this.state.selectedTemplateName}
|
|
onChange={this._onEditName}
|
|
onKeyDown={this._onEnter(this._saveName)}
|
|
/>
|
|
<button className="btn template-name-btn" onClick={this._saveName}>
|
|
Save Name
|
|
</button>
|
|
<button className="btn template-name-btn" onClick={this._cancelEditName}>
|
|
Cancel
|
|
</button>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
_renderName() {
|
|
const rawText = this.state.editAsHTML ? 'Raw HTML ' : '';
|
|
return (
|
|
<div className="section-title">
|
|
{rawText}Template: {this._renderTemplatePicker()}
|
|
<button
|
|
className="btn template-name-btn"
|
|
title="New template"
|
|
onClick={this._startNewTemplate}
|
|
>
|
|
New
|
|
</button>
|
|
<button
|
|
className="btn template-name-btn"
|
|
onClick={() => {
|
|
this.setState({ editState: 'name' });
|
|
}}
|
|
>
|
|
Rename
|
|
</button>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
_onEditName = event => {
|
|
this.setState({ selectedTemplateName: event.target.value });
|
|
};
|
|
|
|
_cancelEditName = () => {
|
|
this.setState({
|
|
selectedTemplateName: this.state.selectedTemplate ? this.state.selectedTemplate.name : null,
|
|
editState: null,
|
|
});
|
|
};
|
|
|
|
_saveName = () => {
|
|
if (
|
|
this.state.selectedTemplate &&
|
|
this.state.selectedTemplate.name !== this.state.selectedTemplateName
|
|
) {
|
|
TemplateStore.renameTemplate(
|
|
this.state.selectedTemplate.name,
|
|
this.state.selectedTemplateName,
|
|
renamedTemplate => {
|
|
this.setState({
|
|
selectedTemplate: renamedTemplate,
|
|
editState: null,
|
|
});
|
|
}
|
|
);
|
|
} else {
|
|
this.setState({
|
|
editState: null,
|
|
});
|
|
}
|
|
};
|
|
|
|
// DELETE AND NEW
|
|
_deleteTemplate = () => {
|
|
const numTemplates = this.state.templates.length;
|
|
if (this.state.selectedTemplate) {
|
|
TemplateStore.deleteTemplate(this.state.selectedTemplate.name);
|
|
}
|
|
if (numTemplates === 1) {
|
|
this.setState({
|
|
editState: 'new',
|
|
selectedTemplate: null,
|
|
selectedTemplateName: '',
|
|
contents: '',
|
|
});
|
|
}
|
|
};
|
|
|
|
_startNewTemplate = () => {
|
|
this.setState({
|
|
editState: 'new',
|
|
selectedTemplate: null,
|
|
selectedTemplateName: '',
|
|
contents: '',
|
|
});
|
|
};
|
|
|
|
_saveNewTemplate = () => {
|
|
this.setState({ contents: '' });
|
|
TemplateStore.saveNewTemplate(this.state.selectedTemplateName, '', template => {
|
|
this.setState({
|
|
selectedTemplate: template,
|
|
editState: null,
|
|
});
|
|
});
|
|
};
|
|
|
|
_cancelNewTemplate = () => {
|
|
const template = this.state.templates.length > 0 ? this.state.templates[0] : null;
|
|
this.setState({
|
|
selectedTemplate: template,
|
|
selectedTemplateName: template ? template.name : null,
|
|
editState: null,
|
|
});
|
|
this._loadTemplateContents(template);
|
|
};
|
|
|
|
_renderCreateNew() {
|
|
const cancel = (
|
|
<button className="btn template-name-btn" onClick={this._cancelNewTemplate}>
|
|
Cancel
|
|
</button>
|
|
);
|
|
return (
|
|
<div className="section-title">
|
|
Template Name:{' '}
|
|
<input
|
|
type="text"
|
|
className="template-name-input"
|
|
value={this.state.selectedTemplateName}
|
|
onChange={this._onEditName}
|
|
onKeyDown={this._onEnter(this._saveNewTemplate)}
|
|
/>
|
|
<button className="btn btn-emphasis template-name-btn" onClick={this._saveNewTemplate}>
|
|
Save
|
|
</button>
|
|
{this.state.templates.length ? cancel : null}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// MAIN RENDER
|
|
render() {
|
|
const deleteBtn = (
|
|
<button className="btn" title="Delete template" onClick={this._deleteTemplate}>
|
|
<RetinaImg name="icon-composer-trash.png" mode={RetinaImg.Mode.ContentIsMask} />
|
|
</button>
|
|
);
|
|
|
|
const editor = (
|
|
<div>
|
|
<div className="template-wrap">
|
|
{this.state.editAsHTML ? this._renderHTMLTemplate() : this._renderEditableTemplate()}
|
|
</div>
|
|
<div style={{ marginTop: '5px' }}>
|
|
<span className="editor-note">
|
|
{_.size(this._templateSaveQueue) === 0 ? 'Changes saved.' : ''}
|
|
|
|
</span>
|
|
<span style={{ float: 'right' }}>{this.state.editState === null ? deleteBtn : ''}</span>
|
|
</div>
|
|
<div className="toggle-mode" style={{ marginTop: '1em' }}>
|
|
{this._renderModeToggle()}
|
|
</div>
|
|
</div>
|
|
);
|
|
|
|
let editContainer = this._renderName();
|
|
if (this.state.editState === 'name') {
|
|
editContainer = this._renderEditName();
|
|
} else if (this.state.editState === 'new') {
|
|
editContainer = this._renderCreateNew();
|
|
}
|
|
|
|
const noTemplatesMessage = (
|
|
<div className="template-status-bar no-templates-message">
|
|
{`You don't have any templates! Enter a template name and press save to create one.`}
|
|
</div>
|
|
);
|
|
|
|
return (
|
|
<div className="container-templates">
|
|
<section style={this.state.editState === 'new' ? { marginBottom: 50 } : null}>
|
|
{editContainer}
|
|
{this.state.editState !== 'new' ? editor : null}
|
|
{this.state.templates.length === 0 ? noTemplatesMessage : null}
|
|
</section>
|
|
|
|
<section className="templates-instructions">
|
|
<p>
|
|
{`To create a variable, type a set of double curly
|
|
brackets wrapping the variable's name, like this`}:{' '}
|
|
<strong>
|
|
{'{{'}variable_name{'}}'}
|
|
</strong>. The highlighting in the variable regions will be removed before the message
|
|
is sent.
|
|
</p>
|
|
<p>
|
|
Reply templates are saved as HTML files in the{' '}
|
|
<strong>{`${AppEnv.getConfigDirPath()}/templates`}</strong> directory on your computer.
|
|
In raw HTML, variables are defined as HTML <code> tags with class "var
|
|
empty".
|
|
</p>
|
|
</section>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
export default PreferencesTemplates;
|