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:
commit
be9afc439c
17 changed files with 116 additions and 57 deletions
|
@ -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%);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
========================================================================== */
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 @@
|
|||
}));
|
||||
}
|
||||
});
|
||||
}());
|
||||
}());
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()));
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Add table
Reference in a new issue