Added sandboxed CKEditor 4 as a template editor

This commit is contained in:
Tomas Bures 2018-11-04 11:19:34 +01:00
parent eacdc74c29
commit 02a7275ae4
23 changed files with 1299 additions and 377 deletions

View file

@ -10,7 +10,7 @@
* Fixed security issue where description tags were able to include script tags. Reported by Andreas Lindh. Fixed with [ae6affda](https://github.com/Mailtrain-org/mailtrain/commit/ae6affda8193f034e06f7e095ee23821a83d5190) * Fixed security issue where description tags were able to include script tags. Reported by Andreas Lindh. Fixed with [ae6affda](https://github.com/Mailtrain-org/mailtrain/commit/ae6affda8193f034e06f7e095ee23821a83d5190)
* Fixed security issue where templates that looked like file paths loaded content from arbitrary files. Reported by Andreas Lindh. Fixed with [0879fa41](https://github.com/Mailtrain-org/mailtrain/commit/0879fa412a2d4a417aeca5cd5092a8f86531e7ef) * Fixed security issue where templates that looked like file paths loaded content from arbitrary files. Reported by Andreas Lindh. Fixed with [0879fa41](https://github.com/Mailtrain-org/mailtrain/commit/0879fa412a2d4a417aeca5cd5092a8f86531e7ef)
* Fixed security issue where users were able to use html tags in subscription values. Reported by Andreas Lindh. Fixed with [9d5fb816](https://github.com/Mailtrain-org/mailtrain/commit/9d5fb816c937114966d4f589e1ad4e164ff3a187) * Fixed security issue where users were able to use html tags in subscription values. Reported by Andreas Lindh. Fixed with [9d5fb816](https://github.com/Mailtrain-org/mailtrain/commit/9d5fb816c937114966d4f589e1ad4e164ff3a187)
* Support for multiple HTML editors (Mosaico, GrapeJS, Summernote, HTML code) * Support for multiple HTML editors (Mosaico, Grapesjs, Summernote, HTML code)
## 1.22.0 2017-03-02 ## 1.22.0 2017-03-02

View file

@ -3,7 +3,7 @@
### Templates ### Templates
- Add MJML template editor - Add MJML template editor
- Include GrapeJS with MJML support - Include Grapesjs with MJML support
- CKEditor to sandbox - CKEditor to sandbox
- Add Files support to CKEditor - Add Files support to CKEditor

View file

@ -5,7 +5,7 @@ The migration should happen almost automatically. There are however the followin
1. Structure of config files (under `config`) has changed at many places. Revisit the default config (`config/default.toml`) 1. Structure of config files (under `config`) has changed at many places. Revisit the default config (`config/default.toml`)
and update your configs accordingly. and update your configs accordingly.
2. Images uploaded in a template editor (Mosaico, GrapeJS, etc.) need to be manually moved to a new destination (under `client`). 2. Images uploaded in a template editor (Mosaico, Grapesjs, etc.) need to be manually moved to a new destination (under `client`).
For Mosaico, this means to move folders named by a number from `public/mosaico` to `client/static/mosaico`. For Mosaico, this means to move folders named by a number from `public/mosaico` to `client/static/mosaico`.
3. Directory for custom Mosaico templates has changed from `public/mosaico/templates` to `client/static/mosaico/templates`. 3. Directory for custom Mosaico templates has changed from `public/mosaico/templates` to `client/static/mosaico/templates`.

1360
client/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -37,6 +37,8 @@
"axios": "^0.16.2", "axios": "^0.16.2",
"datatables.net": "^1.10.15", "datatables.net": "^1.10.15",
"datatables.net-bs": "^1.10.15", "datatables.net-bs": "^1.10.15",
"grapesjs": "^0.14.40",
"grapesjs-mjml": "0.0.27",
"i18next": "^8.4.3", "i18next": "^8.4.3",
"i18next-xhr-backend": "^1.4.2", "i18next-xhr-backend": "^1.4.2",
"immutable": "^3.8.1", "immutable": "^3.8.1",
@ -46,6 +48,7 @@
"querystringify": "^1.0.0", "querystringify": "^1.0.0",
"react": "^15.6.1", "react": "^15.6.1",
"react-ace": "^5.1.0", "react-ace": "^5.1.0",
"react-ckeditor-component": "^1.1.0",
"react-day-picker": "^6.1.0", "react-day-picker": "^6.1.0",
"react-dnd-html5-backend": "^2.4.1", "react-dnd-html5-backend": "^2.4.1",
"react-dnd-touch-backend": "^0.3.13", "react-dnd-touch-backend": "^0.3.13",

View file

@ -17,7 +17,7 @@ import ImagePlugin from '@ckeditor/ckeditor5-image/src/image';
import ImageCaptionPlugin from '@ckeditor/ckeditor5-image/src/imagecaption'; import ImageCaptionPlugin from '@ckeditor/ckeditor5-image/src/imagecaption';
import ImageStylePlugin from '@ckeditor/ckeditor5-image/src/imagestyle'; import ImageStylePlugin from '@ckeditor/ckeditor5-image/src/imagestyle';
import ImageToolbarPlugin from '@ckeditor/ckeditor5-image/src/imagetoolbar'; import ImageToolbarPlugin from '@ckeditor/ckeditor5-image/src/imagetoolbar';
import ImageUploadPlugin from '@ckeditor/ckeditor5-image/src/imageupload'; //import ImageUploadPlugin from '@ckeditor/ckeditor5-image/src/imageupload';
import LinkPlugin from '@ckeditor/ckeditor5-link/src/link'; import LinkPlugin from '@ckeditor/ckeditor5-link/src/link';
import ListPlugin from '@ckeditor/ckeditor5-list/src/list'; import ListPlugin from '@ckeditor/ckeditor5-list/src/list';
import ParagraphPlugin from '@ckeditor/ckeditor5-paragraph/src/paragraph'; import ParagraphPlugin from '@ckeditor/ckeditor5-paragraph/src/paragraph';
@ -75,6 +75,8 @@ class InsertImage extends Plugin {
} }
} }
/*
Upload through CKEditor is disable because files can be managed by Files tab
class UploadAdapter { class UploadAdapter {
constructor(loader, url, t) { constructor(loader, url, t) {
@ -105,6 +107,7 @@ class MailtrainUploadAdapter extends Plugin {
this.editor.plugins.get(FileRepository).createUploadAdapter = loader => new UploadAdapter(loader, this.editor.t); this.editor.plugins.get(FileRepository).createUploadAdapter = loader => new UploadAdapter(loader, this.editor.t);
} }
} }
*/
@ -123,14 +126,14 @@ ClassicEditor.builtinPlugins = [
ImageCaptionPlugin, ImageCaptionPlugin,
ImageStylePlugin, ImageStylePlugin,
ImageToolbarPlugin, ImageToolbarPlugin,
ImageUploadPlugin, //ImageUploadPlugin,
LinkPlugin, LinkPlugin,
ListPlugin, ListPlugin,
ParagraphPlugin, ParagraphPlugin,
AlignmentPlugin, AlignmentPlugin,
TablePlugin, TablePlugin,
TableToolbarPlugin, TableToolbarPlugin,
MailtrainUploadAdapter, //MailtrainUploadAdapter,
InsertImage InsertImage
]; ];
@ -152,7 +155,7 @@ ClassicEditor.defaultConfig = {
'numberedList', 'numberedList',
'|', '|',
'insertImage', 'insertImage',
'imageUpload', // 'imageUpload',
'blockQuote', 'blockQuote',
'|', '|',
'insertTable', 'insertTable',

View file

@ -17,7 +17,7 @@ import ACEEditorRaw from 'react-ace';
import 'brace/theme/github'; import 'brace/theme/github';
import 'brace/ext/searchbox'; import 'brace/ext/searchbox';
import CKEditorRaw from './ckeditor'; import CKEditorRaw from './ckeditor5';
import DayPicker from 'react-day-picker'; import DayPicker from 'react-day-picker';
import 'react-day-picker/lib/style.css'; import 'react-day-picker/lib/style.css';

View file

@ -0,0 +1,48 @@
'use strict';
import React, {Component} from 'react';
import 'grapesjs/dist/css/grapes.min.css';
import grapesjs from 'grapesjs';
import grapesjsMjml from 'grapesjs-mjml';
export default class GrapesJs extends Component {
constructor(props) {
super(props);
this.state = {
};
}
componentDidMount() {
const editor = grapesjs.init({
container: this.canvasNode,
height: '100%',
width: 'auto',
noticeOnUnload: 0,
storageManager:{autoload: 0},
fromElement: false,
components:
' <mj-container>\n' +
' <mj-section>\n' +
' <mj-column>\n' +
' <mj-text>My Company</mj-text>\n' +
' </mj-column>\n' +
' </mj-section>\n' +
' <mj-container>',
plugins: ['gjs-mjml'],
pluginsOpts: {
'gjs-mjml': {}
}
});
}
render() {
return (
<div>
<div ref={node => this.canvasNode = node}/>
</div>
);
}
}

View file

@ -0,0 +1,5 @@
.gjs-cv-canvas {
top: 0;
width: 100%;
height: 100%;
}

View file

@ -16,26 +16,66 @@ import {
base, base,
unbase unbase
} from "../../../shared/templates"; } from "../../../shared/templates";
import CKEditor from './ckeditor'; import CKEditor from "react-ckeditor-component";
const initialHeight = 600;
const navbarHeight = 34; // Sync this with navbarheight in sandboxed-ckeditor.scss
@translate(null, { withRef: true }) @translate(null, { withRef: true })
export class CKEditorHost extends Component { export class CKEditorHost extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = {} this.state = {
fullscreen: false
}
this.onWindowResizeHandler = ::this.onWindowResize;
} }
static propTypes = { static propTypes = {
entityTypeId: PropTypes.string, entityTypeId: PropTypes.string,
entity: PropTypes.object, entity: PropTypes.object,
initialHtml: PropTypes.string initialHtml: PropTypes.string,
title: PropTypes.string,
onFullscreenAsync: PropTypes.func
}
async toggleFullscreenAsync() {
const fullscreen = !this.state.fullscreen;
this.setState({
fullscreen
});
await this.props.onFullscreenAsync(fullscreen);
let newHeight;
if (fullscreen) {
newHeight = window.innerHeight - navbarHeight;
} else {
newHeight = initialHeight;
}
await this.contentNode.ask('setHeight', newHeight);
} }
async exportState() { async exportState() {
return await this.contentNode.ask('exportState'); return await this.contentNode.ask('exportState');
} }
onWindowResize() {
if (this.state.fullscreen) {
const newHeight = window.innerHeight - navbarHeight;
this.contentNode.ask('setHeight', newHeight);
}
}
componentDidMount() {
window.addEventListener('resize', this.onWindowResizeHandler, false);
}
componentWillUnmount() {
window.removeEventListener('resize', this.onWindowResizeHandler, false);
}
render() { render() {
const t = this.props.t; const t = this.props.t;
@ -51,7 +91,12 @@ export class CKEditorHost extends Component {
}; };
return ( return (
<div className={styles.editor}> <div className={this.state.fullscreen ? styles.editorFullscreen : styles.editor}>
<div className={styles.navbar}>
{this.state.fullscreen && <img className={styles.logo} src={getTrustedUrl('static/mailtrain-notext.png')}/>}
<div className={styles.title}>{this.props.title}</div>
<a className={styles.btn} onClick={::this.toggleFullscreenAsync}><Icon icon="fullscreen"/></a>
</div>
<UntrustedContentHost ref={node => this.contentNode = node} className={styles.host} singleToken={true} contentProps={editorData} contentSrc="ckeditor/editor" tokenMethod="ckeditor" tokenParams={editorData}/> <UntrustedContentHost ref={node => this.contentNode = node} className={styles.host} singleToken={true} contentProps={editorData} contentSrc="ckeditor/editor" tokenMethod="ckeditor" tokenParams={editorData}/>
</div> </div>
); );
@ -93,16 +138,30 @@ export class CKEditorSandbox extends Component {
}; };
} }
async setHeight(methods, params) {
this.node.editorInstance.resize('100%', params);
}
componentDidMount() { componentDidMount() {
parentRPC.setMethodHandler('exportState', ::this.exportState); parentRPC.setMethodHandler('exportState', ::this.exportState);
parentRPC.setMethodHandler('setHeight', ::this.setHeight);
} }
render() { render() {
const config = {
removeButtons: 'Underline,Subscript,Superscript,Maximize',
resize_enabled: false,
height: initialHeight
};
return ( return (
<div className={styles.sandbox}> <div className={styles.sandbox}>
<CKEditor <CKEditor ref={node => this.node = node}
onChange={(event, editor) => this.setState({html: editor.getData()})} content={this.state.html}
data={this.state.html} events={{
change: evt => this.setState({html: evt.editor.getData()}),
}}
config={config}
/> />
</div> </div>
); );

View file

@ -1,10 +1,72 @@
$navbarHeight: 34px;
.editor { .editor {
.host { .host {
} }
} }
.sandbox { .sandbox {
:global .ck-editor__editable { height: 100%;
height: 500px; 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;
}

View file

@ -14,6 +14,7 @@ import 'brace/mode/html';
import { MosaicoEditorHost } from "../lib/sandboxed-mosaico"; import { MosaicoEditorHost } from "../lib/sandboxed-mosaico";
import { CKEditorHost } from "../lib/sandboxed-ckeditor"; import { CKEditorHost } from "../lib/sandboxed-ckeditor";
import GrapesJS from "../lib/grapesjs";
import {getTemplateTypes as getMosaicoTemplateTypes} from './mosaico/helpers'; import {getTemplateTypes as getMosaicoTemplateTypes} from './mosaico/helpers';
import {getSandboxUrl} from "../lib/urls"; import {getSandboxUrl} from "../lib/urls";
@ -174,7 +175,7 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
validate: state => {} validate: state => {}
}; };
templateTypes.grapejs = { // FIXME templateTypes.xgrapesjs = { // FIXME
typeName: t('GrapeJS'), typeName: t('GrapeJS'),
getTypeForm: (owner, isEdit) => null, getTypeForm: (owner, isEdit) => null,
getHTMLEditor: owner => null, getHTMLEditor: owner => null,
@ -188,12 +189,12 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
validate: state => {} validate: state => {}
}; };
templateTypes.xckeditor = { templateTypes.grapesjs = {
typeName: t('CKEditor'), typeName: t('GrapeJS (fake)'),
getTypeForm: (owner, isEdit) => null, getTypeForm: (owner, isEdit) => null,
getHTMLEditor: owner => getHTMLEditor: owner =>
<AlignedRow label={t('Template content (HTML)')}> <AlignedRow label={t('Template content (HTML)')}>
<CKEditorHost <GrapesJS
ref={node => owner.editorNode = node} ref={node => owner.editorNode = node}
entity={owner.props.entity} entity={owner.props.entity}
initialHtml={owner.getFormValue(prefix + 'html')} initialHtml={owner.getFormValue(prefix + 'html')}
@ -212,10 +213,37 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
validate: state => {} validate: state => {}
}; };
templateTypes.ckeditor = { templateTypes.ckeditor4 = {
typeName: t('CKEditor'), typeName: t('CKEditor 4'),
getTypeForm: (owner, isEdit) => null, getTypeForm: (owner, isEdit) => null,
getHTMLEditor: owner => <CKEditor id={prefix + 'html'} height="600px" mode="html" label={t('Template content (HTML)')}/>, getHTMLEditor: owner =>
<AlignedRow label={t('Template content')}>
<CKEditorHost
ref={node => owner.editorNode = node}
entity={owner.props.entity}
initialHtml={owner.getFormValue(prefix + 'html')}
entityTypeId={entityTypeId}
title={t('CKEditor 4 Template Designer')}
onFullscreenAsync={::owner.setElementInFullscreen}
/>
</AlignedRow>,
exportHTMLEditorData: async owner => {
const {html} = await owner.editorNode.exportState();
owner.updateFormValue(prefix + 'html', html);
},
initData: () => ({}),
afterLoad: data => {},
beforeSave: data => {
clearBeforeSave(data);
},
afterTypeChange: mutState => {},
validate: state => {}
};
templateTypes.ckeditor5 = {
typeName: t('CKEditor 5'),
getTypeForm: (owner, isEdit) => null,
getHTMLEditor: owner => <CKEditor id={prefix + 'html'} height="600px" mode="html" label={t('Template content')}/>,
exportHTMLEditorData: async owner => {}, exportHTMLEditorData: async owner => {},
initData: () => ({}), initData: () => ({}),
afterLoad: data => {}, afterLoad: data => {},

View file

@ -13,9 +13,9 @@ module.exports = {
} ) } )
], ],
entry: { entry: {
root: ['babel-polyfill', './src/root.js'], "root": ['babel-polyfill', './src/root.js'],
mosaico: ['babel-polyfill', './src/lib/sandboxed-mosaico-root.js'], "mosaico-root": ['babel-polyfill', './src/lib/sandboxed-mosaico-root.js'],
ckeditor: ['babel-polyfill', './src/lib/sandboxed-ckeditor-root.js'], "ckeditor-root": ['babel-polyfill', './src/lib/sandboxed-ckeditor-root.js'],
}, },
output: { output: {
library: 'MailtrainReactBody', library: 'MailtrainReactBody',
@ -46,8 +46,6 @@ module.exports = {
} }
} }
] ]
// exclude: /(disposables|react-dnd-touch-backend|attr-accept)/ /* https://github.com/react-dnd/react-dnd/issues/407 */,
// use: [ 'babel-loader' ]
}, },
{ {
test: /\.css$/, test: /\.css$/,
@ -69,7 +67,7 @@ module.exports = {
}, },
minify: false minify: false
} ) } )
}, }
] ]
}, },
{ {
@ -84,10 +82,7 @@ module.exports = {
] ]
}, },
{ {
// Or /ckeditor5-[^/]+\/theme\/icons\/[^/]+\.svg$/ if you want to limit this loader
// to CKEditor 5 icons only.
test: /\.svg$/, test: /\.svg$/,
use: [ 'raw-loader' ] use: [ 'raw-loader' ]
}, },
{ {
@ -105,8 +100,8 @@ module.exports = {
'sass-loader' ] 'sass-loader' ]
}, },
{ {
test: /\.(woff|ttf|eot)$/, test: /\.(otf|woff2|woff|ttf|eot)$/,
use: [ 'url-loader' ] use: [ 'raw-loader' ]
} }
] ]
}, },

View file

@ -16,10 +16,12 @@ title: mailtrain
# Enabled HTML editors # Enabled HTML editors
editors: editors:
- ckeditor - ckeditor4
- ckeditor5
- codeeditor - codeeditor
- mosaico - mosaico
- mosaicoWithFsTemplate - mosaicoWithFsTemplate
- grapesjs
# Default language to use # Default language to use
language: en language: en
@ -149,7 +151,7 @@ mosaico:
# customscripts: # customscripts:
# - /mosaico/custom/my-mosaico-plugin.js # - /mosaico/custom/my-mosaico-plugin.js
grapejs: grapesjs:
# Installed templates # Installed templates
templates: templates:
- key: demo - key: demo

Binary file not shown.

View file

@ -974,11 +974,11 @@ msgstr "Vorlagen Editoren"
#: views/index.hbs:21 #: views/index.hbs:21
msgid "" msgid ""
"Mailtrain ships with GrapeJS and Mosaico built in, two advanced template " "Mailtrain ships with Grapesjs and Mosaico built in, two advanced template "
"editors. Mailtrain also offers a code editor if you prefer to handcraft the " "editors. Mailtrain also offers a code editor if you prefer to handcraft the "
"HTML yourself." "HTML yourself."
msgstr "" msgstr ""
"Mailtrain beinhaltet GrapeJS und Mosaico, zwei Programme zum Bearbeiten der " "Mailtrain beinhaltet Grapesjs und Mosaico, zwei Programme zum Bearbeiten der "
"E-Mail-Vorlagen. Natürlich bietet Mailtrain auch einen Code-Editor, sofern " "E-Mail-Vorlagen. Natürlich bietet Mailtrain auch einen Code-Editor, sofern "
"Sie den HTML Code selber erstellen möchten." "Sie den HTML Code selber erstellen möchten."
@ -1913,8 +1913,8 @@ msgid "CLOSE"
msgstr "SCHLIESSEN" msgstr "SCHLIESSEN"
#: views/partials/grapejs.hbs:2 #: views/partials/grapejs.hbs:2
msgid "Open GrapeJS" msgid "Open Grapesjs"
msgstr "GrapeJS öffnen" msgstr "Grapesjs öffnen"
#: views/partials/html-preview.hbs:1 #: views/partials/html-preview.hbs:1
msgid "Toggle HTML preview" msgid "Toggle HTML preview"
@ -3699,7 +3699,7 @@ msgid "Attachment not found"
msgstr "Anhangs-Datei nicht gefunden" msgstr "Anhangs-Datei nicht gefunden"
#: routes/blacklist.js:13 routes/campaigns.js:26 routes/editorapi.js:35 #: routes/blacklist.js:13 routes/campaigns.js:26 routes/editorapi.js:35
#: routes/fields.js:13 routes/forms.js:16 routes/grapejs.js:13 #: routes/fields.js:13 routes/forms.js:16 routes/grapesjs.js:13
#: routes/lists.js:50 routes/mosaico.js:14 routes/report-templates.js:20 #: routes/lists.js:50 routes/mosaico.js:14 routes/report-templates.js:20
#: routes/reports.js:22 routes/segments.js:13 routes/settings.js:23 #: routes/reports.js:22 routes/segments.js:13 routes/settings.js:23
#: routes/templates.js:18 routes/triggers.js:18 routes/users.js:75 #: routes/templates.js:18 routes/triggers.js:18 routes/users.js:75

Binary file not shown.

View file

@ -979,11 +979,11 @@ msgstr "Editores plantilla"
#: views/index.hbs:21 #: views/index.hbs:21
msgid "" msgid ""
"Mailtrain ships with GrapeJS and Mosaico built in, two advanced template " "Mailtrain ships with Grapesjs and Mosaico built in, two advanced template "
"editors. Mailtrain also offers a code editor if you prefer to handcraft the " "editors. Mailtrain also offers a code editor if you prefer to handcraft the "
"HTML yourself." "HTML yourself."
msgstr "" msgstr ""
"Mailtrain envía con los incorporados GrapeJS y Mosaico, dos editores " "Mailtrain envía con los incorporados Grapesjs y Mosaico, dos editores "
"avanzados de plantillas . Mailtrain también ofrece un editor de código, por " "avanzados de plantillas . Mailtrain también ofrece un editor de código, por "
"si prefieres modificar HTML." "si prefieres modificar HTML."
@ -1946,8 +1946,8 @@ msgid "CLOSE"
msgstr "CERRAR" msgstr "CERRAR"
#: views/partials/grapejs.hbs:2 #: views/partials/grapejs.hbs:2
msgid "Open GrapeJS" msgid "Open Grapesjs"
msgstr "Abrir GrapeJS" msgstr "Abrir Grapesjs"
#: views/partials/html-preview.hbs:1 #: views/partials/html-preview.hbs:1
msgid "Toggle HTML preview" msgid "Toggle HTML preview"
@ -3831,7 +3831,7 @@ msgid "Attachment not found"
msgstr "Adjunto no encontrado" msgstr "Adjunto no encontrado"
#: routes/blacklist.js:13 routes/campaigns.js:26 routes/editorapi.js:35 #: routes/blacklist.js:13 routes/campaigns.js:26 routes/editorapi.js:35
#: routes/fields.js:13 routes/forms.js:16 routes/grapejs.js:14 #: routes/fields.js:13 routes/forms.js:16 routes/grapesjs.js:14
#: routes/lists.js:50 routes/mosaico.js:14 routes/report-templates.js:20 #: routes/lists.js:50 routes/mosaico.js:14 routes/report-templates.js:20
#: routes/reports.js:22 routes/segments.js:13 routes/settings.js:23 #: routes/reports.js:22 routes/segments.js:13 routes/settings.js:23
#: routes/templates.js:18 routes/triggers.js:18 routes/users.js:75 #: routes/templates.js:18 routes/triggers.js:18 routes/users.js:75

Binary file not shown.

View file

@ -977,11 +977,11 @@ msgstr "Editors dei templates"
#: views/index.hbs:21 #: views/index.hbs:21
msgid "" msgid ""
"Mailtrain ships with GrapeJS and Mosaico built in, two advanced template " "Mailtrain ships with Grapesjs and Mosaico built in, two advanced template "
"editors. Mailtrain also offers a code editor if you prefer to handcraft the " "editors. Mailtrain also offers a code editor if you prefer to handcraft the "
"HTML yourself." "HTML yourself."
msgstr "" msgstr ""
"Mailtrain include GrapeJS e Mosaico, due editors di template avanzati. " "Mailtrain include Grapesjs e Mosaico, due editors di template avanzati. "
"Mailtrain offre inoltre un editor di codice se preferisci scrivere tu l'HTML." "Mailtrain offre inoltre un editor di codice se preferisci scrivere tu l'HTML."
#: views/index.hbs:23 #: views/index.hbs:23
@ -1913,8 +1913,8 @@ msgid "CLOSE"
msgstr "CHIUDI" msgstr "CHIUDI"
#: views/partials/grapejs.hbs:2 #: views/partials/grapejs.hbs:2
msgid "Open GrapeJS" msgid "Open Grapesjs"
msgstr "Apri GrapeJS" msgstr "Apri Grapesjs"
#: views/partials/html-preview.hbs:1 #: views/partials/html-preview.hbs:1
msgid "Toggle HTML preview" msgid "Toggle HTML preview"
@ -3809,7 +3809,7 @@ msgid "Attachment not found"
msgstr "Allegato non trovato" msgstr "Allegato non trovato"
#: routes/blacklist.js:13 routes/campaigns.js:26 routes/editorapi.js:32 #: routes/blacklist.js:13 routes/campaigns.js:26 routes/editorapi.js:32
#: routes/fields.js:13 routes/forms.js:16 routes/grapejs.js:15 #: routes/fields.js:13 routes/forms.js:16 routes/grapesjs.js:15
#: routes/lists.js:50 routes/mosaico.js:14 routes/report-templates.js:20 #: routes/lists.js:50 routes/mosaico.js:14 routes/report-templates.js:20
#: routes/reports.js:22 routes/segments.js:13 routes/settings.js:23 #: routes/reports.js:22 routes/segments.js:13 routes/settings.js:23
#: routes/templates.js:18 routes/triggers.js:18 routes/users.js:75 #: routes/templates.js:18 routes/triggers.js:18 routes/users.js:75

View file

@ -48,7 +48,7 @@ function getRouter(appType) {
mailtrainConfig: JSON.stringify(mailtrainConfig), mailtrainConfig: JSON.stringify(mailtrainConfig),
scriptFiles: [ scriptFiles: [
getSandboxUrl('mailtrain/common.js'), getSandboxUrl('mailtrain/common.js'),
getSandboxUrl('mailtrain/ckeditor.js') getSandboxUrl('mailtrain/ckeditor-root.js')
], ],
publicPath: getSandboxUrl() publicPath: getSandboxUrl()
}); });

View file

@ -199,7 +199,7 @@ function getRouter(appType) {
mailtrainConfig: JSON.stringify(mailtrainConfig), mailtrainConfig: JSON.stringify(mailtrainConfig),
scriptFiles: [ scriptFiles: [
getSandboxUrl('mailtrain/common.js'), getSandboxUrl('mailtrain/common.js'),
getSandboxUrl('mailtrain/mosaico.js') getSandboxUrl('mailtrain/mosaico-root.js')
], ],
publicPath: getSandboxUrl() publicPath: getSandboxUrl()
}); });

View file

@ -20,6 +20,7 @@
<script src="/static/jquery-2.2.1.min.js"></script> <script src="/static/jquery-2.2.1.min.js"></script>
<script src="/static/bootstrap/js/bootstrap.min.js"></script> <script src="/static/bootstrap/js/bootstrap.min.js"></script>
{{#if mailtrainConfig}} {{#if mailtrainConfig}}
<script> <script>
{{#if reactCsrfToken}}window.csfrToken = '{{reactCsrfToken}}';{{/if}} {{#if reactCsrfToken}}window.csfrToken = '{{reactCsrfToken}}';{{/if}}