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 "./markdown-toolbar.js";
|
||||||
export * from "./themed-logo.js";
|
export * from "./themed-logo.js";
|
||||||
export * from "./topic-info-ignore.js";
|
export * from "./topic-info-ignore.js";
|
||||||
|
export * from "./unignore-all-button.js";
|
||||||
export * from "./user-labels.js";
|
export * from "./user-labels.js";
|
||||||
export * from "./username-colors.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,
|
runMarkdownToolbarFeature,
|
||||||
runThemedLogoFeature,
|
runThemedLogoFeature,
|
||||||
runTopicInfoIgnore,
|
runTopicInfoIgnore,
|
||||||
|
runUnignoreAllButtonFeature,
|
||||||
runUsernameColorsFeature,
|
runUsernameColorsFeature,
|
||||||
} from "./features/exports.js";
|
} from "./features/exports.js";
|
||||||
|
|
||||||
|
@ -176,6 +177,13 @@ async function initialize() {
|
||||||
runTopicInfoIgnore();
|
runTopicInfoIgnore();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
miscEnabled.value.has(MiscellaneousFeature.UnignoreAllButton) &&
|
||||||
|
isLoggedIn
|
||||||
|
) {
|
||||||
|
runUnignoreAllButtonFeature();
|
||||||
|
}
|
||||||
|
|
||||||
// Insert a placeholder at the end of the body first, then render the rest
|
// 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
|
// 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.
|
// 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 <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ export enum MiscellaneousFeature {
|
||||||
CommentAnchorFix = "comment-anchor-fix",
|
CommentAnchorFix = "comment-anchor-fix",
|
||||||
GroupListSubscribeButtons = "group-list-subscribe-buttons",
|
GroupListSubscribeButtons = "group-list-subscribe-buttons",
|
||||||
TopicInfoIgnore = "topic-info-ignore",
|
TopicInfoIgnore = "topic-info-ignore",
|
||||||
|
UnignoreAllButton = "unignore-all-button",
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -7,6 +7,7 @@ export * from "./http.js";
|
||||||
export * from "./logging.js";
|
export * from "./logging.js";
|
||||||
export * from "./query-selectors.js";
|
export * from "./query-selectors.js";
|
||||||
export * from "./report-a-bug.js";
|
export * from "./report-a-bug.js";
|
||||||
|
export * from "./sleep.js";
|
||||||
export * from "./text.js";
|
export * from "./text.js";
|
||||||
export * from "./user.js";
|
export * from "./user.js";
|
||||||
export * from "./validators.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