diff --git a/source/content-scripts/features/exports.ts b/source/content-scripts/features/exports.ts index c956c9e..1bb0126 100644 --- a/source/content-scripts/features/exports.ts +++ b/source/content-scripts/features/exports.ts @@ -2,6 +2,7 @@ export * from "./anonymize-usernames.js"; export * from "./autocomplete.js"; export * from "./back-to-top.js"; export * from "./comment-anchor-fix.js"; +export * from "./group-list-subscribe-button.js"; export * from "./hide-topics.js"; export * from "./hide-votes.js"; export * from "./jump-to-new-comment.js"; diff --git a/source/content-scripts/features/group-list-subscribe-button.tsx b/source/content-scripts/features/group-list-subscribe-button.tsx new file mode 100644 index 0000000..ee5c9ad --- /dev/null +++ b/source/content-scripts/features/group-list-subscribe-button.tsx @@ -0,0 +1,127 @@ +import {Component, render} from "preact"; +import {log, pluralize, querySelectorAll} from "../../utilities/exports.js"; + +export function runGroupListSubscribeButtonFeature(): void { + const count = addSubscribeButtonsToGroupList(); + if (count > 0) { + const pluralized = `${count} ${pluralize(count, "subscribe button")}`; + log(`Added ${pluralized} to the group list`); + } +} + +function addSubscribeButtonsToGroupList(): number { + if (window.location.pathname !== "/groups") { + return 0; + } + + const csrfToken = document.querySelector( + 'meta[name="csrftoken"]', + )?.content; + if (csrfToken === undefined) { + log("No CSRF token found", true); + return 0; + } + + let count = 0; + for (const listItem of querySelectorAll( + ".group-list li:not(.trx-group-list-subscribe-button)", + )) { + const group = listItem.querySelector(".link-group")?.textContent?.slice(1); + if (group === undefined) { + log(`Missing expected group in list item`, true); + log(listItem, true); + continue; + } + + const button = document.createDocumentFragment(); + render( + , + button, + ); + + const activity = + listItem.querySelector(".group-list-activity") ?? undefined; + if (activity === undefined) { + listItem.append(button); + } else { + activity.before(button); + } + + listItem.classList.add("trx-group-list-subscribe-button"); + count++; + } + + return count; +} + +type Props = { + csrfToken: string; + listItem: HTMLLIElement; + group: string; +}; + +type State = { + isSubscribed: boolean; +}; + +class SubscribeButton extends Component { + constructor(props: Props) { + super(props); + + this.state = { + isSubscribed: props.listItem.classList.contains( + "group-list-item-subscribed", + ), + }; + } + + clickHandler = async () => { + const {csrfToken, group} = this.props; + const {isSubscribed} = this.state; + + const response = await window.fetch( + `https://tildes.net/api/web/group/${group}/subscribe`, + { + headers: { + "X-CSRF-Token": csrfToken, + "X-IC-Request": "true", + }, + method: isSubscribed ? "DELETE" : "PUT", + referrer: "https://tildes.net", + }, + ); + + if (response.status !== 200) { + log(`Unexpected status code: ${response.status}`, true); + return; + } + + this.setState({isSubscribed: !isSubscribed}); + }; + + render() { + const {listItem} = this.props; + const {isSubscribed} = this.state; + + if (isSubscribed) { + listItem.classList.add("group-list-item-subscribed"); + listItem.classList.remove("group-list-item-not-subscribed"); + } else { + listItem.classList.add("group-list-item-not-subscribed"); + listItem.classList.remove("group-list-item-subscribed"); + } + + return ( + + ); + } +} diff --git a/source/content-scripts/setup.tsx b/source/content-scripts/setup.tsx index 1e6d886..c933f2c 100644 --- a/source/content-scripts/setup.tsx +++ b/source/content-scripts/setup.tsx @@ -13,6 +13,7 @@ import { UserLabelsFeature, runAnonymizeUsernamesFeature, runCommentAnchorFixFeature, + runGroupListSubscribeButtonFeature, runHideTopicsFeature, runHideVotesFeature, runMarkdownToolbarFeature, @@ -25,6 +26,7 @@ async function initialize() { const start = window.performance.now(); initializeGlobals(); const enabledFeatures = await fromStorage(Data.EnabledFeatures); + const miscEnabled = await fromStorage(Data.MiscellaneousEnabledFeatures); window.TildesReExtended.debug = enabledFeatures.value.has(Feature.Debug); // Any features that will use the knownGroups data should be added to this @@ -146,11 +148,14 @@ async function initialize() { ); } - const miscEnabled = await fromStorage(Data.MiscellaneousEnabledFeatures); if (miscEnabled.value.has(MiscellaneousFeature.CommentAnchorFix)) { runCommentAnchorFixFeature(); } + if (miscEnabled.value.has(MiscellaneousFeature.GroupListSubscribeButtons)) { + runGroupListSubscribeButtonFeature(); + } + if (miscEnabled.value.has(MiscellaneousFeature.TopicInfoIgnore)) { runTopicInfoIgnore(); } diff --git a/source/options/components/miscellaneous.tsx b/source/options/components/miscellaneous.tsx index 82734b5..22bbd89 100644 --- a/source/options/components/miscellaneous.tsx +++ b/source/options/components/miscellaneous.tsx @@ -25,6 +25,14 @@ function FeatureDescription({ ); } + if (feature === MiscellaneousFeature.GroupListSubscribeButtons) { + return ( +

+ Add Subscribe and Unsubscribe buttons to the group list. +

+ ); + } + if (feature === MiscellaneousFeature.TopicInfoIgnore) { return (

diff --git a/source/scss/content-scripts.scss b/source/scss/content-scripts.scss index 254aace..c9015ca 100644 --- a/source/scss/content-scripts.scss +++ b/source/scss/content-scripts.scss @@ -1,6 +1,7 @@ // Scripts @import "scripts/autocomplete"; @import "scripts/back-to-top"; +@import "scripts/group-list-subscribe-button"; @import "scripts/hide-topics"; @import "scripts/jump-to-new-comment"; @import "scripts/markdown-toolbar"; diff --git a/source/scss/scripts/_group-list-subscribe-button.scss b/source/scss/scripts/_group-list-subscribe-button.scss new file mode 100644 index 0000000..c36ac22 --- /dev/null +++ b/source/scss/scripts/_group-list-subscribe-button.scss @@ -0,0 +1,12 @@ +.group-list { + .trx-group-list-subscribe-button { + // Remove the text-indent set by Tildes so it the button is left-aligned + // properly. + text-indent: unset; + + button { + // Add some space between the button and the activity text. + margin-right: 0.5rem; + } + } +} diff --git a/source/storage/enums.ts b/source/storage/enums.ts index 4249a00..1f541fc 100644 --- a/source/storage/enums.ts +++ b/source/storage/enums.ts @@ -21,6 +21,7 @@ export enum Feature { */ export enum MiscellaneousFeature { CommentAnchorFix = "comment-anchor-fix", + GroupListSubscribeButtons = "group-list-subscribe-buttons", TopicInfoIgnore = "topic-info-ignore", }