mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-08-28 09:11:06 +00:00
fix: fix login issue and improve google caledar flow
This commit is contained in:
@@ -5,7 +5,7 @@ import {
|
|||||||
defineSettings,
|
defineSettings,
|
||||||
hotkeySetting,
|
hotkeySetting,
|
||||||
} from "../../core/settingsHelpers";
|
} from "../../core/settingsHelpers";
|
||||||
import { isSeqtaEngageExperience } from "@/seqta/utils/isSeqtaEngage";
|
import { isSeqtaEngageExperience, isSeqtaLoginPage } from "@/seqta/utils/isSeqtaEngage";
|
||||||
import styles from "./src/core/styles.css?inline";
|
import styles from "./src/core/styles.css?inline";
|
||||||
import {
|
import {
|
||||||
resetSearchIndexes,
|
resetSearchIndexes,
|
||||||
@@ -90,7 +90,7 @@ const globalSearchPlugin = defineLazyPlugin({
|
|||||||
const runGlobalSearch = globalSearchPlugin.run!;
|
const runGlobalSearch = globalSearchPlugin.run!;
|
||||||
|
|
||||||
globalSearchPlugin.run = async (api) => {
|
globalSearchPlugin.run = async (api) => {
|
||||||
if (isSeqtaEngageExperience()) return () => {};
|
if (isSeqtaEngageExperience() || isSeqtaLoginPage()) return () => {};
|
||||||
|
|
||||||
// Eager chrome (like Analytics menu injection) — heavy chunk loads behind it.
|
// Eager chrome (like Analytics menu injection) — heavy chunk loads behind it.
|
||||||
const hotkey = isValidHotkey(api.settings.searchHotkey ?? "")
|
const hotkey = isValidHotkey(api.settings.searchHotkey ?? "")
|
||||||
|
|||||||
@@ -58,6 +58,7 @@
|
|||||||
let modalProvider = $state<CalendarProvider | null>(null);
|
let modalProvider = $state<CalendarProvider | null>(null);
|
||||||
let showDisconnect = $state(false);
|
let showDisconnect = $state(false);
|
||||||
let showDeleteEvents = $state(false);
|
let showDeleteEvents = $state(false);
|
||||||
|
let disconnectOverlay = $state(false);
|
||||||
let toast = $state<{ message: string; error: boolean } | null>(null);
|
let toast = $state<{ message: string; error: boolean } | null>(null);
|
||||||
let syncProgress = $state<GoogleCalendarSyncProgress | null>(null);
|
let syncProgress = $state<GoogleCalendarSyncProgress | null>(null);
|
||||||
let syncWeeksAhead = $state(12);
|
let syncWeeksAhead = $state(12);
|
||||||
@@ -67,20 +68,20 @@
|
|||||||
let triggerEl = $state<HTMLButtonElement | null>(null);
|
let triggerEl = $state<HTMLButtonElement | null>(null);
|
||||||
let menuEl = $state<HTMLDivElement | null>(null);
|
let menuEl = $state<HTMLDivElement | null>(null);
|
||||||
let modalEl = $state<HTMLDivElement | null>(null);
|
let modalEl = $state<HTMLDivElement | null>(null);
|
||||||
|
let overlayEl = $state<HTMLDivElement | null>(null);
|
||||||
let menuStyle = $state("");
|
let menuStyle = $state("");
|
||||||
let toastTimer: ReturnType<typeof setTimeout> | null = null;
|
let toastTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
|
||||||
const isBusy = $derived(busy !== null);
|
const isBusy = $derived(busy !== null);
|
||||||
const anyConnected = $derived(googleStatus.connected || outlookStatus.connected);
|
const anyConnected = $derived(googleStatus.connected || outlookStatus.connected);
|
||||||
const modalOpen = $derived(showDisconnect || showDeleteEvents);
|
const modalOpen = $derived(showDisconnect || showDeleteEvents);
|
||||||
const modalBusy = $derived(
|
const modalBusy = $derived(showDisconnect ? busy?.phase === "disconnect" : busy?.phase === "delete");
|
||||||
showDisconnect ? busy?.phase === "disconnect" : busy?.phase === "delete",
|
|
||||||
);
|
|
||||||
const providerLabel = $derived(calendarProviderLabel(modalProvider ?? "google"));
|
const providerLabel = $derived(calendarProviderLabel(modalProvider ?? "google"));
|
||||||
const showTriggerProgress = $derived(
|
const showTriggerProgress = $derived(
|
||||||
isBusy &&
|
isBusy &&
|
||||||
(busy?.phase === "sync" ||
|
(busy?.phase === "sync" ||
|
||||||
busy?.phase === "delete" ||
|
busy?.phase === "delete" ||
|
||||||
|
busy?.phase === "disconnect" ||
|
||||||
busy?.phase === "connect" ||
|
busy?.phase === "connect" ||
|
||||||
(syncProgress !== null && syncProgress.phase !== "done")),
|
(syncProgress !== null && syncProgress.phase !== "done")),
|
||||||
);
|
);
|
||||||
@@ -93,7 +94,12 @@
|
|||||||
});
|
});
|
||||||
const triggerStatusText = $derived.by(() => {
|
const triggerStatusText = $derived.by(() => {
|
||||||
if (!showTriggerProgress) return "Sync with Calendar";
|
if (!showTriggerProgress) return "Sync with Calendar";
|
||||||
const verb = busy?.phase === "delete" ? "Deleting" : "Syncing";
|
const verb =
|
||||||
|
busy?.phase === "delete"
|
||||||
|
? "Deleting"
|
||||||
|
: busy?.phase === "disconnect"
|
||||||
|
? "Disconnecting"
|
||||||
|
: "Syncing";
|
||||||
if (syncProgress?.total) return `${verb} ${triggerProgressPercent}%`;
|
if (syncProgress?.total) return `${verb} ${triggerProgressPercent}%`;
|
||||||
return `${verb}…`;
|
return `${verb}…`;
|
||||||
});
|
});
|
||||||
@@ -102,15 +108,27 @@
|
|||||||
return anyConnected ? "Calendar sync options" : "Sync with Calendar";
|
return anyConnected ? "Calendar sync options" : "Sync with Calendar";
|
||||||
}
|
}
|
||||||
if (syncProgress?.total) {
|
if (syncProgress?.total) {
|
||||||
const verb = busy?.phase === "delete" ? "Deleting" : "Syncing";
|
const verb =
|
||||||
|
busy?.phase === "delete"
|
||||||
|
? "Deleting"
|
||||||
|
: busy?.phase === "disconnect"
|
||||||
|
? "Disconnecting"
|
||||||
|
: "Syncing";
|
||||||
return `Calendar ${verb.toLowerCase()} in progress, ${triggerProgressPercent} percent complete`;
|
return `Calendar ${verb.toLowerCase()} in progress, ${triggerProgressPercent} percent complete`;
|
||||||
}
|
}
|
||||||
return busy?.phase === "delete"
|
if (busy?.phase === "delete") return "Calendar deletion in progress";
|
||||||
? "Calendar deletion in progress"
|
if (busy?.phase === "disconnect") return "Calendar disconnect in progress";
|
||||||
: "Calendar sync in progress";
|
return "Calendar sync in progress";
|
||||||
});
|
});
|
||||||
const accent = "var(--bsplus-cal-accent, var(--better-main, #3b82f6))";
|
const accent = "var(--bsplus-cal-accent, var(--better-main, #3b82f6))";
|
||||||
|
|
||||||
|
const overlayProgressPercent = $derived(syncProgressPercent(syncProgress));
|
||||||
|
const overlayStatusText = $derived.by(() => {
|
||||||
|
if (busy?.phase === "disconnect") return "Disconnecting account…";
|
||||||
|
if (syncProgress?.message) return syncProgress.message;
|
||||||
|
return "Removing synced classes…";
|
||||||
|
});
|
||||||
|
|
||||||
function providerPhase(provider: CalendarProvider): BusyPhase {
|
function providerPhase(provider: CalendarProvider): BusyPhase {
|
||||||
return busy?.provider === provider ? busy.phase : null;
|
return busy?.provider === provider ? busy.phase : null;
|
||||||
}
|
}
|
||||||
@@ -225,6 +243,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function deleteProviderEvents(provider: CalendarProvider) {
|
||||||
|
const deleteFn =
|
||||||
|
provider === "google"
|
||||||
|
? deleteSyncedEventsFromGoogleCalendar
|
||||||
|
: deleteSyncedEventsFromOutlookCalendar;
|
||||||
|
return deleteFn(location.origin, () => getCalendarAccessToken(provider), {
|
||||||
|
onProgress: handleSyncProgress,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function confirmDeleteEvents() {
|
async function confirmDeleteEvents() {
|
||||||
if (isBusy || !modalProvider) return;
|
if (isBusy || !modalProvider) return;
|
||||||
const provider = modalProvider;
|
const provider = modalProvider;
|
||||||
@@ -238,13 +266,7 @@
|
|||||||
message: "Preparing removal…",
|
message: "Preparing removal…",
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
const deleteFn =
|
const result = await deleteProviderEvents(provider);
|
||||||
provider === "google"
|
|
||||||
? deleteSyncedEventsFromGoogleCalendar
|
|
||||||
: deleteSyncedEventsFromOutlookCalendar;
|
|
||||||
const result = await deleteFn(location.origin, () => getCalendarAccessToken(provider), {
|
|
||||||
onProgress: handleSyncProgress,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
showToastMessage(result.error ?? "Could not remove calendar events.", true);
|
showToastMessage(result.error ?? "Could not remove calendar events.", true);
|
||||||
@@ -279,22 +301,77 @@
|
|||||||
await saveSyncSettings({ autoSyncWeekly: checked });
|
await saveSyncSettings({ autoSyncWeekly: checked });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function confirmDisconnect() {
|
async function disconnectProvider(
|
||||||
if (isBusy || !modalProvider) return;
|
provider: CalendarProvider,
|
||||||
const provider = modalProvider;
|
label: string,
|
||||||
busy = { provider, phase: "disconnect" };
|
toastMessage?: string,
|
||||||
const label = calendarProviderLabel(provider);
|
) {
|
||||||
try {
|
|
||||||
const result = await disconnectCalendarProvider(provider);
|
const result = await disconnectCalendarProvider(provider);
|
||||||
if (!result?.success) {
|
if (!result?.success) {
|
||||||
showToastMessage(`Could not disconnect ${label} Calendar.`, true);
|
showToastMessage(`Could not disconnect ${label} Calendar.`, true);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
setProviderStatus(provider, { connected: false, lastSyncAt: undefined });
|
setProviderStatus(provider, { connected: false, lastSyncAt: undefined });
|
||||||
showDisconnect = false;
|
|
||||||
menuOpen = false;
|
|
||||||
modalProvider = null;
|
modalProvider = null;
|
||||||
showToastMessage(`Disconnected from ${label} Calendar.`);
|
if (toastMessage) showToastMessage(toastMessage);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function confirmDisconnect(deleteEvents: boolean) {
|
||||||
|
if (isBusy || !modalProvider) return;
|
||||||
|
const provider = modalProvider;
|
||||||
|
const label = calendarProviderLabel(provider);
|
||||||
|
menuOpen = false;
|
||||||
|
|
||||||
|
if (deleteEvents) {
|
||||||
|
showDisconnect = false;
|
||||||
|
disconnectOverlay = true;
|
||||||
|
busy = { provider, phase: "delete" };
|
||||||
|
syncProgress = {
|
||||||
|
phase: "preparing",
|
||||||
|
current: 0,
|
||||||
|
total: 1,
|
||||||
|
message: "Preparing removal…",
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const deleteResult = await deleteProviderEvents(provider);
|
||||||
|
if (!deleteResult.success) {
|
||||||
|
showToastMessage(deleteResult.error ?? "Could not remove calendar events.", true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
busy = { provider, phase: "disconnect" };
|
||||||
|
syncProgress = {
|
||||||
|
phase: "preparing",
|
||||||
|
current: 0,
|
||||||
|
total: 1,
|
||||||
|
message: "Disconnecting account…",
|
||||||
|
};
|
||||||
|
|
||||||
|
const removed = deleteResult.deleted ?? 0;
|
||||||
|
const toastMessage =
|
||||||
|
removed > 0
|
||||||
|
? `Removed ${removed} event${removed === 1 ? "" : "s"} and disconnected from ${label} Calendar.`
|
||||||
|
: `Disconnected from ${label} Calendar.`;
|
||||||
|
await disconnectProvider(provider, label, toastMessage);
|
||||||
|
} catch (err) {
|
||||||
|
showToastMessage(err instanceof Error ? err.message : "Disconnect failed.", true);
|
||||||
|
} finally {
|
||||||
|
syncProgress = null;
|
||||||
|
busy = null;
|
||||||
|
disconnectOverlay = false;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
busy = { provider, phase: "disconnect" };
|
||||||
|
try {
|
||||||
|
const ok = await disconnectProvider(
|
||||||
|
provider,
|
||||||
|
label,
|
||||||
|
`Disconnected from ${label} Calendar.`,
|
||||||
|
);
|
||||||
|
if (ok) showDisconnect = false;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showToastMessage(err instanceof Error ? err.message : "Disconnect failed.", true);
|
showToastMessage(err instanceof Error ? err.message : "Disconnect failed.", true);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -327,6 +404,7 @@
|
|||||||
if (themeHost instanceof HTMLElement) syncCalendarSyncTheme(themeHost);
|
if (themeHost instanceof HTMLElement) syncCalendarSyncTheme(themeHost);
|
||||||
if (menuEl) syncCalendarSyncTheme(menuEl);
|
if (menuEl) syncCalendarSyncTheme(menuEl);
|
||||||
if (modalEl) syncCalendarSyncTheme(modalEl);
|
if (modalEl) syncCalendarSyncTheme(modalEl);
|
||||||
|
if (overlayEl) syncCalendarSyncTheme(overlayEl);
|
||||||
}
|
}
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
@@ -337,6 +415,20 @@
|
|||||||
if (modalOpen && modalEl) syncHostTheme();
|
if (modalOpen && modalEl) syncHostTheme();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (disconnectOverlay && overlayEl) syncHostTheme();
|
||||||
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (!disconnectOverlay) return;
|
||||||
|
const onBeforeUnload = (event: BeforeUnloadEvent) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.returnValue = "";
|
||||||
|
};
|
||||||
|
window.addEventListener("beforeunload", onBeforeUnload);
|
||||||
|
return () => window.removeEventListener("beforeunload", onBeforeUnload);
|
||||||
|
});
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (!menuOpen || !triggerEl) return;
|
if (!menuOpen || !triggerEl) return;
|
||||||
updateMenuPosition();
|
updateMenuPosition();
|
||||||
@@ -579,10 +671,10 @@
|
|||||||
Disconnect {providerLabel} Calendar?
|
Disconnect {providerLabel} Calendar?
|
||||||
</h2>
|
</h2>
|
||||||
<p class="bsplus-cal-modal-body">
|
<p class="bsplus-cal-modal-body">
|
||||||
Stops BetterSEQTA+ from updating your calendar. Synced classes stay in {providerLabel} Calendar
|
Disconnecting normally removes synced classes from {providerLabel} Calendar and signs you out.
|
||||||
until you delete them or connect again.
|
Keep this tab open while that runs. You can also disconnect without deleting your events.
|
||||||
</p>
|
</p>
|
||||||
<div class="bsplus-cal-modal-actions">
|
<div class="bsplus-cal-modal-actions bsplus-cal-modal-actions--disconnect">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="bsplus-cal-btn bsplus-cal-btn--ghost"
|
class="bsplus-cal-btn bsplus-cal-btn--ghost"
|
||||||
@@ -591,13 +683,21 @@
|
|||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="bsplus-cal-btn bsplus-cal-btn--ghost"
|
||||||
|
disabled={modalBusy}
|
||||||
|
onclick={() => void confirmDisconnect(false)}
|
||||||
|
>
|
||||||
|
{modalBusy ? "Disconnecting…" : "Disconnect without deleting"}
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="bsplus-cal-btn bsplus-cal-btn--danger"
|
class="bsplus-cal-btn bsplus-cal-btn--danger"
|
||||||
disabled={modalBusy}
|
disabled={modalBusy}
|
||||||
onclick={() => void confirmDisconnect()}
|
onclick={() => void confirmDisconnect(true)}
|
||||||
>
|
>
|
||||||
{modalBusy ? "Disconnecting…" : "Disconnect account"}
|
Disconnect & delete classes
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
@@ -631,6 +731,43 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if disconnectOverlay}
|
||||||
|
<div
|
||||||
|
class="bsplus-cal-disconnect-overlay"
|
||||||
|
bind:this={overlayEl}
|
||||||
|
use:portalToBody
|
||||||
|
role="alertdialog"
|
||||||
|
aria-modal="true"
|
||||||
|
aria-labelledby="bsplus-cal-disconnect-title"
|
||||||
|
aria-describedby="bsplus-cal-disconnect-desc"
|
||||||
|
transition:fade={{ duration: 150 }}
|
||||||
|
>
|
||||||
|
<div class="bsplus-cal-disconnect-card">
|
||||||
|
<h2 id="bsplus-cal-disconnect-title" class="bsplus-cal-disconnect-title">
|
||||||
|
Do not close this tab
|
||||||
|
</h2>
|
||||||
|
<p id="bsplus-cal-disconnect-desc" class="bsplus-cal-disconnect-body">
|
||||||
|
Removing synced classes and disconnecting {providerLabel} Calendar. Closing SEQTA now may
|
||||||
|
leave events behind or interrupt sign-out.
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
class="bsplus-cal-disconnect-progress"
|
||||||
|
role="progressbar"
|
||||||
|
aria-valuemin="0"
|
||||||
|
aria-valuemax="100"
|
||||||
|
aria-valuenow={overlayProgressPercent}
|
||||||
|
aria-label={overlayStatusText}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="bsplus-cal-disconnect-progress-fill"
|
||||||
|
style:width="{overlayProgressPercent}%"
|
||||||
|
></span>
|
||||||
|
</div>
|
||||||
|
<p class="bsplus-cal-disconnect-status">{overlayStatusText}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if toast}
|
{#if toast}
|
||||||
<div
|
<div
|
||||||
class="bsplus-cal-toast"
|
class="bsplus-cal-toast"
|
||||||
@@ -1001,9 +1138,81 @@
|
|||||||
.bsplus-cal-modal-actions {
|
.bsplus-cal-modal-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
flex-wrap: wrap;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bsplus-cal-modal-actions--disconnect {
|
||||||
|
justify-content: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsplus-cal-modal-actions--disconnect .bsplus-cal-btn {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsplus-cal-disconnect-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: calc(var(--bsplus-cal-z-modal, 1300) + 1);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 24px;
|
||||||
|
background: rgba(0, 0, 0, 0.72);
|
||||||
|
backdrop-filter: blur(6px);
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsplus-cal-disconnect-card {
|
||||||
|
width: min(100%, 420px);
|
||||||
|
padding: 24px;
|
||||||
|
border-radius: 16px;
|
||||||
|
background: var(--bsplus-cal-surface, #fff);
|
||||||
|
color: var(--bsplus-cal-text, #111);
|
||||||
|
border: 1px solid color-mix(in srgb, var(--bsplus-cal-text, #111) 12%, transparent);
|
||||||
|
box-shadow: 0 24px 48px rgba(0, 0, 0, 0.35);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsplus-cal-disconnect-title {
|
||||||
|
margin: 0 0 8px;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.3;
|
||||||
|
color: #dc2626;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsplus-cal-disconnect-body {
|
||||||
|
margin: 0 0 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: color-mix(in srgb, var(--bsplus-cal-text, #111) 78%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsplus-cal-disconnect-progress {
|
||||||
|
height: 8px;
|
||||||
|
margin: 0 0 10px;
|
||||||
|
border-radius: 999px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: color-mix(in srgb, var(--bsplus-cal-text, #111) 10%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsplus-cal-disconnect-progress-fill {
|
||||||
|
display: block;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: inherit;
|
||||||
|
background: var(--bsplus-cal-accent, var(--better-main, #3b82f6));
|
||||||
|
transition: width 0.25s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bsplus-cal-disconnect-status {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: color-mix(in srgb, var(--bsplus-cal-text, #111) 72%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
.bsplus-cal-btn {
|
.bsplus-cal-btn {
|
||||||
padding: 8px 14px;
|
padding: 8px 14px;
|
||||||
border: none;
|
border: none;
|
||||||
|
|||||||
@@ -63,7 +63,12 @@ export function portalToBody(node: HTMLElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function isCalendarSyncModalTarget(target: EventTarget | null): boolean {
|
export function isCalendarSyncModalTarget(target: EventTarget | null): boolean {
|
||||||
return target instanceof Element && Boolean(target.closest(".bsplus-cal-modal-backdrop"));
|
return (
|
||||||
|
target instanceof Element &&
|
||||||
|
Boolean(
|
||||||
|
target.closest(".bsplus-cal-modal-backdrop, .bsplus-cal-disconnect-overlay"),
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sync extension theme (including dark mode) onto a calendar UI host or portaled menu. */
|
/** Sync extension theme (including dark mode) onto a calendar UI host or portaled menu. */
|
||||||
|
|||||||
+17
-7
@@ -13,7 +13,7 @@ import { eventManager } from "@/seqta/utils/listeners/EventManager";
|
|||||||
import debounce from "@/seqta/utils/debounce";
|
import debounce from "@/seqta/utils/debounce";
|
||||||
|
|
||||||
// UI and theme management
|
// UI and theme management
|
||||||
import { isSeqtaEngageExperience } from "@/seqta/utils/isSeqtaEngage";
|
import { isSeqtaEngageExperience, isSeqtaLoginPage } from "@/seqta/utils/isSeqtaEngage";
|
||||||
import RegisterClickListeners from "@/seqta/utils/listeners/ClickListeners";
|
import RegisterClickListeners from "@/seqta/utils/listeners/ClickListeners";
|
||||||
import { AddBetterSEQTAElements } from "@/seqta/ui/AddBetterSEQTAElements";
|
import { AddBetterSEQTAElements } from "@/seqta/ui/AddBetterSEQTAElements";
|
||||||
import { updateAllColors } from "@/seqta/ui/colors/Manager";
|
import { updateAllColors } from "@/seqta/ui/colors/Manager";
|
||||||
@@ -509,10 +509,8 @@ function CheckNoticeTextColour(notice: Element) {
|
|||||||
noticeColourObserver.observe(notice, { childList: true, subtree: true });
|
noticeColourObserver.observe(notice, { childList: true, subtree: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
function watchForEngageLogin() {
|
function watchForLoginDismiss() {
|
||||||
if (!document.querySelector(".login")) {
|
if (!document.querySelector(".login")) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
const observer = new MutationObserver(() => {
|
const observer = new MutationObserver(() => {
|
||||||
if (!document.querySelector(".login")) {
|
if (!document.querySelector(".login")) {
|
||||||
observer.disconnect();
|
observer.disconnect();
|
||||||
@@ -570,7 +568,7 @@ export function tryLoad() {
|
|||||||
const mode = await waitForEngageLoginOrContent();
|
const mode = await waitForEngageLoginOrContent();
|
||||||
if (mode === "login") {
|
if (mode === "login") {
|
||||||
finishLoad();
|
finishLoad();
|
||||||
watchForEngageLogin();
|
watchForLoginDismiss();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (mode === "timeout") {
|
if (mode === "timeout") {
|
||||||
@@ -589,8 +587,15 @@ export function tryLoad() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isSeqtaLoginPage()) {
|
||||||
|
finishLoad();
|
||||||
|
watchForLoginDismiss();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
waitForElm(".login").then(() => {
|
waitForElm(".login").then(() => {
|
||||||
finishLoad();
|
finishLoad();
|
||||||
|
watchForLoginDismiss();
|
||||||
});
|
});
|
||||||
|
|
||||||
waitForElm(".day-container").then(() => {
|
waitForElm(".day-container").then(() => {
|
||||||
@@ -702,6 +707,7 @@ export function init() {
|
|||||||
|
|
||||||
if (settingsState.onoff) {
|
if (settingsState.onoff) {
|
||||||
verboseInfo("[BetterSEQTA+] Enabled");
|
verboseInfo("[BetterSEQTA+] Enabled");
|
||||||
|
const onLogin = isSeqtaLoginPage();
|
||||||
if (settingsState.DarkMode) document.documentElement.classList.add("dark");
|
if (settingsState.DarkMode) document.documentElement.classList.add("dark");
|
||||||
if (settingsState.iconOnlySidebar) {
|
if (settingsState.iconOnlySidebar) {
|
||||||
if (document.body) {
|
if (document.body) {
|
||||||
@@ -713,11 +719,13 @@ export function init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!onLogin) {
|
||||||
document.querySelector(".legacy-root")?.classList.add("hidden");
|
document.querySelector(".legacy-root")?.classList.add("hidden");
|
||||||
|
}
|
||||||
|
|
||||||
// Learn only: hide native sidebar + mount Svelte replacement during loading.
|
// Learn only: hide native sidebar + mount Svelte replacement during loading.
|
||||||
// Engage keeps its native React menu — never apply the pending hide class there.
|
// Engage keeps its native React menu — never apply the pending hide class there.
|
||||||
if (!isSeqtaEngageExperience()) {
|
if (!onLogin && !isSeqtaEngageExperience()) {
|
||||||
document.documentElement.classList.add("bsplus-custom-sidebar-pending");
|
document.documentElement.classList.add("bsplus-custom-sidebar-pending");
|
||||||
document.documentElement.classList.add("bsplus-custom-title-pending");
|
document.documentElement.classList.add("bsplus-custom-title-pending");
|
||||||
void import("@/seqta/ui/sidebar/mountCustomSidebar").then((mod) => {
|
void import("@/seqta/ui/sidebar/mountCustomSidebar").then((mod) => {
|
||||||
@@ -742,7 +750,9 @@ export function init() {
|
|||||||
window.addEventListener("hashchange", () => {
|
window.addEventListener("hashchange", () => {
|
||||||
if (settingsState.adaptiveThemeColour) void updateAllColors();
|
if (settingsState.adaptiveThemeColour) void updateAllColors();
|
||||||
});
|
});
|
||||||
|
if (!onLogin) {
|
||||||
loading();
|
loading();
|
||||||
|
}
|
||||||
InjectCustomIcons();
|
InjectCustomIcons();
|
||||||
applyMenuItemVisibility();
|
applyMenuItemVisibility();
|
||||||
syncTimetableUrlMonitoring();
|
syncTimetableUrlMonitoring();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { mount, unmount } from "svelte";
|
import { mount, unmount } from "svelte";
|
||||||
import type { SettingsState } from "@/types/storage";
|
import type { SettingsState } from "@/types/storage";
|
||||||
import { settingsState } from "@/seqta/utils/listeners/SettingsState";
|
import { settingsState } from "@/seqta/utils/listeners/SettingsState";
|
||||||
import { isSeqtaEngageExperience } from "@/seqta/utils/isSeqtaEngage";
|
import { isSeqtaEngageExperience, isSeqtaLoginPage } from "@/seqta/utils/isSeqtaEngage";
|
||||||
import { waitForElm } from "@/seqta/utils/waitForElm";
|
import { waitForElm } from "@/seqta/utils/waitForElm";
|
||||||
import { waitForSeqtaMenu } from "@/seqta/utils/waitForSeqtaShell";
|
import { waitForSeqtaMenu } from "@/seqta/utils/waitForSeqtaShell";
|
||||||
import Sidebar from "./Sidebar.svelte";
|
import Sidebar from "./Sidebar.svelte";
|
||||||
@@ -199,7 +199,7 @@ function clearPendingClass() {
|
|||||||
* and begin mounting the Svelte sidebar as soon as `#menu` exists.
|
* and begin mounting the Svelte sidebar as soon as `#menu` exists.
|
||||||
*/
|
*/
|
||||||
export function prepareCustomSidebarEarly() {
|
export function prepareCustomSidebarEarly() {
|
||||||
if (isSeqtaEngageExperience()) return;
|
if (isSeqtaEngageExperience() || isSeqtaLoginPage()) return;
|
||||||
if (!settingsState.onoff) return;
|
if (!settingsState.onoff) return;
|
||||||
if (earlyPrepareStarted) return;
|
if (earlyPrepareStarted) return;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { mount, unmount } from "svelte";
|
import { mount, unmount } from "svelte";
|
||||||
import { settingsState } from "@/seqta/utils/listeners/SettingsState";
|
import { settingsState } from "@/seqta/utils/listeners/SettingsState";
|
||||||
import { isSeqtaEngageExperience } from "@/seqta/utils/isSeqtaEngage";
|
import { isSeqtaEngageExperience, isSeqtaLoginPage } from "@/seqta/utils/isSeqtaEngage";
|
||||||
import { waitForSeqtaTitle } from "@/seqta/utils/waitForSeqtaShell";
|
import { waitForSeqtaTitle } from "@/seqta/utils/waitForSeqtaShell";
|
||||||
import TitleBar from "./TitleBar.svelte";
|
import TitleBar from "./TitleBar.svelte";
|
||||||
import { titleBarState } from "./titleBarState.svelte";
|
import { titleBarState } from "./titleBarState.svelte";
|
||||||
@@ -46,7 +46,7 @@ function isReady() {
|
|||||||
|
|
||||||
/** finishLoad waits here so the overlay stays until the title bar is ready. */
|
/** finishLoad waits here so the overlay stays until the title bar is ready. */
|
||||||
export async function waitForCustomTitleBarReady(timeoutMs = 10000) {
|
export async function waitForCustomTitleBarReady(timeoutMs = 10000) {
|
||||||
if (isSeqtaEngageExperience() || !settingsState.onoff) {
|
if (isSeqtaEngageExperience() || isSeqtaLoginPage() || !settingsState.onoff) {
|
||||||
document.documentElement.classList.remove(PENDING_CLASS);
|
document.documentElement.classList.remove(PENDING_CLASS);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -85,7 +85,7 @@ function observeHost(host: HTMLElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function prepareCustomTitleBarEarly() {
|
export function prepareCustomTitleBarEarly() {
|
||||||
if (isSeqtaEngageExperience() || !settingsState.onoff || earlyPrepareStarted) {
|
if (isSeqtaEngageExperience() || isSeqtaLoginPage() || !settingsState.onoff || earlyPrepareStarted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
earlyPrepareStarted = true;
|
earlyPrepareStarted = true;
|
||||||
@@ -94,7 +94,7 @@ export function prepareCustomTitleBarEarly() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function mountCustomTitleBar(): Promise<boolean> {
|
export async function mountCustomTitleBar(): Promise<boolean> {
|
||||||
if (isSeqtaEngageExperience() || !settingsState.onoff) return false;
|
if (isSeqtaEngageExperience() || isSeqtaLoginPage() || !settingsState.onoff) return false;
|
||||||
|
|
||||||
if (app && titleEl && document.getElementById(ROOT_ID)) {
|
if (app && titleEl && document.getElementById(ROOT_ID)) {
|
||||||
observeHost(titleEl);
|
observeHost(titleEl);
|
||||||
|
|||||||
@@ -2,3 +2,8 @@
|
|||||||
export function isSeqtaEngageExperience(): boolean {
|
export function isSeqtaEngageExperience(): boolean {
|
||||||
return document.title.includes("SEQTA Engage");
|
return document.title.includes("SEQTA Engage");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Unauthenticated SEQTA login shell (Learn + Engage). */
|
||||||
|
export function isSeqtaLoginPage(): boolean {
|
||||||
|
return document.querySelector(".login") !== null;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user