diff --git a/app.js b/app.js index d6b05349..36525f50 100644 --- a/app.js +++ b/app.js @@ -47,6 +47,8 @@ if (config.www.proxy) { // Do not expose software used app.disable('x-powered-by'); +hbs.registerPartials(__dirname + '/views/partials'); + /** * We need this helper to make sure that we consume flash messages only * when we are able to actually display these. Otherwise we might end up diff --git a/config/default.toml b/config/default.toml index 60780084..aee1c248 100644 --- a/config/default.toml +++ b/config/default.toml @@ -21,6 +21,12 @@ # Process title visible in monitoring logs and process listing title="mailtrain" +# Enabled HTML editors +editors=[ + ["summernote", "Summernote"], + ["codeeditor", "Code Editor"] +] + # If you start out as a root user (eg. if you want to use ports lower than 1000) # then you can downgrade the user once all services are up and running #user="nobody" diff --git a/lib/models/campaigns.js b/lib/models/campaigns.js index e5b12e70..8d2a31ce 100644 --- a/lib/models/campaigns.js +++ b/lib/models/campaigns.js @@ -13,7 +13,7 @@ let log = require('npmlog'); let mailer = require('../mailer'); let humanize = require('humanize'); -let allowedKeys = ['description', 'from', 'address', 'reply_to', 'subject', 'template', 'source_url', 'list', 'segment', 'html', 'text', 'tracking_disabled']; +let allowedKeys = ['description', 'from', 'address', 'reply_to', 'subject', 'editor_name', 'editor_data', 'template', 'source_url', 'list', 'segment', 'html', 'text', 'tracking_disabled']; module.exports.list = (start, limit, callback) => { db.getConnection((err, connection) => { @@ -729,6 +729,8 @@ module.exports.create = (campaign, opts, callback) => { return callback(new Error('Selected template not found')); } + campaign.editorName = template.editorName; + campaign.editorData = template.editorData; campaign.html = template.html; campaign.text = template.text; diff --git a/lib/models/templates.js b/lib/models/templates.js index ea54cac3..6df4c6ad 100644 --- a/lib/models/templates.js +++ b/lib/models/templates.js @@ -3,7 +3,7 @@ let db = require('../db'); let tools = require('../tools'); -let allowedKeys = ['description', 'html', 'text']; +let allowedKeys = ['description', 'editor_name', 'editor_data', 'html', 'text']; module.exports.list = (start, limit, callback) => { db.getConnection((err, connection) => { diff --git a/meta.json b/meta.json index f0ac0c49..731dc8bf 100644 --- a/meta.json +++ b/meta.json @@ -1,3 +1,3 @@ { - "schemaVersion": 20 + "schemaVersion": 21 } diff --git a/routes/archive.js b/routes/archive.js index ff8fcab4..a4356462 100644 --- a/routes/archive.js +++ b/routes/archive.js @@ -62,8 +62,13 @@ router.get('/:campaign/:list/:subscription', passport.csrfProtection, (req, res, } let renderHtml = (html, renderTags) => { - res.render('archive/view', { - layout: 'archive/layout', + campaign.editorName = campaign.editorName || 'summernote'; + let sfx = ''; + if (campaign.editorName !== 'summernote' && campaign.editorName !== 'codeeditor') { + sfx = '-raw'; + } + res.render('archive/view' + sfx, { + layout: 'archive/layout' + sfx, message: renderTags ? tools.formatMessage(serviceUrl, campaign, list, subscription, html) : html, campaign, list, diff --git a/routes/campaigns.js b/routes/campaigns.js index 8a9b2e23..e5d715a9 100644 --- a/routes/campaigns.js +++ b/routes/campaigns.js @@ -1,5 +1,6 @@ 'use strict'; +let config = require('config'); let express = require('express'); let router = new express.Router(); let lists = require('../lib/models/lists'); @@ -115,7 +116,10 @@ router.post('/create', passport.parseForm, passport.csrfProtection, (req, res) = return res.redirect('/campaigns/create?' + tools.queryParams(req.body)); } req.flash('success', 'Campaign “' + req.body.name + '” created'); - res.redirect('/campaigns/view/' + id); + res.redirect((req.body.type === 'rss') + ? '/campaigns/edit/' + id + : '/campaigns/edit/' + id + '?tab=template' + ); }); }); @@ -159,6 +163,8 @@ router.get('/edit/:id', passport.csrfProtection, (req, res, next) => { campaign.csrfToken = req.csrfToken(); campaign.listItems = listItems; campaign.useEditor = true; + campaign.editorName = campaign.editorName || 'summernote'; + campaign.editorConfig = config[campaign.editorName]; campaign.disableWysiwyg = configItems.disableWysiwyg; campaign.showGeneral = req.query.tab === 'general' || !req.query.tab; diff --git a/routes/templates.js b/routes/templates.js index 60df77ab..576d446a 100644 --- a/routes/templates.js +++ b/routes/templates.js @@ -1,5 +1,6 @@ 'use strict'; +let config = require('config'); let express = require('express'); let router = new express.Router(); let templates = require('../lib/models/templates'); @@ -66,6 +67,23 @@ router.get('/create', passport.csrfProtection, (req, res, next) => { data.text = data.text || rendererText(configItems); data.disableWysiwyg = configItems.disableWysiwyg; + data.editors = config.editors || [['summernote', 'Summernote']]; + data.editors = data.editors.map(ed => { + let editor = { + name: ed[0], + label: ed[1], + }; + if (config[editor.name] && config[editor.name].templates) { + editor.templates = config[editor.name].templates.map(tmpl => { + return { + name: tmpl[0], + label: tmpl[1], + } + }); + } + return editor; + }); + res.render('templates/create', data); }); }); @@ -79,7 +97,7 @@ router.post('/create', passport.parseForm, passport.csrfProtection, (req, res) = return res.redirect('/templates/create?' + tools.queryParams(req.body)); } req.flash('success', 'Template created'); - res.redirect('/templates'); + res.redirect('/templates/edit/' + id); }); }); @@ -95,6 +113,8 @@ router.get('/edit/:id', passport.csrfProtection, (req, res, next) => { } template.csrfToken = req.csrfToken(); template.useEditor = true; + template.editorName = template.editorName || 'summernote'; + template.editorConfig = config[template.editorName]; template.disableWysiwyg = configItems.disableWysiwyg; res.render('templates/edit', template); }); diff --git a/setup/sql/upgrade-00021.sql b/setup/sql/upgrade-00021.sql new file mode 100644 index 00000000..ca3a85ec --- /dev/null +++ b/setup/sql/upgrade-00021.sql @@ -0,0 +1,16 @@ +# Header section +# Define incrementing schema version number +SET @schema_version = '21'; + +# Add fields editor_name, editor_data to templates +ALTER TABLE `templates` ADD COLUMN `editor_name` varchar(50) DEFAULT '' AFTER `description`; +ALTER TABLE `templates` ADD COLUMN `editor_data` longtext DEFAULT '' AFTER `editor_name`; + +# Add fields editor_name, editor_data to campaigns +ALTER TABLE `campaigns` ADD COLUMN `editor_name` varchar(50) DEFAULT '' AFTER `source_url`; +ALTER TABLE `campaigns` ADD COLUMN `editor_data` longtext DEFAULT '' AFTER `editor_name`; + +# Footer section +LOCK TABLES `settings` WRITE; +INSERT INTO `settings` (`key`, `value`) VALUES('db_schema_version', @schema_version) ON DUPLICATE KEY UPDATE `value`=@schema_version; +UNLOCK TABLES; diff --git a/views/archive/layout-raw.hbs b/views/archive/layout-raw.hbs new file mode 100644 index 00000000..38e54992 --- /dev/null +++ b/views/archive/layout-raw.hbs @@ -0,0 +1 @@ +{{{body}}} diff --git a/views/archive/view-raw.hbs b/views/archive/view-raw.hbs new file mode 100644 index 00000000..2e4767a6 --- /dev/null +++ b/views/archive/view-raw.hbs @@ -0,0 +1 @@ +{{{message}}} diff --git a/views/campaigns/create-rss.hbs b/views/campaigns/create-rss.hbs index 7ca4b849..1dbb715d 100644 --- a/views/campaigns/create-rss.hbs +++ b/views/campaigns/create-rss.hbs @@ -58,7 +58,7 @@
[RSS_ENTRY]
to mark the position for the RSS post content. Additionally you can use any valid merge tag as well.
+ {{#if disableWysiwyg}}
+ {{> codeeditor}}
+ {{else}}
+ {{> summernote}}
+ {{/if}}
+ [RSS_ENTRY]
to mark the position for the RSS post content. Additionally you can use any valid merge tag as well.
- Merge tags are tags that are replaced before sending out the message. The format of the merge tag is the following: [TAG_NAME]
or [TAG_NAME/fallback]
where fallback
is an optional text value used
- when TAG_NAME
is empty.
-
[EMAIL]
– email address of the subscriber
- [FIRST_NAME]
– first name of the subscriber
- [LAST_NAME]
– last name of the subscriber
- [FULL_NAME]
– first and last names of the subscriber joined
- [LINK_UNSUBSCRIBE]
– URL that points to the preferences page of the subscriber
- [LINK_PREFERENCES]
– URL that points to the unsubscribe page
- [LINK_BROWSER]
– URL to preview the message in a browser
- [SUBSCRIPTION_ID]
– Unique ID that identifies the recipient
- [LIST_ID]
– Unique ID that identifies the list used for this campaign
- [CAMPAIGN_ID]
– Unique ID that identifies current campaign
- - In addition to that any custom field can have its own merge tag. -
-