mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-13 10:55:58 +03:00
Implementing backbone for the blog / content view
closes #64 - adds a full example of using backbone on the frontend remembered to squash this one!
This commit is contained in:
parent
33c28a88e8
commit
185eee2a6b
4
app.js
4
app.js
@ -83,8 +83,8 @@
|
||||
* Expose the standard locals that every external page should have available;
|
||||
* path, navItems and ghostGlobals
|
||||
*/
|
||||
ghostLocals = function(req, res, next) {
|
||||
ghost.doFilter('ghostNavItems', {path: req.path, navItems: []}, function(navData) {
|
||||
ghostLocals = function (req, res, next) {
|
||||
ghost.doFilter('ghostNavItems', {path: req.path, navItems: []}, function (navData) {
|
||||
// Make sure we have a locals value.
|
||||
res.locals = res.locals || {};
|
||||
|
||||
|
@ -43,24 +43,6 @@
|
||||
$('body').toggleClass('fullscreen');
|
||||
});
|
||||
|
||||
$('.content-list-content li').on('click', function (e) {
|
||||
var $target = $(e.target).closest('li'),
|
||||
$preview = $('.content-preview');
|
||||
$('.content-list-content li').removeClass('active');
|
||||
$target.addClass('active');
|
||||
// *****
|
||||
// this means a *lot* of extra gumpf is in the DOM and should really be done with AJAX when we have proper
|
||||
// data API endpoints
|
||||
// ideally, we need a way to bind data to views properly... backbone marionette, angular, etc
|
||||
// *****
|
||||
//
|
||||
/**
|
||||
* @todo Remove gumpf
|
||||
*/
|
||||
$preview.find('.content-preview-content .wrapper').html($target.data('content'));
|
||||
$preview.find('.post-controls .post-edit').attr('href', '/ghost/editor/' + $target.data('id'));
|
||||
});
|
||||
|
||||
$('.options.up').on('click', function (e) {
|
||||
e.stopPropagation();
|
||||
$(this).next("ul").fadeToggle(200);
|
||||
|
@ -1,42 +0,0 @@
|
||||
/*global window, history, jQuery, Showdown, CodeMirror */
|
||||
(function ($) {
|
||||
"use strict";
|
||||
|
||||
$(document).ready(function () {
|
||||
|
||||
// Shadow on Markdown if scrolled
|
||||
$('.content-list-content').on('scroll', function (e) {
|
||||
if ($('.content-list-content').scrollTop() > 10) {
|
||||
$('.content-list').addClass('scrolling');
|
||||
} else {
|
||||
$('.content-list').removeClass('scrolling');
|
||||
}
|
||||
});
|
||||
|
||||
// Shadow on Preview if scrolled
|
||||
$('.content-preview-content').on('scroll', function (e) {
|
||||
if ($('.content-preview-content').scrollTop() > 10) {
|
||||
$('.content-preview').addClass('scrolling');
|
||||
} else {
|
||||
$('.content-preview').removeClass('scrolling');
|
||||
}
|
||||
});
|
||||
|
||||
$('.post-controls .delete').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
var postID = $('.content-list-content').find('li.active').data('id');
|
||||
$.ajax({
|
||||
method: 'DELETE',
|
||||
url: '/api/v0.1/posts/' + postID,
|
||||
success: function (res) {
|
||||
window.location.reload();
|
||||
},
|
||||
error: function () {
|
||||
window.alert('Delete failed.');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}(jQuery));
|
20
core/admin/assets/js/init.js
Normal file
20
core/admin/assets/js/init.js
Normal file
@ -0,0 +1,20 @@
|
||||
/*globals window, Backbone */
|
||||
(function ($) {
|
||||
"use strict";
|
||||
|
||||
var Ghost = {
|
||||
Layout : {},
|
||||
View : {},
|
||||
Collection : {},
|
||||
Model : {},
|
||||
|
||||
settings: {
|
||||
baseUrl: '/api/v0.1'
|
||||
},
|
||||
|
||||
currentView: null
|
||||
};
|
||||
|
||||
window.Ghost = Ghost;
|
||||
|
||||
}());
|
17
core/admin/assets/js/models/post.js
Normal file
17
core/admin/assets/js/models/post.js
Normal file
@ -0,0 +1,17 @@
|
||||
/*global window, document, Ghost, $, Backbone, _ */
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
Ghost.Model.Post = Backbone.Model.extend({
|
||||
urlRoot: '/api/v0.1/posts/',
|
||||
defaults: {
|
||||
status: 'draft'
|
||||
}
|
||||
});
|
||||
|
||||
Ghost.Collection.Posts = Backbone.Collection.extend({
|
||||
url: Ghost.settings.baseURL + '/posts',
|
||||
model: Ghost.Model.Post
|
||||
});
|
||||
|
||||
}());
|
105
core/admin/assets/js/views/blog.js
Normal file
105
core/admin/assets/js/views/blog.js
Normal file
@ -0,0 +1,105 @@
|
||||
/*global window, document, Ghost, Backbone, $, _ */
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
// Base view
|
||||
// ----------
|
||||
Ghost.Layout.Blog = Backbone.Layout.extend({
|
||||
initialize: function (options) {
|
||||
this.addViews({
|
||||
list : new Ghost.View.ContentList({ el: '.content-list' }),
|
||||
preview : new Ghost.View.ContentPreview({ el: '.content-preview' })
|
||||
});
|
||||
|
||||
// TODO: render templates on the client
|
||||
// this.render()
|
||||
}
|
||||
});
|
||||
|
||||
// Add shadow during scrolling
|
||||
var scrollShadow = function (target, e) {
|
||||
if ($(e.currentTarget).scrollTop() > 10) {
|
||||
$(target).addClass('scrolling');
|
||||
} else {
|
||||
$(target).removeClass('scrolling');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Content list (sidebar)
|
||||
// -----------------------
|
||||
Ghost.View.ContentList = Backbone.View.extend({
|
||||
initialize: function (options) {
|
||||
this.$('.content-list-content').on('scroll', _.bind(scrollShadow, null, '.content-list'));
|
||||
// Select first item
|
||||
_.defer(function (el) {
|
||||
el.find('.content-list-content li:first').trigger('click');
|
||||
}, this.$el);
|
||||
},
|
||||
|
||||
events: {
|
||||
'click .content-list-content' : 'scrollHandler',
|
||||
'click .content-list-content li' : 'showPreview'
|
||||
},
|
||||
|
||||
showPreview: function (e) {
|
||||
var item = $(e.currentTarget);
|
||||
this.$('.content-list-content li').removeClass('active');
|
||||
item.addClass('active');
|
||||
Backbone.trigger("blog:showPreview", item.data('id'));
|
||||
}
|
||||
});
|
||||
|
||||
// Content preview
|
||||
// ----------------
|
||||
Ghost.View.ContentPreview = Backbone.View.extend({
|
||||
initialize: function (options) {
|
||||
this.listenTo(Backbone, "blog:showPreview", this.showPost);
|
||||
this.$('.content-preview-content').on('scroll', _.bind(scrollShadow, null, '.content-preview'));
|
||||
},
|
||||
|
||||
events: {
|
||||
'click .post-controls .delete' : 'deletePost',
|
||||
'click .post-controls .post-edit' : 'editPost'
|
||||
},
|
||||
|
||||
deletePost: function (e) {
|
||||
e.preventDefault();
|
||||
this.model.destroy({
|
||||
success: function (model) {
|
||||
// here the ContentList would pick up the change in the Posts collection automatically
|
||||
// after client-side rendering is implemented
|
||||
var item = $('.content-list-content li[data-id=' + model.get('id') + ']');
|
||||
item.next().add(item.prev()).eq(0).trigger('click');
|
||||
item.remove();
|
||||
},
|
||||
error: function () {
|
||||
// TODO: decent error handling
|
||||
console.error('Error');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
editPost: function (e) {
|
||||
e.preventDefault();
|
||||
// for now this will disable "open in new tab", but when we have a Router implemented
|
||||
// it can go back to being a normal link to '#/ghost/editor/X'
|
||||
window.location = '/ghost/editor/' + this.model.get('id');
|
||||
},
|
||||
|
||||
showPost: function (id) {
|
||||
this.model = new Ghost.Model.Post({ id: id });
|
||||
this.model.once('change', this.render, this);
|
||||
this.model.fetch();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.$('.wrapper').html(this.model.get('content_html'));
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize views.
|
||||
// TODO: move to a `Backbone.Router`
|
||||
Ghost.currentView = new Ghost.Layout.Blog({ el: '#main' });
|
||||
|
||||
}());
|
56
core/admin/assets/lib/backbone/backbone-layout.js
Normal file
56
core/admin/assets/lib/backbone/backbone-layout.js
Normal file
@ -0,0 +1,56 @@
|
||||
// Layout manager
|
||||
// --------------
|
||||
/*
|
||||
* .addChild('sidebar', App.View.Sidebar)
|
||||
* .childViews.sidebar.$('blah')
|
||||
*/
|
||||
|
||||
Backbone.Layout = Backbone.View.extend({
|
||||
// default to loading state, reverted on render()
|
||||
loading: true,
|
||||
|
||||
addViews: function (views) {
|
||||
if (!this.views) this.views = {}
|
||||
|
||||
_.each(views, function(view, name){
|
||||
if (typeof view.model === 'undefined'){
|
||||
view.model = this.model
|
||||
}
|
||||
this.views[name] = view
|
||||
}, this)
|
||||
return this
|
||||
},
|
||||
|
||||
renderViews: function (data) {
|
||||
_.invoke(this.views, 'render', data)
|
||||
this.trigger('render')
|
||||
return this
|
||||
},
|
||||
|
||||
appendViews: function (target) {
|
||||
_.each(this.views, function(view){
|
||||
this.$el.append(view.el)
|
||||
}, this)
|
||||
this.trigger('append')
|
||||
return this
|
||||
},
|
||||
|
||||
destroyViews: function () {
|
||||
_.each(this.views, function(view){
|
||||
view.model = null
|
||||
view.remove()
|
||||
})
|
||||
return this
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.loading = false
|
||||
this.renderViews()
|
||||
return this
|
||||
},
|
||||
|
||||
remove: function () {
|
||||
this.destroyViews()
|
||||
Backbone.View.prototype.remove.call(this)
|
||||
}
|
||||
})
|
1571
core/admin/assets/lib/backbone/backbone.js
Normal file
1571
core/admin/assets/lib/backbone/backbone.js
Normal file
File diff suppressed because it is too large
Load Diff
1227
core/admin/assets/lib/underscore.js
Normal file
1227
core/admin/assets/lib/underscore.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -43,7 +43,7 @@
|
||||
}
|
||||
};
|
||||
|
||||
ghost.doFilter('messWithAdmin', adminNavbar, function() {
|
||||
ghost.doFilter('messWithAdmin', adminNavbar, function () {
|
||||
console.log('the dofilter hook called in /core/admin/controllers/index.js');
|
||||
});
|
||||
|
||||
|
@ -1,7 +1,3 @@
|
||||
{{#contentFor 'bodyScripts'}}
|
||||
<script src="/core/admin/assets/js/blog.js"></script>
|
||||
{{/contentFor}}
|
||||
|
||||
{{!< default}}
|
||||
<section class="content-list">
|
||||
<header class="floatingheader">
|
||||
@ -20,7 +16,7 @@
|
||||
<ol>
|
||||
{{#each posts}}
|
||||
{{! #if featured class="featured"{{/if}}
|
||||
<li data-id="{{id}}" data-content="{{content_html}}">
|
||||
<li data-id="{{id}}">
|
||||
<a class="permalink" href="#">
|
||||
<h3 class="entry-title">{{title}}</h3>
|
||||
<section class="entry-meta">
|
||||
|
@ -23,8 +23,12 @@
|
||||
<link rel="stylesheet" type="text/css" href="/core/admin/assets/lib/icheck/css/icheck.css"> <!-- TODO: Kill this - #29 -->
|
||||
{{{block "pageStyles"}}}
|
||||
|
||||
<!-- TODO: move all scripts to the end of body -->
|
||||
<script type="text/javascript" src="/core/admin/assets/lib/jquery/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="/core/admin/assets/lib/icheck/jquery.icheck.min.js"></script> <!-- The right place for this? -->
|
||||
<script type="text/javascript" src="/core/admin/assets/lib/icheck/jquery.icheck.min.js"></script>
|
||||
<script type="text/javascript" src="/core/admin/assets/lib/underscore.js"></script>
|
||||
<script type="text/javascript" src="/core/admin/assets/lib/backbone/backbone.js"></script>
|
||||
<script type="text/javascript" src="/core/admin/assets/lib/backbone/backbone-layout.js"></script>
|
||||
<script type="text/javascript" src="/core/admin/assets/js/toggle.js"></script>
|
||||
<script type="text/javascript" src="/core/admin/assets/js/admin-ui-temp.js"></script>
|
||||
{{{block "headScripts"}}}
|
||||
@ -34,12 +38,17 @@
|
||||
{{> navbar}}
|
||||
{{/unless}}
|
||||
|
||||
<main role="main">
|
||||
<main role="main" id="main">
|
||||
{{> flashes}}
|
||||
|
||||
{{{body}}}
|
||||
</main>
|
||||
|
||||
<script type="text/javascript" src="/core/admin/assets/js/init.js"></script>
|
||||
<!-- // require '/core/admin/assets/js/models/*' -->
|
||||
<script type="text/javascript" src="/core/admin/assets/js/models/post.js"></script>
|
||||
<!-- // require '/core/admin/assets/js/views/*' -->
|
||||
<script type="text/javascript" src="/core/admin/assets/js/views/blog.js"></script>
|
||||
{{{block "bodyScripts"}}}
|
||||
</body>
|
||||
</html>
|
@ -25,7 +25,7 @@
|
||||
// Allow people to overwrite the navTemplatePath
|
||||
templatePath = templatePath || this.navTemplatePath;
|
||||
|
||||
return nodefn.call(fs.readFile, templatePath).then(function(navTemplateContents) {
|
||||
return nodefn.call(fs.readFile, templatePath).then(function (navTemplateContents) {
|
||||
// TODO: Can handlebars compile async?
|
||||
self.navTemplateFunc = handlebars.compile(navTemplateContents.toString());
|
||||
});
|
||||
@ -40,11 +40,11 @@
|
||||
};
|
||||
|
||||
// A static helper method for registering with ghost
|
||||
GhostNavHelper.registerWithGhost = function(ghost) {
|
||||
GhostNavHelper.registerWithGhost = function (ghost) {
|
||||
var templatePath = path.join(ghost.paths().frontendViews, 'nav.hbs'),
|
||||
ghostNavHelper = new GhostNavHelper(templatePath);
|
||||
|
||||
return ghostNavHelper.compileTemplate().then(function() {
|
||||
return ghostNavHelper.compileTemplate().then(function () {
|
||||
ghost.registerThemeHelper("ghostNav", ghostNavHelper.renderNavItems);
|
||||
});
|
||||
};
|
||||
|
@ -68,7 +68,7 @@
|
||||
dataProvider: function () { return bookshelfDataProvider; },
|
||||
statuses: function () { return statuses; },
|
||||
polyglot: function () { return polyglot; },
|
||||
plugin: function() { return plugin; },
|
||||
plugin: function () { return plugin; },
|
||||
paths: function () {
|
||||
return {
|
||||
'activeTheme': __dirname + '/../content/' + config.themeDir + '/' + config.activeTheme + '/',
|
||||
|
@ -81,7 +81,7 @@
|
||||
return function (req, res) {
|
||||
var options = _.extend(req.body, req.params);
|
||||
return apiMethod(options).then(function (result) {
|
||||
res.json(result);
|
||||
res.json(result || {});
|
||||
}, function (error) {
|
||||
res.json(400, {error: error});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user