mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-20 22:42:53 -05:00
Notifications on front end
Should close #37. There are persistent and passive notifications. Persistent ones: * are stored on `ghost.notifications`. * have an api made to add / remove them with client side ajax logic (probably not the most elegant, but works) * uses a modified `flashes.hbs` template * will only disappear if user closes the bar * stack Passive * added with backbone view / collection combo * stack * disappears on navigation and when user closes it
This commit is contained in:
parent
d337e16afe
commit
b77a8fd0d9
9 changed files with 184 additions and 41 deletions
|
@ -341,4 +341,4 @@ var path = require('path'),
|
|||
grunt.registerTask("default", ['sass:admin', 'handlebars']);
|
||||
};
|
||||
|
||||
module.exports = configureGrunt;
|
||||
module.exports = configureGrunt;
|
||||
|
|
|
@ -38,10 +38,50 @@
|
|||
$(window).one('centered', fadeInAndFocus);
|
||||
|
||||
// Allow notifications to be dismissed
|
||||
$(document).on('click', '.js-notification .close', function () {
|
||||
$(document).on('click', '.js-notification.notification-passive .close', function () {
|
||||
$(this).parent().fadeOut(200, function () { $(this).remove(); });
|
||||
});
|
||||
|
||||
$(document).on('click', '.js-notification.notification-persistent .close', function () {
|
||||
var self = this;
|
||||
$.ajax({
|
||||
type: "DELETE",
|
||||
url: '/api/v0.1/notifications/' + $(this).data('id')
|
||||
}).done(function (result) {
|
||||
if ($(self).parent().parent().hasClass('js-bb-notification')) {
|
||||
$(self).parent().parent().fadeOut(200, function () { $(self).remove(); });
|
||||
} else {
|
||||
$(self).parent().fadeOut(200, function () { $(self).remove(); });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Example of how to add a persistent notification.
|
||||
*/
|
||||
// $(document).on('click', '.add-persistent-notification', function (event) {
|
||||
// event.preventDefault();
|
||||
// var msg = {
|
||||
// type: 'error',
|
||||
// message: 'This is an error',
|
||||
// status: 'persistent',
|
||||
// id: 'per-' + $('.notification-persistent').length + 1
|
||||
// };
|
||||
|
||||
// $.ajax({
|
||||
// type: "POST",
|
||||
// url: '/api/v0.1/notifications/',
|
||||
// data: msg
|
||||
// }).done(function (result) {
|
||||
// var fcv;
|
||||
// fcv = new Ghost.Views.FlashCollectionView({
|
||||
// model: [msg]
|
||||
// });
|
||||
// console.log(fcv);
|
||||
// });
|
||||
// });
|
||||
|
||||
$(document).ready(function () {
|
||||
|
||||
// ## Set interactions for all menus
|
||||
|
@ -79,4 +119,4 @@
|
|||
|
||||
});
|
||||
|
||||
}());
|
||||
}());
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// # Article Editor
|
||||
|
||||
/*global window, document, $, _, Backbone, Ghost, Showdown, CodeMirror, shortcut, Countable */
|
||||
/*global window, document, $, _, Backbone, Ghost, Showdown, CodeMirror, shortcut, Countable, JST */
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
|
@ -93,26 +93,55 @@
|
|||
this.savePost({
|
||||
status: keys[newIndex]
|
||||
}).then(function () {
|
||||
window.alert('Your post: ' + model.get('title') + ' has been ' + keys[newIndex]);
|
||||
this.addSubview(new Ghost.Views.FlashCollectionView({
|
||||
model: [{
|
||||
type: 'success',
|
||||
message: 'Your post: ' + model.get('title') + ' has been ' + keys[newIndex],
|
||||
status: 'passive'
|
||||
}]
|
||||
}));
|
||||
// window.alert('Your post: ' + model.get('title') + ' has been ' + keys[newIndex]);
|
||||
});
|
||||
},
|
||||
|
||||
handleStatus: function (e) {
|
||||
e.preventDefault();
|
||||
var status = $(e.currentTarget).attr('data-set-status'),
|
||||
model = this.model;
|
||||
model = this.model,
|
||||
self = this;
|
||||
|
||||
if (status === 'publish-on') {
|
||||
return window.alert('Scheduled publishing not supported yet.');
|
||||
this.addSubview(new Ghost.Views.FlashCollectionView({
|
||||
model: [{
|
||||
type: 'alert',
|
||||
message: 'Scheduled publishing not supported yet.',
|
||||
status: 'passive'
|
||||
}]
|
||||
}));
|
||||
// return window.alert('Scheduled publishing not supported yet.');
|
||||
}
|
||||
if (status === 'queue') {
|
||||
return window.alert('Scheduled publishing not supported yet.');
|
||||
this.addSubview(new Ghost.Views.FlashCollectionView({
|
||||
model: [{
|
||||
type: 'alert',
|
||||
message: 'Scheduled publishing not supported yet.',
|
||||
status: 'passive'
|
||||
}]
|
||||
}));
|
||||
// return window.alert('Scheduled publishing not supported yet.');
|
||||
}
|
||||
|
||||
this.savePost({
|
||||
status: status
|
||||
}).then(function () {
|
||||
window.alert('Your post: ' + model.get('title') + ' has been ' + status);
|
||||
self.addSubview(new Ghost.Views.FlashCollectionView({
|
||||
model: [{
|
||||
type: 'success',
|
||||
message: 'Your post: ' + model.get('title') + ' has been ' + status,
|
||||
status: 'passive'
|
||||
}]
|
||||
}));
|
||||
// window.alert('Your post: ' + model.get('title') + ' has been ' + status);
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -120,11 +149,26 @@
|
|||
if (e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
var model = this.model;
|
||||
var model = this.model,
|
||||
self = this;
|
||||
this.savePost().then(function () {
|
||||
window.alert('Your post was saved as ' + model.get('status'));
|
||||
self.addSubview(new Ghost.Views.FlashCollectionView({
|
||||
model: [{
|
||||
type: 'success',
|
||||
message: 'Your post was saved as ' + model.get('status'),
|
||||
status: 'passive'
|
||||
}]
|
||||
}));
|
||||
// window.alert('Your post was saved as ' + model.get('status'));
|
||||
}, function () {
|
||||
window.alert(model.validationError);
|
||||
self.addSubview(new Ghost.Views.FlashCollectionView({
|
||||
model: [{
|
||||
type: 'error',
|
||||
message: model.validationError,
|
||||
status: 'passive'
|
||||
}]
|
||||
}));
|
||||
// window.alert(model.validationError);
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -254,4 +298,52 @@
|
|||
}
|
||||
});
|
||||
|
||||
}());
|
||||
/**
|
||||
* This is the view to generate the markup for the individual
|
||||
* notification. Will be included into #flashbar.
|
||||
*
|
||||
* States can be
|
||||
* - persistent
|
||||
* - passive
|
||||
*
|
||||
* Types can be
|
||||
* - error
|
||||
* - success
|
||||
* - alert
|
||||
* - (empty)
|
||||
*
|
||||
*/
|
||||
Ghost.Views.FlashView = Ghost.View.extend({
|
||||
templateName: 'notification',
|
||||
className: 'js-bb-notification',
|
||||
template: function (data) {
|
||||
return JST[this.templateName](data);
|
||||
},
|
||||
render: function() {
|
||||
var html = this.template(this.model);
|
||||
this.$el.html(html);
|
||||
return this;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* This handles Notification groups
|
||||
*/
|
||||
Ghost.Views.FlashCollectionView = Ghost.View.extend({
|
||||
el: '#flashbar',
|
||||
initialize: function() {
|
||||
this.render();
|
||||
},
|
||||
render: function() {
|
||||
_.each(this.model, function (item) {
|
||||
this.renderItem(item);
|
||||
}, this);
|
||||
},
|
||||
renderItem: function (item) {
|
||||
var itemView = new Ghost.Views.FlashView({ model: item });
|
||||
this.$el.prepend(itemView.render().el);
|
||||
}
|
||||
});
|
||||
|
||||
}());
|
||||
|
|
|
@ -13,6 +13,7 @@ var Ghost = require('../ghost'),
|
|||
dataProvider = ghost.dataProvider,
|
||||
posts,
|
||||
users,
|
||||
notifications,
|
||||
settings,
|
||||
requestHandler,
|
||||
cachedSettingsRequestHandler,
|
||||
|
@ -58,6 +59,20 @@ users = {
|
|||
}
|
||||
};
|
||||
|
||||
// # Notifications
|
||||
|
||||
notifications = {
|
||||
destroy: function destroy(i) {
|
||||
ghost.notifications = _.reject(ghost.notifications, function (element) {
|
||||
return element.id === i.id;
|
||||
});
|
||||
return when(ghost.notifications);
|
||||
},
|
||||
add: function add(notification) {
|
||||
return when(ghost.notifications.push(notification));
|
||||
}
|
||||
};
|
||||
|
||||
// # Settings
|
||||
|
||||
// Turn a settings collection into a single object/hashmap
|
||||
|
@ -147,6 +162,7 @@ cachedSettingsRequestHandler = function (apiMethod) {
|
|||
|
||||
module.exports.posts = posts;
|
||||
module.exports.users = users;
|
||||
module.exports.notifications = notifications;
|
||||
module.exports.settings = settings;
|
||||
module.exports.requestHandler = requestHandler;
|
||||
module.exports.cachedSettingsRequestHandler = cachedSettingsRequestHandler;
|
||||
module.exports.cachedSettingsRequestHandler = cachedSettingsRequestHandler;
|
||||
|
|
|
@ -110,6 +110,7 @@ adminControllers = {
|
|||
});
|
||||
},
|
||||
'editor': function (req, res) {
|
||||
console.log(res.locals);
|
||||
if (req.params.id !== undefined) {
|
||||
api.posts.read({id: parseInt(req.params.id, 10)})
|
||||
.then(function (post) {
|
||||
|
|
4
core/server/helpers/tpl/notification.hbs
Normal file
4
core/server/helpers/tpl/notification.hbs
Normal file
|
@ -0,0 +1,4 @@
|
|||
<section class="notification{{#if type}}-{{type}}{{/if}} notification-{{status}} js-notification">
|
||||
{{message}}
|
||||
<a class="close" href="#"><span class="hidden">Close</span></a>
|
||||
</section>
|
|
@ -27,7 +27,9 @@
|
|||
{{/unless}}
|
||||
|
||||
<main role="main" id="main">
|
||||
{{> flashes}}
|
||||
<aside id="flashbar">
|
||||
{{> flashes}}
|
||||
</aside>
|
||||
|
||||
{{{body}}}
|
||||
</main>
|
||||
|
@ -72,4 +74,4 @@
|
|||
Ghost.init();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
@ -1,26 +1,8 @@
|
|||
{{#if messages}}
|
||||
{{#each messages.error}}
|
||||
<section class="notification-error js-notification">
|
||||
{{.}}
|
||||
<a class="close" href="#"><span class="hidden">Close</span></a>
|
||||
{{#each messages}}
|
||||
<section class="notification{{#if type}}-{{type}}{{/if}} notification-{{status}} js-notification">
|
||||
{{message}}
|
||||
<a class="close" href="#" data-id="{{id}}"><span class="hidden">Close</span></a>
|
||||
</section>
|
||||
{{/each}}
|
||||
{{#each messages.success}}
|
||||
<section class="notification-success js-notification">
|
||||
{{.}}
|
||||
<a class="close" href="#"><span class="hidden">Close</span></a>
|
||||
</section>
|
||||
{{/each}}
|
||||
{{#each messages.warn}}
|
||||
<section class="notification-alert js-notification">
|
||||
{{.}}
|
||||
<a class="close" href="#"><span class="hidden">Close</span></a>
|
||||
</section>
|
||||
{{/each}}
|
||||
{{#each messages.info}}
|
||||
<section class="notification js-notification">
|
||||
{{.}}
|
||||
<a class="close" href="#"><span class="hidden">Close</span></a>
|
||||
</section>
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
|
10
index.js
10
index.js
|
@ -96,7 +96,7 @@ ghostLocals = function (req, res, next) {
|
|||
} else {
|
||||
_.extend(res.locals, {
|
||||
// pass the admin flash messages, settings and paths
|
||||
messages: req.flash(),
|
||||
messages: ghost.notifications,
|
||||
settings: ghost.settings(),
|
||||
availableThemes: ghost.paths().availableThemes,
|
||||
availablePlugins: ghost.paths().availablePlugins
|
||||
|
@ -173,6 +173,12 @@ when.all([ghost.init(), filters.loadCoreFilters(ghost), helpers.loadCoreHelpers(
|
|||
});
|
||||
ghost.app().get('/ghost/', auth, admin.index);
|
||||
|
||||
|
||||
// Notifications routes
|
||||
ghost.app().del('/api/v0.1/notifications/:id', authAPI, disableCachedResult, api.requestHandler(api.notifications.destroy));
|
||||
ghost.app().post('/api/v0.1/notifications/', authAPI, disableCachedResult, api.requestHandler(api.notifications.add));
|
||||
|
||||
|
||||
/**
|
||||
* Frontend routes..
|
||||
* @todo dynamic routing, homepage generator, filters ETC ETC
|
||||
|
@ -183,6 +189,7 @@ when.all([ghost.init(), filters.loadCoreFilters(ghost), helpers.loadCoreHelpers(
|
|||
|
||||
|
||||
|
||||
|
||||
ghost.app().listen(
|
||||
ghost.config().env[process.env.NODE_ENV || 'development'].url.port,
|
||||
ghost.config().env[process.env.NODE_ENV || 'development'].url.host,
|
||||
|
@ -195,5 +202,4 @@ when.all([ghost.init(), filters.loadCoreFilters(ghost), helpers.loadCoreHelpers(
|
|||
loading.resolve();
|
||||
}
|
||||
);
|
||||
|
||||
}, errors.logAndThrowError);
|
||||
|
|
Loading…
Add table
Reference in a new issue