diff --git a/ghost/admin/app/components/posts-list/list.hbs b/ghost/admin/app/components/posts-list/list.hbs index 4755c76d62..b1ab3acd99 100644 --- a/ghost/admin/app/components/posts-list/list.hbs +++ b/ghost/admin/app/components/posts-list/list.hbs @@ -1,14 +1,39 @@ - {{#each @model as |post|}} - - - + {{!-- always order as scheduled, draft, remainder --}} + {{#if (or @model.scheduledPosts (or @model.draftPosts @model.publishedAndSentPosts))}} + {{#if @model.scheduledPosts}} + {{#each @model.scheduledPosts as |post|}} + + + + {{/each}} + {{/if}} + {{#if (and @model.draftPosts (or (not @model.scheduledPosts) (and @model.scheduledPosts @model.scheduledPosts.reachedInfinity)))}} + {{#each @model.draftPosts as |post|}} + + + + {{/each}} + {{/if}} + {{#if (and @model.publishedAndSentPosts (and (or (not @model.scheduledPosts) @model.scheduledPosts.reachedInfinity) (or (not @model.draftPosts) @model.draftPosts.reachedInfinity)))}} + {{#each @model.publishedAndSentPosts as |post|}} + + + + {{/each}} + {{/if}} {{else}} {{yield}} - {{/each}} + {{/if}} {{!-- The currently selected item or items are passed to the context menu --}} diff --git a/ghost/admin/app/routes/posts.js b/ghost/admin/app/routes/posts.js index 93e7d5d4ab..f4e6966403 100644 --- a/ghost/admin/app/routes/posts.js +++ b/ghost/admin/app/routes/posts.js @@ -1,4 +1,5 @@ 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'; @@ -46,36 +47,46 @@ export default class PostsRoute extends AuthenticatedRoute { 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) { - // authors can only view their own posts filterParams.authors = user.slug; } else if (user.isContributor) { - // Contributors can only view their own draft posts filterParams.authors = user.slug; - // filterParams.status = 'draft'; + // otherwise we need to filter by author if present } else if (params.author) { filterParams.authors = params.author; } - let filter = this._filterString(filterParams); - if (!isBlank(filter)) { - queryParams.filter = filter; - } - - if (!isBlank(params.order)) { - queryParams.order = params.order; - } - let perPage = this.perPage; - let paginationSettings = assign({perPage, startingPage: 1}, paginationParams, queryParams); - return this.infinity.model(this.modelName, paginationSettings); + const filterStatuses = filterParams.status; + let models = {}; + if (filterStatuses.includes('scheduled')) { + let scheduledPostsParams = {...queryParams, order: params.order || 'published_at desc', filter: this._filterString({...filterParams, status: 'scheduled'})}; + models.scheduledPosts = this.infinity.model('post', assign({perPage, startingPage: 1}, paginationParams, scheduledPostsParams)); + } + if (filterStatuses.includes('draft')) { + let draftPostsParams = {...queryParams, order: params.order || 'updated_at desc', filter: this._filterString({...filterParams, status: 'draft'})}; + models.draftPosts = this.infinity.model('post', assign({perPage, startingPage: 1}, paginationParams, draftPostsParams)); + } + if (filterStatuses.includes('published') || filterStatuses.includes('sent')) { + let publishedAndSentPostsParams; + if (filterStatuses.includes('published') && filterStatuses.includes('sent')) { + publishedAndSentPostsParams = {...queryParams, order: params.order || 'published_at desc', filter: this._filterString({...filterParams, status: '[published,sent]'})}; + } else { + publishedAndSentPostsParams = {...queryParams, order: params.order || 'published_at desc', filter: this._filterString({...filterParams, status: filterStatuses.includes('published') ? 'published' : 'sent'})}; + } + models.publishedAndSentPosts = this.infinity.model('post', assign({perPage, startingPage: 1}, paginationParams, publishedAndSentPostsParams)); + } + + return RSVP.hash(models); } // trigger a background load of all tags and authors for use in filter dropdowns @@ -120,6 +131,12 @@ export default class PostsRoute extends AuthenticatedRoute { }; } + /** + * 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]'; diff --git a/ghost/admin/app/templates/posts.hbs b/ghost/admin/app/templates/posts.hbs index f0d0b6bbe8..abe3f88a47 100644 --- a/ghost/admin/app/templates/posts.hbs +++ b/ghost/admin/app/templates/posts.hbs @@ -30,7 +30,7 @@
  • @@ -51,11 +51,26 @@
  • + {{!-- only show one infinity loader wheel at a time - always order as scheduled, draft, remainder --}} + {{#if @model.scheduledPosts}} -
    + {{/if}} + {{#if (and @model.draftPosts (or (not @model.scheduledPosts) (and @model.scheduledPosts @model.scheduledPosts.reachedInfinity)))}} + + {{/if}} + {{#if (and @model.publishedAndSentPosts (and (or (not @model.scheduledPosts) @model.scheduledPosts.reachedInfinity) (or (not @model.draftPosts) @model.draftPosts.reachedInfinity)))}} + + {{/if}} + {{outlet}} diff --git a/ghost/admin/tests/acceptance/content-test.js b/ghost/admin/tests/acceptance/content-test.js index da312358da..996f4eec00 100644 --- a/ghost/admin/tests/acceptance/content-test.js +++ b/ghost/admin/tests/acceptance/content-test.js @@ -1,6 +1,6 @@ import {authenticateSession, invalidateSession} from 'ember-simple-auth/test-support'; import {beforeEach, describe, it} from 'mocha'; -import {blur, click, currentURL, fillIn, find, findAll, settled, visit} from '@ember/test-helpers'; +import {blur, click, currentURL, fillIn, find, findAll, visit} from '@ember/test-helpers'; import {clickTrigger, selectChoose} from 'ember-power-select/test-support/helpers'; import {expect} from 'chai'; import {setupApplicationTest} from 'ember-mocha'; @@ -41,7 +41,7 @@ describe('Acceptance: Content', function () { return await authenticateSession(); }); - it.skip('displays and filters posts', async function () { + it('displays and filters posts', async function () { await visit('/posts'); // Not checking request here as it won't be the last request made // Displays all posts + pages @@ -81,38 +81,29 @@ describe('Acceptance: Content', function () { // show all posts await selectChoose('[data-test-type-select]', 'All posts'); - // API request is correct + // Posts are ordered scheduled -> draft -> published/sent + // check API request is correct - we submit one request for scheduled, one for drafts, and one for published+sent + [lastRequest] = this.server.pretender.handledRequests.slice(-3); + expect(lastRequest.queryParams.filter, '"all" request status filter').to.have.string('status:scheduled'); + [lastRequest] = this.server.pretender.handledRequests.slice(-2); + expect(lastRequest.queryParams.filter, '"all" request status filter').to.have.string('status:draft'); [lastRequest] = this.server.pretender.handledRequests.slice(-1); - expect(lastRequest.queryParams.filter, '"all" request status filter').to.have.string('status:[draft,scheduled,published]'); + expect(lastRequest.queryParams.filter, '"all" request status filter').to.have.string('status:[published,sent]'); + + // check order display is correct + let postIds = findAll('[data-test-post-id]').map(el => el.getAttribute('data-test-post-id')); + expect(postIds, 'post order').to.deep.equal([scheduledPost.id, draftPost.id, publishedPost.id, authorPost.id]); // show all posts by editor + await selectChoose('[data-test-type-select]', 'Published posts'); await selectChoose('[data-test-author-select]', editor.name); // API request is correct [lastRequest] = this.server.pretender.handledRequests.slice(-1); expect(lastRequest.queryParams.filter, '"editor" request status filter') - .to.have.string('status:[draft,scheduled,published]'); + .to.have.string('status: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,tiers]+status:[draft,scheduled,published]'); - - // Displays editor post - // TODO: implement "filter" param support and fix mirage post->author association - // expect(find('[data-test-post-id]').length, 'editor post count').to.equal(1); - // expect(find(`[data-test-post-id="${authorPost.id}"]`), 'author post').to.exist; - - // TODO: test tags dropdown }); // TODO: skipped due to consistently random failures on Travis