mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-25 09:03:12 +03:00
✨ Added access level filter to posts and pages lists in admin
no issue - adds `visibility` query param to posts and pages controllers/routes that is tied to the `filter` query param used in API requests - adds dropdown for selecting post/page visibility to `<GhContentFilter>`
This commit is contained in:
parent
1a69f391ea
commit
758f5285fb
@ -17,6 +17,24 @@
|
|||||||
</div>
|
</div>
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
|
|
||||||
|
{{#if this.feature.members}}
|
||||||
|
<div class="gh-contentfilter-menu gh-contentfilter-visibility {{if @selectedVisibility.value "gh-contentfilter-selected"}}" data-test-visibility-select="true">
|
||||||
|
<PowerSelect
|
||||||
|
@selected={{@selectedVisibility}}
|
||||||
|
@options={{@availableVisibilities}}
|
||||||
|
@searchEnabled={{false}}
|
||||||
|
@onChange={{@onVisibilityChange}}
|
||||||
|
@triggerComponent="gh-power-select/trigger"
|
||||||
|
@triggerClass="gh-contentfilter-menu-trigger"
|
||||||
|
@dropdownClass="gh-contentfilter-menu-dropdown"
|
||||||
|
@matchTriggerWidth={{false}}
|
||||||
|
as |visibility|
|
||||||
|
>
|
||||||
|
{{#if visibility.name}}{{visibility.name}}{{else}}<span class="red">Unknown visibility</span>{{/if}}
|
||||||
|
</PowerSelect>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
{{#unless @currentUser.isAuthorOrContributor}}
|
{{#unless @currentUser.isAuthorOrContributor}}
|
||||||
<div class="gh-contentfilter-menu gh-contentfilter-author {{if @selectedAuthor.slug "gh-contentfilter-selected"}}" data-test-author-select="true">
|
<div class="gh-contentfilter-menu gh-contentfilter-author {{if @selectedAuthor.slug "gh-contentfilter-selected"}}" data-test-author-select="true">
|
||||||
<PowerSelect
|
<PowerSelect
|
||||||
|
@ -4,6 +4,7 @@ import {inject as service} from '@ember/service';
|
|||||||
|
|
||||||
export default class GhContentfilterComponent extends Component {
|
export default class GhContentfilterComponent extends Component {
|
||||||
@service customViews;
|
@service customViews;
|
||||||
|
@service feature;
|
||||||
@service router;
|
@service router;
|
||||||
|
|
||||||
get showCustomViewManagement() {
|
get showCustomViewManagement() {
|
||||||
@ -11,6 +12,7 @@ export default class GhContentfilterComponent extends Component {
|
|||||||
let onPostsScreen = this.router.currentRouteName === 'posts';
|
let onPostsScreen = this.router.currentRouteName === 'posts';
|
||||||
let isDefaultView = this.customViews?.activeView?.isDefault;
|
let isDefaultView = this.customViews?.activeView?.isDefault;
|
||||||
let hasFilter = this.args.selectedType.value
|
let hasFilter = this.args.selectedType.value
|
||||||
|
|| this.args.selectedVisibility.value
|
||||||
|| this.args.selectedAuthor.slug
|
|| this.args.selectedAuthor.slug
|
||||||
|| this.args.selectedTag.slug
|
|| this.args.selectedTag.slug
|
||||||
|| this.args.selectedOrder.value;
|
|| this.args.selectedOrder.value;
|
||||||
|
@ -76,13 +76,11 @@
|
|||||||
{{/unless}}
|
{{/unless}}
|
||||||
|
|
||||||
|
|
||||||
{{#if this.feature.members}}
|
{{#if (and this.feature.members this.showVisibilityInput)}}
|
||||||
{{#if this.showVisibilityInput}}
|
<div class="form-group">
|
||||||
<div class="form-group">
|
<label for="visibility-input">Post access</label>
|
||||||
<label for="visibility-input">Post access</label>
|
<GhPsmVisibilityInput @post={{this.post}} @triggerId="visibility-input" />
|
||||||
<GhPsmVisibilityInput @post={{this.post}} @triggerId="visibility-input" />
|
</div>
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
|
||||||
|
@ -11,6 +11,8 @@ export default Controller.extend({
|
|||||||
|
|
||||||
availableTypes: readOnly('postsController.availableTypes'),
|
availableTypes: readOnly('postsController.availableTypes'),
|
||||||
selectedType: readOnly('postsController.selectedType'),
|
selectedType: readOnly('postsController.selectedType'),
|
||||||
|
selectedVisibility: readOnly('postsController.selectedVisibility'),
|
||||||
|
availableVisibilities: readOnly('postsController.availableVisibilities'),
|
||||||
availableTags: readOnly('postsController.availableTags'),
|
availableTags: readOnly('postsController.availableTags'),
|
||||||
selectedTag: readOnly('postsController.selectedTag'),
|
selectedTag: readOnly('postsController.selectedTag'),
|
||||||
availableAuthors: readOnly('postsController.availableAuthors'),
|
availableAuthors: readOnly('postsController.availableAuthors'),
|
||||||
|
@ -22,6 +22,20 @@ const TYPES = [{
|
|||||||
value: 'featured'
|
value: 'featured'
|
||||||
}];
|
}];
|
||||||
|
|
||||||
|
const VISIBILITIES = [{
|
||||||
|
name: 'All access',
|
||||||
|
value: null
|
||||||
|
}, {
|
||||||
|
name: 'Public',
|
||||||
|
value: 'public'
|
||||||
|
}, {
|
||||||
|
name: 'Members-only',
|
||||||
|
value: 'members'
|
||||||
|
}, {
|
||||||
|
name: 'Paid members-only',
|
||||||
|
value: 'paid'
|
||||||
|
}];
|
||||||
|
|
||||||
const ORDERS = [{
|
const ORDERS = [{
|
||||||
name: 'Newest',
|
name: 'Newest',
|
||||||
value: null
|
value: null
|
||||||
@ -38,27 +52,29 @@ export default Controller.extend({
|
|||||||
store: service(),
|
store: service(),
|
||||||
|
|
||||||
// default values for these are set in `init` and defined in `helpers/reset-query-params`
|
// default values for these are set in `init` and defined in `helpers/reset-query-params`
|
||||||
queryParams: ['type', 'author', 'tag', 'order'],
|
queryParams: ['type', 'access', 'author', 'tag', 'order'],
|
||||||
|
|
||||||
_hasLoadedTags: false,
|
_hasLoadedTags: false,
|
||||||
_hasLoadedAuthors: false,
|
_hasLoadedAuthors: false,
|
||||||
|
|
||||||
availableTypes: null,
|
availableTypes: null,
|
||||||
|
availableVisibilities: null,
|
||||||
availableOrders: null,
|
availableOrders: null,
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
this.availableTypes = TYPES;
|
this.availableTypes = TYPES;
|
||||||
this.availableOrders = ORDERS;
|
this.availableOrders = ORDERS;
|
||||||
|
this.availableVisibilities = VISIBILITIES;
|
||||||
this.setProperties(DEFAULT_QUERY_PARAMS.posts);
|
this.setProperties(DEFAULT_QUERY_PARAMS.posts);
|
||||||
},
|
},
|
||||||
|
|
||||||
postsInfinityModel: alias('model'),
|
postsInfinityModel: alias('model'),
|
||||||
|
|
||||||
showingAll: computed('type', 'author', 'tag', function () {
|
showingAll: computed('type', 'author', 'tag', function () {
|
||||||
let {type, author, tag} = this.getProperties(['type', 'author', 'tag']);
|
let {type, author, tag, visibility} = this.getProperties(['type', 'visibility', 'author', 'tag']);
|
||||||
|
|
||||||
return !type && !author && !tag;
|
return !type && !visibility && !author && !tag;
|
||||||
}),
|
}),
|
||||||
|
|
||||||
selectedType: computed('type', function () {
|
selectedType: computed('type', function () {
|
||||||
@ -66,6 +82,11 @@ export default Controller.extend({
|
|||||||
return types.findBy('value', this.get('type')) || {value: '!unknown'};
|
return types.findBy('value', this.get('type')) || {value: '!unknown'};
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
selectedVisibility: computed('visibility', function () {
|
||||||
|
let visibilities = this.get('availableVisibilities');
|
||||||
|
return visibilities.findBy('value', this.get('visibility')) || {value: '!unknown'};
|
||||||
|
}),
|
||||||
|
|
||||||
selectedOrder: computed('order', function () {
|
selectedOrder: computed('order', function () {
|
||||||
let orders = this.get('availableOrders');
|
let orders = this.get('availableOrders');
|
||||||
return orders.findBy('value', this.get('order')) || {value: '!unknown'};
|
return orders.findBy('value', this.get('order')) || {value: '!unknown'};
|
||||||
@ -117,6 +138,10 @@ export default Controller.extend({
|
|||||||
this.set('type', get(type, 'value'));
|
this.set('type', get(type, 'value'));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
changeVisibility(visibility) {
|
||||||
|
this.set('visibility', get(visibility, 'value'));
|
||||||
|
},
|
||||||
|
|
||||||
changeAuthor(author) {
|
changeAuthor(author) {
|
||||||
this.set('author', get(author, 'slug'));
|
this.set('author', get(author, 'slug'));
|
||||||
},
|
},
|
||||||
|
@ -3,12 +3,14 @@ import {helper} from '@ember/component/helper';
|
|||||||
export const DEFAULT_QUERY_PARAMS = {
|
export const DEFAULT_QUERY_PARAMS = {
|
||||||
posts: {
|
posts: {
|
||||||
type: null,
|
type: null,
|
||||||
|
visibility: null,
|
||||||
author: null,
|
author: null,
|
||||||
tag: null,
|
tag: null,
|
||||||
order: null
|
order: null
|
||||||
},
|
},
|
||||||
pages: {
|
pages: {
|
||||||
type: null,
|
type: null,
|
||||||
|
visibility: null,
|
||||||
author: null,
|
author: null,
|
||||||
tag: null,
|
tag: null,
|
||||||
order: null
|
order: null
|
||||||
|
@ -9,6 +9,8 @@ export default AuthenticatedRoute.extend({
|
|||||||
|
|
||||||
queryParams: {
|
queryParams: {
|
||||||
type: {refreshModel: true},
|
type: {refreshModel: true},
|
||||||
|
visibility: {refreshModel: true},
|
||||||
|
access: {refreshModel: true},
|
||||||
author: {refreshModel: true},
|
author: {refreshModel: true},
|
||||||
tag: {refreshModel: true},
|
tag: {refreshModel: true},
|
||||||
order: {refreshModel: true}
|
order: {refreshModel: true}
|
||||||
@ -38,7 +40,7 @@ export default AuthenticatedRoute.extend({
|
|||||||
model(params) {
|
model(params) {
|
||||||
return this.session.user.then((user) => {
|
return this.session.user.then((user) => {
|
||||||
let queryParams = {};
|
let queryParams = {};
|
||||||
let filterParams = {tag: params.tag};
|
let filterParams = {tag: params.tag, visibility: params.visibility};
|
||||||
let paginationParams = {
|
let paginationParams = {
|
||||||
perPageParam: 'limit',
|
perPageParam: 'limit',
|
||||||
totalPagesParam: 'meta.pagination.pages'
|
totalPagesParam: 'meta.pagination.pages'
|
||||||
|
@ -7,6 +7,9 @@
|
|||||||
@selectedType={{this.selectedType}}
|
@selectedType={{this.selectedType}}
|
||||||
@availableTypes={{this.availableTypes}}
|
@availableTypes={{this.availableTypes}}
|
||||||
@onTypeChange={{action (mut k)}}
|
@onTypeChange={{action (mut k)}}
|
||||||
|
@selectedVisibility={{this.selectedVisibility}}
|
||||||
|
@availableVisibilities={{this.availableVisibilities}}
|
||||||
|
@onVisibilityChange={{action (mut k)}}
|
||||||
@selectedAuthor={{this.selectedAuthor}}
|
@selectedAuthor={{this.selectedAuthor}}
|
||||||
@availableAuthors={{this.availableAuthors}}
|
@availableAuthors={{this.availableAuthors}}
|
||||||
@onAuthorChange={{action (mut k)}}
|
@onAuthorChange={{action (mut k)}}
|
||||||
|
@ -8,6 +8,9 @@
|
|||||||
@selectedType={{this.selectedType}}
|
@selectedType={{this.selectedType}}
|
||||||
@availableTypes={{this.availableTypes}}
|
@availableTypes={{this.availableTypes}}
|
||||||
@onTypeChange={{action "changeType"}}
|
@onTypeChange={{action "changeType"}}
|
||||||
|
@selectedVisibility={{this.selectedVisibility}}
|
||||||
|
@availableVisibilities={{this.availableVisibilities}}
|
||||||
|
@onVisibilityChange={{action "changeVisibility"}}
|
||||||
@selectedAuthor={{this.selectedAuthor}}
|
@selectedAuthor={{this.selectedAuthor}}
|
||||||
@availableAuthors={{this.availableAuthors}}
|
@availableAuthors={{this.availableAuthors}}
|
||||||
@onAuthorChange={{action "changeAuthor"}}
|
@onAuthorChange={{action "changeAuthor"}}
|
||||||
|
@ -7,6 +7,9 @@
|
|||||||
@selectedType={{this.selectedType}}
|
@selectedType={{this.selectedType}}
|
||||||
@availableTypes={{this.availableTypes}}
|
@availableTypes={{this.availableTypes}}
|
||||||
@onTypeChange={{action (mut k)}}
|
@onTypeChange={{action (mut k)}}
|
||||||
|
@selectedVisibility={{this.selectedVisibility}}
|
||||||
|
@availableVisibilities={{this.availableVisibilities}}
|
||||||
|
@onVisibilityChange={{action (mut k)}}
|
||||||
@selectedAuthor={{this.selectedAuthor}}
|
@selectedAuthor={{this.selectedAuthor}}
|
||||||
@availableAuthors={{this.availableAuthors}}
|
@availableAuthors={{this.availableAuthors}}
|
||||||
@onAuthorChange={{action (mut k)}}
|
@onAuthorChange={{action (mut k)}}
|
||||||
|
@ -8,6 +8,9 @@
|
|||||||
@selectedType={{this.selectedType}}
|
@selectedType={{this.selectedType}}
|
||||||
@availableTypes={{this.availableTypes}}
|
@availableTypes={{this.availableTypes}}
|
||||||
@onTypeChange={{action "changeType"}}
|
@onTypeChange={{action "changeType"}}
|
||||||
|
@selectedVisibility={{this.selectedVisibility}}
|
||||||
|
@availableVisibilities={{this.availableVisibilities}}
|
||||||
|
@onVisibilityChange={{action "changeVisibility"}}
|
||||||
@selectedAuthor={{this.selectedAuthor}}
|
@selectedAuthor={{this.selectedAuthor}}
|
||||||
@availableAuthors={{this.availableAuthors}}
|
@availableAuthors={{this.availableAuthors}}
|
||||||
@onAuthorChange={{action "changeAuthor"}}
|
@onAuthorChange={{action "changeAuthor"}}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import {authenticateSession, invalidateSession} from 'ember-simple-auth/test-support';
|
import {authenticateSession, invalidateSession} from 'ember-simple-auth/test-support';
|
||||||
import {beforeEach, describe, it} from 'mocha';
|
import {beforeEach, describe, it} from 'mocha';
|
||||||
import {click, currentURL, fillIn, find, findAll, visit} from '@ember/test-helpers';
|
import {click, currentURL, fillIn, find, findAll, settled, visit} from '@ember/test-helpers';
|
||||||
import {clickTrigger, selectChoose} from 'ember-power-select/test-support/helpers';
|
import {clickTrigger, selectChoose} from 'ember-power-select/test-support/helpers';
|
||||||
import {expect} from 'chai';
|
import {expect} from 'chai';
|
||||||
import {setupApplicationTest} from 'ember-mocha';
|
import {setupApplicationTest} from 'ember-mocha';
|
||||||
@ -86,8 +86,22 @@ describe('Acceptance: Content', function () {
|
|||||||
|
|
||||||
// API request is correct
|
// API request is correct
|
||||||
[lastRequest] = this.server.pretender.handledRequests.slice(-1);
|
[lastRequest] = this.server.pretender.handledRequests.slice(-1);
|
||||||
expect(lastRequest.queryParams.filter, '"editor" request status filter').to.have.string('status:[draft,scheduled,published]');
|
expect(lastRequest.queryParams.filter, '"editor" request status filter')
|
||||||
expect(lastRequest.queryParams.filter, '"editor" request filter param').to.have.string(`authors:${editor.slug}`);
|
.to.have.string('status:[draft,scheduled,published]');
|
||||||
|
expect(lastRequest.queryParams.filter, '"editor" request filter param')
|
||||||
|
.to.have.string(`authors:${editor.slug}`);
|
||||||
|
|
||||||
|
// Post status is only visible when members is enabled
|
||||||
|
expect(find('[data-test-visibility-select]'), 'access dropdown before members enabled').to.not.exist;
|
||||||
|
let featureService = this.owner.lookup('service:feature');
|
||||||
|
featureService.set('members', true);
|
||||||
|
await settled();
|
||||||
|
expect(find('[data-test-visibility-select]'), 'access dropdown after members enabled').to.exist;
|
||||||
|
|
||||||
|
await selectChoose('[data-test-visibility-select]', 'Paid members-only');
|
||||||
|
[lastRequest] = this.server.pretender.handledRequests.slice(-1);
|
||||||
|
expect(lastRequest.queryParams.filter, '"visibility" request filter param')
|
||||||
|
.to.have.string('visibility:paid+status:[draft,scheduled,published]');
|
||||||
|
|
||||||
// Displays editor post
|
// Displays editor post
|
||||||
// TODO: implement "filter" param support and fix mirage post->author association
|
// TODO: implement "filter" param support and fix mirage post->author association
|
||||||
|
Loading…
Reference in New Issue
Block a user