0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-10 23:36:14 -05:00

Merge pull request #1839 from hswolff/1351-post-settings-ux

New Post UX behaviour.
This commit is contained in:
Hannah Wolfe 2014-01-07 02:00:14 -08:00
commit b64511acf3
10 changed files with 172 additions and 74 deletions

View file

@ -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 {

View file

@ -396,6 +396,9 @@
if (rawTitle !== trimmedTitle) {
$title.val(trimmedTitle);
}
// Trigger title change for post-settings.js
this.model.set('title', trimmedTitle);
},
renderTitle: function () {

View file

@ -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')) {
@ -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();
},
@ -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;
}
@ -102,7 +147,7 @@
pubDateMoment,
newPubDateMoment;
// Ignore empty or unchanged dates
// if there is no new pub date do nothing
if (!newPubDate) {
return;
}
@ -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()
@ -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
}, {
@ -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: {

View file

@ -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

View file

@ -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);
}
}, {
@ -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);
}
});

View file

@ -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});
});
@ -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});
});
@ -398,7 +400,6 @@ Post = ghostBookshelf.Model.extend({
return post.destroy(options);
});
}
});
Posts = ghostBookshelf.Collection.extend({

View file

@ -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});
});

View file

@ -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});
});

View file

@ -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));

View file

@ -51,7 +51,7 @@
<label for="url">URL</label>
</td>
<td class="post-setting-field">
<input id="url" class="post-setting-slug" type="text" value="" />
<input id="url" class="post-setting-slug" type="text" placeholder="" value="" />
</td>
</tr>
<tr class="post-setting">
@ -59,7 +59,7 @@
<label for="pub-date">Pub Date</label>
</td>
<td class="post-setting-field">
<input id="pub-date" class="post-setting-date" type="text" value=""><!--<span class="post-setting-calendar"></span>-->
<input id="pub-date" class="post-setting-date" type="text" placeholder="Now" value=""><!--<span class="post-setting-calendar"></span>-->
</td>
</tr>
<tr class="post-setting">