mjml4 support moved to a separate package

support for file handling in grapesjs
This commit is contained in:
Tomas Bures 2018-11-10 19:40:20 +01:00
parent 9f467762c0
commit b089993360
15 changed files with 136 additions and 10242 deletions

View file

@ -1 +0,0 @@
/dist

@ -1 +0,0 @@
Subproject commit 3ff2c60ae31a561a2fb7695bcf4eb012f6d43ede

View file

@ -1,9 +0,0 @@
export default {
readFileSync: (filename) => {
if (filename === '/.mjmlconfig') {
return '{ "packages": [] }';
} else {
console.log('readFileSync - unknown file name "' + filename + '"');
}
}
};

View file

@ -1 +0,0 @@
export default {};

File diff suppressed because it is too large Load diff

View file

@ -1,32 +0,0 @@
{
"name": "mjml",
"description": "mjml",
"version": "4.2.0",
"license": "MIT",
"dependencies": {
"html-minifier": "^3.5.3",
"js-beautify": "^1.6.14",
"juice": "^4.1.0",
"lodash": "^4.17.2",
"htmlparser2": "^3.9.2"
},
"devDependencies": {
"babel-cli": "^6.24.1",
"babel-loader": "^7.1.1",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-plugin-transform-function-bind": "^6.22.0",
"babel-preset-env": "^1.7.0",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-1": "^6.24.1",
"css-loader": "^0.28.4",
"file-loader": "^2.0.0",
"i18next-conv": "^3.0.3",
"node-sass": "^4.5.3",
"postcss-loader": "^3.0.0",
"raw-loader": "^0.5.1",
"sass-loader": "^6.0.6",
"style-loader": "^0.18.2",
"url-loader": "^0.5.9",
"webpack": "^2.6.1"
}
}

View file

@ -1,135 +0,0 @@
const webpack = require('webpack');
const path = require('path');
module.exports = {
entry: {
"mjml": ['./mjml-git/packages/mjml/src/index'],
},
output: {
library: 'mjml',
filename: '[name].js',
path: path.resolve(__dirname, './dist'),
libraryTarget: 'umd',
umdNamedDefine: true
},
resolve: {
alias: {
'mjml-core/lib': path.resolve(__dirname, './mjml-git/packages/mjml-core/src'),
'mjml-accordion': path.resolve(__dirname, './mjml-git/packages/mjml-accordion/src'),
'mjml-body': path.resolve(__dirname, './mjml-git/packages/mjml-body/src'),
'mjml-button': path.resolve(__dirname, './mjml-git/packages/mjml-button/src'),
'mjml-carousel': path.resolve(__dirname, './mjml-git/packages/mjml-carousel/src'),
'mjml-cli': path.resolve(__dirname, './mjml-git/packages/mjml-cli/src'),
'mjml-column': path.resolve(__dirname, './mjml-git/packages/mjml-column/src'),
'mjml-core': path.resolve(__dirname, './mjml-git/packages/mjml-core/src'),
'mjml-divider': path.resolve(__dirname, './mjml-git/packages/mjml-divider/src'),
'mjml-group': path.resolve(__dirname, './mjml-git/packages/mjml-group/src'),
'mjml-head': path.resolve(__dirname, './mjml-git/packages/mjml-head/src'),
'mjml-head-attributes': path.resolve(__dirname, './mjml-git/packages/mjml-head-attributes/src'),
'mjml-head-breakpoint': path.resolve(__dirname, './mjml-git/packages/mjml-head-breakpoint/src'),
'mjml-head-font': path.resolve(__dirname, './mjml-git/packages/mjml-head-font/src'),
'mjml-head-preview': path.resolve(__dirname, './mjml-git/packages/mjml-head-preview/src'),
'mjml-head-style': path.resolve(__dirname, './mjml-git/packages/mjml-head-style/src'),
'mjml-head-title': path.resolve(__dirname, './mjml-git/packages/mjml-head-title/src'),
'mjml-hero': path.resolve(__dirname, './mjml-git/packages/mjml-hero/src'),
'mjml-image': path.resolve(__dirname, './mjml-git/packages/mjml-image/src'),
'mjml-migrate': path.resolve(__dirname, './mjml-git/packages/mjml-migrate/src/migrate.js'),
'mjml-navbar': path.resolve(__dirname, './mjml-git/packages/mjml-navbar/src'),
'mjml-raw': path.resolve(__dirname, './mjml-git/packages/mjml-raw/src'),
'mjml-section': path.resolve(__dirname, './mjml-git/packages/mjml-section/src'),
'mjml-social': path.resolve(__dirname, './mjml-git/packages/mjml-social/src'),
'mjml-spacer': path.resolve(__dirname, './mjml-git/packages/mjml-spacer/src'),
'mjml-table': path.resolve(__dirname, './mjml-git/packages/mjml-table/src'),
'mjml-text': path.resolve(__dirname, './mjml-git/packages/mjml-text/src'),
'mjml-validator': path.resolve(__dirname, './mjml-git/packages/mjml-validator/src'),
'mjml-wrapper': path.resolve(__dirname, './mjml-git/packages/mjml-wrapper/src'),
'mjml-parser-xml': path.resolve(__dirname, './mjml-git/packages/mjml-parser-xml/src'),
'fs': path.resolve(__dirname, 'mocks/fs'),
'uglify-js': path.resolve(__dirname, 'mocks/uglify-js'),
}
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: path.join(__dirname, 'node_modules'),
use: [
{
loader: 'babel-loader',
options: {
presets: [
['env', {
targets: {
"chrome": "58",
"edge": "15",
"firefox": "55",
"ios": "10"
}
}],
'stage-1'
],
plugins: ['add-module-exports', 'transform-react-jsx', 'transform-decorators-legacy', 'transform-function-bind'],
babelrc: false
}
}
]
},
{
test: /\.css$/,
use: [
{
loader: 'style-loader'
},
{
loader: 'css-loader'
}
]
},
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 8192 // inline base64 URLs for <=8k images, direct URLs for the rest
}
}
]
},
{
test: /\.scss$/,
exclude: path.join(__dirname, 'node_modules'),
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: true,
localIdentName: '[path][name]__[local]--[hash:base64:5]'
}
},
'sass-loader'
]
},
{
test: /\.(svg|otf|woff2|woff|ttf|eot)$/,
use: [
'url-loader'
]
}
]
},
externals: {
jquery: 'jQuery',
csfrToken: 'csfrToken',
mailtrainConfig: 'mailtrainConfig'
},
plugins: [
// new webpack.optimize.UglifyJsPlugin(),
],
watchOptions: {
ignored: 'node_modules/',
poll: 2000
}
};

1812
client/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -42,6 +42,7 @@
"i18next": "^8.4.3",
"i18next-xhr-backend": "^1.4.2",
"immutable": "^3.8.1",
"mjml4-in-browser": "^1.0.1",
"moment": "^2.18.1",
"moment-timezone": "^0.5.13",
"prop-types": "^15.5.10",
@ -65,7 +66,6 @@
"@ckeditor/ckeditor5-dev-webpack-plugin": "^7.0.1",
"babel-cli": "^6.24.1",
"babel-loader": "^7.1.1",
"babel-plugin-add-module-exports": "^1.0.0",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-plugin-transform-function-bind": "^6.22.0",
"babel-preset-env": "^1.7.0",

View file

@ -27,8 +27,7 @@ import {
base,
unbase
} from "../../../shared/templates";
import mjml2html from "../../mjml/dist/mjml";
console.log(mjml2html);
import mjml2html from "mjml4-in-browser";
import 'grapesjs/dist/css/grapes.min.css';
import grapesjs from 'grapesjs';
@ -36,9 +35,12 @@ import "grapesjs-mjml";
import "./sandboxed-grapesjs.scss";
import axios from './axios';
grapesjs.plugins.add('mailtrain', (editor, opts = {}) => {
const panelManager = editor.Panels;
panelManager.removeButton('options','fullscreen')
panelManager.removeButton('options','fullscreen');
panelManager.removeButton('options','export-template');
});
@ -47,7 +49,10 @@ export class GrapesJSSandbox extends Component {
constructor(props) {
super(props);
this.initialized = false;
this.state = {
assets: null
};
}
@ -64,12 +69,10 @@ export class GrapesJSSandbox extends Component {
// If exportState comes during text editing (via RichTextEditor), we need to cancel the editing, so that the
// text being edited is stored in the model
const sel = editor.getSelected();
if (sel) {
if (sel && sel.view && sel.disableEditing) {
sel.view.disableEditing();
}
editor.select(null);
const trustedUrlBase = getTrustedUrl();
const sandboxUrlBase = getSandboxUrl();
const publicUrlBase = getPublicUrl();
@ -82,13 +85,8 @@ export class GrapesJSSandbox extends Component {
const preMjml = '<mjml><mj-head></mj-head><mj-body>';
const postMjml = '</mj-body></mjml>';
const mjml = preMjml + source + postMjml;
console.log(mjml);
const mjmlRes = mjml2html(mjml);
console.log(mjmlRes);
console.log(mjmlRes.html);
console.log(mjmlRes.errors);
console.log(mjmlRes.errors[0]);
return {
html,
@ -97,7 +95,27 @@ export class GrapesJSSandbox extends Component {
};
}
async fetchAssets() {
const props = this.props;
const resp = await axios.get(getSandboxUrl(`rest/files-list/${props.entityTypeId}/file/${props.entityId}`));
this.setState({
assets: resp.data.map( f => ({type: 'image', src: getPublicUrl(`files/${props.entityTypeId}/file/${props.entityId}/${f.filename}`)}) )
});
}
componentDidMount() {
// noinspection JSIgnoredPromiseFromCall
this.fetchAssets();
}
componentDidUpdate() {
if (!this.initialized && this.state.assets !== null) {
this.initGrapesJs();
this.initialized = true;
}
}
initGrapesJs() {
const props = this.props;
parentRPC.setMethodHandler('exportState', ::this.exportState);
@ -127,12 +145,13 @@ export class GrapesJSSandbox extends Component {
type: 'none'
},
assetManager: {
assets: [],
upload: '/editorapi/upload?type={{type}}&id={{resource.id}}&editor={{editor.name}}',
assets: this.state.assets,
upload: getSandboxUrl(`grapesjs/upload/${this.props.entityTypeId}/${this.props.entityId}`),
uploadText: 'Drop images here or click to upload',
headers: {
'X-CSRF-TOKEN': '{{csrfToken}}',
},
autoAdd: true
},
styleManager: {
clearProperties: true,

View file

@ -11,9 +11,9 @@ const multer = require('multer')({
dest: uploadedFilesDir
});
function installUploadHandler(router, url, replacementBehavior, type, subType) {
function installUploadHandler(router, url, replacementBehavior, type, subType, transformResponseFn) {
router.postAsync(url, passport.loggedIn, multer.array('files[]'), async (req, res) => {
return res.json(await files.createFiles(req.context, type || req.params.type, subType || req.params.subType, castToInteger(req.params.entityId), req.files, replacementBehavior));
return res.json(await files.createFiles(req.context, type || req.params.type, subType || req.params.subType, castToInteger(req.params.entityId), req.files, replacementBehavior, transformResponseFn));
});
}

View file

@ -21,7 +21,7 @@ const filesDir = path.join(__dirname, '..', 'files');
const ReplacementBehavior = entitySettings.ReplacementBehavior;
function enforceTypePermitted(type, subType) {
enforce(type in entityTypes && entityTypes[type].files && entityTypes[type].files[subType]);
enforce(type in entityTypes && entityTypes[type].files && entityTypes[type].files[subType], `File type ${type}:${subType} does not exist`);
}
function getFilePath(type, subType, entityId, filename) {
@ -58,7 +58,7 @@ async function listTx(tx, context, type, subType, entityId) {
async function list(context, type, subType, entityId) {
return await knex.transaction(async tx => {
return listTx(tx, context, type, subType, entityId);
return await listTx(tx, context, type, subType, entityId);
});
}
@ -135,7 +135,7 @@ async function getFileByUrl(context, url) {
}
// Adds files to an entity. The source data can be either a file (then it's path is contained in file.path) or in-memory data (then it's content is in file.data).
async function createFiles(context, type, subType, entityId, files, replacementBehavior) {
async function createFiles(context, type, subType, entityId, files, replacementBehavior, transformResponseFn) {
enforceTypePermitted(type, subType);
if (files.length == 0) {
// No files uploaded
@ -260,13 +260,19 @@ async function createFiles(context, type, subType, entityId, files, replacementB
}
}
return {
const resp = {
uploaded: files.length,
added: fileEntities.length - removedFiles.length,
replaced: removedFiles.length,
ignored: ignoredFiles.length,
files: filesRet
};
if (transformResponseFn) {
return transformResponseFn(resp);
} else {
return resp;
}
}
async function removeFile(context, type, subType, id) {

View file

@ -10,6 +10,11 @@ router.postAsync('/files-table/:type/:subType/:entityId', passport.loggedIn, asy
return res.json(await files.listDTAjax(req.context, req.params.type, req.params.subType, castToInteger(req.params.entityId), req.body));
});
router.getAsync('/files-list/:type/:subType/:entityId', passport.loggedIn, async (req, res) => {
return res.json(await files.list(req.context, req.params.type, req.params.subType, castToInteger(req.params.entityId)));
});
router.getAsync('/files/:type/:subType/:fileId', passport.loggedIn, async (req, res) => {
const file = await files.getFileById(req.context, req.params.type, req.params.subType, castToInteger(req.params.fileId));
res.type(file.mimetype);

View file

@ -24,7 +24,7 @@ users.registerRestrictedAccessTokenMethod('grapesjs', async ({entityTypeId, enti
return {
permissions: {
'template': {
[entityId]: new Set(['manageFiles', 'view'])
[entityId]: new Set(['viewFiles', 'manageFiles', 'view'])
}
}
};
@ -51,6 +51,13 @@ function getRouter(appType) {
publicPath: getSandboxUrl()
});
});
fileHelpers.installUploadHandler(router, '/upload/:type/:entityId', files.ReplacementBehavior.RENAME, null, 'file', resp => {
return {
data: resp.files.map( f => ({type: 'image', src: f.url}) )
};
});
}
return router;

View file

@ -38,7 +38,7 @@ users.registerRestrictedAccessTokenMethod('mosaico', async ({entityTypeId, entit
return {
permissions: {
'template': {
[entityId]: new Set(['manageFiles', 'view'])
[entityId]: new Set(['viewFiles', 'manageFiles', 'view'])
},
'mosaicoTemplate': {
[tmpl.data.mosaicoTemplate]: new Set(['view'])
@ -156,7 +156,11 @@ function getRouter(appType) {
router.use('/templates/:mosaicoTemplateId/edres', express.static(path.join(__dirname, '..', 'client', 'static', 'mosaico', 'templates', 'versafix-1', 'edres')));
fileHelpers.installUploadHandler(router, '/upload/:type/:entityId', files.ReplacementBehavior.RENAME, null, 'file');
fileHelpers.installUploadHandler(router, '/upload/:type/:entityId', files.ReplacementBehavior.RENAME, null, 'file', resp => {
return {
files: resp.files.map(f => ({name: f.name, url: f.url, size: f.size, thumbnailUrl: f.thumbnailUrl}))
};
});
router.getAsync('/upload/:type/:entityId', passport.loggedIn, async (req, res) => {
const id = castToInteger(req.params.entityId);