Merge pull request #441 from StroepWafel/main

fix: TOTM
This commit is contained in:
Aden Lindsay
2026-06-01 18:37:59 +09:30
committed by GitHub
7 changed files with 273 additions and 53 deletions
@@ -45,10 +45,10 @@ export async function fetchThemeOfTheMonth(): Promise<ThemeOfTheMonthEntry | nul
}
}
/** True when we have a new monthly entry the user hasn't dismissed yet. */
/** True when the current month's entry should appear in the startup queue. */
export function shouldShowThemeOfTheMonth(entry: ThemeOfTheMonthEntry | null): boolean {
if (!entry || settingsState.themeOfTheMonthDisabled) return false;
return settingsState.themeOfTheMonthLastSeenId !== entry.id;
return settingsState.themeOfTheMonthDismissedMonth !== entry.month;
}
function escapeHTML(str: string): string {
@@ -108,18 +108,9 @@ async function resolvePopupHeroImageUrl(entry: ThemeOfTheMonthEntry): Promise<st
return fallback || null;
}
function closeThemeOfTheMonthCard(
card: HTMLElement,
onDismissed?: () => void,
markSeen = true,
) {
function closeThemeOfTheMonthCard(card: HTMLElement, onDismissed?: () => void) {
if (card.classList.contains("themeOfTheMonthCardClosing")) return;
if (markSeen) {
const entryId = card.dataset.entryId;
if (entryId) settingsState.themeOfTheMonthLastSeenId = entryId;
}
card.classList.add("themeOfTheMonthCardClosing");
window.setTimeout(() => {
card.remove();
@@ -143,7 +134,6 @@ export async function OpenThemeOfTheMonthPopup(
const card = stringToHTML(/* html */ `
<aside id="theme-of-the-month-card" class="themeOfTheMonthCard" role="dialog" aria-label="Theme of the Month">
<button type="button" class="themeOfTheMonthCardClose" aria-label="Close Theme of the Month">×</button>
${
heroUrl
? `<img class="themeOfTheMonthCardImage" src="${escapeHTML(heroUrl)}" alt="${escapeHTML(entry.title)}" />`
@@ -154,39 +144,74 @@ export async function OpenThemeOfTheMonthPopup(
<h2>${escapeHTML(entry.title)}</h2>
<p class="themeOfTheMonthCardDescription">${description}</p>
<div class="themeOfTheMonthCardActions">
${
linkedThemeId
? `<button type="button" class="themeOfTheMonthCardPrimary">Open Store</button>`
: ""
}
<button type="button" class="themeOfTheMonthCardSecondary">Don't show again</button>
<div class="themeOfTheMonthCardActionsStart">
${
linkedThemeId
? `<button type="button" class="themeOfTheMonthCardPrimary">Open Store</button>`
: ""
}
</div>
<div class="themeOfTheMonthCardActionsEnd">
<button type="button" class="themeOfTheMonthCardSecondary">Close</button>
<button type="button" class="themeOfTheMonthCardDontShow">Don't show again</button>
</div>
</div>
</div>
<div class="themeOfTheMonthCardConfirm" hidden>
<div class="themeOfTheMonthCardConfirmInner">
<h3>Don't show again?</h3>
<p>Theme of the Month popups will be turned off. You can turn them back on in BetterSEQTA+ settings.</p>
<div class="themeOfTheMonthCardConfirmActions">
<button type="button" class="themeOfTheMonthCardConfirmCancel">Cancel</button>
<button type="button" class="themeOfTheMonthCardConfirmAccept">Don't show again</button>
</div>
</div>
</div>
</aside>
`).firstChild as HTMLElement;
card.dataset.entryId = entry.id;
const autoCloseTimeout = window.setTimeout(() => {
closeThemeOfTheMonthCard(card, onDismissed);
}, 12000);
}, 30_000);
const dismiss = (markSeen = true) => {
const dismiss = () => {
window.clearTimeout(autoCloseTimeout);
closeThemeOfTheMonthCard(card, onDismissed, markSeen);
closeThemeOfTheMonthCard(card, onDismissed);
};
card.addEventListener("mouseenter", () => window.clearTimeout(autoCloseTimeout), { once: true });
card.querySelector(".themeOfTheMonthCardClose")?.addEventListener("click", () => {
const confirmEl = card.querySelector<HTMLElement>(".themeOfTheMonthCardConfirm");
card.querySelector(".themeOfTheMonthCardSecondary")?.addEventListener("click", () => {
settingsState.themeOfTheMonthDismissedMonth = entry.month;
dismiss();
});
card.querySelector(".themeOfTheMonthCardPrimary")?.addEventListener("click", () => {
settingsState.themeOfTheMonthDismissedMonth = entry.month;
dismiss();
openThemeStoreWithHighlight(linkedThemeId!);
});
card.querySelector(".themeOfTheMonthCardSecondary")?.addEventListener("click", () => {
const openDontShowConfirm = () => {
window.clearTimeout(autoCloseTimeout);
if (!confirmEl) return;
confirmEl.hidden = false;
requestAnimationFrame(() => confirmEl.classList.add("themeOfTheMonthCardConfirmVisible"));
};
card.querySelector(".themeOfTheMonthCardDontShow")?.addEventListener("click", openDontShowConfirm);
card.querySelector(".themeOfTheMonthCardConfirmCancel")?.addEventListener("click", () => {
if (!confirmEl) return;
confirmEl.classList.remove("themeOfTheMonthCardConfirmVisible");
window.setTimeout(() => {
confirmEl.hidden = true;
}, 160);
});
card.querySelector(".themeOfTheMonthCardConfirmAccept")?.addEventListener("click", () => {
settingsState.themeOfTheMonthDisabled = true;
dismiss();
});
@@ -196,7 +221,7 @@ export async function OpenThemeOfTheMonthPopup(
/**
* Dev helper: fetch the current month's entry and show the popup immediately,
* even if the user has already dismissed it this month.
* even if the user dismissed it for this calendar month.
*/
export async function showThemeOfTheMonthPopupNow(): Promise<void> {
const entry = await fetchThemeOfTheMonth();
@@ -207,7 +232,7 @@ export async function showThemeOfTheMonthPopupNow(): Promise<void> {
return;
}
settingsState.themeOfTheMonthLastSeenId = undefined;
settingsState.themeOfTheMonthDismissedMonth = undefined;
if (document.getElementById("whatsnewbk")) {
await closePopup();
+1 -1
View File
@@ -15,7 +15,7 @@ type QueueStep = (goNext: () => void) => void;
/**
* Runs startup modals in order: What's New (if the extension just updated),
* Theme of the Month (when a new monthly entry hasn't been seen), then shows
* Theme of the Month (when the user hasn't dismissed this calendar month), then shows
* the SEQTA Engage toast (once, non-blocking).
*/
export async function runStartupPopupQueue() {