diff --git a/.rubocop.yml b/.rubocop.yml
index f3c5b2e98..073b96ef0 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -225,6 +225,7 @@ Lint/MissingSuper:
Enabled: true
Exclude:
- 'app/components/**/*.rb'
+ - 'app/policies/**/*.rb'
Lint/MixedRegexpCaptureTypes:
Description: 'Do not mix named captures and numbered captures in a Regexp literal.'
diff --git a/app/assets/stylesheets/config/_colors.css b/app/assets/stylesheets/config/_colors.css
index e45df0a52..bed6c0057 100644
--- a/app/assets/stylesheets/config/_colors.css
+++ b/app/assets/stylesheets/config/_colors.css
@@ -366,6 +366,30 @@
--banner-color: var(--white);
--banner-accent: var(--accent-brand);
+ /***********************************************
+ ** Tag Moderation ******************************
+ ***********************************************/
+
+ /* Tag item */
+ --tag-item-bg-hover: rgb(var(--indigo-50));
+
+ /* Subtract */
+ --subtract-color: rgb(var(--red-600));
+ --subtract-color-hover: rgb(var(--white));
+ --subtract-bg: rgba(var(--red-600), 0.2);
+ --subtract-bg-hover: rgba(var(--red-600));
+
+ /* Plus */
+ --plus-color: rgb(var(--green-800));
+ --plus-color-hover: rgb(var(--white));
+ --plus-bg: rgb(var(--green-100));
+ --plus-bg-hover: rgb(var(--green-600));
+
+ /* Tag name */
+ --tag-hash: rgb(var(--grey-500));
+ --tag-name: rgb(var(--grey-800));
+ --add-tag-hash: rgb(var(--grey-700));
+
/***********************************************
** Utilities ***********************************
***********************************************/
diff --git a/app/assets/stylesheets/themes/dark.css b/app/assets/stylesheets/themes/dark.css
index 228ae1f49..a8cf067f5 100644
--- a/app/assets/stylesheets/themes/dark.css
+++ b/app/assets/stylesheets/themes/dark.css
@@ -1,6 +1,6 @@
:root {
color-scheme: dark;
-
+
--theme-secondary-color: #cedae2;
--theme-container-background: #141f2d;
--theme-container-accent-background: #202c3d;
@@ -353,6 +353,30 @@
--selected-date-color: var(--body-color);
--selected-range-color: var(--body-color-inverted);
+ /***********************************************
+ ** Tag Moderation ******************************
+ ***********************************************/
+
+ /* Tag item */
+ --tag-item-bg-hover: rgba(var(--indigo-900), 0.5);
+
+ /* Subtract */
+ --subtract-color: rgb(var(--red-200));
+ --subtract-color-hover: rgb(var(--white));
+ --subtract-bg: rgba(var(--red-600), 0.4);
+ --subtract-bg-hover: rgb(var(--red-600));
+
+ /* Plus */
+ --plus-color: rgb(var(--green-200));
+ --plus-color-hover: rgb(var(--white));
+ --plus-bg: rgba(var(--green-600), 0.4);
+ --plus-bg-hover: rgb(var(--green-600));
+
+ /* Tag name */
+ --tag-hash: rgb(var(--grey-500));
+ --tag-name: rgb(var(--grey-50));
+ --add-tag-hash: rgb(var(--grey-200));
+
/***********************************************
** Utilities ***********************************
***********************************************/
diff --git a/app/assets/stylesheets/views/mod-actions.scss b/app/assets/stylesheets/views/mod-actions.scss
index 5f7fed6b6..f95c5718f 100644
--- a/app/assets/stylesheets/views/mod-actions.scss
+++ b/app/assets/stylesheets/views/mod-actions.scss
@@ -31,9 +31,18 @@
}
.mod-container {
+ hr {
+ width: 100% !important;
+ max-width: none !important;
+ height: 1px;
+ margin: 8px 0px 8px 0px !important;
+ padding: 0px;
+ background-color: var(--grey-200);
+ }
+
display: flex;
flex-direction: column;
- background-color: var(--base-0);
+ background-color: var(--base-inverted);
border: none;
box-shadow: none;
margin-bottom: 0;
@@ -201,6 +210,20 @@
}
}
+ .max-tags-text {
+ display: flex;
+ padding: var(--su-2) var(--su-3);
+ align-items: center;
+ gap: var(--su-2);
+
+ span {
+ color: var(--color-secondary);
+ font-size: var(--fs-s);
+ line-height: var(--lh-base);
+ letter-spacing: -0.28px;
+ }
+ }
+
.additional-subtext-section {
font-weight: var(--fw-medium);
font-size: var(--fs-xs);
@@ -375,49 +398,133 @@
.add-tag-container {
padding: var(--su-2) var(--su-1);
}
+ .add-tag-header {
+ color: var(--tag-name);
+ font-size: var(--fs-base);
+ font-weight: var(--fw-bold);
+ line-height: var(--lh-base);
+ }
+ .tag-input-container {
+ display: flex;
+ margin-top: 12px;
+ padding: 8px 8px 8px 12px;
+ align-items: center;
+ gap: 8px;
+ align-self: stretch;
+ border-radius: 6px;
+ border-width: 1px;
+ border-style: solid;
+ border-color: var(--grey-500);
+
+ span {
+ font-weight: var(--fw-medium);
+ font-size: var(--fs-xl);
+ color: var(--add-tag-hash);
+ }
+ input {
+ border: none;
+ outline: none;
+ background-color: transparent;
+ color: var(--grey-500);
+ font-size: var(--fs-base);
+ line-height: var(--ls-base);
+ }
+ .input:active,
+ .input:focus {
+ border: none;
+ outline: none;
+ box-shadow: none;
+ }
+ }
button.adjustable-tag {
- background: inherit;
+ background-color: inherit;
border: none;
- padding: var(--su-2) 2px;
+ padding: var(--su-2) var(--su-3);
+ font-family: var(--ff-monospace);
font-size: var(--fs-base);
text-align: left;
- color: var(--base-80);
- font-family: var(--ff-monospace);
- .num-sign {
- color: var(--base-40);
- padding-left: 2px;
+ line-height: var(--lh-base);
+ border-radius: var(--radius);
+ letter-spacing: -0.32px;
+ color: var(--tag-name);
+
+ .js-focus-visible &.focus-visible:focus,
+ &:is(label):focus-within {
+ z-index: var(--z-elevate);
}
- .adjustment-icon {
+
+ .js-focus-visible &.focus-visible:focus,
+ &:is(label):focus-within {
+ box-shadow: var(--focus-ring);
+ outline: 0;
+ }
+
+ .num-sign {
+ color: var(--tag-hash);
+ padding-left: 2px;
+ padding-right: 2px;
+ }
+ .remove-icon {
width: 24px;
height: 24px;
float: right;
margin-right: 2px;
- &.subtract {
- background: var(--accent-danger);
- }
- &.plus {
- background: var(--accent-success-darker);
- }
- svg > path {
- fill: var(--base-inverted);
- }
+ background-color: var(--subtract-bg);
+ color: var(--subtract-color);
}
&:hover {
- background: var(--base-10);
- border-radius: 8px;
+ background-color: var(--tag-item-bg-hover);
+ border-radius: 6px;
+ .remove-icon {
+ opacity: 1;
+ background-color: var(--subtract-bg-hover);
+ color: var(--subtract-color-hover);
+ }
}
- &.active {
- background: var(--base-10);
- border-radius: 8px;
- }
- // &.selected {}
}
- .adjustment-reason-container {
+ button.add-tag {
+ background-color: inherit;
+ border: none;
+ padding: var(--su-2) var(--su-3);
+ font-size: var(--fs-base);
+ text-align: left;
+ color: var(--base-80);
+ border-radius: var(--radius);
+ font-family: var(--ff-monospace);
+ .add-icon {
+ width: 24px;
+ height: 24px;
+ float: right;
+ margin-right: 2px;
+ background: var(--plus-bg);
+ color: var(--plus-color);
+ }
+ &:hover {
+ background-color: var(--tag-item-bg-hover);
+ border-radius: 6px;
+ .add-icon {
+ background-color: var(--plus-bg-hover);
+ color: var(--plus-color-hover);
+ }
+ }
+
+ .js-focus-visible &.focus-visible:focus,
+ &:is(label):focus-within {
+ z-index: var(--z-elevate);
+ }
+
+ .js-focus-visible &.focus-visible:focus,
+ &:is(label):focus-within {
+ box-shadow: var(--focus-ring);
+ outline: 0;
+ }
+ }
+ .reason-container {
display: flex;
flex-direction: column;
- padding: var(--su-1);
+ padding: var(--su-1) var(--su-3) var(--su-2) var(--su-3);
textarea {
- margin-bottom: var(--su-2);
+ margin-bottom: var(--su-3);
}
}
}
diff --git a/app/javascript/actionsPanel/__tests__/actionsPanel.test.js b/app/javascript/actionsPanel/__tests__/actionsPanel.test.js
index 1f509a559..dec5c1ede 100644
--- a/app/javascript/actionsPanel/__tests__/actionsPanel.test.js
+++ b/app/javascript/actionsPanel/__tests__/actionsPanel.test.js
@@ -1,12 +1,19 @@
/* eslint-disable no-restricted-globals */
import fetch from 'jest-fetch-mock';
+import { fireEvent } from '@testing-library/preact';
import {
addCloseListener,
initializeHeight,
addReactionButtonListeners,
- addAdjustTagListeners,
+ handleAddTagButtonListeners,
+ handleAddModTagButtonsListeners,
+ handleRemoveTagButtonsListeners,
} from '../actionsPanel';
+global.fetch = fetch;
+global.top.addSnackbarItem = jest.fn();
+global.alert = jest.fn();
+
describe('addCloseListener()', () => {
test('toggles the mod actions panel and its button on click', () => {
document.body.innerHTML = `
@@ -152,72 +159,288 @@ describe('addReactionButtonListeners()', () => {
});
describe('addAdjustTagListeners()', () => {
- describe('when the user is tag moderator of #discuss', () => {
+ describe('when an article has no tags', () => {
beforeEach(() => {
- const tagName = 'discuss';
- document.body.innerHTML = `
- ${tagName}
-
-
- `;
+ fetch.resetMocks();
- addAdjustTagListeners();
+ document.body.innerHTML = `
+
+
+
+
+
+
+
+
+
+
+
+ `;
+
+ handleAddTagButtonListeners();
});
- describe('when an article is tagged with #discuss', () => {
- it('toggles the tag button and the form', () => {
- const tagBtn = document.getElementsByClassName('adjustable-tag')[0];
- tagBtn.click();
- expect(tagBtn.classList).toContain('active');
+
+ function tagResponse() {
+ return JSON.stringify({
+ status: 'Success',
+ result: 'addition',
+ colors: {
+ bg: '#8c5595',
+ text: '#39ad55',
+ },
});
+ }
+
+ it('shows the add tag button', () => {
+ expect(document.getElementById('add-tag-button').classList).not.toContain(
+ 'hidden',
+ );
+
+ expect(document.getElementById('add-tag-container').classList).toContain(
+ 'hidden',
+ );
+ });
+
+ it('click on add tag button shows the container and hides the button', () => {
+ document.getElementById('add-tag-button').click();
+ expect(document.getElementById('add-tag-button').classList).toContain(
+ 'hidden',
+ );
+
+ expect(
+ document.getElementById('add-tag-container').classList,
+ ).not.toContain('hidden');
+ });
+
+ it('by default the add tag submit button is disabled and gets enabled when entered tag', () => {
+ document.getElementById('add-tag-button').click();
+ expect(
+ document.getElementById('tag-add-submit').hasAttribute('disabled'),
+ ).toBeTruthy();
+
+ fireEvent.input(document.getElementById('admin-add-tag'), {
+ target: { value: 'New Tag' },
+ });
+
+ expect(
+ document.getElementById('tag-add-submit').hasAttribute('disabled'),
+ ).toBeFalsy();
+ });
+
+ it('click on cancel button hides the container and shows add-tag button', () => {
+ document.getElementById('add-tag-button').click();
+ document.getElementById('cancel-add-tag-button').click();
+ expect(document.getElementById('add-tag-button').classList).not.toContain(
+ 'hidden',
+ );
+
+ expect(document.getElementById('add-tag-container').classList).toContain(
+ 'hidden',
+ );
+ });
+
+ it('click on add-tag button hides the container and shows add-tag button', async () => {
+ fetch.mockResponseOnce(tagResponse());
+
+ const addTagButton = document.getElementById('add-tag-button');
+
+ addTagButton.click();
+
+ fireEvent.input(document.getElementById('admin-add-tag'), {
+ target: { value: 'New Tag' },
+ });
+ expect(
+ document.getElementById('tag-add-submit').hasAttribute('disabled'),
+ ).toBeFalsy();
+
+ document.getElementById('tag-add-reason').value = 'Adding a new tag';
+ document.getElementById('tag-add-submit').click();
+
+ expect(fetch).toHaveBeenCalledTimes(1);
+ });
+
+ it('shows the adjustment container when admin input is focused', () => {
+ document.getElementById('add-tag-button').click();
+ document.getElementById('admin-add-tag').value = 'pizza';
+ document.getElementById('tag-add-reason').value = 'Adding a new tag';
+ document.getElementById('tag-add-submit').click();
+
+ expect(
+ document.getElementById('add-reason-container').classList,
+ ).not.toContain('hidden');
});
});
- describe('when the user is an admin and the article has room for tags', () => {
- describe('tag adjustment interactions', () => {
- beforeEach(() => {
- const tagName = 'discuss';
- document.body.innerHTML = `
-
-
+ describe('article has 1 tag', () => {
+ const discussTag = 'discuss';
+
+ function removeTagResponse() {
+ return JSON.stringify({
+ status: 'Success',
+ result: 'removal',
+ colors: {
+ bg: '#8c5595',
+ text: '#39ad55',
+ },
+ });
+ }
+
+ beforeEach(() => {
+ fetch.resetMocks();
+
+ document.body.innerHTML = `
+
+
+
+
+
+
+
-
${tagName}
-
-
-
- `;
- addAdjustTagListeners();
- });
- it('shows the adjustment container when admin input is focused', () => {
- document.getElementById('admin-add-tag').focus();
- expect(
- document.getElementById('adjustment-reason-container').classList,
- ).not.toContain('hidden');
- });
- it('triggers a confirmation if the admin add tag input was filled in', () => {
- window.confirm = jest.fn();
- document.getElementById('admin-add-tag').value = 'pizza';
- document.querySelector('.adjustable-tag[data-tag-name="ruby"]').click();
- expect(window.confirm).toHaveBeenCalled();
- });
- it('does not the hide reason container when going from one tag to another tag', () => {
- document.getElementsByClassName('adjustable-tag')[0].click();
- document.querySelector('.adjustable-tag[data-tag-name="ruby"]').click();
- expect(
- document.getElementById('adjustment-reason-container').classList,
- ).not.toContain('hidden');
+
+
+ `;
+
+ handleRemoveTagButtonsListeners();
+ });
+
+ it('remove tag container is hidden', () => {
+ expect(
+ document.getElementById(`remove-tag-container-${discussTag}`).classList,
+ ).toContain('hidden');
+ });
+
+ it('hides remove icon on button click', () => {
+ document.getElementById(`remove-tag-button-${discussTag}`).click();
+
+ expect(
+ document.getElementById(`remove-tag-icon-${discussTag}`).style.display,
+ ).toEqual('none');
+ });
+
+ it('shows tag container on button click', () => {
+ document.getElementById(`remove-tag-button-${discussTag}`).click();
+
+ expect(
+ document.getElementById(`remove-tag-container-${discussTag}`).classList,
+ ).not.toContain('hidden');
+ });
+
+ it('click on cancel button hides the container', () => {
+ document.getElementById(`remove-tag-button-${discussTag}`).click();
+ document.getElementById(`cancel-remove-tag-button-${discussTag}`).click();
+
+ expect(
+ document.getElementById(`remove-tag-container-${discussTag}`).classList,
+ ).toContain('hidden');
+ });
+
+ it('click on remove button should successfully remove the item', async () => {
+ fetch.mockResponseOnce(removeTagResponse());
+
+ document.getElementById(`remove-tag-button-${discussTag}`).click();
+ document.getElementById(`tag-removal-reason-${discussTag}`).value =
+ 'Removing a tag';
+ document.getElementById(`remove-tag-submit-${discussTag}`).click();
+
+ expect(fetch).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ describe('tag moderator role', () => {
+ const discussTag = 'discuss';
+
+ function addModTagResponse() {
+ return JSON.stringify({
+ status: 'Success',
+ result: 'addition',
+ colors: {
+ bg: '#8c5595',
+ text: '#39ad55',
+ },
});
+ }
+
+ beforeEach(() => {
+ fetch.resetMocks();
+
+ document.body.innerHTML = `
+
+
+
+
+
+
+
+
+
+
+ `;
+
+ handleAddModTagButtonsListeners();
+ });
+
+ it('add moderator tag container is hidden', () => {
+ expect(
+ document.getElementById(`add-tag-container-${discussTag}`).classList,
+ ).toContain('hidden');
+ });
+
+ it('hides add icon on button click', () => {
+ document.getElementById(`add-tag-button-${discussTag}`).click();
+
+ expect(
+ document.getElementById(`add-tag-icon-${discussTag}`).style.display,
+ ).toEqual('none');
+ });
+
+ it('shows tag container on button click', () => {
+ document.getElementById(`add-tag-button-${discussTag}`).click();
+
+ expect(
+ document.getElementById(`add-tag-container-${discussTag}`).classList,
+ ).not.toContain('hidden');
+ });
+
+ it('click on cancel button hides the container', () => {
+ document.getElementById(`add-tag-button-${discussTag}`).click();
+ document.getElementById(`cancel-add-tag-button-${discussTag}`).click();
+
+ expect(
+ document.getElementById(`add-tag-container-${discussTag}`).classList,
+ ).toContain('hidden');
+ });
+
+ it('click on add button should successfully add the item', async () => {
+ fetch.mockResponseOnce(addModTagResponse());
+
+ document.getElementById(`add-tag-button-${discussTag}`).click();
+ document.getElementById(`tag-add-reason-${discussTag}`).value =
+ 'Add a tag';
+ document.getElementById(`tag-add-submit-${discussTag}`).click();
+
+ expect(fetch).toHaveBeenCalledTimes(1);
});
});
});
diff --git a/app/javascript/actionsPanel/actionsPanel.js b/app/javascript/actionsPanel/actionsPanel.js
index 9a9950938..a47544723 100644
--- a/app/javascript/actionsPanel/actionsPanel.js
+++ b/app/javascript/actionsPanel/actionsPanel.js
@@ -6,6 +6,11 @@ import { request } from '@utilities/http';
export function addCloseListener() {
const button = document.getElementsByClassName('close-actions-panel')[0];
+ const parentPath = window.parent.location.pathname;
+ if (!parentPath.startsWith('/mod')) {
+ button.classList.remove('hidden');
+ }
+
button.addEventListener('click', () => {
// getting the article show page document because this is called within an iframe
// eslint-disable-next-line no-restricted-globals
@@ -182,10 +187,6 @@ const adminFeatureArticle = async (id, featured) => {
}
};
-function clearAdjustmentReason() {
- document.getElementById('tag-adjustment-reason').value = '';
-}
-
function renderTagOnArticle(tagName, colors) {
const articleTagsContainer =
getArticleContainer().getElementsByClassName('spec__tags')[0];
@@ -208,24 +209,31 @@ function getArticleContainer() {
: window.parent.document.getElementById('main-content');
}
-async function adjustTag(el) {
- const reasonForAdjustment = document.getElementById(
- 'tag-adjustment-reason',
- ).value;
+/**
+ * This function sends an asynchronous request to the server to add or remove
+ * a specific tag from an article.
+ */
+async function adjustTag(el, reasonElement) {
+ const tagName = el.dataset.tagName || el.value;
const body = {
tag_adjustment: {
// TODO: change to tag ID
- tag_name: el.dataset.tagName || el.value,
+ tag_name: tagName,
article_id: el.dataset.articleId,
adjustment_type:
el.dataset.adjustmentType === 'subtract' ? 'removal' : 'addition',
- reason_for_adjustment: reasonForAdjustment,
+ reason_for_adjustment: reasonElement.value,
},
};
try {
- const response = await request('/tag_adjustments', {
+ const response = await fetch('/tag_adjustments', {
method: 'POST',
+ headers: {
+ Accept: 'application/json',
+ 'X-CSRF-Token': window.csrfToken,
+ 'Content-Type': 'application/json',
+ },
body: JSON.stringify(body),
});
@@ -235,15 +243,12 @@ async function adjustTag(el) {
let adjustedTagName;
if (el.tagName === 'BUTTON') {
adjustedTagName = el.dataset.tagName;
- el.remove();
} else {
adjustedTagName = el.value;
// eslint-disable-next-line no-param-reassign, require-atomic-updates
el.value = '';
}
- clearAdjustmentReason();
-
if (outcome.result === 'addition') {
renderTagOnArticle(adjustedTagName, outcome.colors);
} else {
@@ -259,6 +264,7 @@ async function adjustTag(el) {
}.`,
addCloseButton: true,
});
+ window.location.reload();
} else {
// eslint-disable-next-line no-restricted-globals
top.addSnackbarItem({
@@ -272,81 +278,174 @@ async function adjustTag(el) {
}
}
-export function handleAdjustTagBtn(btn) {
- const currentActiveTags = document.querySelectorAll(
- 'button.adjustable-tag.active',
+export function handleAddModTagButton(btn) {
+ const { tagName } = btn.dataset;
+ const addButton = document.getElementById(`add-tag-button-${tagName}`);
+ const addIcon = document.getElementById(`add-tag-icon-${tagName}`);
+ const addTagContainer = document.getElementById(
+ `add-tag-container-${tagName}`,
);
- 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');
- });
+
+ const containerIsVisible = addTagContainer.classList.contains('hidden');
+ if (containerIsVisible) {
+ addIcon.style.display = 'none';
+ addTagContainer.classList.remove('hidden');
+ addButton.classList.add('fw-bold');
+ addButton.classList.remove('fw-normal');
} else {
- btn.classList.toggle('active');
+ addIcon.style.display = 'flex';
+ addTagContainer.classList.add('hidden');
+ addButton.classList.remove('fw-bold');
+ addButton.classList.add('fw-normal');
+ }
+
+ const cancelAddModTagButton = document.getElementById(
+ `cancel-add-tag-button-${tagName}`,
+ );
+ cancelAddModTagButton.addEventListener('click', () => {
+ handleAddModTagButton(btn);
+ });
+
+ const addTagButton = document.getElementById(`tag-add-submit-${tagName}`);
+ if (addTagButton) {
+ addTagButton.addEventListener('click', (e) => {
+ e.preventDefault();
+ const dataSource = document.getElementById(`add-tag-button-${tagName}`);
+ const reasonFoRemoval = document.getElementById(
+ `tag-add-reason-${tagName}`,
+ );
+ adjustTag(dataSource, reasonFoRemoval);
+ });
}
}
-function handleAdminInput() {
- const addTagInput = document.getElementById('admin-add-tag');
+/**
+ * Handles various listeners required to handle remove tag functionality.
+ */
+export function handleRemoveTagButton(btn) {
+ const { tagName } = btn.dataset;
- if (addTagInput) {
- addTagInput.addEventListener('focus', () => {
- const activeTagBtns = Array.from(
- document.querySelectorAll('button.adjustable-tag.active'),
+ const removeButton = document.getElementById(`remove-tag-button-${tagName}`);
+ const removeIcon = document.getElementById(`remove-tag-icon-${tagName}`);
+ const removeTagContainer = document.getElementById(
+ `remove-tag-container-${tagName}`,
+ );
+
+ if (!(removeButton && removeIcon && removeTagContainer)) {
+ return false;
+ }
+
+ const containerIsVisible = removeTagContainer?.classList.contains('hidden');
+ if (containerIsVisible) {
+ removeIcon.style.display = 'none';
+ removeTagContainer.classList.remove('hidden');
+ removeButton.classList.add('fw-bold');
+ removeButton.classList.remove('fw-normal');
+ } else {
+ removeIcon.style.display = 'flex';
+ removeTagContainer.classList.add('hidden');
+ removeButton.classList.remove('fw-bold');
+ removeButton.classList.add('fw-normal');
+ }
+
+ const cancelRemoveTagButton = document.getElementById(
+ `cancel-remove-tag-button-${tagName}`,
+ );
+ cancelRemoveTagButton.addEventListener('click', () => {
+ handleRemoveTagButton(btn);
+ });
+
+ const removeTagButton = document.getElementById(
+ `remove-tag-submit-${tagName}`,
+ );
+ if (removeTagButton) {
+ removeTagButton.addEventListener('click', (e) => {
+ e.preventDefault();
+
+ const dataSource = document.getElementById(
+ `remove-tag-button-${tagName}`,
);
- activeTagBtns.forEach((btn) => {
- btn.classList.remove('active');
- });
+ const reasonFoRemoval = document.getElementById(
+ `tag-removal-reason-${tagName}`,
+ );
+ adjustTag(dataSource, reasonFoRemoval);
});
- addTagInput.addEventListener('focusout', () => {
- if (addTagInput.value === '') {
+ }
+}
+
+/**
+ * Handles various listeners required to handle add tag functionality.
+ */
+export function handleAddTagButtonListeners() {
+ const inputTag = document.getElementById('admin-add-tag');
+ const submitButton = document.getElementById('tag-add-submit');
+
+ if (inputTag) {
+ inputTag.addEventListener('input', () => {
+ if (inputTag.value.trim().length > 0) {
+ submitButton.removeAttribute('disabled');
+ } else {
+ submitButton.setAttribute('disabled', 'disabled');
}
});
}
+
+ const addTagButton = document.getElementById('add-tag-button');
+
+ if (addTagButton) {
+ addTagButton.addEventListener('click', () => {
+ const addTagContainer = document.getElementById('add-tag-container');
+ addTagContainer.classList.remove('hidden');
+ addTagButton.classList.add('hidden');
+ });
+
+ const cancelAddTagButton = document.getElementById('cancel-add-tag-button');
+
+ if (cancelAddTagButton) {
+ cancelAddTagButton.addEventListener('click', () => {
+ const addTagContainer = document.getElementById('add-tag-container');
+ addTagContainer.classList.add('hidden');
+ addTagButton.classList.remove('hidden');
+ });
+ }
+
+ const addTagSubmitButton = document.getElementById('tag-add-submit');
+ if (addTagSubmitButton) {
+ addTagSubmitButton.addEventListener('click', (e) => {
+ e.preventDefault();
+
+ const dataSource = document.getElementById('admin-add-tag');
+ const reasonFoAddition = document.getElementById('tag-add-reason');
+ adjustTag(dataSource, reasonFoAddition);
+ });
+ }
+ }
}
-export function addAdjustTagListeners() {
- Array.from(document.getElementsByClassName('adjustable-tag')).forEach(
+export function handleAddModTagButtonsListeners() {
+ Array.from(document.getElementsByClassName('adjustable-tag add-tag')).forEach(
(btn) => {
btn.addEventListener('click', () => {
- handleAdjustTagBtn(btn);
+ handleAddModTagButton(btn);
});
},
);
+}
- const form = document.getElementById('tag-adjust-submit')?.form;
- if (form) {
- form.addEventListener('submit', (e) => {
- e.preventDefault();
-
- const dataSource =
- document.querySelector('button.adjustable-tag.active') ??
- document.getElementById('admin-add-tag');
-
- adjustTag(dataSource);
- });
-
- handleAdminInput();
- }
+export function handleRemoveTagButtonsListeners() {
+ Array.from(document.getElementsByClassName('adjustable-tag')).forEach(
+ (btn) => {
+ btn.addEventListener('click', () => {
+ handleRemoveTagButton(btn);
+ });
+ },
+ );
}
export function addModActionsListeners() {
- addAdjustTagListeners();
+ handleAddTagButtonListeners();
+ handleAddModTagButtonsListeners();
+ handleRemoveTagButtonsListeners();
Array.from(document.getElementsByClassName('other-things-btn')).forEach(
(btn) => {
btn.addEventListener('click', () => {
diff --git a/app/policies/article_policy.rb b/app/policies/article_policy.rb
index 4e813e108..a80d2fce2 100644
--- a/app/policies/article_policy.rb
+++ b/app/policies/article_policy.rb
@@ -170,21 +170,7 @@ class ArticlePolicy < ApplicationPolicy
def tag_moderator_eligible?
tag_ids_moderated_by_user = Tag.with_role(:tag_moderator, @user).ids
- return false unless tag_ids_moderated_by_user.size.positive?
-
- adjustments = TagAdjustment.where(article_id: @record.id)
- has_room_for_tags = @record.tag_list.size < MAX_TAG_LIST_SIZE
- # ensures that mods cannot adjust an already-adjusted tag
- # "zero?" because intersection has just one integer (0 or 1)
- has_no_relevant_adjustments = adjustments.pluck(:tag_id).intersection(tag_ids_moderated_by_user).empty?
-
- # tag_mod can add their moderated tags
- return true if has_room_for_tags && has_no_relevant_adjustments
-
- authorized_to_adjust = @record.tags.ids.intersection(tag_ids_moderated_by_user).size.positive?
-
- # tag_mod can remove their moderated tags
- !has_room_for_tags && has_no_relevant_adjustments && authorized_to_adjust
+ tag_ids_moderated_by_user.size.positive?
end
def destroy?
diff --git a/app/views/moderations/actions_panel.html.erb b/app/views/moderations/actions_panel.html.erb
index 65ca87b47..c80f2d5ea 100644
--- a/app/views/moderations/actions_panel.html.erb
+++ b/app/views/moderations/actions_panel.html.erb
@@ -93,7 +93,7 @@
<%# Checking if the user should see the "Adjust tags" dropdown %>
<% if policy(@moderatable).allow_tag_adjustment? %>
-