mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-23 10:53:34 +03:00
Merge branch 'master' into ember
Conflicts: bower.json core/client/views/editor.js
This commit is contained in:
commit
79a333b480
2
.gitignore
vendored
2
.gitignore
vendored
@ -14,7 +14,7 @@ results
|
||||
npm-debug.log
|
||||
node_modules
|
||||
bower_components
|
||||
|
||||
.bowerrc
|
||||
.idea/*
|
||||
*.iml
|
||||
projectFilesBackup
|
||||
|
@ -76,6 +76,8 @@ var path = require('path'),
|
||||
files: [
|
||||
// Theme CSS
|
||||
'content/themes/casper/css/*.css',
|
||||
// Ghost UI CSS
|
||||
'bower_components/ghost-ui/dist/css/*.css',
|
||||
// Theme JS
|
||||
'content/themes/casper/js/*.js',
|
||||
// Admin JS
|
||||
@ -1021,4 +1023,4 @@ var path = require('path'),
|
||||
grunt.registerTask('default', 'Build JS & templates for development', ['update_submodules', 'handlebars', 'concat', 'copy:dev', 'emberBuild']);
|
||||
};
|
||||
|
||||
module.exports = configureGrunt;
|
||||
module.exports = configureGrunt;
|
||||
|
4
LICENSE
4
LICENSE
@ -1,4 +1,4 @@
|
||||
Copyright (c) 2014 Ghost Foundation - Released under The MIT License.
|
||||
Copyright (c) 2013-2014 Ghost Foundation - Released under The MIT License.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
@ -19,4 +19,4 @@ 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.
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
@ -83,4 +83,4 @@ Constructed with the following guidelines:
|
||||
|
||||
## Copyright & License
|
||||
|
||||
Copyright (C) 2014 Ghost Foundation - Released under the [MIT license](LICENSE).
|
||||
Copyright (c) 2013-2014 Ghost Foundation - Released under the [MIT license](LICENSE).
|
||||
|
@ -7,10 +7,9 @@
|
||||
"ember": "~1.4.0",
|
||||
"ember-resolver": "git://github.com/stefanpenner/ember-jj-abrams-resolver.git#9805033c178e7f857f801359664adb599444b430",
|
||||
"fastclick": "1.0.0",
|
||||
"ghost-ui": "0.1.0",
|
||||
"ghost-ui": "0.1.2",
|
||||
"handlebars": "~1.1.2",
|
||||
"ic-ajax": "1.0.1",
|
||||
"iCheck": "1.0.1",
|
||||
"jquery": "1.11.0",
|
||||
"jquery-file-upload": "9.5.6",
|
||||
"jquery-hammerjs": "1.0.1",
|
||||
|
@ -1,114 +1,69 @@
|
||||
<section class="markdown-help-container">
|
||||
<table class="modal-markdown-help-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Result</th>
|
||||
<th>Markdown</th>
|
||||
<th>Shortcut</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Result</th>
|
||||
<th>Markdown</th>
|
||||
<th>Shortcut</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>Bold</strong></td>
|
||||
<td>**text**</td>
|
||||
<td>Ctrl / Cmd + B</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><em>Emphasize</em></td>
|
||||
<td>*text*</td>
|
||||
<td>Ctrl / Cmd + I</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Inline Code</code></td>
|
||||
<td>`code`</td>
|
||||
<td>Cmd + K / Ctrl + Shift + K</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Strike-through</td>
|
||||
<td>~~text~~</td>
|
||||
<td>Ctrl + Alt + U</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="#">Link</a></td>
|
||||
<td>[title](http://)</td>
|
||||
<td>Ctrl + Shift + L</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Image</td>
|
||||
<td>![alt](http://)</td>
|
||||
<td>Ctrl + Shift + I</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>List</td>
|
||||
<td>* item</td>
|
||||
<td>Ctrl + L</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Blockquote</td>
|
||||
<td>> quote</td>
|
||||
<td>Ctrl + Q</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>H1</td>
|
||||
<td># Heading</td>
|
||||
<td>Ctrl + Alt + 1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>H2</td>
|
||||
<td>## Heading</td>
|
||||
<td>Ctrl + Alt + 2</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>H3</td>
|
||||
<td>### Heading</td>
|
||||
<td>Ctrl + Alt + 3</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>H4</td>
|
||||
<td>#### Heading</td>
|
||||
<td>Ctrl + Alt + 4</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>H5</td>
|
||||
<td>##### Heading</td>
|
||||
<td>Ctrl + Alt + 5</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>H6</td>
|
||||
<td>###### Heading</td>
|
||||
<td>Ctrl + Alt + 6</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Select Word</td>
|
||||
<td></td>
|
||||
<td>Ctrl + Alt + W</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>New Paragraph</td>
|
||||
<td></td>
|
||||
<td>Ctrl / Cmd + Enter</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Uppercase</td>
|
||||
<td></td>
|
||||
<td>Ctrl + U</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Lowercase</td>
|
||||
<td></td>
|
||||
<td>Ctrl + Shift + U</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Titlecase</td>
|
||||
<td></td>
|
||||
<td>Ctrl + Alt + Shift + U</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Insert Current Date</td>
|
||||
<td></td>
|
||||
<td>Ctrl + Shift + 1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Bold</strong></td>
|
||||
<td>**text**</td>
|
||||
<td>Ctrl / Cmd + B</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><em>Emphasize</em></td>
|
||||
<td>*text*</td>
|
||||
<td>Ctrl / Cmd + I</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Strike-through</td>
|
||||
<td>~~text~~</td>
|
||||
<td>Ctrl + Alt + U</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="#">Link</a></td>
|
||||
<td>[title](http://)</td>
|
||||
<td>Ctrl + Shift + L</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Image</td>
|
||||
<td>![alt](http://)</td>
|
||||
<td>Ctrl + Shift + I</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>List</td>
|
||||
<td>* item</td>
|
||||
<td>Ctrl + L</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Blockquote</td>
|
||||
<td>> quote</td>
|
||||
<td>Ctrl + Q</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>H1</td>
|
||||
<td># Heading</td>
|
||||
<td>Ctrl + Alt + 1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>H2</td>
|
||||
<td>## Heading</td>
|
||||
<td>Ctrl + Alt + 2</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>H3</td>
|
||||
<td>### Heading</td>
|
||||
<td>Ctrl + Alt + 3</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Inline Code</code></td>
|
||||
<td>`code`</td>
|
||||
<td>Cmd + K / Ctrl + Shift + K</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
For further Markdown syntax reference: <a href="http://daringfireball.net/projects/markdown/syntax" target="_blank">Markdown Documentation</a>
|
||||
</section>
|
||||
</section>
|
@ -387,7 +387,6 @@
|
||||
model: {
|
||||
options: {
|
||||
close: true,
|
||||
type: "info",
|
||||
style: ["wide"],
|
||||
animation: 'fade'
|
||||
},
|
||||
@ -531,7 +530,6 @@
|
||||
model: {
|
||||
options: {
|
||||
close: true,
|
||||
type: "info",
|
||||
style: ["wide"],
|
||||
animation: 'fade'
|
||||
},
|
||||
|
@ -2,17 +2,31 @@
|
||||
// Orchestrates the loading of Ghost
|
||||
// When run from command line.
|
||||
|
||||
var bootstrap = require('./bootstrap'),
|
||||
errors = require('./server/errorHandling');
|
||||
var when = require('when'),
|
||||
bootstrap = require('./bootstrap');
|
||||
|
||||
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
|
||||
|
||||
function startGhost(options) {
|
||||
// When we no longer need to require('./server')
|
||||
// in a callback this extra deferred object
|
||||
// won't be necessary, we'll just be able to return
|
||||
// the server object directly.
|
||||
var deferred = when.defer();
|
||||
|
||||
options = options || {};
|
||||
|
||||
bootstrap(options.config).then(function () {
|
||||
var ghost = require('./server');
|
||||
ghost(options.app);
|
||||
}).otherwise(errors.logAndThrowError);
|
||||
return ghost(options.app).then(deferred.resolve).otherwise(function (e) {
|
||||
// We don't return the rejected promise to stop
|
||||
// the propogation of the rejection and just
|
||||
// allow the user to manage what to do.
|
||||
deferred.reject(e);
|
||||
});
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
module.exports = startGhost;
|
@ -14,6 +14,14 @@ var path = require('path'),
|
||||
appRoot = path.resolve(__dirname, '../../../'),
|
||||
corePath = path.resolve(appRoot, 'core/');
|
||||
|
||||
// Are we using sockets? Custom socket or the default?
|
||||
function getSocket() {
|
||||
if (ghostConfig.server.hasOwnProperty('socket')) {
|
||||
return _.isString(ghostConfig.server.socket) ? ghostConfig.server.socket : path.join(ghostConfig.paths.contentPath, process.env.NODE_ENV + '.socket');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function updateConfig(config) {
|
||||
var localPath,
|
||||
contentPath,
|
||||
@ -110,5 +118,6 @@ function config() {
|
||||
module.exports = config;
|
||||
module.exports.init = initConfig;
|
||||
module.exports.theme = theme;
|
||||
module.exports.getSocket = getSocket;
|
||||
module.exports.urlFor = configUrl.urlFor;
|
||||
module.exports.urlForPost = configUrl.urlForPost;
|
||||
|
@ -252,11 +252,16 @@ frontendControllers = {
|
||||
'rss': function (req, res, next) {
|
||||
// Initialize RSS
|
||||
var pageParam = req.params.page !== undefined ? parseInt(req.params.page, 10) : 1,
|
||||
feed;
|
||||
tagParam = req.params.slug;
|
||||
|
||||
// No negative pages, or page 1
|
||||
if (isNaN(pageParam) || pageParam < 1 || (pageParam === 1 && req.route.path === '/rss/:page/')) {
|
||||
return res.redirect(config().paths.subdir + '/rss/');
|
||||
if (isNaN(pageParam) || pageParam < 1 ||
|
||||
(pageParam === 1 && (req.route.path === '/rss/:page/' || req.route.path === '/tag/:slug/rss/:page/'))) {
|
||||
if (tagParam !== undefined) {
|
||||
return res.redirect(config().paths.subdir + '/tag/' + tagParam + '/rss/');
|
||||
} else {
|
||||
return res.redirect(config().paths.subdir + '/rss/');
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: needs refactor for multi user to not use first user as default
|
||||
@ -266,25 +271,37 @@ frontendControllers = {
|
||||
api.settings.read('description'),
|
||||
api.settings.read('permalinks')
|
||||
]).then(function (result) {
|
||||
var user = result[0].value,
|
||||
title = result[1].value.value,
|
||||
description = result[2].value.value,
|
||||
permalinks = result[3].value,
|
||||
siteUrl = config.urlFor('home', null, true),
|
||||
feedUrl = config.urlFor('rss', null, true);
|
||||
|
||||
feed = new RSS({
|
||||
title: title,
|
||||
description: description,
|
||||
generator: 'Ghost v' + res.locals.version,
|
||||
feed_url: feedUrl,
|
||||
site_url: siteUrl,
|
||||
ttl: '60'
|
||||
});
|
||||
var options = {};
|
||||
if (pageParam) { options.page = pageParam; }
|
||||
if (tagParam) { options.tag = tagParam; }
|
||||
|
||||
return api.posts.browse(options).then(function (page) {
|
||||
|
||||
var user = result[0].value,
|
||||
title = result[1].value.value,
|
||||
description = result[2].value.value,
|
||||
permalinks = result[3].value,
|
||||
siteUrl = config.urlFor('home', null, true),
|
||||
feedUrl = config.urlFor('rss', null, true),
|
||||
maxPage = page.pages,
|
||||
feedItems = [],
|
||||
feed;
|
||||
|
||||
if (tagParam) {
|
||||
title = page.aspect.tag.name + ' - ' + title;
|
||||
feedUrl = feedUrl + 'tag/' + page.aspect.tag.slug + '/';
|
||||
}
|
||||
|
||||
feed = new RSS({
|
||||
title: title,
|
||||
description: description,
|
||||
generator: 'Ghost v' + res.locals.version,
|
||||
feed_url: feedUrl,
|
||||
site_url: siteUrl,
|
||||
ttl: '60'
|
||||
});
|
||||
|
||||
return api.posts.browse({page: pageParam}).then(function (page) {
|
||||
var maxPage = page.pages,
|
||||
feedItems = [];
|
||||
|
||||
// A bit of a hack for situations with no content.
|
||||
if (maxPage === 0) {
|
||||
@ -294,14 +311,18 @@ frontendControllers = {
|
||||
|
||||
// If page is greater than number of pages we have, redirect to last page
|
||||
if (pageParam > maxPage) {
|
||||
return res.redirect(config().paths.subdir + '/rss/' + maxPage + '/');
|
||||
if (tagParam) {
|
||||
return res.redirect(config().paths.subdir + '/tag/' + tagParam + '/rss/' + maxPage + '/');
|
||||
} else {
|
||||
return res.redirect(config().paths.subdir + '/rss/' + maxPage + '/');
|
||||
}
|
||||
}
|
||||
|
||||
filters.doFilter('prePostsRender', page.posts).then(function (posts) {
|
||||
posts.forEach(function (post) {
|
||||
var deferred = when.defer(),
|
||||
item = {
|
||||
title: _.escape(post.title),
|
||||
title: post.title,
|
||||
guid: post.uuid,
|
||||
url: config.urlFor('post', {post: post, permalinks: permalinks}, true),
|
||||
date: post.published_at,
|
||||
|
@ -4,7 +4,6 @@ var crypto = require('crypto'),
|
||||
hbs = require('express-hbs'),
|
||||
fs = require('fs'),
|
||||
uuid = require('node-uuid'),
|
||||
path = require('path'),
|
||||
Polyglot = require('node-polyglot'),
|
||||
semver = require('semver'),
|
||||
_ = require('lodash'),
|
||||
@ -22,7 +21,6 @@ var crypto = require('crypto'),
|
||||
routes = require('./routes'),
|
||||
packageInfo = require('../../package.json'),
|
||||
|
||||
|
||||
// Variables
|
||||
dbHash;
|
||||
|
||||
@ -107,21 +105,92 @@ function builtFilesExist() {
|
||||
return when.all(deferreds);
|
||||
}
|
||||
|
||||
function startGhost(deferred) {
|
||||
|
||||
return function () {
|
||||
// Tell users if their node version is not supported, and exit
|
||||
if (!semver.satisfies(process.versions.node, packageInfo.engines.node)) {
|
||||
console.log(
|
||||
"\nERROR: Unsupported version of Node".red,
|
||||
"\nGhost needs Node version".red,
|
||||
packageInfo.engines.node.yellow,
|
||||
"you are using version".red,
|
||||
process.versions.node.yellow,
|
||||
"\nPlease go to http://nodejs.org to get a supported version".green
|
||||
);
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Startup & Shutdown messages
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
console.log(
|
||||
"Ghost is running...".green,
|
||||
"\nYour blog is now available on",
|
||||
config().url,
|
||||
"\nCtrl+C to shut down".grey
|
||||
);
|
||||
|
||||
// ensure that Ghost exits correctly on Ctrl+C
|
||||
process.on('SIGINT', function () {
|
||||
console.log(
|
||||
"\nGhost has shut down".red,
|
||||
"\nYour blog is now offline"
|
||||
);
|
||||
process.exit(0);
|
||||
});
|
||||
} else {
|
||||
console.log(
|
||||
("Ghost is running in " + process.env.NODE_ENV + "...").green,
|
||||
"\nListening on",
|
||||
config.getSocket() || config().server.host + ':' + config().server.port,
|
||||
"\nUrl configured as:",
|
||||
config().url,
|
||||
"\nCtrl+C to shut down".grey
|
||||
);
|
||||
// ensure that Ghost exits correctly on Ctrl+C
|
||||
process.on('SIGINT', function () {
|
||||
console.log(
|
||||
"\nGhost has shutdown".red,
|
||||
"\nGhost was running for",
|
||||
Math.round(process.uptime()),
|
||||
"seconds"
|
||||
);
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
deferred.resolve();
|
||||
};
|
||||
}
|
||||
|
||||
// ## Initializes the ghost application.
|
||||
// Sets up the express server instance.
|
||||
// Instantiates the ghost singleton, helpers, routes, middleware, and apps.
|
||||
// Finally it starts the http server.
|
||||
function setup(server) {
|
||||
|
||||
function init(server) {
|
||||
// create a hash for cache busting assets
|
||||
var assetHash = (crypto.createHash('md5').update(packageInfo.version + Date.now()).digest('hex')).substring(0, 10);
|
||||
|
||||
// If no express instance is passed in
|
||||
// then create our own
|
||||
if (!server) {
|
||||
server = express();
|
||||
}
|
||||
|
||||
// Set up Polygot instance on the require module
|
||||
Polyglot.instance = new Polyglot();
|
||||
|
||||
// ### Initialisation
|
||||
// The server and its dependencies require a populated config
|
||||
// It returns a promise that is resolved when the application
|
||||
// has finished starting up.
|
||||
|
||||
// Initialise the models
|
||||
models.init().then(function () {
|
||||
// Make sure javascript files have been built via grunt concat
|
||||
return builtFilesExist().then(function () {
|
||||
// Initialise the models
|
||||
return models.init();
|
||||
}).then(function () {
|
||||
// Populate any missing default settings
|
||||
return models.Settings.populateDefaults();
|
||||
}).then(function () {
|
||||
@ -136,19 +205,17 @@ function setup(server) {
|
||||
// Check for or initialise a dbHash.
|
||||
initDbHashAndFirstRun(),
|
||||
// Initialize the permissions actions and objects
|
||||
permissions.init()
|
||||
permissions.init(),
|
||||
// Initialize mail
|
||||
mailer.init(),
|
||||
// Initialize apps
|
||||
apps.init()
|
||||
);
|
||||
}).then(function () {
|
||||
// Make sure javascript files have been built via grunt concat
|
||||
return builtFilesExist();
|
||||
}).then(function () {
|
||||
// Initialize mail
|
||||
return mailer.init();
|
||||
}).then(function () {
|
||||
var adminHbs = hbs.create();
|
||||
var adminHbs = hbs.create(),
|
||||
deferred = when.defer();
|
||||
|
||||
// ##Configuration
|
||||
server.set('version hash', assetHash);
|
||||
|
||||
// return the correct mime type for woff filess
|
||||
express['static'].mime.define({'application/font-woff': ['woff']});
|
||||
@ -177,111 +244,37 @@ function setup(server) {
|
||||
// Set up Frontend routes
|
||||
routes.frontend(server);
|
||||
|
||||
// Are we using sockets? Custom socket or the default?
|
||||
function getSocket() {
|
||||
if (config().server.hasOwnProperty('socket')) {
|
||||
return _.isString(config().server.socket) ? config().server.socket : path.join(config.path().contentPath, process.env.NODE_ENV + '.socket');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function startGhost() {
|
||||
// Tell users if their node version is not supported, and exit
|
||||
if (!semver.satisfies(process.versions.node, packageInfo.engines.node)) {
|
||||
console.log(
|
||||
"\nERROR: Unsupported version of Node".red,
|
||||
"\nGhost needs Node version".red,
|
||||
packageInfo.engines.node.yellow,
|
||||
"you are using version".red,
|
||||
process.versions.node.yellow,
|
||||
"\nPlease go to http://nodejs.org to get a supported version".green
|
||||
);
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Startup & Shutdown messages
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
console.log(
|
||||
"Ghost is running...".green,
|
||||
"\nYour blog is now available on",
|
||||
config().url,
|
||||
"\nCtrl+C to shut down".grey
|
||||
);
|
||||
|
||||
// ensure that Ghost exits correctly on Ctrl+C
|
||||
process.on('SIGINT', function () {
|
||||
console.log(
|
||||
"\nGhost has shut down".red,
|
||||
"\nYour blog is now offline"
|
||||
);
|
||||
process.exit(0);
|
||||
});
|
||||
} else {
|
||||
console.log(
|
||||
("Ghost is running in " + process.env.NODE_ENV + "...").green,
|
||||
"\nListening on",
|
||||
getSocket() || config().server.host + ':' + config().server.port,
|
||||
"\nUrl configured as:",
|
||||
config().url,
|
||||
"\nCtrl+C to shut down".grey
|
||||
);
|
||||
// ensure that Ghost exits correctly on Ctrl+C
|
||||
process.on('SIGINT', function () {
|
||||
console.log(
|
||||
"\nGhost has shutdown".red,
|
||||
"\nGhost was running for",
|
||||
Math.round(process.uptime()),
|
||||
"seconds"
|
||||
);
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Initialize apps then start the server
|
||||
apps.init().then(function () {
|
||||
|
||||
// ## Start Ghost App
|
||||
if (getSocket()) {
|
||||
// Make sure the socket is gone before trying to create another
|
||||
fs.unlink(getSocket(), function (err) {
|
||||
/*jshint unused:false*/
|
||||
server.listen(
|
||||
getSocket(),
|
||||
startGhost
|
||||
);
|
||||
fs.chmod(getSocket(), '0660');
|
||||
});
|
||||
|
||||
} else {
|
||||
server.listen(
|
||||
config().server.port,
|
||||
config().server.host,
|
||||
startGhost
|
||||
);
|
||||
}
|
||||
_.each(config().paths.availableThemes._messages.errors, function (error) {
|
||||
errors.logError(error.message, error.context);
|
||||
});
|
||||
_.each(config().paths.availableThemes._messages.warns, function (warn) {
|
||||
errors.logWarn(warn.message, warn.context);
|
||||
});
|
||||
// Log all theme errors and warnings
|
||||
_.each(config().paths.availableThemes._messages.errors, function (error) {
|
||||
errors.logError(error.message, error.context);
|
||||
});
|
||||
}, function (err) {
|
||||
errors.logErrorAndExit(err, err.context, err.help);
|
||||
|
||||
_.each(config().paths.availableThemes._messages.warns, function (warn) {
|
||||
errors.logWarn(warn.message, warn.context);
|
||||
});
|
||||
|
||||
// ## Start Ghost App
|
||||
if (config.getSocket()) {
|
||||
// Make sure the socket is gone before trying to create another
|
||||
fs.unlink(config.getSocket(), function (err) {
|
||||
/*jshint unused:false*/
|
||||
server.listen(
|
||||
config.getSocket(),
|
||||
startGhost(deferred)
|
||||
);
|
||||
fs.chmod(config.getSocket(), '0660');
|
||||
});
|
||||
|
||||
} else {
|
||||
server.listen(
|
||||
config().server.port,
|
||||
config().server.host,
|
||||
startGhost(deferred)
|
||||
);
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
});
|
||||
}
|
||||
|
||||
// Initializes the ghost application.
|
||||
function init(app) {
|
||||
if (!app) {
|
||||
app = express();
|
||||
}
|
||||
|
||||
// The server and its dependencies require a populated config
|
||||
setup(app);
|
||||
}
|
||||
|
||||
module.exports = init;
|
||||
|
@ -194,6 +194,41 @@ function checkSSL(req, res, next) {
|
||||
next();
|
||||
}
|
||||
|
||||
// ### Robots Middleware
|
||||
// Handle requests to robots.txt and cache file
|
||||
function robots() {
|
||||
var content, // file cache
|
||||
filePath = path.join(config().paths.corePath, '/shared/robots.txt');
|
||||
|
||||
return function robots(req, res, next) {
|
||||
if ('/robots.txt' === req.url) {
|
||||
if (content) {
|
||||
res.writeHead(200, content.headers);
|
||||
res.end(content.body);
|
||||
} else {
|
||||
fs.readFile(filePath, function (err, buf) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
content = {
|
||||
headers: {
|
||||
'Content-Type': 'text/plain',
|
||||
'Content-Length': buf.length,
|
||||
'Cache-Control': 'public, max-age=' + ONE_YEAR_MS / 1000
|
||||
},
|
||||
body: buf
|
||||
};
|
||||
res.writeHead(200, content.headers);
|
||||
res.end(content.body);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = function (server, dbHash) {
|
||||
var logging = config().logging,
|
||||
subdir = config().paths.subdir,
|
||||
@ -229,7 +264,6 @@ module.exports = function (server, dbHash) {
|
||||
// First determine whether we're serving admin or theme content
|
||||
expressServer.use(manageAdminAndTheme);
|
||||
|
||||
|
||||
// Admin only config
|
||||
expressServer.use(subdir + '/ghost', middleware.whenEnabled('admin', express['static'](path.join(corePath, '/clientold/assets'), {maxAge: ONE_YEAR_MS})));
|
||||
expressServer.use(subdir + '/ghost/ember', middleware.whenEnabled('admin', express['static'](path.join(corePath, '/client/assets'), {maxAge: ONE_YEAR_MS})));
|
||||
@ -243,6 +277,9 @@ module.exports = function (server, dbHash) {
|
||||
// Theme only config
|
||||
expressServer.use(subdir, middleware.whenEnabled(expressServer.get('activeTheme'), middleware.staticTheme()));
|
||||
|
||||
// Serve robots.txt if not found in theme
|
||||
expressServer.use(robots());
|
||||
|
||||
// Add in all trailing slashes
|
||||
expressServer.use(slashes(true, {headers: {'Cache-Control': 'public, max-age=' + ONE_YEAR_S}}));
|
||||
|
||||
|
@ -14,11 +14,6 @@ module.exports = {
|
||||
init: function () {
|
||||
return migrations.init();
|
||||
},
|
||||
reset: function () {
|
||||
return migrations.reset().then(function () {
|
||||
return migrations.init();
|
||||
});
|
||||
},
|
||||
// ### deleteAllContent
|
||||
// Delete all content from the database (posts, tags, tags_posts)
|
||||
deleteAllContent: function () {
|
||||
|
@ -6,6 +6,8 @@ module.exports = function (server) {
|
||||
// ### Frontend routes
|
||||
server.get('/rss/', frontend.rss);
|
||||
server.get('/rss/:page/', frontend.rss);
|
||||
server.get('/tag/:slug/rss/', frontend.rss);
|
||||
server.get('/tag/:slug/rss/:page/', frontend.rss);
|
||||
server.get('/tag/:slug/page/:page/', frontend.tag);
|
||||
server.get('/tag/:slug/', frontend.tag);
|
||||
server.get('/page/:page/', frontend.homepage);
|
||||
|
@ -4,32 +4,34 @@
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html" charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
<meta name="csrf-param" content="{{csrfToken}}">
|
||||
<meta name="csrf-param" content="{{csrfToken}}" />
|
||||
|
||||
<title>Ghost Admin</title>
|
||||
|
||||
<meta name="HandheldFriendly" content="True">
|
||||
<meta name="MobileOptimized" content="320">
|
||||
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<meta name="HandheldFriendly" content="True" />
|
||||
<meta name="MobileOptimized" content="320" />
|
||||
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1, minimal-ui" />
|
||||
|
||||
<meta http-equiv="cleartype" content="on">
|
||||
<meta http-equiv="cleartype" content="on" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
|
||||
<meta name="apple-mobile-web-app-title" content="Ghost" />
|
||||
|
||||
<link rel="shortcut icon" href="{{asset "favicon.ico"}}">
|
||||
<link rel="shortcut icon" href="{{asset "favicon.ico"}}" />
|
||||
<link rel="apple-touch-icon-precomposed" href="{{asset "img/touch-icon-iphone.png" ember="true"}}" />
|
||||
<link rel="apple-touch-icon-precomposed" sizes="76x76" href="{{asset "img/touch-icon-ipad.png" ember="true"}}" />
|
||||
<link rel="apple-touch-icon-precomposed" sizes="120x120" href="{{asset "img/small.png" ember="true"}}" />
|
||||
<link rel="apple-touch-icon-precomposed" sizes="152x152" href="{{asset "img/medium.png" ember="true"}}" />
|
||||
|
||||
<meta name="application-name" content="Ghost"/>
|
||||
<meta name="msapplication-TileColor" content="#ffffff"/>
|
||||
<meta name="application-name" content="Ghost" />
|
||||
<meta name="msapplication-TileColor" content="#ffffff" />
|
||||
<meta name="msapplication-square70x70logo" content="{{asset "img/small.png" ember="true"}}" />
|
||||
<meta name="msapplication-square150x150logo" content="{{asset "img/medium.png" ember="true"}}" />
|
||||
<meta name="msapplication-square310x310logo" content="{{asset "img/large.png" ember="true"}}" />
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="//fonts.googleapis.com/css?family=Open+Sans:400,300,700">
|
||||
<link rel="stylesheet" href="{{asset "css/ghost-ui.min.css" ember="true"}}">
|
||||
<link rel="stylesheet" href="{{asset "css/ember-hacks.css" ember="true"}}">
|
||||
<link rel="stylesheet" type="text/css" href="//fonts.googleapis.com/css?family=Open+Sans:400,300,700" />
|
||||
<link rel="stylesheet" href="{{asset "css/ghost-ui.min.css" ember="true"}}" />
|
||||
<link rel="stylesheet" href="{{asset "css/ember-hacks.css" ember="true"}}" />
|
||||
</head>
|
||||
<body class="{{bodyClass}}{{update_notification classOnly="true"}}">
|
||||
|
||||
|
@ -4,31 +4,33 @@
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html" charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
<meta name="csrf-param" content="{{csrfToken}}">
|
||||
<meta name="csrf-param" content="{{csrfToken}}" />
|
||||
|
||||
<title>Ghost Admin</title>
|
||||
|
||||
<meta name="HandheldFriendly" content="True">
|
||||
<meta name="MobileOptimized" content="320">
|
||||
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<meta name="HandheldFriendly" content="True" />
|
||||
<meta name="MobileOptimized" content="320" />
|
||||
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1, minimal-ui" />
|
||||
|
||||
<meta http-equiv="cleartype" content="on">
|
||||
<meta http-equiv="cleartype" content="on" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
|
||||
<meta name="apple-mobile-web-app-title" content="Ghost" />
|
||||
|
||||
<link rel="shortcut icon" href="{{asset "favicon.ico"}}">
|
||||
<link rel="shortcut icon" href="{{asset "favicon.ico"}}" />
|
||||
<link rel="apple-touch-icon-precomposed" href="{{asset "img/touch-icon-iphone.png" ghost="true"}}" />
|
||||
<link rel="apple-touch-icon-precomposed" sizes="76x76" href="{{asset "img/touch-icon-ipad.png" ghost="true"}}" />
|
||||
<link rel="apple-touch-icon-precomposed" sizes="120x120" href="{{asset "img/small.png" ghost="true"}}" />
|
||||
<link rel="apple-touch-icon-precomposed" sizes="152x152" href="{{asset "img/medium.png" ghost="true"}}" />
|
||||
|
||||
<meta name="application-name" content="Ghost"/>
|
||||
<meta name="msapplication-TileColor" content="#ffffff"/>
|
||||
<meta name="application-name" content="Ghost" />
|
||||
<meta name="msapplication-TileColor" content="#ffffff" />
|
||||
<meta name="msapplication-square70x70logo" content="{{asset "img/small.png" ghost="true"}}" />
|
||||
<meta name="msapplication-square150x150logo" content="{{asset "img/medium.png" ghost="true"}}" />
|
||||
<meta name="msapplication-square310x310logo" content="{{asset "img/large.png" ghost="true"}}" />
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="//fonts.googleapis.com/css?family=Open+Sans:400,300,700">
|
||||
<link rel="stylesheet" href="{{asset "css/ghost-ui.min.css" ghost="true"}}">
|
||||
<link rel="stylesheet" type="text/css" href="//fonts.googleapis.com/css?family=Open+Sans:400,300,700" />
|
||||
<link rel="stylesheet" href="{{asset "css/ghost-ui.min.css" ghost="true"}}" />
|
||||
</head>
|
||||
<body class="{{bodyClass}}{{update_notification classOnly="true"}}">
|
||||
{{#unless hideNavbar}}
|
||||
|
@ -91,6 +91,81 @@ CasperTest.begin("Word count and plurality", 4, function suite(test) {
|
||||
});
|
||||
});
|
||||
|
||||
CasperTest.begin("Image Uploads", 14, function suite(test) {
|
||||
casper.thenOpen(url + "ghost/editor/", function testTitleAndUrl() {
|
||||
test.assertTitle("Ghost Admin", "Ghost admin has no title");
|
||||
});
|
||||
|
||||
// Test standard image upload modal
|
||||
casper.then(function () {
|
||||
casper.writeContentToCodeMirror("![]()");
|
||||
});
|
||||
|
||||
function assertEmptyImageUploaderDisplaysCorrectly() {
|
||||
test.assertExists(".entry-preview .js-upload-target", "Upload target exists");
|
||||
test.assertExists(".entry-preview .js-fileupload", "File upload target exists");
|
||||
test.assertExists(".entry-preview .image-url", "Image URL button exists");
|
||||
};
|
||||
|
||||
casper.waitForSelector(".entry-preview .js-drop-zone.image-uploader", assertEmptyImageUploaderDisplaysCorrectly);
|
||||
|
||||
// Test image URL upload modal
|
||||
casper.thenClick(".entry-preview .image-uploader a.image-url");
|
||||
|
||||
casper.waitForSelector(".image-uploader-url", function onSuccess() {
|
||||
test.assertExists(".image-uploader-url .url.js-upload-url", "Image URL uploader exists")
|
||||
test.assertExists(".image-uploader-url .button-save.js-button-accept", "Image URL accept button exists")
|
||||
test.assertExists(".image-uploader-url .image-upload", "Back to normal image upload style button exists")
|
||||
});
|
||||
|
||||
// Test image source location
|
||||
casper.thenOpen(url + "ghost/editor/", function testTitleAndUrl() {
|
||||
test.assertTitle("Ghost Admin", "Ghost admin has no title");
|
||||
});
|
||||
|
||||
var testFileLocation = "test/file/location";
|
||||
|
||||
casper.then(function () {
|
||||
var markdownImageString = "![](" + testFileLocation + ")";
|
||||
casper.writeContentToCodeMirror(markdownImageString);
|
||||
});
|
||||
|
||||
casper.waitForSelector(".entry-preview .js-drop-zone.pre-image-uploader", function onSuccess() {
|
||||
var imageJQuerySelector = ".entry-preview img.js-upload-target[src='" + testFileLocation + "']"
|
||||
test.assertExists(imageJQuerySelector, "Uploaded image tag properly links to source location");
|
||||
});
|
||||
|
||||
// Test cancel image button
|
||||
casper.thenClick(".pre-image-uploader a.image-cancel.js-cancel");
|
||||
|
||||
casper.waitForSelector(".entry-preview .js-drop-zone.image-uploader", assertEmptyImageUploaderDisplaysCorrectly);
|
||||
|
||||
// Test image url source location
|
||||
casper.thenOpen(url + "ghost/editor/", function testTitleAndUrl() {
|
||||
test.assertTitle("Ghost Admin", "Ghost admin has no title");
|
||||
});
|
||||
|
||||
casper.then(function () {
|
||||
casper.writeContentToCodeMirror("![]()");
|
||||
});
|
||||
|
||||
casper.waitForSelector(".entry-preview .js-drop-zone.image-uploader", function onSuccess() {
|
||||
casper.thenClick(".entry-preview .image-uploader a.image-url");
|
||||
});
|
||||
|
||||
var imageURL = "random.url";
|
||||
casper.waitForSelector(".image-uploader-url", function onSuccess() {
|
||||
casper.sendKeys(".image-uploader-url input.url.js-upload-url", imageURL);
|
||||
casper.thenClick(".js-button-accept.button-save");
|
||||
});
|
||||
|
||||
casper.waitForSelector(".entry-preview .js-drop-zone.pre-image-uploader", function onSuccess() {
|
||||
var imageJQuerySelector = ".entry-preview img.js-upload-target[src='" + imageURL + "']"
|
||||
test.assertExists(imageJQuerySelector, "Uploaded image tag properly links to inputted image URL");
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
CasperTest.begin('Required Title', 4, function suite(test) {
|
||||
casper.thenOpen(url + "ghost/editor/", function testTitleAndUrl() {
|
||||
test.assertTitle("Ghost Admin", "Ghost admin has no title");
|
||||
@ -131,6 +206,117 @@ CasperTest.begin('Title Trimming', 2, function suite(test) {
|
||||
});
|
||||
});
|
||||
|
||||
CasperTest.begin("Tag editor", 6, function suite(test) {
|
||||
casper.thenOpen(url + "ghost/editor/", function testTitleAndUrl() {
|
||||
test.assertTitle("Ghost Admin", "Ghost admin has no title");
|
||||
});
|
||||
|
||||
var tagName = "someTagName";
|
||||
|
||||
casper.then(function () {
|
||||
test.assertExists("#entry-tags", "should have tag label area");
|
||||
test.assertExists("#entry-tags .tag-label", "should have tag label icon");
|
||||
test.assertExists("#entry-tags input.tag-input", "should have tag input area");
|
||||
casper.sendKeys("#entry-tags input.tag-input", tagName);
|
||||
casper.sendKeys("#entry-tags input.tag-input", casper.page.event.key.Enter);
|
||||
});
|
||||
|
||||
var createdTagSelector = "#entry-tags .tags .tag";
|
||||
casper.waitForSelector(createdTagSelector, function onSuccess() {
|
||||
test.assertSelectorHasText(createdTagSelector, tagName, "typing enter after tag name should create tag");
|
||||
});
|
||||
|
||||
casper.thenClick(createdTagSelector);
|
||||
|
||||
casper.then(function () {
|
||||
test.assertDoesntExist(createdTagSelector, "clicking the tag should delete the tag");
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
CasperTest.begin("Post settings menu", 17, function suite(test) {
|
||||
casper.thenOpen(url + "ghost/editor/", function testTitleAndUrl() {
|
||||
test.assertTitle("Ghost Admin", "Ghost admin has no title");
|
||||
});
|
||||
|
||||
casper.then(function () {
|
||||
test.assertExists("#publish-bar a.post-settings", "icon toggle should exist");
|
||||
test.assertNotVisible("#publish-bar .post-settings-menu", "popup menu should not be visible at startup");
|
||||
test.assertExists(".post-settings-menu input#url", "url field exists");
|
||||
test.assertExists(".post-settings-menu input#pub-date", "publication date field exists");
|
||||
test.assertExists(".post-settings-menu input#static-page", "static page checkbox field exists");
|
||||
test.assertExists(".post-settings-menu a.delete", "delete post button exists")
|
||||
});
|
||||
|
||||
casper.thenClick("#publish-bar a.post-settings");
|
||||
|
||||
casper.waitUntilVisible("#publish-bar .post-settings-menu", function onSuccess() {
|
||||
test.assert(true, "popup menu should be visible after clicking post-settings icon");
|
||||
test.assertNotVisible(".post-settings-menu a.delete", "delete post btn shouldn't be visible on unsaved drafts");
|
||||
});
|
||||
|
||||
casper.thenClick("#publish-bar a.post-settings");
|
||||
|
||||
casper.waitWhileVisible("#publish-bar .post-settings-menu", function onSuccess() {
|
||||
test.assert(true, "popup menu should not be visible after clicking post-settings icon");
|
||||
});
|
||||
|
||||
// Enter a title and save draft so converting to/from static post
|
||||
// will result in notifications and 'Delete This Post' button appears
|
||||
casper.then(function (){
|
||||
casper.sendKeys("#entry-title", "aTitle");
|
||||
casper.thenClick(".js-publish-button");
|
||||
});
|
||||
|
||||
casper.thenClick("#publish-bar a.post-settings");
|
||||
|
||||
casper.waitUntilVisible("#publish-bar .post-settings-menu", function onSuccess() {
|
||||
test.assertVisible(".post-settings-menu a.delete", "delete post button should be visible for saved drafts");
|
||||
});
|
||||
|
||||
// Test Static Page conversion
|
||||
casper.thenClick(".post-settings-menu #static-page");
|
||||
|
||||
var staticPageConversionText = "Successfully converted to static page.";
|
||||
casper.waitForText(staticPageConversionText, function onSuccess() {
|
||||
test.assertSelectorHasText(
|
||||
".notification-success", staticPageConversionText, "correct static page conversion notification appears");
|
||||
})
|
||||
|
||||
casper.thenClick(".post-settings-menu #static-page");
|
||||
|
||||
var postConversionText = "Successfully converted to post.";
|
||||
casper.waitForText(postConversionText, function onSuccess() {
|
||||
test.assertSelectorHasText(
|
||||
".notification-success", postConversionText, "correct post conversion notification appears");
|
||||
});
|
||||
|
||||
// Test Delete Post Modal
|
||||
casper.thenClick(".post-settings-menu a.delete");
|
||||
|
||||
casper.waitUntilVisible("#modal-container", function onSuccess() {
|
||||
test.assert(true, "delete post modal is visible after clicking delete");
|
||||
test.assertSelectorHasText(
|
||||
"#modal-container .modal-header",
|
||||
"Are you sure you want to delete this post?",
|
||||
"delete post modal header has correct text");
|
||||
});
|
||||
|
||||
casper.thenClick("#modal-container .js-button-reject");
|
||||
|
||||
casper.waitWhileVisible("#modal-container", function onSuccess() {
|
||||
test.assert(true, "clicking cancel should close the delete post modal");
|
||||
});
|
||||
|
||||
casper.thenClick("#publish-bar a.post-settings");
|
||||
casper.thenClick(".post-settings-menu a.delete");
|
||||
casper.thenClick("#modal-container .js-button-accept");
|
||||
|
||||
casper.waitForUrl(/ghost\/content\/$/, function onSuccess() {
|
||||
test.assert(true, "clicking the delete post button should bring us to the content page");
|
||||
});
|
||||
});
|
||||
|
||||
CasperTest.begin('Publish menu - new post', 10, function suite(test) {
|
||||
casper.thenOpen(url + 'ghost/editor/', function testTitleAndUrl() {
|
||||
test.assertTitle("Ghost Admin", 'Ghost admin has no title');
|
||||
|
@ -154,6 +154,89 @@ CasperTest.begin('Ensure general blog description field length validation', 3, f
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
CasperTest.begin('Ensure image upload modals display correctly', 6, function suite(test) {
|
||||
casper.thenOpen(url + "ghost/settings/general/", function testTitleAndUrl() {
|
||||
test.assertTitle("Ghost Admin", "Ghost admin has no title");
|
||||
test.assertUrlMatch(/ghost\/settings\/general\/$/, "Ghost doesn't require login this time");
|
||||
});
|
||||
|
||||
function assertImageUploaderModalThenClose() {
|
||||
test.assertExists('.js-drop-zone.image-uploader', 'Image drop zone modal renders correctly');
|
||||
this.click('#modal-container .js-button-accept');
|
||||
casper.waitForSelector('.notification-success', function onSuccess() {
|
||||
test.assert(true, 'Got success notification');
|
||||
}, function onTimeout() {
|
||||
test.fail('No success notification');
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
// Test Blog Logo Upload Button
|
||||
casper.waitForSelector('#general', function then() {
|
||||
this.click('#general .js-modal-logo');
|
||||
});
|
||||
|
||||
casper.waitForSelector('#modal-container .modal-content', assertImageUploaderModalThenClose,
|
||||
function onTimeout() {
|
||||
test.fail('No upload logo modal container appeared');
|
||||
}, 1000);
|
||||
|
||||
// Test Blog Cover Upload Button
|
||||
casper.then(function() {
|
||||
this.click('#general .js-modal-cover');
|
||||
});
|
||||
|
||||
casper.waitForSelector('#modal-container .modal-content', assertImageUploaderModalThenClose,
|
||||
function onTimeout() {
|
||||
test.fail('No upload cover modal container appeared');
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
CasperTest.begin("User settings screen validates email", 6, function suite(test) {
|
||||
var email, brokenEmail;
|
||||
|
||||
casper.thenOpen(url + "ghost/settings/user/", function testTitleAndUrl() {
|
||||
test.assertTitle("Ghost Admin", "Ghost admin has no title");
|
||||
test.assertUrlMatch(/ghost\/settings\/user\/$/, "Ghost doesn't require login this time");
|
||||
});
|
||||
|
||||
casper.then(function setEmailToInvalid() {
|
||||
email = casper.getElementInfo('#user-email').attributes.value;
|
||||
brokenEmail = email.replace('.', '-');
|
||||
|
||||
casper.fillSelectors('.user-profile', {
|
||||
'#user-email': brokenEmail
|
||||
}, false);
|
||||
});
|
||||
|
||||
casper.thenClick('#user .button-save');
|
||||
|
||||
casper.waitForResource('/users/');
|
||||
|
||||
casper.waitForSelector('.notification-error', function onSuccess() {
|
||||
test.assert(true, 'Got error notification');
|
||||
test.assertSelectorDoesntHaveText('.notification-error', '[object Object]');
|
||||
}, function onTimeout() {
|
||||
test.assert(false, 'No error notification :(');
|
||||
});
|
||||
|
||||
casper.then(function resetEmailToValid() {
|
||||
casper.fillSelectors('.user-profile', {
|
||||
'#user-email': email
|
||||
}, false);
|
||||
});
|
||||
|
||||
casper.thenClick('#user .button-save');
|
||||
|
||||
casper.waitForResource(/users/);
|
||||
|
||||
casper.waitForSelector('.notification-success', function onSuccess() {
|
||||
test.assert(true, 'Got success notification');
|
||||
test.assertSelectorDoesntHaveText('.notification-success', '[object Object]');
|
||||
}, function onTimeout() {
|
||||
test.assert(false, 'No success notification :(');
|
||||
});
|
||||
});
|
||||
|
||||
CasperTest.begin('Ensure postsPerPage number field form validation', 3, function suite(test) {
|
||||
casper.thenOpen(url + "ghost/settings/general/", function testTitleAndUrl() {
|
||||
test.assertTitle("Ghost Admin", "Ghost admin has no title");
|
||||
@ -217,52 +300,6 @@ CasperTest.begin('Ensure postsPerPage min of 0', 3, function suite(test) {
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
CasperTest.begin("User settings screen validates email", 6, function suite(test) {
|
||||
var email, brokenEmail;
|
||||
|
||||
casper.thenOpen(url + "ghost/settings/user/", function testTitleAndUrl() {
|
||||
test.assertTitle("Ghost Admin", "Ghost admin has no title");
|
||||
test.assertUrlMatch(/ghost\/settings\/user\/$/, "Ghost doesn't require login this time");
|
||||
});
|
||||
|
||||
casper.then(function setEmailToInvalid() {
|
||||
email = casper.getElementInfo('#user-email').attributes.value;
|
||||
brokenEmail = email.replace('.', '-');
|
||||
|
||||
casper.fillSelectors('.user-profile', {
|
||||
'#user-email': brokenEmail
|
||||
}, false);
|
||||
});
|
||||
|
||||
casper.thenClick('#user .button-save');
|
||||
|
||||
casper.waitForResource('/users/');
|
||||
|
||||
casper.waitForSelector('.notification-error', function onSuccess() {
|
||||
test.assert(true, 'Got error notification');
|
||||
test.assertSelectorDoesntHaveText('.notification-error', '[object Object]');
|
||||
}, function onTimeout() {
|
||||
test.assert(false, 'No error notification :(');
|
||||
});
|
||||
|
||||
casper.then(function resetEmailToValid() {
|
||||
casper.fillSelectors('.user-profile', {
|
||||
'#user-email': email
|
||||
}, false);
|
||||
});
|
||||
|
||||
casper.thenClick('#user .button-save');
|
||||
|
||||
casper.waitForResource(/users/);
|
||||
|
||||
casper.waitForSelector('.notification-success', function onSuccess() {
|
||||
test.assert(true, 'Got success notification');
|
||||
test.assertSelectorDoesntHaveText('.notification-success', '[object Object]');
|
||||
}, function onTimeout() {
|
||||
test.assert(false, 'No success notification :(');
|
||||
});
|
||||
});
|
||||
|
||||
CasperTest.begin("User settings screen shows remaining characters for Bio properly", 4, function suite(test) {
|
||||
|
||||
function getRemainingBioCharacterCount() {
|
||||
|
@ -171,7 +171,7 @@ describe('Frontend Routing', function () {
|
||||
.end(doEnd(done));
|
||||
});
|
||||
|
||||
it('should redirect to last page is page too high', function (done) {
|
||||
it('should redirect to last page if page too high', function (done) {
|
||||
request.get('/page/4/')
|
||||
.expect('Location', '/page/3/')
|
||||
.expect('Cache-Control', cacheRules['public'])
|
||||
@ -179,7 +179,7 @@ describe('Frontend Routing', function () {
|
||||
.end(doEnd(done));
|
||||
});
|
||||
|
||||
it('should redirect to first page is page too low', function (done) {
|
||||
it('should redirect to first page if page too low', function (done) {
|
||||
request.get('/page/0/')
|
||||
.expect('Location', '/')
|
||||
.expect('Cache-Control', cacheRules['public'])
|
||||
@ -214,7 +214,7 @@ describe('Frontend Routing', function () {
|
||||
.end(doEnd(done));
|
||||
});
|
||||
|
||||
it('should redirect to last page is page too high', function (done) {
|
||||
it('should redirect to last page if page too high', function (done) {
|
||||
request.get('/rss/3/')
|
||||
.expect('Location', '/rss/2/')
|
||||
.expect('Cache-Control', cacheRules['public'])
|
||||
@ -222,7 +222,7 @@ describe('Frontend Routing', function () {
|
||||
.end(doEnd(done));
|
||||
});
|
||||
|
||||
it('should redirect to first page is page too low', function (done) {
|
||||
it('should redirect to first page if page too low', function (done) {
|
||||
request.get('/rss/0/')
|
||||
.expect('Location', '/rss/')
|
||||
.expect('Cache-Control', cacheRules['public'])
|
||||
@ -231,6 +231,49 @@ describe('Frontend Routing', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Tag based RSS pages', function () {
|
||||
it('should redirect without slash', function (done) {
|
||||
request.get('/tag/getting-started/rss')
|
||||
.expect('Location', '/tag/getting-started/rss/')
|
||||
.expect('Cache-Control', cacheRules.year)
|
||||
.expect(301)
|
||||
.end(doEnd(done));
|
||||
});
|
||||
|
||||
it('should respond with xml', function (done) {
|
||||
request.get('/tag/getting-started/rss/')
|
||||
.expect('Content-Type', /xml/)
|
||||
.expect('Cache-Control', cacheRules['public'])
|
||||
.expect(200)
|
||||
.end(doEnd(done));
|
||||
});
|
||||
|
||||
it('should redirect page 1', function (done) {
|
||||
request.get('/tag/getting-started/rss/1/')
|
||||
.expect('Location', '/tag/getting-started/rss/')
|
||||
.expect('Cache-Control', cacheRules['public'])
|
||||
// TODO: This should probably be a 301?
|
||||
.expect(302)
|
||||
.end(doEnd(done));
|
||||
});
|
||||
|
||||
it('should redirect to last page if page too high', function (done) {
|
||||
request.get('/tag/getting-started/rss/2/')
|
||||
.expect('Location', '/tag/getting-started/rss/1/')
|
||||
.expect('Cache-Control', cacheRules['public'])
|
||||
.expect(302)
|
||||
.end(doEnd(done));
|
||||
});
|
||||
|
||||
it('should redirect to first page if page too low', function (done) {
|
||||
request.get('/tag/getting-started/rss/0/')
|
||||
.expect('Location', '/tag/getting-started/rss/')
|
||||
.expect('Cache-Control', cacheRules['public'])
|
||||
.expect(302)
|
||||
.end(doEnd(done));
|
||||
});
|
||||
});
|
||||
|
||||
describe('Static page', function () {
|
||||
it('should redirect without slash', function (done) {
|
||||
request.get('/static-page-test')
|
||||
@ -282,6 +325,13 @@ describe('Frontend Routing', function () {
|
||||
.end(doEnd(done));
|
||||
});
|
||||
|
||||
it('should retrieve default robots.txt', function (done) {
|
||||
request.get('/robots.txt')
|
||||
.expect('Cache-Control', cacheRules.year)
|
||||
.expect(200)
|
||||
.end(doEnd(done));
|
||||
})
|
||||
|
||||
// at the moment there is no image fixture to test
|
||||
// it('should retrieve image assets', function (done) {
|
||||
// request.get('/content/images/some.jpg')
|
||||
@ -335,7 +385,7 @@ describe('Frontend Routing', function () {
|
||||
.end(doEnd(done));
|
||||
});
|
||||
|
||||
it('should redirect to last page is page too high', function (done) {
|
||||
it('should redirect to last page if page too high', function (done) {
|
||||
request.get('/tag/injection/page/4/')
|
||||
.expect('Location', '/tag/injection/page/2/')
|
||||
.expect('Cache-Control', cacheRules['public'])
|
||||
@ -343,7 +393,7 @@ describe('Frontend Routing', function () {
|
||||
.end(doEnd(done));
|
||||
});
|
||||
|
||||
it('should redirect to first page is page too low', function (done) {
|
||||
it('should redirect to first page if page too low', function (done) {
|
||||
request.get('/tag/injection/page/0/')
|
||||
.expect('Location', '/tag/injection/')
|
||||
.expect('Cache-Control', cacheRules['public'])
|
||||
|
7
index.js
7
index.js
@ -2,6 +2,9 @@
|
||||
// Orchestrates the loading of Ghost
|
||||
// When run from command line.
|
||||
|
||||
var ghost = require('./core');
|
||||
var ghost = require('./core'),
|
||||
errors = require('./core/server/errorHandling');
|
||||
|
||||
ghost();
|
||||
ghost().otherwise(function (err) {
|
||||
errors.logErrorAndExit(err, err.context, err.help);
|
||||
});
|
Loading…
Reference in New Issue
Block a user