Skip to content
This repository has been archived by the owner on Nov 28, 2022. It is now read-only.

Commit

Permalink
add dropdown filters for post type, author, and tag (#554)
Browse files Browse the repository at this point in the history
refs TryGhost/Ghost#7860
- removes post type filter links
- adds dropdown filters for post type, author, and tag
- replaces custom refresh on query params change with Ember's standard `refreshModel` config
  • Loading branch information
kevinansfield authored and acburdine committed Mar 2, 2017
1 parent ffa1afb commit dc351d3
Show file tree
Hide file tree
Showing 13 changed files with 305 additions and 90 deletions.
16 changes: 16 additions & 0 deletions app/controllers/posts-loading.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import Controller from 'ember-controller';
import {readOnly} from 'ember-computed';
import injectController from 'ember-controller/inject';

export default Controller.extend({

postsController: injectController('posts'),

availableTypes: readOnly('postsController.availableTypes'),
selectedType: readOnly('postsController.selectedType'),
availableTags: readOnly('postsController.availableTags'),
selectedTag: readOnly('postsController.selectedTag'),
availableAuthors: readOnly('postsController.availableAuthors'),
selectedAuthor: readOnly('postsController.selectedAuthor')

});
91 changes: 87 additions & 4 deletions app/controllers/posts.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,106 @@
import Controller from 'ember-controller';
import computed from 'ember-computed';
import injectService from 'ember-service/inject';
import get from 'ember-metal/get';

export default Controller.extend({

queryParams: ['type'],
session: injectService(),
store: injectService(),

queryParams: ['type', 'author', 'tag'],
type: null,
author: null,
tag: null,

session: injectService(),
_hasLoadedTags: false,
_hasLoadedAuthors: false,

showDeletePostModal: false,

showingAll: computed('type', function () {
return this.get('type') === null;
availableTypes: [{
name: 'All posts',
value: null
}, {
name: 'Draft posts',
value: 'draft'
}, {
name: 'Published posts',
value: 'published'
}, {
name: 'Scheduled posts',
value: 'scheduled'
}, {
name: 'Pages',
value: 'page'
}],

showingAll: computed('type', 'author', 'tag', function () {
let {type, author, tag} = this.getProperties(['type', 'author', 'tag']);

return !type && !author && !tag;
}),

selectedType: computed('type', function () {
let types = this.get('availableTypes');
return types.findBy('value', this.get('type'));
}),

_availableTags: computed(function () {
return this.get('store').peekAll('tag');
}),

availableTags: computed('_availableTags.[]', function () {
let tags = this.get('_availableTags');
let options = tags.toArray();

options.unshiftObject({name: 'All tags', slug: null});

return options;
}),

selectedTag: computed('tag', '_availableTags.[]', function () {
let tag = this.get('tag');
let tags = this.get('availableTags');

return tags.findBy('slug', tag);
}),

_availableAuthors: computed(function () {
return this.get('store').peekAll('user');
}),

availableAuthors: computed('_availableAuthors.[]', function () {
let authors = this.get('_availableAuthors');
let options = authors.toArray();

options.unshiftObject({name: 'All authors', slug: null});

return options;
}),

selectedAuthor: computed('author', 'availableAuthors.[]', function () {
let author = this.get('author');
let authors = this.get('availableAuthors');

return authors.findBy('slug', author);
}),

actions: {
toggleDeletePostModal() {
this.toggleProperty('showDeletePostModal');
},

changeType(type) {
this.set('type', get(type, 'value'));
},

changeAuthor(author) {
this.set('author', get(author, 'slug'));
},

changeTag(tag) {
this.set('tag', get(tag, 'slug'));
}
}
});
78 changes: 62 additions & 16 deletions app/routes/posts.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import AuthenticatedRoute from 'ghost-admin/routes/authenticated';
import ShortcutsRoute from 'ghost-admin/mixins/shortcuts-route';
import InfinityRoute from 'ember-infinity/mixins/route';
import computed from 'ember-computed';
import {assign} from 'ember-platform';
import {isBlank} from 'ember-utils';
import $ from 'jquery';

export default AuthenticatedRoute.extend(InfinityRoute, ShortcutsRoute, {
Expand All @@ -12,28 +12,49 @@ export default AuthenticatedRoute.extend(InfinityRoute, ShortcutsRoute, {
perPageParam: 'limit',
totalPagesParam: 'meta.pagination.pages',

queryParams: {
type: {
refreshModel: true,
replace: true
},
author: {
refreshModel: true,
replace: true
},
tag: {
refreshModel: true,
replace: true
}
},

_type: null,
_selectedPostIndex: null,

model(params) {
this.set('_type', params.type);
let filterSettings = this.get('filterSettings');

return this.get('session.user').then((user) => {
let queryParams = this._typeParams(params.type);
let filterParams = {tag: params.tag};

if (user.get('isAuthor')) {
filterSettings.filter = filterSettings.filter
? `${filterSettings.filter}+author:${user.get('slug')}` : `author:${user.get('slug')}`;
// authors can only view their own posts
filterParams.author = user.get('slug');
} else if (params.author) {
filterParams.author = params.author;
}

let filter = this._filterString(filterParams);
if (!isBlank(filter)) {
queryParams.filter = filter;
}

let perPage = this.get('perPage');
let paginationSettings = assign({perPage, startingPage: 1}, filterSettings);
let paginationSettings = assign({perPage, startingPage: 1}, queryParams);

return this.infinityModel('post', paginationSettings);
});
},

filterSettings: computed('_type', function () {
let type = this.get('_type');
_typeParams(type) {
let status = 'all';
let staticPages = 'all';

Expand All @@ -59,7 +80,36 @@ export default AuthenticatedRoute.extend(InfinityRoute, ShortcutsRoute, {
status,
staticPages
};
}),
},

_filterString(filter) {
return Object.keys(filter).map((key) => {
let value = filter[key];

if (!isBlank(value)) {
return `${key}:${filter[key]}`;
}
}).compact().join('+');
},

// trigger a background load of all tags and authors for use in the filter dropdowns
setupController(controller) {
this._super(...arguments);

if (!controller._hasLoadedTags) {
this.get('store').query('tag', {limit: 'all'}).then(() => {
controller._hasLoadedTags = true;
});
}

this.get('session.user').then((user) => {
if (!user.get('isAuthor') && !controller._hasLoadedAuthors) {
this.get('store').query('user', {limit: 'all'}).then(() => {
controller._hasLoadedAuthors = true;
});
}
});
},

stepThroughPosts(step) {
let currentPost = this.get('controller.selectedPost');
Expand Down Expand Up @@ -111,14 +161,10 @@ export default AuthenticatedRoute.extend(InfinityRoute, ShortcutsRoute, {
},

queryParamsDidChange() {
// on direct page load controller won't exist so we want to
// avoid a double transition
if (this.get('controller')) {
this.refresh();
}

// scroll back to the top
$('.content-list').scrollTop(0);

this._super(...arguments);
},

newPost() {
Expand Down
2 changes: 1 addition & 1 deletion app/templates/components/gh-nav-menu.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
<li>
{{!-- clicking the Content link whilst on the content screen should reset the filter --}}
{{#if (eq routing.currentRouteName "posts.index")}}
{{#link-to "posts" (query-params type=null) classNames="gh-nav-main-content active"}}<i class="icon-content"></i>Stories{{/link-to}}
{{#link-to "posts" (query-params type=null author=null tag=null) classNames="gh-nav-main-content active"}}<i class="icon-content"></i>Stories{{/link-to}}
{{else}}
{{#link-to "posts" classNames="gh-nav-main-content"}}<i class="icon-content"></i>Stories{{/link-to}}
{{/if}}
Expand Down
55 changes: 40 additions & 15 deletions app/templates/posts-loading.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,46 @@
</header>

<div class="gh-contentfilter">
{{#active-link}}
{{link-to "All" "posts.index" (query-params type=null) data-test-all-filter-link=true}}
{{/active-link}}
{{#active-link}}
{{link-to "Drafts" "posts.index" (query-params type="draft") data-test-drafts-filter-link=true}}
{{/active-link}}
{{#active-link}}
{{link-to "Published" "posts.index" (query-params type="published") data-test-published-filter-link=true}}
{{/active-link}}
{{#active-link}}
{{link-to "Scheduled" "posts.index" (query-params type="scheduled") data-test-scheduled-filter-link=true}}
{{/active-link}}
{{#active-link}}
{{link-to "Pages" "posts.index" (query-params type="page") data-test-pages-filter-link=true}}
{{/active-link}}
{{#power-select
placeholder="All posts"
selected=selectedType
options=availableTypes
searchField="name"
onchange=(action (mut k))
tagName="div"
data-test-type-select=true
as |type|
}}
{{type.name}}
{{/power-select}}

{{#unless session.user.isAuthor}}
{{#power-select
placeholder="All authors"
selected=selectedAuthor
options=availableAuthors
searchField="name"
onchange=(action (mut k))
tagName="div"
data-test-author-select=true
as |author|
}}
{{author.name}}
{{/power-select}}
{{/unless}}

{{#power-select
placeholder="All tags"
selected=selectedTag
options=availableTags
searchField="name"
onchange=(action (mut k))
tagName="div"
data-test-tag-select=true
as |tag|
}}
{{tag.name}}
{{/power-select}}
</div>

{{gh-loading-spinner}}
Expand Down
54 changes: 38 additions & 16 deletions app/templates/posts.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,43 @@
</header>

<div class="gh-contentfilter">
{{#active-link}}
{{link-to "All" "posts.index" (query-params type=null) data-test-all-filter-link=true}}
{{/active-link}}
{{#active-link}}
{{link-to "Drafts" "posts.index" (query-params type="draft") data-test-drafts-filter-link=true}}
{{/active-link}}
{{#active-link}}
{{link-to "Published" "posts.index" (query-params type="published") data-test-published-filter-link=true}}
{{/active-link}}
{{#active-link}}
{{link-to "Scheduled" "posts.index" (query-params type="scheduled") data-test-scheduled-filter-link=true}}
{{/active-link}}
{{#active-link}}
{{link-to "Pages" "posts.index" (query-params type="page") data-test-pages-filter-link=true}}
{{/active-link}}
{{#power-select
selected=selectedType
options=availableTypes
searchField="name"
onchange=(action "changeType")
tagName="div"
data-test-type-select=true
as |type|
}}
{{type.name}}
{{/power-select}}

{{#unless session.user.isAuthor}}
{{#power-select
selected=selectedAuthor
options=availableAuthors
searchField="name"
onchange=(action "changeAuthor")
tagName="div"
data-test-author-select=true
as |author|
}}
{{author.name}}
{{/power-select}}
{{/unless}}

{{#power-select
selected=selectedTag
options=availableTags
searchField="name"
onchange=(action "changeTag")
tagName="div"
data-test-tag-select=true
as |tag|
}}
{{tag.name}}
{{/power-select}}
</div>

<section class="content-list">
Expand All @@ -31,7 +53,7 @@
post=post
active=(eq post selectedPost)
onDoubleClick="openEditor"
data-test-posts-list-item-id=post.id}}
data-test-post-id=post.id}}
{{else}}
<li class="no-posts-box">
<div class="no-posts">
Expand Down
6 changes: 4 additions & 2 deletions mirage/models/post.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import {Model} from 'ember-cli-mirage';
import {Model, belongsTo} from 'ember-cli-mirage';

export default Model.extend();
export default Model.extend({
author: belongsTo('user')
});
Loading

0 comments on commit dc351d3

Please sign in to comment.