From 7e5200021996b7ca948922a65d14944be644a67c Mon Sep 17 00:00:00 2001 From: Tomas Bures Date: Tue, 13 Nov 2018 21:35:33 +0100 Subject: [PATCH] Added MJML/HTML codeeditor with a preview for template design. --- app-builder.js | 2 + client/package-lock.json | 332 ++++++++++++++++-- client/package.json | 1 + client/src/lib/sandbox-common.scss | 71 ++++ client/src/lib/sandboxed-ckeditor.scss | 71 +--- client/src/lib/sandboxed-codeeditor-root.js | 166 +++++++++ client/src/lib/sandboxed-codeeditor-shared.js | 11 + client/src/lib/sandboxed-codeeditor.js | 86 +++++ client/src/lib/sandboxed-codeeditor.scss | 35 ++ client/src/lib/sandboxed-grapesjs-shared.js | 5 + client/src/lib/sandboxed-grapesjs.js | 1 - client/src/lib/sandboxed-grapesjs.scss | 70 +--- client/src/lib/sandboxed-mosaico.scss | 69 +--- client/src/templates/helpers.js | 91 +++-- client/src/templates/mosaico/helpers.js | 2 +- client/webpack.config.js | 1 + config/default.yaml | 8 +- lib/tools.js | 2 +- routes/sandboxed-ckeditor.js | 2 +- routes/sandboxed-codeeditor.js | 59 ++++ views/codeeditor/layout.hbs | 35 ++ views/codeeditor/root.hbs | 6 + workers/reports/config/default.toml | 18 - workers/reports/config/default.yaml | 22 ++ 24 files changed, 887 insertions(+), 279 deletions(-) create mode 100644 client/src/lib/sandbox-common.scss create mode 100644 client/src/lib/sandboxed-codeeditor-root.js create mode 100644 client/src/lib/sandboxed-codeeditor-shared.js create mode 100644 client/src/lib/sandboxed-codeeditor.js create mode 100644 client/src/lib/sandboxed-codeeditor.scss create mode 100644 routes/sandboxed-codeeditor.js create mode 100644 views/codeeditor/layout.hbs create mode 100644 views/codeeditor/root.hbs delete mode 100644 workers/reports/config/default.toml create mode 100644 workers/reports/config/default.yaml diff --git a/app-builder.js b/app-builder.js index 3be78a84..135dd7de 100644 --- a/app-builder.js +++ b/app-builder.js @@ -25,6 +25,7 @@ const subscription = require('./routes/subscription'); const sandboxedMosaico = require('./routes/sandboxed-mosaico'); const sandboxedCKEditor = require('./routes/sandboxed-ckeditor'); const sandboxedGrapesJS = require('./routes/sandboxed-grapesjs'); +const sandboxedCodeEditor = require('./routes/sandboxed-codeeditor'); const files = require('./routes/files'); const links = require('./routes/links'); const archive = require('./routes/archive'); @@ -226,6 +227,7 @@ function createApp(appType) { useWith404Fallback('/mosaico', sandboxedMosaico.getRouter(appType)); useWith404Fallback('/ckeditor', sandboxedCKEditor.getRouter(appType)); useWith404Fallback('/grapesjs', sandboxedGrapesJS.getRouter(appType)); + useWith404Fallback('/codeeditor', sandboxedCodeEditor.getRouter(appType)); if (appType === AppType.TRUSTED || appType === AppType.SANDBOXED) { if (config.reports && config.reports.enabled === true) { diff --git a/client/package-lock.json b/client/package-lock.json index 580edded..f7d532ad 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -2180,11 +2180,6 @@ "safe-buffer": "5.1.1" } }, - "ckeditor": { - "version": "4.11.1", - "resolved": "https://registry.npmjs.org/ckeditor/-/ckeditor-4.11.1.tgz", - "integrity": "sha512-UhHe02cc/wWJquDQZysEgh0ohLMEMU56zDx+s8prDdjylY/aBDY2xdIiIpbgCBTXdjhrEPIAPyiDS9g3RxYXig==" - }, "ckeditor5": { "version": "11.1.1", "resolved": "https://registry.npmjs.org/ckeditor5/-/ckeditor5-11.1.1.tgz", @@ -3413,9 +3408,9 @@ "dev": true }, "deep-extend": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.5.1.tgz", - "integrity": "sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w==" + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" }, "deep-is": { "version": "0.1.3", @@ -4624,6 +4619,16 @@ "mjml": "3.3.5" }, "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "requires": { + "lru-cache": "4.1.1", + "shebang-command": "1.2.0", + "which": "1.3.0" + } + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -4632,6 +4637,11 @@ "ms": "2.0.0" } }, + "deep-extend": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.5.1.tgz", + "integrity": "sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w==" + }, "hoist-non-react-statics": { "version": "1.2.0", "resolved": "http://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz", @@ -4748,6 +4758,27 @@ "react": "15.6.2", "react-dom": "15.6.2", "warning": "3.0.0" + }, + "dependencies": { + "commander": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==" + }, + "juice": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/juice/-/juice-4.3.2.tgz", + "integrity": "sha512-3Qym/RnFoCGa9qrDz6xn4zRnohgI6G87xKWZV+/seF3dYpaVqNS1HijsDef+elGhytRY79RIboOzk0hucLtx6g==", + "requires": { + "cheerio": "0.22.0", + "commander": "2.19.0", + "cross-spawn": "5.1.0", + "deep-extend": "0.5.1", + "mensch": "0.3.3", + "slick": "1.12.2", + "web-resource-inliner": "4.2.1" + } + } } }, "mjml-divider": { @@ -4914,17 +4945,48 @@ } } }, - "grapesjs-plugin-ckeditor": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/grapesjs-plugin-ckeditor/-/grapesjs-plugin-ckeditor-0.0.9.tgz", - "integrity": "sha512-QXyAcSwgi09pzigGVS/NsHag5Skuw4zTkVGmEiBN/Qi8KU12/cQBG/OjAcjAB3/ZpToyPoglI33Ydjgj2nJuxQ==" - }, "grapesjs-preset-newsletter": { "version": "0.2.20", "resolved": "https://registry.npmjs.org/grapesjs-preset-newsletter/-/grapesjs-preset-newsletter-0.2.20.tgz", "integrity": "sha512-rffUeuznf9Saig+kIUddmGfhWwbLjxdaqAYf6Hoge4b0sfT8knOS4mQXJBdRsSROfzuRhFe6ybRHm4yC32lHxA==", "requires": { "juice": "4.3.2" + }, + "dependencies": { + "commander": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==" + }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "requires": { + "lru-cache": "4.1.1", + "shebang-command": "1.2.0", + "which": "1.3.0" + } + }, + "deep-extend": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.5.1.tgz", + "integrity": "sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w==" + }, + "juice": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/juice/-/juice-4.3.2.tgz", + "integrity": "sha512-3Qym/RnFoCGa9qrDz6xn4zRnohgI6G87xKWZV+/seF3dYpaVqNS1HijsDef+elGhytRY79RIboOzk0hucLtx6g==", + "requires": { + "cheerio": "0.22.0", + "commander": "2.19.0", + "cross-spawn": "5.1.0", + "deep-extend": "0.5.1", + "mensch": "0.3.3", + "slick": "1.12.2", + "web-resource-inliner": "4.2.1" + } + } } }, "har-validator": { @@ -5819,14 +5881,14 @@ } }, "juice": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/juice/-/juice-4.3.2.tgz", - "integrity": "sha512-3Qym/RnFoCGa9qrDz6xn4zRnohgI6G87xKWZV+/seF3dYpaVqNS1HijsDef+elGhytRY79RIboOzk0hucLtx6g==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/juice/-/juice-5.0.1.tgz", + "integrity": "sha512-3XJgQxfXo4uHGbCCI6hKwlVtovj0IM+2BVAUCUfWlIiOn1Mljsm4+pYLatOyzY6SF0ks7eT2MSUmOBvue/39sQ==", "requires": { "cheerio": "0.22.0", "commander": "2.19.0", - "cross-spawn": "5.1.0", - "deep-extend": "0.5.1", + "cross-spawn": "6.0.5", + "deep-extend": "0.6.0", "mensch": "0.3.3", "slick": "1.12.2", "web-resource-inliner": "4.2.1" @@ -5838,11 +5900,13 @@ "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==" }, "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "requires": { - "lru-cache": "4.1.1", + "nice-try": "1.0.5", + "path-key": "2.0.1", + "semver": "5.5.0", "shebang-command": "1.2.0", "which": "1.3.0" } @@ -6313,6 +6377,21 @@ "react": "15.6.2" }, "dependencies": { + "commander": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==" + }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "requires": { + "lru-cache": "4.1.1", + "shebang-command": "1.2.0", + "which": "1.3.0" + } + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -6321,6 +6400,11 @@ "ms": "2.0.0" } }, + "deep-extend": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.5.1.tgz", + "integrity": "sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w==" + }, "hoist-non-react-statics": { "version": "1.2.0", "resolved": "http://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz", @@ -6346,6 +6430,22 @@ "react": "15.6.2", "react-dom": "15.6.2", "warning": "3.0.0" + }, + "dependencies": { + "juice": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/juice/-/juice-4.3.2.tgz", + "integrity": "sha512-3Qym/RnFoCGa9qrDz6xn4zRnohgI6G87xKWZV+/seF3dYpaVqNS1HijsDef+elGhytRY79RIboOzk0hucLtx6g==", + "requires": { + "cheerio": "0.22.0", + "commander": "2.19.0", + "cross-spawn": "5.1.0", + "deep-extend": "0.5.1", + "mensch": "0.3.3", + "slick": "1.12.2", + "web-resource-inliner": "4.2.1" + } + } } }, "mjml-validator": { @@ -6369,6 +6469,21 @@ "react": "15.6.2" }, "dependencies": { + "commander": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==" + }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "requires": { + "lru-cache": "4.1.1", + "shebang-command": "1.2.0", + "which": "1.3.0" + } + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -6377,6 +6492,11 @@ "ms": "2.0.0" } }, + "deep-extend": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.5.1.tgz", + "integrity": "sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w==" + }, "hoist-non-react-statics": { "version": "1.2.0", "resolved": "http://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz", @@ -6402,6 +6522,22 @@ "react": "15.6.2", "react-dom": "15.6.2", "warning": "3.0.0" + }, + "dependencies": { + "juice": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/juice/-/juice-4.3.2.tgz", + "integrity": "sha512-3Qym/RnFoCGa9qrDz6xn4zRnohgI6G87xKWZV+/seF3dYpaVqNS1HijsDef+elGhytRY79RIboOzk0hucLtx6g==", + "requires": { + "cheerio": "0.22.0", + "commander": "2.19.0", + "cross-spawn": "5.1.0", + "deep-extend": "0.5.1", + "mensch": "0.3.3", + "slick": "1.12.2", + "web-resource-inliner": "4.2.1" + } + } } }, "mjml-validator": { @@ -6428,6 +6564,21 @@ "react": "15.6.2" }, "dependencies": { + "commander": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==" + }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "requires": { + "lru-cache": "4.1.1", + "shebang-command": "1.2.0", + "which": "1.3.0" + } + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -6436,6 +6587,11 @@ "ms": "2.0.0" } }, + "deep-extend": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.5.1.tgz", + "integrity": "sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w==" + }, "hoist-non-react-statics": { "version": "1.2.0", "resolved": "http://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz", @@ -6461,6 +6617,22 @@ "react": "15.6.2", "react-dom": "15.6.2", "warning": "3.0.0" + }, + "dependencies": { + "juice": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/juice/-/juice-4.3.2.tgz", + "integrity": "sha512-3Qym/RnFoCGa9qrDz6xn4zRnohgI6G87xKWZV+/seF3dYpaVqNS1HijsDef+elGhytRY79RIboOzk0hucLtx6g==", + "requires": { + "cheerio": "0.22.0", + "commander": "2.19.0", + "cross-spawn": "5.1.0", + "deep-extend": "0.5.1", + "mensch": "0.3.3", + "slick": "1.12.2", + "web-resource-inliner": "4.2.1" + } + } } }, "mjml-table": { @@ -6493,6 +6665,21 @@ "react": "15.6.2" }, "dependencies": { + "commander": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==" + }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "requires": { + "lru-cache": "4.1.1", + "shebang-command": "1.2.0", + "which": "1.3.0" + } + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -6501,6 +6688,11 @@ "ms": "2.0.0" } }, + "deep-extend": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.5.1.tgz", + "integrity": "sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w==" + }, "hoist-non-react-statics": { "version": "1.2.0", "resolved": "http://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz", @@ -6526,6 +6718,22 @@ "react": "15.6.2", "react-dom": "15.6.2", "warning": "3.0.0" + }, + "dependencies": { + "juice": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/juice/-/juice-4.3.2.tgz", + "integrity": "sha512-3Qym/RnFoCGa9qrDz6xn4zRnohgI6G87xKWZV+/seF3dYpaVqNS1HijsDef+elGhytRY79RIboOzk0hucLtx6g==", + "requires": { + "cheerio": "0.22.0", + "commander": "2.19.0", + "cross-spawn": "5.1.0", + "deep-extend": "0.5.1", + "mensch": "0.3.3", + "slick": "1.12.2", + "web-resource-inliner": "4.2.1" + } + } } }, "mjml-validator": { @@ -6551,6 +6759,21 @@ "react": "15.6.2" }, "dependencies": { + "commander": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==" + }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "requires": { + "lru-cache": "4.1.1", + "shebang-command": "1.2.0", + "which": "1.3.0" + } + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -6559,6 +6782,11 @@ "ms": "2.0.0" } }, + "deep-extend": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.5.1.tgz", + "integrity": "sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w==" + }, "hoist-non-react-statics": { "version": "1.2.0", "resolved": "http://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz", @@ -6584,6 +6812,22 @@ "react": "15.6.2", "react-dom": "15.6.2", "warning": "3.0.0" + }, + "dependencies": { + "juice": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/juice/-/juice-4.3.2.tgz", + "integrity": "sha512-3Qym/RnFoCGa9qrDz6xn4zRnohgI6G87xKWZV+/seF3dYpaVqNS1HijsDef+elGhytRY79RIboOzk0hucLtx6g==", + "requires": { + "cheerio": "0.22.0", + "commander": "2.19.0", + "cross-spawn": "5.1.0", + "deep-extend": "0.5.1", + "mensch": "0.3.3", + "slick": "1.12.2", + "web-resource-inliner": "4.2.1" + } + } } }, "mjml-image": { @@ -6631,6 +6875,40 @@ "warning": "4.0.2" }, "dependencies": { + "commander": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==" + }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "requires": { + "lru-cache": "4.1.1", + "shebang-command": "1.2.0", + "which": "1.3.0" + } + }, + "deep-extend": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.5.1.tgz", + "integrity": "sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w==" + }, + "juice": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/juice/-/juice-4.3.2.tgz", + "integrity": "sha512-3Qym/RnFoCGa9qrDz6xn4zRnohgI6G87xKWZV+/seF3dYpaVqNS1HijsDef+elGhytRY79RIboOzk0hucLtx6g==", + "requires": { + "cheerio": "0.22.0", + "commander": "2.19.0", + "cross-spawn": "5.1.0", + "deep-extend": "0.5.1", + "mensch": "0.3.3", + "slick": "1.12.2", + "web-resource-inliner": "4.2.1" + } + }, "warning": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.2.tgz", @@ -6683,6 +6961,11 @@ "integrity": "sha1-7XFfP+neArV6XmJS2QqWZ14fCFo=", "dev": true }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" + }, "no-case": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", @@ -7111,6 +7394,11 @@ "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", "dev": true }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + }, "path-parse": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", diff --git a/client/package.json b/client/package.json index 43940f59..c06e3994 100644 --- a/client/package.json +++ b/client/package.json @@ -43,6 +43,7 @@ "i18next": "^8.4.3", "i18next-xhr-backend": "^1.4.2", "immutable": "^3.8.1", + "juice": "^5.0.1", "mjml4-in-browser": "^1.0.1", "moment": "^2.18.1", "moment-timezone": "^0.5.13", diff --git a/client/src/lib/sandbox-common.scss b/client/src/lib/sandbox-common.scss new file mode 100644 index 00000000..d5bc6093 --- /dev/null +++ b/client/src/lib/sandbox-common.scss @@ -0,0 +1,71 @@ +$navbarHeight: 34px; +$editorNormalHeight: 800px !default; + +.editor { + .host { + @if $editorNormalHeight { + height: $editorNormalHeight; + } + } +} + +.editorFullscreen { + position: fixed; + top: 0px; + bottom: 0px; + left: 0px; + right: 0px; + z-index: 1000; + background: white; + margin-top: $navbarHeight; + + .navbar { + margin-top: -$navbarHeight; + } + + .host { + height: 100%; + } +} + +.navbar { + background: #DE4320; + width: 100%; + height: $navbarHeight; +} + +.logo { + float: left; + height: $navbarHeight; + padding: 5px 0 5px 10px; + filter: brightness(0) invert(1); +} + +.title { + padding: 5px 0 5px 10px; + font-size: 18px; + font-family: sans-serif; + font-family: "Ubuntu",Tahoma,"Helvetica Neue",Helvetica,Arial,sans-serif; + font-weight: bold; + float: left; + color: white; + height: $navbarHeight; +} + +.btn { + display: block; + float: right; + padding: 0px 15px; + line-height: $navbarHeight; + text-align: center; + color: white; + font-size: 14px; + font-weight: bold; + font-family: sans-serif; + cursor: pointer; +} + +.btn:hover { + background-color: #b1381e; + color: white; +} diff --git a/client/src/lib/sandboxed-ckeditor.scss b/client/src/lib/sandboxed-ckeditor.scss index 6b6f3b8d..2fba69c3 100644 --- a/client/src/lib/sandboxed-ckeditor.scss +++ b/client/src/lib/sandboxed-ckeditor.scss @@ -1,72 +1,7 @@ -$navbarHeight: 34px; - -.editor { - .host { - } -} +$editorNormalHeight: false; +@import "sandbox-common"; .sandbox { height: 100%; overflow: hidden; -} - -.editorFullscreen { - position: fixed; - top: 0px; - bottom: 0px; - left: 0px; - right: 0px; - z-index: 1000; - background: white; - margin-top: $navbarHeight; - - .navbar { - margin-top: -$navbarHeight; - } - - .host { - height: 100%; - } -} - -.navbar { - background: #DE4320; - width: 100%; - height: $navbarHeight; -} - -.logo { - float: left; - height: $navbarHeight; - padding: 5px 0 5px 10px; - filter: brightness(0) invert(1); -} - -.title { - padding: 5px 0 5px 10px; - font-size: 18px; - font-family: sans-serif; - font-family: "Ubuntu",Tahoma,"Helvetica Neue",Helvetica,Arial,sans-serif; - font-weight: bold; - float: left; - color: white; - height: $navbarHeight; -} - -.btn { - display: block; - float: right; - padding: 0px 15px; - line-height: $navbarHeight; - text-align: center; - color: white; - font-size: 14px; - font-weight: bold; - font-family: sans-serif; - cursor: pointer; -} - -.btn:hover { - background-color: #b1381e; - color: white; -} +} \ No newline at end of file diff --git a/client/src/lib/sandboxed-codeeditor-root.js b/client/src/lib/sandboxed-codeeditor-root.js new file mode 100644 index 00000000..7a6c3b97 --- /dev/null +++ b/client/src/lib/sandboxed-codeeditor-root.js @@ -0,0 +1,166 @@ +'use strict'; + +import './public-path'; + +import React, {Component} from 'react'; +import ReactDOM + from 'react-dom'; +import { + I18nextProvider, + translate, +} from 'react-i18next'; +import i18n + from './i18n'; +import { + parentRPC, + UntrustedContentRoot +} from './untrusted'; +import PropTypes + from "prop-types"; +import styles + from "./sandboxed-codeeditor.scss"; +import { + getPublicUrl, + getSandboxUrl, + getTrustedUrl +} from "./urls"; +import { + base, + unbase +} from "../../../shared/templates"; + +import brace from 'brace'; +import ACEEditorRaw from 'react-ace'; +import 'brace/theme/github'; +import 'brace/ext/searchbox'; +import 'brace/mode/html'; +import {CodeEditorSourceType} from "./sandboxed-codeeditor-shared"; + +import mjml2html from "mjml4-in-browser"; +import juice from "juice"; + +@translate(null, { withRef: true }) +class CodeEditorSandbox extends Component { + constructor(props) { + super(props); + + let defaultSource; + + if (props.sourceType === CodeEditorSourceType.MJML) { + defaultSource = + '\n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ''; + + } else if (props.sourceType === CodeEditorSourceType.HTML) { + defaultSource = + '\n' + + '\n' + + '\n' + + ' \n' + + ' Title of the document\n' + + '\n' + + '\n' + + ' Content of the document......\n' + + '\n' + + ''; + } + + + const trustedUrlBase = getTrustedUrl(); + const sandboxUrlBase = getSandboxUrl(); + const publicUrlBase = getPublicUrl(); + const source = this.props.initialSource ? base(this.props.initialSource, trustedUrlBase, sandboxUrlBase, publicUrlBase) : defaultSource; + + this.state = { + source, + preview: props.initialPreview + }; + } + + static propTypes = { + entityTypeId: PropTypes.string, + entityId: PropTypes.number, + initialSource: PropTypes.string, + sourceType: PropTypes.string, + initialPreview: PropTypes.bool + } + + async exportState(method, params) { + const trustedUrlBase = getTrustedUrl(); + const sandboxUrlBase = getSandboxUrl(); + const publicUrlBase = getPublicUrl(); + return { + source: unbase(this.state.source, trustedUrlBase, sandboxUrlBase, publicUrlBase, true) + }; + } + + async setPreview(method, preview) { + this.setState({ + preview + }); + } + + componentDidMount() { + parentRPC.setMethodHandler('exportState', ::this.exportState); + parentRPC.setMethodHandler('setPreview', ::this.setPreview); + } + + render() { + let previewContents; + + if (this.props.sourceType === CodeEditorSourceType.MJML) { + const res = mjml2html(this.state.source); + previewContents = res.html; + } else if (this.props.sourceType === CodeEditorSourceType.HTML) { + previewContents = juice(this.state.source); + } + + return ( +
+
+ this.setState({source: data})} + fontSize={12} + showPrintMargin={false} + value={this.state.source} + tabSize={2} + setOptions={{useWorker: false}} // This disables syntax check because it does not always work well (e.g. in case of JS code in report templates) + /> +
+ { + this.state.preview && +
+ +
+ } +
+ ); + } +} + +export default function() { + parentRPC.init(); + + ReactDOM.render( + + } /> + , + document.getElementById('root') + ); +}; + + diff --git a/client/src/lib/sandboxed-codeeditor-shared.js b/client/src/lib/sandboxed-codeeditor-shared.js new file mode 100644 index 00000000..a3e830f1 --- /dev/null +++ b/client/src/lib/sandboxed-codeeditor-shared.js @@ -0,0 +1,11 @@ +'use strict'; + +export const CodeEditorSourceType = { + MJML: 'mjml', + HTML: 'html' +}; + +export const getCodeEditorSourceTypeOptions = t => [ + {key: CodeEditorSourceType.MJML, label: t('MJML')}, + {key: CodeEditorSourceType.HTML, label: t('HTML')} +]; diff --git a/client/src/lib/sandboxed-codeeditor.js b/client/src/lib/sandboxed-codeeditor.js new file mode 100644 index 00000000..aed4caff --- /dev/null +++ b/client/src/lib/sandboxed-codeeditor.js @@ -0,0 +1,86 @@ +'use strict'; + +import React, {Component} from 'react'; +import {translate} from 'react-i18next'; +import PropTypes + from "prop-types"; +import styles + from "./sandboxed-codeeditor.scss"; + +import {UntrustedContentHost} from './untrusted'; +import {Icon} from "./bootstrap-components"; +import {getTrustedUrl} from "./urls"; + +@translate(null, { withRef: true }) +export class CodeEditorHost extends Component { + constructor(props) { + super(props); + + this.state = { + fullscreen: false, + preview: true + } + } + + static propTypes = { + entityTypeId: PropTypes.string, + entity: PropTypes.object, + initialSource: PropTypes.string, + sourceType: PropTypes.string, + title: PropTypes.string, + onFullscreenAsync: PropTypes.func + } + + async toggleFullscreenAsync() { + const fullscreen = !this.state.fullscreen; + this.setState({ + fullscreen + }); + await this.props.onFullscreenAsync(fullscreen); + } + + async togglePreviewAsync() { + const preview = !this.state.preview; + this.setState({ + preview + }); + + await this.contentNode.ask('setPreview', preview); + } + async exportState() { + return await this.contentNode.ask('exportState'); + } + + render() { + const t = this.props.t; + + const editorData = { + entityTypeId: this.props.entityTypeId, + entityId: this.props.entity.id, + initialSource: this.props.initialSource, + sourceType: this.props.sourceType, + initialPreview: this.state.preview + }; + + const tokenData = { + entityTypeId: this.props.entityTypeId, + entityId: this.props.entity.id + }; + + return ( +
+
+ {this.state.fullscreen && } +
{this.props.title}
+ + +
+ this.contentNode = node} className={styles.host} singleToken={true} contentProps={editorData} contentSrc="codeeditor/editor" tokenMethod="codeeditor" tokenParams={tokenData}/> +
+ ); + } +} + +CodeEditorHost.prototype.exportState = async function() { + return await this.getWrappedInstance().exportState(); +}; diff --git a/client/src/lib/sandboxed-codeeditor.scss b/client/src/lib/sandboxed-codeeditor.scss new file mode 100644 index 00000000..0cbca4a2 --- /dev/null +++ b/client/src/lib/sandboxed-codeeditor.scss @@ -0,0 +1,35 @@ +@import "sandbox-common"; + +.sandbox { +} + +.aceEditorWithPreview, .aceEditorWithoutPreview, .preview { + position: absolute; + height: 100%; +} + +.aceEditorWithPreview { + border-right: #e8e8e8 solid 2px; + width: 50%; +} + +.aceEditorWithoutPreview { + width: 100%; +} + +.preview { + border-left: #e8e8e8 solid 2px; + width: 50%; + left: 50%; + overflow: hidden; + + iframe { + width: 100%; + height: 100%; + border: 0px none; + + body { + margin: 0px; + } + } +} \ No newline at end of file diff --git a/client/src/lib/sandboxed-grapesjs-shared.js b/client/src/lib/sandboxed-grapesjs-shared.js index 339580c4..75c82545 100644 --- a/client/src/lib/sandboxed-grapesjs-shared.js +++ b/client/src/lib/sandboxed-grapesjs-shared.js @@ -4,3 +4,8 @@ export const GrapesJSSourceType = { MJML: 'mjml', HTML: 'html' }; + +export const getGrapesJSSourceTypeOptions = t => [ + {key: GrapesJSSourceType.MJML, label: t('MJML')}, + {key: GrapesJSSourceType.HTML, label: t('HTML')} +]; diff --git a/client/src/lib/sandboxed-grapesjs.js b/client/src/lib/sandboxed-grapesjs.js index 1dabee78..49a91239 100644 --- a/client/src/lib/sandboxed-grapesjs.js +++ b/client/src/lib/sandboxed-grapesjs.js @@ -10,7 +10,6 @@ import styles import {UntrustedContentHost} from './untrusted'; import {Icon} from "./bootstrap-components"; import {getTrustedUrl} from "./urls"; -import {GrapesJSSourceType} from "./sandboxed-grapesjs-shared"; @translate(null, { withRef: true }) export class GrapesJSHost extends Component { diff --git a/client/src/lib/sandboxed-grapesjs.scss b/client/src/lib/sandboxed-grapesjs.scss index cdffac64..4907b12e 100644 --- a/client/src/lib/sandboxed-grapesjs.scss +++ b/client/src/lib/sandboxed-grapesjs.scss @@ -1,72 +1,4 @@ -$navbarHeight: 34px; - -.editor { - .host { - height: 800px; - } -} - -.editorFullscreen { - position: fixed; - top: 0px; - bottom: 0px; - left: 0px; - right: 0px; - z-index: 1000; - background: white; - margin-top: $navbarHeight; - - .navbar { - margin-top: -$navbarHeight; - } - - .host { - height: 100%; - } -} - -.navbar { - background: #DE4320; - width: 100%; - height: $navbarHeight; -} - -.logo { - float: left; - height: $navbarHeight; - padding: 5px 0 5px 10px; - filter: brightness(0) invert(1); -} - -.title { - padding: 5px 0 5px 10px; - font-size: 18px; - font-family: sans-serif; - font-family: "Ubuntu",Tahoma,"Helvetica Neue",Helvetica,Arial,sans-serif; - font-weight: bold; - float: left; - color: white; - height: $navbarHeight; -} - -.btn { - display: block; - float: right; - padding: 0px 15px; - line-height: $navbarHeight; - text-align: center; - color: white; - font-size: 14px; - font-weight: bold; - font-family: sans-serif; - cursor: pointer; -} - -.btn:hover { - background-color: #b1381e; - color: white; -} - +@import "sandbox-common"; :global .grapesjs-body { margin: 0px; diff --git a/client/src/lib/sandboxed-mosaico.scss b/client/src/lib/sandboxed-mosaico.scss index c289714d..baafeafe 100644 --- a/client/src/lib/sandboxed-mosaico.scss +++ b/client/src/lib/sandboxed-mosaico.scss @@ -1,29 +1,4 @@ -$navbarHeight: 34px; - -.editor { - .host { - height: 800px; - } -} - -.editorFullscreen { - position: fixed; - top: 0px; - bottom: 0px; - left: 0px; - right: 0px; - z-index: 1000; - background: white; - margin-top: $navbarHeight; - - .navbar { - margin-top: -$navbarHeight; - } - - .host { - height: 100%; - } -} +@import "sandbox-common"; :global .mo-standalone { top: 0px; @@ -31,45 +6,3 @@ $navbarHeight: 34px; width: 100%; position: absolute; } - -.navbar { - background: #DE4320; - width: 100%; - height: $navbarHeight; -} - -.logo { - float: left; - height: $navbarHeight; - padding: 5px 0 5px 10px; - filter: brightness(0) invert(1); -} - -.title { - padding: 5px 0 5px 10px; - font-size: 18px; - font-family: sans-serif; - font-family: "Ubuntu",Tahoma,"Helvetica Neue",Helvetica,Arial,sans-serif; - font-weight: bold; - float: left; - color: white; - height: $navbarHeight; -} - -.btn { - display: block; - float: right; - padding: 0px 15px; - line-height: $navbarHeight; - text-align: center; - color: white; - font-size: 14px; - font-weight: bold; - font-family: sans-serif; - cursor: pointer; -} - -.btn:hover { - background-color: #b1381e; - color: white; -} diff --git a/client/src/templates/helpers.js b/client/src/templates/helpers.js index 7d71a29c..09ca8ca4 100644 --- a/client/src/templates/helpers.js +++ b/client/src/templates/helpers.js @@ -4,6 +4,7 @@ import React from "react"; import { ACEEditor, AlignedRow, + CheckBox, CKEditor, Dropdown, StaticField, @@ -15,6 +16,17 @@ import 'brace/mode/html'; import { MosaicoHost } from "../lib/sandboxed-mosaico"; import { CKEditorHost } from "../lib/sandboxed-ckeditor"; import { GrapesJSHost } from "../lib/sandboxed-grapesjs"; +import { CodeEditorHost } from "../lib/sandboxed-codeeditor"; + +import { + getGrapesJSSourceTypeOptions, + GrapesJSSourceType +} from "../lib/sandboxed-grapesjs-shared"; + +import { + getCodeEditorSourceTypeOptions, + CodeEditorSourceType +} from "../lib/sandboxed-codeeditor-shared"; import {getTemplateTypes as getMosaicoTemplateTypes} from './mosaico/helpers'; import {getSandboxUrl} from "../lib/urls"; @@ -26,7 +38,6 @@ import { import {Trans} from "react-i18next"; import styles from "../lib/styles.scss"; -import {GrapesJSSourceType} from "../lib/sandboxed-grapesjs-shared"; export const ResourceType = { TEMPLATE: 'template', @@ -177,21 +188,17 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM }; - const grapesJSSourceTypes = [ - {key: GrapesJSSourceType.MJML, label: t('MJML')}, - {key: GrapesJSSourceType.HTML, label: t('HTML')} - ]; - + const grapesJSSourceTypes = getGrapesJSSourceTypeOptions(t); const grapesJSSourceTypeLabels = {}; for ({key, label} of grapesJSSourceTypes) { grapesJSSourceTypeLabels[key] = label; } templateTypes.grapesjs = { - typeName: t('GrapeJS'), + typeName: t('GrapesJS'), getTypeForm: (owner, isEdit) => { if (isEdit) { - return {grapesJSSourceTypeLabels[(owner.getFormValue(prefix + 'grapesJSSourceType'))]}; + return {grapesJSSourceTypeLabels[owner.getFormValue(prefix + 'grapesJSSourceType')]}; } else { return ; } @@ -283,30 +290,62 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM validate: state => {} }; + const codeEditorSourceTypes = getCodeEditorSourceTypeOptions(t); + const codeEditorSourceTypeLabels = {}; + for ({key, label} of codeEditorSourceTypes) { + codeEditorSourceTypeLabels[key] = label; + } + templateTypes.codeeditor = { typeName: t('Code Editor'), - getTypeForm: (owner, isEdit) => null, - getHTMLEditor: owner => , - exportHTMLEditorData: async owner => {}, - initData: () => ({}), - afterLoad: data => {}, - beforeSave: data => { - clearBeforeSave(data); + getTypeForm: (owner, isEdit) => { + const sourceType = owner.getFormValue(prefix + 'codeEditorSourceType'); + if (isEdit) { + return {codeEditorSourceTypeLabels[sourceType]}; + } else { + return ; + } }, - afterTypeChange: mutState => {}, - validate: state => {} - }; + getHTMLEditor: owner => + + owner.editorNode = node} + entity={owner.props.entity} + entityTypeId={entityTypeId} + initialSource={owner.getFormValue(prefix + 'codeEditorData').source} + sourceType={owner.getFormValue(prefix + 'codeEditorSourceType')} + title={t('Code Editor Template Designer')} + onFullscreenAsync={::owner.setElementInFullscreen} + /> + , + exportHTMLEditorData: async owner => { + const {html, source} = await owner.editorNode.exportState(); + owner.updateFormValue(prefix + 'html', html); + owner.updateFormValue(prefix + 'codeEditorData', { + source + }); + }, + initData: () => ({ + [prefix + 'codeEditorSourceType']: CodeEditorSourceType.HTML, + [prefix + 'codeEditorData']: {} + }), + afterLoad: data => { + data[prefix + 'codeEditorSourceType'] = data[prefix + 'data'].sourceType; + data[prefix + 'codeEditorData'] = { + source: data[prefix + 'data'].source + }; + }, + beforeSave: data => { + data[prefix + 'data'] = { + sourceType: data[prefix + 'codeEditorSourceType'], + source: data[prefix + 'codeEditorData'].source + }; - templateTypes.mjml = { // FIXME - getTypeForm: (owner, isEdit) => null, - getHTMLEditor: owner => null, - exportHTMLEditorData: async owner => {}, - initData: () => ({}), - afterLoad: data => {}, - beforeSave: data => { clearBeforeSave(data); }, - afterTypeChange: mutState => {}, + afterTypeChange: mutState => { + initFieldsIfMissing(mutState, 'codeeditor'); + }, validate: state => {} }; diff --git a/client/src/templates/mosaico/helpers.js b/client/src/templates/mosaico/helpers.js index 9f2e052c..891b390a 100644 --- a/client/src/templates/mosaico/helpers.js +++ b/client/src/templates/mosaico/helpers.js @@ -6,7 +6,7 @@ import 'brace/mode/html' import 'brace/mode/xml' export function getTemplateTypesOrder() { - return ['mjml', 'html']; + return [/* 'mjml' , */ 'html']; } export function getTemplateTypes(t) { diff --git a/client/webpack.config.js b/client/webpack.config.js index 06d2e303..c154847e 100644 --- a/client/webpack.config.js +++ b/client/webpack.config.js @@ -17,6 +17,7 @@ module.exports = { "mosaico-root": ['babel-polyfill', './src/lib/sandboxed-mosaico-root.js'], "ckeditor-root": ['babel-polyfill', './src/lib/sandboxed-ckeditor-root.js'], "grapesjs-root": ['babel-polyfill', './src/lib/sandboxed-grapesjs-root.js'], + "codeeditor-root": ['babel-polyfill', './src/lib/sandboxed-codeeditor-root.js'], }, output: { library: 'MailtrainReactBody', diff --git a/config/default.yaml b/config/default.yaml index 15369125..6c0d611a 100644 --- a/config/default.yaml +++ b/config/default.yaml @@ -16,12 +16,12 @@ title: mailtrain # Enabled HTML editors editors: -- ckeditor4 -- ckeditor5 -- codeeditor +- grapesjs - mosaico - mosaicoWithFsTemplate -- grapesjs +- ckeditor5 +- ckeditor4 +- codeeditor # Default language to use language: en diff --git a/lib/tools.js b/lib/tools.js index 8ba43879..ad696aaa 100644 --- a/lib/tools.js +++ b/lib/tools.js @@ -150,7 +150,7 @@ async function prepareHtml(html) { FetchExternalResources: false, // disables resource loading over HTTP / filesystem ProcessExternalResources: false // do not execute JS within script blocks } - }); + });pre const head = win.document.querySelector('head'); let hasCharsetTag = false; diff --git a/routes/sandboxed-ckeditor.js b/routes/sandboxed-ckeditor.js index 8701832e..47461bfd 100644 --- a/routes/sandboxed-ckeditor.js +++ b/routes/sandboxed-ckeditor.js @@ -20,7 +20,7 @@ users.registerRestrictedAccessTokenMethod('ckeditor', async ({entityTypeId, enti if (entityTypeId === 'template') { const tmpl = await templates.getById(contextHelpers.getAdminContext(), entityId, false); - if (tmpl.type === 'ckeditor') { + if (tmpl.type === 'ckeditor4') { return { permissions: { 'template': { diff --git a/routes/sandboxed-codeeditor.js b/routes/sandboxed-codeeditor.js new file mode 100644 index 00000000..055d3e4c --- /dev/null +++ b/routes/sandboxed-codeeditor.js @@ -0,0 +1,59 @@ +'use strict'; + +const routerFactory = require('../lib/router-async'); +const passport = require('../lib/passport'); +const clientHelpers = require('../lib/client-helpers'); +const users = require('../models/users'); + +const files = require('../models/files'); +const fileHelpers = require('../lib/file-helpers'); + +const templates = require('../models/templates'); + +const contextHelpers = require('../lib/context-helpers'); + +const { getTrustedUrl, getSandboxUrl, getPublicUrl } = require('../lib/urls'); +const { AppType } = require('../shared/app'); + + +users.registerRestrictedAccessTokenMethod('codeeditor', async ({entityTypeId, entityId}) => { + if (entityTypeId === 'template') { + const tmpl = await templates.getById(contextHelpers.getAdminContext(), entityId, false); + + if (tmpl.type === 'codeeditor') { + return { + permissions: { + 'template': { + [entityId]: new Set(['manageFiles', 'view']) + } + } + }; + } + } +}); + + +function getRouter(appType) { + const router = routerFactory.create(); + + if (appType === AppType.SANDBOXED) { + router.getAsync('/editor', passport.csrfProtection, async (req, res) => { + const mailtrainConfig = await clientHelpers.getAnonymousConfig(req.context, appType); + + res.render('ckeditor/root', { + layout: 'ckeditor/layout', + reactCsrfToken: req.csrfToken(), + mailtrainConfig: JSON.stringify(mailtrainConfig), + scriptFiles: [ + getSandboxUrl('mailtrain/common.js'), + getSandboxUrl('mailtrain/codeeditor-root.js') + ], + publicPath: getSandboxUrl() + }); + }); + } + + return router; +} + +module.exports.getRouter = getRouter; diff --git a/views/codeeditor/layout.hbs b/views/codeeditor/layout.hbs new file mode 100644 index 00000000..f044a18b --- /dev/null +++ b/views/codeeditor/layout.hbs @@ -0,0 +1,35 @@ + + + + + + + + + + + + + Mailtrain + + + + + + {{#if mailtrainConfig}} + + + {{#each scriptFiles}} + + {{/each}} + {{/if}} + + + +{{{body}}} + + + diff --git a/views/codeeditor/root.hbs b/views/codeeditor/root.hbs new file mode 100644 index 00000000..53ae02fe --- /dev/null +++ b/views/codeeditor/root.hbs @@ -0,0 +1,6 @@ +
+ \ No newline at end of file diff --git a/workers/reports/config/default.toml b/workers/reports/config/default.toml deleted file mode 100644 index 9e7fb70d..00000000 --- a/workers/reports/config/default.toml +++ /dev/null @@ -1,18 +0,0 @@ -# Process title visible in monitoring logs and process listing -title="mailtrain" - -# Default language to use -language="en" - -[log] -# silly|verbose|info|http|warn|error|silent -level="verbose" - -[mysql] -host="localhost" -user="mailtrain" -password="mailtrain" -database="mailtrain" -port=3306 -charset="utf8mb4" -timezone="local" diff --git a/workers/reports/config/default.yaml b/workers/reports/config/default.yaml new file mode 100644 index 00000000..99575b7f --- /dev/null +++ b/workers/reports/config/default.yaml @@ -0,0 +1,22 @@ +# Process title visible in monitoring logs and process listing +title: mailtrain + +# Default language to use +language: en + +log: + # silly|verbose|info|http|warn|error|silent + level: verbose + +mysql: + host: localhost + user: mailtrain + password: mailtrain + database: mailtrain + # Some installations, eg. MAMP can use a different port (8889) + # MAMP users should also turn on Allow network access to MySQL otherwise MySQL might not be accessible + port: 3306 + charset: utf8mb4 + # The timezone configured on the MySQL server. This can be 'local', 'Z', or an offset in the form +HH:MM or -HH:MM + timezone: local +