Merge pull request #6011 from kevinansfield/routable-tags-slugs

Use tag slugs in URLs for tag management and add front-end edit redirect
This commit is contained in:
Hannah Wolfe 2015-11-02 15:32:22 +00:00
commit 90e7d71910
7 changed files with 108 additions and 14 deletions

View File

@ -0,0 +1,17 @@
import ApplicationAdapter from 'ghost/adapters/application';
import Ember from 'ember';
const {isBlank} = Ember;
export default ApplicationAdapter.extend({
buildURL: function (_modelName, _id, _snapshot, _requestType, query) {
let url = this._super(...arguments);
if (query && !isBlank(query.slug)) {
url += `slug/${query.slug}/`;
delete query.slug;
}
return url;
}
});

View File

@ -44,7 +44,7 @@ Router.map(function () {
this.route('settings.general', {path: '/settings/general'});
this.route('settings.tags', {path: '/settings/tags'}, function () {
this.route('tag', {path: ':tag_id'});
this.route('tag', {path: ':tag_slug'});
this.route('new');
});
this.route('settings.labs', {path: '/settings/labs'});

View File

@ -3,7 +3,11 @@ import AuthenticatedRoute from 'ghost/routes/authenticated';
export default AuthenticatedRoute.extend({
model: function (params) {
return this.store.findRecord('tag', params.tag_id);
return this.store.queryRecord('tag', {slug: params.tag_slug});
},
serialize: function (model) {
return {tag_slug: model.get('slug')};
},
// reset the model so that mobile screens react to an empty selectedTag

View File

@ -16,7 +16,7 @@
<section class="tag-list-content settings-tags {{if tagListFocused 'keyboard-focused'}}">
{{#each tags as |tag|}}
<div class="settings-tag">
{{#link-to 'settings.tags.tag' tag.id class="tag-edit-button"}}
{{#link-to 'settings.tags.tag' tag class="tag-edit-button"}}
<span class="tag-title">{{tag.name}}</span>
<span class="label label-default">/{{tag.slug}}</span>
<p class="tag-description">{{tag.description}}</p>

View File

@ -129,6 +129,30 @@ describe('Acceptance: Settings - Tags', function () {
return [200, {'Content-Type': 'application/json'}, JSON.stringify(response)];
});
this.get('/ghost/api/v0.1/tags/slug/tag-two/', function (_request) {
let response = {};
response.tag = {
id: 2,
parent: null,
uuid: '0cade0f9-7a3f-4fd1-a80a-3a1ab7028340',
image: '/content/images/2015/10/tag-2.jpg',
name: 'Tag Two',
slug: 'tag-two',
description: 'Description two.',
meta_title: 'Meta Title Two',
meta_description: 'Meta description two.',
created_at: '2015-09-11T09:44:29.871Z',
created_by: 1,
updated_at: '2015-10-19T16:25:07.756Z',
updated_by: 1,
hidden: false,
post_count: 2
};
return [200, {'Content-Type': 'application/json'}, JSON.stringify(response)];
});
this.put('/ghost/api/v0.1/tags/2/', function (_request) {
let response = {};
@ -237,7 +261,7 @@ describe('Acceptance: Settings - Tags', function () {
andThen(() => {
// it redirects to first tag
expect(currentURL(), 'currentURL').to.equal('/settings/tags/1');
expect(currentURL(), 'currentURL').to.equal('/settings/tags/tag-one');
// it has correct page title
expect(document.title, 'page title').to.equal('Settings - Tags - Test Blog');
@ -253,7 +277,7 @@ describe('Acceptance: Settings - Tags', function () {
.to.equal('Tag One');
// it highlights selected tag
expect(find('a[href="/settings/tags/1"]').hasClass('active'), 'highlights selected tag')
expect(find('a[href="/settings/tags/tag-one"]').hasClass('active'), 'highlights selected tag')
.to.be.true;
// it shows selected tag form
@ -268,10 +292,10 @@ describe('Acceptance: Settings - Tags', function () {
andThen(() => {
// it navigates to selected tag
expect(currentURL(), 'url after clicking tag').to.equal('/settings/tags/2');
expect(currentURL(), 'url after clicking tag').to.equal('/settings/tags/tag-two');
// it highlights selected tag
expect(find('a[href="/settings/tags/2"]').hasClass('active'), 'highlights selected tag')
expect(find('a[href="/settings/tags/tag-two"]').hasClass('active'), 'highlights selected tag')
.to.be.true;
// it shows selected tag form
@ -287,10 +311,10 @@ describe('Acceptance: Settings - Tags', function () {
});
// it navigates to previous tag
expect(currentURL(), 'url after keyboard up arrow').to.equal('/settings/tags/1');
expect(currentURL(), 'url after keyboard up arrow').to.equal('/settings/tags/tag-one');
// it highlights selected tag
expect(find('a[href="/settings/tags/1"]').hasClass('active'), 'selects previous tag')
expect(find('a[href="/settings/tags/tag-one"]').hasClass('active'), 'selects previous tag')
.to.be.true;
});
@ -302,10 +326,10 @@ describe('Acceptance: Settings - Tags', function () {
});
// it navigates to previous tag
expect(currentURL(), 'url after keyboard down arrow').to.equal('/settings/tags/2');
expect(currentURL(), 'url after keyboard down arrow').to.equal('/settings/tags/tag-two');
// it highlights selected tag
expect(find('a[href="/settings/tags/2"]').hasClass('active'), 'selects next tag')
expect(find('a[href="/settings/tags/tag-two"]').hasClass('active'), 'selects next tag')
.to.be.true;
});
@ -345,14 +369,14 @@ describe('Acceptance: Settings - Tags', function () {
andThen(() => {
// it redirects to the new tag's URL
expect(currentURL(), 'URL after tag creation').to.equal('/settings/tags/3');
expect(currentURL(), 'URL after tag creation').to.equal('/settings/tags/tag-three');
// it adds the tag to the list and selects
expect(find('.settings-tags .settings-tag').length, 'tag list count after creation')
.to.equal(3);
expect(find('.settings-tags .settings-tag:last .tag-title').text(), 'new tag list item title')
.to.equal('Tag Three');
expect(find('a[href="/settings/tags/3"]').hasClass('active'), 'highlights new tag')
expect(find('a[href="/settings/tags/tag-three"]').hasClass('active'), 'highlights new tag')
.to.be.true;
});
@ -362,7 +386,7 @@ describe('Acceptance: Settings - Tags', function () {
andThen(() => {
// it redirects to the first tag
expect(currentURL(), 'URL after tag deletion').to.equal('/settings/tags/1');
expect(currentURL(), 'URL after tag deletion').to.equal('/settings/tags/tag-one');
// it removes the tag from the list
expect(find('.settings-tags .settings-tag').length, 'tag list count after deletion')
@ -370,6 +394,26 @@ describe('Acceptance: Settings - Tags', function () {
});
});
it('loads tag via slug when accessed directly', function () {
visit('/settings/tags/tag-two');
andThen(() => {
expect(currentURL(), 'URL after direct load').to.equal('/settings/tags/tag-two');
// it loads all other tags
expect(find('.settings-tags .settings-tag').length, 'tag list count after direct load')
.to.equal(2);
// selects tag in list
expect(find('a[href="/settings/tags/tag-two"]').hasClass('active'), 'highlights requested tag')
.to.be.true;
// shows requested tag in settings pane
expect(find('.tag-settings-pane input[name="name"]').val(), 'loads correct tag into form')
.to.equal('Tag Two');
});
});
it('has infinite scroll pagination of tags list');
});
});

View File

@ -62,6 +62,9 @@ frontendRoutes = function frontendRoutes(middleware) {
// Tags
tagRouter.route('/').get(frontend.tag);
tagRouter.route('/' + routeKeywords.page + '/:page/').get(frontend.tag);
tagRouter.route('/edit?').get(function redirect(req, res) {
res.redirect(subdir + '/ghost/settings/tags/' + req.params.slug + '/');
});
tagRouter.use(rssRouter);
// Authors

View File

@ -756,6 +756,32 @@ describe('Frontend Routing', function () {
});
});
describe('Tag edit', function () {
it('should redirect without slash', function (done) {
request.get('/tag/getting-started/edit')
.expect('Location', '/tag/getting-started/edit/')
.expect('Cache-Control', testUtils.cacheRules.year)
.expect(301)
.end(doEnd(done));
});
it('should redirect to tag settings', function (done) {
request.get('/tag/getting-started/edit/')
.expect('Location', '/ghost/settings/tags/getting-started/')
.expect('Cache-Control', testUtils.cacheRules.public)
.expect(302)
.end(doEnd(done));
});
it('should 404 for non-edit parameter', function (done) {
request.get('/tag/getting-started/notedit/')
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(404)
.expect(/Page not found/)
.end(doEnd(done));
});
});
describe('Subdirectory (no slash)', function () {
var forkedGhost, request;
before(function (done) {