mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-12-12 20:36:29 +01:00
* feats: add script to improve localization * fix: the script used to generate the translations didn't take certain concatenations into account. Previously : Many features are unavailable in plain-text mode. To create a single Now : Many features are unavailable in plain-text mode. To create a single plain-text draft, hold Alt or Option while clicking Compose or Reply. - Previously : These features were %@ of the messages you sentin this time period, so these numbers do not reflect all of your activity. To enableread receipts and link tracking on emails you send, click the %@ or link tracking %@ icons in the composer. Now : These features were %@ of the messages you sent in this time period, so these numbers do not reflect all of your activity. To enable read receipts and link tracking on emails you send, click the %@ or link tracking %@ icons in the composer. - Previously : To make changes to contacts in this account, you'll need to re-authorize Mailspring to access your data.\n\n Now : To make changes to contacts in this account, you'll need to re-authorize Mailspring to access your data.\n\nIn Mailspring's main window, go to Preferences > Accounts, select this account, and click \"Re-authenticate\". You'll be prompted to give Mailspring additional permission to update and delete your contacts. ... * chore: improving the french and format-localizations.js executed * feats: add templates to localizations * fix(localization): define default template to english * chore(localization): added explanations on how to use script files --------- Co-authored-by: Ben Gotow <ben@foundry376.com>
323 lines
9.1 KiB
TypeScript
323 lines
9.1 KiB
TypeScript
/* eslint global-require: 0*/
|
|
|
|
import {
|
|
localized,
|
|
DraftStore,
|
|
Actions,
|
|
QuotedHTMLTransformer,
|
|
RegExpUtils,
|
|
} from 'mailspring-exports';
|
|
|
|
import MailspringStore from 'mailspring-store';
|
|
import path from 'path';
|
|
import fs from 'fs';
|
|
|
|
// Support accented characters in template names
|
|
// https://regex101.com/r/nD3eY8/1
|
|
const INVALID_TEMPLATE_NAME_REGEX = /[^a-zA-Z\u00C0-\u017F0-9_\- ]+/g;
|
|
|
|
class TemplateStore extends MailspringStore {
|
|
private _items = [];
|
|
private _templatesDir = path.join(AppEnv.getConfigDirPath(), 'templates');
|
|
private _watcher = null;
|
|
|
|
constructor() {
|
|
super();
|
|
|
|
this.listenTo(Actions.insertTemplateId, this._onInsertTemplateId);
|
|
this.listenTo(Actions.createTemplate, this._onCreateTemplate);
|
|
this.listenTo(Actions.showTemplates, this._onShowTemplates);
|
|
this.listenTo(Actions.deleteTemplate, this._onDeleteTemplate);
|
|
this.listenTo(Actions.renameTemplate, this._onRenameTemplate);
|
|
|
|
// I know this is a bit of pain but don't do anything that
|
|
// could possibly slow down app launch
|
|
fs.exists(this._templatesDir, exists => {
|
|
if (exists) {
|
|
this._populate();
|
|
this.watch();
|
|
} else {
|
|
fs.mkdir(this._templatesDir, () => {
|
|
this._welcomeTemplate().then(welcomeTemplate => {
|
|
fs.readFile(welcomeTemplate.path, (err, welcome) => {
|
|
fs.writeFile(path.join(this._templatesDir, welcomeTemplate.name), welcome, () => {
|
|
this.watch();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
directory() {
|
|
return this._templatesDir;
|
|
}
|
|
|
|
watch() {
|
|
if (!this._watcher) {
|
|
try {
|
|
this._watcher = fs.watch(this._templatesDir, () => this._populate());
|
|
} catch (err) {
|
|
// usually an ENOSPC error
|
|
console.warn(err);
|
|
}
|
|
}
|
|
}
|
|
|
|
unwatch() {
|
|
if (this._watcher) {
|
|
this._watcher.close();
|
|
}
|
|
this._watcher = null;
|
|
}
|
|
|
|
items() {
|
|
return this._items;
|
|
}
|
|
|
|
_populate() {
|
|
fs.readdir(this._templatesDir, (err, filenames) => {
|
|
if (err) {
|
|
AppEnv.showErrorDialog({
|
|
title: localized('Cannot scan templates directory'),
|
|
message: localized(
|
|
'Mailspring was unable to read the contents of your templates directory (%@). You may want to delete this folder or ensure filesystem permissions are set correctly.',
|
|
this._templatesDir
|
|
),
|
|
});
|
|
return;
|
|
}
|
|
this._items = [];
|
|
for (let i = 0, filename; i < filenames.length; i++) {
|
|
filename = filenames[i];
|
|
if (filename[0] === '.') {
|
|
continue;
|
|
}
|
|
const displayname = path.basename(filename, path.extname(filename));
|
|
this._items.push({
|
|
id: filename,
|
|
name: displayname,
|
|
path: path.join(this._templatesDir, filename),
|
|
});
|
|
}
|
|
this.trigger(this);
|
|
});
|
|
}
|
|
|
|
_onCreateTemplate({
|
|
headerMessageId,
|
|
name,
|
|
contents,
|
|
}: { headerMessageId?: string; name?: string; contents?: string } = {}) {
|
|
if (headerMessageId) {
|
|
this._onCreateTemplateFromDraft(headerMessageId);
|
|
return;
|
|
}
|
|
if (!name || name.length === 0) {
|
|
this._displayError(localized('You must provide a name for your template.'));
|
|
}
|
|
if (!contents || contents.length === 0) {
|
|
this._displayError(localized('You must provide contents for your template.'));
|
|
}
|
|
this.saveNewTemplate(name, contents, this._onShowTemplates);
|
|
}
|
|
|
|
async _onCreateTemplateFromDraft(headerMessageId) {
|
|
const session = await DraftStore.sessionForClientId(headerMessageId);
|
|
const draft = session.draft();
|
|
const draftName = draft.subject.replace(INVALID_TEMPLATE_NAME_REGEX, '');
|
|
|
|
let draftContents = QuotedHTMLTransformer.removeQuotedHTML(draft.body);
|
|
const sigIndex = draftContents.search(RegExpUtils.mailspringSignatureRegex());
|
|
draftContents = sigIndex > -1 ? draftContents.substr(0, sigIndex) : draftContents;
|
|
|
|
if (!draftName || draftName.length === 0) {
|
|
this._displayError(localized('Give your draft a subject to name your template.'));
|
|
}
|
|
if (!draftContents || draftContents.length === 0) {
|
|
this._displayError(
|
|
localized('To create a template you need to fill the body of the current draft.')
|
|
);
|
|
}
|
|
this.saveNewTemplate(draftName, draftContents, this._onShowTemplates);
|
|
}
|
|
|
|
_onShowTemplates() {
|
|
Actions.switchPreferencesTab('Templates');
|
|
Actions.openPreferences();
|
|
}
|
|
|
|
_displayError(message) {
|
|
require('@electron/remote').dialog.showErrorBox(localized('Template Creation Error'), message);
|
|
}
|
|
|
|
_displayDialog(title, message, buttons) {
|
|
return (
|
|
require('@electron/remote').dialog.showMessageBoxSync({
|
|
title: title,
|
|
message: title,
|
|
detail: message,
|
|
buttons: buttons,
|
|
type: 'info',
|
|
}) === 0
|
|
);
|
|
}
|
|
|
|
saveNewTemplate(name, contents, callback) {
|
|
if (!name || name.length === 0) {
|
|
this._displayError(localized('You must provide a template name.'));
|
|
return;
|
|
}
|
|
|
|
if (name.match(INVALID_TEMPLATE_NAME_REGEX)) {
|
|
this._displayError(
|
|
localized(
|
|
'Invalid template name! Names can only contain letters, numbers, spaces, dashes, and underscores.'
|
|
)
|
|
);
|
|
return;
|
|
}
|
|
|
|
let number = 1;
|
|
let resolvedName = name;
|
|
const sameName = t => t.name === resolvedName;
|
|
while (this._items.find(sameName)) {
|
|
resolvedName = `${name} ${number}`;
|
|
number += 1;
|
|
}
|
|
this.saveTemplate(resolvedName, contents, callback);
|
|
this.trigger(this);
|
|
}
|
|
|
|
saveTemplate(name, contents, callback) {
|
|
const filename = `${name}.html`;
|
|
const templatePath = path.join(this._templatesDir, filename);
|
|
let template = this._items.find(t => t.name === name);
|
|
|
|
this.unwatch();
|
|
fs.writeFile(templatePath, contents, err => {
|
|
this.watch();
|
|
if (err) {
|
|
this._displayError(err);
|
|
}
|
|
if (!template) {
|
|
template = {
|
|
id: filename,
|
|
name: name,
|
|
path: templatePath,
|
|
};
|
|
this._items.unshift(template);
|
|
}
|
|
if (callback) {
|
|
callback(template);
|
|
}
|
|
});
|
|
}
|
|
|
|
_onDeleteTemplate(name) {
|
|
const template = this._items.find(t => t.name === name);
|
|
if (!template) {
|
|
return;
|
|
}
|
|
|
|
if (
|
|
this._displayDialog(
|
|
localized('Delete Template?'),
|
|
localized('The template and its file will be permanently deleted.'),
|
|
[localized('Delete'), localized('Cancel')]
|
|
)
|
|
) {
|
|
fs.unlink(template.path, () => {
|
|
this._populate();
|
|
});
|
|
}
|
|
}
|
|
|
|
_onRenameTemplate(name, newName) {
|
|
const template = this._items.find(t => t.name === name);
|
|
if (!template) {
|
|
return;
|
|
}
|
|
|
|
if (newName.match(INVALID_TEMPLATE_NAME_REGEX)) {
|
|
this._displayError(
|
|
localized(
|
|
'Invalid template name! Names can only contain letters, numbers, spaces, dashes, and underscores.'
|
|
)
|
|
);
|
|
return;
|
|
}
|
|
if (newName.length === 0) {
|
|
this._displayError(localized('You must provide a template name.'));
|
|
return;
|
|
}
|
|
|
|
const newFilename = `${newName}.html`;
|
|
const oldPath = path.join(this._templatesDir, `${name}.html`);
|
|
const newPath = path.join(this._templatesDir, newFilename);
|
|
fs.rename(oldPath, newPath, () => {
|
|
template.name = newName;
|
|
template.id = newFilename;
|
|
template.path = newPath;
|
|
this.trigger(this);
|
|
});
|
|
}
|
|
|
|
async _onInsertTemplateId({
|
|
templateId,
|
|
headerMessageId,
|
|
}: { templateId?: string; headerMessageId?: string } = {}) {
|
|
const template = this._items.find(t => t.id === templateId);
|
|
const templateBody = fs.readFileSync(template.path).toString();
|
|
const session = await DraftStore.sessionForClientId(headerMessageId);
|
|
|
|
let proceed = true;
|
|
if (!session.draft().pristine && !session.draft().hasEmptyBody()) {
|
|
proceed = this._displayDialog(
|
|
localized('Replace draft contents?'),
|
|
localized(
|
|
'It looks like your draft already has some content. Loading this template will overwrite all draft contents.'
|
|
),
|
|
[localized('Replace contents'), localized('Cancel')]
|
|
);
|
|
}
|
|
|
|
if (proceed) {
|
|
const current = session.draft().body;
|
|
let insertion = current.length;
|
|
for (const s of [
|
|
'<signature',
|
|
'<div class="gmail_quote_attribution"',
|
|
'<blockquote class="gmail_quote"',
|
|
]) {
|
|
const i = current.indexOf(s);
|
|
if (i !== -1) {
|
|
insertion = Math.min(insertion, i);
|
|
}
|
|
}
|
|
session.changes.add({ body: `${templateBody}${current.substr(insertion)}` });
|
|
}
|
|
}
|
|
|
|
_welcomeTemplate(): Promise<{ name: string; path: string }> {
|
|
const getTemplatePath = name => path.join(__dirname, '..', 'assets', `${name}.html`);
|
|
let welcomeName = localized('Welcome to Templates');
|
|
|
|
return new Promise((resolve, reject) => {
|
|
fs.exists(getTemplatePath(welcomeName), exists => {
|
|
if (!exists) {
|
|
welcomeName = 'Welcome to Templates';
|
|
}
|
|
|
|
resolve({
|
|
name: `${welcomeName}.html`,
|
|
path: getTemplatePath(welcomeName),
|
|
});
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
export default new TemplateStore();
|