diff --git a/src/background.ts b/src/background.ts
index 638a952b..4360540b 100644
--- a/src/background.ts
+++ b/src/background.ts
@@ -65,7 +65,6 @@ const DefaultValues: SettingsState = {
bksliderinput: "50",
transparencyEffects: false,
lessonalert: true,
- notificationcollector: true,
defaultmenuorder: [],
menuitems: {
assessments: { toggle: true },
diff --git a/src/interface/index.html b/src/interface/index.html
index b324d8bb..0a3f1b8a 100644
--- a/src/interface/index.html
+++ b/src/interface/index.html
@@ -5,8 +5,8 @@
BetterSEQTA+ Settings
-
-
+
+
\ No newline at end of file
diff --git a/src/interface/pages/settings/general.svelte b/src/interface/pages/settings/general.svelte
index faa95790..61db53ab 100644
--- a/src/interface/pages/settings/general.svelte
+++ b/src/interface/pages/settings/general.svelte
@@ -25,6 +25,7 @@
interface Plugin {
pluginId: string;
name: string;
+ description: string;
settings: Record;
}
@@ -78,6 +79,23 @@
pluginSettings.forEach(plugin => {
if (Object.keys(plugin.settings).length === 0) return;
+ // Add enable/disable toggle if plugin has disableToggle set
+ if ((plugin as any).disableToggle) {
+ entries.push({
+ title: `Enable ${plugin.name}`,
+ description: `${plugin.description}`,
+ id: getPluginSettingId(plugin.pluginId, 'enabled'),
+ Component: Switch,
+ props: {
+ state: pluginSettingsValues[plugin.pluginId]?.enabled ?? true,
+ onChange: (value: boolean) => {
+ updatePluginSetting(plugin.pluginId, 'enabled', value);
+ // The plugin manager will handle the actual enabling/disabling
+ }
+ }
+ });
+ }
+
Object.entries(plugin.settings).forEach(([key, setting]) => {
const id = getPluginSettingId(plugin.pluginId, key);
diff --git a/src/plugins/built-in/notificationCollector/index.ts b/src/plugins/built-in/notificationCollector/index.ts
index 0fd297dc..880cb03a 100644
--- a/src/plugins/built-in/notificationCollector/index.ts
+++ b/src/plugins/built-in/notificationCollector/index.ts
@@ -1,29 +1,18 @@
import type { Plugin } from '../../core/types';
-import { BasePlugin, BooleanSetting } from '../../core/settings';
interface NotificationCollectorStorage {
lastNotificationCount: number;
lastCheckedTime: string;
}
-class NotificationCollectorPluginClass extends BasePlugin {
- @BooleanSetting({
- default: true,
- title: "Notification Collector",
- description: "Uncaps the 9+ limit for notifications, showing the real number.",
- })
- enabled!: boolean;
-}
-
-// Create an instance to extract settings
-const settingsInstance = new NotificationCollectorPluginClass();
-
-const notificationCollectorPlugin: Plugin = {
+const notificationCollectorPlugin: Plugin<{}, NotificationCollectorStorage> = {
id: 'notificationCollector',
name: 'Notification Collector',
description: 'Collects and displays SEQTA notifications',
version: '1.0.0',
- settings: settingsInstance.settings,
+ settings: {},
+ disableToggle: true,
+
run: async (api) => {
let pollInterval: number | null = null;
@@ -80,30 +69,21 @@ const notificationCollectorPlugin: Plugin 9) {
+ alertDiv.textContent = "9+";
+ } else {
+ alertDiv.textContent = api.storage.lastNotificationCount.toString();
+ }
}
}
};
- if (api.settings.enabled) {
- api.seqta.onMount(".notifications__bubble___1EkSQ", (_) => {
- startPolling();
- });
- }
-
- const enabledCallback = (value: any) => {
- if (value) {
- startPolling();
- } else {
- stopPolling();
- }
- };
-
- api.settings.onChange('enabled', enabledCallback);
+ api.seqta.onMount(".notifications__bubble___1EkSQ", (_) => {
+ startPolling();
+ });
return () => {
stopPolling();
- api.settings.offChange('enabled', enabledCallback);
};
}
};
diff --git a/src/plugins/built-in/test/index.ts b/src/plugins/built-in/test/index.ts
index 2788ac12..d77bc5c1 100644
--- a/src/plugins/built-in/test/index.ts
+++ b/src/plugins/built-in/test/index.ts
@@ -5,9 +5,9 @@ class TestPluginClass extends BasePlugin {
@BooleanSetting({
default: true,
title: "Test Plugin",
- description: "A test plugin for BetterSEQTA+",
+ description: "Some random setting",
})
- enabled!: boolean;
+ someSetting!: boolean;
}
const settingsInstance = new TestPluginClass();
@@ -22,9 +22,14 @@ const testPlugin: Plugin = {
run: async (api) => {
console.log('Test plugin running');
- api.seqta.onPageChange((page) => {
+ const { unregister } = api.seqta.onPageChange((page) => {
console.log('Page changed to', page);
});
+
+ return () => {
+ console.log('Test plugin stopped');
+ unregister();
+ }
}
};
diff --git a/src/plugins/built-in/timetable/index.ts b/src/plugins/built-in/timetable/index.ts
index e3254d8a..af25fb85 100644
--- a/src/plugins/built-in/timetable/index.ts
+++ b/src/plugins/built-in/timetable/index.ts
@@ -2,53 +2,32 @@ import { settingsState } from '@/seqta/utils/listeners/SettingsState';
import type { Plugin } from '../../core/types';
import { convertTo12HourFormat } from '@/seqta/utils/convertTo12HourFormat';
import { waitForElm } from '@/seqta/utils/waitForElm';
-import { BasePlugin, BooleanSetting } from '../../core/settings';
-// Define only the typed settings - no need for redundant interface
-class TimetablePluginClass extends BasePlugin {
- @BooleanSetting({
- default: true,
- title: "Timetable Enhancer",
- description: "Adds extra features to the timetable view."
- })
- enabled!: boolean;
-}
-
-// Create an instance to extract settings
-const settingsInstance = new TimetablePluginClass();
-
-const timetablePlugin: Plugin = {
+const timetablePlugin: Plugin<{}, {}> = {
id: 'timetable',
name: 'Timetable Enhancer',
description: 'Adds extra features to the timetable view',
version: '1.0.0',
- settings: settingsInstance.settings,
+ settings: {},
+ disableToggle: true,
+
run: async (api) => {
- if (api.settings.enabled) {
- api.seqta.onMount('.timetablepage', handleTimetable)
- }
-
- const enabledCallback = (value: any) => {
- if (value) {
- api.seqta.onMount('.timetablepage', handleTimetable)
- } else {
- const timetablePage = document.querySelector('.timetablepage')
- if (timetablePage) {
- const zoomControls = document.querySelector('.timetable-zoom-controls')
- if (zoomControls) zoomControls.remove()
-
- const hideControls = document.querySelector('.timetable-hide-controls')
- if (hideControls) hideControls.remove()
-
- resetTimetableStyles()
- }
- }
- }
-
- api.settings.onChange('enabled', enabledCallback)
-
+ const { unregister } = api.seqta.onMount('.timetablepage', handleTimetable)
+
return () => {
- api.settings.offChange('enabled', enabledCallback)
+ // Call the unregister function to remove the mount listener
+ unregister();
+
+ const timetablePage = document.querySelector('.timetablepage')
+ if (timetablePage) {
+ const zoomControls = document.querySelector('.timetable-zoom-controls')
+ if (zoomControls) zoomControls.remove()
+
+ const hideControls = document.querySelector('.timetable-hide-controls')
+ if (hideControls) hideControls.remove()
+
+ resetTimetableStyles()
+ }
}
}
};
diff --git a/src/plugins/core/createAPI.ts b/src/plugins/core/createAPI.ts
index b1e1cf7d..d24df593 100644
--- a/src/plugins/core/createAPI.ts
+++ b/src/plugins/core/createAPI.ts
@@ -6,7 +6,7 @@ import browser from 'webextension-polyfill';
function createSEQTAAPI(): SEQTAAPI {
return {
onMount: (selector, callback) => {
- eventManager.register(
+ return eventManager.register(
`${selector}Added`,
{
customCheck: (element) => element.matches(selector),
@@ -22,11 +22,20 @@ function createSEQTAAPI(): SEQTAAPI {
return path.split('/')[0];
},
onPageChange: (callback) => {
- window.addEventListener('hashchange', () => {
+ const handler = () => {
const page = window.location.hash.split('?page=/')[1] || '';
callback(page.split('/')[0]);
- });
- },
+ };
+
+ window.addEventListener('hashchange', handler);
+
+ // Return an unregister function
+ return {
+ unregister: () => {
+ window.removeEventListener('hashchange', handler);
+ }
+ };
+ }
};
}
diff --git a/src/plugins/core/manager.ts b/src/plugins/core/manager.ts
index 78017c90..b329f8f3 100644
--- a/src/plugins/core/manager.ts
+++ b/src/plugins/core/manager.ts
@@ -1,5 +1,16 @@
import type { Plugin, PluginSettings } from './types';
import { createPluginAPI } from './createAPI';
+import browser from 'webextension-polyfill';
+
+interface PluginSettingsStorage {
+ enabled?: boolean;
+ [key: string]: any;
+}
+
+interface StorageChange {
+ oldValue?: T;
+ newValue?: T;
+}
export class PluginManager {
private static instance: PluginManager;
@@ -9,7 +20,9 @@ export class PluginManager {
private cleanupFunctions: Map void> = new Map();
private listeners: Map void>> = new Map();
- private constructor() {}
+ private constructor() {
+ this.setupPluginStateListener();
+ }
public static getInstance(): PluginManager {
if (!PluginManager.instance) {
@@ -66,6 +79,17 @@ export class PluginManager {
try {
const api = createPluginAPI(plugin);
+ // Check if plugin is enabled before starting
+ if (plugin.disableToggle) {
+ const settings = await browser.storage.local.get(`plugin.${pluginId}.settings`);
+ const pluginSettings = settings[`plugin.${pluginId}.settings`] as PluginSettingsStorage | undefined;
+ const enabled = pluginSettings?.enabled ?? true;
+ if (!enabled) {
+ console.info(`Plugin "${pluginId}" is disabled, skipping initialization`);
+ return;
+ }
+ }
+
// Wait for both settings and storage to be loaded before starting the plugin
await Promise.all([
(api.settings as any).loaded,
@@ -145,9 +169,21 @@ export class PluginManager {
}];
});
+ if (plugin.disableToggle) {
+ settingsEntries.push([
+ 'enabled', {
+ id: 'enabled',
+ title: plugin.name,
+ description: plugin.description,
+ type: 'boolean',
+ default: true
+ }
+ ])
+ }
return {
pluginId: id,
name: plugin.name,
+ description: plugin.description,
settings: Object.fromEntries(settingsEntries)
};
});
@@ -177,4 +213,36 @@ export class PluginManager {
listeners.delete(callback);
}
}
+
+ // Add handler for plugin enable/disable state changes
+ private async handlePluginStateChange(pluginId: string, enabled: boolean): Promise {
+ if (enabled) {
+ await this.startPlugin(pluginId);
+ } else {
+ await this.stopPlugin(pluginId);
+ }
+ }
+
+ // Add listener for plugin settings changes
+ private setupPluginStateListener(): void {
+ browser.storage.onChanged.addListener((changes: { [key: string]: StorageChange }, area: string) => {
+ if (area !== 'local') return;
+
+ for (const [key, change] of Object.entries(changes)) {
+ const match = key.match(/^plugin\.(.+)\.settings$/);
+ if (!match) continue;
+
+ const pluginId = match[1];
+ const plugin = this.plugins.get(pluginId);
+ if (!plugin?.disableToggle) continue;
+
+ const enabled = (change.newValue as PluginSettingsStorage)?.enabled ?? true;
+ const wasEnabled = (change.oldValue as PluginSettingsStorage)?.enabled ?? true;
+
+ if (enabled !== wasEnabled) {
+ this.handlePluginStateChange(pluginId, enabled);
+ }
+ }
+ });
+ }
}
\ No newline at end of file
diff --git a/src/plugins/core/types.ts b/src/plugins/core/types.ts
index 269df35b..097d232f 100644
--- a/src/plugins/core/types.ts
+++ b/src/plugins/core/types.ts
@@ -56,10 +56,10 @@ export type SettingsAPI = {
}
export interface SEQTAAPI {
- onMount: (selector: string, callback: (element: Element) => void) => void;
+ onMount: (selector: string, callback: (element: Element) => void) => { unregister: () => void };
getFiber: (selector: string) => ReactFiber;
getCurrentPage: () => string;
- onPageChange: (callback: (page: string) => void) => void;
+ onPageChange: (callback: (page: string) => void) => { unregister: () => void };
}
export interface StorageAPI {
@@ -101,5 +101,6 @@ export interface Plugin {
description: string;
version: string;
settings: T;
- run: (api: PluginAPI) => void | Promise | (() => void) | Promise<() => void>;
+ disableToggle?: boolean; // Optional flag to show/hide the plugin's enable/disable toggle in settings
+ run: (api: PluginAPI) => void | Promise | (() => void) | Promise<(() => void)>;
}
\ No newline at end of file
diff --git a/src/seqta/utils/CreateEnable/EnableNotificationCollector.ts b/src/seqta/utils/CreateEnable/EnableNotificationCollector.ts
deleted file mode 100644
index 8477d668..00000000
--- a/src/seqta/utils/CreateEnable/EnableNotificationCollector.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-export function enableNotificationCollector() {
- var xhr3 = new XMLHttpRequest()
- xhr3.open("POST", `${location.origin}/seqta/student/heartbeat?`, true)
- xhr3.setRequestHeader("Content-Type", "application/json; charset=utf-8")
- xhr3.onreadystatechange = function () {
- if (xhr3.readyState === 4) {
- var Notifications = JSON.parse(xhr3.response)
- var alertdiv = document.getElementsByClassName(
- "notifications__bubble___1EkSQ",
- )[0]
- if (typeof alertdiv == "undefined") {
- console.info("[BetterSEQTA+] No notifications currently")
- } else {
- alertdiv.textContent = Notifications.payload.notifications.length
- }
- }
- }
- xhr3.send(
- JSON.stringify({
- timestamp: "1970-01-01 00:00:00.0",
- hash: "#?page=/home",
- }),
- )
-}
\ No newline at end of file
diff --git a/src/seqta/utils/DisableRemove/DisableNotificationCollector.ts b/src/seqta/utils/DisableRemove/DisableNotificationCollector.ts
deleted file mode 100644
index 10edf229..00000000
--- a/src/seqta/utils/DisableRemove/DisableNotificationCollector.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-export function disableNotificationCollector() {
- var alertdiv = document.getElementsByClassName(
- "notifications__bubble___1EkSQ",
- )[0]
- if (typeof alertdiv != "undefined") {
- var currentNumber = parseInt(alertdiv.textContent!)
- if (currentNumber < 9) {
- alertdiv.textContent = currentNumber.toString()
- } else {
- alertdiv.textContent = "9+"
- }
- }
-}
\ No newline at end of file
diff --git a/src/seqta/utils/Loaders/LoadHomePage.ts b/src/seqta/utils/Loaders/LoadHomePage.ts
index 167f1a28..c3664fe1 100644
--- a/src/seqta/utils/Loaders/LoadHomePage.ts
+++ b/src/seqta/utils/Loaders/LoadHomePage.ts
@@ -19,8 +19,6 @@ import { CreateElement } from "@/seqta/utils/CreateEnable/CreateElement"
import { convertTo12HourFormat } from "../convertTo12HourFormat"
-import { enableNotificationCollector } from "@/seqta/utils/CreateEnable/EnableNotificationCollector"
-
let LessonInterval: any
let currentSelectedDate = new Date()
@@ -249,10 +247,6 @@ export async function loadHomePage() {
}
}
- if (settingsState.notificationcollector) {
- enableNotificationCollector()
- }
-
return cleanup
}
diff --git a/src/seqta/utils/listeners/StorageChanges.ts b/src/seqta/utils/listeners/StorageChanges.ts
index c61485fe..5ccf07ca 100644
--- a/src/seqta/utils/listeners/StorageChanges.ts
+++ b/src/seqta/utils/listeners/StorageChanges.ts
@@ -5,8 +5,6 @@ import { updateAllColors } from '@/seqta/ui/colors/Manager';
import { addShortcuts } from "@/seqta/utils/Adders/AddShortcuts";
import { CreateBackground } from "@/seqta/utils/CreateEnable/CreateBackground";
import { CreateCustomShortcutDiv } from "@/seqta/utils/CreateEnable/CreateCustomShortcutDiv";
-import { disableNotificationCollector } from "@/seqta/utils/DisableRemove/DisableNotificationCollector";
-import { enableNotificationCollector } from "@/seqta/utils/CreateEnable/EnableNotificationCollector";
import { FilterUpcomingAssessments } from "@/seqta/utils/FilterUpcomingAssessments";
import { RemoveBackground } from "@/seqta/utils/DisableRemove/RemoveBackground";
import { RemoveShortcutDiv } from "@/seqta/utils/DisableRemove/RemoveShortcutDiv";
@@ -27,7 +25,6 @@ export class StorageChangeHandler {
settingsState.register('onoff', this.handleOnOffChange.bind(this));
settingsState.register('shortcuts', this.handleShortcutsChange.bind(this));
settingsState.register('customshortcuts', this.handleCustomShortcutsChange.bind(this));
- settingsState.register('notificationcollector', this.handleNotificationCollectorChange.bind(this));
settingsState.register('bksliderinput', updateBgDurations.bind(this));
settingsState.register('animatedbk', this.handleAnimatedBkChange.bind(this));
settingsState.register('transparencyEffects', this.handleTransparencyEffectsChange.bind(this));
@@ -43,14 +40,6 @@ export class StorageChangeHandler {
browser.runtime.sendMessage({ type: 'reloadTabs' });
}
- private handleNotificationCollectorChange(newValue: boolean) {
- if (newValue) {
- enableNotificationCollector();
- } else {
- disableNotificationCollector();
- }
- }
-
private handleCustomShortcutsChange(newValue: CustomShortcut[], oldValue: CustomShortcut[]) {
if (newValue) {
if (newValue.length > oldValue.length) {
diff --git a/src/types/storage.ts b/src/types/storage.ts
index ee42560d..fd3a7ffb 100644
--- a/src/types/storage.ts
+++ b/src/types/storage.ts
@@ -25,7 +25,6 @@ export interface SettingsState {
welcome: ToggleItem;
};
menuorder: any[];
- notificationcollector: boolean;
onoff: boolean;
selectedColor: string;
originalSelectedColor: string;