docbrown/app/javascript/packs/subscribeButton.js
Lawrence 0c93bde6c2
Subscribe to Comment Functionality (#19555)
* Move save over and add button labels

* comment notification design and functionality

* controller error handling

* add ternery operator for background class

* minor cleanup

* add proper styling toggles

* tests and mobile spec

* add for all buttons

* add article as a param

* comment is not required on article preview

* update policy spec

* add test for coverage

* add proper button titles

* use the last user comment as comment id

* add better error handling and remove multiple fetch calls in js

* copy change to threads

* update copy, fix multiple fetch calls, add observer for loading more notifications

* update spec

* add rspecs

* more test specs

* fix test specs

* subscribe button text tests

* add export

* optimistically update ui function tests

* button initialize test

* check for comment ancestry

* add feature flag block

* some cleanup

* update logic and specs

* oops

* rubocop issues

* change config names

* change config names

* fix spec

* add one more conditional

* check for comments

* service-object the toggler

* separate routes

* continual refactoring

* refine spec tests

* clean up policy

* Adjust js and view files to match be params

* some service object refactoring

* separate endpoints on client-side

* broken spec

* service specs

* policy spec fixes

* remove action param

* remove action param from spec

* minor updates

* trigger build

* update to post

* Services don't need permitted params

* Notifications continue to be a mess, decorator specs to follow

* Not toggling now

* This doesn't really belong here

* Fix tests after moving

* Streamline front-end

* There could be errors?

* polymorphic_name is a class thing

* ☠️ json_data in notification views

* Use feature_flag helper to get current_user

* Cannot derive current subscription status from notification, only from subscription

* Default is subscribe to all article comments

* Sync up matching unsubscribe, make subscribe idempotent-ish

* Temporarily comment-out button jest tests to see if I can unstick CI

* Stop trying to detect potential subscribe-to-comment

* Stop trying to detect potential subscribe-to-comment

* Minor spec refactoring

* Add specs for article notification decoration

* NotificationDecorator specs are rather long

* Rubocop likes it?

* Minor spec refactoring

* Doesn't need to stay pending

* Add spec for subscription finder

* Expand Subscribe service specs

* YAGNI, but I don't like hard-coded config

* Refactor Unsubscribe service specs

* nobody expects the infinite scroll

* This *might* work, except for feature flag

* Ancestry can be quite deep actually

* Rubocop

* Update subscribe button for subscribe-to-thread

* Jest tests are just tests

* hasAttribute != getAttribute

* Test knows **nothing** about window size

* Tests have been lying about mobileLabel this whole time

* Make payload/endpoint testable

* Is Jest actually happy???

* Still cleaning up jest/eslint

* Temporarily comment-out cypress tests pending feature flag

* Thanks, Rubocop, what would we do without you

* Preserve round-trip for 'top-level' and 'only-author' subscriptions

* Update specs

* Move save button icon

* Update test with better config param

* Try feature flag specific to cypress env

* Tell cypress to wait for (un)subscribing to work

---------

Co-authored-by: Lawrence S <lawrence@forem.com>
Co-authored-by: Joshua Wehner <joshua@forem.com>
2023-07-10 17:11:46 +02:00

154 lines
4.3 KiB
JavaScript

// /* global showModalAfterError*/
export function updateSubscribeButtonText(
button,
overrideSubscribed,
window_size,
) {
let label = '';
let mobileLabel = '';
if (typeof window_size == 'undefined') {
window_size = window.innerWidth;
}
let noun = 'comments';
const { subscription_id, subscription_config, comment_id } = button.dataset;
let subscriptionIsActive = subscription_id != '';
if (typeof overrideSubscribed != 'undefined') {
subscriptionIsActive = overrideSubscribed == 'subscribe';
}
const pressed = subscriptionIsActive;
const verb = subscriptionIsActive ? 'Subscribed' : 'Subscribe';
// comment_id should only be present if there's a subscription, so a button
// that initially renders as 'Subscribed-to-thread' can be a toggle until refreshed
if (comment_id && comment_id != '') {
noun = 'thread';
}
// Find the <span> element within the button
const spanElement = button.querySelector('span');
switch (subscription_config) {
case 'top_level_comments':
label = `${verb} to top-level comments`;
mobileLabel = `Top-level ${noun}`;
break;
case 'only_author_comments':
label = `${verb} to author comments`;
mobileLabel = `Author ${noun}`;
break;
default:
label = `${verb} to ${noun}`;
mobileLabel = `${noun}`.charAt(0).toUpperCase() + noun.slice(1);
}
button.setAttribute('aria-label', label);
spanElement.innerText = window_size <= 760 ? mobileLabel : label;
button.setAttribute('aria-pressed', pressed);
}
export function optimisticallyUpdateButtonUI(button, modeChange) {
if (typeof modeChange == 'undefined') {
modeChange = button.dataset.subscription_id ? 'unsubscribe' : 'subscribe';
}
if (modeChange == 'unsubscribe') {
button.classList.remove('comment-subscribed');
updateSubscribeButtonText(button, 'unsubscribe');
} else {
button.classList.add('comment-subscribed');
updateSubscribeButtonText(button, 'subscribe');
}
return;
}
export function determinePayloadAndEndpoint(button) {
let payload;
let endpoint;
if (button.dataset.subscription_id != '') {
payload = {
subscription_id: button.dataset.subscription_id,
};
endpoint = 'comment-unsubscribe';
} else if (
button.dataset.subscribed_to &&
button.dataset.subscribed_to == 'comment'
) {
payload = {
comment_id: button.dataset.comment_id,
};
endpoint = 'comment-subscribe';
} else {
payload = {
article_id: button.dataset.article_id,
subscription_config: button.dataset.subscription_config,
};
endpoint = 'comment-subscribe';
}
return {
payload,
endpoint,
};
}
async function handleSubscribeButtonClick({ target }) {
optimisticallyUpdateButtonUI(target);
const { payload, endpoint } = determinePayloadAndEndpoint(target);
const requestJson = JSON.stringify(payload);
getCsrfToken()
.then(await sendFetch(endpoint, requestJson))
.then(async (response) => {
if (response.status === 200) {
const res = await response.json();
if (res.destroyed) {
const matchingButtons = document.querySelectorAll(
`button[data-subscription_id='${target.dataset.subscription_id}']`,
);
for (let i = 0; i < matchingButtons.length; i++) {
const button = matchingButtons[i];
button.dataset.subscription_id = '';
if (button != target) {
optimisticallyUpdateButtonUI(button, 'unsubscribe');
}
}
} else if (res.subscription) {
target.dataset.subscription_id = res.subscription.id;
} else {
throw `Problem (un)subscribing: ${JSON.stringify(res)}`;
}
}
});
}
export function initializeSubscribeButton() {
const buttons = document.querySelectorAll('.subscribe-button');
if (buttons.length === 0) {
return;
}
Array.from(buttons, (button) => {
if (button.wasInitialized) {
return;
}
button.removeEventListener('click', handleSubscribeButtonClick); // Remove previous event listener
button.addEventListener('click', handleSubscribeButtonClick);
button.wasInitialized = true;
updateSubscribeButtonText(button);
});
}
// Some subscribe buttons are added to the DOM dynamically.
// They will need to call this — see initializeNotifications > initPagination
initializeSubscribeButton();