diff --git a/client/public/mosaico/uploads/.gitignore b/client/public/mosaico/uploads/.gitignore new file mode 100644 index 00000000..d5b76284 --- /dev/null +++ b/client/public/mosaico/uploads/.gitignore @@ -0,0 +1,3 @@ +* +!.gitignore +!README.md \ No newline at end of file diff --git a/client/public/mosaico/uploads/README.md b/client/public/mosaico/uploads/README.md new file mode 100644 index 00000000..b03f7887 --- /dev/null +++ b/client/public/mosaico/uploads/README.md @@ -0,0 +1,2 @@ +This is a directory for images uploaded to Mosaico in Mailtrain ver. 1. +Move the content from /public/mosaico/uploads to here (/client/public/mosaico/uploads). diff --git a/client/src/lib/mosaico.js b/client/src/lib/mosaico.js index 70d6bdd9..af64fb90 100644 --- a/client/src/lib/mosaico.js +++ b/client/src/lib/mosaico.js @@ -37,7 +37,7 @@ export class MosaicoEditor extends Component { entity: PropTypes.object, title: PropTypes.string, onFullscreenAsync: PropTypes.func, - templateId: PropTypes.number, + templatePath: PropTypes.string, initialModel: PropTypes.string, initialMetadata: PropTypes.string } @@ -60,7 +60,7 @@ export class MosaicoEditor extends Component { const mosaicoData = { entityTypeId: this.props.entityTypeId, entityId: this.props.entity.id, - templateId: this.props.templateId, + templatePath: this.props.templatePath, initialModel: this.props.initialModel, initialMetadata: this.props.initialMetadata }; @@ -96,7 +96,7 @@ export class MosaicoSandbox extends Component { static propTypes = { entityTypeId: PropTypes.string, entityId: PropTypes.number, - templateId: PropTypes.number, + templatePath: PropTypes.string, initialModel: PropTypes.string, initialMetadata: PropTypes.string } @@ -156,7 +156,7 @@ export class MosaicoSandbox extends Component { const trustedUrlBase = getTrustedUrl(); const metadata = this.props.initialMetadata && JSON.parse(base(this.props.initialMetadata, trustedUrlBase, sandboxUrlBase)); const model = this.props.initialModel && JSON.parse(base(this.props.initialModel, trustedUrlBase, sandboxUrlBase)); - const template = getSandboxUrl(`mosaico/templates/${this.props.templateId}/index.html`); + const template = this.props.templatePath; const allPlugins = plugins.concat(window.mosaicoPlugins); diff --git a/client/src/templates/helpers.js b/client/src/templates/helpers.js index 5894c673..a28232e3 100644 --- a/client/src/templates/helpers.js +++ b/client/src/templates/helpers.js @@ -5,6 +5,7 @@ import { ACEEditor, AlignedRow, CKEditor, + Dropdown, TableSelect } from "../lib/form"; import 'brace/mode/text'; @@ -16,6 +17,8 @@ import { } from "../lib/mosaico"; import {getTemplateTypes as getMosaicoTemplateTypes} from './mosaico/helpers'; +import {getTrustedUrl, getSandboxUrl} from "../lib/urls"; +import mailtrainConfig from 'mailtrainConfig'; export function getTemplateTypes(t) { @@ -60,7 +63,7 @@ export function getTemplateTypes(t) { entity={owner.props.entity} initialModel={owner.getFormValue('mosaicoData').model} initialMetadata={owner.getFormValue('mosaicoData').metadata} - templateId={owner.getFormValue('mosaicoTemplate')} + templatePath={getSandboxUrl(`mosaico/templates/${owner.getFormValue('mosaicoTemplate')}/index.html`)} entityTypeId={ResourceType.TEMPLATE} title={t('Mosaico Template Designer')} onFullscreenAsync={::owner.setElementInFullscreen}/> @@ -79,7 +82,6 @@ export function getTemplateTypes(t) { }), afterLoad: data => { data.mosaicoTemplate = data.data.mosaicoTemplate; - data.html = data.data.html; data.mosaicoData = { metadata: data.data.metadata, model: data.data.model @@ -106,6 +108,57 @@ export function getTemplateTypes(t) { } }; + const mosaicoFsTemplatesOptions = mailtrainConfig.mosaico.fsTemplates.map(([key, label]) => ({key, label})); + + templateTypes.mosaicoWithFsTemplate = { + typeName: t('Mosaico with predefined templates'), + getTypeForm: (owner, isEdit) => + , + getHTMLEditor: owner => + + owner.editorNode = node} + entity={owner.props.entity} + initialModel={owner.getFormValue('mosaicoData').model} + initialMetadata={owner.getFormValue('mosaicoData').metadata} + templatePath={getSandboxUrl(`public/mosaico/templates/${owner.getFormValue('mosaicoFsTemplate')}/index.html`)} + entityTypeId={ResourceType.TEMPLATE} + title={t('Mosaico Template Designer')} + onFullscreenAsync={::owner.setElementInFullscreen}/> + , + exportHTMLEditorData: async owner => { + const {html, metadata, model} = await owner.editorNode.exportState(); + owner.updateFormValue('html', html); + owner.updateFormValue('mosaicoData', { + metadata, + model + }); + }, + initData: () => ({ + mosaicoFsTemplate: mailtrainConfig.mosaico.fsTemplates[0][0], + mosaicoData: {} + }), + afterLoad: data => { + data.mosaicoFsTemplate = data.data.mosaicoFsTemplate; + data.mosaicoData = { + metadata: data.data.metadata, + model: data.data.model + }; + }, + beforeSave: data => { + data.data = { + mosaicoFsTemplate: data.mosaicoFsTemplate, + metadata: data.mosaicoData.metadata, + model: data.mosaicoData.model + }; + clearBeforeSave(data); + }, + afterTypeChange: mutState => { + initFieldsIfMissing(mutState, 'mosaico'); + }, + validate: state => {} + }; + templateTypes.grapejs = { // TODO typeName: t('GrapeJS'), getTypeForm: (owner, isEdit) => null, diff --git a/config/default.toml b/config/default.toml index 3e3f5e4d..ebf48fe3 100644 --- a/config/default.toml +++ b/config/default.toml @@ -22,7 +22,7 @@ title="mailtrain" # Enabled HTML editors -editors=["ckeditor", "codeeditor", "mosaico"] +editors=["ckeditor", "codeeditor", "mosaico", "mosaicoWithFsTemplate"] # Default language to use language="en" @@ -150,7 +150,7 @@ processes=1 [mosaico] # Installed templates -templates=[["versafix-1", "Versafix One"]] +fsTemplates=[["versafix-1", "Versafix One"]] # Inject custom scripts # customscripts=["/mosaico/custom/my-mosaico-plugin.js"] diff --git a/lib/client-helpers.js b/lib/client-helpers.js index 9aea43e0..bc28fc2b 100644 --- a/lib/client-helpers.js +++ b/lib/client-helpers.js @@ -32,6 +32,7 @@ async function getAuthenticatedConfig(context) { }, globalPermissions: shares.getGlobalPermissions(context), editors: config.editors, + mosaico: config.mosaico, verpEnabled: config.verp.enabled } } diff --git a/models/templates.js b/models/templates.js index eca6e5ca..7656da5d 100644 --- a/models/templates.js +++ b/models/templates.js @@ -91,7 +91,7 @@ async function remove(context, id) { await knex.transaction(async tx => { await shares.enforceEntityPermissionTx(tx, context, 'template', id, 'delete'); - await reports.removeAllByReportTemplateIdTx(tx, context, id); + // FIXME - deal with deletion of dependent entities await tx('templates').where('id', id).del(); }); diff --git a/routes/mosaico.js b/routes/mosaico.js index 634d0b71..403f7f99 100644 --- a/routes/mosaico.js +++ b/routes/mosaico.js @@ -222,8 +222,18 @@ function getRouter(trusted) { width = sanitizeSize(width, 1, 2048, 600, false); height = sanitizeSize(height, 1, 2048, 300, true); - const file = await files.getFileByUrl(contextHelpers.getAdminContext(), req.params.type, req.params.entityId, req.query.src); - image = await resizedImage(file.path, method, width, height); + let filePath; + const url = req.query.src; + + const mosaicoLegacyUrlPrefix = getTrustedUrl(`mosaico/uploads/`); + if (url.startsWith(mosaicoLegacyUrlPrefix)) { + filePath = path.join(__dirname, '..', 'client', 'public' , 'mosaico', 'uploads', url.substring(mosaicoLegacyUrlPrefix.length)); + } else { + const file = await files.getFileByUrl(contextHelpers.getAdminContext(), req.params.type, req.params.entityId, url); + filePath = file.path; + } + + image = await resizedImage(filePath, method, width, height); } res.set('Content-Type', 'image/' + image.format); diff --git a/setup/knex/migrations/20180111120444_upgrade_templates.js b/setup/knex/migrations/20180111120444_upgrade_templates.js index c57ea8d6..01b9505e 100644 --- a/setup/knex/migrations/20180111120444_upgrade_templates.js +++ b/setup/knex/migrations/20180111120444_upgrade_templates.js @@ -14,6 +14,12 @@ exports.up = (knex, Promise) => (async() => { type = 'ckeditor'; } + if (type == 'mosaico') { + type = 'mosaicoWithFsTemplate'; + data.mosaicoFsTemplate = data.template; + delete data.template; + } + await knex('templates').where('id', template.id).update({type, data: JSON.stringify(data)}); } diff --git a/setup/knex/migrations/20180718220444_upgrade_campaigns.js b/setup/knex/migrations/20180718220444_upgrade_campaigns.js index 81887936..38471b16 100644 --- a/setup/knex/migrations/20180718220444_upgrade_campaigns.js +++ b/setup/knex/migrations/20180718220444_upgrade_campaigns.js @@ -109,23 +109,19 @@ exports.up = (knex, Promise) => (async() => { campaign.source_type = CampaignSource.CUSTOM; data.source = { type: editorType, - data: JSON.stringify(editorData), + data: editorData, html: campaign.html, text: campaign.text, htmlPrepared: campaign.html_prepared }; } else { campaign.source_type = CampaignSource.URL; - data.source = { - url: campaign.source_url - }; + data.sourceUrl = campaign.source_url; } } else if (campaign.type === CampaignType.RSS) { campaign.source_type = CampaignSource.RSS; - data.source = { - url: campaign.source_url - }; + data.feedUrl = campaign.source_url; data.checkStatus = campaign.checkStatus; }