commit
12496918de
1
.gitignore
vendored
1
.gitignore
vendored
@ -23,6 +23,7 @@ projectFilesBackup
|
||||
# Ghost DB file
|
||||
*.db
|
||||
|
||||
/core/admin/assets/tmpl/hbs-tmpl.js
|
||||
/core/admin/assets/css
|
||||
/core/admin/assets/sass/modules/bourbon
|
||||
.sass-cache/
|
||||
|
39
Gruntfile.js
39
Gruntfile.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/tmpl/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,20 +1,66 @@
|
||||
/*globals window, Backbone */
|
||||
/*globals window, _, $, Backbone */
|
||||
(function ($) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var Ghost = {
|
||||
Layout : {},
|
||||
View : {},
|
||||
Collection : {},
|
||||
Model : {},
|
||||
Layout : {},
|
||||
Views : {},
|
||||
Collections : {},
|
||||
Models : {},
|
||||
|
||||
settings: {
|
||||
baseUrl: '/api/v0.1'
|
||||
apiRoot: '/api/v0.1'
|
||||
},
|
||||
|
||||
currentView: null
|
||||
// This is a helper object to denote legacy things in the
|
||||
// middle of being transitioned.
|
||||
temporary: {},
|
||||
|
||||
currentView: null,
|
||||
router: null
|
||||
};
|
||||
|
||||
Ghost.View = Backbone.View.extend({
|
||||
|
||||
// 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;
|
||||
},
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
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 = Ghost.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 = Ghost.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 = Ghost.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 = Ghost.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' });
|
||||
|
||||
}());
|
219
core/admin/assets/js/views/editor.js
Normal file
219
core/admin/assets/js/views/editor.js
Normal file
@ -0,0 +1,219 @@
|
||||
// # 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 = Ghost.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 = Ghost.View.extend({
|
||||
|
||||
});
|
||||
|
||||
// The Publish, Queue, Publish Now buttons
|
||||
// ----------------------------------------
|
||||
ActionsWidget = Ghost.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();
|
||||
var model = this.model;
|
||||
this.savePost().then(function () {
|
||||
alert('Your post was saved as ' + model.get('status'));
|
||||
}, function () {
|
||||
alert(model.validationError);
|
||||
});
|
||||
},
|
||||
|
||||
savePost: function (data) {
|
||||
// TODO: The content getter here isn't great, shouldn't rely on currentView.
|
||||
var saved = this.model.save(_.extend({
|
||||
title: $('#entry-title').val(),
|
||||
content: Ghost.currentView.editor.getValue()
|
||||
}, data));
|
||||
|
||||
// TODO: Take this out if #2489 gets merged in Backbone. Or patch Backbone
|
||||
// ourselves for more consistent promises.
|
||||
if (saved) {
|
||||
return saved;
|
||||
}
|
||||
return $.Deferred().reject();
|
||||
},
|
||||
|
||||
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 = Ghost.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/tmpl/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
|
||||
};
|
||||
}());
|
@ -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