[deploy] [Admin Tooling] Allow admins unpublish articles from the Mod Actions Panel (#10038)
This commit is contained in:
parent
7f928555a5
commit
b79766f697
8 changed files with 117 additions and 7 deletions
|
|
@ -419,6 +419,16 @@
|
|||
border-top: 1px solid var(--base-20);
|
||||
overflow-y: auto;
|
||||
|
||||
div.unpublish-article {
|
||||
background: inherit;
|
||||
border: none;
|
||||
padding: var(--su-3) var(--su-4);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
button.other-things-btn {
|
||||
background: inherit;
|
||||
border: none;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ class ArticlesController < ApplicationController
|
|||
include ApplicationHelper
|
||||
|
||||
before_action :authenticate_user!, except: %i[feed new]
|
||||
before_action :set_article, only: %i[edit manage update destroy stats]
|
||||
before_action :set_article, only: %i[edit manage update destroy stats admin_unpublish]
|
||||
before_action :raise_suspended, only: %i[new create update]
|
||||
before_action :set_cache_control_headers, only: %i[feed]
|
||||
after_action :verify_authorized
|
||||
|
|
@ -190,6 +190,21 @@ class ArticlesController < ApplicationController
|
|||
@organization_id = @article.organization_id
|
||||
end
|
||||
|
||||
def admin_unpublish
|
||||
authorize @article
|
||||
if @article.has_frontmatter?
|
||||
@article.body_markdown.sub!(/\npublished:\s*true\s*\n/, "\npublished: false\n")
|
||||
else
|
||||
@article.published = false
|
||||
end
|
||||
|
||||
if @article.save
|
||||
render json: { message: "success", path: @article.current_state_path }, status: :ok
|
||||
else
|
||||
render json: { message: @article.errors.full_messages }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def base_editor_assigments
|
||||
|
|
|
|||
|
|
@ -17,9 +17,9 @@ export function addCloseListener() {
|
|||
}
|
||||
|
||||
export function initializeHeight() {
|
||||
|
||||
document.documentElement.style.height = '100%';
|
||||
document.body.style.cssText = 'height: 100%; margin: 0; padding-top: 0; overflow-y: hidden';
|
||||
document.body.style.cssText =
|
||||
'height: 100%; margin: 0; padding-top: 0; overflow-y: hidden';
|
||||
document.getElementById('page-content').style.cssText =
|
||||
'margin-top: 0 !important; margin-bottom: 0;';
|
||||
}
|
||||
|
|
@ -34,7 +34,6 @@ function toggleDropdown(type) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
function applyReactedClass(category) {
|
||||
const upVote = document.querySelector("[data-category='thumbsup']");
|
||||
const downVote = document.querySelector("[data-category='thumbsdown']");
|
||||
|
|
@ -143,6 +142,33 @@ async function updateExperienceLevel(currentUserId, articleId, rating, group) {
|
|||
}
|
||||
}
|
||||
|
||||
const adminUnpublishArticle = async (id, username, slug) => {
|
||||
try {
|
||||
const response = await request(`/articles/${id}/admin_unpublish`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({ id, username, slug }),
|
||||
credentials: 'same-origin',
|
||||
});
|
||||
|
||||
const outcome = await response.json();
|
||||
|
||||
/* eslint-disable no-restricted-globals */
|
||||
if (outcome.message == 'success') {
|
||||
window.top.location.assign(`${window.location.origin}${outcome.path}`);
|
||||
} else {
|
||||
top.addSnackbarItem({
|
||||
message: `Error: ${outcome.message}`,
|
||||
addCloseButton: true,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
top.addSnackbarItem({
|
||||
message: `Error: ${error}`,
|
||||
addCloseButton: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function toggleSubmitContainer() {
|
||||
document
|
||||
.getElementById('adjustment-reason-container')
|
||||
|
|
@ -362,6 +388,21 @@ export function addBottomActionsListeners() {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
const unpublishArticleBtn = document.querySelector('#unpublish-article-btn');
|
||||
if (unpublishArticleBtn) {
|
||||
unpublishArticleBtn.addEventListener('click', () => {
|
||||
const {
|
||||
articleId: id,
|
||||
articleAuthor: username,
|
||||
articleSlug: slug,
|
||||
} = unpublishArticleBtn.dataset;
|
||||
|
||||
if (confirm('You are unpublishing this article; are you sure?')) {
|
||||
adminUnpublishArticle(id, username, slug);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function initializeActionsPanel() {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,10 @@ class ArticlePolicy < ApplicationPolicy
|
|||
user_is_author? || user_admin? || user_org_admin? || minimal_admin?
|
||||
end
|
||||
|
||||
def admin_unpublish?
|
||||
minimal_admin?
|
||||
end
|
||||
|
||||
def new?
|
||||
true
|
||||
end
|
||||
|
|
|
|||
|
|
@ -161,6 +161,20 @@
|
|||
</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<% if current_user.any_admin? && @moderatable.published %>
|
||||
<div class="unpublish-article">
|
||||
<button
|
||||
class="crayons-btn crayons-btn--danger"
|
||||
id="unpublish-article-btn"
|
||||
data-article-id="<%= @moderatable.id %>"
|
||||
data-article-author="<%= @moderatable.username %>"
|
||||
data-article-slug="<%= @moderatable.slug %>"
|
||||
>
|
||||
Unpublish Article
|
||||
</button>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="bottom-actions">
|
||||
|
|
|
|||
|
|
@ -195,7 +195,9 @@ Rails.application.routes.draw do
|
|||
resources :messages, only: [:create]
|
||||
resources :chat_channels, only: %i[index show create update]
|
||||
resources :chat_channel_memberships, only: %i[index create edit update destroy]
|
||||
resources :articles, only: %i[update create destroy]
|
||||
resources :articles, only: %i[update create destroy] do
|
||||
patch "/admin_unpublish", to: "articles#admin_unpublish"
|
||||
end
|
||||
resources :article_mutes, only: %i[update]
|
||||
resources :comments, only: %i[create update destroy] do
|
||||
patch "/hide", to: "comments#hide"
|
||||
|
|
|
|||
|
|
@ -22,13 +22,13 @@ RSpec.describe ArticlePolicy do
|
|||
|
||||
context "when user is not the author" do
|
||||
it { is_expected.to permit_actions(%i[new create preview]) }
|
||||
it { is_expected.to forbid_actions(%i[update edit manage delete_confirm destroy]) }
|
||||
it { is_expected.to forbid_actions(%i[update edit manage delete_confirm destroy admin_unpublish]) }
|
||||
|
||||
context "with banned status" do
|
||||
before { user.add_role(:banned) }
|
||||
|
||||
it { is_expected.to permit_actions(%i[new preview]) }
|
||||
it { is_expected.to forbid_actions(%i[create edit manage update delete_confirm destroy]) }
|
||||
it { is_expected.to forbid_actions(%i[create edit manage update delete_confirm destroy admin_unpublish]) }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -49,5 +49,6 @@ RSpec.describe ArticlePolicy do
|
|||
let(:user) { build(:user, :super_admin) }
|
||||
|
||||
it { is_expected.to permit_actions(%i[update new edit manage create delete_confirm destroy preview]) }
|
||||
it { is_expected.to permit_actions(%i[admin_unpublish]) }
|
||||
end
|
||||
end
|
||||
|
|
|
|||
23
spec/requests/articles/articles_admin_unpublish_spec.rb
Normal file
23
spec/requests/articles/articles_admin_unpublish_spec.rb
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
require "rails_helper"
|
||||
|
||||
RSpec.describe "ArticlesAdminUnpublish", type: :request do
|
||||
let(:user) { create(:user) }
|
||||
let(:article) { create(:article, user_id: user.id) }
|
||||
let(:super_admin) { create(:user, :super_admin) }
|
||||
|
||||
before do
|
||||
sign_in super_admin
|
||||
end
|
||||
|
||||
it "unpublishes an article" do
|
||||
expect(article.published).to be true
|
||||
patch "/articles/#{article.id}/admin_unpublish", params: {
|
||||
id: article.id,
|
||||
username: user.username,
|
||||
slug: article.slug
|
||||
}
|
||||
|
||||
article.reload
|
||||
expect(article.published).to be false
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Reference in a new issue