* db * Add an iframe to host mod actions menu This iframe is probably the fastest way to get a working mod actions menu without rewriting existing code. We may eventually re-write this, but based on Ben's recommendation we are going to go with an iframe because it gets us a working feature quickly and it seems to be serving us well in other parts of the application. * Add the mod actions menu button to articles When the user is "trusted" they will see a small badge on the lower right-hand side of the screen. Clicking the icon reveals the mod actions menu. * Add header to actions panel (#7395) * Update iframe border * Add 'Moderate Post' header and button * Use inline SVG over <img src='svg'> * Use Crayons variables for units * Use flexbox to center chevron * Add accidentally deleted positioning * Add chevron-right.svg * Use a partial for actions panel and revert /mod page * Load actions panel in iframe and not /mod * Style UpVote, DownVote and Vomit buttons (#7421) * Add a iframe to host mod actions menu * Up and Downvote Icons * Starting with vomit button * Completed UI; interactions pending * Incorporated Andy changes; making mod action-panel * space reactions away from header * UI tweaks and corrections 1 * Fixing styles to match crayons * choose pure white color css variable * Add thumbs-up emoji; address Lisa comments * Trying to fix reactions interactions * remove clearThumbReactions function Co-authored-by: jacobherrington <jacobherringtondeveloper@gmail.com> * Add bottom layout for actions panel (#7501) * WIP for Lisa * Finalize mod actions bottom section * Use showing instead of hidden for transition * Add new optimized SVGs * Fix weird height issues for actions panel * Move hover styles below the cascade, yo * Use margin-top for sticky footer over height: 100% * Move initialize actions panel button to Webpack (#7506) * Move actions panel JS code to webpack * Refactor and use destructuring Thanks @nickytonline! Co-Authored-By: Nick Taylor <nick@iamdeveloper.com> * Rename file Co-authored-by: Nick Taylor <nick@iamdeveloper.com> * Configure Experience Level options for Mod Action Panel (#7507) * Add a iframe to host mod actions menu * Click interactions (no toggle of dropdowns yet) Co-authored-by: jacobherrington <jacobherringtondeveloper@gmail.com> * Fix the iframe's path (#7509) * Toggle the 'Set Experience Level' menu on button-click (#7529) * Add a iframe to host mod actions menu * Add set-exp dropdown functionality * WIP: making exp level updates async * Completes async implementation * Fix controller for "actions_panel" and "/mod" Co-authored-by: jacobherrington <jacobherringtondeveloper@gmail.com> * Add close button interaction (#7552) * Rename actions panel pack file * Add close button toggle to actions panel * DB update * Move more code to Webpack and add conditional tag adjustments section (#7652) * Move JS code to Webpack * Clean up a few style issues * Add styling for tag adjustments * Add active/inactive functionality * Add conditional rendering for tag adjustments section * Add missing variable oops (#7673) * update with upstream * Add missing subtract svg (#7695) * Create 'Flag User' modal (#7626) * Add a iframe to host mod actions menu * Starting task * Phase 1 * Fix "Close Modal" icon * Implement open modal * Working on all "Close Modal" actions * Almost-complete implementation * Remove Abuse Report snackbar * Delete unneeded JS code * Remove "How does this work?" links * Link to Community Moderation page * Addressing code review corrections * Update api call to use "request" method * Make else path clearer in api call Co-authored-by: jacobherrington <jacobherringtondeveloper@gmail.com> * Mod Actions Panel: Prevent nonsensical reaction pairs (#7699) * Still implementing * Completed Implementation * Address code review feednack; refactor "create" method * Move scss variables to native-css (for ModPanel code only) * Correct missing native css * update to native-css again * More sccs -> native-css fixes * Adjust ModPanel Button position on mobile (#7744) * Adjusted ModPanel position on mobile * Better adjustment * Fix phantom schema.rb changes * Fix phantom schema.rb changes * Finalize tag adjustments (#7740) * Finalize tag adjustments and flow * Fix logic for rendering adjust tags options * Clean up alert response * Rename user to trusted user * Use new CSS variables * Add/remove tag on article's tag section after adjusting * Fix logic for admin tag input and some other niceties * Final bug fixes mod panel (#7772) * Use correct boolean statement * Clear the value of the textarea not div * Use the correct attribute * Use actions panel for /mod page (#7775) * Fix loading logic with tag adjust button conditional render * Link to tag moderation for adjust tags section * Use top over window.parent Thanks @nickytonline! Co-authored-by: Nick Taylor <nick@iamdeveloper.com> * Use Webpack alias to import Co-authored-by: Nick Taylor <nick@iamdeveloper.com> * Use place-items shorthand over align-items + justify-content Thanks @nickytonline! Co-authored-by: Nick Taylor <nick@iamdeveloper.com> * Fix typo for utilities * Add new line to end of file * Disable linter for use for top function * Use align-items and justify-content center * Update SVGs to spec * Remove unnecessary height and use spacing units for open button * Remove accidentally added comment * Error handling when selecting reactions; optimize "reactions.each" loop * Add comment about clearing innerHTML * Revert schema back to master * Use each since there should only be two reactions * Fix typo oops * Add missing height oops * Return if there are no reactions to be destroyed * Center path with GUI tool shrug * chose more appropriate name for JS pack; rewrote api calls using async-await * Rename variables and methods and check categories before clearing * Use addSnackbarItem instead of hardcoded version * Place articleModerationTools pack behind user_signed_in * Correct omission * Implement dynamic imports for ModPanel tools * Implemented async/await; added snackbar message for null response use-case * Fix the "/article/mod" page; make sure all reactions, vomit-user and exp-level working as expected * Update language to reflect new UI * Close the modal after the "Confirm Action" button is clicked * Change text for Spam/Abuse button * Fix schema to use with master * Update schema to master again * Use crayons button for actions panel button * UI tweak to play nicer with site themes * Make actions panel work for dev.to/mod * Add snackbar for reactions * Update how does this work links * Add "thumbsup" reactions to logging * Add thumbsup to categories * Add external link icon * Changes to reflect "Privileged" and not "Negative" reactions; spec changes pending * Updated instance variable * Starting to update the specs * Update "thumbsup" points from 10 to 5 * Still working on both requests * Embracing an easier implemantation (thanks Andy!) * Add tests for actions panel * Add some basic tests for actions panel * Change to flag to admins * Indicate that 'Reason for Tag adjust' is required Co-authored-by: Mac Siri <krairit.siri@gmail.com> * Rename negative_reactions to privileged * Update rating level to use correct values * Use a different display value than backend value * Don't show adjust tags for trusted users * Add specs for moderation_routes for admin and super_admin Co-authored-by: Arit Amana <msarit@gmail.com> Co-authored-by: jacobherrington <jacobherringtondeveloper@gmail.com> Co-authored-by: Arit Amana <32520970+msarit@users.noreply.github.com> Co-authored-by: Nick Taylor <nick@iamdeveloper.com> Co-authored-by: Mac Siri <krairit.siri@gmail.com>
370 lines
10 KiB
JavaScript
370 lines
10 KiB
JavaScript
import { request } from '@utilities/http';
|
|
|
|
export function addCloseListener() {
|
|
const button = document.querySelector('.close-actions-panel');
|
|
button.addEventListener('click', () => {
|
|
// getting the article show page document because this is called within an iframe
|
|
// eslint-disable-next-line no-restricted-globals
|
|
const articleDocument = top.document;
|
|
|
|
articleDocument
|
|
.querySelector('.mod-actions-menu')
|
|
.classList.toggle('showing');
|
|
articleDocument
|
|
.querySelector('.mod-actions-menu-btn')
|
|
.classList.toggle('hidden');
|
|
});
|
|
}
|
|
|
|
export function initializeHeight() {
|
|
document.documentElement.style.height = '100%';
|
|
document.body.style.cssText = 'height: 100%; margin: 0; padding-top: 0;';
|
|
document.getElementById('page-content').style.cssText =
|
|
'margin-top: 0 !important; margin-bottom: 0;';
|
|
}
|
|
|
|
function toggleDropdown(type) {
|
|
if (type === 'set-experience') {
|
|
document
|
|
.querySelector('.set-experience-options')
|
|
.classList.toggle('hidden');
|
|
} else if (type === 'adjust-tags') {
|
|
document.querySelector('.adjust-tags-options').classList.toggle('hidden');
|
|
}
|
|
}
|
|
|
|
function applyReactedClass(category) {
|
|
const upVote = document.querySelector("[data-category='thumbsup']");
|
|
const downVote = document.querySelector("[data-category='thumbsdown']");
|
|
const vomitVote = document.querySelector("[data-category='vomit']");
|
|
|
|
if (category === 'thumbsup') {
|
|
downVote.classList.remove('reacted');
|
|
vomitVote.classList.remove('reacted');
|
|
} else {
|
|
upVote.classList.remove('reacted');
|
|
}
|
|
}
|
|
|
|
export function addReactionButtonListeners() {
|
|
const butts = Array.from(
|
|
document.querySelectorAll('.reaction-button, .reaction-vomit-button'),
|
|
);
|
|
/* eslint-disable camelcase */
|
|
butts.forEach((butt) => {
|
|
butt.addEventListener('click', async (event) => {
|
|
event.preventDefault();
|
|
const {
|
|
reactableType: reactable_type,
|
|
category,
|
|
reactableId: reactable_id,
|
|
} = butt.dataset;
|
|
|
|
applyReactedClass(category);
|
|
butt.classList.toggle('reacted');
|
|
|
|
try {
|
|
const response = await request('/reactions', {
|
|
method: 'POST',
|
|
body: { reactable_type, category, reactable_id },
|
|
});
|
|
|
|
const outcome = await response.json();
|
|
|
|
let message;
|
|
/* eslint-disable no-restricted-globals */
|
|
if (outcome.result === 'create' && outcome.category === 'thumbsup') {
|
|
message = 'This post will be more visible.';
|
|
} else if (
|
|
outcome.result === 'create' &&
|
|
outcome.category === 'thumbsdown'
|
|
) {
|
|
message = 'This post will be less visible.';
|
|
} else if (
|
|
outcome.result === 'create' &&
|
|
outcome.category === 'vomit'
|
|
) {
|
|
message = "You've flagged this post as abusive or spam.";
|
|
} else if (outcome.result === 'destroy') {
|
|
message = 'Your quality rating was removed.';
|
|
} else if (outcome.error) {
|
|
message = `Error: ${outcome.error}`;
|
|
}
|
|
top.addSnackbarItem({
|
|
message,
|
|
addCloseButton: true,
|
|
});
|
|
/* eslint-enable no-restricted-globals */
|
|
} catch (error) {
|
|
// eslint-disable-next-line no-alert
|
|
alert(error);
|
|
}
|
|
});
|
|
});
|
|
/* eslint-enable camelcase */
|
|
}
|
|
|
|
function clearExpLevels() {
|
|
Array.from(
|
|
document.getElementsByClassName('level-rating-button selected'),
|
|
).forEach((el) => {
|
|
el.classList.remove('selected');
|
|
});
|
|
}
|
|
|
|
async function updateExperienceLevel(currentUserId, articleId, rating, group) {
|
|
try {
|
|
const response = await request('/rating_votes', {
|
|
method: 'POST',
|
|
body: JSON.stringify({
|
|
user_id: currentUserId,
|
|
article_id: articleId,
|
|
rating,
|
|
group,
|
|
}),
|
|
});
|
|
|
|
const outcome = await response.json();
|
|
|
|
if (outcome.result === 'Success') {
|
|
clearExpLevels();
|
|
document
|
|
.getElementById(`js__rating__vote__${rating}`)
|
|
.classList.add('selected');
|
|
} else {
|
|
// eslint-disable-next-line no-alert
|
|
alert(outcome.error);
|
|
}
|
|
} catch (error) {
|
|
// eslint-disable-next-line no-alert
|
|
alert(error);
|
|
}
|
|
}
|
|
|
|
function toggleSubmitContainer() {
|
|
document
|
|
.getElementById('adjustment-reason-container')
|
|
.classList.toggle('hidden');
|
|
}
|
|
|
|
function clearAdjustmentReason() {
|
|
document.getElementById('tag-adjustment-reason').value = '';
|
|
}
|
|
|
|
function renderTagOnArticle(tagName, colors) {
|
|
/* eslint-disable no-restricted-globals */
|
|
let articleTagsContainer;
|
|
if (top.document.location.pathname.endsWith('/mod')) {
|
|
[articleTagsContainer] = top.document
|
|
.getElementById('quick-mod-article')
|
|
.contentDocument.getElementsByClassName('tags');
|
|
} else {
|
|
[articleTagsContainer] = top.document.getElementsByClassName('tags');
|
|
}
|
|
/* eslint-enable no-restricted-globals */
|
|
|
|
const newTag = document.createElement('a');
|
|
newTag.innerText = `#${tagName}`;
|
|
newTag.setAttribute('class', 'tag');
|
|
newTag.setAttribute('href', `/t/${tagName}`);
|
|
newTag.style = `background-color: ${colors.bg}; color: ${colors.text};`;
|
|
|
|
articleTagsContainer.appendChild(newTag);
|
|
}
|
|
|
|
async function adjustTag(el) {
|
|
const reasonForAdjustment = document.getElementById('tag-adjustment-reason')
|
|
.value;
|
|
const body = {
|
|
tag_adjustment: {
|
|
// TODO: change to tag ID
|
|
tag_name: el.dataset.tagName || el.value,
|
|
article_id: el.dataset.articleId,
|
|
adjustment_type:
|
|
el.dataset.adjustmentType === 'subtract' ? 'removal' : 'addition',
|
|
reason_for_adjustment: reasonForAdjustment,
|
|
},
|
|
};
|
|
|
|
try {
|
|
const response = await request('/tag_adjustments', {
|
|
method: 'POST',
|
|
body: JSON.stringify(body),
|
|
});
|
|
|
|
const outcome = await response.json();
|
|
|
|
if (outcome.status === 'Success') {
|
|
let adjustedTagName;
|
|
if (el.tagName === 'BUTTON') {
|
|
adjustedTagName = el.dataset.tagName;
|
|
el.remove();
|
|
} else {
|
|
adjustedTagName = el.value;
|
|
// eslint-disable-next-line no-param-reassign
|
|
el.value = '';
|
|
}
|
|
|
|
toggleSubmitContainer();
|
|
clearAdjustmentReason();
|
|
|
|
if (outcome.result === 'addition') {
|
|
renderTagOnArticle(adjustedTagName, outcome.colors);
|
|
} else {
|
|
// eslint-disable-next-line no-restricted-globals
|
|
const tagOnArticle = top.document.querySelector(
|
|
`.tag[href="/t/${adjustedTagName}"]`,
|
|
);
|
|
tagOnArticle.remove();
|
|
}
|
|
|
|
// eslint-disable-next-line no-restricted-globals
|
|
top.addSnackbarItem({
|
|
message: `The #${adjustedTagName} tag was ${
|
|
outcome.result === 'addition' ? 'added' : 'removed'
|
|
}.`,
|
|
addCloseButton: true,
|
|
});
|
|
} else {
|
|
// eslint-disable-next-line no-restricted-globals
|
|
top.addSnackbarItem({
|
|
message: `An error occurred: ${outcome.error}`,
|
|
addCloseButton: true,
|
|
});
|
|
}
|
|
} catch (error) {
|
|
// eslint-disable-next-line no-alert
|
|
alert(error);
|
|
}
|
|
}
|
|
|
|
export function handleAdjustTagBtn(btn) {
|
|
const currentActiveTags = document.querySelectorAll(
|
|
'button.adjustable-tag.active',
|
|
);
|
|
const adminTagInput = document.getElementById('admin-add-tag');
|
|
/* eslint-disable no-restricted-globals */
|
|
/* eslint-disable no-alert */
|
|
if (
|
|
adminTagInput &&
|
|
adminTagInput.value !== '' &&
|
|
confirm(
|
|
'This will clear your current "Add a tag" input. Do you want to continue?',
|
|
)
|
|
) {
|
|
/* eslint-enable no-restricted-globals */
|
|
/* eslint-enable no-alert */
|
|
adminTagInput.value = '';
|
|
} else if (currentActiveTags.length > 0) {
|
|
currentActiveTags.forEach((tag) => {
|
|
if (tag !== btn) {
|
|
tag.classList.remove('active');
|
|
}
|
|
btn.classList.toggle('active');
|
|
});
|
|
if (btn.classList.contains('active')) {
|
|
document
|
|
.getElementById('adjustment-reason-container')
|
|
.classList.remove('hidden');
|
|
} else {
|
|
document
|
|
.getElementById('adjustment-reason-container')
|
|
.classList.add('hidden');
|
|
}
|
|
} else {
|
|
btn.classList.toggle('active');
|
|
toggleSubmitContainer();
|
|
}
|
|
}
|
|
|
|
function handleAdminInput() {
|
|
const addTagInput = document.getElementById('admin-add-tag');
|
|
|
|
if (addTagInput) {
|
|
addTagInput.addEventListener('focus', () => {
|
|
document
|
|
.getElementById('adjustment-reason-container')
|
|
.classList.remove('hidden');
|
|
|
|
const activeTagBtns = Array.from(
|
|
document.querySelectorAll('button.adjustable-tag.active'),
|
|
);
|
|
activeTagBtns.forEach((btn) => {
|
|
btn.classList.remove('active');
|
|
});
|
|
});
|
|
addTagInput.addEventListener('focusout', () => {
|
|
if (addTagInput.value === '') {
|
|
toggleSubmitContainer();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
export function addAdjustTagListeners() {
|
|
Array.from(document.getElementsByClassName('adjustable-tag')).forEach(
|
|
(btn) => {
|
|
btn.addEventListener('click', () => {
|
|
handleAdjustTagBtn(btn);
|
|
});
|
|
},
|
|
);
|
|
|
|
const adjustTagSubmitBtn = document.getElementById('tag-adjust-submit');
|
|
if (adjustTagSubmitBtn) {
|
|
adjustTagSubmitBtn.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
const textArea = document.getElementById('tag-adjustment-reason');
|
|
const dataSource =
|
|
document.querySelector('button.adjustable-tag.active') ||
|
|
document.getElementById('admin-add-tag');
|
|
|
|
if (textArea.checkValidity()) {
|
|
adjustTag(dataSource);
|
|
}
|
|
});
|
|
|
|
handleAdminInput();
|
|
}
|
|
}
|
|
|
|
export function addBottomActionsListeners() {
|
|
addAdjustTagListeners();
|
|
Array.from(document.getElementsByClassName('other-things-btn')).forEach(
|
|
(btn) => {
|
|
btn.addEventListener('click', () => {
|
|
btn.classList.toggle('active');
|
|
const otherBtns = Array.from(
|
|
document.getElementsByClassName('other-things-btn'),
|
|
).filter((otherBtn) => otherBtn !== btn);
|
|
otherBtns.forEach((otherBtn) => {
|
|
otherBtn.classList.toggle('inactive');
|
|
});
|
|
|
|
btn.querySelector('.label-wrapper > .icon').classList.toggle('hidden');
|
|
btn
|
|
.querySelector('.toggle-chevron-container')
|
|
.classList.toggle('rotated');
|
|
toggleDropdown(btn.dataset.otherThingsType);
|
|
});
|
|
},
|
|
);
|
|
|
|
document.querySelectorAll('.level-rating-button').forEach((btn) => {
|
|
btn.addEventListener('click', () => {
|
|
updateExperienceLevel(
|
|
btn.dataset.userId,
|
|
btn.dataset.articleId,
|
|
btn.value,
|
|
btn.dataset.group,
|
|
);
|
|
});
|
|
});
|
|
}
|
|
|
|
export function initializeActionsPanel() {
|
|
initializeHeight();
|
|
addCloseListener();
|
|
addReactionButtonListeners();
|
|
addBottomActionsListeners();
|
|
}
|