diff --git a/RoboFile.php b/RoboFile.php index 7f314f0a04..43e6f73501 100644 --- a/RoboFile.php +++ b/RoboFile.php @@ -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); } diff --git a/assets/js/src/newsletter_editor/behaviors/MediaManagerBehavior.js b/assets/js/src/newsletter_editor/behaviors/MediaManagerBehavior.js new file mode 100644 index 0000000000..1cd9e309fa --- /dev/null +++ b/assets/js/src/newsletter_editor/behaviors/MediaManagerBehavior.js @@ -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(); + } + } + }); +}); diff --git a/assets/js/src/newsletter_editor/blocks/container.js b/assets/js/src/newsletter_editor/blocks/container.js index 0843fee878..0c86112b19 100644 --- a/assets/js/src/newsletter_editor/blocks/container.js +++ b/assets/js/src/newsletter_editor/blocks/container.js @@ -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(); } }); diff --git a/assets/js/src/newsletter_editor/blocks/image.js b/assets/js/src/newsletter_editor/blocks/image.js index 2b9c69660c..1a84011b4f 100644 --- a/assets/js/src/newsletter_editor/blocks/image.js +++ b/assets/js/src/newsletter_editor/blocks/image.js @@ -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(); } }); diff --git a/lib/Newsletter/Renderer/Columns/Renderer.php b/lib/Newsletter/Renderer/Columns/Renderer.php index c44ef533cf..55aa72c8ed 100644 --- a/lib/Newsletter/Renderer/Columns/Renderer.php +++ b/lib/Newsletter/Renderer/Columns/Renderer.php @@ -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'] = ' - +
- +
'; $template['content_end'] = ' @@ -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'] = ' -
+ @@ -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; + } } } \ No newline at end of file diff --git a/lib/Newsletter/Renderer/Renderer.php b/lib/Newsletter/Renderer/Renderer.php index f9b051c9f6..65a98677e4 100644 --- a/lib/Newsletter/Renderer/Renderer.php +++ b/lib/Newsletter/Renderer/Renderer.php @@ -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 ); diff --git a/tests/javascript/newsletter_editor/blocks/container.spec.js b/tests/javascript/newsletter_editor/blocks/container.spec.js index ef0e7ebfe9..231c0dfab0 100644 --- a/tests/javascript/newsletter_editor/blocks/container.spec.js +++ b/tests/javascript/newsletter_editor/blocks/container.spec.js @@ -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; diff --git a/tests/unit/Newsletter/RendererTest.php b/tests/unit/Newsletter/RendererTest.php index a600fa2e3c..9c8405f180 100644 --- a/tests/unit/Newsletter/RendererTest.php +++ b/tests/unit/Newsletter/RendererTest.php @@ -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]; diff --git a/views/newsletter/editor.html b/views/newsletter/editor.html index e38dd2c5b8..6339afe961 100644 --- a/views/newsletter/editor.html +++ b/views/newsletter/editor.html @@ -1113,6 +1113,10 @@ }, }, container: { + image: { + src: null, + display: 'scale', + }, styles: { block: { backgroundColor: 'transparent', diff --git a/views/newsletter/templates/blocks/container/block.hbs b/views/newsletter/templates/blocks/container/block.hbs index 61687e89d9..a13c2905f5 100644 --- a/views/newsletter/templates/blocks/container/block.hbs +++ b/views/newsletter/templates/blocks/container/block.hbs @@ -1,8 +1,22 @@ -{{#ifCond model.styles.block.backgroundColor '!=' 'transparent'}} - -{{/ifCond}} +{{#if model.image.src}} + +{{else}} + {{#ifCond model.styles.block.backgroundColor '!=' 'transparent'}} + + {{/ifCond}} +{{/if}} +
diff --git a/views/newsletter/templates/blocks/container/settings.hbs b/views/newsletter/templates/blocks/container/settings.hbs index 1ecf80f3c8..256f66fcf0 100644 --- a/views/newsletter/templates/blocks/container/settings.hbs +++ b/views/newsletter/templates/blocks/container/settings.hbs @@ -4,11 +4,41 @@
-
<%= __('Background') %>
+
<%= __('Background color') %>
- -
+
+ +
+
+ +
+
+
<%= __('Display options') %>
+
+ +
+
+ +
+
+ +
+
diff --git a/views/newsletter/templates/blocks/image/block.hbs b/views/newsletter/templates/blocks/image/block.hbs index d345bbbff2..079ea6b31b 100644 --- a/views/newsletter/templates/blocks/image/block.hbs +++ b/views/newsletter/templates/blocks/image/block.hbs @@ -2,7 +2,7 @@
- {{ model.alt }} + {{ model.alt }}
diff --git a/views/newsletter/templates/blocks/image/settings.hbs b/views/newsletter/templates/blocks/image/settings.hbs index 1b198802b9..26ca017929 100644 --- a/views/newsletter/templates/blocks/image/settings.hbs +++ b/views/newsletter/templates/blocks/image/settings.hbs @@ -80,7 +80,7 @@

- +
diff --git a/webpack.config.js b/webpack.config.js index 559fe9a3a1..014d550ceb 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -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',