Added unpublish bulk action to posts

fixes https://github.com/TryGhost/Team/issues/2994
This commit is contained in:
Simon Backx 2023-04-12 15:28:09 +02:00
parent d4e9c23f84
commit 9eb886d26d
4 changed files with 117 additions and 6 deletions

View File

@ -1,9 +1,12 @@
<ul class="gh-posts-context-menu dropdown-menu dropdown-triangle-top-left">
<li>
<button class="mr2" type="button" disabled {{on "click" @menu.close}}>
<span>Unpublish</span>
</button>
</li>
{{#if this.canUnpublishSelection}}
<li>
<button class="mr2" type="button" {{on "click" this.unpublishPosts}}>
<span>Unpublish</span>
</button>
</li>
{{/if}}
{{#if this.canFeatureSelection}}
{{#if this.shouldFeatureSelection }}
<li>

View File

@ -1,6 +1,8 @@
import Component from '@glimmer/component';
import DeletePostsModal from './modals/delete-posts';
import EditPostsAccessModal from './modals/edit-posts-access';
import UnpublishPostsModal from './modals/unpublish-posts';
import nql from '@tryghost/nql';
import {action} from '@ember/object';
import {inject as service} from '@ember/service';
import {task} from 'ember-concurrency';
@ -31,6 +33,16 @@ export default class PostsContextMenu extends Component {
});
}
@action
async unpublishPosts() {
this.menu.close();
await this.modals.open(UnpublishPostsModal, {
isSingle: this.selectionList.isSingle,
count: this.selectionList.count,
confirm: this.unpublishPostsTask
});
}
@action
async editPostsAccess() {
this.menu.close();
@ -56,6 +68,50 @@ export default class PostsContextMenu extends Component {
return true;
}
@task
*unpublishPostsTask(close) {
const updatedModels = this.selectionList.availableModels;
yield this.performBulkEdit('unpublish');
// Update the models on the client side
for (const post of updatedModels) {
if (post.status === 'published' || post.status === 'sent') {
// We need to do it this way to prevent marking the model as dirty
this.store.push({
data: {
id: post.id,
type: 'post',
attributes: {
status: 'draft'
}
}
});
}
}
// Remove posts that no longer match the filter
this.updateFilteredPosts();
close();
return true;
}
updateFilteredPosts() {
const updatedModels = this.selectionList.availableModels;
const filter = this.selectionList.allFilter;
const filterNql = nql(filter);
const remainingModels = this.selectionList.infinityModel.content.filter((model) => {
if (!updatedModels.find(u => u.id === model.id)) {
return true;
}
return filterNql.queryJSON(model);
});
// Deleteobjects method from infintiymodel is broken for all models except the first page, so we cannot use this
this.infinity.replace(this.selectionList.infinityModel, remainingModels);
}
@task
*editPostsAccessTask(close, {visibility, tiers}) {
const updatedModels = this.selectionList.availableModels;
@ -80,6 +136,9 @@ export default class PostsContextMenu extends Component {
});
}
// Remove posts that no longer match the filter
this.updateFilteredPosts();
close();
return true;
@ -116,7 +175,16 @@ export default class PostsContextMenu extends Component {
if (!this.selectionList.isSingle) {
return true;
}
return this.selectionList.availableModels[0].get('status') !== 'sent';
return this.selectionList.availableModels[0]?.get('status') !== 'sent';
}
get canUnpublishSelection() {
for (const m of this.selectionList.availableModels) {
if (['published', 'sent'].includes(m.status)) {
return true;
}
}
return false;
}
@action
@ -138,6 +206,9 @@ export default class PostsContextMenu extends Component {
});
}
// Remove posts that no longer match the filter
this.updateFilteredPosts();
// Close the menu
this.menu.close();
}
@ -161,6 +232,9 @@ export default class PostsContextMenu extends Component {
});
}
// Remove posts that no longer match the filter
this.updateFilteredPosts();
// Close the menu
this.menu.close();
}

View File

@ -0,0 +1,27 @@
<div class="modal-content" data-test-modal="unpublish-posts">
<header class="modal-header">
<h1>Are you sure you want to unpublish {{if @data.isSingle 'this post' (concat @data.count ' posts')}}?</h1>
</header>
<button type="button" class="close" title="Close" {{on "click" (fn @close false)}} data-test-button="close">{{svg-jar "close"}}<span class="hidden">Close</span></button>
<div class="modal-body">
<p>
You're about to unpublish {{if @data.isSingle 'this post' (concat @data.count ' posts')}}.
</p>
</div>
<div class="modal-footer">
<button class="gh-btn" data-test-button="cancel" type="button" {{on "click" (fn @close false)}}><span>Cancel</span></button>
<GhTaskButton
@buttonText="Unpublish"
@runningText="Unpublishing"
@showSuccess={{false}}
@task={{@data.confirm}}
@taskArgs={{@close}}
@class="gh-btn gh-btn-red gh-btn-icon"
data-test-button="confirm"
/>
</div>
</div>

View File

@ -63,7 +63,14 @@ class PostsService {
return model;
}
#mergeFilters(...filters) {
return filters.filter(filter => filter).map(f => `(${f})`).join('+');
}
async bulkEdit(data, options) {
if (data.action === 'unpublish') {
return await this.#updatePosts({status: 'draft'}, {filter: this.#mergeFilters('status:[published,sent]', options.filter)});
}
if (data.action === 'feature') {
return await this.#updatePosts({featured: true}, {filter: options.filter});
}