mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-24 23:48:13 -05:00
Correct behavior on updating user slug.
No Issue - Defer save until after slug is checked. - If new slug is empty or all whitespace, reset to previous value. - If new slug is the same as existing slug except for an increment value (e.g. ghost-user-2), use the original slug. - If the slug has changed, change the URL path to reflect the change so that the browser refresh and back button still work. - Added tests.
This commit is contained in:
parent
153a0c818f
commit
bb50791dce
2 changed files with 73 additions and 37 deletions
|
@ -2,16 +2,14 @@ import SlugGenerator from 'ghost/models/slug-generator';
|
||||||
|
|
||||||
var SettingsUserController = Ember.ObjectController.extend({
|
var SettingsUserController = Ember.ObjectController.extend({
|
||||||
|
|
||||||
_lastSlug: null,
|
|
||||||
|
|
||||||
updateLastSlug: Ember.observer(function () {
|
|
||||||
this.set('_lastSlug', this.get('user.slug'));
|
|
||||||
}),
|
|
||||||
|
|
||||||
user: Ember.computed.alias('model'),
|
user: Ember.computed.alias('model'),
|
||||||
|
|
||||||
email: Ember.computed.readOnly('user.email'),
|
email: Ember.computed.readOnly('user.email'),
|
||||||
|
|
||||||
|
slugValue: Ember.computed.oneWay('user.slug'),
|
||||||
|
|
||||||
|
lastPromise: null,
|
||||||
|
|
||||||
coverDefault: function () {
|
coverDefault: function () {
|
||||||
return this.get('ghostPaths.url').asset('/shared/img/user-cover.png');
|
return this.get('ghostPaths.url').asset('/shared/img/user-cover.png');
|
||||||
}.property('ghostPaths'),
|
}.property('ghostPaths'),
|
||||||
|
@ -106,15 +104,43 @@ var SettingsUserController = Ember.ObjectController.extend({
|
||||||
|
|
||||||
save: function () {
|
save: function () {
|
||||||
var user = this.get('user'),
|
var user = this.get('user'),
|
||||||
|
slugValue = this.get('slugValue'),
|
||||||
|
afterUpdateSlug = this.get('lastPromise'),
|
||||||
|
promise,
|
||||||
|
slugChanged,
|
||||||
self = this;
|
self = this;
|
||||||
|
|
||||||
user.save({ format: false }).then(function (model) {
|
if (user.get('slug') !== slugValue) {
|
||||||
|
slugChanged = true;
|
||||||
|
user.set('slug', slugValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
promise = Ember.RSVP.resolve(afterUpdateSlug).then(function () {
|
||||||
|
return user.save({ format: false });
|
||||||
|
}).then(function (model) {
|
||||||
|
var currentPath,
|
||||||
|
newPath;
|
||||||
|
|
||||||
self.notifications.showSuccess('Settings successfully saved.');
|
self.notifications.showSuccess('Settings successfully saved.');
|
||||||
|
|
||||||
|
// If the user's slug has changed, change the URL and replace
|
||||||
|
// the history so refresh and back button still work
|
||||||
|
if (slugChanged) {
|
||||||
|
currentPath = window.history.state.path;
|
||||||
|
|
||||||
|
newPath = currentPath.split('/');
|
||||||
|
newPath[newPath.length - 2] = model.get('slug');
|
||||||
|
newPath = newPath.join('/');
|
||||||
|
|
||||||
|
window.history.replaceState({ path: newPath }, '', newPath);
|
||||||
|
}
|
||||||
|
|
||||||
return model;
|
return model;
|
||||||
}).catch(function (errors) {
|
}).catch(function (errors) {
|
||||||
self.notifications.showErrors(errors);
|
self.notifications.showErrors(errors);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.set('lastPromise', promise);
|
||||||
},
|
},
|
||||||
|
|
||||||
password: function () {
|
password: function () {
|
||||||
|
@ -143,46 +169,57 @@ var SettingsUserController = Ember.ObjectController.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
updateSlug: function (newSlug) {
|
updateSlug: function (newSlug) {
|
||||||
var slug = this.get('_lastSlug'),
|
var self = this,
|
||||||
self = this;
|
afterSave = this.get('lastPromise'),
|
||||||
|
promise;
|
||||||
|
|
||||||
newSlug = newSlug || slug;
|
promise = Ember.RSVP.resolve(afterSave).then(function () {
|
||||||
|
var slug = self.get('slug');
|
||||||
|
|
||||||
newSlug = newSlug.trim();
|
newSlug = newSlug || slug;
|
||||||
|
|
||||||
// Ignore unchanged slugs or candidate slugs that are empty
|
newSlug = newSlug.trim();
|
||||||
if (!newSlug || slug === newSlug) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.get('slugGenerator').generateSlug(newSlug).then(function (serverSlug) {
|
// Ignore unchanged slugs or candidate slugs that are empty
|
||||||
|
if (!newSlug || slug === newSlug) {
|
||||||
|
self.set('slugValue', slug);
|
||||||
|
|
||||||
// If after getting the sanitized and unique slug back from the API
|
|
||||||
// we end up with a slug that matches the existing slug, abort the change
|
|
||||||
if (serverSlug === slug) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Because the server transforms the candidate slug by stripping
|
return self.get('slugGenerator').generateSlug(newSlug).then(function (serverSlug) {
|
||||||
// certain characters and appending a number onto the end of slugs
|
|
||||||
// to enforce uniqueness, there are cases where we can get back a
|
|
||||||
// candidate slug that is a duplicate of the original except for
|
|
||||||
// the trailing incrementor (e.g., this-is-a-slug and this-is-a-slug-2)
|
|
||||||
|
|
||||||
// get the last token out of the slug candidate and see if it's a number
|
// If after getting the sanitized and unique slug back from the API
|
||||||
var slugTokens = serverSlug.split('-'),
|
// we end up with a slug that matches the existing slug, abort the change
|
||||||
check = Number(slugTokens.pop());
|
if (serverSlug === slug) {
|
||||||
|
|
||||||
// if the candidate slug is the same as the existing slug except
|
|
||||||
// for the incrementor then the existing slug should be used
|
|
||||||
if (_.isNumber(check) && check > 0) {
|
|
||||||
if (slug === slugTokens.join('-') && serverSlug !== newSlug) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
self.set('_lastSlug', serverSlug);
|
// Because the server transforms the candidate slug by stripping
|
||||||
|
// certain characters and appending a number onto the end of slugs
|
||||||
|
// to enforce uniqueness, there are cases where we can get back a
|
||||||
|
// candidate slug that is a duplicate of the original except for
|
||||||
|
// the trailing incrementor (e.g., this-is-a-slug and this-is-a-slug-2)
|
||||||
|
|
||||||
|
// get the last token out of the slug candidate and see if it's a number
|
||||||
|
var slugTokens = serverSlug.split('-'),
|
||||||
|
check = Number(slugTokens.pop());
|
||||||
|
|
||||||
|
// if the candidate slug is the same as the existing slug except
|
||||||
|
// for the incrementor then the existing slug should be used
|
||||||
|
if (_.isNumber(check) && check > 0) {
|
||||||
|
if (slug === slugTokens.join('-') && serverSlug !== newSlug) {
|
||||||
|
self.set('slugValue', slug);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.set('slugValue', serverSlug);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.set('lastPromise', promise);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -60,9 +60,8 @@
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="user-slug">Slug</label>
|
<label for="user-slug">Slug</label>
|
||||||
{{!-- {{input value=user.slug id="user-slug" placeholder="Slug" autocorrect="off"}} --}}
|
{{gh-blur-input class="user-name" id="user-slug" value=slugValue name="user" action="updateSlug" placeholder="Slug" selectOnClick="true" autocorrect="off"}}
|
||||||
{{gh-blur-input class="user-name" id="user-slug" value=user.slug name="user" action="updateSlug" placeholder="Slug" selectOnClick="true" autocorrect="off"}}
|
<p>{{gh-blog-url}}/author/{{slugValue}}</p>
|
||||||
<p>{{gh-blog-url}}/author/{{user.slug}}</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|
Loading…
Add table
Reference in a new issue