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