Merge pull request #141 from mailpoet/react_editor

Newsletter Editor on newsletters listing
This commit is contained in:
Marco
2015-09-25 16:18:21 +02:00
36 changed files with 1515 additions and 143 deletions

View File

@ -69,13 +69,31 @@ define(
);
}
var item_actions = (
<div>
<div className="row-actions">
var custom_actions = this.props.item_actions;
var item_actions = false;
if(custom_actions.length > 0) {
item_actions = custom_actions.map(function(action, index) {
return (
<span key={ 'action-'+index } className={ action.name }>
{ action.link(this.props.item.id) }
{(index < (custom_actions.length - 1)) ? ' | ' : ''}
</span>
);
}.bind(this));
} else {
item_actions = (
<span className="edit">
<Link to="edit" params={{ id: this.props.item.id }}>Edit</Link>
</span>
&nbsp;|&nbsp;
);
}
var actions = (
<div>
<div className="row-actions">
{ item_actions }
{ ' | ' }
<span className="trash">
<a
href="javascript:;"
@ -97,7 +115,7 @@ define(
return (
<tr className={ row_classes }>
{ checkbox }
{ this.props.onRenderItem(this.props.item, item_actions) }
{ this.props.onRenderItem(this.props.item, actions) }
</tr>
);
}
@ -166,6 +184,7 @@ define(
onDeleteItem={ this.props.onDeleteItem }
selection={ this.props.selection }
is_selectable={ this.props.is_selectable }
item_actions={ this.props.item_actions }
key={ 'item-' + item.id }
item={ item } />
);
@ -367,6 +386,9 @@ define(
// bulk actions
var bulk_actions = this.props.bulk_actions || [];
// item actions
var item_actions = this.props.item_actions || [];
var tableClasses = classNames(
'wp-list-table',
'widefat',
@ -420,6 +442,7 @@ define(
loading={ this.state.loading }
count={ this.state.count }
limit={ this.state.limit }
item_actions={ item_actions }
items={ items } />
<tfoot>

View File

@ -5,8 +5,9 @@ define([
'jquery',
'underscore',
'handlebars',
'handlebars_helpers',
'handlebars_helpers'
], function(Backbone, Marionette, SuperModel, jQuery, _, Handlebars) {
var app = new Marionette.Application(), AppView;
// Decoupled communication between application components

View File

@ -5,7 +5,7 @@
* For more check: http://marionettejs.com/docs/marionette.behaviors.html#behaviorslookup
*/
define([
'backbone.marionette',
'backbone.marionette'
], function(Marionette) {
var BehaviorsLookup = {};

View File

@ -6,7 +6,7 @@
define([
'backbone.marionette',
'newsletter_editor/behaviors/BehaviorsLookup',
'spectrum',
'spectrum'
], function(Marionette, BehaviorsLookup, Spectrum) {
BehaviorsLookup.ColorPickerBehavior = Marionette.Behavior.extend({

View File

@ -10,7 +10,7 @@ define([
'underscore',
'jquery',
'newsletter_editor/behaviors/BehaviorsLookup',
'interact',
'interact'
], function(Marionette, _, jQuery, BehaviorsLookup, interact) {
BehaviorsLookup.ContainerDropZoneBehavior = Marionette.Behavior.extend({

View File

@ -9,7 +9,7 @@ define([
'underscore',
'jquery',
'newsletter_editor/behaviors/BehaviorsLookup',
'interact',
'interact'
], function(Marionette, _, jQuery, BehaviorsLookup, interact) {
BehaviorsLookup.DraggableBehavior = Marionette.Behavior.extend({

View File

@ -6,7 +6,7 @@
define([
'backbone.marionette',
'newsletter_editor/behaviors/BehaviorsLookup',
'interact',
'interact'
], function(Marionette, BehaviorsLookup, interact) {
BehaviorsLookup.ResizableBehavior = Marionette.Behavior.extend({

View File

@ -6,7 +6,7 @@
define([
'backbone.marionette',
'underscore',
'newsletter_editor/behaviors/BehaviorsLookup',
'newsletter_editor/behaviors/BehaviorsLookup'
], function(Marionette, _, BehaviorsLookup) {
BehaviorsLookup.SortableBehavior = Marionette.Behavior.extend({

View File

@ -13,7 +13,7 @@ define([
'newsletter_editor/blocks/divider',
'newsletter_editor/components/wordpress',
'underscore',
'jquery',
'jquery'
], function(App, BaseBlock, ButtonBlock, DividerBlock, WordpressComponent, _, jQuery) {
"use strict";

View File

@ -11,7 +11,7 @@ define([
'underscore',
'jquery',
'mailpoet',
'modal',
'modal'
], function(App, Marionette, SuperModel, _, jQuery, MailPoet, Modal) {
"use strict";

View File

@ -6,7 +6,7 @@ define([
'newsletter_editor/blocks/base',
'mailpoet',
'underscore',
'jquery',
'jquery'
], function(App, BaseBlock, MailPoet, _, jQuery) {
"use strict";

View File

@ -9,7 +9,7 @@ define([
'underscore',
'jquery',
'newsletter_editor/App',
'newsletter_editor/blocks/base',
'newsletter_editor/blocks/base'
], function(Backbone, Marionette, _, jQuery, App, BaseBlock) {
"use strict";

View File

@ -6,7 +6,7 @@ define([
'newsletter_editor/blocks/base',
'underscore',
'jquery',
'mailpoet',
'mailpoet'
], function(App, BaseBlock, _, jQuery, MailPoet) {
"use strict";

View File

@ -4,7 +4,7 @@
define([
'newsletter_editor/App',
'newsletter_editor/blocks/base',
'underscore',
'underscore'
], function(App, BaseBlock, _) {
"use strict";

View File

@ -4,7 +4,7 @@
define([
'newsletter_editor/App',
'newsletter_editor/blocks/base',
'underscore',
'underscore'
], function(App, BaseBlock, _) {
"use strict";

View File

@ -4,7 +4,7 @@
define([
'newsletter_editor/App',
'newsletter_editor/blocks/base',
'underscore',
'underscore'
], function(App, BaseBlock, _) {
"use strict";

View File

@ -21,7 +21,7 @@ define([
'newsletter_editor/components/wordpress',
'newsletter_editor/blocks/base',
'newsletter_editor/blocks/button',
'newsletter_editor/blocks/divider',
'newsletter_editor/blocks/divider'
], function(Backbone, Marionette, Radio, _, jQuery, MailPoet, App, WordpressComponent, BaseBlock, ButtonBlock, DividerBlock) {
"use strict";

View File

@ -8,7 +8,7 @@ define([
'backbone.marionette',
'backbone.supermodel',
'underscore',
'jquery',
'jquery'
], function(App, BaseBlock, Backbone, Marionette, SuperModel, _, jQuery) {
"use strict";

View File

@ -4,7 +4,7 @@
define([
'newsletter_editor/App',
'newsletter_editor/blocks/base',
'underscore',
'underscore'
], function(App, BaseBlock, _) {
"use strict";

View File

@ -4,7 +4,7 @@
define([
'newsletter_editor/App',
'newsletter_editor/blocks/base',
'underscore',
'underscore'
], function(App, BaseBlock, _) {
"use strict";

View File

@ -1,6 +1,6 @@
define([
'newsletter_editor/App',
'backbone.supermodel',
'backbone.supermodel'
], function(App, SuperModel) {
var Module = {};

View File

@ -1,7 +1,7 @@
define([
'newsletter_editor/App',
'backbone.supermodel',
'underscore',
'underscore'
], function(App, SuperModel, _) {
"use strict";

View File

@ -3,7 +3,7 @@ define([
'backbone',
'backbone.marionette',
'underscore',
'jquery',
'jquery'
], function(App, Backbone, Marionette, _, jQuery) {
"use strict";

View File

@ -1,8 +1,9 @@
define([
'newsletter_editor/App',
'newsletter_editor/components/wordpress',
'backbone',
'backbone.marionette',
], function(App, Backbone, Marionette) {
'backbone.marionette'
], function(App, Wordpress, Backbone, Marionette) {
"use strict";
@ -15,27 +16,26 @@ define([
var json = App.toJSON();
// save newsletter
// TODO: Migrate logic to new AJAX format
//mailpoet_post_wpi('newsletter_save.php', json, function(response) {
//if(response.success !== undefined && response.success === true) {
////MailPoet.Notice.success("<?php _e('Newsletter has been saved.'); ?>");
//} else if(response.error !== undefined) {
//if(response.error.length === 0) {
//// TODO: Handle translations
//MailPoet.Notice.error("<?php _e('An unknown error occurred, please check your settings.'); ?>");
//} else {
//$(response.error).each(function(i, error) {
//MailPoet.Notice.error(error);
//});
//}
//}
//App.getChannel().trigger('afterEditorSave', json, response);
//}, function(error) {
//// TODO: Handle saving errors
//App.getChannel().trigger('afterEditorSave', {}, error);
//});
Wordpress.saveNewsletter(json).done(function(response) {
if(response.success !== undefined && response.success === true) {
// TODO: Handle translations
//MailPoet.Notice.success("<?php _e('Newsletter has been saved.'); ?>");
} else if(response.error !== undefined) {
if(response.error.length === 0) {
// TODO: Handle translations
MailPoet.Notice.error("<?php _e('An unknown error occurred, please check your settings.'); ?>");
} else {
$(response.error).each(function(i, error) {
MailPoet.Notice.error(error);
});
}
}
App.getChannel().trigger('afterEditorSave', json, response);
}).fail(function() {
// TODO: Handle saving errors
App.getChannel().trigger('afterEditorSave', {}, error);
});
};
Module.SaveView = Marionette.LayoutView.extend({

View File

@ -5,7 +5,7 @@ define([
'backbone.supermodel',
'underscore',
'jquery',
'sticky-kit',
'sticky-kit'
], function(App, Backbone, Marionette, SuperModel, _, jQuery, StickyKit) {
"use strict";
@ -225,23 +225,23 @@ define([
MailPoet.Modal.loading(true);
// TODO: Migrate logic to new AJAX format
//mailpoet_post_wpi('newsletter_preview.php', data, function(response) {
//if(response.success !== undefined && response.success === true) {
//MailPoet.Notice.success(App.getConfig().get('translations.testEmailSent'));
//} else if(response.error !== undefined) {
//if(response.error.length === 0) {
//MailPoet.Notice.error(App.getConfig().get('translations.unknownErrorOccurred'));
//} else {
//$(response.error).each(function(i, error) {
//MailPoet.Notice.error(error);
//});
//}
//}
//MailPoet.Modal.loading(false);
//}, function(error) {
//// an error occurred
//MailPoet.Modal.loading(false);
//});
Wordpress.previewNewsletter(data).done(function(response) {
if(response.success !== undefined && response.success === true) {
MailPoet.Notice.success(App.getConfig().get('translations.testEmailSent'));
} else if(response.error !== undefined) {
if(response.error.length === 0) {
MailPoet.Notice.error(App.getConfig().get('translations.unknownErrorOccurred'));
} else {
$(response.error).each(function(i, error) {
MailPoet.Notice.error(error);
});
}
}
MailPoet.Modal.loading(false);
}).fail(function(response) {
// an error occurred
MailPoet.Modal.loading(false);
});
},
});

View File

@ -1,7 +1,7 @@
define([
'newsletter_editor/App',
'backbone.marionette',
'backbone.supermodel',
'backbone.supermodel'
], function(App, Marionette, SuperModel) {
"use strict";

View File

@ -7,13 +7,21 @@ define([
var Module = {};
Module._cachedQuery = _.memoize(function(args) {
Module._query = function(args) {
return MailPoet.Ajax.post({
endpoint: 'wordpress',
action: args.action,
data: args.options || {},
});
}, JSON.stringify);
};
Module._cachedQuery = _.memoize(Module._query, JSON.stringify);
Module.getNewsletter = function(options) {
return Module._query({
action: 'get',
options: options,
});
};
Module.getPostTypes = function() {
return Module._cachedQuery({
@ -54,6 +62,20 @@ define([
});
};
Module.saveNewsletter = function(options) {
return Module._query({
action: 'save',
options: options,
});
};
Module.previewNewsletter = function(options) {
return Module._query({
action: 'preview',
options: options,
});
};
App.on('start', function(options) {
// Prefetch post types
Module.getPostTypes();

View File

@ -1,14 +1,18 @@
define(
[
'react',
'react-router',
'listing/listing.jsx',
'classnames'
],
function(
React,
Router,
Listing,
classNames
) {
var Link = Router.Link;
var columns = [
{
name: 'subject',
@ -34,8 +38,22 @@ define(
}
];
var item_actions = [
{
name: 'edit',
link: function(id) {
return (
<a href={ '?page=mailpoet-newsletter-editor&id=' + id }>
Edit
</a>
);
}
}
];
var NewsletterList = React.createClass({
renderItem: function(newsletter, actions) {
var rowClasses = classNames(
'manage-column',
'column-primary',
@ -65,7 +83,8 @@ define(
endpoint="newsletters"
onRenderItem={this.renderItem}
columns={columns}
bulk_actions={ bulk_actions } />
bulk_actions={ bulk_actions }
item_actions={ item_actions } />
);
}
});

View File

@ -59,14 +59,30 @@ class Menu {
'mailpoet-settings',
array($this, 'settings')
);
add_submenu_page(
'mailpoet',
__('Newsletter editor'),
__('Newsletter editor'),
'manage_options',
'mailpoet-newsletter-editor',
array($this, 'newsletterEditor')
// add_submenu_page(
// 'mailpoet',
// __('Newsletter editor'),
// __('Newsletter editor'),
// 'manage_options',
// 'mailpoet-newsletter-editor',
// array($this, 'newletterEditor')
// );
$this->registered_pages();
}
function registered_pages() {
global $_registered_pages;
$pages = array(
//'mailpoet-form-editor' => 'formEditor',
'mailpoet-newsletter-editor' => array($this, 'newletterForm')
);
foreach($pages as $menu_slug => $callback) {
$hookname = get_plugin_page_hookname($menu_slug, null);
if(!empty($hookname)) {
add_action($hookname, $callback);
}
$_registered_pages[$hookname] = true;
}
}
function home() {
@ -97,11 +113,11 @@ class Menu {
echo $this->renderer->render('newsletters.html', $data);
}
function newsletterEditor() {
function newletterForm() {
$data = array();
wp_enqueue_media();
wp_enqueue_script('tinymce-wplink', includes_url('js/tinymce/plugins/wplink/plugin.js'));
wp_enqueue_style('editor', includes_url('css/editor.css'));
echo $this->renderer->render('newsletter/editor.html', $data);
echo $this->renderer->render('newsletter/form.html', $data);
}
}

View File

@ -3,14 +3,14 @@ namespace MailPoet\Twig;
class Functions extends \Twig_Extension {
public function __construct() {
function __construct() {
}
public function getName() {
function getName() {
return 'functions';
}
public function getFunctions() {
function getFunctions() {
return array(
new \Twig_SimpleFunction(
'json_encode',
@ -21,7 +21,25 @@ class Functions extends \Twig_Extension {
'json_decode',
'json_decode',
array('is_safe' => array('all'))
),
new \Twig_SimpleFunction(
'wp_nonce_field',
'wp_nonce_field',
array('is_safe' => array('all'))
),
new \Twig_SimpleFunction(
'params',
array($this, 'params'),
array('is_safe' => array('all'))
)
);
}
function params($key = null) {
$args = stripslashes_deep($_GET);
if(array_key_exists($key, $args)) {
return $args[$key];
}
return null;
}
}

View File

@ -8,8 +8,8 @@
"interact.js": "taye/interact.js.git"
},
"dependencies": {
"backbone": "1.2.0",
"backbone.marionette": "2.4.2",
"backbone": "1.2.3",
"backbone.marionette": "2.4.3",
"backbone.radio": "0.9.0",
"backbone.supermodel": "1.2.0",
"c3": "~0.4.10",

View File

@ -1,40 +1,56 @@
define([
'newsletter_editor/App',
'newsletter_editor/components/save'
], function(EditorApplication, SaveComponent) {
'newsletter_editor/components/save',
'amd-inject-loader!newsletter_editor/components/save'
], function(EditorApplication, SaveComponent, SaveInjector) {
describe('Save', function() {
describe('save method', function() {
var module;
before(function() {
module = SaveInjector({
'newsletter_editor/components/wordpress': {
saveNewsletter: function() {
var deferred = jQuery.Deferred();
deferred.resolve({});
return deferred;
}
}
});
});
it('triggers beforeEditorSave event', function() {
var spy = sinon.spy();
global.stubChannel(EditorApplication, {
trigger: spy,
});
global.mailpoet_post_wpi = sinon.stub();
EditorApplication.toJSON = sinon.stub();
SaveComponent.save();
module.save();
expect(spy.withArgs('beforeEditorSave').calledOnce).to.be.true;
});
it.skip('triggers afterEditorSave event', function() {
it('triggers afterEditorSave event', function() {
var stub = sinon.stub().callsArgWith(2, { success: true }),
spy = sinon.spy();
global.mailpoet_post_wpi = stub;
global.stubChannel(EditorApplication, {
trigger: spy,
});
EditorApplication.toJSON = sinon.stub();
SaveComponent.save();
module.save();
expect(spy.withArgs('afterEditorSave').calledOnce).to.be.true;
});
it.skip('sends newsletter json to server for saving', function() {
var mock = sinon.mock({ mailpoet_post_wpi: function() {} }).expects('mailpoet_post_wpi').once();
it('sends newsletter json to server for saving', function() {
var mock = sinon.mock({ saveNewsletter: function() {} }).expects('saveNewsletter').once().returns(jQuery.Deferred());
var module = SaveInjector({
'newsletter_editor/components/wordpress': {
saveNewsletter: mock,
}
});
global.stubChannel(EditorApplication);
global.mailpoet_post_wpi = mock;
EditorApplication.toJSON = sinon.stub().returns({});
SaveComponent.save();
module.save();
mock.verify();
});

View File

@ -1,16 +1,12 @@
define([
'newsletter_editor/App',
'newsletter_editor/components/wordpress'
], function(EditorApplication, Wordpress) {
'newsletter_editor/components/wordpress',
'amd-inject-loader!newsletter_editor/components/wordpress'
], function(EditorApplication, Wordpress, WordpressInjector) {
describe('getPostTypes', function() {
var injector;
beforeEach(function() {
injector = require('amd-inject-loader!newsletter_editor/components/wordpress');
});
it('fetches post types from the server', function() {
var module = injector({
var module = WordpressInjector({
"mailpoet": {
Ajax: {
post: function() {
@ -32,7 +28,7 @@ define([
it('caches results', function() {
var deferred = jQuery.Deferred(),
mock = sinon.mock({ post: function() {} }).expects('post').once().returns(deferred),
module = injector({
module = WordpressInjector({
"mailpoet": {
Ajax: {
post: mock,
@ -51,11 +47,6 @@ define([
});
describe('getTaxonomies', function() {
var injector;
beforeEach(function() {
injector = require('amd-inject-loader!newsletter_editor/components/wordpress');
});
it('sends post type to endpoint', function() {
var spy,
post = function(params) {
@ -68,7 +59,7 @@ define([
},
module;
spy = sinon.spy(post);
module = injector({
module = WordpressInjector({
"mailpoet": {
Ajax: {
post: spy,
@ -81,7 +72,7 @@ define([
});
it('fetches post types from the server', function() {
var module = injector({
var module = WordpressInjector({
"mailpoet": {
Ajax: {
post: function() {
@ -100,7 +91,7 @@ define([
it('caches results', function() {
var deferred = jQuery.Deferred(),
mock = sinon.mock({ post: function() {} }).expects('post').once().returns(deferred),
module = injector({
module = WordpressInjector({
"mailpoet": {
Ajax: {
post: mock,
@ -116,11 +107,6 @@ define([
});
describe('getTerms', function() {
var injector;
beforeEach(function() {
injector = require('amd-inject-loader!newsletter_editor/components/wordpress');
});
it('sends terms to endpoint', function() {
var spy,
post = function(params) {
@ -130,7 +116,7 @@ define([
},
module;
spy = sinon.spy(post);
module = injector({
module = WordpressInjector({
"mailpoet": {
Ajax: {
post: spy,
@ -145,7 +131,7 @@ define([
});
it('fetches terms from the server', function() {
var module = injector({
var module = WordpressInjector({
"mailpoet": {
Ajax: {
post: function() {
@ -164,7 +150,7 @@ define([
it('caches results', function() {
var deferred = jQuery.Deferred(),
mock = sinon.mock({ post: function() {} }).expects('post').once().returns(deferred),
module = injector({
module = WordpressInjector({
"mailpoet": {
Ajax: {
post: mock,
@ -180,11 +166,6 @@ define([
});
describe('getPosts', function() {
var injector;
beforeEach(function() {
injector = require('amd-inject-loader!newsletter_editor/components/wordpress');
});
it('sends options to endpoint', function() {
var spy,
post = function(params) {
@ -194,7 +175,7 @@ define([
},
module;
spy = sinon.spy(post);
module = injector({
module = WordpressInjector({
"mailpoet": {
Ajax: {
post: spy,
@ -213,7 +194,7 @@ define([
});
it('fetches posts from the server', function() {
var module = injector({
var module = WordpressInjector({
"mailpoet": {
Ajax: {
post: function() {
@ -232,7 +213,7 @@ define([
it('caches results', function() {
var deferred = jQuery.Deferred(),
mock = sinon.mock({ post: function() {} }).expects('post').once().returns(deferred),
module = injector({
module = WordpressInjector({
"mailpoet": {
Ajax: {
post: mock,
@ -251,11 +232,6 @@ define([
});
describe('getTransformedPosts', function() {
var injector;
beforeEach(function() {
injector = require('amd-inject-loader!newsletter_editor/components/wordpress');
});
it('sends options to endpoint', function() {
var spy,
post = function(params) {
@ -265,7 +241,7 @@ define([
},
module;
spy = sinon.spy(post);
module = injector({
module = WordpressInjector({
"mailpoet": {
Ajax: {
post: spy,
@ -284,7 +260,7 @@ define([
});
it('fetches transformed posts from the server', function() {
var module = injector({
var module = WordpressInjector({
"mailpoet": {
Ajax: {
post: function() {
@ -303,7 +279,7 @@ define([
it('caches results', function() {
var deferred = jQuery.Deferred(),
mock = sinon.mock({ post: function() {} }).expects('post').once().returns(deferred),
module = injector({
module = WordpressInjector({
"mailpoet": {
Ajax: {
post: mock,

1274
views/newsletter/form.html Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,15 @@
<div class="mailpoet_form_field mailpoet_heading_form_field">
<input type="text" class="mailpoet_input mailpoet_input_title" value="{{ model.subject }}" placeholder="<%= __('Click to change the subject!') %>" />
<input
type="text"
class="mailpoet_input mailpoet_input_title"
value="{{ model.subject }}"
placeholder="<%= __('Click to change the subject!') %>"
/>
</div>
<div class="mailpoet_form_field mailpoet_heading_form_field">
<input type="text" class="mailpoet_input mailpoet_input_preheader" value="{{ model.preheader }}" placeholder="<%= __('Write your preheader here...') %>" />
<input type="text"
class="mailpoet_input mailpoet_input_preheader"
value="{{ model.preheader }}"
placeholder="<%= __('Write your preheader here...') %>"
/>
</div>

View File

@ -1,2 +0,0 @@
<h3><%= __('Settings') %></h3>
{{ user.name }} ({{ user.age }}) - extra value: {{ extra_value }}