Add a method to bulk update ALC blocks in newsletter editor

This commit is contained in:
Tautvidas Sipavičius
2016-05-31 13:53:45 +03:00
parent c6b13c5175
commit 5d48ecac80
9 changed files with 1478 additions and 451 deletions

View File

@ -13,6 +13,7 @@ define([
'newsletter_editor/blocks/divider', 'newsletter_editor/blocks/divider',
'newsletter_editor/components/communication', 'newsletter_editor/components/communication',
'mailpoet', 'mailpoet',
'backbone.supermodel',
'underscore', 'underscore',
'jquery' 'jquery'
], function( ], function(
@ -22,6 +23,7 @@ define([
DividerBlock, DividerBlock,
CommunicationComponent, CommunicationComponent,
MailPoet, MailPoet,
SuperModel,
_, _,
jQuery jQuery
) { ) {
@ -31,6 +33,36 @@ define([
var Module = {}, var Module = {},
base = BaseBlock; base = BaseBlock;
Module.ALCSupervisor = SuperModel.extend({
initialize: function() {
this.listenTo(App.getChannel(), 'automatedLatestContentRefresh', this.refresh);
},
refresh: function() {
var models = App.findModels(function(model) {
return model.get('type') === 'automatedLatestContent';
}) || [];
if (models.length === 0) return;
var blocks = _.map(models, function(model) {
return model.toJSON();
});
CommunicationComponent.getBulkTransformedPosts({
blocks: blocks,
}).then(_.partial(this.refreshBlocks, models));
},
refreshBlocks: function(models, renderedBlocks) {
_.each(
_.zip(models, renderedBlocks),
function(args) {
var model = args[0],
contents = args[1];
model.trigger('refreshPosts', contents);
}
);
},
});
Module.AutomatedLatestContentBlockModel = base.BlockModel.extend({ Module.AutomatedLatestContentBlockModel = base.BlockModel.extend({
stale: ['_container'], stale: ['_container'],
defaults: function() { defaults: function() {
@ -72,34 +104,32 @@ define([
}, },
initialize: function() { initialize: function() {
base.BlockView.prototype.initialize.apply(this, arguments); base.BlockView.prototype.initialize.apply(this, arguments);
this.fetchPosts();
this.on('change:amount change:contentType change:terms change:inclusionType change:displayType change:titleFormat change:featuredImagePosition change:titleAlignment change:titleIsLink change:imageFullWidth change:showAuthor change:authorPrecededBy change:showCategories change:categoriesPrecededBy change:readMoreType change:readMoreText change:sortBy change:showDivider', this._scheduleFetchPosts, this); this.on('change:amount change:contentType change:terms change:inclusionType change:displayType change:titleFormat change:featuredImagePosition change:titleAlignment change:titleIsLink change:imageFullWidth change:showAuthor change:authorPrecededBy change:showCategories change:categoriesPrecededBy change:readMoreType change:readMoreText change:sortBy change:showDivider', this._scheduleFetchPosts, this);
this.listenTo(this.get('readMoreButton'), 'change', this._scheduleFetchPosts); this.listenTo(this.get('readMoreButton'), 'change', this._scheduleFetchPosts);
this.listenTo(this.get('divider'), 'change', this._scheduleFetchPosts); this.listenTo(this.get('divider'), 'change', this._scheduleFetchPosts);
}, this.on('add remove update reset', function(model, collection, options) {
fetchPosts: function() { App.getChannel().trigger('automatedLatestContentRefresh');
var that = this;
CommunicationComponent.getTransformedPosts(this.toJSON()).done(function(content) {
that.get('_container').get('blocks').reset(content, {parse: true});
that.trigger('postsChanged');
}).fail(function(error) {
MailPoet.Notice.error(MailPoet.I18n.t('failedToFetchRenderedPosts'));
}); });
this.on('refreshPosts', this.updatePosts, this);
},
updatePosts: function(posts) {
this.get('_container.blocks').reset(posts, {parse: true});
}, },
/** /**
* Batch more changes during a specific time, instead of fetching * Batch more changes during a specific time, instead of fetching
* ALC posts on each model change * ALC posts on each model change
*/ */
_scheduleFetchPosts: function() { _scheduleFetchPosts: function() {
var timeout = 500, var TIMEOUT = 500,
that = this; that = this;
if (this._fetchPostsTimer !== undefined) { if (this._fetchPostsTimer !== undefined) {
clearTimeout(this._fetchPostsTimer); clearTimeout(this._fetchPostsTimer);
} }
this._fetchPostsTimer = setTimeout(function() { this._fetchPostsTimer = setTimeout(function() {
that.fetchPosts(); //that.fetchPosts();
App.getChannel().trigger('automatedLatestContentRefresh');
that._fetchPostsTimer = undefined; that._fetchPostsTimer = undefined;
}, timeout); }, TIMEOUT);
}, },
}); });
@ -363,5 +393,10 @@ define([
}); });
}); });
App.on('start', function() {
App._ALCSupervisor = new Module.ALCSupervisor();
App._ALCSupervisor.refresh();
});
return Module; return Module;
}); });

View File

@ -40,6 +40,9 @@ define([
// Remove stale attributes from resulting JSON object // Remove stale attributes from resulting JSON object
return _.omit(SuperModel.prototype.toJSON.call(this), this.stale); return _.omit(SuperModel.prototype.toJSON.call(this), this.stale);
}, },
getChildren: function() {
return [];
},
}); });
Module.BlockView = AugmentedView.extend({ Module.BlockView = AugmentedView.extend({

View File

@ -65,6 +65,13 @@ define([
} }
return response; return response;
}, },
getChildren: function() {
var models = this.get('blocks').map(function(model, index, list) {
return [model, model.getChildren()];
});
return _.flatten(models);
},
}); });
Module.ContainerBlockView = Marionette.CompositeView.extend({ Module.ContainerBlockView = Marionette.CompositeView.extend({

View File

@ -62,6 +62,13 @@ define([
}); });
}; };
Module.getBulkTransformedPosts = function(options) {
return Module._query({
action: 'getBulkTransformedPosts',
options: options,
});
};
Module.saveNewsletter = function(options) { Module.saveNewsletter = function(options) {
return MailPoet.Ajax.post({ return MailPoet.Ajax.post({
endpoint: 'newsletters', endpoint: 'newsletters',

View File

@ -60,6 +60,11 @@ define([
return Module.newsletter; return Module.newsletter;
}; };
Module.findModels = function(predicate) {
var blocks = App._contentContainer.getChildren();
return _.filter(blocks, predicate);
};
App.on('before:start', function(options) { App.on('before:start', function(options) {
// Expose block methods globally // Expose block methods globally
App.registerBlockType = Module.registerBlockType; App.registerBlockType = Module.registerBlockType;
@ -68,6 +73,7 @@ define([
App.toJSON = Module.toJSON; App.toJSON = Module.toJSON;
App.getBody = Module.getBody; App.getBody = Module.getBody;
App.getNewsletter = Module.getNewsletter; App.getNewsletter = Module.getNewsletter;
App.findModels = Module.findModels;
Module.newsletter = new Module.NewsletterModel(_.omit(_.clone(options.newsletter), ['body'])); Module.newsletter = new Module.NewsletterModel(_.omit(_.clone(options.newsletter), ['body']));
}); });

File diff suppressed because it is too large Load Diff

View File

@ -43,4 +43,22 @@ class AutomatedLatestContent {
$posts = $this->ALC->getPosts($args); $posts = $this->ALC->getPosts($args);
return $this->ALC->transformPosts($args, $posts); return $this->ALC->transformPosts($args, $posts);
} }
function getBulkTransformedPosts($args) {
$alc = new \MailPoet\Newsletter\AutomatedLatestContent();
$used_posts = array();
$rendered_posts = array();
foreach ($args['blocks'] as $block) {
$posts = $alc->getPosts($block, $used_posts);
$rendered_posts[] = $alc->transformPosts($block, $posts);
foreach ($posts as $post) {
$used_posts[] = $post->ID;
}
}
return $rendered_posts;
}
} }

View File

@ -1,22 +1,71 @@
define([ define([
'newsletter_editor/App', 'newsletter_editor/App',
'newsletter_editor/blocks/automatedLatestContent', 'newsletter_editor/blocks/automatedLatestContent',
'newsletter_editor/blocks/container',
'amd-inject-loader!newsletter_editor/blocks/automatedLatestContent', 'amd-inject-loader!newsletter_editor/blocks/automatedLatestContent',
'newsletter_editor/components/communication', 'newsletter_editor/components/communication',
], function(EditorApplication, AutomatedLatestContentBlock, AutomatedLatestContentInjector, CommunicationComponent) { ], function(
EditorApplication,
AutomatedLatestContentBlock,
ContainerBlock,
AutomatedLatestContentInjector,
CommunicationComponent
) {
describe('Automated Latest Content Supervisor', function() {
var model;
beforeEach(function() {
model = new AutomatedLatestContentBlock.ALCSupervisor();
});
it('fetches posts in bulk from the server', function() {
global.stubChannel(EditorApplication);
EditorApplication.findModels = sinon.stub().returns([new Backbone.SuperModel()]);
var mock = sinon.mock({ getBulkTransformedPosts: function() {} })
.expects('getBulkTransformedPosts').once().returns(jQuery.Deferred());
var module = AutomatedLatestContentInjector({
'newsletter_editor/components/communication': {
getBulkTransformedPosts: mock
},
});
var model = new module.ALCSupervisor();
model.refresh();
mock.verify();
});
it('refreshes posts for given blocks', function() {
var block1 = new Backbone.SuperModel(),
block2 = new Backbone.SuperModel(),
postsSet1 = [
{ type: 'customTypeOne' },
],
postsSet2 = [
{ type: 'customTypeTwo' },
{ type: 'customTypeTwo' },
],
mock1 = sinon.mock(block1),
mock2 = sinon.mock(block2);
mock1.expects('trigger').once().withArgs('refreshPosts', postsSet1);
mock2.expects('trigger').once().withArgs('refreshPosts', postsSet2);
model.refreshBlocks([block1, block2], [postsSet1, postsSet2]);
mock1.verify();
mock2.verify();
});
});
describe('Automated latest content', function () { describe('Automated latest content', function () {
describe('model', function () { describe('model', function () {
var model, module; var model, module;
before(function() { before(function() {
module = AutomatedLatestContentInjector({ module = AutomatedLatestContentBlock;
'newsletter_editor/components/communication': {
getTransformedPosts: function() {
return jQuery.Deferred();
}
},
});
}); });
beforeEach(function () { beforeEach(function () {
@ -28,6 +77,7 @@ define([
afterEach(function () { afterEach(function () {
delete EditorApplication.getChannel; delete EditorApplication.getChannel;
delete EditorApplication.getBlockTypeModel;
}); });
it('has automatedLatestContent type', function () { it('has automatedLatestContent type', function () {
@ -192,19 +242,25 @@ define([
expect(model.get('divider.styles.block.backgroundColor')).to.equal('#456789'); expect(model.get('divider.styles.block.backgroundColor')).to.equal('#456789');
expect(model.get('divider.styles.block.padding')).to.equal('38px'); expect(model.get('divider.styles.block.padding')).to.equal('38px');
}); });
it('accepts displayable posts', function() {
EditorApplication.getBlockTypeModel = sinon.stub().returns(ContainerBlock.ContainerBlockModel);
var model = new (module.AutomatedLatestContentBlockModel)();
model.updatePosts([{
type: 'someCustomType',
}]);
expect(model.get('_container.blocks').size()).to.equal(1);
expect(model.get('_container.blocks').first().get('type')).to.equal('someCustomType');
});
}); });
describe('block view', function () { describe('block view', function () {
var model, view, module; var model, view, module;
before(function() { before(function() {
module = AutomatedLatestContentInjector({ module = AutomatedLatestContentBlock;
'newsletter_editor/components/communication': {
getTransformedPosts: function() {
return jQuery.Deferred();
}
},
});
}); });
beforeEach(function () { beforeEach(function () {
@ -232,9 +288,6 @@ define([
before(function() { before(function() {
module = AutomatedLatestContentInjector({ module = AutomatedLatestContentInjector({
'newsletter_editor/components/communication': { 'newsletter_editor/components/communication': {
getTransformedPosts: function() {
return jQuery.Deferred();
},
getPostTypes: function() { getPostTypes: function() {
return jQuery.Deferred(); return jQuery.Deferred();
} }

View File

@ -237,7 +237,7 @@ define([
}); });
it('has a specified font family', function () { it('has a specified font family', function () {
expect(view.$('.mailpoet_editor_button').css('font-family')).to.equal(model.get('styles.block.fontFamily')); expect(view.$('.mailpoet_editor_button').css('font-family')).to.contain(model.get('styles.block.fontFamily'));
}); });
it('has a specified font size', function () { it('has a specified font size', function () {