` blocks really easily with back ticks. Want to show off something more comprehensive? 4 spaces of indentation gets you there.\n\n .awesome-thing {\n display: block;\n width: 100%;\n }\n\n### Ready for a Break? \n\nThrow 3 or more dashes down on any new line and you've got yourself a fancy new divider. Aw yeah.\n\n---\n\n### Advanced Usage\n\nThere's one fantastic secret about Markdown. If you want, you can write plain old HTML and it'll still work! Very flexible.\n\n\n\nThat should be enough to get you started. Have fun - and let us know what you think :)",
- "html": "You're in! Nice. We've put together a little post to introduce you to the Ghost editor and get you started. Go ahead and edit this post to get going and learn how it all works!
\n\nGetting Started
\n\nWriting in markdown is really easy. In the left hand panel of Ghost, you simply write as you normally would. Where appropriate, you can use formatting shortcuts to style your content. For example, a list:
\n\n\n- Item number one
\n- Item number two\n
- A nested item
\n- A final item
\n
\n\nor with numbers!
\n\n\n- Remember to buy some milk
\n- Drink the milk
\n- Tweet that I remembered to buy the milk, and drank it
\n
\n\nLinks
\n\nWant to link to a source? No problem. If you paste in url, like http://ghost.org - it'll automatically be linked up. But if you want to customise your anchor text, you can do that too! Here's a link to the Ghost website. Neat.
\n\nWhat about Images?
\n\nImages work too! Already know the URL of the image you want to include in your article? Simply paste it in like this to make it show up:
\n\n\n\nNot sure which image you want to use yet? That's ok too. Leave yourself a descriptive placeholder and keep writing. Come back later and drag and drop the image in to upload:
\n\n\n\nQuoting
\n\nSometimes a link isn't enough, you want to quote someone on what they've said. It was probably very wisdomous. Is wisdomous a word? Find out in a future release when we introduce spellcheck! For now - it's definitely a word.
\n\n\n Wisdomous - it's definitely a word.
\n
\n\nWorking with Code
\n\nGot a streak of geek? We've got you covered there, too. You can write inline <code>
blocks really easily with back ticks. Want to show off something more comprehensive? 4 spaces of indentation gets you there.
\n\n.awesome-thing {\n display: block;\n width: 100%;\n}\n
\n\nReady for a Break?
\n\nThrow 3 or more dashes down on any new line and you've got yourself a fancy new divider. Aw yeah.
\n\n
\n\nAdvanced Usage
\n\nThere's one fantastic secret about Markdown. If you want, you can write plain old HTML and it'll still work! Very flexible.
\n\n\n\nThat should be enough to get you started. Have fun - and let us know what you think :)
",
+ "html": "You're in! Nice. We've put together a little post to introduce you to the Ghost editor and get you started. Go ahead and edit this post to get going and learn how it all works!
\n\nGetting Started
\n\nWriting in markdown is really easy. In the left hand panel of Ghost, you simply write as you normally would. Where appropriate, you can use formatting shortcuts to style your content. For example, a list:
\n\n\n- Item number one
\n- Item number two\n
- A nested item
\n- A final item
\n
\n\nor with numbers!
\n\n\n- Remember to buy some milk
\n- Drink the milk
\n- Tweet that I remembered to buy the milk, and drank it
\n
\n\nLinks
\n\nWant to link to a source? No problem. If you paste in url, like http://ghost.org - it'll automatically be linked up. But if you want to customise your anchor text, you can do that too! Here's a link to the Ghost website. Neat.
\n\nWhat about Images?
\n\nImages work too! Already know the URL of the image you want to include in your article? Simply paste it in like this to make it show up:
\n\n\n\nNot sure which image you want to use yet? That's ok too. Leave yourself a descriptive placeholder and keep writing. Come back later and drag and drop the image in to upload:
\n\nQuoting
\n\nSometimes a link isn't enough, you want to quote someone on what they've said. It was probably very wisdomous. Is wisdomous a word? Find out in a future release when we introduce spellcheck! For now - it's definitely a word.
\n\n\n Wisdomous - it's definitely a word.
\n
\n\nWorking with Code
\n\nGot a streak of geek? We've got you covered there, too. You can write inline <code>
blocks really easily with back ticks. Want to show off something more comprehensive? 4 spaces of indentation gets you there.
\n\n.awesome-thing {\n display: block;\n width: 100%;\n}\n
\n\nReady for a Break?
\n\nThrow 3 or more dashes down on any new line and you've got yourself a fancy new divider. Aw yeah.
\n\n
\n\nAdvanced Usage
\n\nThere's one fantastic secret about Markdown. If you want, you can write plain old HTML and it'll still work! Very flexible.
\n\n\n\nThat should be enough to get you started. Have fun - and let us know what you think :)
",
"image": null,
"featured": false,
"page": false,
diff --git a/core/server/data/import/000.js b/core/server/data/import/000.js
index 9c2bdd8fa8..73cdf49b7d 100644
--- a/core/server/data/import/000.js
+++ b/core/server/data/import/000.js
@@ -1,19 +1,19 @@
-var when = require("when"),
- _ = require("underscore"),
+var when = require('when'),
+ _ = require('underscore'),
models = require('../../models'),
errors = require('../../errorHandling'),
Importer000;
Importer000 = function () {
- _.bindAll(this, "basicImport");
+ _.bindAll(this, 'basicImport');
- this.version = "000";
+ this.version = '000';
this.importFrom = {
- "000": this.basicImport,
- "001": this.tempImport,
- "002": this.tempImport
+ '000': this.basicImport,
+ '001': this.tempImport,
+ '002': this.tempImport
};
};
@@ -81,7 +81,12 @@ function preProcessPostTags(tableData) {
function importTags(ops, tableData) {
tableData = stripProperties(['id'], tableData);
_.each(tableData, function (tag) {
- ops.push(models.Tag.add(tag));
+ ops.push(models.Tag.read({name: tag.name}).then(function (_tag) {
+ if (!_tag) {
+ return models.Tag.add(tag);
+ }
+ return when.resolve(_tag);
+ }));
});
}
diff --git a/core/server/data/import/index.js b/core/server/data/import/index.js
index cab3327250..6bfb98e216 100644
--- a/core/server/data/import/index.js
+++ b/core/server/data/import/index.js
@@ -4,7 +4,7 @@ module.exports = function (version, data) {
var importer;
try {
- importer = require("./" + version);
+ importer = require('./' + version);
} catch (ignore) {
// Zero effs given
}
diff --git a/core/server/data/migration/000.js b/core/server/data/migration/000.js
index d5b4bf1aab..003e62b3c5 100644
--- a/core/server/data/migration/000.js
+++ b/core/server/data/migration/000.js
@@ -131,22 +131,22 @@ up = function () {
down = function () {
return when.all([
- knex.Schema.dropTableIfExists("posts"),
- knex.Schema.dropTableIfExists("users"),
- knex.Schema.dropTableIfExists("roles"),
- knex.Schema.dropTableIfExists("settings"),
- knex.Schema.dropTableIfExists("permissions"),
- knex.Schema.dropTableIfExists("tags")
+ knex.Schema.dropTableIfExists('posts_tags'),
+ knex.Schema.dropTableIfExists('roles_users'),
+ knex.Schema.dropTableIfExists('permissions_users'),
+ knex.Schema.dropTableIfExists('permissions_roles'),
+ knex.Schema.dropTableIfExists('users')
+
]).then(function () {
- // Drop the relation tables after the model tables
return when.all([
- knex.Schema.dropTableIfExists("roles_users"),
- knex.Schema.dropTableIfExists("permissions_users"),
- knex.Schema.dropTableIfExists("permissions_roles"),
- knex.Schema.dropTableIfExists("posts_tags")
+ knex.Schema.dropTableIfExists('roles'),
+ knex.Schema.dropTableIfExists('settings'),
+ knex.Schema.dropTableIfExists('permissions'),
+ knex.Schema.dropTableIfExists('tags'),
+ knex.Schema.dropTableIfExists('posts')
]);
});
};
-exports.up = up;
+exports.up = up;
exports.down = down;
\ No newline at end of file
diff --git a/core/server/data/migration/index.js b/core/server/data/migration/index.js
index 8b62da0dd1..00e2523773 100644
--- a/core/server/data/migration/index.js
+++ b/core/server/data/migration/index.js
@@ -1,15 +1,15 @@
-var _ = require('underscore'),
- when = require('when'),
- series = require('when/sequence'),
- errors = require('../../errorHandling'),
- knex = require('../../models/base').Knex,
+var _ = require('underscore'),
+ when = require('when'),
+ series = require('when/sequence'),
+ errors = require('../../errorHandling'),
+ knex = require('../../models/base').Knex,
defaultSettings = require('../default-settings'),
- Settings = require('../../models/settings').Settings,
- fixtures = require('../fixtures'),
+ Settings = require('../../models/settings').Settings,
+ fixtures = require('../fixtures'),
- initialVersion = '000',
+ initialVersion = '000',
defaultDatabaseVersion;
// Default Database Version
diff --git a/core/server/errorHandling.js b/core/server/errorHandling.js
index 8da0c69373..4cc201b2aa 100644
--- a/core/server/errorHandling.js
+++ b/core/server/errorHandling.js
@@ -1,16 +1,16 @@
/*jslint regexp: true */
-var _ = require('underscore'),
- colors = require("colors"),
- fs = require('fs'),
- path = require('path'),
+var _ = require('underscore'),
+ colors = require('colors'),
+ fs = require('fs'),
+ path = require('path'),
errors,
// Paths for views
- appRoot = path.resolve(__dirname, '../'),
- themePath = path.resolve(appRoot + '/content/themes'),
- adminTemplatePath = path.resolve(appRoot + '/server/views/'),
+ appRoot = path.resolve(__dirname, '../'),
+ themePath = path.resolve(appRoot + '/content/themes'),
+ adminTemplatePath = path.resolve(appRoot + '/server/views/'),
defaultErrorTemplatePath = path.resolve(adminTemplatePath + '/user-error.hbs'),
- userErrorTemplatePath = path.resolve(themePath + '/error.hbs'),
+ userErrorTemplatePath = path.resolve(themePath + '/error.hbs'),
userErrorTemplateExists;
/**
@@ -31,14 +31,14 @@ errors = {
logError: function (err, context, help) {
var stack = err ? err.stack : null;
- err = err.message || err || "Unknown";
+ err = err.message || err || 'Unknown';
// TODO: Logging framework hookup
// Eventually we'll have better logging which will know about envs
if ((process.env.NODE_ENV === 'development' ||
process.env.NODE_ENV === 'staging' ||
process.env.NODE_ENV === 'production')) {
- console.error("\nERROR:".red, err.red);
+ console.error('\nERROR:'.red, err.red);
if (context) {
console.error(context);
@@ -49,10 +49,10 @@ errors = {
}
// add a new line
- console.error("");
+ console.error('');
if (stack) {
- console.error(stack, "\n");
+ console.error(stack, '\n');
}
}
},
@@ -84,7 +84,7 @@ errors = {
renderErrorPage: function (code, err, req, res, next) {
function parseStack(stack) {
- if (typeof stack !== "string") {
+ if (typeof stack !== 'string') {
return stack;
}
@@ -102,8 +102,8 @@ errors = {
}
return {
- "function": parts[1],
- "at": parts[2]
+ 'function': parts[1],
+ 'at': parts[2]
};
})
.filter(function (line) {
@@ -116,12 +116,12 @@ errors = {
function renderErrorInt(errorView) {
var stack = null;
- if (process.env.NODE_ENV !== "production" && err.stack) {
+ if (process.env.NODE_ENV !== 'production' && err.stack) {
stack = parseStack(err.stack);
}
// TODO: Attach node-polyglot
- res.render((errorView || "error"), {
+ res.render((errorView || 'error'), {
message: err.message || err,
code: code,
stack: stack
@@ -177,13 +177,13 @@ errors = {
// Ensure our 'this' context in the functions
_.bindAll(
errors,
- "throwError",
- "logError",
- "logAndThrowError",
- "logErrorWithRedirect",
- "renderErrorPage",
- "render404Page",
- "render500Page"
+ 'throwError',
+ 'logError',
+ 'logAndThrowError',
+ 'logErrorWithRedirect',
+ 'renderErrorPage',
+ 'render404Page',
+ 'render500Page'
);
module.exports = errors;
\ No newline at end of file
diff --git a/core/server/helpers/index.js b/core/server/helpers/index.js
index df3b9144e5..85796b64e5 100644
--- a/core/server/helpers/index.js
+++ b/core/server/helpers/index.js
@@ -1,18 +1,18 @@
-var _ = require('underscore'),
- moment = require('moment'),
- downsize = require('downsize'),
- when = require('when'),
- hbs = require('express-hbs'),
+var _ = require('underscore'),
+ moment = require('moment'),
+ downsize = require('downsize'),
+ when = require('when'),
+ hbs = require('express-hbs'),
packageInfo = require('../../../package.json'),
- errors = require('../errorHandling'),
- models = require('../models'),
+ errors = require('../errorHandling'),
+ models = require('../models'),
coreHelpers;
coreHelpers = function (ghost) {
var paginationHelper,
scriptTemplate = _.template(""),
- isProduction = process.env.NODE_ENV === "production",
+ isProduction = process.env.NODE_ENV === 'production',
version = encodeURIComponent(packageInfo.version);
/**
@@ -34,7 +34,7 @@ coreHelpers = function (ghost) {
}
}
- var f = options.hash.format || "MMM Do, YYYY",
+ var f = options.hash.format || 'MMM Do, YYYY',
timeago = options.hash.timeago,
date;
@@ -76,7 +76,7 @@ coreHelpers = function (ghost) {
}
if (models.isPost(this)) {
- output += "/" + this.slug + '/';
+ output += '/' + this.slug + '/';
}
return output;
@@ -91,7 +91,7 @@ coreHelpers = function (ghost) {
// if the author could not be determined.
//
ghost.registerThemeHelper('author', function (context, options) {
- return this.author ? this.author.name : "";
+ return this.author ? this.author.name : '';
});
// ### Tags Helper
@@ -106,10 +106,10 @@ coreHelpers = function (ghost) {
// Note that the standard {{#each tags}} implementation is unaffected by this helper
// and can be used for more complex templates.
ghost.registerThemeHelper('tags', function (options) {
- var separator = ", ",
+ var separator = ', ',
tagNames;
- if (typeof options.hash.separator === "string") {
+ if (typeof options.hash.separator === 'string') {
separator = options.hash.separator;
}
@@ -133,7 +133,7 @@ coreHelpers = function (ghost) {
//
ghost.registerThemeHelper('content', function (options) {
var truncateOptions = (options || {}).hash || {};
- truncateOptions = _.pick(truncateOptions, ["words", "characters"]);
+ truncateOptions = _.pick(truncateOptions, ['words', 'characters']);
if (truncateOptions.words || truncateOptions.characters) {
return new hbs.handlebars.SafeString(
@@ -162,10 +162,10 @@ coreHelpers = function (ghost) {
var truncateOptions = (options || {}).hash || {},
excerpt;
- truncateOptions = _.pick(truncateOptions, ["words", "characters"]);
+ truncateOptions = _.pick(truncateOptions, ['words', 'characters']);
/*jslint regexp:true */
- excerpt = String(this.html).replace(/<\/?[^>]+>/gi, "");
+ excerpt = String(this.html).replace(/<\/?[^>]+>/gi, '');
/*jslint regexp:false */
if (!truncateOptions.words && !truncateOptions.characters) {
@@ -198,7 +198,7 @@ coreHelpers = function (ghost) {
var classes = ['post'];
if (this.tags) {
- classes = classes.concat(this.tags.map(function (tag) { return "tag-" + tag.name; }));
+ classes = classes.concat(this.tags.map(function (tag) { return 'tag-' + tag.name; }));
}
return ghost.doFilter('post_class', classes, function (classes) {
@@ -208,12 +208,15 @@ coreHelpers = function (ghost) {
});
ghost.registerThemeHelper('ghost_head', function (options) {
- var head = [];
- head.push('');
+ var head = [],
+ majorMinor = /^(\d+\.)?(\d+)/,
+ trimmedVersion = this.version.match(majorMinor)[0];
+
+ head.push('');
head.push('');
return ghost.doFilter('ghost_head', head, function (head) {
- var headString = _.reduce(head, function (memo, item) { return memo + "\n" + item; }, '');
+ var headString = _.reduce(head, function (memo, item) { return memo + '\n' + item; }, '');
return new hbs.handlebars.SafeString(headString.trim());
});
});
@@ -292,7 +295,7 @@ coreHelpers = function (ghost) {
j = 0,
columns = options.hash.columns,
key,
- ret = "",
+ ret = '',
data;
if (options.data) {
@@ -356,18 +359,18 @@ coreHelpers = function (ghost) {
});
// A helper for inserting the javascript tags with version hashes
- ghost.registerThemeHelper("ghostScriptTags", function () {
+ ghost.registerThemeHelper('ghostScriptTags', function () {
var scriptFiles = [];
if (isProduction) {
scriptFiles.push("ghost.min.js");
} else {
scriptFiles = [
- "vendor.js",
- "helpers.js",
- "templates.js",
- "models.js",
- "views.js"
+ 'vendor.js',
+ 'helpers.js',
+ 'templates.js',
+ 'models.js',
+ 'views.js'
];
}
@@ -378,7 +381,7 @@ coreHelpers = function (ghost) {
});
});
- return scriptFiles.join("");
+ return scriptFiles.join('');
});
// ## Template driven helpers
diff --git a/core/server/mail.js b/core/server/mail.js
index 541e173d8c..990bc0608d 100644
--- a/core/server/mail.js
+++ b/core/server/mail.js
@@ -1,8 +1,8 @@
-var cp = require('child_process'),
- url = require('url'),
- _ = require('underscore'),
- when = require('when'),
- nodefn = require('when/node/function'),
+var cp = require('child_process'),
+ url = require('url'),
+ _ = require('underscore'),
+ when = require('when'),
+ nodefn = require('when/node/function'),
nodemailer = require('nodemailer');
function GhostMailer(opts) {
@@ -51,7 +51,7 @@ GhostMailer.prototype.detectSendmail = function () {
if (err && !/bin\/sendmail/.test(stdout)) {
return reject();
}
- resolve(stdout.toString());
+ resolve(stdout.toString().replace(/(\n|\r|\r\n)$/, ''));
});
});
};
diff --git a/core/server/models/base.js b/core/server/models/base.js
index 24e9e37630..a8e2cd1638 100644
--- a/core/server/models/base.js
+++ b/core/server/models/base.js
@@ -1,10 +1,10 @@
var GhostBookshelf,
Bookshelf = require('bookshelf'),
- when = require('when'),
- moment = require('moment'),
- _ = require('underscore'),
- uuid = require('node-uuid'),
- config = require('../../../config'),
+ when = require('when'),
+ moment = require('moment'),
+ _ = require('underscore'),
+ uuid = require('node-uuid'),
+ config = require('../../../config'),
Validator = require('validator').Validator;
// Initializes Bookshelf as its own instance, so we can modify the Models and not mess up
@@ -70,7 +70,7 @@ GhostBookshelf.Model = GhostBookshelf.Model.extend({
}
_.each(relations, function (relation, key) {
- if (key.substring(0, 7) !== "_pivot_") {
+ if (key.substring(0, 7) !== '_pivot_') {
attrs[key] = relation.toJSON ? relation.toJSON() : relation;
}
});
@@ -126,7 +126,7 @@ GhostBookshelf.Model = GhostBookshelf.Model.extend({
//if slug is empty after trimming use "post"
if (!slug) {
- slug = "post";
+ slug = 'post';
}
// Test for duplicate slugs.
return checkIfSlugExists(slug);
diff --git a/core/server/models/index.js b/core/server/models/index.js
index 991fe3741f..c91d737bf4 100644
--- a/core/server/models/index.js
+++ b/core/server/models/index.js
@@ -16,7 +16,7 @@ module.exports = {
});
},
isPost: function (jsonData) {
- return jsonData.hasOwnProperty("html") && jsonData.hasOwnProperty("markdown")
- && jsonData.hasOwnProperty("title") && jsonData.hasOwnProperty("slug");
+ return jsonData.hasOwnProperty('html') && jsonData.hasOwnProperty('markdown')
+ && jsonData.hasOwnProperty('title') && jsonData.hasOwnProperty('slug');
}
};
diff --git a/core/server/models/permission.js b/core/server/models/permission.js
index 8fa0688356..ccd3ffeaf4 100644
--- a/core/server/models/permission.js
+++ b/core/server/models/permission.js
@@ -1,6 +1,6 @@
var GhostBookshelf = require('./base'),
- User = require('./user').User,
- Role = require('./role').Role,
+ User = require('./user').User,
+ Role = require('./role').Role,
Permission,
Permissions;
diff --git a/core/server/models/post.js b/core/server/models/post.js
index 8e6d8f6fd1..74a197c263 100644
--- a/core/server/models/post.js
+++ b/core/server/models/post.js
@@ -128,7 +128,7 @@ Post = GhostBookshelf.Model.extend({
}
});
- if (tagsToAttach) {
+ if (!_.isEmpty(tagsToAttach)) {
return Tags.forge().query('whereIn', 'name', _.pluck(tagsToAttach, 'name')).fetch().then(function (matchingTags) {
_.each(matchingTags.toJSON(), function (matchingTag) {
tagOperations.push(self.tags().attach(matchingTag.id));
@@ -173,7 +173,7 @@ Post = GhostBookshelf.Model.extend({
// Extends base model findAll to eager-fetch author and user relationships.
findAll: function (options) {
options = options || {};
- options.withRelated = [ "author", "user", "tags" ];
+ options.withRelated = [ 'author', 'user', 'tags' ];
return GhostBookshelf.Model.findAll.call(this, options);
},
@@ -181,7 +181,7 @@ Post = GhostBookshelf.Model.extend({
// Extends base model findOne to eager-fetch author and user relationships.
findOne: function (args, options) {
options = options || {};
- options.withRelated = [ "author", "user", "tags" ];
+ options.withRelated = [ 'author', 'user', 'tags' ];
return GhostBookshelf.Model.findOne.call(this, args, options);
},
@@ -235,7 +235,7 @@ Post = GhostBookshelf.Model.extend({
postCollection.query('where', opts.where);
}
- opts.withRelated = [ "author", "user", "tags" ];
+ opts.withRelated = [ 'author', 'user', 'tags' ];
// Set the limit & offset for the query, fetching
// with the opts (to specify any eager relations, etc.)
@@ -332,6 +332,19 @@ Post = GhostBookshelf.Model.extend({
// associated models can't be created until the post has an ID, so run this after
return post.updateTags(newPostData.tags);
});
+ },
+ destroy: function (_identifier, options) {
+ options = options || {};
+ return this.forge({id: _identifier}).fetch({withRelated: ['tags']}).then(function destroyTags(post) {
+ var tagIds = _.pluck(post.related('tags').toJSON(), 'id');
+ if (tagIds) {
+ return post.tags().detach(tagIds).then(function destroyPost() {
+ return post.destroy(options);
+ });
+ }
+
+ return post.destroy(options);
+ });
}
});
diff --git a/core/server/models/role.js b/core/server/models/role.js
index 2e960dc73f..6d0bd6962c 100644
--- a/core/server/models/role.js
+++ b/core/server/models/role.js
@@ -1,5 +1,5 @@
-var User = require('./user').User,
- Permission = require('./permission').Permission,
+var User = require('./user').User,
+ Permission = require('./permission').Permission,
GhostBookshelf = require('./base'),
Role,
Roles;
diff --git a/core/server/models/settings.js b/core/server/models/settings.js
index ba6206a46d..5faf9e301a 100644
--- a/core/server/models/settings.js
+++ b/core/server/models/settings.js
@@ -1,10 +1,10 @@
var Settings,
GhostBookshelf = require('./base'),
- validator = GhostBookshelf.validator,
- uuid = require('node-uuid'),
- _ = require('underscore'),
- errors = require('../errorHandling'),
- when = require('when'),
+ validator = GhostBookshelf.validator,
+ uuid = require('node-uuid'),
+ _ = require('underscore'),
+ errors = require('../errorHandling'),
+ when = require('when'),
defaultSettings;
// For neatness, the defaults file is split into categories.
diff --git a/core/server/models/tag.js b/core/server/models/tag.js
index 4f20e3af5f..a1d972c80b 100644
--- a/core/server/models/tag.js
+++ b/core/server/models/tag.js
@@ -1,6 +1,6 @@
var Tag,
Tags,
- Posts = require('./post').Posts,
+ Posts = require('./post').Posts,
GhostBookshelf = require('./base');
Tag = GhostBookshelf.Model.extend({
diff --git a/core/server/models/user.js b/core/server/models/user.js
index f2898459de..894a3df261 100644
--- a/core/server/models/user.js
+++ b/core/server/models/user.js
@@ -1,20 +1,20 @@
var User,
Users,
- _ = require('underscore'),
- uuid = require('node-uuid'),
- when = require('when'),
- errors = require('../errorHandling'),
- nodefn = require('when/node/function'),
- bcrypt = require('bcrypt-nodejs'),
- Posts = require('./post').Posts,
+ _ = require('underscore'),
+ uuid = require('node-uuid'),
+ when = require('when'),
+ errors = require('../errorHandling'),
+ nodefn = require('when/node/function'),
+ bcrypt = require('bcrypt-nodejs'),
+ Posts = require('./post').Posts,
GhostBookshelf = require('./base'),
- Role = require('./role').Role,
- Permission = require('./permission').Permission;
+ Role = require('./role').Role,
+ Permission = require('./permission').Permission;
function validatePasswordLength(password) {
try {
- GhostBookshelf.validator.check(password, "Your password is not long enough. It must be at least 8 characters long.").len(8);
+ GhostBookshelf.validator.check(password, "Your must be at least 8 characters long.").len(8);
} catch (error) {
return when.reject(error);
}
@@ -33,10 +33,10 @@ User = GhostBookshelf.Model.extend({
],
validate: function () {
- GhostBookshelf.validator.check(this.get('email'), "Please check your email address. It does not seem to be valid.").isEmail();
- GhostBookshelf.validator.check(this.get('bio'), "Your bio is too long. Please keep it to 200 chars.").len(0, 200);
+ GhostBookshelf.validator.check(this.get('email'), "Please enter a valid email address. That one looks a bit dodgy.").isEmail();
+ GhostBookshelf.validator.check(this.get('bio'), "We're not writing a novel here! I'm afraid your bio has to stay under 200 characters.").len(0, 200);
if (this.get('website') && this.get('website').length > 0) {
- GhostBookshelf.validator.check(this.get('website'), "Your website is not a valid URL.").isUrl();
+ GhostBookshelf.validator.check(this.get('website'), "Looks like your website is not actually a website. Try again?").isUrl();
}
return true;
},
diff --git a/core/server/permissions/index.js b/core/server/permissions/index.js
index fbd10a39ff..4059075acc 100644
--- a/core/server/permissions/index.js
+++ b/core/server/permissions/index.js
@@ -1,11 +1,11 @@
// canThis(someUser).edit.posts([id]|[[ids]])
// canThis(someUser).edit.post(somePost|somePostId)
-var _ = require('underscore'),
- when = require('when'),
- Models = require('../models'),
- objectTypeModelMap = require('./objectTypeModelMap'),
- UserProvider = Models.User,
+var _ = require('underscore'),
+ when = require('when'),
+ Models = require('../models'),
+ objectTypeModelMap = require('./objectTypeModelMap'),
+ UserProvider = Models.User,
PermissionsProvider = Models.Permission,
init,
refresh,
diff --git a/core/server/permissions/objectTypeModelMap.js b/core/server/permissions/objectTypeModelMap.js
index 74dd55e232..58efe95a69 100644
--- a/core/server/permissions/objectTypeModelMap.js
+++ b/core/server/permissions/objectTypeModelMap.js
@@ -1,7 +1,7 @@
module.exports = {
- 'post': require('../models/post').Post,
- 'role': require('../models/role').Role,
- 'user': require('../models/user').User,
+ 'post': require('../models/post').Post,
+ 'role': require('../models/role').Role,
+ 'user': require('../models/user').User,
'permission': require('../models/permission').Permission,
- 'setting': require('../models/settings').Settings
+ 'setting': require('../models/settings').Settings
};
diff --git a/core/server/plugins/index.js b/core/server/plugins/index.js
index c99e40c74d..468d0b9184 100644
--- a/core/server/plugins/index.js
+++ b/core/server/plugins/index.js
@@ -1,17 +1,17 @@
-var _ = require("underscore"),
- when = require('when'),
+var _ = require('underscore'),
+ when = require('when'),
ghostApi,
- loader = require("./loader"),
- GhostPlugin = require("./GhostPlugin");
+ loader = require('./loader'),
+ GhostPlugin = require('./GhostPlugin');
function getInstalledPlugins() {
if (!ghostApi) {
ghostApi = require('../api');
}
- return ghostApi.settings.read("installedPlugins").then(function (installed) {
- installed.value = installed.value || "[]";
+ return ghostApi.settings.read('installedPlugins').then(function (installed) {
+ installed.value = installed.value || '[]';
try {
installed = JSON.parse(installed.value);
@@ -27,7 +27,7 @@ function saveInstalledPlugins(installedPlugins) {
return getInstalledPlugins().then(function (currentInstalledPlugins) {
var updatedPluginsInstalled = _.uniq(installedPlugins.concat(currentInstalledPlugins));
- return ghostApi.settings.edit("installedPlugins", updatedPluginsInstalled);
+ return ghostApi.settings.edit('installedPlugins', updatedPluginsInstalled);
});
}
diff --git a/core/server/plugins/loader.js b/core/server/plugins/loader.js
index a26797a2da..26e3f0035b 100644
--- a/core/server/plugins/loader.js
+++ b/core/server/plugins/loader.js
@@ -1,7 +1,7 @@
-var path = require("path"),
- _ = require("underscore"),
- when = require("when"),
+var path = require('path'),
+ _ = require('underscore'),
+ when = require('when'),
ghostInstance,
loader;
@@ -10,7 +10,7 @@ function getGhostInstance() {
return ghostInstance;
}
- var Ghost = require("../../ghost");
+ var Ghost = require('../../ghost');
ghostInstance = new Ghost();
diff --git a/core/server/require-tree.js b/core/server/require-tree.js
index dc4238b89b..902e860511 100644
--- a/core/server/require-tree.js
+++ b/core/server/require-tree.js
@@ -1,7 +1,7 @@
-var when = require('when'),
- keys = require('when/keys'),
- fs = require('fs'),
- path = require('path'),
+var when = require('when'),
+ keys = require('when/keys'),
+ fs = require('fs'),
+ path = require('path'),
extend = function (obj, source) {
var key;
for (key in source) {
diff --git a/core/server/views/debug.hbs b/core/server/views/debug.hbs
index b309fbb623..841e828aeb 100644
--- a/core/server/views/debug.hbs
+++ b/core/server/views/debug.hbs
@@ -34,15 +34,6 @@
-
\ No newline at end of file
diff --git a/core/server/views/partials/navbar.hbs b/core/server/views/partials/navbar.hbs
index 76d66dab93..ab4478f1b9 100644
--- a/core/server/views/partials/navbar.hbs
+++ b/core/server/views/partials/navbar.hbs
@@ -9,13 +9,13 @@
-
- {{#if currentUser.name}}{{currentUser.name}}{{else}}Ghost{{/if}}
+
+ {{#if currentUser.name}}{{currentUser.name}}{{else}}{{currentUser.email}}{{/if}}
diff --git a/core/shared/vendor/showdown/extensions/github.js b/core/shared/vendor/showdown/extensions/github.js
index 07db047f10..f0a8b1bfea 100644
--- a/core/shared/vendor/showdown/extensions/github.js
+++ b/core/shared/vendor/showdown/extensions/github.js
@@ -16,10 +16,10 @@
}
},
{
- // GFM newline and underscore modifications
+ // GFM newline and underscore modifications, happen BEFORE showdown
type : 'lang',
filter : function (text) {
- var extractions = {},
+ var preExtractions = {},
imageMarkdownRegex = /^(?:\{(.*?)\})?!(?:\[([^\n\]]*)\])(?:\(([^\n\]]*)\))?$/gim,
hashID = 0;
@@ -30,15 +30,10 @@
// Extract pre blocks
text = text.replace(/[\s\S]*?<\/pre>/gim, function (x) {
var hash = hashId();
- extractions[hash] = x;
+ preExtractions[hash] = x;
return "{gfm-js-extract-pre-" + hash + "}";
}, 'm');
- // better URL support, but no title support
- text = text.replace(imageMarkdownRegex, function (match, key, alt, src) {
- return '';
- });
-
//prevent foo_bar and foo_bar_baz from ending up with an italic word in the middle
text = text.replace(/(^(?! {4}|\t)\w+_\w+_\w[\w_]*)/gm, function (x) {
return x.replace(/_/gm, '\\_');
@@ -49,8 +44,17 @@
return x.match(/\n{2}/) ? x : x.trim() + " \n";
});
+ // better URL support, but no title support
+ text = text.replace(imageMarkdownRegex, function (match, key, alt, src) {
+ if (src) {
+ return '';
+ }
+
+ return '';
+ });
+
text = text.replace(/\{gfm-js-extract-pre-([0-9]+)\}/gm, function (x, y) {
- return "\n\n" + extractions[y];
+ return "\n\n" + preExtractions[y];
});
@@ -58,22 +62,30 @@
}
},
{
- // Auto-link URLs and emails
- type : 'lang',
+ // GFM autolinking & custom image handling, happens AFTER showdown
+ type : 'html',
filter : function (text) {
- var extractions = {},
+ var refExtractions = {},
+ preExtractions = {},
hashID = 0;
function hashId() {
return hashID++;
}
+ // Extract pre blocks
+ text = text.replace(/<(pre|code)>[\s\S]*?<\/(\1)>/gim, function (x) {
+ var hash = hashId();
+ preExtractions[hash] = x;
+ return "{gfm-js-extract-pre-" + hash + "}";
+ }, 'm');
+
// filter out def urls
// from Marked https://github.com/chjj/marked/blob/master/lib/marked.js#L24
text = text.replace(/^ *\[([^\]]+)\]: *([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/gmi,
function (x) {
var hash = hashId();
- extractions[hash] = x;
+ refExtractions[hash] = x;
return "{gfm-js-extract-ref-url-" + hash + "}";
});
@@ -91,13 +103,18 @@
return lookBehind ? wholeMatch : "" + wholeMatch + "";
});
- // match emil
+ // match email
text = text.replace(/[a-z0-9_\-+=.]+@[a-z0-9\-]+(\.[a-z0-9-]+)+/gmi, function (wholeMatch) {
return "" + wholeMatch + "";
});
+ // replace extractions
+ text = text.replace(/\{gfm-js-extract-pre-([0-9]+)\}/gm, function (x, y) {
+ return preExtractions[y];
+ });
+
text = text.replace(/\{gfm-js-extract-ref-url-([0-9]+)\}/gi, function (x, y) {
- return "\n\n" + extractions[y];
+ return "\n\n" + refExtractions[y];
});
return text;
diff --git a/core/test/unit/admin_spec.js b/core/test/unit/admin_spec.js
index 0366fd7210..5ad8bac87a 100644
--- a/core/test/unit/admin_spec.js
+++ b/core/test/unit/admin_spec.js
@@ -146,6 +146,11 @@ describe('Admin Controller', function() {
fs.exists.withArgs('content/images/2013/Sep/IMAGE.jpg').yields(true);
fs.exists.withArgs('content/images/2013/Sep/IMAGE-1.jpg').yields(false);
+ // if on windows need to setup with back slashes
+ // doesn't hurt for the test to cope with both
+ fs.exists.withArgs('content\\images\\2013\\Sep\\IMAGE.jpg').yields(true);
+ fs.exists.withArgs('content\\images\\2013\\Sep\\IMAGE-1.jpg').yields(false);
+
sinon.stub(res, 'send', function(data) {
data.should.equal('/content/images/2013/Sep/IMAGE-1.jpg');
return done();
@@ -163,6 +168,13 @@ describe('Admin Controller', function() {
fs.exists.withArgs('content/images/2013/Sep/IMAGE-3.jpg').yields(true);
fs.exists.withArgs('content/images/2013/Sep/IMAGE-4.jpg').yields(false);
+ // windows setup
+ fs.exists.withArgs('content\\images\\2013\\Sep\\IMAGE.jpg').yields(true);
+ fs.exists.withArgs('content\\images\\2013\\Sep\\IMAGE-1.jpg').yields(true);
+ fs.exists.withArgs('content\\images\\2013\\Sep\\IMAGE-2.jpg').yields(true);
+ fs.exists.withArgs('content\\images\\2013\\Sep\\IMAGE-3.jpg').yields(true);
+ fs.exists.withArgs('content\\images\\2013\\Sep\\IMAGE-4.jpg').yields(false);
+
sinon.stub(res, 'send', function(data) {
data.should.equal('/content/images/2013/Sep/IMAGE-4.jpg');
return done();
diff --git a/core/test/unit/api_posts_spec.js b/core/test/unit/api_posts_spec.js
index d1dac21be7..6731d9222b 100644
--- a/core/test/unit/api_posts_spec.js
+++ b/core/test/unit/api_posts_spec.js
@@ -1,7 +1,7 @@
/*globals describe, before, beforeEach, afterEach, it */
var testUtils = require('./testUtils'),
should = require('should'),
- _ = require("underscore"),
+ _ = require('underscore'),
when = require('when'),
sequence = require('when/sequence'),
@@ -14,8 +14,8 @@ describe('Post Model', function () {
UserModel = Models.User,
userData = {
password: 'testpass1',
- email: "test@test1.com",
- name: "Mr Biscuits"
+ email: 'test@test1.com',
+ name: 'Mr Biscuits'
};
before(function (done) {
@@ -79,10 +79,10 @@ describe('Post Model', function () {
results.length.should.be.above(0);
firstPost = results.models[0].toJSON();
- firstPost.author.should.be.a("object");
- firstPost.user.should.be.a("object");
- firstPost.author.name.should.equal("Mr Biscuits");
- firstPost.user.name.should.equal("Mr Biscuits");
+ firstPost.author.should.be.a('object');
+ firstPost.user.should.be.a('object');
+ firstPost.author.name.should.equal('Mr Biscuits');
+ firstPost.user.name.should.equal('Mr Biscuits');
done();
}, done);
@@ -95,10 +95,10 @@ describe('Post Model', function () {
should.exist(result);
firstPost = result.toJSON();
- firstPost.author.should.be.a("object");
- firstPost.user.should.be.a("object");
- firstPost.author.name.should.equal("Mr Biscuits");
- firstPost.user.name.should.equal("Mr Biscuits");
+ firstPost.author.should.be.a('object');
+ firstPost.user.should.be.a('object');
+ firstPost.author.name.should.equal('Mr Biscuits');
+ firstPost.user.name.should.equal('Mr Biscuits');
done();
}, done);
@@ -112,7 +112,7 @@ describe('Post Model', function () {
results.length.should.be.above(0);
firstPost = results.models[0];
- return PostModel.edit({id: firstPost.id, title: "new title"});
+ return PostModel.edit({id: firstPost.id, title: 'new title'});
}).then(function (edited) {
should.exist(edited);
edited.attributes.title.should.equal('new title');
@@ -134,16 +134,16 @@ describe('Post Model', function () {
should.exist(createdPost);
createdPost.has('uuid').should.equal(true);
createdPost.get('status').should.equal('draft');
- createdPost.get('title').should.equal(newPost.title, "title is correct");
- createdPost.get('markdown').should.equal(newPost.markdown, "markdown is correct");
+ createdPost.get('title').should.equal(newPost.title, 'title is correct');
+ createdPost.get('markdown').should.equal(newPost.markdown, 'markdown is correct');
createdPost.has('html').should.equal(true);
createdPost.get('html').should.equal('' + newPost.markdown + '
');
createdPost.get('slug').should.equal('test-title-1');
- createdPost.get('created_at').should.be.below(new Date().getTime()).and.be.above(new Date(0).getTime());
+ createdPost.get('created_at').should.be.above(new Date(0).getTime());
createdPost.get('created_by').should.equal(1);
createdPost.get('author_id').should.equal(1);
createdPost.get('created_by').should.equal(createdPost.get('author_id'));
- createdPost.get('updated_at').should.be.below(new Date().getTime()).and.be.above(new Date(0).getTime());
+ createdPost.get('updated_at').should.be.above(new Date(0).getTime());
createdPost.get('updated_by').should.equal(1);
should.equal(createdPost.get('published_at'), null);
should.equal(createdPost.get('published_by'), null);
@@ -198,8 +198,8 @@ describe('Post Model', function () {
sequence(_.times(12, function (i) {
return function () {
return PostModel.add({
- title: "Test Title",
- markdown: "Test Content " + (i+1)
+ title: 'Test Title',
+ markdown: 'Test Content ' + (i+1)
});
};
})).then(function (createdPosts) {
@@ -224,7 +224,6 @@ describe('Post Model', function () {
}).otherwise(done);
});
-
it('can generate slugs without duplicate hyphens', function (done) {
var newPost = {
title: 'apprehensive titles have too many spaces ',
@@ -239,6 +238,53 @@ describe('Post Model', function () {
}).then(null, done);
});
+ it('detects duplicate slugs before saving', function (done) {
+ var firstPost = {
+ title: 'First post',
+ markdown: 'First content 1'
+ },
+ secondPost = {
+ title: 'Second post',
+ markdown: 'Second content 1'
+ };
+
+ // Create the first post
+ PostModel.add(firstPost)
+ .then(function (createdFirstPost) {
+ // Store the slug for later
+ firstPost.slug = createdFirstPost.get('slug');
+
+ // Create the second post
+ return PostModel.add(secondPost);
+ }).then(function (createdSecondPost) {
+ // Store the slug for comparison later
+ secondPost.slug = createdSecondPost.get('slug');
+
+ // Update with a conflicting slug from the first post
+ return createdSecondPost.save({
+ slug: firstPost.slug
+ });
+ }).then(function (updatedSecondPost) {
+
+ // Should have updated from original
+ updatedSecondPost.get('slug').should.not.equal(secondPost.slug);
+ // Should not have a conflicted slug from the first
+ updatedSecondPost.get('slug').should.not.equal(firstPost.slug);
+
+ return PostModel.read({
+ id: updatedSecondPost.id
+ });
+ }).then(function (foundPost) {
+
+ // Should have updated from original
+ foundPost.get('slug').should.not.equal(secondPost.slug);
+ // Should not have a conflicted slug from the first
+ foundPost.get('slug').should.not.equal(firstPost.slug);
+
+ done();
+ }).otherwise(done);
+ });
+
it('can delete', function (done) {
var firstPostId;
PostModel.browse().then(function (results) {
@@ -252,7 +298,7 @@ describe('Post Model', function () {
}).then(function (newResults) {
var ids, hasDeletedId;
- ids = _.pluck(newResults.models, "id");
+ ids = _.pluck(newResults.models, 'id');
hasDeletedId = _.any(ids, function (id) {
return id === firstPostId;
});
@@ -262,6 +308,25 @@ describe('Post Model', function () {
}).then(null, done);
});
+ it('can create a new Post with a previous published_at date', function (done) {
+ var previousPublishedAtDate = new Date(2013, 8, 21, 12);
+
+ PostModel.add({
+ status: 'published',
+ published_at: previousPublishedAtDate,
+ title: 'published_at test',
+ markdown: 'This is some content'
+ }).then(function (newPost) {
+
+ should.exist(newPost);
+
+ newPost.get('published_at').should.equal(previousPublishedAtDate);
+
+ done();
+
+ }).otherwise(done);
+ });
+
it('can fetch a paginated set, with various options', function (done) {
this.timeout(10000); // this is a patch to ensure it doesn't timeout.
diff --git a/core/test/unit/api_settings_spec.js b/core/test/unit/api_settings_spec.js
index adbe1aca8b..ff8351ce21 100644
--- a/core/test/unit/api_settings_spec.js
+++ b/core/test/unit/api_settings_spec.js
@@ -76,13 +76,7 @@ describe('Settings Model', function () {
results.length.should.be.above(0);
- firstSetting = results.models[1];
-
- // The edit method has been modified to take an object of
- // key/value pairs
- firstSetting.set('value', 'new value');
-
- return SettingsModel.edit(firstSetting);
+ return SettingsModel.edit({key: "description", value: "new value"});
}).then(function (edited) {
@@ -92,7 +86,7 @@ describe('Settings Model', function () {
edited = edited[0];
- edited.attributes.key.should.equal(firstSetting.attributes.key);
+ edited.attributes.key.should.equal('description');
edited.attributes.value.should.equal('new value');
done();
@@ -111,13 +105,8 @@ describe('Settings Model', function () {
results.length.should.be.above(0);
- model1 = results.models[1];
- model2 = results.models[2];
-
- // The edit method has been modified to take an object of
- // key/value pairs
- model1.set('value', 'new value1');
- model2.set('value', 'new value2');
+ model1 = {key: "description", value: "another new value"};
+ model2 = {key: "title", value: "new title"};
return SettingsModel.edit([model1, model2]);
@@ -129,13 +118,13 @@ describe('Settings Model', function () {
editedModel = edited[0];
- editedModel.attributes.key.should.equal(model1.attributes.key);
- editedModel.attributes.value.should.equal('new value1');
+ editedModel.attributes.key.should.equal(model1.key);
+ editedModel.attributes.value.should.equal(model1.value);
editedModel = edited[1];
- editedModel.attributes.key.should.equal(model2.attributes.key);
- editedModel.attributes.value.should.equal('new value2');
+ editedModel.attributes.key.should.equal(model2.key);
+ editedModel.attributes.value.should.equal(model2.value);
done();
@@ -205,7 +194,6 @@ describe('Settings Model', function () {
it('populates any unset settings from the JSON defaults', function (done) {
SettingsModel.findAll().then(function (allSettings) {
- console.log(allSettings.models)
allSettings.length.should.equal(0);
return SettingsModel.populateDefaults();
}).then(function () {
diff --git a/core/test/unit/api_tags_spec.js b/core/test/unit/api_tags_spec.js
index 3acec15709..072db66b9d 100644
--- a/core/test/unit/api_tags_spec.js
+++ b/core/test/unit/api_tags_spec.js
@@ -130,7 +130,7 @@ describe('Tag Model', function () {
return postModel.save();
}).then(function (postModel) {
var tagNames = postModel.related('tags').models.map(function (t) { return t.attributes.name; });
- tagNames.should.eql(seededTagNames);
+ tagNames.sort().should.eql(seededTagNames);
return TagModel.findAll();
}).then(function (tagsFromDB) {
@@ -155,7 +155,7 @@ describe('Tag Model', function () {
return PostModel.read({id: postModel.id}, { withRelated: ['tags']});
}).then(function (reloadedPost) {
var tagNames = reloadedPost.related('tags').models.map(function (t) { return t.attributes.name; });
- tagNames.should.eql(['tag1', 'tag3']);
+ tagNames.sort().should.eql(['tag1', 'tag3']);
done();
}).then(null, done);
@@ -180,7 +180,7 @@ describe('Tag Model', function () {
}).then(function (reloadedPost) {
var tagModels = reloadedPost.related('tags').models,
tagNames = tagModels.map(function (t) { return t.attributes.name; });
- tagNames.should.eql(['tag1', 'tag2', 'tag3']);
+ tagNames.sort().should.eql(['tag1', 'tag2', 'tag3']);
tagModels[2].id.should.eql(4); // make sure it hasn't just added a new tag with the same name
done();
@@ -201,7 +201,7 @@ describe('Tag Model', function () {
return PostModel.read({id: postModel.id}, { withRelated: ['tags']});
}).then(function (reloadedPost) {
var tagNames = reloadedPost.related('tags').models.map(function (t) { return t.attributes.name; });
- tagNames.should.eql(['tag1', 'tag2', 'tag3']);
+ tagNames.sort().should.eql(['tag1', 'tag2', 'tag3']);
done();
}).then(null, done);
diff --git a/core/test/unit/client_showdown_int_spec.js b/core/test/unit/client_showdown_int_spec.js
index 1b427c2f2b..ff95c524b5 100644
--- a/core/test/unit/client_showdown_int_spec.js
+++ b/core/test/unit/client_showdown_int_spec.js
@@ -178,7 +178,7 @@ describe("Showdown client side converter", function () {
},
{
input: "# http://google.co.uk",
- output: /^http:\/\/google.co.uk<\/a><\/h1>$/
+ output: /^http:\/\/google.co.uk<\/a><\/h1>$/
},
{
input: "* http://google.co.uk",
@@ -279,6 +279,30 @@ describe("Showdown client side converter", function () {
});
});
+ it("should NOT auto-link URLS inside of code/pre blocks", function () {
+ var testPhrases = [
+ {
+ input: "```\nurl: http://google.co.uk\n```",
+ output: /^url: http:\/\/google.co.uk \n<\/code><\/pre>$/
+ },
+ {
+ input: "`url: http://google.co.uk`",
+ output: /^url: http:\/\/google.co.uk<\/code><\/p>$/
+ },
+ {
+ input: "Hello type some `url: http://google.co.uk` stuff",
+ output: /^Hello type some url: http:\/\/google.co.uk<\/code> stuff<\/p>$/
+ }
+
+ ],
+ processedMarkup;
+
+ testPhrases.forEach(function (testPhrase) {
+ processedMarkup = converter.makeHtml(testPhrase.input);
+ processedMarkup.should.match(testPhrase.output);
+ });
+ });
+
it("should not display anything for reference URL", function () {
var testPhrases = [
{
diff --git a/core/test/unit/ghost_spec.js b/core/test/unit/ghost_spec.js
index fdeb029e49..13cb58fa45 100644
--- a/core/test/unit/ghost_spec.js
+++ b/core/test/unit/ghost_spec.js
@@ -12,6 +12,7 @@ var testUtils = require('./testUtils'),
describe("Ghost API", function () {
var testTemplatePath = 'core/test/unit/fixtures/',
themeTemplatePath = 'core/test/unit/fixtures/theme',
+ sandbox,
ghost;
before(function (done) {
@@ -21,23 +22,26 @@ describe("Ghost API", function () {
});
beforeEach(function (done) {
+ sandbox = sinon.sandbox.create();
+
testUtils.initData().then(function () {
ghost = new Ghost();
done();
}, done);
});
- it("is a singleton", function () {
- var logStub = sinon.stub(console, "log"),
- ghost1 = new Ghost(),
- ghost2 = new Ghost();
+ afterEach(function () {
+ sandbox.restore();
+ });
- should.strictEqual(ghost1, ghost2);
- logStub.restore();
+ it("is a singleton", function () {
+ var ghost2 = new Ghost();
+
+ should.strictEqual(ghost, ghost2);
});
it("uses init() to initialize", function (done) {
- var dataProviderInitMock = sinon.stub(ghost.dataProvider, "init", function () {
+ var dataProviderInitMock = sandbox.stub(ghost.dataProvider, "init", function () {
return when.resolve();
});
@@ -49,8 +53,6 @@ describe("Ghost API", function () {
dataProviderInitMock.called.should.equal(true);
- dataProviderInitMock.restore();
-
done();
}, done);
@@ -59,7 +61,7 @@ describe("Ghost API", function () {
it("can register filters with specific priority", function () {
var filterName = 'test',
filterPriority = 9,
- testFilterHandler = sinon.spy();
+ testFilterHandler = sandbox.spy();
ghost.registerFilter(filterName, filterPriority, testFilterHandler);
@@ -72,7 +74,7 @@ describe("Ghost API", function () {
it("can register filters with default priority", function () {
var filterName = 'test',
defaultPriority = 5,
- testFilterHandler = sinon.spy();
+ testFilterHandler = sandbox.spy();
ghost.registerFilter(filterName, testFilterHandler);
@@ -84,9 +86,9 @@ describe("Ghost API", function () {
it("executes filters in priority order", function (done) {
var filterName = 'testpriority',
- testFilterHandler1 = sinon.spy(),
- testFilterHandler2 = sinon.spy(),
- testFilterHandler3 = sinon.spy();
+ testFilterHandler1 = sandbox.spy(),
+ testFilterHandler2 = sandbox.spy(),
+ testFilterHandler3 = sandbox.spy();
ghost.registerFilter(filterName, 0, testFilterHandler1);
ghost.registerFilter(filterName, 2, testFilterHandler2);
@@ -118,13 +120,13 @@ describe("Ghost API", function () {
});
it("loads templates for helpers", function (done) {
- var compileSpy = sinon.spy(ghost, 'compileTemplate'),
+ var compileSpy = sandbox.spy(ghost, 'compileTemplate'),
pathsStub;
should.exist(ghost.loadTemplate, 'load template function exists');
// In order for the test to work, need to replace the path to the template
- pathsStub = sinon.stub(ghost, "paths", function () {
+ pathsStub = sandbox.stub(ghost, "paths", function () {
return {
// Forcing the theme path to be the same
activeTheme: path.join(process.cwd(), testTemplatePath),
@@ -145,20 +147,18 @@ describe("Ghost API", function () {
templateFn().should.equal('HelloWorld
');
-
-
done();
}).then(null, done);
});
it("loads templates from themes first", function (done) {
- var compileSpy = sinon.spy(ghost, 'compileTemplate'),
+ var compileSpy = sandbox.spy(ghost, 'compileTemplate'),
pathsStub;
should.exist(ghost.loadTemplate, 'load template function exists');
// In order for the test to work, need to replace the path to the template
- pathsStub = sinon.stub(ghost, "paths", function () {
+ pathsStub = sandbox.stub(ghost, "paths", function () {
return {
activeTheme: path.join(process.cwd(), themeTemplatePath),
helperTemplates: path.join(process.cwd(), testTemplatePath)
diff --git a/core/test/unit/import_spec.js b/core/test/unit/import_spec.js
index d52370e295..bd23779a6c 100644
--- a/core/test/unit/import_spec.js
+++ b/core/test/unit/import_spec.js
@@ -1,202 +1,98 @@
-///*globals describe, beforeEach, it*/
-//var testUtils = require('./testUtils'),
-// should = require('should'),
-// sinon = require('sinon'),
-// when = require('when'),
-// _ = require("underscore"),
-// errors = require('../../server/errorHandling'),
-//
-// // Stuff we are testing
-// knex = require("../../server/models/base").Knex,
-// migration = require('../../server/data/migration'),
-// exporter = require('../../server/data/export'),
-// importer = require('../../server/data/import'),
-// Importer001 = require('../../server/data/import/001'),
-// Importer002 = require('../../server/data/import/002'),
-// Settings = require('../../server/models/settings').Settings;
-//
-//describe("Import", function () {
-//
-// should.exist(exporter);
-// should.exist(importer);
-//
-// beforeEach(function (done) {
-// // clear database... we need to initialise it manually for each test
-// testUtils.clearData().then(function () {
-// done();
-// }, done);
-// });
-//
-// it("resolves 001", function (done) {
-// var importStub = sinon.stub(Importer001, "importData", function () {
-// return when.resolve();
-// }),
-// fakeData = { test: true };
-//
-// importer("001", fakeData).then(function () {
-// importStub.calledWith(fakeData).should.equal(true);
-//
-// importStub.restore();
-//
-// done();
-// }).then(null, done);
-// });
-//
-// describe("001", function () {
-// this.timeout(4000);
-//
-// should.exist(Importer001);
-//
-// it("imports data from 001", function (done) {
-// var exportData;
-//
-// // initialise database to version 001 - confusingly we have to set the max version to be one higher
-// // than the migration version we want
-// migration.migrateUpFromVersion('001', '002').then(function () {
-// return Settings.populateDefaults();
-// }).then(function () {
-// // export the version 001 data ready to import
-// // TODO: Should have static test data here?
-// return exporter("001");
-// }).then(function (exported) {
-// exportData = exported;
-//
-// // Version 001 exporter required the database be empty...
-// var tables = [
-// 'posts', 'users', 'roles', 'roles_users', 'permissions', 'permissions_roles',
-// 'settings'
-// ],
-// truncateOps = _.map(tables, function (name) {
-// return knex(name).truncate();
-// });
-//
-// return when.all(truncateOps);
-// }).then(function () {
-// return importer("001", exportData);
-// }).then(function () {
-// // Grab the data from tables
-// return when.all([
-// knex("users").select(),
-// knex("posts").select(),
-// knex("settings").select()
-// ]);
-// }).then(function (importedData) {
-//
-// should.exist(importedData);
-// importedData.length.should.equal(3);
-//
-// // we always have 0 users as there isn't one in fixtures
-// importedData[0].length.should.equal(0);
-// importedData[1].length.should.equal(exportData.data.posts.length);
-// importedData[2].length.should.be.above(0);
-//
-// _.findWhere(exportData.data.settings, {key: "databaseVersion"}).value.should.equal("001");
-//
-// done();
-// }).then(null, done);
-// });
-// });
-//
-// it("resolves 002", function (done) {
-// var importStub = sinon.stub(Importer002, "importData", function () {
-// return when.resolve();
-// }),
-// fakeData = { test: true };
-//
-// importer("002", fakeData).then(function () {
-// importStub.calledWith(fakeData).should.equal(true);
-//
-// importStub.restore();
-//
-// done();
-// }).then(null, done);
-// });
-//
-// describe("002", function () {
-// this.timeout(4000);
-//
-// should.exist(Importer002);
-//
-// it("imports data from 001", function (done) {
-// var exportData;
-//
-// // initialise database to version 001 - confusingly we have to set the max version to be one higher
-// // than the migration version we want
-// migration.migrateUpFromVersion('001', '002').then(function () {
-// return Settings.populateDefaults();
-// }).then(function () {
-// // export the version 001 data ready to import
-// // TODO: Should have static test data here?
-// return exporter("001");
-// }).then(function (exported) {
-// exportData = exported;
-//
-// // now migrate up to the proper version ready for importing - confusingly we have to set the max version
-// // to be one higher than the migration version we want
-// return migration.migrateUpFromVersion('002', '003');
-// }).then(function () {
-// return importer("002", exportData);
-// }).then(function () {
-// // Grab the data from tables
-// return when.all([
-// knex("users").select(),
-// knex("posts").select(),
-// knex("settings").select()
-// ]);
-// }).then(function (importedData) {
-//
-// should.exist(importedData);
-// importedData.length.should.equal(3);
-//
-// // we always have 0 users as there isn't one in fixtures
-// importedData[0].length.should.equal(0);
-// // import no longer requires all data to be dropped, and adds posts
-// importedData[1].length.should.equal(exportData.data.posts.length + 1);
-// importedData[2].length.should.be.above(0);
-//
-// _.findWhere(importedData[2], {key: "databaseVersion"}).value.should.equal("002");
-//
-// done();
-// }).then(null, done);
-// });
-//
-// it("imports data from 002", function (done) {
-// var exportData;
-//
-// // initialise database to version 001 - confusingly we have to set the max version to be one higher
-// // than the migration version we want
-// migration.migrateUpFromVersion('001', '003').then(function () {
-// return Settings.populateDefaults();
-// }).then(function () {
-// // export the version 002 data ready to import
-// // TODO: Should have static test data here?
-// return exporter("002");
-// }).then(function (exported) {
-// exportData = exported;
-//
-// return importer("002", exportData);
-// }).then(function () {
-// // Grab the data from tables
-// return when.all([
-// knex("users").select(),
-// knex("posts").select(),
-// knex("settings").select()
-// ]);
-// }).then(function (importedData) {
-//
-// should.exist(importedData);
-// importedData.length.should.equal(3);
-//
-// // we always have 0 users as there isn't one in fixtures
-// importedData[0].length.should.equal(0);
-// // import no longer requires all data to be dropped, and adds posts
-// importedData[1].length.should.equal(exportData.data.posts.length + 1);
-// importedData[2].length.should.be.above(0);
-//
-// _.findWhere(importedData[2], {key: "databaseVersion"}).value.should.equal("002");
-//
-// done();
-// }).then(null, done);
-// });
-// });
-//});
+/*globals describe, beforeEach, it*/
+var testUtils = require('./testUtils'),
+ should = require('should'),
+ sinon = require('sinon'),
+ when = require('when'),
+ _ = require("underscore"),
+ errors = require('../../server/errorHandling'),
+
+ // Stuff we are testing
+ knex = require("../../server/models/base").Knex,
+ migration = require('../../server/data/migration'),
+ exporter = require('../../server/data/export'),
+ importer = require('../../server/data/import'),
+ Importer000 = require('../../server/data/import/000'),
+ fixtures = require('../../server/data/fixtures'),
+ Settings = require('../../server/models/settings').Settings;
+
+describe("Import", function () {
+
+ should.exist(exporter);
+ should.exist(importer);
+
+ beforeEach(function (done) {
+ // clear database... we need to initialise it manually for each test
+ testUtils.clearData().then(function () {
+ done();
+ }, done);
+ });
+
+ it("resolves 000", function (done) {
+ var importStub = sinon.stub(Importer000, "importData", function () {
+ return when.resolve();
+ }),
+ fakeData = { test: true };
+
+ importer("000", fakeData).then(function () {
+ importStub.calledWith(fakeData).should.equal(true);
+
+ importStub.restore();
+
+ done();
+ }).then(null, done);
+ });
+
+ describe("000", function () {
+ this.timeout(4000);
+
+ should.exist(Importer000);
+
+ it("imports data from 000", function (done) {
+ var exportData;
+
+ // initialise database to version 000 - confusingly we have to set the max version to be one higher
+ // than the migration version we want. Could just use migrate from fresh here... but this is more explicit
+ migration.migrateUpFromVersion('000', '001').then(function () {
+ // Load the fixtures
+ return fixtures.populateFixtures();
+ }).then(function () {
+ // Initialise the default settings
+ return Settings.populateDefaults();
+ }).then(function () {
+ // export the version 000 data ready to import
+ // TODO: Should have static test data here?
+ return exporter();
+ }).then(function (exported) {
+ exportData = exported;
+
+ return importer("000", exportData);
+ }).then(function () {
+ // Grab the data from tables
+ return when.all([
+ knex("users").select(),
+ knex("posts").select(),
+ knex("settings").select(),
+ knex("tags").select()
+ ]);
+ }).then(function (importedData) {
+
+ should.exist(importedData);
+ importedData.length.should.equal(4, 'Did not get data successfully');
+
+ // we always have 0 users as there isn't one in fixtures
+ importedData[0].length.should.equal(0, 'There should not be a user');
+ // import no longer requires all data to be dropped, and adds posts
+ importedData[1].length.should.equal(exportData.data.posts.length + 1, 'Wrong number of posts');
+
+ // test settings
+ importedData[2].length.should.be.above(0, 'Wrong number of settings');
+ _.findWhere(importedData[2], {key: "databaseVersion"}).value.should.equal("000", 'Wrong database version');
+
+ // test tags
+ importedData[3].length.should.equal(exportData.data.tags.length, 'no new tags');
+
+ done();
+ }).then(null, done);
+ });
+ });
+});
diff --git a/core/test/unit/permissions_spec.js b/core/test/unit/permissions_spec.js
index 112d20f4ec..468e8aab7f 100644
--- a/core/test/unit/permissions_spec.js
+++ b/core/test/unit/permissions_spec.js
@@ -94,7 +94,7 @@ describe('permissions', function () {
.then(function (actionsMap) {
should.exist(actionsMap);
- actionsMap.edit.should.eql(['post', 'tag', 'user', 'page']);
+ actionsMap.edit.sort().should.eql(['post', 'tag', 'user', 'page'].sort());
actionsMap.should.equal(permissions.actionsMap);
diff --git a/core/test/unit/server_helpers_index_spec.js b/core/test/unit/server_helpers_index_spec.js
index 567e335db2..5499f30c4a 100644
--- a/core/test/unit/server_helpers_index_spec.js
+++ b/core/test/unit/server_helpers_index_spec.js
@@ -189,10 +189,16 @@ describe('Core Helpers', function () {
});
it('returns meta tag string', function () {
- var rendered = handlebars.helpers.ghost_head.call({version: "0.3"});
+ var rendered = handlebars.helpers.ghost_head.call({version: "0.3.0"});
should.exist(rendered);
rendered.string.should.equal('\n');
});
+
+ it('returns meta tag string even if version is invalid', function () {
+ var rendered = handlebars.helpers.ghost_head.call({version: "0.9"});
+ should.exist(rendered);
+ rendered.string.should.equal('\n');
+ });
});
describe('ghost_foot Helper', function () {
diff --git a/core/test/unit/shared_gfm_spec.js b/core/test/unit/shared_gfm_spec.js
index f657f2abf3..1f7699f790 100644
--- a/core/test/unit/shared_gfm_spec.js
+++ b/core/test/unit/shared_gfm_spec.js
@@ -252,4 +252,19 @@ describe("Github showdown extensions", function () {
});
});
+ it("should not output image if there is no src", function () {
+ var testPhrases = [
+ {
+ input: "![anything here]()",
+ output: /^$/
+ }
+ ],
+ processedMarkup;
+
+ testPhrases.forEach(function (testPhrase) {
+ processedMarkup = _ConvertPhrase(testPhrase.input);
+ processedMarkup.should.match(testPhrase.output);
+ });
+ });
+
});
\ No newline at end of file
diff --git a/core/test/unit/testUtils.js b/core/test/unit/testUtils.js
index 489ff5f7b5..7f1310c881 100644
--- a/core/test/unit/testUtils.js
+++ b/core/test/unit/testUtils.js
@@ -37,7 +37,7 @@ sampleUser = function (i) {
email: "joe_" + i + "@bloggs.com",
password: "$2a$10$c5G9RS5.dXRt3UqvZ5wNgOLQLc7ZFc2DJo01du0oLT1YYOM67KJMe",
created_by: 1,
- created_at: 1234567890
+ created_at: new Date()
};
};
diff --git a/index.js b/index.js
index 53f4f31df9..fef21300a4 100644
--- a/index.js
+++ b/index.js
@@ -2,7 +2,7 @@
// Orchestrates the loading of Ghost
var configLoader = require('./core/config-loader.js'),
- error = require('./core/server/errorHandling');
+ error = require('./core/server/errorHandling');
// If no env is set, default to development
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
diff --git a/package.json b/package.json
index b126389387..103a53dc60 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "ghost",
- "version": "0.3.0",
+ "version": "0.3.1",
"private": true,
"scripts": {
"start": "node index",