mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-30 21:40:39 +03:00
c61c42ce1d
ref 8ea1dfb
ref https://linear.app/tryghost/issue/ONC-111
* undid the reversion for the performance improvements
* built upon new tests for the posts list functionality in admin,
including right click actions
* added tests for pages view in Admin
This was reverted because it broke the Pages list view in Admin, which
is a thin extension of the Posts functionality in admin (route &
controller). That has been fixed and tests added.
This was originally reverted because the changes to improve loading
response times broke right click (bulk) actions in the posts list. This
was not caught because it turned out we had near-zero test coverage of
that part of the codebase. Test coverage has been expanded for the posts
list, and while not comprehensive, is a much better place for us to be
in.
176 lines
6.5 KiB
JavaScript
176 lines
6.5 KiB
JavaScript
import AuthenticatedRoute from 'ghost-admin/routes/authenticated';
|
|
import RSVP from 'rsvp';
|
|
import {action} from '@ember/object';
|
|
import {assign} from '@ember/polyfills';
|
|
import {isBlank} from '@ember/utils';
|
|
import {inject as service} from '@ember/service';
|
|
|
|
export default class PostsRoute extends AuthenticatedRoute {
|
|
@service infinity;
|
|
@service router;
|
|
@service feature;
|
|
|
|
queryParams = {
|
|
type: {refreshModel: true},
|
|
visibility: {refreshModel: true},
|
|
author: {refreshModel: true},
|
|
tag: {refreshModel: true},
|
|
order: {refreshModel: true}
|
|
};
|
|
|
|
modelName = 'post';
|
|
perPage = 30;
|
|
|
|
constructor() {
|
|
super(...arguments);
|
|
|
|
// if we're already on this route and we're transiting _to_ this route
|
|
// then the filters are being changed and we shouldn't create a new
|
|
// browser history entry
|
|
// see https://github.com/TryGhost/Ghost/issues/11057
|
|
this.router.on('routeWillChange', (transition) => {
|
|
if (transition.to && (this.routeName === 'posts' || this.routeName === 'pages')) {
|
|
let toThisRoute = transition.to.find(route => route.name === this.routeName);
|
|
if (transition.from && transition.from.name === this.routeName && toThisRoute) {
|
|
transition.method('replace');
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
model(params) {
|
|
const user = this.session.user;
|
|
let filterParams = {tag: params.tag, visibility: params.visibility};
|
|
let paginationParams = {
|
|
perPageParam: 'limit',
|
|
totalPagesParam: 'meta.pagination.pages'
|
|
};
|
|
|
|
// type filters are actually mapping statuses
|
|
assign(filterParams, this._getTypeFilters(params.type));
|
|
|
|
if (params.type === 'featured') {
|
|
filterParams.featured = true;
|
|
}
|
|
|
|
// authors and contributors can only view their own posts
|
|
if (user.isAuthor) {
|
|
filterParams.authors = user.slug;
|
|
} else if (user.isContributor) {
|
|
filterParams.authors = user.slug;
|
|
// otherwise we need to filter by author if present
|
|
} else if (params.author) {
|
|
filterParams.authors = params.author;
|
|
}
|
|
|
|
let perPage = this.perPage;
|
|
|
|
const filterStatuses = filterParams.status;
|
|
let queryParams = {allFilter: this._filterString({...filterParams})}; // pass along the parent filter so it's easier to apply the params filter to each infinity model
|
|
let models = {};
|
|
|
|
if (filterStatuses.includes('scheduled')) {
|
|
let scheduledInfinityModelParams = {...queryParams, order: params.order || 'published_at desc', filter: this._filterString({...filterParams, status: 'scheduled'})};
|
|
models.scheduledInfinityModel = this.infinity.model(this.modelName, assign({perPage, startingPage: 1}, paginationParams, scheduledInfinityModelParams));
|
|
}
|
|
if (filterStatuses.includes('draft')) {
|
|
let draftInfinityModelParams = {...queryParams, order: params.order || 'updated_at desc', filter: this._filterString({...filterParams, status: 'draft'})};
|
|
models.draftInfinityModel = this.infinity.model(this.modelName, assign({perPage, startingPage: 1}, paginationParams, draftInfinityModelParams));
|
|
}
|
|
if (filterStatuses.includes('published') || filterStatuses.includes('sent')) {
|
|
let publishedAndSentInfinityModelParams;
|
|
if (filterStatuses.includes('published') && filterStatuses.includes('sent')) {
|
|
publishedAndSentInfinityModelParams = {...queryParams, order: params.order || 'published_at desc', filter: this._filterString({...filterParams, status: '[published,sent]'})};
|
|
} else {
|
|
publishedAndSentInfinityModelParams = {...queryParams, order: params.order || 'published_at desc', filter: this._filterString({...filterParams, status: filterStatuses.includes('published') ? 'published' : 'sent'})};
|
|
}
|
|
models.publishedAndSentInfinityModel = this.infinity.model(this.modelName, assign({perPage, startingPage: 1}, paginationParams, publishedAndSentInfinityModelParams));
|
|
}
|
|
|
|
return RSVP.hash(models);
|
|
}
|
|
|
|
// trigger a background load of all tags and authors for use in filter dropdowns
|
|
setupController(controller, model) {
|
|
super.setupController(...arguments);
|
|
|
|
if (!controller._hasLoadedTags) {
|
|
this.store.query('tag', {limit: 'all'}).then(() => {
|
|
controller._hasLoadedTags = true;
|
|
});
|
|
}
|
|
|
|
if (!this.session.user.isAuthorOrContributor && !controller._hasLoadedAuthors) {
|
|
this.store.query('user', {limit: 'all'}).then(() => {
|
|
controller._hasLoadedAuthors = true;
|
|
});
|
|
}
|
|
|
|
if (controller.selectionList) {
|
|
if (this.session.user.isAuthorOrContributor) {
|
|
controller.selectionList.enabled = false;
|
|
}
|
|
controller.selectionList.infinityModel = model;
|
|
controller.selectionList.clearSelection();
|
|
}
|
|
}
|
|
|
|
@action
|
|
queryParamsDidChange() {
|
|
// scroll back to the top
|
|
let contentList = document.querySelector('.content-list');
|
|
if (contentList) {
|
|
contentList.scrollTop = 0;
|
|
}
|
|
|
|
super.actions.queryParamsDidChange.call(this, ...arguments);
|
|
}
|
|
|
|
buildRouteInfoMetadata() {
|
|
return {
|
|
titleToken: 'Posts'
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Returns an object containing the status filter based on the given type.
|
|
*
|
|
* @param {string} type - The type of filter to generate (draft, published, scheduled, sent).
|
|
* @returns {Object} - An object containing the status filter.
|
|
*/
|
|
_getTypeFilters(type) {
|
|
let status = '[draft,scheduled,published,sent]';
|
|
|
|
switch (type) {
|
|
case 'draft':
|
|
status = 'draft';
|
|
break;
|
|
case 'published':
|
|
status = 'published';
|
|
break;
|
|
case 'scheduled':
|
|
status = 'scheduled';
|
|
break;
|
|
case 'sent':
|
|
status = 'sent';
|
|
break;
|
|
}
|
|
|
|
return {
|
|
status
|
|
};
|
|
}
|
|
|
|
_filterString(filter) {
|
|
return Object.keys(filter).map((key) => {
|
|
let value = filter[key];
|
|
|
|
if (!isBlank(value)) {
|
|
return `${key}:${filter[key]}`;
|
|
}
|
|
|
|
return undefined;
|
|
}).compact().join('+');
|
|
}
|
|
}
|