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 = ` + + + `; + + 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? %> -