Compare commits
2 Commits
99180035e3
...
061df73df8
Author | SHA1 | Date |
---|---|---|
Bauke | 061df73df8 | |
Bauke | c818362773 |
|
@ -9,5 +9,6 @@ export * from "./jump-to-new-comment.js";
|
|||
export * from "./markdown-toolbar.js";
|
||||
export * from "./themed-logo.js";
|
||||
export * from "./topic-info-ignore.js";
|
||||
export * from "./unignore-all-button.js";
|
||||
export * from "./user-labels.js";
|
||||
export * from "./username-colors.js";
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
import {Component, render} from "preact";
|
||||
import {log, querySelectorAll, sleep} from "../../utilities/exports.js";
|
||||
|
||||
export function runUnignoreAllButtonFeature(): void {
|
||||
if (addUnignoreAllButton()) {
|
||||
log("Added Unignore All button.");
|
||||
}
|
||||
}
|
||||
|
||||
function addUnignoreAllButton(): boolean {
|
||||
// Only add the button when we're on the ignore list page and the ignore list
|
||||
// isn't empty.
|
||||
if (
|
||||
window.location.pathname !== "/ignored_topics" &&
|
||||
document.querySelector("main > .empty") === null
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const heading = document.querySelector(".heading-main") ?? undefined;
|
||||
if (heading === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const button = document.createDocumentFragment();
|
||||
render(<UnignoreAllButton />, button);
|
||||
heading.after(button);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
type Props = Record<string, unknown>;
|
||||
|
||||
type State = {
|
||||
isRunning: boolean;
|
||||
remaining: number;
|
||||
total: number;
|
||||
wasCanceled: boolean;
|
||||
};
|
||||
|
||||
class UnignoreAllButton extends Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isRunning: false,
|
||||
remaining: 0,
|
||||
total: 0,
|
||||
wasCanceled: false,
|
||||
};
|
||||
}
|
||||
|
||||
click = () => {
|
||||
if (this.state.isRunning) {
|
||||
// If we're already running, cancel the run.
|
||||
this.setState({isRunning: false, wasCanceled: true});
|
||||
window.setTimeout(() => {
|
||||
// And after 5 seconds, return back to the default state.
|
||||
this.setState({wasCanceled: false});
|
||||
}, 5000);
|
||||
return;
|
||||
}
|
||||
|
||||
// Select the ignore buttons that have a HTTP DELETE method set. Since we're
|
||||
// going to have Intercooler do all the work for us, we don't want to
|
||||
// accidentally also select ignore buttons that would ignore the topics
|
||||
// again.
|
||||
const unignoreButtons = querySelectorAll<HTMLButtonElement>(
|
||||
'button[name="topic-actions-ignore"][data-ic-delete-from]',
|
||||
);
|
||||
this.setState({
|
||||
isRunning: true,
|
||||
remaining: unignoreButtons.length,
|
||||
total: unignoreButtons.length,
|
||||
});
|
||||
void this.unignoreAll(unignoreButtons);
|
||||
};
|
||||
|
||||
unignoreAll = async (buttons: HTMLButtonElement[]) => {
|
||||
let remaining = buttons.length;
|
||||
for (const ignoreButton of buttons) {
|
||||
// Stop the loop if the user canceled it.
|
||||
if (!this.state.isRunning && this.state.wasCanceled) {
|
||||
return;
|
||||
}
|
||||
|
||||
ignoreButton.click();
|
||||
remaining--;
|
||||
this.setState({remaining});
|
||||
await sleep(250);
|
||||
}
|
||||
|
||||
this.setState({isRunning: false});
|
||||
};
|
||||
|
||||
render() {
|
||||
const {isRunning, remaining, total, wasCanceled} = this.state;
|
||||
let text = "Unignore All";
|
||||
|
||||
if (isRunning) {
|
||||
// When we're running show how many topics are remaining.
|
||||
text = `Unignoring topics, ${remaining} out of ${total} remaining`;
|
||||
} else if (wasCanceled) {
|
||||
// If the user canceled, say that.
|
||||
text = "Canceled unignoring all topics";
|
||||
}
|
||||
|
||||
return (
|
||||
<button class="btn" onClick={this.click}>
|
||||
{text}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -24,6 +24,7 @@ import {
|
|||
runMarkdownToolbarFeature,
|
||||
runThemedLogoFeature,
|
||||
runTopicInfoIgnore,
|
||||
runUnignoreAllButtonFeature,
|
||||
runUsernameColorsFeature,
|
||||
} from "./features/exports.js";
|
||||
|
||||
|
@ -176,6 +177,13 @@ async function initialize() {
|
|||
runTopicInfoIgnore();
|
||||
}
|
||||
|
||||
if (
|
||||
miscEnabled.value.has(MiscellaneousFeature.UnignoreAllButton) &&
|
||||
isLoggedIn
|
||||
) {
|
||||
runUnignoreAllButtonFeature();
|
||||
}
|
||||
|
||||
// Insert a placeholder at the end of the body first, then render the rest
|
||||
// and use that as the replacement element. Otherwise render() would put it
|
||||
// at the beginning of the body which causes a bunch of different issues.
|
||||
|
|
|
@ -42,6 +42,14 @@ function FeatureDescription({
|
|||
);
|
||||
}
|
||||
|
||||
if (feature === MiscellaneousFeature.UnignoreAllButton) {
|
||||
return (
|
||||
<p class="description">
|
||||
Add an "Unignore All" button to your list of ignored topics.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
return <></>;
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ export enum MiscellaneousFeature {
|
|||
CommentAnchorFix = "comment-anchor-fix",
|
||||
GroupListSubscribeButtons = "group-list-subscribe-buttons",
|
||||
TopicInfoIgnore = "topic-info-ignore",
|
||||
UnignoreAllButton = "unignore-all-button",
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -7,6 +7,7 @@ export * from "./http.js";
|
|||
export * from "./logging.js";
|
||||
export * from "./query-selectors.js";
|
||||
export * from "./report-a-bug.js";
|
||||
export * from "./sleep.js";
|
||||
export * from "./text.js";
|
||||
export * from "./user.js";
|
||||
export * from "./validators.js";
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
/**
|
||||
* Promisified {@linkcode window.setTimeout}.
|
||||
* @param timeout The amount of time in milliseconds to sleep for.
|
||||
*/
|
||||
export async function sleep(timeout: number): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
window.setTimeout(resolve, timeout);
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue