diff --git a/Gruntfile.js b/Gruntfile.js
index daf89982..1ec322bc 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -5,7 +5,7 @@ module.exports = function (grunt) {
// Project configuration.
grunt.initConfig({
eslint: {
- all: ['lib/**/*.js', 'test/**/*.js', 'config/**/*.js', 'Gruntfile.js', 'app.js', 'index.js']
+ all: ['lib/**/*.js', 'test/**/*.js', 'config/**/*.js', 'Gruntfile.js', 'app.js', 'index.js', 'routes/editorapi.js']
},
nodeunit: {
diff --git a/package.json b/package.json
index 5a927ff8..b571ea5b 100644
--- a/package.json
+++ b/package.json
@@ -67,7 +67,6 @@
"express-session": "^1.15.2",
"faker": "^4.1.0",
"feedparser": "^2.1.0",
- "file-type": "^5.2.0",
"fs-extra": "^3.0.1",
"geoip-ultralight": "^0.1.5",
"gettext-parser": "^1.2.2",
diff --git a/public/mosaico/templates/versafix-1/index.html b/public/mosaico/templates/versafix-1/index.html
index 693d5051..0cffc2f7 100644
--- a/public/mosaico/templates/versafix-1/index.html
+++ b/public/mosaico/templates/versafix-1/index.html
@@ -1395,78 +1395,78 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
|
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
|
@@ -1514,7 +1514,7 @@
- ![sponsor](/mosaico/templates/versafix-1/img/sponsor.gif)
|
diff --git a/routes/editorapi.js b/routes/editorapi.js
index e927163d..a285fab6 100644
--- a/routes/editorapi.js
+++ b/routes/editorapi.js
@@ -1,38 +1,34 @@
'use strict';
-let log = require('npmlog');
-let config = require('config');
-let express = require('express');
-let router = new express.Router();
-let passport = require('../lib/passport');
-let os = require('os');
-let fs = require('fs');
-let path = require('path');
-let mkdirp = require('mkdirp');
-let cache = require('memory-cache');
-let crypto = require('crypto');
-let fetch = require('node-fetch');
-let events = require('events');
-let httpMocks = require('node-mocks-http');
-let multiparty = require('multiparty');
-let fileType = require('file-type');
-let escapeStringRegexp = require('escape-string-regexp');
-let jqueryFileUpload = require('jquery-file-upload-middleware');
-let gm = require('gm').subClass({
+const log = require('npmlog');
+const config = require('config');
+const express = require('express');
+const router = new express.Router();
+const passport = require('../lib/passport');
+const os = require('os');
+const fs = require('fs');
+const path = require('path');
+const mkdirp = require('mkdirp');
+const crypto = require('crypto');
+const events = require('events');
+const httpMocks = require('node-mocks-http');
+const multiparty = require('multiparty');
+const escapeStringRegexp = require('escape-string-regexp');
+const jqueryFileUpload = require('jquery-file-upload-middleware');
+const gm = require('gm').subClass({
imageMagick: true
});
-let url = require('url');
-let htmlToText = require('html-to-text');
-let premailerApi = require('premailer-api');
-let editorHelpers = require('../lib/editor-helpers');
-let _ = require('../lib/translate')._;
-let mailer = require('../lib/mailer');
-let settings = require('../lib/models/settings');
-let templates = require('../lib/models/templates');
-let campaigns = require('../lib/models/campaigns');
+const url = require('url');
+const htmlToText = require('html-to-text');
+const premailerApi = require('premailer-api');
+const _ = require('../lib/translate')._;
+const mailer = require('../lib/mailer');
+const settings = require('../lib/models/settings');
+const templates = require('../lib/models/templates');
+const campaigns = require('../lib/models/campaigns');
router.all('/*', (req, res, next) => {
- if (!req.user && !cache.get(req.get('If-Match'))) {
+ if (!req.user) {
return res.status(403).send(_('Need to be logged in to access restricted content'));
}
if (req.originalUrl.startsWith('/editorapi/img?')) {
@@ -48,30 +44,150 @@ jqueryFileUpload.on('begin', fileInfo => {
fileInfo.name = fileInfo.name
.toLowerCase()
.replace(/ /g, '-')
- .replace(/[^a-z0-9+-\.]+/g, '');
+ .replace(/[^a-z0-9+-.]+/g, '');
});
-let listImages = (dir, dirURL, callback) => {
+const listImages = (dir, dirURL, callback) => {
fs.readdir(dir, (err, files = []) => {
if (err && err.code !== 'ENOENT') {
return callback(err.message || err);
}
files = files.filter(name => /\.(jpe?g|png|gif)$/i.test(name));
- files = files.map(name => {
- return {
- // mosaico
- name,
- url: dirURL + '/' + name,
- thumbnailUrl: dirURL + '/thumbnail/' + name,
- // grapejs
- src: dirURL + '/' + name,
- };
- });
+ files = files.map(name => ({
+ // mosaico
+ name,
+ url: dirURL + '/' + name,
+ thumbnailUrl: dirURL + '/thumbnail/' + name,
+ // grapejs
+ src: dirURL + '/' + name
+ }));
callback(null, files);
});
};
+const placeholderImage = (width, height, callback) => {
+ const magick = gm(width, height, '#707070');
+ const size = 40;
+ let x = 0;
+ let y = 0;
+
+ // stripes
+ while (y < height) {
+ magick
+ .fill('#808080')
+ .drawPolygon([x, y], [x + size, y], [x + size * 2, y + size], [x + size * 2, y + size * 2])
+ .drawPolygon([x, y + size], [x + size, y + size * 2], [x, y + size * 2]);
+ x = x + size * 2;
+ if (x > width) {
+ x = 0;
+ y = y + size * 2;
+ }
+ }
+
+ // text
+ magick
+ .fill('#B0B0B0')
+ .fontSize(20)
+ .drawText(0, 0, width + ' x ' + height, 'center');
+
+ magick.stream('png', (err, stream) => {
+ if (err) {
+ return callback(err);
+ }
+
+ const image = {
+ format: 'PNG',
+ stream
+ };
+
+ callback(null, image);
+ });
+};
+
+const resizedImage = (src, method, width, height, callback) => {
+ const pathname = path.join('/', url.parse(src).pathname);
+ const filePath = path.join(__dirname, '..', 'public', pathname);
+ const magick = gm(filePath);
+
+ magick.format((err, format) => {
+ if (err) {
+ return callback(err);
+ }
+
+ const streamHandler = (err, stream) => {
+ if (err) {
+ return callback(err);
+ }
+
+ const image = {
+ format,
+ stream
+ };
+
+ callback(null, image);
+ };
+
+ switch (method) {
+ case 'resize':
+ return magick
+ .autoOrient()
+ .resize(width, height)
+ .stream(streamHandler);
+
+ case 'cover':
+ return magick
+ .autoOrient()
+ .resize(width, height + '^')
+ .gravity('Center')
+ .extent(width, height + '>')
+ .stream(streamHandler);
+
+ default:
+ return callback(new Error(_('Method not supported')));
+ }
+ });
+};
+
+const getProcessedImage = (dynamicUrl, callback) => {
+ if (!dynamicUrl.includes('/editorapi/img?')) {
+ return callback(new Error('Invalid dynamicUrl'));
+ }
+
+ const {
+ src,
+ method,
+ params = '600,null'
+ } = url.parse(dynamicUrl, true).query;
+
+ let width = params.split(',')[0];
+ let height = params.split(',')[1];
+
+ const sanitizeSize = (val, min, max, defaultVal, allowNull) => {
+ if (val === 'null' && allowNull) {
+ return null;
+ }
+ val = Number(val) || defaultVal;
+ val = Math.max(min, val);
+ val = Math.min(max, val);
+ return val;
+ };
+
+ if (method === 'placeholder') {
+ width = sanitizeSize(width, 1, 2048, 600, false);
+ height = sanitizeSize(height, 1, 2048, 300, false);
+ placeholderImage(width, height, callback);
+ } else {
+ width = sanitizeSize(width, 1, 2048, 600, false);
+ height = sanitizeSize(height, 1, 2048, 300, true);
+ resizedImage(src, method, width, height, callback);
+ }
+};
+
const getStaticImageUrl = (dynamicUrl, staticDir, staticDirUrl, callback) => {
+ if (!dynamicUrl.includes('/editorapi/img?')) {
+ return callback(null, dynamicUrl);
+ }
+
mkdirp(staticDir, err => {
if (err) {
return callback(err);
@@ -82,242 +198,126 @@ const getStaticImageUrl = (dynamicUrl, staticDir, staticDirUrl, callback) => {
return callback(err);
}
- let hash = crypto.createHash('md5').update(dynamicUrl).digest('hex');
- let match = files.find(el => el.startsWith(hash));
- let headers = {};
+ const hash = crypto.createHash('md5').update(dynamicUrl).digest('hex');
+ const match = files.find(el => el.startsWith(hash));
if (match) {
return callback(null, staticDirUrl + '/' + match);
}
- if (dynamicUrl.includes('/editorapi/img?')) {
- let token = crypto.randomBytes(16).toString('hex');
- cache.put(token, true, 1000);
- headers['If-Match'] = token;
- }
+ getProcessedImage(dynamicUrl, (err, image) => {
+ if (err) {
+ return callback(err);
+ }
- fetch(dynamicUrl, {
- headers
- })
- .then(res => {
- if (res.status < 200 || res.status >= 300) {
- throw new Error(`Received HTTP status code ${res.status} while fetching image ${dynamicUrl}`);
- }
- return res.buffer();
- })
- .then(buffer => {
- let ft = fileType(buffer);
- if (ft && ['image/jpeg', 'image/png', 'image/gif'].includes(ft.mime)) {
- fs.writeFile(path.join(staticDir, hash + '.' + ft.ext), buffer, err => {
- if (err) {
- throw err;
- }
- let staticUrl = staticDirUrl + '/' + hash + '.' + ft.ext;
- callback(null, staticUrl);
- });
- } else {
- throw new Error(`Unsupported image MIME type for ${dynamicUrl}`);
- }
- })
- .catch(err => {
- callback(err);
- });
+ const fileName = hash + '.' + image.format.toLowerCase();
+ const filePath = path.join(staticDir, fileName);
+ const fileUrl = staticDirUrl + '/' + fileName;
+
+ const writeStream = fs.createWriteStream(filePath);
+ writeStream.on('error', err => callback(err));
+ writeStream.on('finish', () => callback(null, fileUrl));
+ image.stream.pipe(writeStream);
+ });
});
});
};
-let prepareHtml = ({
- editorName,
- html
-}, callback) => {
+const prepareHtml = (html, editorName, callback) => {
settings.get('serviceUrl', (err, serviceUrl) => {
if (err) {
return callback(err.message || err);
}
+ const srcs = new Map();
+ const re = /
]+src="([^"]*\/editorapi\/img\?[^"]+)"/ig;
let jobs = 0;
- let srcs = {};
- let re = /
]+src="([^"]+)"/g;
let result;
while ((result = re.exec(html)) !== null) {
- srcs[result[1]] = result[1];
+ srcs.set(result[1], result[1]);
}
- let done = () => {
+ const done = () => {
if (jobs === 0) {
- Object.keys(srcs).forEach(src => {
- // console.log(`replace dynamic - ${src} - with static - ${srcs[src]}`);
- html = html.replace(new RegExp(escapeStringRegexp(src), 'g'), srcs[src]);
- });
- callback(null, html);
+ for (const [key, value] of srcs) {
+ // console.log(`replace dynamicUrl: ${key} - with staticUrl: ${value}`);
+ html = html.replace(new RegExp(escapeStringRegexp(key), 'g'), value);
+ }
+ return callback(null, html);
}
};
const staticDir = path.join(__dirname, '..', 'public', editorName, 'uploads', 'static');
const staticDirUrl = url.resolve(serviceUrl, editorName + '/uploads/static');
- Object.keys(srcs).forEach(src => {
+ for (const key of srcs.keys()) {
jobs++;
- let dynamicUrl = src.replace(/&/g, '&');
- dynamicUrl = /^https?:\/\/|^\/\//i.test(dynamicUrl) ? dynamicUrl : url.resolve(serviceUrl, dynamicUrl);
+ const dynamicUrl = key.replace(/&/g, '&');
getStaticImageUrl(dynamicUrl, staticDir, staticDirUrl, (err, staticUrl) => {
if (err) {
// TODO: Send a warning back to the editor. For now we just skip image resizing.
- log.error('editorapi', err.message || err);
+ log.error('editorapi', err);
if (dynamicUrl.includes('/editorapi/img?')) {
- staticUrl = url.parse(dynamicUrl, true).query.src || dynamicUrl;
+ staticUrl = url.parse(dynamicUrl, true).query.src || dynamicUrl;
} else {
staticUrl = dynamicUrl;
}
+
+ if (!/^https?:\/\/|^\/\//i.test(staticUrl)) {
+ staticUrl = url.resolve(serviceUrl, staticUrl);
+ }
}
- srcs[src] = staticUrl;
+ srcs.set(key, staticUrl);
jobs--;
done();
});
- });
+ }
done();
});
};
-let placeholderImage = (req, res, {
- width,
- height
-}) => {
- let magick = gm(width, height, '#707070');
- let x = 0;
- let y = 0;
- let size = 40;
- // stripes
- while (y < height) {
- magick = magick
- .fill('#808080')
- .drawPolygon([x, y], [x + size, y], [x + size * 2, y + size], [x + size * 2, y + size * 2])
- .drawPolygon([x, y + size], [x + size, y + size * 2], [x, y + size * 2]);
- x = x + size * 2;
- if (x > width) {
- x = 0;
- y = y + size * 2;
- }
- }
- // text
- magick = magick
- .fill('#B0B0B0')
- .fontSize(20)
- .drawText(0, 0, width + ' x ' + height, 'center');
-
- res.set('Content-Type', 'image/png');
- magick.stream('png').pipe(res);
-};
-
-let resizedImage = (req, res, {
- src,
- method,
- width,
- height
-}) => {
- let magick = gm(src);
- magick.format((err, format) => {
- if (err) {
- return res.status(500).send(err.message || err);
- }
-
- switch (method) {
- case 'resize':
- res.set('Content-Type', 'image/' + format.toLowerCase());
- magick.autoOrient()
- .resize(width, height)
- .stream()
- .pipe(res);
- return;
-
- case 'cover':
- res.set('Content-Type', 'image/' + format.toLowerCase());
- magick.autoOrient()
- .resize(width, height + '^')
- .gravity('Center')
- .extent(width, height + '>')
- .stream()
- .pipe(res);
- return;
-
- default:
- res.status(501).send(_('Method not supported'));
- }
- });
-};
-
+// URL structure defined by Mosaico
// /editorapi/img?src=" + encodeURIComponent(src) + "&method=" + encodeURIComponent(method) + "¶ms=" + encodeURIComponent(width + "," + height);
-router.get('/img', passport.csrfProtection, (req, res) => {
- settings.get('serviceUrl', (err, serviceUrl) => {
+router.get('/img', (req, res) => {
+ getProcessedImage(req.originalUrl, (err, image) => {
if (err) {
- return res.status(500).send(err.message || err);
+ res.status(err.status || 500);
+ res.send(err.message || err);
+ return;
}
- let {
- src,
- method,
- params = '600,null'
- } = req.query;
- let width = params.split(',')[0];
- let height = params.split(',')[1];
- width = (width === 'null') ? null : Number(width);
- height = (height === 'null') ? null : Number(height);
-
- switch (method) {
- case 'placeholder':
- return placeholderImage(req, res, {
- width,
- height
- });
- case 'resize':
- case 'cover':
- src = /^https?:\/\/|^\/\//i.test(src) ? src : url.resolve(serviceUrl, src);
- return resizedImage(req, res, {
- src,
- method,
- width,
- height
- });
- default:
- return res.status(501).send(_('Method not supported'));
- }
+ res.set('Content-Type', 'image/' + image.format.toLowerCase());
+ image.stream.pipe(res);
});
});
router.post('/update', passport.parseForm, passport.csrfProtection, (req, res) => {
- prepareHtml({
- editorName: req.query.editor,
- html: req.body.html
- }, (err, html) => {
+ const sendResponse = err => {
if (err) {
return res.status(500).send(err.message || err);
}
+ res.send('ok');
+ };
+
+ prepareHtml(req.body.html, req.query.editor, (err, html) => {
+ if (err) {
+ return sendResponse(err);
+ }
req.body.html = html;
- if (req.query.type === 'template') {
- templates.update(req.body.id, req.body, (err, updated) => {
- if (err) {
- return res.status(500).send(err.message || err);
- }
- res.send('ok');
- });
-
- } else if (req.query.type === 'campaign') {
- campaigns.update(req.body.id, req.body, (err, updated) => {
- if (err) {
- return res.status(500).send(err.message || err);
- }
- res.send('ok');
- });
-
- } else {
- res.status(500).send(_('Invalid resource type'));
+ switch (req.query.type) {
+ case 'template':
+ return templates.update(req.body.id, req.body, sendResponse);
+ case 'campaign':
+ return campaigns.update(req.body.id, req.body, sendResponse);
+ default:
+ return sendResponse(new Error(_('Invalid resource type')));
}
});
});
@@ -331,8 +331,8 @@ router.get('/upload', passport.csrfProtection, (req, res) => {
return res.status(500).send(err.message || err);
}
- let baseDir = path.join(__dirname, '..', 'public', req.query.editor, 'uploads');
- let baseDirUrl = serviceUrl + req.query.editor + '/uploads';
+ const baseDir = path.join(__dirname, '..', 'public', req.query.editor, 'uploads');
+ const baseDirUrl = serviceUrl + req.query.editor + '/uploads';
listImages(path.join(baseDir, '0'), baseDirUrl + '/0', (err, sharedImages) => {
if (err) {
@@ -358,55 +358,78 @@ router.get('/upload', passport.csrfProtection, (req, res) => {
});
router.post('/upload', passport.csrfProtection, (req, res) => {
- let dirName = req.query.type === 'template' ? '0' :
- req.query.type === 'campaign' && Number(req.query.id) > 0 ? req.query.id :
- null;
-
- if (dirName === null) {
- return res.status(500).send(_('Invalid resource type or ID'));
- }
-
- let opts = {
- tmpDir: config.www.tmpdir || os.tmpdir(),
- imageVersions: req.query.editor === 'mosaico' ? {
- thumbnail: {
- width: 90,
- height: 90
- }
- } : {},
- uploadDir: path.join(__dirname, '..', 'public', req.query.editor, 'uploads', dirName),
- uploadUrl: '/' + req.query.editor + '/uploads/' + dirName, // must be root relative
- acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i,
- };
-
- let mockres = httpMocks.createResponse({
- eventEmitter: events.EventEmitter
- });
-
- mockres.on('end', () => {
- if (req.query.editor === 'grapejs') {
- let data = [];
- JSON.parse(mockres._getData()).files.forEach(file => {
- data.push({
- src: file.url
- });
- });
- res.json({
- data
- });
- } else {
- res.send(mockres._getData());
+ settings.get('serviceUrl', (err, serviceUrl) => {
+ if (err) {
+ return res.status(500).send(err.message || err);
}
- });
- jqueryFileUpload.fileHandler(opts)(req, mockres);
+ const getDirName = () => {
+ switch (req.query.type) {
+ case 'template':
+ return '0';
+ case 'campaign':
+ return Number(req.query.id) > 0 ? req.query.id : false;
+ default:
+ return false;
+ }
+ };
+
+ const dirName = getDirName();
+
+ if (dirName === false) {
+ return res.status(500).send(_('Invalid resource type or ID'));
+ }
+
+ const opts = {
+ tmpDir: config.www.tmpdir || os.tmpdir(),
+ imageVersions: req.query.editor === 'mosaico' ? {
+ thumbnail: {
+ width: 90,
+ height: 90
+ }
+ } : {},
+ uploadDir: path.join(__dirname, '..', 'public', req.query.editor, 'uploads', dirName),
+ uploadUrl: '/' + req.query.editor + '/uploads/' + dirName, // must be root relative
+ acceptFileTypes: /\.(gif|jpe?g|png)$/i,
+ hostname: url.parse(serviceUrl).host // include port
+ };
+
+ const mockres = httpMocks.createResponse({
+ eventEmitter: events.EventEmitter
+ });
+
+ mockres.on('error', err => {
+ res.status(500).json({
+ error: err.message || err,
+ data: []
+ });
+ });
+
+ mockres.on('end', () => {
+ const data = [];
+ try {
+ JSON.parse(mockres._getData()).files.forEach(file => {
+ data.push({
+ src: file.url
+ });
+ });
+ res.json({
+ data
+ });
+ } catch(err) {
+ res.status(500).json({
+ error: err.message || err,
+ data
+ });
+ }
+ });
+
+ jqueryFileUpload.fileHandler(opts)(req, req.query.editor === 'grapejs' ? mockres : res);
+ });
});
router.post('/download', passport.csrfProtection, (req, res) => {
- prepareHtml({
- editorName: req.query.editor,
- html: req.body.html
- }, (err, html) => {
+ prepareHtml(req.body.html, req.query.editor, (err, html) => {
if (err) {
return res.status(500).send(err.message || err);
}
@@ -416,9 +439,12 @@ router.post('/download', passport.csrfProtection, (req, res) => {
});
});
-let parseGrapejsMultipartTestForm = (req, res, next) => {
+const parseGrapejsMultipartTestForm = (req, res, next) => {
if (req.query.editor === 'grapejs') {
- new multiparty.Form().parse(req, (err, fields, files) => {
+ new multiparty.Form().parse(req, (err, fields) => {
+ if (err) {
+ return next(err);
+ }
req.body.email = fields.email[0];
req.body.subject = fields.subject[0];
req.body.html = fields.html[0];
@@ -431,7 +457,7 @@ let parseGrapejsMultipartTestForm = (req, res, next) => {
};
router.post('/test', parseGrapejsMultipartTestForm, passport.csrfProtection, (req, res) => {
- let sendError = err => {
+ const sendError = err => {
if (req.query.editor === 'grapejs') {
res.status(500).json({
errors: err.message || err
@@ -441,10 +467,7 @@ router.post('/test', parseGrapejsMultipartTestForm, passport.csrfProtection, (re
}
};
- prepareHtml({
- editorName: req.query.editor,
- html: req.body.html
- }, (err, html) => {
+ prepareHtml(req.body.html, req.query.editor, (err, html) => {
if (err) {
return sendError(err);
}
@@ -459,29 +482,31 @@ router.post('/test', parseGrapejsMultipartTestForm, passport.csrfProtection, (re
return sendError(err);
}
- let opts = {
+ const opts = {
from: {
name: configItems.defaultFrom,
- address: configItems.defaultAddress,
+ address: configItems.defaultAddress
},
to: req.body.email,
subject: req.body.subject,
text: htmlToText.fromString(html, {
wordwrap: 100
}),
- html,
+ html
};
- transport.sendMail(opts, (err, info) => {
+ transport.sendMail(opts, err => {
if (err) {
return sendError(err);
}
- req.query.editor === 'grapejs' ?
+ if (req.query.editor === 'grapejs') {
res.json({
data: 'ok'
- }) :
+ });
+ } else {
res.send('ok');
+ }
});
});
});
diff --git a/routes/grapejs.js b/routes/grapejs.js
index 5b32cf35..38546503 100644
--- a/routes/grapejs.js
+++ b/routes/grapejs.js
@@ -7,6 +7,7 @@ const passport = require('../lib/passport');
const _ = require('../lib/translate')._;
const fs = require('fs');
const path = require('path');
+const settings = require('../lib/models/settings');
const editorHelpers = require('../lib/editor-helpers')
router.all('/*', (req, res, next) => {
@@ -18,44 +19,52 @@ router.all('/*', (req, res, next) => {
});
router.get('/editor', passport.csrfProtection, (req, res) => {
- editorHelpers.getResource(req.query.type, req.query.id, (err, resource) => {
+ settings.get('serviceUrl', (err, serviceUrl) => {
if (err) {
req.flash('danger', err.message || err);
return res.redirect('/');
}
- try {
- resource.editorData = JSON.parse(resource.editorData);
- } catch (err) {
- resource.editorData = {
- template: req.query.template || 'demo'
+ editorHelpers.getResource(req.query.type, req.query.id, (err, resource) => {
+ if (err) {
+ req.flash('danger', err.message || err);
+ return res.redirect('/');
}
- }
- if (!resource.html && !resource.editorData.html && !resource.editorData.mjml) {
- const base = path.join(__dirname, '..', 'public', 'grapejs', 'templates', resource.editorData.template);
try {
- resource.editorData.mjml = fs.readFileSync(path.join(base, 'index.mjml'), 'utf8');
+ resource.editorData = JSON.parse(resource.editorData);
} catch (err) {
- try {
- resource.html = fs.readFileSync(path.join(base, 'index.html'), 'utf8');
- } catch (err) {
- resource.html = err.message || err;
+ resource.editorData = {
+ template: req.query.template || 'demo'
}
}
- }
- res.render('grapejs/editor', {
- layout: 'grapejs/layout-editor',
- type: req.query.type,
- stringifiedResource: JSON.stringify(resource),
- resource,
- editor: {
- name: resource.editorName || 'grapejs',
- mode: resource.editorData.mjml ? 'mjml' : 'html',
- config: config.grapejs
- },
- csrfToken: req.csrfToken()
+ if (!resource.html && !resource.editorData.html && !resource.editorData.mjml) {
+ const base = path.join(__dirname, '..', 'public', 'grapejs', 'templates', path.join('/', resource.editorData.template));
+ try {
+ resource.editorData.mjml = fs.readFileSync(path.join(base, 'index.mjml'), 'utf8');
+ } catch (err) {
+ try {
+ resource.html = fs.readFileSync(path.join(base, 'index.html'), 'utf8');
+ } catch (err) {
+ resource.html = err.message || err;
+ }
+ }
+ }
+
+ res.render('grapejs/editor', {
+ layout: 'grapejs/layout-editor',
+ type: req.query.type,
+ stringifiedResource: JSON.stringify(resource),
+ resource,
+ editor: {
+ name: resource.editorName || 'grapejs',
+ mode: resource.editorData.mjml ? 'mjml' : 'html',
+ config: config.grapejs
+ },
+ csrfToken: req.csrfToken(),
+ serviceUrl
+ });
});
});
});
diff --git a/views/grapejs/editor.hbs b/views/grapejs/editor.hbs
index 1867139a..1a53f82d 100644
--- a/views/grapejs/editor.hbs
+++ b/views/grapejs/editor.hbs
@@ -129,6 +129,7 @@