Added models & collections for various pieces
Saving post as draft, or publishing Added HBS parser for some client tmpls Parsing paginated posts Added grunt watch for hbs parsing on updates
This commit is contained in:
parent
b7064185d4
commit
e5ce70e175
1
.gitignore
vendored
1
.gitignore
vendored
@ -23,6 +23,7 @@ projectFilesBackup
|
||||
# Ghost DB file
|
||||
*.db
|
||||
|
||||
/core/admin/assets/js/hbs-tmpl.js
|
||||
/core/admin/assets/css
|
||||
/core/admin/assets/sass/modules/bourbon
|
||||
.sass-cache/
|
||||
|
41
Gruntfile.js
41
Gruntfile.js
@ -17,7 +17,7 @@
|
||||
// Lint files in the root, including Gruntfile.js
|
||||
"*.js",
|
||||
// Lint core files, but not libs
|
||||
["core/**/*.js", "!**/assets/lib/**/*.js"]
|
||||
["core/**/*.js", "!**/assets/lib/**/*.js", "!**/assets/**/hbs-tmpl.js"]
|
||||
]
|
||||
},
|
||||
|
||||
@ -49,16 +49,50 @@
|
||||
bourbon: {
|
||||
command: 'bourbon install --path core/admin/assets/sass/modules/'
|
||||
}
|
||||
},
|
||||
|
||||
handlebars: {
|
||||
|
||||
core: {
|
||||
|
||||
options: {
|
||||
|
||||
namespace: "JST",
|
||||
|
||||
processName: function (filename) {
|
||||
filename = filename.replace('./core/admin/assets/tmpl/', '');
|
||||
return filename.replace('.hbs', '');
|
||||
}
|
||||
},
|
||||
|
||||
files: {
|
||||
"./core/admin/assets/js/hbs-tmpl.js": "./core/admin/assets/tmpl/**/*.hbs"
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
handlebars: {
|
||||
files: './core/admin/assets/tmpl/**/*.hbs',
|
||||
tasks: ['handlebars']
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
grunt.initConfig(cfg);
|
||||
|
||||
grunt.loadNpmTasks("grunt-jslint");
|
||||
grunt.loadNpmTasks("grunt-mocha-test");
|
||||
grunt.loadNpmTasks("grunt-contrib-sass");
|
||||
grunt.loadNpmTasks("grunt-shell");
|
||||
|
||||
grunt.loadNpmTasks("grunt-contrib-watch");
|
||||
grunt.loadNpmTasks("grunt-contrib-sass");
|
||||
grunt.loadNpmTasks("grunt-contrib-handlebars");
|
||||
|
||||
|
||||
// Prepare the project for development
|
||||
// TODO: Git submodule init/update (https://github.com/jaubourg/grunt-update-submodules)?
|
||||
grunt.registerTask("init", ["shell:bourbon", "sass:admin"]);
|
||||
@ -68,6 +102,9 @@
|
||||
|
||||
// Run tests and lint code
|
||||
grunt.registerTask("validate", ["jslint", "mochaTest:all"]);
|
||||
|
||||
// When you just say "grunt"
|
||||
grunt.registerTask("default", ['sass:admin', 'handlebars', 'watch']);
|
||||
};
|
||||
|
||||
module.exports = configureGrunt;
|
||||
|
@ -48,10 +48,5 @@
|
||||
$(this).next("ul").fadeToggle(200);
|
||||
});
|
||||
|
||||
$('.editor-options').on('click', 'li', function (e) {
|
||||
$('.button-save').data("state", $(this).data("title")).attr('data-state', $(this).data("title")).text($(this).text());
|
||||
$('.editor-options .active').removeClass('active');
|
||||
$(this).addClass('active');
|
||||
});
|
||||
});
|
||||
}(jQuery));
|
@ -1,242 +0,0 @@
|
||||
// # Article Editor
|
||||
|
||||
/*global window, document, history, jQuery, Showdown, CodeMirror, shortcut, Countable */
|
||||
(function ($, ShowDown, CodeMirror, shortcut, Countable) {
|
||||
"use strict";
|
||||
|
||||
var wordCount = $('.entry-word-count'),
|
||||
charCount = $('.entry-character-count'),
|
||||
paragraphCount = $('.entry-paragraph-count'),
|
||||
|
||||
// ## Converter Initialisation
|
||||
/**
|
||||
* @property converter
|
||||
* @type {ShowDown.converter}
|
||||
*/
|
||||
// Initialise the Showdown converter for Markdown.
|
||||
// var delay;
|
||||
converter = new ShowDown.converter({extensions: ['ghostdown']}),
|
||||
editor = CodeMirror.fromTextArea(document.getElementById('entry-markdown'), {
|
||||
mode: 'markdown',
|
||||
tabMode: 'indent',
|
||||
lineWrapping: true
|
||||
});
|
||||
|
||||
|
||||
// ## Functions
|
||||
/**
|
||||
* @method Update word count
|
||||
* @todo Really not the best way to do things as it includes Markdown formatting along with words
|
||||
* @constructor
|
||||
*/
|
||||
// This updates the word count on the editor preview panel.
|
||||
// function updateWordCount() {
|
||||
// var wordCount = document.getElementsByClassName('entry-word-count')[0],
|
||||
// editorValue = editor.getValue();
|
||||
|
||||
// if (editorValue.length) {
|
||||
// wordCount.innerHTML = editorValue.match(/\S+/g).length + ' words';
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* @method updatePreview
|
||||
* @constructor
|
||||
*/
|
||||
// This updates the editor preview panel.
|
||||
// Currently gets called on every key press.
|
||||
// Also trigger word count update
|
||||
function updatePreview() {
|
||||
var preview = document.getElementsByClassName('rendered-markdown')[0];
|
||||
preview.innerHTML = converter.makeHtml(editor.getValue());
|
||||
console.log(preview);
|
||||
Countable.once(preview, function (counter) {
|
||||
// updateWordCount(counter);
|
||||
wordCount.text(counter.words + ' words');
|
||||
charCount.text(counter.characters + ' characters');
|
||||
paragraphCount.text(counter.paragraphs + ' paragraphs');
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @method Save
|
||||
* @constructor
|
||||
*/
|
||||
// This method saves a post
|
||||
function save() {
|
||||
var entry = {
|
||||
title: document.getElementById('entry-title').value,
|
||||
content: editor.getValue()
|
||||
},
|
||||
urlSegments = window.location.pathname.split('/'),
|
||||
id;
|
||||
|
||||
if (urlSegments[2] === 'editor' && urlSegments[3] && /^[a-zA-Z0-9]+$/.test(urlSegments[2])) {
|
||||
id = urlSegments[3];
|
||||
$.ajax({
|
||||
url: '/api/v0.1/posts/' + id,
|
||||
method: 'PUT',
|
||||
data: entry,
|
||||
success: function (data) {
|
||||
console.log('response', data);
|
||||
},
|
||||
error: function (error) {
|
||||
console.log('error', error);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$.ajax({
|
||||
url: '/api/v0.1/posts',
|
||||
method: 'POST',
|
||||
data: entry,
|
||||
success: function (data) {
|
||||
console.log('response', data);
|
||||
history.pushState(data, '', '/ghost/editor/' + data.id);
|
||||
},
|
||||
error: function (jqXHR, status, error) {
|
||||
var errors = JSON.parse(jqXHR.responseText);
|
||||
console.log('FAILED', errors);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ## Main Initialisation
|
||||
$(document).ready(function () {
|
||||
|
||||
$('.entry-markdown header, .entry-preview header').click(function (e) {
|
||||
$('.entry-markdown, .entry-preview').removeClass('active');
|
||||
$(e.target).closest('section').addClass('active');
|
||||
});
|
||||
|
||||
editor.on("change", function () {
|
||||
//clearTimeout(delay);
|
||||
//delay = setTimeout(updatePreview, 50);
|
||||
updatePreview();
|
||||
});
|
||||
|
||||
updatePreview();
|
||||
|
||||
$('.button-save').on('click', function () {
|
||||
save();
|
||||
});
|
||||
|
||||
// Sync scrolling
|
||||
function syncScroll(e) {
|
||||
// vars
|
||||
var $codeViewport = $(e.target),
|
||||
$previewViewport = $('.entry-preview-content'),
|
||||
$codeContent = $('.CodeMirror-sizer'),
|
||||
$previewContent = $('.rendered-markdown'),
|
||||
|
||||
// calc position
|
||||
codeHeight = $codeContent.height() - $codeViewport.height(),
|
||||
previewHeight = $previewContent.height() - $previewViewport.height(),
|
||||
ratio = previewHeight / codeHeight,
|
||||
previewPostition = $codeViewport.scrollTop() * ratio;
|
||||
|
||||
// apply new scroll
|
||||
$previewViewport.scrollTop(previewPostition);
|
||||
|
||||
}
|
||||
// TODO: Debounce
|
||||
$('.CodeMirror-scroll').on('scroll', syncScroll);
|
||||
|
||||
// Shadow on Markdown if scrolled
|
||||
$('.CodeMirror-scroll').on('scroll', function (e) {
|
||||
if ($('.CodeMirror-scroll').scrollTop() > 10) {
|
||||
$('.entry-markdown').addClass('scrolling');
|
||||
} else {
|
||||
$('.entry-markdown').removeClass('scrolling');
|
||||
}
|
||||
});
|
||||
// Shadow on Preview if scrolled
|
||||
$('.entry-preview-content').on('scroll', function (e) {
|
||||
if ($('.entry-preview-content').scrollTop() > 10) {
|
||||
$('.entry-preview').addClass('scrolling');
|
||||
} else {
|
||||
$('.entry-preview').removeClass('scrolling');
|
||||
}
|
||||
});
|
||||
|
||||
// ## Shortcuts
|
||||
// Zen writing mode
|
||||
shortcut.add("Alt+Shift+Z", function () {
|
||||
$('body').toggleClass('zen');
|
||||
});
|
||||
|
||||
var MarkdownShortcuts = [
|
||||
{
|
||||
'key': 'Ctrl+B',
|
||||
'style': 'bold'
|
||||
},
|
||||
{
|
||||
'key': 'Meta+B',
|
||||
'style': 'bold'
|
||||
},
|
||||
{
|
||||
'key': 'Ctrl+I',
|
||||
'style': 'italic'
|
||||
},
|
||||
{
|
||||
'key': 'Meta+I',
|
||||
'style': 'italic'
|
||||
},
|
||||
{
|
||||
'key': 'Ctrl+Alt+U',
|
||||
'style': 'strike'
|
||||
},
|
||||
{
|
||||
'key': 'Ctrl+Shift+K',
|
||||
'style': 'code'
|
||||
},
|
||||
{
|
||||
'key': 'Alt+1',
|
||||
'style': 'h1'
|
||||
},
|
||||
{
|
||||
'key': 'Alt+2',
|
||||
'style': 'h2'
|
||||
},
|
||||
{
|
||||
'key': 'Alt+3',
|
||||
'style': 'h3'
|
||||
},
|
||||
{
|
||||
'key': 'Alt+4',
|
||||
'style': 'h4'
|
||||
},
|
||||
{
|
||||
'key': 'Alt+5',
|
||||
'style': 'h5'
|
||||
},
|
||||
{
|
||||
'key': 'Alt+6',
|
||||
'style': 'h6'
|
||||
},
|
||||
{
|
||||
'key': 'Ctrl+Shift+L',
|
||||
'style': 'link'
|
||||
},
|
||||
{
|
||||
'key': 'Ctrl+Shift+I',
|
||||
'style': 'image'
|
||||
},
|
||||
{
|
||||
'key': 'Ctrl+Q',
|
||||
'style': 'blockquote'
|
||||
},
|
||||
{
|
||||
'key': 'Ctrl+Shift+1',
|
||||
'style': 'currentdate'
|
||||
}
|
||||
];
|
||||
|
||||
$.each(MarkdownShortcuts, function (index, short) {
|
||||
shortcut.add(short.key, function () {
|
||||
return editor.addMarkdown({style: short.style});
|
||||
});
|
||||
});
|
||||
});
|
||||
}(jQuery, Showdown, CodeMirror, shortcut, Countable));
|
@ -1,18 +1,63 @@
|
||||
/*globals window, Backbone */
|
||||
/*globals window, _, $, Backbone */
|
||||
(function ($) {
|
||||
"use strict";
|
||||
|
||||
var Ghost = {
|
||||
Layout : {},
|
||||
View : {},
|
||||
Collection : {},
|
||||
Model : {},
|
||||
_.extend(Backbone.View.prototype, {
|
||||
|
||||
settings: {
|
||||
baseUrl: '/api/v0.1'
|
||||
// Adds a subview to the current view, which will
|
||||
// ensure its removal when this view is removed,
|
||||
// or when view.removeSubviews is called
|
||||
addSubview: function (view) {
|
||||
if (!(view instanceof Backbone.View)) {
|
||||
throw new Error("Subview must be a Backbone.View");
|
||||
}
|
||||
this.subviews = this.subviews || [];
|
||||
this.subviews.push(view);
|
||||
return view;
|
||||
},
|
||||
|
||||
currentView: null
|
||||
// Removes any subviews associated with this view
|
||||
// by `addSubview`, which will in-turn remove any
|
||||
// children of those views, and so on.
|
||||
removeSubviews: function () {
|
||||
var i, l, children = this.subviews;
|
||||
if (!children) {
|
||||
return this;
|
||||
}
|
||||
for (i = 0, l = children.length; i < l; i += 1) {
|
||||
children[i].remove();
|
||||
}
|
||||
this.subviews = [];
|
||||
return this;
|
||||
},
|
||||
|
||||
// Extends the view's remove, by calling `removeSubviews`
|
||||
// if any subviews exist.
|
||||
remove: function () {
|
||||
if (this.subviews) {
|
||||
this.removeSubviews();
|
||||
}
|
||||
return Backbone.View.prototype.remove.apply(this, arguments);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
var Ghost = {
|
||||
Layout : {},
|
||||
Views : {},
|
||||
Collections : {},
|
||||
Models : {},
|
||||
|
||||
settings: {
|
||||
apiRoot: '/api/v0.1'
|
||||
},
|
||||
|
||||
// This is a helper object to denote legacy things in the
|
||||
// middle of being transitioned.
|
||||
temporary: {},
|
||||
|
||||
currentView: null,
|
||||
router: null
|
||||
};
|
||||
|
||||
window.Ghost = Ghost;
|
||||
|
@ -2,16 +2,40 @@
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
Ghost.Model.Post = Backbone.Model.extend({
|
||||
urlRoot: '/api/v0.1/posts/',
|
||||
Ghost.Models.Post = Backbone.Model.extend({
|
||||
|
||||
defaults: {
|
||||
status: 'draft'
|
||||
},
|
||||
|
||||
parse: function (resp) {
|
||||
if (resp.tags) {
|
||||
// TODO: parse tags into it's own collection on the model (this.tags)
|
||||
return resp;
|
||||
}
|
||||
return resp;
|
||||
},
|
||||
|
||||
validate: function (attrs) {
|
||||
if (_.isEmpty(attrs.title)) {
|
||||
return 'You must specify a title for the post.';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ghost.Collection.Posts = Backbone.Collection.extend({
|
||||
url: Ghost.settings.baseURL + '/posts',
|
||||
model: Ghost.Model.Post
|
||||
Ghost.Collections.Posts = Backbone.Collection.extend({
|
||||
url: Ghost.settings.apiRoot + '/posts',
|
||||
model: Ghost.Models.Post,
|
||||
parse: function (resp) {
|
||||
if (_.isArray(resp.posts)) {
|
||||
this.limit = resp.limit;
|
||||
this.currentPage = resp.page;
|
||||
this.totalPages = resp.pages;
|
||||
this.totalPosts = resp.total;
|
||||
return resp.posts;
|
||||
}
|
||||
return resp;
|
||||
}
|
||||
});
|
||||
|
||||
}());
|
38
core/admin/assets/js/router.js
Normal file
38
core/admin/assets/js/router.js
Normal file
@ -0,0 +1,38 @@
|
||||
/*global window, document, Ghost, Backbone, $, _ */
|
||||
(function () {
|
||||
|
||||
"use strict";
|
||||
|
||||
Ghost.Router = Backbone.Router.extend({
|
||||
|
||||
routes: {
|
||||
'content/': 'blog',
|
||||
'editor': 'editor',
|
||||
'editor/': 'editor',
|
||||
'editor/:id': 'editor'
|
||||
},
|
||||
|
||||
blog: function () {
|
||||
var posts = new Ghost.Collections.Posts();
|
||||
posts.fetch({data: {status: 'all'}}).then(function () {
|
||||
Ghost.currentView = new Ghost.Views.Blog({ el: '#main', collection: posts });
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
editor: function (id) {
|
||||
var post = new Ghost.Models.Post();
|
||||
post.urlRoot = Ghost.settings.apiRoot + '/posts';
|
||||
if (id) {
|
||||
post.id = id;
|
||||
post.fetch().then(function () {
|
||||
Ghost.currentView = new Ghost.Views.Editor({ el: '#main', model: post });
|
||||
});
|
||||
} else {
|
||||
Ghost.currentView = new Ghost.Views.Editor({ el: '#main', model: post });
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}());
|
15
core/admin/assets/js/starter.js
Normal file
15
core/admin/assets/js/starter.js
Normal file
@ -0,0 +1,15 @@
|
||||
/*global window, document, Ghost, Backbone, $, _ */
|
||||
(function () {
|
||||
|
||||
"use strict";
|
||||
|
||||
Ghost.router = new Ghost.Router();
|
||||
|
||||
$(function () {
|
||||
|
||||
Backbone.history.start({pushState: true, hashChange: false, root: '/ghost'});
|
||||
|
||||
});
|
||||
|
||||
|
||||
}());
|
@ -1,8 +1,26 @@
|
||||
// # Toggle Support
|
||||
|
||||
/*global document, jQuery */
|
||||
/*global document, jQuery, Ghost */
|
||||
(function ($) {
|
||||
"use strict";
|
||||
|
||||
Ghost.temporary.initToggles = function ($el) {
|
||||
|
||||
$el.find('[data-toggle]').each(function () {
|
||||
var toggle = $(this).data('toggle');
|
||||
$(this).parent().children(toggle).hide();
|
||||
});
|
||||
|
||||
$el.find('[data-toggle]').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
$(this).toggleClass('active');
|
||||
var toggle = $(this).data('toggle');
|
||||
$(this).parent().children(toggle).fadeToggle(100).toggleClass('open');
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
|
||||
$(document).ready(function () {
|
||||
|
||||
// ## Toggle Up In Your Grill
|
||||
@ -14,17 +32,7 @@
|
||||
// <li>Toggled yo</li>
|
||||
// </ul>
|
||||
// </nav>
|
||||
$('[data-toggle]').each(function () {
|
||||
var toggle = $(this).data('toggle');
|
||||
$(this).parent().children(toggle).hide();
|
||||
});
|
||||
|
||||
$('[data-toggle]').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
$(this).toggleClass('active');
|
||||
var toggle = $(this).data('toggle');
|
||||
$(this).parent().children(toggle).fadeToggle(100).toggleClass('open');
|
||||
});
|
||||
|
||||
Ghost.temporary.initToggles($(document));
|
||||
});
|
||||
|
||||
}(jQuery));
|
@ -1,83 +1,153 @@
|
||||
/*global window, document, Ghost, Backbone, $, _ */
|
||||
/*global window, document, Ghost, Backbone, confirm, JST, $, _ */
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var ContentList,
|
||||
ContentItem,
|
||||
PreviewContainer,
|
||||
|
||||
// Add shadow during scrolling
|
||||
scrollShadow = function (target, e) {
|
||||
if ($(e.currentTarget).scrollTop() > 10) {
|
||||
$(target).addClass('scrolling');
|
||||
} else {
|
||||
$(target).removeClass('scrolling');
|
||||
}
|
||||
};
|
||||
|
||||
// Base view
|
||||
// ----------
|
||||
Ghost.Layout.Blog = Backbone.Layout.extend({
|
||||
Ghost.Views.Blog = Backbone.View.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()
|
||||
this.addSubview(new PreviewContainer({ el: '.js-content-preview', collection: this.collection })).render();
|
||||
this.addSubview(new ContentList({ el: '.js-content-list', collection: this.collection })).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);
|
||||
},
|
||||
ContentList = Backbone.View.extend({
|
||||
|
||||
events: {
|
||||
'click .content-list-content' : 'scrollHandler',
|
||||
'click .content-list-content li' : 'showPreview'
|
||||
'click .content-list-content' : 'scrollHandler'
|
||||
},
|
||||
|
||||
initialize: function (options) {
|
||||
this.$('.content-list-content').on('scroll', _.bind(scrollShadow, null, '.content-list'));
|
||||
this.listenTo(this.collection, 'remove', this.showNext);
|
||||
},
|
||||
|
||||
showNext: function () {
|
||||
var id = this.collection.at(0).id;
|
||||
if (id) {
|
||||
Backbone.trigger('blog:activeItem', id);
|
||||
}
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.collection.each(function (model) {
|
||||
this.$('ol').append(this.addSubview(new ContentItem({model: model})).render().el);
|
||||
}, this);
|
||||
this.showNext();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// Content Item
|
||||
// -----------------------
|
||||
ContentItem = Backbone.View.extend({
|
||||
|
||||
tagName: 'li',
|
||||
|
||||
events: {
|
||||
'click a': 'setActiveItem'
|
||||
},
|
||||
|
||||
active: false,
|
||||
|
||||
initialize: function () {
|
||||
this.listenTo(Backbone, 'blog:activeItem', this.checkActive);
|
||||
this.listenTo(this.model, 'destroy', this.removeItem);
|
||||
},
|
||||
|
||||
removeItem: function () {
|
||||
var view = this;
|
||||
$.when(this.$el.slideUp()).then(function () {
|
||||
view.remove();
|
||||
});
|
||||
},
|
||||
|
||||
// If the current item isn't active, we trigger the event to
|
||||
// notify a change in which item we're viewing.
|
||||
setActiveItem: function (e) {
|
||||
e.preventDefault();
|
||||
if (this.active !== true) {
|
||||
Backbone.trigger('blog:activeItem', this.model.id);
|
||||
this.render();
|
||||
}
|
||||
},
|
||||
|
||||
// Checks whether this item is active and doesn't match the current id.
|
||||
checkActive: function (id) {
|
||||
if (this.model.id !== id) {
|
||||
if (this.active) {
|
||||
this.active = false;
|
||||
this.$el.removeClass('active');
|
||||
this.render();
|
||||
}
|
||||
} else {
|
||||
this.active = true;
|
||||
this.$el.addClass('active');
|
||||
}
|
||||
},
|
||||
|
||||
showPreview: function (e) {
|
||||
var item = $(e.currentTarget);
|
||||
this.$('.content-list-content li').removeClass('active');
|
||||
item.addClass('active');
|
||||
Backbone.trigger("blog:showPreview", item.data('id'));
|
||||
Backbone.trigger('blog:activeItem', item.data('id'));
|
||||
},
|
||||
|
||||
template: JST['content/list-item'],
|
||||
|
||||
render: function () {
|
||||
this.$el.html(this.template(_.extend({active: this.active}, this.model.toJSON())));
|
||||
return this;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// 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'));
|
||||
},
|
||||
PreviewContainer = Backbone.View.extend({
|
||||
|
||||
activeId: null,
|
||||
|
||||
events: {
|
||||
'click .post-controls .delete' : 'deletePost',
|
||||
'click .post-controls .post-edit' : 'editPost'
|
||||
},
|
||||
|
||||
initialize: function (options) {
|
||||
this.listenTo(Backbone, 'blog:activeItem', this.setActivePreview);
|
||||
this.$('.content-preview-content').on('scroll', _.bind(scrollShadow, null, '.content-preview'));
|
||||
},
|
||||
|
||||
setActivePreview: function (id) {
|
||||
if (this.activeId !== id) {
|
||||
this.activeId = id;
|
||||
this.render();
|
||||
}
|
||||
},
|
||||
|
||||
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');
|
||||
}
|
||||
});
|
||||
if (confirm('Are you sure you want to delete this post?')) {
|
||||
this.model.destroy({
|
||||
wait: true
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
editPost: function (e) {
|
||||
@ -87,19 +157,20 @@
|
||||
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();
|
||||
},
|
||||
template: JST['content/preview'],
|
||||
|
||||
render: function () {
|
||||
this.$('.wrapper').html(this.model.get('content_html'));
|
||||
if (this.activeId) {
|
||||
this.model = this.collection.get(this.activeId);
|
||||
this.$el.html(this.template(this.model.toJSON()));
|
||||
}
|
||||
this.$('.wrapper').on('click', 'a', function (e) {
|
||||
$(e.currentTarget).attr('target', '_blank');
|
||||
});
|
||||
Ghost.temporary.initToggles(this.$el);
|
||||
return this;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// Initialize views.
|
||||
// TODO: move to a `Backbone.Router`
|
||||
Ghost.currentView = new Ghost.Layout.Blog({ el: '#main' });
|
||||
|
||||
}());
|
207
core/admin/assets/js/views/editor.js
Normal file
207
core/admin/assets/js/views/editor.js
Normal file
@ -0,0 +1,207 @@
|
||||
// # Article Editor
|
||||
|
||||
/*global window, alert, document, history, Backbone, Ghost, $, _, Showdown, CodeMirror, shortcut, Countable */
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var PublishBar,
|
||||
TagWidget,
|
||||
ActionsWidget,
|
||||
MarkdownShortcuts = [
|
||||
{'key': 'Ctrl+B', 'style': 'bold'},
|
||||
{'key': 'Meta+B', 'style': 'bold'},
|
||||
{'key': 'Ctrl+I', 'style': 'italic'},
|
||||
{'key': 'Meta+I', 'style': 'italic'},
|
||||
{'key': 'Ctrl+Alt+U', 'style': 'strike'},
|
||||
{'key': 'Ctrl+Shift+K', 'style': 'code'},
|
||||
{'key': 'Alt+1', 'style': 'h1'},
|
||||
{'key': 'Alt+2', 'style': 'h2'},
|
||||
{'key': 'Alt+3', 'style': 'h3'},
|
||||
{'key': 'Alt+4', 'style': 'h4'},
|
||||
{'key': 'Alt+5', 'style': 'h5'},
|
||||
{'key': 'Alt+6', 'style': 'h6'},
|
||||
{'key': 'Ctrl+Shift+L', 'style': 'link'},
|
||||
{'key': 'Ctrl+Shift+I', 'style': 'image'},
|
||||
{'key': 'Ctrl+Q', 'style': 'blockquote'},
|
||||
{'key': 'Ctrl+Shift+1', 'style': 'currentdate'}
|
||||
];
|
||||
|
||||
// The publish bar associated with a post, which has the TagWidget and
|
||||
// Save button and options and such.
|
||||
// ----------------------------------------
|
||||
PublishBar = Backbone.View.extend({
|
||||
|
||||
initialize: function () {
|
||||
this.addSubview(new TagWidget({el: this.$('#entry-categories'), model: this.model})).render();
|
||||
this.addSubview(new ActionsWidget({el: this.$('#entry-actions'), model: this.model})).render();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// The Tag UI area associated with a post
|
||||
// ----------------------------------------
|
||||
TagWidget = Backbone.View.extend({
|
||||
|
||||
});
|
||||
|
||||
// The Publish, Queue, Publish Now buttons
|
||||
// ----------------------------------------
|
||||
ActionsWidget = Backbone.View.extend({
|
||||
|
||||
events: {
|
||||
'click [data-set-status]': 'handleStatus',
|
||||
'click .js-post-button': 'updatePost'
|
||||
},
|
||||
|
||||
statusMap: {
|
||||
'draft' : 'Save Draft',
|
||||
'published': 'Update Post',
|
||||
'scheduled' : 'Save Schedued Post'
|
||||
},
|
||||
|
||||
initialize: function () {
|
||||
this.listenTo(this.model, 'change:status', this.render);
|
||||
this.model.on('change:id', function (m) {
|
||||
Backbone.history.navigate('/editor/' + m.id);
|
||||
});
|
||||
},
|
||||
|
||||
handleStatus: function (e) {
|
||||
e.preventDefault();
|
||||
var status = $(e.currentTarget).attr('data-set-status'),
|
||||
model = this.model;
|
||||
|
||||
if (status === 'publish-on') {
|
||||
return alert('Scheduled publishing not supported yet.');
|
||||
}
|
||||
if (status === 'queue') {
|
||||
return alert('Scheduled publishing not supported yet.');
|
||||
}
|
||||
|
||||
this.savePost({
|
||||
status: status
|
||||
}).then(function () {
|
||||
alert('Your post: ' + model.get('title') + ' has been ' + status);
|
||||
});
|
||||
},
|
||||
|
||||
updatePost: function (e) {
|
||||
e.preventDefault();
|
||||
this.savePost();
|
||||
},
|
||||
|
||||
savePost: function (data) {
|
||||
// TODO: The content getter here isn't great, shouldn't rely on currentView.
|
||||
return this.model.save(_.extend({
|
||||
title: $('#entry-title').val(),
|
||||
content: Ghost.currentView.editor.getValue()
|
||||
}, data));
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.$('.js-post-button').text(this.statusMap[this.model.get('status')]);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// The entire /editor page's route (TODO: move all views to client side templates)
|
||||
// ----------------------------------------
|
||||
Ghost.Views.Editor = Backbone.View.extend({
|
||||
|
||||
initialize: function () {
|
||||
|
||||
// Add the container view for the Publish Bar
|
||||
this.addSubview(new PublishBar({el: "#publish-bar", model: this.model})).render();
|
||||
|
||||
this.$('#entry-markdown').html(this.model.get('content'));
|
||||
|
||||
this.initMarkdown();
|
||||
this.renderPreview();
|
||||
|
||||
// TODO: Debounce
|
||||
this.$('.CodeMirror-scroll').on('scroll', this.syncScroll);
|
||||
|
||||
// Shadow on Markdown if scrolled
|
||||
this.$('.CodeMirror-scroll').on('scroll', function (e) {
|
||||
if ($('.CodeMirror-scroll').scrollTop() > 10) {
|
||||
$('.entry-markdown').addClass('scrolling');
|
||||
} else {
|
||||
$('.entry-markdown').removeClass('scrolling');
|
||||
}
|
||||
});
|
||||
|
||||
// Shadow on Preview if scrolled
|
||||
this.$('.entry-preview-content').on('scroll', function (e) {
|
||||
if ($('.entry-preview-content').scrollTop() > 10) {
|
||||
$('.entry-preview').addClass('scrolling');
|
||||
} else {
|
||||
$('.entry-preview').removeClass('scrolling');
|
||||
}
|
||||
});
|
||||
|
||||
// Zen writing mode shortcut
|
||||
shortcut.add("Alt+Shift+Z", function () {
|
||||
$('body').toggleClass('zen');
|
||||
});
|
||||
|
||||
$('.entry-markdown header, .entry-preview header').click(function (e) {
|
||||
$('.entry-markdown, .entry-preview').removeClass('active');
|
||||
$(e.target).closest('section').addClass('active');
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
syncScroll: function (e) {
|
||||
var $codeViewport = $(e.target),
|
||||
$previewViewport = $('.entry-preview-content'),
|
||||
$codeContent = $('.CodeMirror-sizer'),
|
||||
$previewContent = $('.rendered-markdown'),
|
||||
|
||||
// calc position
|
||||
codeHeight = $codeContent.height() - $codeViewport.height(),
|
||||
previewHeight = $previewContent.height() - $previewViewport.height(),
|
||||
ratio = previewHeight / codeHeight,
|
||||
previewPostition = $codeViewport.scrollTop() * ratio;
|
||||
|
||||
// apply new scroll
|
||||
$previewViewport.scrollTop(previewPostition);
|
||||
},
|
||||
|
||||
// This updates the editor preview panel.
|
||||
// Currently gets called on every key press.
|
||||
// Also trigger word count update
|
||||
renderPreview: function () {
|
||||
var view = this,
|
||||
preview = document.getElementsByClassName('rendered-markdown')[0];
|
||||
preview.innerHTML = this.converter.makeHtml(this.editor.getValue());
|
||||
Countable.once(preview, function (counter) {
|
||||
view.$('.entry-word-count').text(counter.words + ' words');
|
||||
view.$('.entry-character-count').text(counter.characters + ' characters');
|
||||
view.$('.entry-paragraph-count').text(counter.paragraphs + ' paragraphs');
|
||||
});
|
||||
},
|
||||
|
||||
// Markdown converter & markdown shortcut initialization.
|
||||
initMarkdown: function () {
|
||||
this.converter = new Showdown.converter({extensions: ['ghostdown']});
|
||||
this.editor = CodeMirror.fromTextArea(document.getElementById('entry-markdown'), {
|
||||
mode: 'markdown',
|
||||
tabMode: 'indent',
|
||||
lineWrapping: true
|
||||
});
|
||||
|
||||
_.each(MarkdownShortcuts, function (combo) {
|
||||
shortcut.add(combo.key, function () {
|
||||
return this.editor.addMarkdown({style: combo.style});
|
||||
});
|
||||
});
|
||||
|
||||
var view = this;
|
||||
this.editor.on('change', function () {
|
||||
view.renderPreview();
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}());
|
@ -1,56 +0,0 @@
|
||||
// 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)
|
||||
}
|
||||
})
|
362
core/admin/assets/lib/handlebars/handlebars-runtime.js
Normal file
362
core/admin/assets/lib/handlebars/handlebars-runtime.js
Normal file
@ -0,0 +1,362 @@
|
||||
/*
|
||||
|
||||
Copyright (C) 2011 by Yehuda Katz
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
// lib/handlebars/browser-prefix.js
|
||||
var Handlebars = {};
|
||||
|
||||
(function(Handlebars, undefined) {
|
||||
;
|
||||
// lib/handlebars/base.js
|
||||
|
||||
Handlebars.VERSION = "1.0.0";
|
||||
Handlebars.COMPILER_REVISION = 4;
|
||||
|
||||
Handlebars.REVISION_CHANGES = {
|
||||
1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
|
||||
2: '== 1.0.0-rc.3',
|
||||
3: '== 1.0.0-rc.4',
|
||||
4: '>= 1.0.0'
|
||||
};
|
||||
|
||||
Handlebars.helpers = {};
|
||||
Handlebars.partials = {};
|
||||
|
||||
var toString = Object.prototype.toString,
|
||||
functionType = '[object Function]',
|
||||
objectType = '[object Object]';
|
||||
|
||||
Handlebars.registerHelper = function(name, fn, inverse) {
|
||||
if (toString.call(name) === objectType) {
|
||||
if (inverse || fn) { throw new Handlebars.Exception('Arg not supported with multiple helpers'); }
|
||||
Handlebars.Utils.extend(this.helpers, name);
|
||||
} else {
|
||||
if (inverse) { fn.not = inverse; }
|
||||
this.helpers[name] = fn;
|
||||
}
|
||||
};
|
||||
|
||||
Handlebars.registerPartial = function(name, str) {
|
||||
if (toString.call(name) === objectType) {
|
||||
Handlebars.Utils.extend(this.partials, name);
|
||||
} else {
|
||||
this.partials[name] = str;
|
||||
}
|
||||
};
|
||||
|
||||
Handlebars.registerHelper('helperMissing', function(arg) {
|
||||
if(arguments.length === 2) {
|
||||
return undefined;
|
||||
} else {
|
||||
throw new Error("Missing helper: '" + arg + "'");
|
||||
}
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('blockHelperMissing', function(context, options) {
|
||||
var inverse = options.inverse || function() {}, fn = options.fn;
|
||||
|
||||
var type = toString.call(context);
|
||||
|
||||
if(type === functionType) { context = context.call(this); }
|
||||
|
||||
if(context === true) {
|
||||
return fn(this);
|
||||
} else if(context === false || context == null) {
|
||||
return inverse(this);
|
||||
} else if(type === "[object Array]") {
|
||||
if(context.length > 0) {
|
||||
return Handlebars.helpers.each(context, options);
|
||||
} else {
|
||||
return inverse(this);
|
||||
}
|
||||
} else {
|
||||
return fn(context);
|
||||
}
|
||||
});
|
||||
|
||||
Handlebars.K = function() {};
|
||||
|
||||
Handlebars.createFrame = Object.create || function(object) {
|
||||
Handlebars.K.prototype = object;
|
||||
var obj = new Handlebars.K();
|
||||
Handlebars.K.prototype = null;
|
||||
return obj;
|
||||
};
|
||||
|
||||
Handlebars.logger = {
|
||||
DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3,
|
||||
|
||||
methodMap: {0: 'debug', 1: 'info', 2: 'warn', 3: 'error'},
|
||||
|
||||
// can be overridden in the host environment
|
||||
log: function(level, obj) {
|
||||
if (Handlebars.logger.level <= level) {
|
||||
var method = Handlebars.logger.methodMap[level];
|
||||
if (typeof console !== 'undefined' && console[method]) {
|
||||
console[method].call(console, obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Handlebars.log = function(level, obj) { Handlebars.logger.log(level, obj); };
|
||||
|
||||
Handlebars.registerHelper('each', function(context, options) {
|
||||
var fn = options.fn, inverse = options.inverse;
|
||||
var i = 0, ret = "", data;
|
||||
|
||||
var type = toString.call(context);
|
||||
if(type === functionType) { context = context.call(this); }
|
||||
|
||||
if (options.data) {
|
||||
data = Handlebars.createFrame(options.data);
|
||||
}
|
||||
|
||||
if(context && typeof context === 'object') {
|
||||
if(context instanceof Array){
|
||||
for(var j = context.length; i<j; i++) {
|
||||
if (data) { data.index = i; }
|
||||
ret = ret + fn(context[i], { data: data });
|
||||
}
|
||||
} else {
|
||||
for(var key in context) {
|
||||
if(context.hasOwnProperty(key)) {
|
||||
if(data) { data.key = key; }
|
||||
ret = ret + fn(context[key], {data: data});
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(i === 0){
|
||||
ret = inverse(this);
|
||||
}
|
||||
|
||||
return ret;
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('if', function(conditional, options) {
|
||||
var type = toString.call(conditional);
|
||||
if(type === functionType) { conditional = conditional.call(this); }
|
||||
|
||||
if(!conditional || Handlebars.Utils.isEmpty(conditional)) {
|
||||
return options.inverse(this);
|
||||
} else {
|
||||
return options.fn(this);
|
||||
}
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('unless', function(conditional, options) {
|
||||
return Handlebars.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn});
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('with', function(context, options) {
|
||||
var type = toString.call(context);
|
||||
if(type === functionType) { context = context.call(this); }
|
||||
|
||||
if (!Handlebars.Utils.isEmpty(context)) return options.fn(context);
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('log', function(context, options) {
|
||||
var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1;
|
||||
Handlebars.log(level, context);
|
||||
});
|
||||
;
|
||||
// lib/handlebars/utils.js
|
||||
|
||||
var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
|
||||
|
||||
Handlebars.Exception = function(message) {
|
||||
var tmp = Error.prototype.constructor.apply(this, arguments);
|
||||
|
||||
// Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
|
||||
for (var idx = 0; idx < errorProps.length; idx++) {
|
||||
this[errorProps[idx]] = tmp[errorProps[idx]];
|
||||
}
|
||||
};
|
||||
Handlebars.Exception.prototype = new Error();
|
||||
|
||||
// Build out our basic SafeString type
|
||||
Handlebars.SafeString = function(string) {
|
||||
this.string = string;
|
||||
};
|
||||
Handlebars.SafeString.prototype.toString = function() {
|
||||
return this.string.toString();
|
||||
};
|
||||
|
||||
var escape = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
'"': """,
|
||||
"'": "'",
|
||||
"`": "`"
|
||||
};
|
||||
|
||||
var badChars = /[&<>"'`]/g;
|
||||
var possible = /[&<>"'`]/;
|
||||
|
||||
var escapeChar = function(chr) {
|
||||
return escape[chr] || "&";
|
||||
};
|
||||
|
||||
Handlebars.Utils = {
|
||||
extend: function(obj, value) {
|
||||
for(var key in value) {
|
||||
if(value.hasOwnProperty(key)) {
|
||||
obj[key] = value[key];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
escapeExpression: function(string) {
|
||||
// don't escape SafeStrings, since they're already safe
|
||||
if (string instanceof Handlebars.SafeString) {
|
||||
return string.toString();
|
||||
} else if (string == null || string === false) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Force a string conversion as this will be done by the append regardless and
|
||||
// the regex test will do this transparently behind the scenes, causing issues if
|
||||
// an object's to string has escaped characters in it.
|
||||
string = string.toString();
|
||||
|
||||
if(!possible.test(string)) { return string; }
|
||||
return string.replace(badChars, escapeChar);
|
||||
},
|
||||
|
||||
isEmpty: function(value) {
|
||||
if (!value && value !== 0) {
|
||||
return true;
|
||||
} else if(toString.call(value) === "[object Array]" && value.length === 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
;
|
||||
// lib/handlebars/runtime.js
|
||||
|
||||
Handlebars.VM = {
|
||||
template: function(templateSpec) {
|
||||
// Just add water
|
||||
var container = {
|
||||
escapeExpression: Handlebars.Utils.escapeExpression,
|
||||
invokePartial: Handlebars.VM.invokePartial,
|
||||
programs: [],
|
||||
program: function(i, fn, data) {
|
||||
var programWrapper = this.programs[i];
|
||||
if(data) {
|
||||
programWrapper = Handlebars.VM.program(i, fn, data);
|
||||
} else if (!programWrapper) {
|
||||
programWrapper = this.programs[i] = Handlebars.VM.program(i, fn);
|
||||
}
|
||||
return programWrapper;
|
||||
},
|
||||
merge: function(param, common) {
|
||||
var ret = param || common;
|
||||
|
||||
if (param && common) {
|
||||
ret = {};
|
||||
Handlebars.Utils.extend(ret, common);
|
||||
Handlebars.Utils.extend(ret, param);
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
programWithDepth: Handlebars.VM.programWithDepth,
|
||||
noop: Handlebars.VM.noop,
|
||||
compilerInfo: null
|
||||
};
|
||||
|
||||
return function(context, options) {
|
||||
options = options || {};
|
||||
var result = templateSpec.call(container, Handlebars, context, options.helpers, options.partials, options.data);
|
||||
|
||||
var compilerInfo = container.compilerInfo || [],
|
||||
compilerRevision = compilerInfo[0] || 1,
|
||||
currentRevision = Handlebars.COMPILER_REVISION;
|
||||
|
||||
if (compilerRevision !== currentRevision) {
|
||||
if (compilerRevision < currentRevision) {
|
||||
var runtimeVersions = Handlebars.REVISION_CHANGES[currentRevision],
|
||||
compilerVersions = Handlebars.REVISION_CHANGES[compilerRevision];
|
||||
throw "Template was precompiled with an older version of Handlebars than the current runtime. "+
|
||||
"Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+").";
|
||||
} else {
|
||||
// Use the embedded version info since the runtime doesn't know about this revision yet
|
||||
throw "Template was precompiled with a newer version of Handlebars than the current runtime. "+
|
||||
"Please update your runtime to a newer version ("+compilerInfo[1]+").";
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
},
|
||||
|
||||
programWithDepth: function(i, fn, data /*, $depth */) {
|
||||
var args = Array.prototype.slice.call(arguments, 3);
|
||||
|
||||
var program = function(context, options) {
|
||||
options = options || {};
|
||||
|
||||
return fn.apply(this, [context, options.data || data].concat(args));
|
||||
};
|
||||
program.program = i;
|
||||
program.depth = args.length;
|
||||
return program;
|
||||
},
|
||||
program: function(i, fn, data) {
|
||||
var program = function(context, options) {
|
||||
options = options || {};
|
||||
|
||||
return fn(context, options.data || data);
|
||||
};
|
||||
program.program = i;
|
||||
program.depth = 0;
|
||||
return program;
|
||||
},
|
||||
noop: function() { return ""; },
|
||||
invokePartial: function(partial, name, context, helpers, partials, data) {
|
||||
var options = { helpers: helpers, partials: partials, data: data };
|
||||
|
||||
if(partial === undefined) {
|
||||
throw new Handlebars.Exception("The partial " + name + " could not be found");
|
||||
} else if(partial instanceof Function) {
|
||||
return partial(context, options);
|
||||
} else if (!Handlebars.compile) {
|
||||
throw new Handlebars.Exception("The partial " + name + " could not be compiled when running in runtime-only mode");
|
||||
} else {
|
||||
partials[name] = Handlebars.compile(partial, {data: data !== undefined});
|
||||
return partials[name](context, options);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Handlebars.template = Handlebars.VM.template;
|
||||
;
|
||||
// lib/handlebars/browser-suffix.js
|
||||
})(Handlebars);
|
||||
;
|
7
core/admin/assets/tmpl/content/list-item.hbs
Normal file
7
core/admin/assets/tmpl/content/list-item.hbs
Normal file
@ -0,0 +1,7 @@
|
||||
<a class="permalink{{#if featured}} featured{{/if}}" href="#">
|
||||
<h3 class="entry-title">{{title}}</h3>
|
||||
<section class="entry-meta">
|
||||
<time datetime="2013-01-04" class="date">5 minutes ago</time>
|
||||
{{!<span class="views">1,934</span>}}
|
||||
</section>
|
||||
</a>
|
21
core/admin/assets/tmpl/content/preview.hbs
Normal file
21
core/admin/assets/tmpl/content/preview.hbs
Normal file
@ -0,0 +1,21 @@
|
||||
<header class="floatingheader">
|
||||
<a class="{{#if featured}}featured{{else}}unfeatured{{/if}}" href="#">
|
||||
<span class="hidden">Star</span>
|
||||
</a>
|
||||
{{! TODO: JavaScript toggle featured/unfeatured}}
|
||||
<span class="status">Published</span>
|
||||
<span class="normal">by</span>
|
||||
<span class="author">John O'Nolan</span>
|
||||
<section class="post-controls">
|
||||
<a class="post-edit" href="#"><span class="hidden">Edit Post</span></a>
|
||||
<a class="post-settings" href="#" data-toggle=".menu-drop-right"><span class="hidden">Post Settings</span></a>
|
||||
<ul class="menu-drop-right">
|
||||
<li><a href="#" class="url">URL</a></li>
|
||||
<li><a href="#" class="something">Something</a></li>
|
||||
<li><a href="#" class="delete">Delete</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
</header>
|
||||
<section class="content-preview-content">
|
||||
<div class="wrapper">{{{content_html}}}</div>
|
||||
</section>
|
@ -1,9 +1,9 @@
|
||||
{{!< default}}
|
||||
<section class="content-list">
|
||||
<section class="content-list js-content-list">
|
||||
<header class="floatingheader">
|
||||
<section class="content-filter">
|
||||
<a class="dropdown" href="#" data-toggle=".menu-drop">All Posts</a>
|
||||
<ul class="menu-drop">
|
||||
<ul class="menu-drop" style="display:none;">
|
||||
<li class="active"><a href="#">All Posts</a></li>
|
||||
<li><a href="#">Recently Edited</a></li>
|
||||
<li><a href="#">By Author...</a></li>
|
||||
@ -13,43 +13,9 @@
|
||||
<a href="/ghost/editor" class="button button-add"><span class="hidden">New Post</span></a>
|
||||
</header>
|
||||
<section class="content-list-content">
|
||||
<ol>
|
||||
{{#each posts}}
|
||||
{{! #if featured class="featured"{{/if}}
|
||||
<li data-id="{{id}}">
|
||||
<a class="permalink" href="#">
|
||||
<h3 class="entry-title">{{title}}</h3>
|
||||
<section class="entry-meta">
|
||||
<time datetime="2013-01-04" class="date">5 minutes ago</time>
|
||||
{{!<span class="views">1,934</span>}}
|
||||
</section>
|
||||
</a>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ol>
|
||||
<ol></ol>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section class="content-preview">
|
||||
<header class="floatingheader">
|
||||
|
||||
<a class="unfeatured" href="#"><span class="hidden">Star</span></a>
|
||||
{{! TODO: JavaScript toggle featured/unfeatured}}
|
||||
|
||||
<span class="status">Published</span>
|
||||
<span class="normal">by</span>
|
||||
<span class="author">John O'Nolan</span>
|
||||
<section class="post-controls">
|
||||
<a class="post-edit" href="#"><span class="hidden">Edit Post</span></a>
|
||||
<a class="post-settings" href="#" data-toggle=".menu-drop-right"><span class="hidden">Post Settings</span></a>
|
||||
<ul class="menu-drop-right">
|
||||
<li><a href="#" class="url">URL</a></li>
|
||||
<li><a href="#" class="something">Something</a></li>
|
||||
<li><a href="#" class="delete">Delete</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
</header>
|
||||
<section class="content-preview-content">
|
||||
<div class="wrapper"></div>
|
||||
</section>
|
||||
<section class="content-preview js-content-preview">
|
||||
</section>
|
@ -2,8 +2,6 @@
|
||||
<script src="/core/admin/assets/lib/chart.min.js"></script>
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
|
||||
//$('body').click(function(){
|
||||
$('.widget-time').fadeIn(1000);
|
||||
$('.widget-image').delay(300).fadeIn(1000);
|
||||
$('.widget-posts').delay(600).fadeIn(900, function(){
|
||||
@ -48,7 +46,6 @@
|
||||
$('.widget-ideas').delay(3200).fadeIn(1000);
|
||||
$('.widget-tweets').delay(3400).fadeIn(1000);
|
||||
$('.widget-backups').delay(3600).fadeIn(1000);
|
||||
//});
|
||||
});
|
||||
</script>
|
||||
{{/contentFor}}
|
||||
|
@ -22,17 +22,6 @@
|
||||
<link rel="stylesheet" type="text/css" href="/core/admin/assets/lib/codemirror/codemirror.css"> <!-- TODO: Kill this - #29 -->
|
||||
<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>
|
||||
<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/lib/countable.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"}}}
|
||||
</head>
|
||||
<body class="{{bodyClass}}">
|
||||
{{#unless hideNavbar}}
|
||||
@ -45,11 +34,32 @@
|
||||
{{{body}}}
|
||||
</main>
|
||||
|
||||
<script type="text/javascript" src="/core/admin/assets/js/init.js"></script>
|
||||
<script src="/core/admin/assets/lib/jquery/jquery.min.js"></script>
|
||||
<script src="/core/admin/assets/lib/icheck/jquery.icheck.min.js"></script>
|
||||
<script src="/core/admin/assets/lib/underscore.js"></script>
|
||||
<script src="/core/admin/assets/lib/backbone/backbone.js"></script>
|
||||
<script src="/core/admin/assets/lib/handlebars/handlebars-runtime.js"></script>
|
||||
<script src="/core/admin/assets/lib/codemirror/codemirror.js"></script>
|
||||
<script src="/core/admin/assets/lib/codemirror/mode/markdown/markdown.js"></script>
|
||||
<script src="/core/admin/assets/lib/showdown/showdown.js"></script>
|
||||
<script src="/core/admin/assets/lib/showdown/extensions/ghostdown.js"></script>
|
||||
<script src="/core/admin/assets/lib/shortcuts.js"></script>
|
||||
<script src="/core/admin/assets/lib/countable.js"></script>
|
||||
|
||||
<script src="/core/admin/assets/js/init.js"></script>
|
||||
<script src="/core/admin/assets/js/hbs-tmpl.js"></script>
|
||||
<script src="/core/admin/assets/js/toggle.js"></script>
|
||||
<script src="/core/admin/assets/js/admin-ui-temp.js"></script>
|
||||
<script src="/core/admin/assets/js/markdown-actions.js"></script>
|
||||
<script src="/core/admin/assets/js/tagui.js"></script>
|
||||
|
||||
<!-- // require '/core/admin/assets/js/models/*' -->
|
||||
<script type="text/javascript" src="/core/admin/assets/js/models/post.js"></script>
|
||||
<script 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>
|
||||
<script src="/core/admin/assets/js/views/blog.js"></script>
|
||||
<script src="/core/admin/assets/js/views/editor.js"></script>
|
||||
<script src="/core/admin/assets/js/router.js"></script>
|
||||
<script src="/core/admin/assets/js/starter.js"></script>
|
||||
{{{block "bodyScripts"}}}
|
||||
</body>
|
||||
</html>
|
@ -1,14 +1,3 @@
|
||||
{{#contentFor 'bodyScripts'}}
|
||||
<script src="/core/admin/assets/lib/codemirror/codemirror.js"></script>
|
||||
<script src="/core/admin/assets/lib/codemirror/mode/markdown/markdown.js"></script>
|
||||
<script src="/core/admin/assets/lib/showdown/showdown.js"></script>
|
||||
<script src="/core/admin/assets/lib/showdown/extensions/ghostdown.js"></script>
|
||||
<script src="/core/admin/assets/lib/shortcuts.js"></script>
|
||||
<script src="/core/admin/assets/js/markdown-actions.js"></script>
|
||||
<script src="/core/admin/assets/js/editor.js"></script>
|
||||
<script src="/core/admin/assets/js/tagui.js"></script>
|
||||
{{/contentFor}}
|
||||
|
||||
{{!< default}}
|
||||
{{! TODO: Add "scrolling" class only when one of the panels is scrolled down by 5px or more }}
|
||||
<header>
|
||||
@ -25,13 +14,13 @@
|
||||
<a class="markdown-help" href="#"><span class="hidden">What is Markdown?</span></a>
|
||||
</header>
|
||||
<section class="entry-markdown-content">
|
||||
<textarea id="entry-markdown">{{content}}</textarea>
|
||||
<textarea id="entry-markdown"></textarea>
|
||||
</section>
|
||||
</section>{{!.entry-markdown}}
|
||||
|
||||
<section class="entry-preview">
|
||||
<header class="floatingheader">
|
||||
Preview <span class="entry-word-count">0 words</span>
|
||||
Preview <span class="entry-word-count js-entry-word-count">0 words</span>
|
||||
</header>
|
||||
<section class="entry-preview-content">
|
||||
<div class="rendered-markdown">
|
||||
@ -53,13 +42,13 @@
|
||||
<div class="right">
|
||||
<button id="entry-settings" href="#" class="button-link"><span class="hidden">Settings</span></button>
|
||||
<section id="entry-actions" class="splitbutton-save">
|
||||
<button type="button" class="button-save" data-state="save-draft">Save Draft</button>
|
||||
<button type="button" class="button-save js-post-button"></button>
|
||||
<a class="options up" href="#"><span class="hidden">Options</span></a>
|
||||
<ul class="editor-options overlay" style="display:none">
|
||||
<li data-title="publish-now" data-url=""><a href="#">Publish Now</a></li>
|
||||
<li data-title="queue" data-url=""><a href="#">Add to Queue</a></li>
|
||||
<li data-title="publish-on" data-url=""><a href="#">Publish on...</a></li>
|
||||
<li data-title="save-draft" data-url="" class="active"><a href="#">Save Draft</a></li>
|
||||
<li data-set-status="published"><a href="#">Publish Now</a></li>
|
||||
<li data-set-status="queue"><a href="#">Add to Queue</a></li>
|
||||
<li data-set-status="publish-on"><a href="#">Publish on...</a></li>
|
||||
<li data-set-status="draft"><a href="#">Save Draft</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
|
@ -80,7 +80,7 @@
|
||||
// takes the API method and wraps it so that it gets data from the request and returns a sensible JSON response
|
||||
requestHandler = function (apiMethod) {
|
||||
return function (req, res) {
|
||||
var options = _.extend(req.body, req.params);
|
||||
var options = _.extend(req.body, req.query, req.params);
|
||||
return apiMethod(options).then(function (result) {
|
||||
res.json(result || {});
|
||||
}, function (error) {
|
||||
|
@ -1,95 +0,0 @@
|
||||
/*global require, module */
|
||||
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
// We should just be able to require bookshelf and have it reference
|
||||
// the `Knex` instance bootstraped at the app initialization.
|
||||
var Bookshelf = require('bookshelf'),
|
||||
Showdown = require('showdown'),
|
||||
converter = new Showdown.converter(),
|
||||
|
||||
Post,
|
||||
Posts,
|
||||
User,
|
||||
Users,
|
||||
Setting,
|
||||
Settings;
|
||||
|
||||
Post = Bookshelf.Model.extend({
|
||||
|
||||
tableName: 'posts',
|
||||
|
||||
hasTimestamps: true,
|
||||
|
||||
initialize: function () {
|
||||
this.on('creating', this.creating, this);
|
||||
this.on('saving', this.saving, this);
|
||||
},
|
||||
|
||||
saving: function () {
|
||||
if (!this.get('title')) {
|
||||
throw new Error('Post title cannot be blank');
|
||||
}
|
||||
this.set('content_html', converter.makeHtml(this.get('content')));
|
||||
|
||||
// refactoring of ghost required in order to make these details available here
|
||||
// this.set('language', this.get('language') || ghost.config().defaultLang);
|
||||
// this.set('status', this.get('status') || ghost.statuses().draft);
|
||||
|
||||
},
|
||||
|
||||
creating: function () {
|
||||
if (!this.get('slug')) {
|
||||
this.generateSlug();
|
||||
}
|
||||
},
|
||||
|
||||
generateSlug: function () {
|
||||
return this.set('slug', this.get('title').replace(/\:/g, '').replace(/\s/g, '-').toLowerCase());
|
||||
},
|
||||
|
||||
user: function () {
|
||||
return this.belongsTo(User, 'created_by');
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
Posts = Bookshelf.Collection.extend({
|
||||
|
||||
model: Post
|
||||
|
||||
});
|
||||
|
||||
User = Bookshelf.Model.extend({
|
||||
tableName: 'users',
|
||||
hasTimestamps: true,
|
||||
posts: function () {
|
||||
return this.hasMany(Posts, 'created_by');
|
||||
}
|
||||
});
|
||||
|
||||
Users = Bookshelf.Collection.extend({
|
||||
|
||||
model: User
|
||||
|
||||
});
|
||||
|
||||
Setting = Bookshelf.Model.extend({
|
||||
tableName: 'settings',
|
||||
hasTimestamps: true
|
||||
});
|
||||
|
||||
Settings = Bookshelf.Collection.extend({
|
||||
model: Setting
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
Post: Post,
|
||||
Posts: Posts,
|
||||
User: User,
|
||||
Users: Users,
|
||||
Setting: Setting,
|
||||
Settings: Settings
|
||||
};
|
||||
}());
|
@ -77,7 +77,8 @@
|
||||
opts = {page: opts};
|
||||
}
|
||||
|
||||
opts = _.extend({page: 1}, {
|
||||
opts = _.extend({
|
||||
page: 1,
|
||||
limit: 15,
|
||||
where: {},
|
||||
status: 'published'
|
||||
|
@ -29,6 +29,8 @@
|
||||
"grunt-shell": "~0.2.2",
|
||||
"grunt-contrib-sass": "~0.3.0",
|
||||
"sinon": "~1.7.2",
|
||||
"mocha": "~1.10.0"
|
||||
"mocha": "~1.10.0",
|
||||
"grunt-contrib-handlebars": "~0.5.9",
|
||||
"grunt-contrib-watch": "~0.4.4"
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user