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:
commit
b64511acf3
10 changed files with 172 additions and 74 deletions
|
@ -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 {
|
||||
|
|
|
@ -396,6 +396,9 @@
|
|||
if (rawTitle !== trimmedTitle) {
|
||||
$title.val(trimmedTitle);
|
||||
}
|
||||
|
||||
// Trigger title change for post-settings.js
|
||||
this.model.set('title', trimmedTitle);
|
||||
},
|
||||
|
||||
renderTitle: function () {
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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});
|
||||
});
|
||||
|
|
|
@ -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});
|
||||
});
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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">
|
||||
|
|
Loading…
Add table
Reference in a new issue