Trusted users can view & use moderator response templates (#17867)
* Trusted users can use moderator response templates * Iron out container reveal logic * Explanatory comment on controller endpoint confusion
This commit is contained in:
parent
551e42d80e
commit
07e1364f54
8 changed files with 104 additions and 55 deletions
|
|
@ -42,7 +42,7 @@ function buildCommentFormHTML(commentableId, commentableType, parentId) {
|
|||
<div class="response-templates-container crayons-card crayons-card--secondary p-4 mb-4 fs-base comment-form__templates hidden">
|
||||
<header>
|
||||
<button type="button" class="personal-template-button active" data-target-type="personal" data-form-id="new_comment">Personal</button>
|
||||
<button type="button" class="moderator-template-button hidden" data-target-type="moderator" data-form-id="new_comment">Moderator</button>
|
||||
<button type="button" class="moderator-template-button hidden" data-target-type="moderator" data-form-id="new_comment">Trusted User</button>
|
||||
</header>
|
||||
<img class="loading-img hidden" src="<%= asset_path("loading-ellipsis.svg") %>" alt="loading">
|
||||
<div class="personal-responses-container"></div>
|
||||
|
|
|
|||
|
|
@ -6,18 +6,22 @@ class ResponseTemplatesController < ApplicationController
|
|||
MOD_TYPES = %w[mod_comment tag_adjustment].freeze
|
||||
ADMIN_TYPES = %w[email_reply abuse_report_email_reply].freeze
|
||||
|
||||
# This endpoint is called to provide a mix of personal & mod_comment templates
|
||||
# when replying to a comment, it also might be called for Email and Abuse
|
||||
# Report Email templates. To be compatible with both uses, it returns a Hash
|
||||
# sometimes and an array at other times. When type_of is present, this returns
|
||||
# an array of templates. When the type_of param is missing, this returns a
|
||||
# hash, keys are type_ofs, values are an array of templates.
|
||||
def index
|
||||
raise ArgumentError, "Missing param type_of" if params[:type_of].blank?
|
||||
|
||||
user_id = params[:type_of] == "personal_comment" ? current_user.id : nil
|
||||
@response_templates = ResponseTemplate.where(type_of: params[:type_of], user_id: user_id)
|
||||
@response_templates = policy_scope(ResponseTemplate).group_by(&:type_of)
|
||||
@response_templates = @response_templates.fetch(params[:type_of], []) if params[:type_of].present?
|
||||
|
||||
if MOD_TYPES.include?(params[:type_of])
|
||||
authorize @response_templates, :moderator_index?
|
||||
authorize :response_template, :moderator_index?
|
||||
elsif ADMIN_TYPES.include?(params[:type_of])
|
||||
authorize @response_templates, :admin_index?
|
||||
authorize :response_template, :admin_index?
|
||||
else
|
||||
authorize @response_templates, :index?
|
||||
authorize :response_template, :index?
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
|
|
|
|||
|
|
@ -157,18 +157,15 @@ function addClickListeners(form) {
|
|||
|
||||
function fetchResponseTemplates(typeOf, formId) {
|
||||
const form = document.getElementById(formId);
|
||||
let dataContainer;
|
||||
if (typeOf === 'personal_comment') {
|
||||
dataContainer = form.getElementsByClassName(
|
||||
'personal-responses-container',
|
||||
)[0];
|
||||
} else if (typeOf === 'mod_comment') {
|
||||
dataContainer = form.getElementsByClassName(
|
||||
'moderator-responses-container',
|
||||
)[0];
|
||||
}
|
||||
|
||||
const typesOf = [
|
||||
['personal_comment', 'personal-responses-container'],
|
||||
['mod_comment', 'moderator-responses-container'],
|
||||
];
|
||||
|
||||
/* eslint-disable-next-line no-undef */
|
||||
fetch(`/response_templates?type_of=${typeOf}`, {
|
||||
|
||||
fetch(`/response_templates`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
|
|
@ -179,9 +176,31 @@ function fetchResponseTemplates(typeOf, formId) {
|
|||
.then((response) => response.json())
|
||||
.then((response) => {
|
||||
form.querySelector('img.loading-img').classList.toggle('hidden');
|
||||
dataContainer.innerHTML = buildHTML(response, typeOf);
|
||||
|
||||
let revealed;
|
||||
const topLevelData = document.getElementById('response-templates-data');
|
||||
topLevelData.innerHTML = dataContainer.parentElement.innerHTML;
|
||||
|
||||
for (const typesOfContainers of typesOf) {
|
||||
const [typeOf, containedIn] = typesOfContainers;
|
||||
|
||||
if (typeof response[typeOf] != 'undefined') {
|
||||
const dataContainer = form.getElementsByClassName(containedIn)[0];
|
||||
dataContainer.innerHTML = buildHTML(response[typeOf], typeOf);
|
||||
|
||||
if (revealed) {
|
||||
topLevelData.classList.add(typeOf);
|
||||
dataContainer.classList.add('hidden');
|
||||
prepareHeaderButtons(form);
|
||||
} else {
|
||||
revealed = dataContainer;
|
||||
dataContainer.classList.remove('hidden');
|
||||
topLevelData.classList.add(typeOf);
|
||||
}
|
||||
|
||||
topLevelData.innerHTML = dataContainer.parentElement.innerHTML;
|
||||
}
|
||||
}
|
||||
|
||||
addClickListeners(form);
|
||||
});
|
||||
}
|
||||
|
|
@ -197,28 +216,12 @@ function prepareHeaderButtons(form) {
|
|||
personalTemplateButton.addEventListener('click', (e) => {
|
||||
toggleTemplateTypeButton(form, e);
|
||||
});
|
||||
personalTemplateButton.classList.remove('hidden');
|
||||
|
||||
modTemplateButton.addEventListener('click', (e) => {
|
||||
toggleTemplateTypeButton(form, e);
|
||||
});
|
||||
modTemplateButton.classList.remove('hidden');
|
||||
|
||||
modTemplateButton.addEventListener(
|
||||
'click',
|
||||
() => {
|
||||
const topLevelData = document.getElementById('response-templates-data');
|
||||
const modDataNotFetched =
|
||||
topLevelData.innerHTML !== ''
|
||||
? topLevelData.getElementsByClassName(
|
||||
'moderator-responses-container',
|
||||
)[0].childElementCount === 0
|
||||
: false;
|
||||
if (modDataNotFetched) {
|
||||
form.querySelector('img.loading-img').classList.toggle('hidden');
|
||||
fetchResponseTemplates('mod_comment', form.id);
|
||||
}
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
}
|
||||
|
||||
function copyData(responsesContainer) {
|
||||
|
|
@ -236,8 +239,8 @@ function openButtonCallback(form) {
|
|||
const responsesContainer = form.getElementsByClassName(
|
||||
'response-templates-container',
|
||||
)[0];
|
||||
const dataFetched =
|
||||
document.getElementById('response-templates-data').innerHTML !== '';
|
||||
const topLevelData = document.getElementById('response-templates-data');
|
||||
const dataFetched = topLevelData.innerHTML !== '';
|
||||
|
||||
responsesContainer.classList.toggle('hidden');
|
||||
|
||||
|
|
@ -249,10 +252,24 @@ function openButtonCallback(form) {
|
|||
} else if (!dataFetched && !containerHidden) {
|
||||
loadData(form);
|
||||
}
|
||||
/* eslint-disable-next-line no-undef */
|
||||
if (userData().moderator_for_tags.length > 0) {
|
||||
|
||||
const hasBothTemplates =
|
||||
topLevelData.classList.contains('personal_comment') &&
|
||||
topLevelData.classList.contains('mod_comment');
|
||||
|
||||
if (hasBothTemplates) {
|
||||
form
|
||||
.getElementsByClassName('moderator-template-button')[0]
|
||||
.classList.remove('hidden');
|
||||
form
|
||||
.getElementsByClassName('personal-template-button')[0]
|
||||
.classList.remove('hidden');
|
||||
|
||||
prepareHeaderButtons(form);
|
||||
} else {
|
||||
form
|
||||
.getElementsByClassName('moderator-template-button')[0]
|
||||
.classList.add('hidden');
|
||||
form
|
||||
.getElementsByClassName('personal-template-button')[0]
|
||||
.classList.add('hidden');
|
||||
|
|
|
|||
|
|
@ -1,6 +1,16 @@
|
|||
class ResponseTemplatePolicy < ApplicationPolicy
|
||||
PERMITTED_ATTRIBUTES = %i[content_type content title].freeze
|
||||
|
||||
class Scope < Scope
|
||||
def resolve
|
||||
if user.has_trusted_role? || user.any_admin? || user.moderator? || user.tag_moderator?
|
||||
scope.where(user: user, type_of: "personal_comment") + scope.where.not(type_of: "personal_comment")
|
||||
else
|
||||
scope.where(user: user, type_of: "personal_comment")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def index?
|
||||
true
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<%= form_with(model: [:admin, response_template], local: true, html: { class: "crayons-card p-6" }) do |f| %>
|
||||
<div class="form-group">
|
||||
<%= f.label :type_of, "Type of Response Template" %>
|
||||
<% type_of_options = [["Mod Comment", "mod_comment"], ["Personal Comment", "personal_comment"], %w[Email email_reply], ["Abuse Report Email", "abuse_report_email_reply"]] %>
|
||||
<% type_of_options = [["Trusted User Comment", "mod_comment"], ["Personal Comment", "personal_comment"], %w[Email email_reply], ["Abuse Report Email", "abuse_report_email_reply"]] %>
|
||||
<%= f.select :type_of, options_for_select(type_of_options, response_template.type_of), html: { required: true }, class: "form-control" %>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
json.array!(@response_templates) do |response_template|
|
||||
json.extract!(
|
||||
response_template,
|
||||
:id,
|
||||
:type_of,
|
||||
:user_id,
|
||||
:title,
|
||||
:content,
|
||||
)
|
||||
attrs = %i[id type_of user_id title content]
|
||||
|
||||
if @response_templates.is_a?(Array)
|
||||
json.array!(@response_templates, *attrs)
|
||||
else
|
||||
@response_templates.each_pair do |type_of, templates|
|
||||
json.set!(type_of) do
|
||||
json.array!(templates, *attrs)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ en:
|
|||
create:
|
||||
subtitle: Create template
|
||||
desc: Templates let you quickly answer FAQs or store snippets for re-use.
|
||||
moderator: Moderator
|
||||
moderator: Trusted User
|
||||
personal: Personal
|
||||
submit: Submit
|
||||
preview: Preview
|
||||
|
|
@ -144,4 +144,3 @@ en:
|
|||
one: "%{count}%{start}\_comment%{end}"
|
||||
other: "%{count}%{start}\_comments%{end}"
|
||||
title: Comments
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,15 @@ RSpec.describe "ResponseTemplate", type: :request do
|
|||
expect(response.parsed_body.length).to eq total_response_templates
|
||||
end
|
||||
|
||||
it "returns a hash with personal response templates if type_of unspecified" do
|
||||
create_list(:response_template, 2, user: nil, type_of: "mod_comment")
|
||||
create_list(:response_template, 2, user: user, type_of: "personal_comment")
|
||||
get response_templates_path, params: { type_of: nil }, headers: { HTTP_ACCEPT: "application/json" }
|
||||
json = JSON.parse(response.body)
|
||||
expect(json.keys).to contain_exactly("personal_comment")
|
||||
expect(json.values.flatten.count).to eq(2)
|
||||
end
|
||||
|
||||
it "returns only the users' response templates" do
|
||||
create(:response_template, user: nil, type_of: "mod_comment")
|
||||
create_list(:response_template, 2, user: user, type_of: "personal_comment")
|
||||
|
|
@ -77,6 +86,15 @@ RSpec.describe "ResponseTemplate", type: :request do
|
|||
expect(JSON.parse(response.body).length).to eq 2
|
||||
end
|
||||
|
||||
it "returns both personal and moderator response templates if type_of unspecified" do
|
||||
create_list(:response_template, 2, user: nil, type_of: "mod_comment")
|
||||
create_list(:response_template, 2, user: moderator, type_of: "personal_comment")
|
||||
get response_templates_path, params: { type_of: nil }, headers: { HTTP_ACCEPT: "application/json" }
|
||||
json = JSON.parse(response.body)
|
||||
expect(json.keys).to contain_exactly("mod_comment", "personal_comment")
|
||||
expect(json.values.flatten.count).to eq(4)
|
||||
end
|
||||
|
||||
it "raises unauthorized error if trying to view admin response templates" do
|
||||
create_list(:response_template, 2, user: nil, type_of: "email_reply", content_type: "html")
|
||||
expect do
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue