Added ability to make a conditional block in MJML Mosaico.

Mosaico switched from master to v0.17.5
Added workaround for Chrome - after save, images in Mosaico disappear
This commit is contained in:
Tomas Bures 2019-05-19 01:42:26 +02:00
parent a527b80291
commit 2e9d44c705
12 changed files with 1483 additions and 1304 deletions

View file

@ -57,7 +57,7 @@ class MosaicoSandbox extends Component {
...
</div>
*/
let html = this.viewModel.exportHTML();
let html = this.viewModel.export();
html = juice(html);
return {
@ -86,6 +86,18 @@ class MosaicoSandbox extends Component {
viewModel.originalExportHTML = viewModel.exportHTML;
viewModel.exportHTML = () => {
let html = viewModel.originalExportHTML();
// Chrome workaround begin -----------------------------------------------------------------------------------
// Chrome v. 74 (and likely other versions too) has problem with how KO sets data during export.
// As the result, the images that have been in the template from previous editing (i.e. before page refresh)
// get lost. The code below refreshes the KO binding, thus effectively reloading the images.
const isChrome = !!window.chrome && (!!window.chrome.webstore || !!window.chrome.runtime);
if (isChrome) {
ko.cleanNode(document.body);
ko.applyBindings(viewModel, document.body);
}
// Chrome workaround end -------------------------------------------------------------------------------------
for (const portRender of window.mosaicoHTMLPostRenderers) {
html = postRender(html);
}

View file

@ -199,8 +199,14 @@ export default class CUD extends Component {
{isEdit && typeKey && this.templateTypes[typeKey].getForm(this)}
<ButtonRow>
<Button type="submit" className="btn-primary" icon="check" label={t('save')}/>
<Button type="submit" className="btn-primary" icon="check" label={t('saveAndLeave')} onClickAsync={async () => await this.submitHandler(true)}/>
{isEdit ?
<>
<Button type="submit" className="btn-primary" icon="check" label={t('save')}/>
<Button type="submit" className="btn-primary" icon="check" label={t('saveAndLeave')} onClickAsync={async () => await this.submitHandler(true)}/>
</>
:
<Button type="submit" className="btn-primary" icon="check" label={t('saveAndEditContent')}/>
}
{canDelete && <LinkButton className="btn-danger" icon="trash-alt" label={t('delete')} to={`/templates/mosaico/${this.props.entity.id}/delete`}/>}
{isEdit && typeKey && this.templateTypes[typeKey].getButtons(this)}
</ButtonRow>

View file

@ -26,7 +26,7 @@ function getParent() {
}
function handleMosaicoEditable(block, src) {
function handleMosaicoAttributes(block, src) {
let newSrc = src;
let offset = 0;
@ -43,13 +43,15 @@ function handleMosaicoEditable(block, src) {
let newFragment = tagStr;
for (const attrMatch of attrsMatches) {
if (attrMatch[1] === 'mj-mosaico-editable') {
const propertyId = attrMatch[2];
block.addMosaicoProperty(propertyId);
newFragment += ` data-ko-editable="${propertyId}"`;
} else if (attrMatch[1] === 'mj-mosaico-display') {
const propertyId = attrMatch[2];
block.addMosaicoProperty(propertyId);
newFragment += ` data-ko-display="${propertyId}"`;
} else {
newFragment += ` ${attrMatch[0]}`;
}
@ -75,35 +77,39 @@ function handleMosaicoEditable(block, src) {
class MjMosaicoProperty extends HeadComponent {
static endingTag = true
static endingTag = true;
static allowedAttributes = {
'property-id': 'string',
label: 'string',
type: 'enum(image,link)'
}
type: 'enum(image,link,visible)'
};
handler() {
const { add } = this.context;
let properties = null;
let extend = null;
const type = this.getAttribute('type');
if (type === 'image') {
properties = 'src url alt';
} else if (type === 'link') {
properties = 'text url';
} else if (type === 'visible') {
extend = 'visible';
}
const propertiesStr = properties ? ` properties: ${properties}` : '';
const extendStr = extend ? ` extend: ${extend}` : '';
add('style', ` @supports -ko-blockdefs { ${this.getAttribute('property-id')} { label: ${this.getAttribute('label')};${propertiesStr} } }`);
add('style', ` @supports -ko-blockdefs { ${this.getAttribute('property-id')} { label: ${this.getAttribute('label')};${propertiesStr}${extendStr} } }`);
}
}
class MjMosaicoContainer extends BodyComponent {
static endingTag = false
static endingTag = false;
render() {
return `
@ -114,6 +120,90 @@ class MjMosaicoContainer extends BodyComponent {
}
}
class MjMosaicoConditionalDisplay extends BodyComponent {
constructor(initialDatas = {}) {
super(initialDatas);
const propertyId = this.getAttribute('property-id');
this.propertyId = propertyId || `display_${getId()}`;
const parentBlock = getParent();
if (parentBlock) {
parentBlock.addMosaicoProperty(this.propertyId);
}
}
componentHeadStyle = breakpoint => {
const label = this.getAttribute('label');
if (label) {
return `
@supports -ko-blockdefs {
${this.propertyId} { label: ${label}; extend: visible }
}
`;
} else {
return '';
}
};
static endingTag = false;
static allowedAttributes = {
'property-id': 'string',
'label': 'string'
};
render() {
const { children } = this.props;
return `
<div
${this.htmlAttributes({
"data-ko-display": this.propertyId,
class: this.getAttribute('css-class'),
'data-ko-block': this.blockId
})}
>
<table
${this.htmlAttributes({
border: '0',
cellpadding: '0',
cellspacing: '0',
role: 'presentation',
style: 'table',
width: '100%',
})}
>
${this.renderChildren(children, {
renderer: component => component.constructor.isRawElement() ? component.render() : `
<tr>
<td
${component.htmlAttributes({
align: component.getAttribute('align'),
'vertical-align': component.getAttribute('vertical-align'),
class: component.getAttribute('css-class'),
style: {
background: component.getAttribute('container-background-color'),
'font-size': '0px',
padding: component.getAttribute('padding'),
'padding-top': component.getAttribute('padding-top'),
'padding-right': component.getAttribute('padding-right'),
'padding-bottom': component.getAttribute('padding-bottom'),
'padding-left': component.getAttribute('padding-left'),
'word-break': 'break-word',
},
})}
>
${component.render()}
</td>
</tr>
`
})}
</table>
</div>
`;
}
}
class MjMosaicoBlock extends BodyComponent {
constructor(initialDatas = {}) {
@ -132,14 +222,14 @@ class MjMosaicoBlock extends BodyComponent {
${this.blockId} { label: ${this.getAttribute('label')}${propertiesOut} }
}
`;
}
};
static endingTag = false
static endingTag = false;
static allowedAttributes = {
'block-id': 'string',
'label': 'string'
}
};
addMosaicoProperty(property) {
this.mosaicoProperties.push(property);
@ -159,7 +249,7 @@ class MjMosaicoBlock extends BodyComponent {
`;
popParent();
return handleMosaicoEditable(this, result);
return handleMosaicoAttributes(this, result);
}
}
@ -181,14 +271,14 @@ class MjMosaicoInnerBlock extends BodyComponent {
${this.blockId} { label: ${this.getAttribute('label')}${propertiesOut} }
}
`;
}
};
static endingTag = false
static endingTag = false;
static allowedAttributes = {
'block-id': 'string',
'label': 'string'
}
};
addMosaicoProperty(property) {
this.mosaicoProperties.push(property);
@ -238,14 +328,14 @@ class MjMosaicoInnerBlock extends BodyComponent {
${component.render()}
</td>
</tr>
`,
`
})}
</table>
</div>
`;
popParent();
return handleMosaicoEditable(this, result);
return handleMosaicoAttributes(this, result);
}
}
@ -264,7 +354,7 @@ class MjMosaicoImage extends BodyComponent {
}
}
static tagOmission = true
static tagOmission = true;
static allowedAttributes = {
'property-id': 'string',
@ -292,7 +382,7 @@ class MjMosaicoImage extends BodyComponent {
target: 'string',
width: 'unit(px)',
height: 'unit(px)',
}
};
static defaultAttributes = {
align: 'center',
@ -302,7 +392,7 @@ class MjMosaicoImage extends BodyComponent {
target: '_blank',
'placeholder-height': '400',
'href-editable': false
}
};
getStyles() {
const width = this.getContentWidth();
@ -453,7 +543,7 @@ class MjMosaicoButton extends BodyComponent {
}
}
static endingTag = true
static endingTag = true;
static allowedAttributes = {
'property-id': 'string',
@ -487,7 +577,7 @@ class MjMosaicoButton extends BodyComponent {
'vertical-align': 'enum(top,bottom,middle)',
'text-align': 'enum(left,right,center)',
width: 'unit(px,%)',
}
};
static defaultAttributes = {
align: 'center',
@ -505,7 +595,7 @@ class MjMosaicoButton extends BodyComponent {
'text-decoration': 'none',
'text-transform': 'none',
'vertical-align': 'middle',
}
};
getStyles() {
return {
@ -591,6 +681,7 @@ class MjMosaicoButton extends BodyComponent {
const mjmlInstance = new MJML();
mjmlInstance.registerComponent(MjMosaicoContainer);
mjmlInstance.registerComponent(MjMosaicoConditionalDisplay);
mjmlInstance.registerComponent(MjMosaicoBlock);
mjmlInstance.registerComponent(MjMosaicoInnerBlock);
mjmlInstance.registerComponent(MjMosaicoImage);
@ -601,8 +692,13 @@ mjmlInstance.registerDependencies({
'mj-mosaico-container': ['mj-mosaico-block', 'mj-mosaico-inner-block'],
'mj-body': ['mj-mosaico-container', 'mj-mosaico-block'],
'mj-section': ['mj-mosaico-container', 'mj-mosaico-block'],
'mj-column': ['mj-mosaico-container', 'mj-mosaico-inner-block', 'mj-mosaico-image', 'mj-mosaico-button'],
'mj-column': ['mj-mosaico-container', 'mj-mosaico-inner-block', 'mj-mosaico-image', 'mj-mosaico-button', 'mj-mosaico-conditional-display'],
'mj-mosaico-block': ['mj-section', 'mj-column'],
'mj-mosaico-conditional-display': [
'mj-mosaico-image', 'mj-mosaico-button',
'mj-accordion', 'mj-button', 'mj-carousel', 'mj-divider', 'mj-html', 'mj-image', 'mj-invoice', 'mj-list',
'mj-location', 'mj-raw', 'mj-social', 'mj-spacer', 'mj-table', 'mj-text', 'mj-navbar'
],
'mj-mosaico-inner-block': [
'mj-mosaico-image', 'mj-mosaico-button',
'mj-accordion', 'mj-button', 'mj-carousel', 'mj-divider', 'mj-html', 'mj-image', 'mj-invoice', 'mj-list',
@ -614,6 +710,8 @@ mjmlInstance.registerDependencies({
mjmlInstance.addToHeader(`
<style type="text/css">
@supports -ko-blockdefs {
visible { label: Visible?; widget: boolean }
color { label: Color; widget: color }
text { label: Paragraph; widget: text }
image { label: Image; properties: src url alt }
link { label: Link; properties: text url }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -43,6 +43,7 @@ defaultLanguage: en-US
enabledLanguages:
- en-US
- es-ES
- pt-BR
- fk-FK
# Inject custom scripts in subscription/layout.mjml.hbs

File diff suppressed because it is too large Load diff