mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-06-05 19:24:39 +00:00
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "betterseqtaplus",
|
||||
"version": "3.6.5",
|
||||
"version": "3.6.6",
|
||||
"type": "module",
|
||||
"description": "Enhance SEQTA Learn's usability and aesthetics! A fork of BetterSEQTA to continue development and add heaps more features!",
|
||||
"browserslist": "> 0.5%, last 2 versions, not dead",
|
||||
|
||||
@@ -551,6 +551,76 @@ async function migrateGlobalSearchDefaultsFor365Upgrade(
|
||||
}
|
||||
}
|
||||
|
||||
/** One-time reset for 3.6.6: re-enable Theme of the Month for existing users. */
|
||||
const THEME_OF_THE_MONTH_RESET_VERSION = "3.6.6";
|
||||
|
||||
async function resetThemeOfTheMonthDisabledFor366Upgrade(
|
||||
previousVersion: string,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const currRaw = browser.runtime.getManifest().version;
|
||||
const prev = semver.coerce(previousVersion);
|
||||
const curr = semver.coerce(currRaw);
|
||||
if (
|
||||
prev == null ||
|
||||
curr == null ||
|
||||
semver.lt(curr, THEME_OF_THE_MONTH_RESET_VERSION) ||
|
||||
!semver.lt(prev, THEME_OF_THE_MONTH_RESET_VERSION)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
await browser.storage.local.set({
|
||||
themeOfTheMonthDisabled: false,
|
||||
themeOfTheMonthLastSeenId: undefined,
|
||||
});
|
||||
|
||||
console.info(
|
||||
`[BetterSEQTA+] Migration ${THEME_OF_THE_MONTH_RESET_VERSION}: Theme of the Month re-enabled (from ${previousVersion}).`,
|
||||
);
|
||||
} catch (e) {
|
||||
console.warn(
|
||||
"[BetterSEQTA+] Theme of the Month 3.6.6 reset migration failed:",
|
||||
e,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/** 3.7.0: Close no longer marks entries seen — clear legacy dismissal keys. */
|
||||
const THEME_OF_THE_MONTH_RELOAD_VERSION = "3.7.0";
|
||||
|
||||
async function resetThemeOfTheMonthDismissalFor370Upgrade(
|
||||
previousVersion: string,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const currRaw = browser.runtime.getManifest().version;
|
||||
const prev = semver.coerce(previousVersion);
|
||||
const curr = semver.coerce(currRaw);
|
||||
if (
|
||||
prev == null ||
|
||||
curr == null ||
|
||||
semver.lt(curr, THEME_OF_THE_MONTH_RELOAD_VERSION) ||
|
||||
!semver.lt(prev, THEME_OF_THE_MONTH_RELOAD_VERSION)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
await browser.storage.local.set({
|
||||
themeOfTheMonthLastSeenId: undefined,
|
||||
themeOfTheMonthDismissedMonth: undefined,
|
||||
});
|
||||
|
||||
console.info(
|
||||
`[BetterSEQTA+] Migration ${THEME_OF_THE_MONTH_RELOAD_VERSION}: Theme of the Month shows again until dismissed for the month (from ${previousVersion}).`,
|
||||
);
|
||||
} catch (e) {
|
||||
console.warn(
|
||||
"[BetterSEQTA+] Theme of the Month 3.7.0 dismissal migration failed:",
|
||||
e,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
browser.runtime.onInstalled.addListener(function (event) {
|
||||
browser.storage.local.remove(["justupdated"]);
|
||||
browser.storage.local.remove(["data"]);
|
||||
@@ -561,6 +631,8 @@ browser.runtime.onInstalled.addListener(function (event) {
|
||||
|
||||
if (event.reason === "update" && event.previousVersion) {
|
||||
void migrateGlobalSearchDefaultsFor365Upgrade(event.previousVersion);
|
||||
void resetThemeOfTheMonthDisabledFor366Upgrade(event.previousVersion);
|
||||
void resetThemeOfTheMonthDismissalFor370Upgrade(event.previousVersion);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
+129
-23
@@ -3809,20 +3809,77 @@ div.day-empty {
|
||||
pointer-events: none;
|
||||
animation: themeOfTheMonthCardOut 0.18s ease-in forwards;
|
||||
}
|
||||
.themeOfTheMonthCardClose {
|
||||
position: absolute !important;
|
||||
top: 4px !important;
|
||||
right: 4px !important;
|
||||
z-index: 2;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.22);
|
||||
border-radius: 16px !important;
|
||||
background: rgba(0, 0, 0, 0.42);
|
||||
color: white;
|
||||
.themeOfTheMonthCardConfirm {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 4;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 16px;
|
||||
border-radius: inherit;
|
||||
background: color-mix(in srgb, var(--background-primary) 88%, transparent);
|
||||
backdrop-filter: blur(8px);
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.16s ease;
|
||||
}
|
||||
.themeOfTheMonthCardConfirm[hidden] {
|
||||
display: none;
|
||||
}
|
||||
.themeOfTheMonthCardConfirmVisible {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
.themeOfTheMonthCardConfirmInner {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
.themeOfTheMonthCardConfirmInner h3 {
|
||||
margin: 0 0 6px;
|
||||
font-size: 1rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.themeOfTheMonthCardConfirmInner p {
|
||||
margin: 0 0 14px;
|
||||
font-size: 0.86rem;
|
||||
line-height: 1.4;
|
||||
color: color-mix(in srgb, var(--text-primary) 78%, transparent);
|
||||
}
|
||||
.themeOfTheMonthCardConfirmActions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.themeOfTheMonthCardConfirmCancel,
|
||||
.themeOfTheMonthCardConfirmAccept {
|
||||
appearance: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 1.35rem;
|
||||
line-height: 1;
|
||||
border-radius: 9999px;
|
||||
padding: 0.5rem 0.85rem;
|
||||
font-size: 0.84rem;
|
||||
font-weight: 700;
|
||||
transition: transform 0.15s ease, filter 0.15s ease, background 0.15s ease;
|
||||
}
|
||||
.themeOfTheMonthCardConfirmCancel {
|
||||
background: color-mix(in srgb, var(--text-primary) 10%, transparent);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
.themeOfTheMonthCardConfirmAccept {
|
||||
background: var(--better-pri, #6366f1);
|
||||
color: white;
|
||||
}
|
||||
.themeOfTheMonthCardConfirmCancel:hover,
|
||||
.themeOfTheMonthCardConfirmAccept:hover {
|
||||
filter: brightness(1.08);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.themeOfTheMonthCardConfirmCancel:active,
|
||||
.themeOfTheMonthCardConfirmAccept:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
.themeOfTheMonthCardImage {
|
||||
display: block;
|
||||
@@ -3861,11 +3918,34 @@ div.day-empty {
|
||||
.themeOfTheMonthCardActions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
}
|
||||
.themeOfTheMonthCardActionsStart {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
.themeOfTheMonthCardActionsEnd {
|
||||
display: inline-flex;
|
||||
flex-wrap: nowrap;
|
||||
align-items: stretch;
|
||||
margin-left: auto;
|
||||
padding: 3px;
|
||||
gap: 0;
|
||||
overflow: hidden;
|
||||
border-radius: 9999px;
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--background-secondary, var(--text-primary)) 28%,
|
||||
var(--background-primary)
|
||||
);
|
||||
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--text-primary) 8%, transparent);
|
||||
}
|
||||
.themeOfTheMonthCardPrimary,
|
||||
.themeOfTheMonthCardSecondary {
|
||||
.themeOfTheMonthCardSecondary,
|
||||
.themeOfTheMonthCardDontShow {
|
||||
appearance: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
@@ -3873,23 +3953,49 @@ div.day-empty {
|
||||
padding: 0.58rem 0.9rem;
|
||||
font-size: 0.86rem;
|
||||
font-weight: 700;
|
||||
transition: transform 0.15s ease, filter 0.15s ease, background 0.15s ease;
|
||||
transition: background 0.15s ease, color 0.15s ease;
|
||||
}
|
||||
.themeOfTheMonthCardPrimary {
|
||||
background: var(--better-pri, #6366f1);
|
||||
color: white;
|
||||
}
|
||||
.themeOfTheMonthCardSecondary {
|
||||
background: color-mix(in srgb, var(--text-primary) 10%, transparent);
|
||||
#theme-of-the-month-card .themeOfTheMonthCardActionsEnd .themeOfTheMonthCardSecondary,
|
||||
#theme-of-the-month-card .themeOfTheMonthCardActionsEnd .themeOfTheMonthCardDontShow {
|
||||
padding: 0.5rem 0.8rem;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
border: none !important;
|
||||
border-radius: 9999px !important;
|
||||
background: transparent !important;
|
||||
box-shadow: none !important;
|
||||
filter: none !important;
|
||||
transform: none !important;
|
||||
}
|
||||
#theme-of-the-month-card .themeOfTheMonthCardActionsEnd .themeOfTheMonthCardSecondary {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
.themeOfTheMonthCardPrimary:hover,
|
||||
.themeOfTheMonthCardSecondary:hover {
|
||||
#theme-of-the-month-card .themeOfTheMonthCardActionsEnd .themeOfTheMonthCardDontShow {
|
||||
color: color-mix(in srgb, var(--text-primary) 58%, transparent);
|
||||
}
|
||||
#theme-of-the-month-card .themeOfTheMonthCardActionsEnd .themeOfTheMonthCardSecondary:hover,
|
||||
#theme-of-the-month-card .themeOfTheMonthCardActionsEnd .themeOfTheMonthCardSecondary:focus-visible,
|
||||
#theme-of-the-month-card .themeOfTheMonthCardActionsEnd .themeOfTheMonthCardDontShow:hover,
|
||||
#theme-of-the-month-card .themeOfTheMonthCardActionsEnd .themeOfTheMonthCardDontShow:focus-visible {
|
||||
background: color-mix(in srgb, var(--text-primary) 10%, transparent) !important;
|
||||
border-radius: 9999px !important;
|
||||
filter: none !important;
|
||||
transform: none !important;
|
||||
}
|
||||
#theme-of-the-month-card .themeOfTheMonthCardActionsEnd .themeOfTheMonthCardSecondary:active,
|
||||
#theme-of-the-month-card .themeOfTheMonthCardActionsEnd .themeOfTheMonthCardDontShow:active {
|
||||
background: color-mix(in srgb, var(--text-primary) 14%, transparent) !important;
|
||||
border-radius: 9999px !important;
|
||||
}
|
||||
.themeOfTheMonthCardPrimary:hover {
|
||||
filter: brightness(1.08);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.themeOfTheMonthCardPrimary:active,
|
||||
.themeOfTheMonthCardSecondary:active {
|
||||
.themeOfTheMonthCardPrimary:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
@keyframes themeOfTheMonthCardIn {
|
||||
|
||||
@@ -423,6 +423,18 @@
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
{#if plugin.pluginId === 'global-search'}
|
||||
{@render Setting({
|
||||
title: "Theme of the Month",
|
||||
description: "Show the monthly featured theme popup when a new entry is available",
|
||||
id: 15,
|
||||
Component: Switch,
|
||||
props: {
|
||||
state: !($settingsState.themeOfTheMonthDisabled ?? false),
|
||||
onChange: (isOn: boolean) => settingsState.themeOfTheMonthDisabled = !isOn
|
||||
}
|
||||
})}
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -36,7 +36,12 @@ export interface SettingsState {
|
||||
engageParentsAnnouncementShown?: boolean;
|
||||
/** One-time announcement: BS Cloud automatic settings sync (last in startup popup queue). */
|
||||
bsCloudAutoSyncAnnouncementShown?: boolean;
|
||||
/** ID of the last Theme of the Month entry shown to the user (shows once per new entry). */
|
||||
/**
|
||||
* Calendar month (`YYYY-MM`) for which the user closed the Theme of the Month popup.
|
||||
* Cleared automatically when a new month's entry is fetched (different `month`).
|
||||
*/
|
||||
themeOfTheMonthDismissedMonth?: string;
|
||||
/** @deprecated Migrated away; no longer read. */
|
||||
themeOfTheMonthLastSeenId?: string;
|
||||
/** Permanently disables Theme of the Month startup prompts. */
|
||||
themeOfTheMonthDisabled?: boolean;
|
||||
|
||||
Reference in New Issue
Block a user