Merge pull request #1431 from mailpoet/background-images
Column Background Images [MAILPOET-1403]
This commit is contained in:
@@ -153,7 +153,7 @@ class RoboFile extends \Robo\Tasks {
|
||||
return $this->_exec('./tasks/transifex_init.sh');
|
||||
}
|
||||
|
||||
function testUnit($opts=['file' => null, 'xml' => false, 'multisite' => false]) {
|
||||
function testUnit(array $opts=['file' => null, 'xml' => false, 'multisite' => false, 'debug' => false]) {
|
||||
$this->loadEnv();
|
||||
|
||||
$command = 'vendor/bin/codecept run unit -c codeception.unit.yml';
|
||||
@@ -170,6 +170,10 @@ class RoboFile extends \Robo\Tasks {
|
||||
$command .= ' --xml';
|
||||
}
|
||||
|
||||
if($opts['debug']) {
|
||||
$command .= ' --debug';
|
||||
}
|
||||
|
||||
return $this->_exec($command);
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,313 @@
|
||||
/* eslint-disable func-names */
|
||||
/**
|
||||
* Media manager behaviour
|
||||
*
|
||||
* Adds a media manager integration with the view
|
||||
*/
|
||||
define([
|
||||
'backbone.marionette',
|
||||
'underscore',
|
||||
'newsletter_editor/behaviors/BehaviorsLookup',
|
||||
'jquery'
|
||||
], function (Marionette, _, BehaviorsLookup, jQuery) {
|
||||
var BL = BehaviorsLookup;
|
||||
|
||||
BL.MediaManagerBehavior = Marionette.Behavior.extend({
|
||||
ui: {
|
||||
'select-image': '.mailpoet_field_image_select_image',
|
||||
'address-input': '.mailpoet_field_image_address'
|
||||
},
|
||||
events: {
|
||||
'click @ui.select-image': 'showMediaManager',
|
||||
'input @ui.address-input': 'changeAddress'
|
||||
},
|
||||
initialize: function () {
|
||||
if (this.view.options.showImageManager) {
|
||||
this.showMediaManager();
|
||||
}
|
||||
},
|
||||
changeAddress: function (event) {
|
||||
var src = jQuery(event.target).val();
|
||||
var image = new Image();
|
||||
|
||||
if (!src && this.options.onSelect) {
|
||||
this.view[this.options.onSelect]({
|
||||
src: null,
|
||||
width: null,
|
||||
height: null
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
image.onload = function () {
|
||||
if (this.options.onSelect) {
|
||||
this.view[this.options.onSelect]({
|
||||
src: src,
|
||||
width: image.naturalWidth + 'px',
|
||||
height: image.naturalHeight + 'px'
|
||||
});
|
||||
}
|
||||
}.bind(this);
|
||||
|
||||
image.src = src;
|
||||
},
|
||||
showMediaManager: function () {
|
||||
var that = this;
|
||||
var MediaManager;
|
||||
var theFrame;
|
||||
if (this._mediaManager) {
|
||||
this._mediaManager.resetSelections();
|
||||
this._mediaManager.open();
|
||||
return;
|
||||
}
|
||||
|
||||
MediaManager = window.wp.media.view.MediaFrame.Select.extend({
|
||||
|
||||
initialize: function () {
|
||||
window.wp.media.view.MediaFrame.prototype.initialize.apply(this, arguments);
|
||||
|
||||
_.defaults(this.options, {
|
||||
multiple: true,
|
||||
editing: false,
|
||||
state: 'insert'
|
||||
});
|
||||
|
||||
this.createSelection();
|
||||
this.createStates();
|
||||
this.bindHandlers();
|
||||
this.createIframeStates();
|
||||
|
||||
// Hide title
|
||||
this.$el.addClass('hide-title');
|
||||
},
|
||||
|
||||
resetSelections: function () {
|
||||
this.state().get('selection').reset();
|
||||
},
|
||||
|
||||
createQuery: function (options) {
|
||||
var query = window.wp.media.query(options);
|
||||
return query;
|
||||
},
|
||||
|
||||
createStates: function () {
|
||||
var options = this.options;
|
||||
|
||||
// Add the default states.
|
||||
this.states.add([
|
||||
// Main states.
|
||||
new window.wp.media.controller.Library({
|
||||
id: 'insert',
|
||||
title: 'Add images',
|
||||
priority: 20,
|
||||
toolbar: 'main-insert',
|
||||
filterable: 'image',
|
||||
library: this.createQuery(options.library),
|
||||
multiple: options.multiple ? 'reset' : false,
|
||||
editable: false,
|
||||
|
||||
// If the user isn't allowed to edit fields,
|
||||
// can they still edit it locally?
|
||||
allowLocalEdits: false,
|
||||
|
||||
// Show the attachment display settings.
|
||||
displaySettings: false,
|
||||
// Update user settings when users adjust the
|
||||
// attachment display settings.
|
||||
displayUserSettings: false
|
||||
})
|
||||
]);
|
||||
|
||||
if (window.wp.media.view.settings.post.featuredImageId) {
|
||||
this.states.add(new window.wp.media.controller.FeaturedImage());
|
||||
}
|
||||
},
|
||||
|
||||
bindHandlers: function () {
|
||||
var handlers;
|
||||
// from Select
|
||||
this.on('router:create:browse', this.createRouter, this);
|
||||
this.on('router:render:browse', this.browseRouter, this);
|
||||
this.on('content:create:browse', this.browseContent, this);
|
||||
this.on('content:render:upload', this.uploadContent, this);
|
||||
this.on('toolbar:create:select', this.createSelectToolbar, this);
|
||||
|
||||
this.on('menu:create:gallery', this.createMenu, this);
|
||||
this.on('toolbar:create:main-insert', this.createToolbar, this);
|
||||
this.on('toolbar:create:main-gallery', this.createToolbar, this);
|
||||
this.on('toolbar:create:main-embed', this.mainEmbedToolbar, this);
|
||||
|
||||
this.on('updateExcluded', this.browseContent, this);
|
||||
|
||||
handlers = {
|
||||
content: {
|
||||
embed: 'embedContent',
|
||||
'edit-selection': 'editSelectionContent'
|
||||
},
|
||||
toolbar: {
|
||||
'main-insert': 'mainInsertToolbar'
|
||||
}
|
||||
};
|
||||
|
||||
_.each(handlers, function (regionHandlers, region) {
|
||||
_.each(regionHandlers, function (callback, handler) {
|
||||
this.on(region + ':render:' + handler, this[callback], this);
|
||||
}, this);
|
||||
}, this);
|
||||
},
|
||||
|
||||
uploadContent: function () {
|
||||
window.wp.media.view.MediaFrame.Select.prototype.uploadContent.apply(this, arguments);
|
||||
this.$el.addClass('hide-toolbar');
|
||||
},
|
||||
|
||||
// Content
|
||||
embedContent: function () {
|
||||
var view = new window.wp.media.view.Embed({
|
||||
controller: this,
|
||||
model: this.state()
|
||||
}).render();
|
||||
|
||||
this.content.set(view);
|
||||
view.url.focus();
|
||||
},
|
||||
|
||||
editSelectionContent: function () {
|
||||
var state = this.state();
|
||||
var selection = state.get('selection');
|
||||
var view;
|
||||
|
||||
view = new window.wp.media.view.AttachmentsBrowser({
|
||||
controller: this,
|
||||
collection: selection,
|
||||
selection: selection,
|
||||
model: state,
|
||||
sortable: true,
|
||||
search: false,
|
||||
dragInfo: true,
|
||||
|
||||
AttachmentView: window.wp.media.view.Attachment.EditSelection
|
||||
}).render();
|
||||
|
||||
view.toolbar.set('backToLibrary', {
|
||||
text: 'Return to library',
|
||||
priority: -100,
|
||||
|
||||
click: function () {
|
||||
this.controller.content.mode('browse');
|
||||
}
|
||||
});
|
||||
|
||||
// Browse our library of attachments.
|
||||
this.content.set(view);
|
||||
},
|
||||
|
||||
// Toolbars
|
||||
selectionStatusToolbar: function (view) {
|
||||
var editable = this.state().get('editable');
|
||||
|
||||
view.set('selection', new window.wp.media.view.Selection({
|
||||
controller: this,
|
||||
collection: this.state().get('selection'),
|
||||
priority: -40,
|
||||
|
||||
// If the selection is editable, pass the callback to
|
||||
// switch the content mode.
|
||||
editable: editable && function () {
|
||||
this.controller.content.mode('edit-selection');
|
||||
}
|
||||
}).render());
|
||||
},
|
||||
|
||||
mainInsertToolbar: function (view) {
|
||||
var controller = this;
|
||||
|
||||
this.selectionStatusToolbar(view);
|
||||
|
||||
view.set('insert', {
|
||||
style: 'primary',
|
||||
priority: 80,
|
||||
text: 'Select Image',
|
||||
requires: { selection: true },
|
||||
|
||||
click: function () {
|
||||
var state = controller.state();
|
||||
var selection = state.get('selection');
|
||||
|
||||
controller.close();
|
||||
state.trigger('insert', selection).reset();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
mainEmbedToolbar: function (toolbar) {
|
||||
var tbar = toolbar;
|
||||
tbar.view = new window.wp.media.view.Toolbar.Embed({
|
||||
controller: this,
|
||||
text: 'Add images'
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
theFrame = new MediaManager({
|
||||
id: 'mailpoet-media-manager',
|
||||
frame: 'select',
|
||||
title: 'Select image',
|
||||
editing: false,
|
||||
multiple: false,
|
||||
library: {
|
||||
type: 'image'
|
||||
},
|
||||
displaySettings: false,
|
||||
button: {
|
||||
text: 'Select'
|
||||
}
|
||||
});
|
||||
this._mediaManager = theFrame;
|
||||
|
||||
this._mediaManager.on('insert', function () {
|
||||
// Append media manager image selections to Images tab
|
||||
var selection = theFrame.state().get('selection');
|
||||
selection.each(function (attachment) {
|
||||
var sizes = attachment.get('sizes');
|
||||
// Following advice from Becs, the target width should
|
||||
// be a double of one column width to render well on
|
||||
// retina screen devices
|
||||
var targetImageWidth = 1320;
|
||||
|
||||
// Pick the width that is closest to target width
|
||||
var increasingByWidthDifference = _.sortBy(
|
||||
_.keys(sizes),
|
||||
function (size) {
|
||||
return Math.abs(targetImageWidth - sizes[size].width);
|
||||
}
|
||||
);
|
||||
var bestWidth = sizes[_.first(increasingByWidthDifference)].width;
|
||||
var imagesOfBestWidth = _.filter(
|
||||
_.values(sizes),
|
||||
function (size) { return size.width === bestWidth; }
|
||||
);
|
||||
|
||||
// Maximize the height if there are multiple images with same width
|
||||
var mainSize = _.max(imagesOfBestWidth, function (size) { return size.height; });
|
||||
|
||||
if (that.options.onSelect) {
|
||||
that.view[that.options.onSelect]({
|
||||
height: mainSize.height + 'px',
|
||||
width: mainSize.width + 'px',
|
||||
src: mainSize.url,
|
||||
alt: (attachment.get('alt') !== '' && attachment.get('alt') !== undefined) ? attachment.get('alt') : attachment.get('title')
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
this._mediaManager.open();
|
||||
},
|
||||
onBeforeDestroy: function () {
|
||||
if (typeof this._mediaManager === 'object') {
|
||||
this._mediaManager.remove();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
@@ -40,6 +40,10 @@ define([
|
||||
return this._getDefaults({
|
||||
type: 'container',
|
||||
orientation: 'vertical',
|
||||
image: {
|
||||
src: null,
|
||||
display: 'scale'
|
||||
},
|
||||
styles: {
|
||||
block: {
|
||||
backgroundColor: 'transparent'
|
||||
@@ -239,16 +243,19 @@ define([
|
||||
});
|
||||
|
||||
Module.ContainerBlockSettingsView = base.BlockSettingsView.extend({
|
||||
behaviors: _.extend({}, base.BlockSettingsView.prototype.behaviors, {
|
||||
MediaManagerBehavior: {
|
||||
onSelect: 'onImageSelect'
|
||||
}
|
||||
}),
|
||||
getTemplate: function () { return window.templates.containerBlockSettings; },
|
||||
events: function () {
|
||||
return {
|
||||
'change .mailpoet_field_container_background_color': _.partial(this.changeColorField, 'styles.block.backgroundColor'),
|
||||
'click .mailpoet_done_editing': 'close'
|
||||
'click .mailpoet_done_editing': 'close',
|
||||
'change .mailpoet_field_display_type': 'changeDisplayType'
|
||||
};
|
||||
},
|
||||
regions: {
|
||||
columnsSettingsRegion: '.mailpoet_container_columns_settings'
|
||||
},
|
||||
initialize: function () {
|
||||
base.BlockSettingsView.prototype.initialize.apply(this, arguments);
|
||||
|
||||
@@ -256,8 +263,14 @@ define([
|
||||
collection: this.model.get('blocks')
|
||||
});
|
||||
},
|
||||
onRender: function () {
|
||||
this.showChildView('columnsSettingsRegion', this._columnsSettingsView);
|
||||
changeDisplayType: function (event) {
|
||||
this.model.get('image').set('display', event.target.value);
|
||||
this.model.trigger('change');
|
||||
},
|
||||
onImageSelect: function (image) {
|
||||
this.model.set('image.src', image.src);
|
||||
this.model.trigger('change');
|
||||
this.render();
|
||||
}
|
||||
});
|
||||
|
||||
|
@@ -75,6 +75,11 @@ define([
|
||||
});
|
||||
|
||||
Module.ImageBlockSettingsView = base.BlockSettingsView.extend({
|
||||
behaviors: _.extend({}, base.BlockSettingsView.prototype.behaviors, {
|
||||
MediaManagerBehavior: {
|
||||
onSelect: 'onImageSelect'
|
||||
}
|
||||
}),
|
||||
onRender: function () {
|
||||
MailPoet.helpTooltip.show(document.getElementById('tooltip-designer-full-width'), {
|
||||
tooltipId: 'tooltip-editor-full-width',
|
||||
@@ -89,11 +94,9 @@ define([
|
||||
events: function () {
|
||||
return {
|
||||
'input .mailpoet_field_image_link': _.partial(this.changeField, 'link'),
|
||||
'input .mailpoet_field_image_address': 'changeAddress',
|
||||
'input .mailpoet_field_image_alt_text': _.partial(this.changeField, 'alt'),
|
||||
'change .mailpoet_field_image_full_width': _.partial(this.changeBoolCheckboxField, 'fullWidth'),
|
||||
'change .mailpoet_field_image_alignment': _.partial(this.changeField, 'styles.block.textAlign'),
|
||||
'click .mailpoet_field_image_select_another_image': 'showMediaManager',
|
||||
'click .mailpoet_done_editing': 'close',
|
||||
'input .mailpoet_field_image_width': _.partial(this.updateValueAndCall, '.mailpoet_field_image_width_input', _.partial(this.changePixelField, 'width').bind(this)),
|
||||
'change .mailpoet_field_image_width': _.partial(this.updateValueAndCall, '.mailpoet_field_image_width_input', _.partial(this.changePixelField, 'width').bind(this)),
|
||||
@@ -120,284 +123,14 @@ define([
|
||||
this.$('.mailpoet_field_image_width').val(width);
|
||||
this.$('.mailpoet_field_image_width_input').val(width);
|
||||
},
|
||||
initialize: function (options) {
|
||||
base.BlockSettingsView.prototype.initialize.apply(this, arguments);
|
||||
|
||||
if (options.showImageManager) {
|
||||
this.showMediaManager();
|
||||
}
|
||||
},
|
||||
showMediaManager: function () {
|
||||
var that = this;
|
||||
var MediaManager;
|
||||
var theFrame;
|
||||
if (this._mediaManager) {
|
||||
this._mediaManager.resetSelections();
|
||||
this._mediaManager.open();
|
||||
return;
|
||||
}
|
||||
|
||||
MediaManager = window.wp.media.view.MediaFrame.Select.extend({
|
||||
|
||||
initialize: function () {
|
||||
window.wp.media.view.MediaFrame.prototype.initialize.apply(this, arguments);
|
||||
|
||||
_.defaults(this.options, {
|
||||
multiple: true,
|
||||
editing: false,
|
||||
state: 'insert'
|
||||
});
|
||||
|
||||
this.createSelection();
|
||||
this.createStates();
|
||||
this.bindHandlers();
|
||||
this.createIframeStates();
|
||||
|
||||
// Hide title
|
||||
this.$el.addClass('hide-title');
|
||||
},
|
||||
|
||||
resetSelections: function () {
|
||||
this.state().get('selection').reset();
|
||||
},
|
||||
|
||||
createQuery: function (options) {
|
||||
var query = window.wp.media.query(options);
|
||||
return query;
|
||||
},
|
||||
|
||||
createStates: function () {
|
||||
var options = this.options;
|
||||
|
||||
// Add the default states.
|
||||
this.states.add([
|
||||
// Main states.
|
||||
new window.wp.media.controller.Library({
|
||||
id: 'insert',
|
||||
title: 'Add images',
|
||||
priority: 20,
|
||||
toolbar: 'main-insert',
|
||||
filterable: 'image',
|
||||
library: this.createQuery(options.library),
|
||||
multiple: options.multiple ? 'reset' : false,
|
||||
editable: false,
|
||||
|
||||
// If the user isn't allowed to edit fields,
|
||||
// can they still edit it locally?
|
||||
allowLocalEdits: false,
|
||||
|
||||
// Show the attachment display settings.
|
||||
displaySettings: false,
|
||||
// Update user settings when users adjust the
|
||||
// attachment display settings.
|
||||
displayUserSettings: false
|
||||
})
|
||||
]);
|
||||
|
||||
if (window.wp.media.view.settings.post.featuredImageId) {
|
||||
this.states.add(new window.wp.media.controller.FeaturedImage());
|
||||
}
|
||||
},
|
||||
|
||||
bindHandlers: function () {
|
||||
var handlers;
|
||||
// from Select
|
||||
this.on('router:create:browse', this.createRouter, this);
|
||||
this.on('router:render:browse', this.browseRouter, this);
|
||||
this.on('content:create:browse', this.browseContent, this);
|
||||
this.on('content:render:upload', this.uploadContent, this);
|
||||
this.on('toolbar:create:select', this.createSelectToolbar, this);
|
||||
|
||||
this.on('menu:create:gallery', this.createMenu, this);
|
||||
this.on('toolbar:create:main-insert', this.createToolbar, this);
|
||||
this.on('toolbar:create:main-gallery', this.createToolbar, this);
|
||||
this.on('toolbar:create:main-embed', this.mainEmbedToolbar, this);
|
||||
|
||||
this.on('updateExcluded', this.browseContent, this);
|
||||
|
||||
handlers = {
|
||||
content: {
|
||||
embed: 'embedContent',
|
||||
'edit-selection': 'editSelectionContent'
|
||||
},
|
||||
toolbar: {
|
||||
'main-insert': 'mainInsertToolbar'
|
||||
}
|
||||
};
|
||||
|
||||
_.each(handlers, function (regionHandlers, region) {
|
||||
_.each(regionHandlers, function (callback, handler) {
|
||||
this.on(region + ':render:' + handler, this[callback], this);
|
||||
}, this);
|
||||
}, this);
|
||||
},
|
||||
|
||||
uploadContent: function () {
|
||||
window.wp.media.view.MediaFrame.Select.prototype.uploadContent.apply(this, arguments);
|
||||
this.$el.addClass('hide-toolbar');
|
||||
},
|
||||
|
||||
// Content
|
||||
embedContent: function () {
|
||||
var view = new window.wp.media.view.Embed({
|
||||
controller: this,
|
||||
model: this.state()
|
||||
}).render();
|
||||
|
||||
this.content.set(view);
|
||||
view.url.focus();
|
||||
},
|
||||
|
||||
editSelectionContent: function () {
|
||||
var state = this.state();
|
||||
var selection = state.get('selection');
|
||||
var view;
|
||||
|
||||
view = new window.wp.media.view.AttachmentsBrowser({
|
||||
controller: this,
|
||||
collection: selection,
|
||||
selection: selection,
|
||||
model: state,
|
||||
sortable: true,
|
||||
search: false,
|
||||
dragInfo: true,
|
||||
|
||||
AttachmentView: window.wp.media.view.Attachment.EditSelection
|
||||
}).render();
|
||||
|
||||
view.toolbar.set('backToLibrary', {
|
||||
text: 'Return to library',
|
||||
priority: -100,
|
||||
|
||||
click: function () {
|
||||
this.controller.content.mode('browse');
|
||||
}
|
||||
});
|
||||
|
||||
// Browse our library of attachments.
|
||||
this.content.set(view);
|
||||
},
|
||||
|
||||
// Toolbars
|
||||
selectionStatusToolbar: function (view) {
|
||||
var editable = this.state().get('editable');
|
||||
|
||||
view.set('selection', new window.wp.media.view.Selection({
|
||||
controller: this,
|
||||
collection: this.state().get('selection'),
|
||||
priority: -40,
|
||||
|
||||
// If the selection is editable, pass the callback to
|
||||
// switch the content mode.
|
||||
editable: editable && function () {
|
||||
this.controller.content.mode('edit-selection');
|
||||
}
|
||||
}).render());
|
||||
},
|
||||
|
||||
mainInsertToolbar: function (view) {
|
||||
var controller = this;
|
||||
|
||||
this.selectionStatusToolbar(view);
|
||||
|
||||
view.set('insert', {
|
||||
style: 'primary',
|
||||
priority: 80,
|
||||
text: 'Select Image',
|
||||
requires: { selection: true },
|
||||
|
||||
click: function () {
|
||||
var state = controller.state();
|
||||
var selection = state.get('selection');
|
||||
|
||||
controller.close();
|
||||
state.trigger('insert', selection).reset();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
mainEmbedToolbar: function (toolbar) {
|
||||
var tbar = toolbar;
|
||||
tbar.view = new window.wp.media.view.Toolbar.Embed({
|
||||
controller: this,
|
||||
text: 'Add images'
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
theFrame = new MediaManager({
|
||||
id: 'mailpoet-media-manager',
|
||||
frame: 'select',
|
||||
title: 'Select image',
|
||||
editing: false,
|
||||
multiple: false,
|
||||
library: {
|
||||
type: 'image'
|
||||
},
|
||||
displaySettings: false,
|
||||
button: {
|
||||
text: 'Select'
|
||||
}
|
||||
});
|
||||
this._mediaManager = theFrame;
|
||||
|
||||
this._mediaManager.on('insert', function () {
|
||||
// Append media manager image selections to Images tab
|
||||
var selection = theFrame.state().get('selection');
|
||||
selection.each(function (attachment) {
|
||||
var sizes = attachment.get('sizes');
|
||||
// Following advice from Becs, the target width should
|
||||
// be a double of one column width to render well on
|
||||
// retina screen devices
|
||||
var targetImageWidth = 1320;
|
||||
|
||||
// Pick the width that is closest to target width
|
||||
var increasingByWidthDifference = _.sortBy(
|
||||
_.keys(sizes),
|
||||
function (size) { return Math.abs(targetImageWidth - sizes[size].width); }
|
||||
);
|
||||
var bestWidth = sizes[_.first(increasingByWidthDifference)].width;
|
||||
var imagesOfBestWidth = _.filter(
|
||||
_.values(sizes),
|
||||
function (size) { return size.width === bestWidth; }
|
||||
);
|
||||
|
||||
// Maximize the height if there are multiple images with same width
|
||||
var mainSize = _.max(imagesOfBestWidth, function (size) { return size.height; });
|
||||
|
||||
that.model.set({
|
||||
height: mainSize.height + 'px',
|
||||
width: mainSize.width + 'px',
|
||||
src: mainSize.url,
|
||||
alt: (attachment.get('alt') !== '' && attachment.get('alt') !== undefined) ? attachment.get('alt') : attachment.get('title')
|
||||
});
|
||||
// Rerender settings view due to changes from outside of settings view
|
||||
that.render();
|
||||
});
|
||||
});
|
||||
|
||||
this._mediaManager.open();
|
||||
},
|
||||
changeAddress: function (event) {
|
||||
var src = jQuery(event.target).val();
|
||||
var image = new Image();
|
||||
|
||||
image.onload = function () {
|
||||
this.model.set({
|
||||
src: src,
|
||||
width: image.naturalWidth + 'px',
|
||||
height: image.naturalHeight + 'px'
|
||||
});
|
||||
}.bind(this);
|
||||
|
||||
image.src = src;
|
||||
},
|
||||
onBeforeDestroy: function () {
|
||||
base.BlockSettingsView.prototype.onBeforeDestroy.apply(this, arguments);
|
||||
if (typeof this._mediaManager === 'object') {
|
||||
this._mediaManager.remove();
|
||||
onImageSelect: function (image) {
|
||||
if (image.src === null) {
|
||||
this.model.set({ src: '' });
|
||||
} else {
|
||||
this.model.set(image);
|
||||
}
|
||||
// Rerender settings view due to changes from outside of settings view
|
||||
this.render();
|
||||
}
|
||||
});
|
||||
|
||||
|
@@ -2,14 +2,14 @@
|
||||
namespace MailPoet\Newsletter\Renderer\Columns;
|
||||
|
||||
class Renderer {
|
||||
function render($column_styles, $columns_count, $columns_data) {
|
||||
function render($column_styles, $column_image, $columns_count, $columns_data) {
|
||||
$styles = $column_styles['block'];
|
||||
$width = ColumnsHelper::columnWidth($columns_count);
|
||||
$class = ColumnsHelper::columnClass($columns_count);
|
||||
$alignment = ColumnsHelper::columnAlignment($columns_count);
|
||||
$template = ($columns_count === 1) ?
|
||||
$this->getOneColumnTemplate($styles, $class) :
|
||||
$this->getMultipleColumnsTemplate($styles, $width, $alignment, $class);
|
||||
$this->getOneColumnTemplate($styles, $column_image, $class) :
|
||||
$this->getMultipleColumnsTemplate($styles, $column_image, $width, $alignment, $class);
|
||||
$result = array_map(function($content) use ($template) {
|
||||
return $template['content_start'] . $content . $template['content_end'];
|
||||
}, $columns_data);
|
||||
@@ -20,16 +20,16 @@ class Renderer {
|
||||
return $result;
|
||||
}
|
||||
|
||||
function getOneColumnTemplate($styles, $class) {
|
||||
$background_color = $this->getBackgroundColor($styles);
|
||||
function getOneColumnTemplate($styles, $image, $class) {
|
||||
$background_css = $this->getBackgroundCss($styles, $image);
|
||||
$template['content_start'] = '
|
||||
<tr>
|
||||
<td class="mailpoet_content" align="center" style="border-collapse:collapse">
|
||||
<td class="mailpoet_content" align="center" style="border-collapse:collapse;' . $background_css . '">
|
||||
<table width="100%" border="0" cellpadding="0" cellspacing="0" style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="padding-left:0;padding-right:0">
|
||||
<table width="100%" border="0" cellpadding="0" cellspacing="0" class="mailpoet_' . $class . '" style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;table-layout:fixed;margin-left:auto;margin-right:auto;padding-left:0;padding-right:0;' . $background_color . '">
|
||||
<table width="100%" border="0" cellpadding="0" cellspacing="0" class="mailpoet_' . $class . '" style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;table-layout:fixed;margin-left:auto;margin-right:auto;padding-left:0;padding-right:0;">
|
||||
<tbody>';
|
||||
$template['content_end'] = '
|
||||
</tbody>
|
||||
@@ -43,11 +43,11 @@ class Renderer {
|
||||
return $template;
|
||||
}
|
||||
|
||||
function getMultipleColumnsTemplate($styles, $width, $alignment, $class) {
|
||||
$background_color = $this->getBackgroundColor($styles);
|
||||
function getMultipleColumnsTemplate($styles, $image, $width, $alignment, $class) {
|
||||
$background_css = $this->getBackgroundCss($styles, $image);
|
||||
$template['container_start'] = '
|
||||
<tr>
|
||||
<td class="mailpoet_content-' . $class . '" align="left" style="border-collapse:collapse;' . $background_color . '">
|
||||
<td class="mailpoet_content-' . $class . '" align="left" style="border-collapse:collapse;' . $background_css . '">
|
||||
<table width="100%" border="0" cellpadding="0" cellspacing="0" style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0">
|
||||
<tbody>
|
||||
<tr>
|
||||
@@ -78,11 +78,21 @@ class Renderer {
|
||||
return $template;
|
||||
}
|
||||
|
||||
function getBackgroundColor($styles) {
|
||||
if(!isset($styles['backgroundColor'])) return false;
|
||||
$background_color = $styles['backgroundColor'];
|
||||
return ($background_color !== 'transparent') ?
|
||||
sprintf('background-color:%s!important;" bgcolor="%s', $background_color, $background_color) :
|
||||
false;
|
||||
private function getBackgroundCss($styles, $image) {
|
||||
if($image !== null && $image['src'] !== null) {
|
||||
$background_color = isset($styles['backgroundColor']) && $styles['backgroundColor'] !== 'transparent' ? $styles['backgroundColor'] : '#ffffff';
|
||||
$repeat = $image['display'] === 'tile' ? 'repeat' : 'no-repeat';
|
||||
$size = $image['display'] === 'scale' ? 'cover' : 'contain';
|
||||
return sprintf(
|
||||
'background: %s url(%s) %s center/%s;background-color: %s;background-image: url(%s);background-repeat: %s;background-position: center;background-size: %s;',
|
||||
$background_color, $image['src'], $repeat, $size, $background_color, $image['src'], $repeat, $size
|
||||
);
|
||||
} else {
|
||||
if(!isset($styles['backgroundColor'])) return false;
|
||||
$background_color = $styles['backgroundColor'];
|
||||
return ($background_color !== 'transparent') ?
|
||||
sprintf('background-color:%s!important;" bgcolor="%s', $background_color, $background_color) :
|
||||
false;
|
||||
}
|
||||
}
|
||||
}
|
@@ -102,8 +102,10 @@ class Renderer {
|
||||
$content_block,
|
||||
$column_count
|
||||
);
|
||||
$content_block_image = isset($content_block['image'])?$content_block['image']:null;
|
||||
return $_this->columns_renderer->render(
|
||||
$content_block['styles'],
|
||||
$content_block_image,
|
||||
$column_count,
|
||||
$column_data
|
||||
);
|
||||
|
@@ -29,6 +29,10 @@ define([
|
||||
expect(model.get('styles.block.backgroundColor')).to.match(/^(#[abcdef0-9]{6})|transparent$/);
|
||||
});
|
||||
|
||||
it('has a image display style', function () {
|
||||
expect(model.get('image.display')).to.equal('scale');
|
||||
});
|
||||
|
||||
it('has a collection of blocks', function () {
|
||||
expect(model.get('blocks')).to.be.instanceof(Backbone.Collection);
|
||||
});
|
||||
@@ -42,6 +46,10 @@ define([
|
||||
block: {
|
||||
backgroundColor: '#123456'
|
||||
}
|
||||
},
|
||||
image: {
|
||||
src: null,
|
||||
display: 'scale'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,6 +57,7 @@ define([
|
||||
innerModel = new (ContainerBlock.ContainerBlockModel)();
|
||||
|
||||
expect(innerModel.get('styles.block.backgroundColor')).to.equal('#123456');
|
||||
expect(innerModel.get('image.display')).to.equal('scale');
|
||||
});
|
||||
|
||||
it('do not update blockDefaults.container when changed', function () {
|
||||
@@ -130,7 +139,22 @@ define([
|
||||
|
||||
describe('once rendered', function () {
|
||||
describe('on root level', function () {
|
||||
var model = new (ContainerBlock.ContainerBlockModel)();
|
||||
var imageSrc = 'http://example.org/someNewImage.png';
|
||||
var model = new (ContainerBlock.ContainerBlockModel)({
|
||||
type: 'container',
|
||||
orientation: 'vertical',
|
||||
image: {
|
||||
src: imageSrc,
|
||||
display: 'scale',
|
||||
width: 123,
|
||||
height: 456
|
||||
},
|
||||
styles: {
|
||||
block: {
|
||||
backgroundColor: 'transparent'
|
||||
}
|
||||
}
|
||||
});
|
||||
var view;
|
||||
|
||||
beforeEach(function () {
|
||||
@@ -159,6 +183,15 @@ define([
|
||||
it('has a duplication tool', function () {
|
||||
expect(view.$('.mailpoet_duplicate_block')).to.have.length(1);
|
||||
});
|
||||
|
||||
it('has a background image set', function () {
|
||||
var style = view.$('style').text();
|
||||
expect(style).contains('.mailpoet_editor_view_' + view.cid);
|
||||
expect(style).contains('background-color: #ffffff !important;');
|
||||
expect(style).contains('background-image: url(http://example.org/someNewImage.png);');
|
||||
expect(style).contains('background-position: center;');
|
||||
expect(style).contains('background-size: cover;');
|
||||
});
|
||||
});
|
||||
|
||||
describe.skip('on non-root levels', function () {
|
||||
@@ -211,11 +244,13 @@ define([
|
||||
describe('once rendered', function () {
|
||||
var model;
|
||||
var view;
|
||||
var newSrc = 'http://example.org/someNewImage.png';
|
||||
beforeEach(function () {
|
||||
global.stubChannel(EditorApplication);
|
||||
global.stubAvailableStyles(EditorApplication);
|
||||
model = new (ContainerBlock.ContainerBlockModel)();
|
||||
view = new (ContainerBlock.ContainerBlockSettingsView)({ model: model });
|
||||
view.render();
|
||||
});
|
||||
|
||||
it('updates the model when background color changes', function () {
|
||||
@@ -223,6 +258,23 @@ define([
|
||||
expect(model.get('styles.block.backgroundColor')).to.equal('#123456');
|
||||
});
|
||||
|
||||
it('updates the model background image display type changes', function () {
|
||||
view.$('.mailpoet_field_display_type:nth(2)').attr('checked', true).change();
|
||||
expect(model.get('image.display')).to.equal('tile');
|
||||
});
|
||||
|
||||
it('updates the model when background image src changes', function () {
|
||||
global.stubImage(123, 456);
|
||||
view.$('.mailpoet_field_image_address').val(newSrc).trigger('input');
|
||||
expect(model.get('image.src')).to.equal(newSrc);
|
||||
});
|
||||
|
||||
it('updates the model when background image src is deleted', function () {
|
||||
global.stubImage(123, 456);
|
||||
view.$('.mailpoet_field_image_address').val('').trigger('input');
|
||||
expect(model.get('image.src')).to.equal(null);
|
||||
});
|
||||
|
||||
it.skip('closes the sidepanel after "Done" is clicked', function () {
|
||||
var mock = sinon.mock().once();
|
||||
global.MailPoet.Modal.cancel = mock;
|
||||
|
@@ -59,6 +59,7 @@ class RendererTest extends \MailPoetTest {
|
||||
$DOM = $this->DOM_parser->parseStr(
|
||||
$this->column_renderer->render(
|
||||
$column_styles,
|
||||
null,
|
||||
count($column_content),
|
||||
$column_content)
|
||||
);
|
||||
@@ -81,6 +82,7 @@ class RendererTest extends \MailPoetTest {
|
||||
$DOM = $this->DOM_parser->parseStr(
|
||||
$this->column_renderer->render(
|
||||
$column_styles,
|
||||
null,
|
||||
count($column_content),
|
||||
$column_content)
|
||||
);
|
||||
@@ -104,6 +106,7 @@ class RendererTest extends \MailPoetTest {
|
||||
$DOM = $this->DOM_parser->parseStr(
|
||||
$this->column_renderer->render(
|
||||
$column_styles,
|
||||
null,
|
||||
count($column_content),
|
||||
$column_content)
|
||||
);
|
||||
@@ -113,6 +116,82 @@ class RendererTest extends \MailPoetTest {
|
||||
expect($rendered_column_content)->equals($column_content);
|
||||
}
|
||||
|
||||
function testItRendersScaledColumnBackgroundImage() {
|
||||
$column_content = ['one'];
|
||||
$column_styles = ['block' => ['backgroundColor' => "#999999"]];
|
||||
$column_image = ['src' => 'https://example.com/image.jpg', 'display' => 'scale', 'width' => '1000px', 'height' => '500px'];
|
||||
$DOM = $this->DOM_parser->parseStr(
|
||||
$this->column_renderer->render(
|
||||
$column_styles,
|
||||
$column_image,
|
||||
count($column_content),
|
||||
$column_content)
|
||||
);
|
||||
$column_css = $DOM('td.mailpoet_content')[0]->attr('style');
|
||||
expect($column_css)->contains('background: #999999 url(https://example.com/image.jpg) no-repeat center/cover;');
|
||||
expect($column_css)->contains('background-color: #999999;');
|
||||
expect($column_css)->contains('background-image: url(https://example.com/image.jpg);');
|
||||
expect($column_css)->contains('background-repeat: no-repeat;');
|
||||
expect($column_css)->contains('background-position: center;');
|
||||
expect($column_css)->contains('background-size: cover;');
|
||||
}
|
||||
|
||||
function testItRendersFitColumnBackgroundImage() {
|
||||
$column_content = ['one'];
|
||||
$column_styles = ['block' => ['backgroundColor' => "#999999"]];
|
||||
$column_image = ['src' => 'https://example.com/image.jpg', 'display' => 'fit', 'width' => '1000px', 'height' => '500px'];
|
||||
$DOM = $this->DOM_parser->parseStr(
|
||||
$this->column_renderer->render(
|
||||
$column_styles,
|
||||
$column_image,
|
||||
count($column_content),
|
||||
$column_content)
|
||||
);
|
||||
$column_css = $DOM('td.mailpoet_content')[0]->attr('style');
|
||||
expect($column_css)->contains('background: #999999 url(https://example.com/image.jpg) no-repeat center/contain;');
|
||||
expect($column_css)->contains('background-color: #999999;');
|
||||
expect($column_css)->contains('background-image: url(https://example.com/image.jpg);');
|
||||
expect($column_css)->contains('background-repeat: no-repeat;');
|
||||
expect($column_css)->contains('background-position: center;');
|
||||
expect($column_css)->contains('background-size: contain;');
|
||||
}
|
||||
|
||||
function testItRendersTiledColumnBackgroundImage() {
|
||||
$column_content = ['one'];
|
||||
$column_styles = ['block' => ['backgroundColor' => "#999999"]];
|
||||
$column_image = ['src' => 'https://example.com/image.jpg', 'display' => 'tile', 'width' => '1000px', 'height' => '500px'];
|
||||
$DOM = $this->DOM_parser->parseStr(
|
||||
$this->column_renderer->render(
|
||||
$column_styles,
|
||||
$column_image,
|
||||
count($column_content),
|
||||
$column_content)
|
||||
);
|
||||
$column_css = $DOM('td.mailpoet_content')[0]->attr('style');
|
||||
expect($column_css)->contains('background: #999999 url(https://example.com/image.jpg) repeat center/contain;');
|
||||
expect($column_css)->contains('background-color: #999999;');
|
||||
expect($column_css)->contains('background-image: url(https://example.com/image.jpg);');
|
||||
expect($column_css)->contains('background-repeat: repeat;');
|
||||
expect($column_css)->contains('background-position: center;');
|
||||
expect($column_css)->contains('background-size: contain;');
|
||||
}
|
||||
|
||||
function testItRendersFallbackColumnBackgroundColorForBackgroundImage() {
|
||||
$column_content = ['one'];
|
||||
$column_styles = ['block' => ['backgroundColor' => 'transparent']];
|
||||
$column_image = ['src' => 'https://example.com/image.jpg', 'display' => 'tile', 'width' => '1000px', 'height' => '500px'];
|
||||
$DOM = $this->DOM_parser->parseStr(
|
||||
$this->column_renderer->render(
|
||||
$column_styles,
|
||||
$column_image,
|
||||
count($column_content),
|
||||
$column_content)
|
||||
);
|
||||
$column_css = $DOM('td.mailpoet_content')[0]->attr('style');
|
||||
expect($column_css)->contains('background: #ffffff url(https://example.com/image.jpg) repeat center/contain;');
|
||||
expect($column_css)->contains('background-color: #ffffff;');
|
||||
}
|
||||
|
||||
function testItRendersHeader() {
|
||||
$newsletter = $this->newsletter['body'];
|
||||
$template = $newsletter['content']['blocks'][0]['blocks'][0]['blocks'][0];
|
||||
|
@@ -1113,6 +1113,10 @@
|
||||
},
|
||||
},
|
||||
container: {
|
||||
image: {
|
||||
src: null,
|
||||
display: 'scale',
|
||||
},
|
||||
styles: {
|
||||
block: {
|
||||
backgroundColor: 'transparent',
|
||||
|
@@ -1,8 +1,22 @@
|
||||
{{#ifCond model.styles.block.backgroundColor '!=' 'transparent'}}
|
||||
<style type="text/css">
|
||||
.mailpoet_editor_view_{{ viewCid }} { background-color: {{ model.styles.block.backgroundColor }}; }
|
||||
.mailpoet_editor_view_{{ viewCid }} .mailpoet_container { background-color: {{ model.styles.block.backgroundColor }}; }
|
||||
</style>
|
||||
{{/ifCond}}
|
||||
{{#if model.image.src}}
|
||||
<style type="text/css">
|
||||
.mailpoet_editor_view_{{ viewCid }} {
|
||||
background-color: {{#ifCond model.styles.block.backgroundColor '!=' 'transparent'}}{{ model.styles.block.backgroundColor }}{{else}}#ffffff{{/ifCond}} !important;
|
||||
background-image: url({{ model.image.src }});
|
||||
background-position: center;
|
||||
background-repeat: {{#ifCond model.image.display '==' 'tile'}}repeat{{else}}no-repeat{{/ifCond}};
|
||||
background-size: {{#ifCond model.image.display '==' 'scale'}}cover{{else}}contain{{/ifCond}};
|
||||
}
|
||||
.mailpoet_editor_view_{{ viewCid }} .mailpoet_container { background: transparent; }
|
||||
</style>
|
||||
{{else}}
|
||||
{{#ifCond model.styles.block.backgroundColor '!=' 'transparent'}}
|
||||
<style type="text/css">
|
||||
.mailpoet_editor_view_{{ viewCid }} { background-color: {{ model.styles.block.backgroundColor }}; }
|
||||
.mailpoet_editor_view_{{ viewCid }} .mailpoet_container { background-color: {{ model.styles.block.backgroundColor }}; }
|
||||
</style>
|
||||
{{/ifCond}}
|
||||
{{/if}}
|
||||
|
||||
<div class="mailpoet_container {{#ifCond model.orientation '===' 'horizontal'}}mailpoet_container_horizontal{{/ifCond}}{{#ifCond model.orientation '===' 'vertical'}}mailpoet_container_vertical{{/ifCond}}"></div>
|
||||
<div class="mailpoet_tools"></div><div class="mailpoet_block_highlight">
|
||||
|
@@ -4,11 +4,41 @@
|
||||
<div class="mailpoet_form_field_input_option">
|
||||
<input type="text" name="background-color" class="mailpoet_field_container_background_color mailpoet_color" value="{{ model.styles.block.backgroundColor }}" />
|
||||
</div>
|
||||
<div class="mailpoet_form_field_title mailpoet_form_field_title_inline"><%= __('Background') %></div>
|
||||
<div class="mailpoet_form_field_title mailpoet_form_field_title_inline"><%= __('Background color') %></div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="mailpoet_container_columns_settings"></div>
|
||||
<div class="mailpoet_form_field">
|
||||
<label>
|
||||
<div class="mailpoet_form_field_title"><%= __('Background image') %></div>
|
||||
<div class="mailpoet_form_field_input_option">
|
||||
<input type="text" name="src" class="mailpoet_input mailpoet_field_image_address" value="{{ model.image.src }}" placeholder="http://" /><br />
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="mailpoet_form_field">
|
||||
<input type="button" name="select-image" class="button button-secondary mailpoet_button_full mailpoet_field_image_select_image" value="{{#if model.image.src}}<%= __('Select another image') | escape('html_attr') %>{{else}}<%= __('Select image') | escape('html_attr') %>{{/if}}" />
|
||||
</div>
|
||||
<div class="mailpoet_form_field">
|
||||
<div class="mailpoet_form_field_title"><%= __('Display options') %></div>
|
||||
<div class="mailpoet_form_field_radio_option">
|
||||
<label>
|
||||
<input type="radio" name="display_type" class="mailpoet_field_display_type" value="scale" {{#ifCond model.image.display '===' 'scale'}}CHECKED{{/ifCond}}/>
|
||||
<%= __('Scale') %>
|
||||
</label>
|
||||
</div>
|
||||
<div class="mailpoet_form_field_radio_option">
|
||||
<label>
|
||||
<input type="radio" name="display_type" class="mailpoet_field_display_type" value="fit" {{#ifCond model.image.display '===' 'fit'}}CHECKED{{/ifCond}}/>
|
||||
<%= __('Fit') %>
|
||||
</label>
|
||||
</div>
|
||||
<div class="mailpoet_form_field_radio_option">
|
||||
<label>
|
||||
<input type="radio" name="display_type" class="mailpoet_field_display_type" value="tile" {{#ifCond model.image.display '===' 'tile'}}CHECKED{{/ifCond}}/>
|
||||
<%= __('Tile') %>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mailpoet_form_field">
|
||||
<input type="button" class="button button-primary mailpoet_done_editing" value="<%= __('Done') | escape('html_attr') %>" />
|
||||
|
@@ -2,7 +2,7 @@
|
||||
<div class="mailpoet_content" style="{{#ifCond model.styles.block.textAlign '==' 'left'}}margin: 0 auto 0 0; {{/ifCond}}{{#ifCond model.styles.block.textAlign '==' 'center'}}margin: auto; {{/ifCond}}{{#ifCond model.styles.block.textAlign '==' 'right'}}margin: 0 0 0 auto; {{/ifCond}}width: {{model.width}}">
|
||||
<div class="mailpoet_image">
|
||||
<a href="{{ model.link }}" onClick="return false;">
|
||||
<img src="{{#ifCond model.src '!=' ''}}{{ model.src }}{{ else }}{{ imageMissingSrc }}{{/ifCond}}" alt="{{ model.alt }}" onerror="if(this.src != '{{ imageMissingSrc }}') {this.src = '{{ imageMissingSrc }}'; this.style.width='auto';}" width="{{model.width}}" />
|
||||
<img src="{{#ifCond model.src '!=' ''}}{{ model.src }}{{ else }}{{ imageMissingSrc }}{{/ifCond}}" alt="{{ model.alt }}" onerror="if(this.src != '{{ imageMissingSrc }}') {this.src = '{{ imageMissingSrc }}';}" width="{{model.width}}" />
|
||||
</a>
|
||||
<div class="mailpoet_image_resize_handle_container">
|
||||
<div class="mailpoet_image_resize_handle">
|
||||
|
@@ -80,7 +80,7 @@
|
||||
</div>
|
||||
<hr />
|
||||
<div class="mailpoet_form_field">
|
||||
<input type="button" name="select-another-image" class="button button-secondary mailpoet_button_full mailpoet_field_image_select_another_image" value="<%= __('Select another image') | escape('html_attr') %>" />
|
||||
<input type="button" name="select-image" class="button button-secondary mailpoet_button_full mailpoet_field_image_select_image" value="<%= __('Select another image') | escape('html_attr') %>" />
|
||||
</div>
|
||||
|
||||
<div class="mailpoet_form_field">
|
||||
|
@@ -310,6 +310,7 @@ var adminConfig = {
|
||||
'newsletter_editor/behaviors/DraggableBehavior.js',
|
||||
'newsletter_editor/behaviors/HighlightContainerBehavior.js',
|
||||
'newsletter_editor/behaviors/HighlightEditingBehavior.js',
|
||||
'newsletter_editor/behaviors/MediaManagerBehavior.js',
|
||||
'newsletter_editor/behaviors/ResizableBehavior.js',
|
||||
'newsletter_editor/behaviors/SortableBehavior.js',
|
||||
'newsletter_editor/behaviors/ShowSettingsBehavior.js',
|
||||
@@ -414,6 +415,7 @@ var testConfig = {
|
||||
'newsletter_editor/behaviors/DraggableBehavior.js',
|
||||
'newsletter_editor/behaviors/HighlightContainerBehavior.js',
|
||||
'newsletter_editor/behaviors/HighlightEditingBehavior.js',
|
||||
'newsletter_editor/behaviors/MediaManagerBehavior.js',
|
||||
'newsletter_editor/behaviors/ResizableBehavior.js',
|
||||
'newsletter_editor/behaviors/SortableBehavior.js',
|
||||
'newsletter_editor/behaviors/ShowSettingsBehavior.js',
|
||||
|
Reference in New Issue
Block a user