Skip to content

Commit

Permalink
Merge pull request #1839 from hswolff/1351-post-settings-ux
Browse files Browse the repository at this point in the history
New Post UX behaviour.
  • Loading branch information
ErisDS committed Jan 7, 2014
2 parents d5b57a9 + a1f64d2 commit b64511a
Show file tree
Hide file tree
Showing 10 changed files with 172 additions and 74 deletions.
14 changes: 14 additions & 0 deletions core/client/assets/sass/layouts/editor.scss
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,20 @@ body.zen {
position: relative;
padding: 0;
z-index: 1;

&.unsaved {
.post-settings-menu {
padding-bottom: 0;

.post-setting:nth-child(3) td {
border-bottom: none;
}

.delete {
display: none;
}
}
}
}

#entry-actions {
Expand Down
3 changes: 3 additions & 0 deletions core/client/views/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,9 @@
if (rawTitle !== trimmedTitle) {
$title.val(trimmedTitle);
}

// Trigger title change for post-settings.js
this.model.set('title', trimmedTitle);
},

renderTitle: function () {
Expand Down
80 changes: 75 additions & 5 deletions core/client/views/post-settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,17 @@
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);
}
},

render: function () {
var slug = this.model ? this.model.get('slug') : '',
pubDate = this.model ? this.model.get('published_at') : 'Not Published',
$pubDateEl = this.$('.post-setting-date');
$pubDateEl = this.$('.post-setting-date'),
$postSettingSlugEl = this.$('.post-setting-slug');

$('.post-setting-slug').val(slug);
$postSettingSlugEl.val(slug);

// Update page status test if already a page.
if (this.model && this.model.get('page')) {
Expand All @@ -46,9 +48,42 @@
this.$('.delete').removeClass('hidden');
}

// Apply different style for model's that aren't
// yet persisted to the server.
// Mostly we're hiding the delete post UI
if (this.model.id === undefined) {
this.$el.addClass('unsaved');
} else {
this.$el.removeClass('unsaved');
}

$pubDateEl.val(pubDate);
},

// Requests a new slug when the title was changed
updateSlugPlaceholder: function () {
var title = this.model.get('title'),
$postSettingSlugEl = this.$('.post-setting-slug');

// If there's a title present we want to
// validate it against existing slugs in the db
// and then update the placeholder value.
if (title) {
$.ajax({
url: Ghost.paths.apiRoot + '/posts/getSlug/' + encodeURIComponent(title) + '/',
success: function (result) {
$postSettingSlugEl.attr('placeholder', result);
}
});
} else {
// If there's no title set placeholder to blank
// and don't make an ajax request to server
// for a proper slug (as there won't be any).
$postSettingSlugEl.attr('placeholder', '');
return;
}
},

selectSlug: function (e) {
e.currentTarget.select();
},
Expand All @@ -60,8 +95,18 @@
slugEl = e.currentTarget,
newSlug = slugEl.value;

// Ignore empty or unchanged slugs
if (newSlug.length === 0 || slug === newSlug) {
// If the model doesn't currently
// exist on the server (aka has no id)
// then just update the model's value
if (self.model.id === undefined) {
this.model.set({
slug: newSlug
});
return;
}

// Ignore unchanged slugs
if (slug === newSlug) {
slugEl.value = slug === undefined ? '' : slug;
return;
}
Expand Down Expand Up @@ -102,7 +147,7 @@
pubDateMoment,
newPubDateMoment;

// Ignore empty or unchanged dates
// if there is no new pub date do nothing
if (!newPubDate) {
return;
}
Expand Down Expand Up @@ -155,6 +200,16 @@
return;
}

// If the model doesn't currently
// exist on the server (aka has no id)
// then just update the model's value
if (self.model.id === undefined) {
this.model.set({
published_at: newPubDateMoment.toDate()
});
return;
}

// Save new 'Published' date
this.model.save({
published_at: newPubDateMoment.toDate()
Expand Down Expand Up @@ -183,6 +238,16 @@
var pageEl = $(e.currentTarget),
page = pageEl.prop('checked');

// Don't try to save
// if the model doesn't currently
// exist on the server
if (this.model.id === undefined) {
this.model.set({
page: page
});
return;
}

this.model.save({
page: page
}, {
Expand All @@ -209,6 +274,11 @@
deletePost: function (e) {
e.preventDefault();
var self = this;
// You can't delete a post
// that hasn't yet been saved
if (this.model.id === undefined) {
return;
}
this.addSubview(new Ghost.Views.Modal({
model: {
options: {
Expand Down
9 changes: 9 additions & 0 deletions core/server/api/posts.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@ posts = {
});
},

getSlug: function getSlug(args) {
return dataProvider.Base.Model.generateSlug(dataProvider.Post, args.title, {status: 'all'}).then(function (slug) {
if (slug) {
return slug;
}
return when.reject({errorCode: 500, message: 'Could not generate slug'});
});
},

// #### Edit

// **takes:** a json object with all the properties which should be updated
Expand Down
122 changes: 61 additions & 61 deletions core/server/models/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,67 +93,6 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({

sanitize: function (attr) {
return sanitize(this.get(attr)).xss();
},

// #### generateSlug
// Create a string act as the permalink for an object.
generateSlug: function (Model, base, readOptions) {
var slug,
slugTryCount = 1,
// Look for a post with a matching slug, append an incrementing number if so
checkIfSlugExists = function (slugToFind) {
var args = {slug: slugToFind};
//status is needed for posts
if (readOptions && readOptions.status) {
args.status = readOptions.status;
}
return Model.findOne(args, readOptions).then(function (found) {
var trimSpace;

if (!found) {
return when.resolve(slugToFind);
}

slugTryCount += 1;

// If this is the first time through, add the hyphen
if (slugTryCount === 2) {
slugToFind += '-';
} else {
// Otherwise, trim the number off the end
trimSpace = -(String(slugTryCount - 1).length);
slugToFind = slugToFind.slice(0, trimSpace);
}

slugToFind += slugTryCount;

return checkIfSlugExists(slugToFind);
});
};

// Remove URL reserved chars: `:/?#[]@!$&'()*+,;=` as well as `\%<>|^~£"`
slug = base.trim().replace(/[:\/\?#\[\]@!$&'()*+,;=\\%<>\|\^~£"]/g, '')
// Replace dots and spaces with a dash
.replace(/(\s|\.)/g, '-')
// Convert 2 or more dashes into a single dash
.replace(/-+/g, '-')
// Make the whole thing lowercase
.toLowerCase();

// 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)$/g
.test(slug) ? slug + '-post' : slug;

//if slug is empty after trimming use "post"
if (!slug) {
slug = 'post';
}
// Test for duplicate slugs.
return checkIfSlugExists(slug);
}

}, {
Expand Down Expand Up @@ -236,6 +175,67 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({

'delete': function () {
return this.destroy.apply(this, arguments);
},

// #### generateSlug
// Create a string act as the permalink for an object.
generateSlug: function (Model, base, readOptions) {
var slug,
slugTryCount = 1,
// Look for a post with a matching slug, append an incrementing number if so
checkIfSlugExists = function (slugToFind) {
var args = {slug: slugToFind};
//status is needed for posts
if (readOptions && readOptions.status) {
args.status = readOptions.status;
}
return Model.findOne(args, readOptions).then(function (found) {
var trimSpace;

if (!found) {
return when.resolve(slugToFind);
}

slugTryCount += 1;

// If this is the first time through, add the hyphen
if (slugTryCount === 2) {
slugToFind += '-';
} else {
// Otherwise, trim the number off the end
trimSpace = -(String(slugTryCount - 1).length);
slugToFind = slugToFind.slice(0, trimSpace);
}

slugToFind += slugTryCount;

return checkIfSlugExists(slugToFind);
});
};

// Remove URL reserved chars: `:/?#[]@!$&'()*+,;=` as well as `\%<>|^~£"`
slug = base.trim().replace(/[:\/\?#\[\]@!$&'()*+,;=\\%<>\|\^~£"]/g, '')
// Replace dots and spaces with a dash
.replace(/(\s|\.)/g, '-')
// Convert 2 or more dashes into a single dash
.replace(/-+/g, '-')
// Make the whole thing lowercase
.toLowerCase();

// 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)$/g
.test(slug) ? slug + '-post' : slug;

//if slug is empty after trimming use "post"
if (!slug) {
slug = 'post';
}
// Test for duplicate slugs.
return checkIfSlugExists(slug);
}

});
Expand Down
7 changes: 4 additions & 3 deletions core/server/models/post.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ Post = ghostBookshelf.Model.extend({

if (this.hasChanged('slug')) {
// Pass the new slug through the generator to strip illegal characters, detect duplicates
return this.generateSlug(Post, this.get('slug'), {status: 'all', transacting: options.transacting})
return ghostBookshelf.Model.generateSlug(Post, this.get('slug'), {status: 'all', transacting: options.transacting})
.then(function (slug) {
self.set({slug: slug});
});
Expand All @@ -85,9 +85,11 @@ Post = ghostBookshelf.Model.extend({

ghostBookshelf.Model.prototype.creating.call(this);

// We require a slug be set when creating a new post
// as the database doesn't allow null slug values.
if (!this.get('slug')) {
// Generating a slug requires a db call to look for conflicting slugs
return this.generateSlug(Post, this.get('title'), {status: 'all', transacting: options.transacting})
return ghostBookshelf.Model.generateSlug(Post, this.get('title'), {status: 'all', transacting: options.transacting})
.then(function (slug) {
self.set({slug: slug});
});
Expand Down Expand Up @@ -398,7 +400,6 @@ Post = ghostBookshelf.Model.extend({
return post.destroy(options);
});
}

});

Posts = ghostBookshelf.Collection.extend({
Expand Down
2 changes: 1 addition & 1 deletion core/server/models/tag.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Tag = ghostBookshelf.Model.extend({

if (!this.get('slug')) {
// Generating a slug requires a db call to look for conflicting slugs
return this.generateSlug(Tag, this.get('name'))
return ghostBookshelf.Model.generateSlug(Tag, this.get('name'))
.then(function (slug) {
self.set({slug: slug});
});
Expand Down
2 changes: 1 addition & 1 deletion core/server/models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ User = ghostBookshelf.Model.extend({

if (!this.get('slug')) {
// Generating a slug requires a db call to look for conflicting slugs
return this.generateSlug(User, this.get('name'))
return ghostBookshelf.Model.generateSlug(User, this.get('name'))
.then(function (slug) {
self.set({slug: slug});
});
Expand Down
1 change: 1 addition & 0 deletions core/server/routes/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module.exports = function (server) {
server.get('/ghost/api/v0.1/posts/:id', middleware.authAPI, api.requestHandler(api.posts.read));
server.put('/ghost/api/v0.1/posts/:id', middleware.authAPI, api.requestHandler(api.posts.edit));
server.del('/ghost/api/v0.1/posts/:id', middleware.authAPI, api.requestHandler(api.posts.destroy));
server.get('/ghost/api/v0.1/posts/getSlug/:title', middleware.authAPI, api.requestHandler(api.posts.getSlug));
// #### Settings
server.get('/ghost/api/v0.1/settings/', middleware.authAPI, api.requestHandler(api.settings.browse));
server.get('/ghost/api/v0.1/settings/:key/', middleware.authAPI, api.requestHandler(api.settings.read));
Expand Down

0 comments on commit b64511a

Please sign in to comment.