mod panel update unpublish all posts action 17709 (#17923)
This commit is contained in:
parent
3a1661690b
commit
50e2131079
10 changed files with 167 additions and 8 deletions
|
|
@ -25,16 +25,21 @@ module Admin
|
|||
last_moderation_notification last_notification_activity
|
||||
].freeze
|
||||
|
||||
MODROLE_ACTIONS_TO_POLICIES = {
|
||||
user_status: :toggle_suspension_status?,
|
||||
unpublish_all_articles: :unpublish_all_articles?
|
||||
}.freeze
|
||||
|
||||
after_action only: %i[update user_status banish full_delete unpublish_all_articles merge] do
|
||||
Audit::Logger.log(:moderator, current_user, params.dup)
|
||||
end
|
||||
|
||||
# Having this method here (which also exists in admin/application_controller)
|
||||
# allows us to authorize the moderator role specifically for the user_status
|
||||
# action, while preserving the implementation for all other admin actions
|
||||
# allows us to authorize the actions of the moderator role specifically,
|
||||
# while preserving the implementation for all other admin actions
|
||||
def authorize_admin
|
||||
if action_name == "user_status"
|
||||
authorize(User, :toggle_suspension_status?)
|
||||
if MODROLE_ACTIONS_TO_POLICIES.key?(action_name.to_sym)
|
||||
authorize(User, MODROLE_ACTIONS_TO_POLICIES[action_name.to_sym])
|
||||
else
|
||||
super
|
||||
end
|
||||
|
|
@ -184,8 +189,17 @@ module Admin
|
|||
|
||||
def unpublish_all_articles
|
||||
Moderator::UnpublishAllArticlesWorker.perform_async(params[:id].to_i)
|
||||
flash[:success] = I18n.t("admin.users_controller.unpublished")
|
||||
redirect_to admin_user_path(params[:id])
|
||||
message = I18n.t("admin.users_controller.unpublished")
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
flash[:success] = message
|
||||
redirect_to admin_user_path(params[:id])
|
||||
end
|
||||
|
||||
format.json do
|
||||
render json: { message: message }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def merge
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { toggleFlagUserModal } from '../packs/flagUserModal';
|
||||
import { toggleModal } from '../packs/toggleUserSuspensionModal';
|
||||
import { toggleUnpublishPostModal } from '../packs/unpublishPostModal';
|
||||
import { toggleUnpublishAllPostsModal } from '../packs/modals/unpublishAllPosts';
|
||||
import { request } from '@utilities/http';
|
||||
|
||||
export function addCloseListener() {
|
||||
|
|
@ -404,6 +405,9 @@ export function addBottomActionsListeners() {
|
|||
document
|
||||
.getElementById('unsuspend-user-btn')
|
||||
?.addEventListener('click', toggleModal);
|
||||
|
||||
document.getElementById('unpublish-all-posts-btn')
|
||||
?.addEventListener('click', toggleUnpublishAllPostsModal);
|
||||
}
|
||||
|
||||
export function initializeActionsPanel() {
|
||||
|
|
|
|||
78
app/javascript/packs/modals/unpublishAllPosts.js
Normal file
78
app/javascript/packs/modals/unpublishAllPosts.js
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import { closeWindowModal, showWindowModal } from '@utilities/showModal';
|
||||
import { request } from '@utilities/http';
|
||||
|
||||
const unpublishAllPosts = async (event) => {
|
||||
event.preventDefault();
|
||||
const { userId } = event.target.dataset;
|
||||
|
||||
try {
|
||||
const response = await request(
|
||||
`/admin/member_manager/users/${userId}/unpublish_all_articles`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ id: userId }),
|
||||
credentials: 'same-origin',
|
||||
},
|
||||
);
|
||||
|
||||
const outcome = await response.json();
|
||||
|
||||
top.addSnackbarItem({
|
||||
message: outcome.message,
|
||||
addCloseButton: true,
|
||||
});
|
||||
} catch (error) {
|
||||
top.addSnackbarItem({
|
||||
message: `Error: ${error}`,
|
||||
addCloseButton: true,
|
||||
});
|
||||
}
|
||||
|
||||
closeWindowModal(window.parent.document);
|
||||
};
|
||||
|
||||
const modalContents = new Map();
|
||||
|
||||
/**
|
||||
* Helper function to handle finding and caching modal content. Since our Preact modal helper works by duplicating HTML content,
|
||||
* and our modals rely on IDs to label form controls, we remove the original hidden content from the DOM to avoid ID conflicts.
|
||||
*
|
||||
* @param {string} modalContentSelector The CSS selector used to identify the correct modal content
|
||||
*/
|
||||
function getModalContents(modalContentSelector) {
|
||||
if (!modalContents.has(modalContentSelector)) {
|
||||
const modalContentElement =
|
||||
window.parent.document.querySelector(modalContentSelector);
|
||||
const modalContent = modalContentElement.innerHTML;
|
||||
|
||||
modalContentElement.remove();
|
||||
modalContents.set(modalContentSelector, modalContent);
|
||||
}
|
||||
|
||||
return modalContents.get(modalContentSelector);
|
||||
}
|
||||
|
||||
function activateUnpublishAllPostsBtn() {
|
||||
const unpublishAllPostsBtn = window.parent.document.getElementById(
|
||||
'unpublish-all-posts-submit-btn',
|
||||
);
|
||||
|
||||
unpublishAllPostsBtn.addEventListener('click', unpublishAllPosts);
|
||||
}
|
||||
|
||||
export function toggleUnpublishAllPostsModal() {
|
||||
const unpublishAllPostsBtn = document.getElementById(
|
||||
'unpublish-all-posts-btn',
|
||||
);
|
||||
|
||||
const { modalTitle, modalSize, modalContentSelector } =
|
||||
unpublishAllPostsBtn.dataset;
|
||||
|
||||
showWindowModal({
|
||||
document: window.parent.document,
|
||||
modalContent: getModalContents(modalContentSelector),
|
||||
title: modalTitle,
|
||||
size: modalSize,
|
||||
onOpen: activateUnpublishAllPostsBtn,
|
||||
});
|
||||
}
|
||||
|
|
@ -94,6 +94,7 @@ class UserPolicy < ApplicationPolicy
|
|||
end
|
||||
|
||||
alias toggle_suspension_status? elevated_user?
|
||||
alias unpublish_all_articles? elevated_user?
|
||||
|
||||
def moderation_routes?
|
||||
(user.has_trusted_role? || elevated_user?) && !user.suspended?
|
||||
|
|
|
|||
|
|
@ -220,8 +220,9 @@
|
|||
<div class="mod-actions-menu print-hidden"></div>
|
||||
<div data-testid="flag-user-modal-container" class="flag-user-modal-container hidden"></div>
|
||||
<div data-testid="unpublish-post-modal-container" class="unpublish-post-modal-container hidden"></div>
|
||||
<%= render "moderations/suspend_user_modal" %>
|
||||
<%= render "moderations/unsuspend_user_modal" %>
|
||||
<%= render "moderations/modals/suspend_user_modal" %>
|
||||
<%= render "moderations/modals/unsuspend_user_modal" %>
|
||||
<%= render "moderations/modals/unpublish_all_posts" %>
|
||||
|
||||
<div class="fullscreen-code js-fullscreen-code"></div>
|
||||
<%= javascript_packs_with_chunks_tag "followButtons", defer: true %>
|
||||
|
|
|
|||
|
|
@ -211,6 +211,17 @@
|
|||
data-modal-size="small" data-modal-content-selector="#<%= user_suspended ? "unsuspend" : "suspend" %>-modal-content" data-username="<%= user_username %>">
|
||||
<%= t("views.moderations.actions.suspend.#{user_suspended ? 'unsuspend' : 'suspend'}_user", username: user_username) %>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
id="unpublish-all-posts-btn"
|
||||
class="c-btn c-btn--destructive align-center fs-s"
|
||||
data-modal-title="Unpublish all posts"
|
||||
data-modal-size="small"
|
||||
data-modal-content-selector="#unpublish-all-posts"
|
||||
data-username="<%= @moderatable.username %>">
|
||||
Unpublish all posts for <%= @moderatable.username %>
|
||||
</button>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
|
|||
18
app/views/moderations/modals/_unpublish_all_posts.html.erb
Normal file
18
app/views/moderations/modals/_unpublish_all_posts.html.erb
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<div id="unpublish-all-posts" class="hidden">
|
||||
<p>
|
||||
Once unpublished, all posts by <%= @article.username %> will become
|
||||
hidden and only accessible to themselves.
|
||||
</p>
|
||||
<p class="mt-2">
|
||||
If <%= @article.username %> is not suspended, they can still re-publish
|
||||
their posts from their dashboard.
|
||||
</p>
|
||||
<div class="mt-4">
|
||||
<button
|
||||
id="unpublish-all-posts-submit-btn"
|
||||
class="c-btn c-btn--destructive c-btn--primary"
|
||||
data-user-id="<%= @article.user.id %>">
|
||||
Unpublish all posts
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -152,6 +152,38 @@ describe('Moderation Tools for Posts', () => {
|
|||
});
|
||||
});
|
||||
|
||||
context('when unpublishing all posts', () => {
|
||||
beforeEach(() => {
|
||||
cy.get('@moderatorUser').then((user) => {
|
||||
cy.loginAndVisit(user, '/series_user/series-test-article-slug');
|
||||
cy.findByRole('heading', { level: 1, name: 'Series test article' });
|
||||
cy.findByRole('button', { name: 'Moderation' }).click();
|
||||
});
|
||||
});
|
||||
|
||||
it('unpublishes all posts', () => {
|
||||
cy.getIframeBody('[title="Moderation panel actions"]').within(() => {
|
||||
cy.findByRole('button', { name: 'Open admin actions' })
|
||||
.as('moderatingActionsButton')
|
||||
.pipe(click)
|
||||
.should('have.attr', 'aria-expanded', 'true');
|
||||
cy.findByRole('button', {
|
||||
name: /Unpublish all posts for series_user/i,
|
||||
}).click();
|
||||
});
|
||||
|
||||
cy.findByRole('dialog').within(() => {
|
||||
cy.findByRole('button', { name: 'Unpublish all posts' }).click();
|
||||
});
|
||||
|
||||
cy.findByTestId('snackbar')
|
||||
.contains(
|
||||
'Posts are being unpublished in the background. The job will complete soon.',
|
||||
)
|
||||
.should('exist');
|
||||
});
|
||||
});
|
||||
|
||||
context('when suspending user', () => {
|
||||
beforeEach(() => {
|
||||
cy.get('@moderatorUser').then((user) => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue