mirror of
https://github.com/Foundry376/Mailspring.git
synced 2024-09-22 00:06:06 +08:00
fix(templates): fix several bugs in templates plugin
Fixes behavior when there are no template files, prevents renaming/creating with an empty name, fixes yet another way to accidentally make yellow text, misc small style fixes
This commit is contained in:
parent
57aee26256
commit
c91a0c6062
|
@ -13,7 +13,7 @@ class PreferencesTemplates extends React.Component
|
|||
{templates, selectedTemplate, selectedTemplateName} = @_getStateFromStores()
|
||||
@state =
|
||||
editAsHTML: false
|
||||
editState: null
|
||||
editState: if templates.length==0 then "new" else null
|
||||
templates: templates
|
||||
selectedTemplate: selectedTemplate
|
||||
selectedTemplateName: selectedTemplateName
|
||||
|
@ -108,14 +108,14 @@ class PreferencesTemplates extends React.Component
|
|||
_renderEditableTemplate: ->
|
||||
<Contenteditable
|
||||
ref="templateInput"
|
||||
value={@state.contents}
|
||||
value={@state.contents || ""}
|
||||
onChange={@_onEditTemplate}
|
||||
extensions={[TemplateEditor]}
|
||||
spellcheck={false} />
|
||||
|
||||
_renderHTMLTemplate: ->
|
||||
<textarea ref="templateHTMLInput"
|
||||
value={@state.contents}
|
||||
value={@state.contents || ""}
|
||||
onChange={@_onEditTemplate}/>
|
||||
|
||||
_renderModeToggle: ->
|
||||
|
@ -164,8 +164,15 @@ class PreferencesTemplates extends React.Component
|
|||
|
||||
# DELETE AND NEW
|
||||
_deleteTemplate: =>
|
||||
numTemplates = @state.templates.length
|
||||
if @state.selectedTemplate?
|
||||
TemplateStore.deleteTemplate(@state.selectedTemplate.name)
|
||||
if numTemplates==1
|
||||
@setState
|
||||
editState: "new"
|
||||
selectedTemplate: null
|
||||
selectedTemplateName: ""
|
||||
contents: ""
|
||||
|
||||
_startNewTemplate: =>
|
||||
@setState
|
||||
|
@ -175,7 +182,7 @@ class PreferencesTemplates extends React.Component
|
|||
contents: ""
|
||||
|
||||
_saveNewTemplate: =>
|
||||
TemplateStore.saveNewTemplate(@state.selectedTemplateName, @state.contents, (template) =>
|
||||
TemplateStore.saveNewTemplate(@state.selectedTemplateName, @state.contents || "", (template) =>
|
||||
@setState
|
||||
selectedTemplate: template
|
||||
editState: null
|
||||
|
@ -190,10 +197,11 @@ class PreferencesTemplates extends React.Component
|
|||
@_loadTemplateContents(template)
|
||||
|
||||
_renderCreateNew: ->
|
||||
cancel = <button className="btn template-name-btn" onClick={@_cancelNewTemplate}>Cancel</button>
|
||||
<div className="section-title">
|
||||
Template Name: <input type="text" className="template-name-input" value={@state.selectedTemplateName} onChange={@_onEditName}/>
|
||||
<button className="btn btn-emphasis template-name-btn" onClick={@_saveNewTemplate}>Save</button>
|
||||
<button className="btn template-name-btn" onClick={@_cancelNewTemplate}>Cancel</button>
|
||||
{if @state.templates.length then cancel}
|
||||
</div>
|
||||
|
||||
|
||||
|
@ -218,6 +226,11 @@ class PreferencesTemplates extends React.Component
|
|||
</div>
|
||||
</div>
|
||||
|
||||
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>
|
||||
|
||||
<div>
|
||||
<section className="container-templates" style={if @state.editState is "new" then {marginBottom:50}}>
|
||||
<h2>Quick Replies</h2>
|
||||
|
@ -228,22 +241,23 @@ class PreferencesTemplates extends React.Component
|
|||
else @_renderName()
|
||||
}
|
||||
{if @state.editState isnt "new" then editor}
|
||||
{if @state.editState is "new" then noTemplatesMessage}
|
||||
</section>
|
||||
|
||||
<section className="templates-instructions">
|
||||
<p>
|
||||
The Quick Replies plugin allows you to create templated email replies. Replies can contain variables, which
|
||||
you can quickly jump between and fill out when using the template. To create a variable, type a set of double curly
|
||||
The Quick Replies plugin allows you to create templated email replies, with variables that
|
||||
you can quickly fill out inside your email message. To create a variable, type a set of double curly
|
||||
brackets wrapping the variable's name, like this: <strong>{"{{"}variable_name{"}}"}</strong>
|
||||
</p>
|
||||
<p>
|
||||
Reply templates are saved in the <strong>~/.nylas/templates</strong> directory on your computer. Each template
|
||||
is an HTML file - the name of the file is the name of the template, and its contents are the default message body.
|
||||
</p>
|
||||
<p>
|
||||
In raw HTML, variables are defined as HTML <code> tags with class "var empty". Typing curly brackets creates a tag
|
||||
automatically. The code tags are colored yellow to show the variable regions, but will be stripped out before the message is sent.
|
||||
</p>
|
||||
<p>
|
||||
Reply templates live in the <strong>~/.nylas/templates</strong> directory on your computer. Each template
|
||||
is an HTML file - the name of the file is the name of the template, and its contents are the default message body.
|
||||
</p>
|
||||
|
||||
</section>
|
||||
</div>
|
||||
|
|
|
@ -18,11 +18,21 @@ class TemplateEditor extends ContenteditableExtension
|
|||
editor.whilePreservingSelection ->
|
||||
DOMUtils.unwrapNode(codeNode)
|
||||
|
||||
# Attempt to sanitize spans that are needlessly created by contenteditable
|
||||
for span in editor.rootNode.querySelectorAll("span")
|
||||
if not span.className
|
||||
# Attempt to sanitize extra nodes that may have been created by contenteditable on certain text editing
|
||||
# operations (insertion/deletion of line breaks, etc.). These are generally <span>, but can also be
|
||||
# <font>, <b>, and possibly others. The extra nodes often grab CSS styles from neighboring elements
|
||||
# as inline style, including the yellow text from <code> nodes that we insert. This is contenteditable
|
||||
# trying to be "smart" and preserve styles, which is very undesirable for the <code> node styles. The
|
||||
# below code is a hack to prevent yellow text from appearing.
|
||||
for node in editor.rootNode.querySelectorAll("*")
|
||||
if not node.className and node.style.color == "#c79b11"
|
||||
editor.whilePreservingSelection ->
|
||||
DOMUtils.unwrapNode(span)
|
||||
DOMUtils.unwrapNode(node)
|
||||
|
||||
for node in editor.rootNode.querySelectorAll("font")
|
||||
if node.color == "#c79b11"
|
||||
editor.whilePreservingSelection ->
|
||||
DOMUtils.unwrapNode(node)
|
||||
|
||||
# Find all {{}} and wrap them in code nodes if they aren't already
|
||||
# Regex finds any {{ <contents> }} that doesn't contain {, }, or \n
|
||||
|
@ -39,16 +49,4 @@ class TemplateEditor extends ContenteditableExtension
|
|||
editor.restoreSelectionByTextIndex(codeNode, selIndex.startIndex, selIndex.endIndex)
|
||||
|
||||
|
||||
@onKeyDown: ({editor}) ->
|
||||
# Look for all existing code tags that we may have added before,
|
||||
# and remove any that now have invalid content (don't start with {{ and
|
||||
# end with }} as well as any that wrap the current selection
|
||||
|
||||
codeNodes = editor.rootNode.querySelectorAll("code.var.empty")
|
||||
for codeNode in codeNodes
|
||||
text = codeNode.textContent
|
||||
if not text.startsWith("{{") or not text.endsWith("}}") or DOMUtils.selectionStartsOrEndsIn(codeNode)
|
||||
editor.whilePreservingSelection ->
|
||||
DOMUtils.unwrapNode(codeNode)
|
||||
|
||||
module.exports = TemplateEditor
|
||||
|
|
|
@ -32,7 +32,7 @@ class TemplateStatusBar extends React.Component {
|
|||
|
||||
static containerStyles = {
|
||||
textAlign: 'center',
|
||||
width: 530,
|
||||
width: 580,
|
||||
margin: 'auto',
|
||||
}
|
||||
|
||||
|
|
|
@ -147,6 +147,11 @@ class TemplateStore extends NylasStore {
|
|||
}
|
||||
|
||||
saveNewTemplate(name, contents, callback) {
|
||||
if(!name || name.length===0){
|
||||
this._displayError('You must provide a template name.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (name.match(TemplateStore.INVALID_TEMPLATE_NAME_REGEX)) {
|
||||
this._displayError('Invalid template name! Names can only contain letters, numbers, spaces, dashes, and underscores.');
|
||||
return;
|
||||
|
@ -219,8 +224,12 @@ class TemplateStore extends NylasStore {
|
|||
this._displayError('Invalid template name! Names can only contain letters, numbers, spaces, dashes, and underscores.');
|
||||
return;
|
||||
}
|
||||
if(newName.length===0){
|
||||
this._displayError('You must provide a template name.');
|
||||
return;
|
||||
}
|
||||
|
||||
const newFilename = `${newName}.html`;
|
||||
const newFilename = `${newName}.html`;
|
||||
const oldPath = path.join(this._templatesDir, `${oldName}.html`);
|
||||
const newPath = path.join(this._templatesDir, newFilename);
|
||||
fs.rename(oldPath, newPath, () => {
|
||||
|
|
|
@ -43,6 +43,11 @@
|
|||
max-width: 640px;
|
||||
|
||||
|
||||
.no-templates-message {
|
||||
text-align: center;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.template-wrap {
|
||||
position: relative;
|
||||
border: 1px solid @input-border-color;
|
||||
|
@ -112,7 +117,7 @@
|
|||
|
||||
.template-name-btn {
|
||||
float: right;
|
||||
margin: 6px;
|
||||
margin: 0 6px;
|
||||
}
|
||||
.template-name-input {
|
||||
display: inline-block;
|
||||
|
|
|
@ -538,7 +538,7 @@ DOMUtils =
|
|||
# current selection.
|
||||
selectionStartsOrEndsIn: (rangeOrNode) ->
|
||||
selection = document.getSelection()
|
||||
return false unless selection
|
||||
return false unless (selection and selection.rangeCount>0)
|
||||
if rangeOrNode instanceof Range
|
||||
return @rangeStartsOrEndsInRange(selection.getRangeAt(0), rangeOrNode)
|
||||
else if rangeOrNode instanceof Node
|
||||
|
@ -552,7 +552,7 @@ DOMUtils =
|
|||
# contained within it.
|
||||
selectionIsWithin: (rangeOrNode) ->
|
||||
selection = document.getSelection()
|
||||
return false unless selection
|
||||
return false unless (selection and selection.rangeCount>0)
|
||||
if rangeOrNode instanceof Range
|
||||
return @rangeInRange(selection.getRangeAt(0), rangeOrNode)
|
||||
else if rangeOrNode instanceof Node
|
||||
|
|
Loading…
Reference in a new issue