0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-01-20 22:42:53 -05:00

Merge branch '0.4-maintenance'

This commit is contained in:
Hannah Wolfe 2014-01-28 09:25:38 +00:00
commit be9afc439c
17 changed files with 116 additions and 57 deletions

View file

@ -55,7 +55,7 @@
1. Login
============================================================================= */
#login {
.login-form {
@include box-sizing(border-box);
max-width: 530px;
color: lighten($midgrey, 15%);
@ -182,7 +182,7 @@
2. Signup and Reset
============================================================================= */
#signup, #reset {
.signup-form, .reset-form {
@include box-sizing(border-box);
max-width: 280px;
color: lighten($midgrey, 15%);
@ -270,7 +270,7 @@
3. Forgotten
============================================================================= */
#forgotten {
.forgotten-form {
@include box-sizing(border-box);
max-width: 280px;
color: lighten($midgrey, 15%);

View file

@ -18,7 +18,7 @@
.editor {
#notifications {
.notifications {
@include breakpoint($biggerthan-mobile) {
bottom: 40px;
}
@ -376,7 +376,7 @@
body.zen {
background: lighten($lightbrown, 3%);
#usermenu {display: none;}
.usermenu {display: none;}
#global-header, #publish-bar {
opacity: 0;
height: 0;

View file

@ -540,7 +540,7 @@ nav {
}//.navbar
// The user menu in the top right corner of the screen
#usermenu {
.usermenu.subnav {
position:absolute;
top:0;
right:0;
@ -636,7 +636,7 @@ nav {
}
#usermenu {
.usermenu {
position:fixed;
top:0;
right:auto;
@ -928,7 +928,7 @@ nav {
Notifications
========================================================================== */
#notifications {
.notifications {
@include breakpoint($biggerthan-mobile) {
position: absolute;
bottom: 0;
@ -1028,6 +1028,12 @@ nav {
background: $blue;
}
// Hide extra space taken up by update notification
.update-available main {
bottom: 56px;
}
/* ==========================================================================
Modals
========================================================================== */

View file

@ -1,4 +1,4 @@
<form id="forgotten" method="post" novalidate="novalidate">
<form id="forgotten" class="forgotten-form" method="post" novalidate="novalidate">
<div class="email-wrap">
<input class="email" type="email" placeholder="Email Address" name="email" autocapitalize="off" autocorrect="off">
</div>

View file

@ -1,4 +1,4 @@
<form id="login" method="post" novalidate="novalidate">
<form id="login" class="login-form" method="post" novalidate="novalidate">
<div class="email-wrap">
<input class="email" type="email" placeholder="Email Address" name="email" autocapitalize="off" autocorrect="off">
</div>

View file

@ -1,4 +1,4 @@
<form id="reset" method="post" novalidate="novalidate">
<form id="reset" class="reset-form" method="post" novalidate="novalidate">
<div class="password-wrap">
<input class="password" type="password" placeholder="Password" name="newpassword" />
</div>

View file

@ -1,4 +1,4 @@
<form id="signup" method="post" novalidate="novalidate">
<form id="signup" class="signup-form" method="post" novalidate="novalidate">
<div class="name-wrap">
<input class="name" type="text" placeholder="Full Name" name="name" autocorrect="off" />
</div>

View file

@ -5,12 +5,17 @@
Ghost.Views.Debug = Ghost.View.extend({
events: {
"click .settings-menu a": "handleMenuClick",
"click #startupload": "handleUploadClick",
"click .js-delete": "handleDeleteClick"
},
initialize: function () {
var view = this;
this.uploadButton = this.$el.find('#startupload');
// Disable import button and initizalize BlueImp file upload
$('#startupload').prop('disabled', true);
this.uploadButton.prop('disabled', 'disabled');
$('#importfile').fileupload({
url: Ghost.paths.apiRoot + '/db/',
limitMultiFileUploads: 1,
@ -21,16 +26,12 @@
dataType: 'json',
add: function (e, data) {
/*jslint unparam:true*/
// unregister click event to preveng duplicate binding
$('#startupload').off("click");
data.context = $('#startupload').prop('disabled', false)
.click(function () {
$('#startupload').prop('disabled', true);
data.context = $('#startupload').text('Importing');
data.submit();
// unregister click event to allow different subsequent uploads
$('#startupload').off('click');
});
// Bind the upload data to the view, so it is
// available to the click handler, and enable the
// upload button.
view.fileUploadData = data;
data.context = view.uploadButton.removeProp('disabled');
},
done: function (e, data) {
/*jslint unparam:true*/
@ -77,6 +78,18 @@
return false;
},
handleUploadClick: function (ev) {
ev.preventDefault();
if (!this.uploadButton.prop('disabled')) {
this.fileUploadData.context = this.uploadButton.text('Importing');
this.fileUploadData.submit();
}
// Prevent double post by disabling the button.
this.uploadButton.prop('disabled', 'disabled');
},
handleDeleteClick: function (ev) {
ev.preventDefault();
this.addSubview(new Ghost.Views.Modal({
@ -141,4 +154,4 @@
}));
}
});
}());
}());

View file

@ -64,6 +64,7 @@
Ghost.Views.Signup = Ghost.View.extend({
initialize: function () {
this.submitted = "no";
this.render();
},
@ -95,10 +96,12 @@
Ghost.Validate.check(name, "Please enter a name").len(1);
Ghost.Validate.check(email, "Please enter a correct email address").isEmail();
Ghost.Validate.check(password, "Your password is not long enough. It must be at least 8 characters long.").len(8);
Ghost.Validate.check(this.submitted, "Ghost is signing you up. Please wait...").equals("no");
if (Ghost.Validate._errors.length > 0) {
Ghost.Validate.handleErrors();
} else {
this.submitted = "yes";
$.ajax({
url: Ghost.paths.subdir + '/ghost/signup/',
type: 'POST',
@ -114,6 +117,7 @@
window.location.href = msg.redirect;
},
error: function (xhr) {
this.submitted = "no";
Ghost.notifications.clearEverything();
Ghost.notifications.addItem({
type: 'error',

View file

@ -5,6 +5,10 @@
(function () {
"use strict";
var parseDateFormats = ['DD MMM YY HH:mm', 'DD MMM YYYY HH:mm', 'DD/MM/YY HH:mm', 'DD/MM/YYYY HH:mm',
'DD-MM-YY HH:mm', 'DD-MM-YYYY HH:mm'],
displayDateFormat = 'DD MMM YY @ HH:mm';
Ghost.View.PostSettings = Ghost.View.extend({
events: {
@ -17,11 +21,10 @@
initialize: function () {
if (this.model) {
// These three items can be updated outside of the post settings menu, so have to be listened to.
this.listenTo(this.model, 'change:id', this.render);
this.listenTo(this.model, 'change:status', this.render);
this.listenTo(this.model, 'change:published_at', this.render);
this.listenTo(this.model, 'change:page', this.render);
this.listenTo(this.model, 'change:title', this.updateSlugPlaceholder);
this.listenTo(this.model, 'change:published_at', this.updatePublishedDate);
}
},
@ -29,8 +32,7 @@
var slug = this.model ? this.model.get('slug') : '',
pubDate = this.model ? this.model.get('published_at') : 'Not Published',
$pubDateEl = this.$('.post-setting-date'),
$postSettingSlugEl = this.$('.post-setting-slug'),
publishedDateFormat = 'DD MMM YY @ HH:mm';
$postSettingSlugEl = this.$('.post-setting-slug');
$postSettingSlugEl.val(slug);
@ -41,10 +43,10 @@
// Insert the published date, and make it editable if it exists.
if (this.model && this.model.get('published_at')) {
pubDate = moment(pubDate).format(publishedDateFormat);
pubDate = moment(pubDate).format(displayDateFormat);
$pubDateEl.attr('placeholder', '');
} else {
$pubDateEl.attr('placeholder', moment().format(publishedDateFormat));
$pubDateEl.attr('placeholder', moment().format(displayDateFormat));
}
if (this.model && this.model.get('id')) {
@ -130,6 +132,7 @@
},
error : function (model, xhr) {
/*jslint unparam:true*/
slugEl.value = model.previous('slug');
Ghost.notifications.addItem({
type: 'error',
message: Ghost.Views.Utils.getRequestErrorMessage(xhr),
@ -139,13 +142,23 @@
});
}, 500),
updatePublishedDate: function () {
var pubDate = this.model.get('published_at') ? moment(this.model.get('published_at'))
.format(displayDateFormat) : '',
$pubDateEl = this.$('.post-setting-date');
// Only change the date if it's different
if (pubDate && $pubDateEl.val() !== pubDate) {
$pubDateEl.val(pubDate);
}
},
editDate: _.debounce(function (e) {
e.preventDefault();
var self = this,
parseDateFormats = ['DD MMM YY HH:mm', 'DD MMM YYYY HH:mm', 'DD/MM/YY HH:mm', 'DD/MM/YYYY HH:mm', 'DD-MM-YY HH:mm', 'DD-MM-YYYY HH:mm'],
displayDateFormat = 'DD MMM YY @ HH:mm',
errMessage = '',
pubDate = self.model.get('published_at'),
pubDate = moment(self.model.get('published_at')).format(displayDateFormat),
pubDateEl = e.currentTarget,
newPubDate = pubDateEl.value,
pubDateMoment,
@ -228,6 +241,8 @@
},
error : function (model, xhr) {
/*jslint unparam:true*/
// Reset back to original value
pubDateEl.value = pubDateMoment ? pubDateMoment.format(displayDateFormat) : '';
Ghost.notifications.addItem({
type: 'error',
message: Ghost.Views.Utils.getRequestErrorMessage(xhr),
@ -266,6 +281,7 @@
},
error : function (model, xhr) {
/*jslint unparam:true*/
pageEl.prop('checked', model.previous('page'));
Ghost.notifications.addItem({
type: 'error',
message: Ghost.Views.Utils.getRequestErrorMessage(xhr),

View file

@ -562,7 +562,7 @@ coreHelpers.adminUrl = function (options) {
return config.paths.urlFor(context, absolute);
};
coreHelpers.updateNotification = function () {
coreHelpers.updateNotification = function (options) {
var output = '';
if (config().updateCheck === false || !this.currentUser) {
@ -571,9 +571,13 @@ coreHelpers.updateNotification = function () {
return updateCheck.showUpdateNotification().then(function (result) {
if (result) {
output = '<div class="notification-success">' +
'A new version of Ghost is available! Hot damn. ' +
'<a href="http://ghost.org/download">Upgrade now</a></div>';
if (options && options.hash && options.hash.classOnly) {
output = ' update-available';
} else {
output = '<div class="notification-success">' +
'A new version of Ghost is available! Hot damn. ' +
'<a href="http://ghost.org/download">Upgrade now</a></div>';
}
}
return output;

View file

@ -183,11 +183,7 @@ function isSSLrequired(isAdmin) {
// and redirect if needed
function checkSSL(req, res, next) {
if (isSSLrequired(res.isAdmin)) {
// Check if X-Forarded-Proto headers are sent, if they are check for https.
// If they are not assume true to avoid infinite redirect loop.
// If the X-Forwarded-Proto header is missing and Express cannot automatically sense HTTPS the redirect will not be made.
var httpsHeader = req.header('X-Forwarded-Proto') !== undefined ? req.header('X-Forwarded-Proto').toLowerCase() === 'https' ? true : false : true;
if (!req.secure && !httpsHeader) {
if (!req.secure) {
return res.redirect(301, url.format({
protocol: 'https:',
hostname: url.parse(config().url).hostname,
@ -208,6 +204,10 @@ module.exports = function (server, dbHash) {
expressServer = server;
middleware.cacheServer(expressServer);
// Make sure 'req.secure' is valid for proxied requests
// (X-Forwarded-Proto header will be checked, if present)
expressServer.enable('trust proxy');
// Logging configuration
if (expressServer.get('env') !== 'development') {
expressServer.use(express.logger());
@ -226,13 +226,16 @@ module.exports = function (server, dbHash) {
// First determine whether we're serving admin or theme content
expressServer.use(manageAdminAndTheme);
// Force SSL
expressServer.use(checkSSL);
// Admin only config
expressServer.use(subdir + '/ghost', middleware.whenEnabled('admin', express['static'](path.join(corePath, '/client/assets'), {maxAge: ONE_YEAR_MS})));
// Force SSL
// NOTE: Importantly this is _after_ the check above for admin-theme static resources,
// which do not need HTTPS. In fact, if HTTPS is forced on them, then 404 page might
// not display properly when HTTPS is not available!
expressServer.use(checkSSL);
// Theme only config
expressServer.use(subdir, middleware.whenEnabled(expressServer.get('activeTheme'), middleware.staticTheme()));

View file

@ -213,8 +213,13 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({
});
};
slug = base.trim();
// Remove non ascii characters
slug = unidecode(slug);
// Remove URL reserved chars: `:/?#[]@!$&'()*+,;=` as well as `\%<>|^~£"`
slug = base.trim().replace(/[:\/\?#\[\]@!$&'()*+,;=\\%<>\|\^~£"]/g, '')
slug = slug.replace(/[:\/\?#\[\]@!$&'()*+,;=\\%<>\|\^~£"]/g, '')
// Replace dots and spaces with a dash
.replace(/(\s|\.)/g, '-')
// Convert 2 or more dashes into a single dash
@ -224,8 +229,7 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({
// Remove trailing hyphen
slug = slug.charAt(slug.length - 1) === '-' ? slug.substr(0, slug.length - 1) : slug;
// Remove non ascii characters
slug = unidecode(slug);
// Check the filtered slug doesn't match any of the reserved keywords
slug = /^(ghost|ghost\-admin|admin|wp\-admin|wp\-login|dashboard|logout|login|signin|signup|signout|register|archive|archives|category|categories|tag|tags|page|pages|post|posts|user|users|rss)$/g
.test(slug) ? slug + '-post' : slug;

View file

@ -31,7 +31,7 @@
<link rel="stylesheet" href="{{asset "css/screen.css" ghost="true"}}">
{{{block "pageStyles"}}}
</head>
<body class="{{bodyClass}}">
<body class="{{bodyClass}}{{updateNotification classOnly="true"}}">
{{#unless hideNavbar}}
{{> navbar}}
{{/unless}}
@ -39,7 +39,7 @@
<main role="main" id="main">
{{updateNotification}}
<aside id="notifications">
<aside id="notifications" class="notifications">
{{> notifications}}
</aside>

View file

@ -8,7 +8,7 @@
<li class="{{navClass}}{{#if selected}} active{{/if}}"><a href="{{adminUrl}}{{path}}">{{name}}</a></li>
{{/each}}
<li id="usermenu" class="subnav">
<li id="usermenu" class="usermenu subnav">
<a href="#" data-toggle="ul" class="dropdown">
<img class="avatar" src="{{#if currentUser.image}}{{currentUser.image}}{{else}}{{asset "shared/img/user-image.png"}}{{/if}}" alt="Avatar" />
<span class="name">{{#if currentUser.name}}{{currentUser.name}}{{else}}{{currentUser.email}}{{/if}}</span>

View file

@ -940,21 +940,30 @@ describe('Core Helpers', function () {
});
describe('updateNotification', function () {
it('outputs a correctly formatted notification when db version is higher than package version', function (done) {
var output = '<div class="notification-success">' +
var defaultOutput = '<div class="notification-success">' +
'A new version of Ghost is available! Hot damn. ' +
'<a href="http://ghost.org/download">Upgrade now</a></div>';
'<a href="http://ghost.org/download">Upgrade now</a></div>',
classOutput = ' update-available';
apiStub.restore();
apiStub = sandbox.stub(api.settings, 'read', function () {
var futureversion = packageInfo.version.split('.');
futureversion[futureversion.length-1] = parseInt(futureversion[futureversion.length-1], 10) + 1;
futureversion[futureversion.length - 1] = parseInt(futureversion[futureversion.length - 1], 10) + 1;
return when({value: futureversion.join('.')});
});
helpers.updateNotification.call({currentUser: {name: 'bob'}}).then(function (rendered) {
should.exist(rendered);
rendered.should.equal(output);
rendered.should.equal(defaultOutput);
// Test classOnly option
return helpers.updateNotification.call({currentUser: {name: 'bob'}}, {'hash': {'classOnly': 'true'}});
}).then(function (rendered) {
should.exist(rendered);
rendered.should.equal(classOutput);
done();
}).then(null, done);
});

View file

@ -1,6 +1,6 @@
{
"name" : "ghost",
"version" : "0.4.0",
"version" : "0.4.1-rc1",
"description" : "Just a blogging platform.",
"author" : "Ghost Foundation",
"homepage" : "http://ghost.org",