From 0f9f61816436e341fdb9f9394fc8759114cbd011 Mon Sep 17 00:00:00 2001 From: SethBurkart123 Date: Mon, 5 May 2025 18:04:10 +1000 Subject: [PATCH] format: run prettify --- .dependency-cruiser.cjs | 221 +- .eslintrc.json | 17 +- .github/ISSUE_TEMPLATE/bug_report.yml | 94 +- .github/ISSUE_TEMPLATE/feature_request.yml | 77 +- .github/workflows/mvp.yml | 38 +- CODE_OF_CONDUCT.md | 22 +- README.md | 17 +- SECURITY.md | 9 +- docs/README.md | 4 +- docs/contributing.md | 8 +- docs/installation.md | 4 +- docs/plugins/README.md | 94 +- docs/plugins/api-reference.md | 178 +- lib/base64loader.ts | 4 +- lib/closePlugin.ts | 20 +- lib/createManifest.ts | 10 +- lib/inlineWorker.ts | 38 +- lib/publish.js | 121 +- lib/touchGlobalCSS.ts | 16 +- lib/types.ts | 134 +- lib/utils.ts | 12 +- src/SEQTA.ts | 60 +- src/background.ts | 181 +- src/background/news.ts | 29 +- src/css/documentload.scss | 8 +- src/css/documentload.ts | 2 +- src/css/iframe.scss | 8 +- src/css/injected.scss | 33 +- src/css/injected/popup.scss | 6 +- src/css/injected/sidebar-animation.scss | 4 +- src/css/injected/theme.scss | 2 +- src/css/injected/transparency.scss | 8 +- src/declarations.d.ts | 16 +- src/interface/components/ColourPicker.tsx | 75 +- src/interface/components/Switch.css | 4 +- src/interface/components/TabbedContainer.css | 2 +- src/interface/hooks/BackgroundDataLoader.ts | 42 +- src/interface/hooks/BackgroundUpdates.ts | 2 +- src/interface/hooks/SettingsPopup.ts | 6 +- src/interface/hooks/ThemeUpdates.ts | 2 +- src/interface/hooks/backgroundState.svelte.ts | 2 +- src/interface/index.css | 4 +- src/interface/index.html | 4 +- src/interface/index.ts | 30 +- src/interface/main.d.ts | 4 +- src/interface/main.ts | 16 +- src/interface/types/SettingsProps.ts | 4 +- src/interface/types/Theme.ts | 2 +- src/interface/utils/standalone.svelte.ts | 50 +- src/interface/utils/themeImageHandlers.ts | 44 +- src/manifests/brave.ts | 13 +- src/manifests/chrome.ts | 13 +- src/manifests/edge.ts | 13 +- src/manifests/firefox.ts | 14 +- src/manifests/opera.ts | 13 +- src/manifests/safari.ts | 14 +- src/pageState.js | 82 +- .../built-in/animatedBackground/index.ts | 45 +- .../built-in/animatedBackground/styles.css | 2 +- .../utils/CreateBackground.ts | 8 +- .../utils/RemoveBackground.ts | 6 +- .../built-in/assessmentsAverage/index.ts | 138 +- .../built-in/globalSearch/src/core/index.ts | 13 +- .../globalSearch/src/core/mountSearchBar.ts | 2 +- .../globalSearch/src/indexing/indexer.ts | 271 +- .../globalSearch/src/indexing/jobs.ts | 2 +- .../src/indexing/jobs/assessments.ts | 61 +- .../src/indexing/jobs/messages.ts | 12 +- .../globalSearch/src/indexing/types.ts | 2 +- .../globalSearch/src/indexing/utils.ts | 30 +- .../src/indexing/worker/vectorWorker.ts | 6 +- .../indexing/worker/vectorWorkerManager.ts | 264 +- .../src/search/vector/vectorSearch.ts | 25 +- .../src/search/vector/vectorTypes.ts | 1 - .../built-in/notificationCollector/index.ts | 49 +- src/plugins/built-in/test/index.ts | 36 +- src/plugins/built-in/themes/ThemeCreator.ts | 127 +- src/plugins/built-in/themes/index.ts | 16 +- src/plugins/built-in/themes/theme-manager.ts | 416 +- src/plugins/built-in/timetable/index.ts | 287 +- src/plugins/core/createAPI.ts | 171 +- src/plugins/core/manager.ts | 182 +- src/plugins/core/settings.ts | 12 +- src/plugins/core/settingsHelpers.ts | 46 +- src/plugins/core/types.ts | 79 +- src/plugins/index.ts | 18 +- src/plugins/monofile.ts | 491 +- src/postcss.config.cjs | 5 +- src/resources/fonts/IconFamily.css | 7 +- src/resources/fonts/IconFamilyList.html | 39942 +++++++++------- src/resources/fonts/demo-files/demo.css | 44 +- src/resources/fonts/demo-files/demo.js | 52 +- src/resources/fonts/style.css | 18 +- src/seqta/icons/assessmentsIcon.ts | 4 +- src/seqta/icons/coursesIcon.ts | 4 +- src/seqta/main.ts | 36 +- src/seqta/ui/AddBetterSEQTAElements.ts | 216 +- src/seqta/ui/ImageBackgrounds.ts | 54 +- src/seqta/ui/Loading.ts | 23 +- src/seqta/ui/SettingsResizer.ts | 13 +- src/seqta/ui/colors/ColorLuminance.ts | 33 +- src/seqta/ui/colors/Manager.ts | 75 +- src/seqta/ui/colors/getThresholdColour.ts | 72 +- src/seqta/ui/colors/lightenAndPaleColor.ts | 34 +- src/seqta/ui/dev/hideSensitiveContent.ts | 383 +- src/seqta/ui/renderStore.ts | 34 +- .../utils/Adders/AddExtensionSettings.ts | 70 +- src/seqta/utils/Adders/AddShortcuts.ts | 42 +- .../utils/Closers/closeExtensionPopup.ts | 54 +- .../CreateEnable/CreateCustomShortcutDiv.ts | 32 +- src/seqta/utils/CreateEnable/CreateElement.ts | 50 +- .../utils/DisableRemove/RemoveShortcutDiv.ts | 22 +- src/seqta/utils/FileUpload.ts | 16 +- src/seqta/utils/FilterUpcomingAssessments.ts | 42 +- src/seqta/utils/Loaders/LoadHomePage.ts | 1832 +- src/seqta/utils/Openers/OpenAboutPage.ts | 146 +- src/seqta/utils/Openers/OpenMenuOptions.ts | 256 +- src/seqta/utils/ReactFiber.ts | 56 +- src/seqta/utils/SendNewsPage.ts | 98 +- src/seqta/utils/Whatsnew.ts | 202 +- src/seqta/utils/base64ToBlob.ts | 4 +- src/seqta/utils/convertTo12HourFormat.ts | 40 +- src/seqta/utils/debounce.ts | 7 +- src/seqta/utils/imageConversions.ts | 6 +- src/seqta/utils/listeners/ClickListeners.ts | 37 +- src/seqta/utils/listeners/EventManager.ts | 56 +- src/seqta/utils/listeners/MessageListener.ts | 114 +- src/seqta/utils/listeners/SettingsState.ts | 20 +- src/seqta/utils/listeners/StorageChanges.ts | 57 +- src/seqta/utils/migration/migrate.html | 2 +- src/seqta/utils/migration/migration-iframe.ts | 108 +- src/seqta/utils/mutex.ts | 4 +- src/seqta/utils/sendThemeUpdate.ts | 2 +- src/seqta/utils/setupSettingsButton.ts | 62 +- src/seqta/utils/stringToHTML.ts | 14 +- src/seqta/utils/waitForElm.ts | 142 +- src/svelte.config.js | 4 +- src/types/CustomThemes.ts | 8 +- src/types/storage.ts | 2 +- tailwind.config.js | 31 +- tsconfig.json | 13 +- vite.config.ts | 90 +- 142 files changed, 28768 insertions(+), 20790 deletions(-) diff --git a/.dependency-cruiser.cjs b/.dependency-cruiser.cjs index bfd1a67d..6d8d3fb6 100644 --- a/.dependency-cruiser.cjs +++ b/.dependency-cruiser.cjs @@ -2,87 +2,83 @@ module.exports = { forbidden: [ { - name: 'no-circular', - severity: 'warn', + name: "no-circular", + severity: "warn", comment: - 'This dependency is part of a circular relationship. You might want to revise ' + - 'your solution (i.e. use dependency inversion, make sure the modules have a single responsibility) ', + "This dependency is part of a circular relationship. You might want to revise " + + "your solution (i.e. use dependency inversion, make sure the modules have a single responsibility) ", from: {}, to: { - circular: true - } + circular: true, + }, }, { - name: 'no-orphans', + name: "no-orphans", comment: "This is an orphan module - it's likely not used (anymore?). Either use it or " + "remove it. If it's logical this module is an orphan (i.e. it's a config file), " + "add an exception for it in your dependency-cruiser configuration. By default " + "this rule does not scrutinize dot-files (e.g. .eslintrc.js), TypeScript declaration " + "files (.d.ts), tsconfig.json and some of the babel and webpack configs.", - severity: 'warn', + severity: "warn", from: { orphan: true, pathNot: [ - '(^|/)[.][^/]+[.](?:js|cjs|mjs|ts|cts|mts|json)$', // dot files - '[.]d[.]ts$', // TypeScript declaration files - '(^|/)tsconfig[.]json$', // TypeScript config - '(^|/)(?:babel|webpack)[.]config[.](?:js|cjs|mjs|ts|cts|mts|json)$' // other configs - ] + "(^|/)[.][^/]+[.](?:js|cjs|mjs|ts|cts|mts|json)$", // dot files + "[.]d[.]ts$", // TypeScript declaration files + "(^|/)tsconfig[.]json$", // TypeScript config + "(^|/)(?:babel|webpack)[.]config[.](?:js|cjs|mjs|ts|cts|mts|json)$", // other configs + ], }, to: {}, }, { - name: 'no-deprecated-core', + name: "no-deprecated-core", comment: - 'A module depends on a node core module that has been deprecated. Find an alternative - these are ' + + "A module depends on a node core module that has been deprecated. Find an alternative - these are " + "bound to exist - node doesn't deprecate lightly.", - severity: 'warn', + severity: "warn", from: {}, to: { - dependencyTypes: [ - 'core' - ], + dependencyTypes: ["core"], path: [ - '^v8/tools/codemap$', - '^v8/tools/consarray$', - '^v8/tools/csvparser$', - '^v8/tools/logreader$', - '^v8/tools/profile_view$', - '^v8/tools/profile$', - '^v8/tools/SourceMap$', - '^v8/tools/splaytree$', - '^v8/tools/tickprocessor-driver$', - '^v8/tools/tickprocessor$', - '^node-inspect/lib/_inspect$', - '^node-inspect/lib/internal/inspect_client$', - '^node-inspect/lib/internal/inspect_repl$', - '^async_hooks$', - '^punycode$', - '^domain$', - '^constants$', - '^sys$', - '^_linklist$', - '^_stream_wrap$' + "^v8/tools/codemap$", + "^v8/tools/consarray$", + "^v8/tools/csvparser$", + "^v8/tools/logreader$", + "^v8/tools/profile_view$", + "^v8/tools/profile$", + "^v8/tools/SourceMap$", + "^v8/tools/splaytree$", + "^v8/tools/tickprocessor-driver$", + "^v8/tools/tickprocessor$", + "^node-inspect/lib/_inspect$", + "^node-inspect/lib/internal/inspect_client$", + "^node-inspect/lib/internal/inspect_repl$", + "^async_hooks$", + "^punycode$", + "^domain$", + "^constants$", + "^sys$", + "^_linklist$", + "^_stream_wrap$", ], - } + }, }, { - name: 'not-to-deprecated', + name: "not-to-deprecated", comment: - 'This module uses a (version of an) npm module that has been deprecated. Either upgrade to a later ' + - 'version of that module, or find an alternative. Deprecated modules are a security risk.', - severity: 'warn', + "This module uses a (version of an) npm module that has been deprecated. Either upgrade to a later " + + "version of that module, or find an alternative. Deprecated modules are a security risk.", + severity: "warn", from: {}, to: { - dependencyTypes: [ - 'deprecated' - ] - } + dependencyTypes: ["deprecated"], + }, }, { - name: 'no-non-package-json', - severity: 'error', + name: "no-non-package-json", + severity: "error", comment: "This module depends on an npm package that isn't in the 'dependencies' section of your package.json. " + "That's problematic as the package either (1) won't be available on live (2 - worse) will be " + @@ -90,84 +86,75 @@ module.exports = { "in your package.json.", from: {}, to: { - dependencyTypes: [ - 'npm-no-pkg', - 'npm-unknown' - ] - } + dependencyTypes: ["npm-no-pkg", "npm-unknown"], + }, }, { - name: 'not-to-unresolvable', + name: "not-to-unresolvable", comment: "This module depends on a module that cannot be found ('resolved to disk'). If it's an npm " + - 'module: add it to your package.json. In all other cases you likely already know what to do.', - severity: 'error', + "module: add it to your package.json. In all other cases you likely already know what to do.", + severity: "error", from: {}, to: { - couldNotResolve: true - } + couldNotResolve: true, + }, }, { - name: 'no-duplicate-dep-types', + name: "no-duplicate-dep-types", comment: "Likely this module depends on an external ('npm') package that occurs more than once " + "in your package.json i.e. bot as a devDependencies and in dependencies. This will cause " + "maintenance problems later on.", - severity: 'warn', + severity: "warn", from: {}, to: { moreThanOneDependencyType: true, - // as it's pretty common to have a type import be a type only import + // as it's pretty common to have a type import be a type only import // _and_ (e.g.) a devDependency - don't consider type-only dependency // types for this rule - dependencyTypesNot: ["type-only"] - } + dependencyTypesNot: ["type-only"], + }, }, /* rules you might want to tweak for your specific situation: */ - + { - name: 'not-to-spec', + name: "not-to-spec", comment: - 'This module depends on a spec (test) file. The sole responsibility of a spec file is to test code. ' + + "This module depends on a spec (test) file. The sole responsibility of a spec file is to test code. " + "If there's something in a spec that's of use to other modules, it doesn't have that single " + - 'responsibility anymore. Factor it out into (e.g.) a separate utility/ helper or a mock.', - severity: 'error', + "responsibility anymore. Factor it out into (e.g.) a separate utility/ helper or a mock.", + severity: "error", from: {}, to: { - path: '[.](?:spec|test)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$' - } + path: "[.](?:spec|test)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$", + }, }, { - name: 'not-to-dev-dep', - severity: 'error', + name: "not-to-dev-dep", + severity: "error", comment: "This module depends on an npm package from the 'devDependencies' section of your " + - 'package.json. It looks like something that ships to production, though. To prevent problems ' + + "package.json. It looks like something that ships to production, though. To prevent problems " + "with npm packages that aren't there on production declare it (only!) in the 'dependencies'" + - 'section of your package.json. If this module is development only - add it to the ' + - 'from.pathNot re of the not-to-dev-dep rule in the dependency-cruiser configuration', + "section of your package.json. If this module is development only - add it to the " + + "from.pathNot re of the not-to-dev-dep rule in the dependency-cruiser configuration", from: { - path: '^(src)', - pathNot: '[.](?:spec|test)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$' + path: "^(src)", + pathNot: "[.](?:spec|test)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$", }, to: { - dependencyTypes: [ - 'npm-dev', - ], + dependencyTypes: ["npm-dev"], // type only dependencies are not a problem as they don't end up in the // production code or are ignored by the runtime. - dependencyTypesNot: [ - 'type-only' - ], - pathNot: [ - 'node_modules/@types/' - ] - } + dependencyTypesNot: ["type-only"], + pathNot: ["node_modules/@types/"], + }, }, { - name: 'optional-deps-used', - severity: 'info', + name: "optional-deps-used", + severity: "info", comment: "This module depends on an npm package that is declared as an optional dependency " + "in your package.json. As this makes sense in limited situations only, it's flagged here. " + @@ -175,33 +162,28 @@ module.exports = { "dependency-cruiser configuration.", from: {}, to: { - dependencyTypes: [ - 'npm-optional' - ] - } + dependencyTypes: ["npm-optional"], + }, }, { - name: 'peer-deps-used', + name: "peer-deps-used", comment: "This module depends on an npm package that is declared as a peer dependency " + "in your package.json. This makes sense if your package is e.g. a plugin, but in " + "other cases - maybe not so much. If the use of a peer dependency is intentional " + "add an exception to your dependency-cruiser configuration.", - severity: 'warn', + severity: "warn", from: {}, to: { - dependencyTypes: [ - 'npm-peer' - ] - } - } + dependencyTypes: ["npm-peer"], + }, + }, ], options: { - /* Which modules not to follow further when encountered */ doNotFollow: { /* path: an array of regular expressions in strings to match against */ - path: ['node_modules'] + path: ["node_modules"], }, /* Which modules to exclude */ @@ -224,7 +206,7 @@ module.exports = { As in practice only commonjs ('cjs') and ecmascript modules ('es6') are widely used, you can limit the moduleSystems to those. */ - + // moduleSystems: ['cjs', 'es6'], /* @@ -249,7 +231,7 @@ module.exports = { "specify": for each dependency identify whether it only exists before compilation or also after */ tsPreCompilationDeps: true, - + /* list of extensions to scan that aren't javascript or compile-to-javascript. Empty by default. Only put extensions in here that you want to take into account that are _not_ parsable. @@ -274,7 +256,7 @@ module.exports = { defaults to './tsconfig.json'. */ tsConfig: { - fileName: 'tsconfig.json' + fileName: "tsconfig.json", }, /* Webpack configuration to use to get resolve options from. @@ -306,7 +288,7 @@ module.exports = { a hack. */ // exoticRequireStrings: [], - + /* options to pass on to enhanced-resolve, the package dependency-cruiser uses to resolve module references to disk. The values below should be suitable for most situations @@ -315,7 +297,7 @@ module.exports = { there will override the ones specified here. */ enhancedResolveOptions: { - /* What to consider as an 'exports' field in package.jsons */ + /* What to consider as an 'exports' field in package.jsons */ exportsFields: ["exports"], /* List of conditions to check for in the exports field. Only works when the 'exportsFields' array is non-empty. @@ -348,13 +330,13 @@ module.exports = { for details */ skipAnalysisNotInRules: true, - + /* List of built-in modules to use on top of the ones node declares. See https://github.com/sverweij/dependency-cruiser/blob/main/doc/options-reference.md#builtinmodules-influencing-what-to-consider-built-in--core-modules for details */ - builtInModules: { + builtInModules: { add: [ "bun", "bun:ffi", @@ -364,8 +346,8 @@ module.exports = { "bun:wrap", "detect-libc", "undici", - "ws" - ] + "ws", + ], }, reporterOptions: { @@ -375,7 +357,7 @@ module.exports = { collapses everything in node_modules to one folder deep so you see the external modules, but their innards. */ - collapsePattern: 'node_modules/(?:@[^/]+/[^/]+|[^/]+)', + collapsePattern: "node_modules/(?:@[^/]+/[^/]+|[^/]+)", /* Options to tweak the appearance of your graph.See https://github.com/sverweij/dependency-cruiser/blob/main/doc/options-reference.md#reporteroptions @@ -397,7 +379,8 @@ module.exports = { dependency graph reporter (`archi`) you probably want to tweak this collapsePattern to your situation. */ - collapsePattern: '^(?:packages|src|lib(s?)|app(s?)|bin|test(s?)|spec(s?))/[^/]+|node_modules/(?:@[^/]+/[^/]+|[^/]+)', + collapsePattern: + "^(?:packages|src|lib(s?)|app(s?)|bin|test(s?)|spec(s?))/[^/]+|node_modules/(?:@[^/]+/[^/]+|[^/]+)", /* Options to tweak the appearance of your graph. If you don't specify a theme for 'archi' dependency-cruiser will use the one specified in the @@ -405,10 +388,10 @@ module.exports = { */ // theme: { }, }, - "text": { - "highlightFocused": true + text: { + highlightFocused: true, }, - } - } + }, + }, }; // generated: dependency-cruiser@16.10.0 on 2025-02-16T22:32:01.621Z diff --git a/.eslintrc.json b/.eslintrc.json index f6cc10d1..c41b1538 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -12,12 +12,15 @@ }, "rules": { // allow importing ts extensions - "sort-imports": ["error", { - "ignoreCase": true, - "ignoreDeclarationSort": true, - "ignoreMemberSort": false, - "memberSyntaxSortOrder": ["none", "all", "multiple", "single"] - }], + "sort-imports": [ + "error", + { + "ignoreCase": true, + "ignoreDeclarationSort": true, + "ignoreMemberSort": false, + "memberSyntaxSortOrder": ["none", "all", "multiple", "single"] + } + ], "import/extensions": [ "error", "ignorePackages", @@ -29,4 +32,4 @@ ] }, "plugins": ["import"] -} \ No newline at end of file +} diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 27bc218d..dad93afc 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -3,54 +3,54 @@ description: Report an issue with the modpack in its unmodified state. For other labels: bug title: "[BUG]" body: -- type: markdown - attributes: - value: | - Before reporting an issue, [please search](https://github.com/BetterSEQTA/BetterSEQTA-Plus/issues) to make sure it has not already been reported (make sure to search closed issues as well!). + - type: markdown + attributes: + value: | + Before reporting an issue, [please search](https://github.com/BetterSEQTA/BetterSEQTA-Plus/issues) to make sure it has not already been reported (make sure to search closed issues as well!). -- type: textarea - attributes: - label: Describe the bug - description: Describe your issue. For general issues and questions you'll get a faster answer [from our Discord.](https://discord.gg/YzmbnCDkat) - validations: - required: true + - type: textarea + attributes: + label: Describe the bug + description: Describe your issue. For general issues and questions you'll get a faster answer [from our Discord.](https://discord.gg/YzmbnCDkat) + validations: + required: true -- type: input - attributes: - label: Extension version - description: What version of the extension are you using? - placeholder: Find it by opening the config menu and clicking the about icon in the top right. - validations: - required: true + - type: input + attributes: + label: Extension version + description: What version of the extension are you using? + placeholder: Find it by opening the config menu and clicking the about icon in the top right. + validations: + required: true -- type: dropdown - attributes: - label: Browser - description: Which Browser are you using? - options: - - Chrome - - Firefox - - Brave - - Safari - - DuckDuckGO - - Microsoft Edge - - Other Chromium-Based Browser - - Other Non-Chromium-Based Browser - validations: - required: true + - type: dropdown + attributes: + label: Browser + description: Which Browser are you using? + options: + - Chrome + - Firefox + - Brave + - Safari + - DuckDuckGO + - Microsoft Edge + - Other Chromium-Based Browser + - Other Non-Chromium-Based Browser + validations: + required: true -- type: checkboxes - attributes: - label: Confirm - options: - - label: This bug report is about an issue with the extension itself. I have not modified the extension nor added any unsupported plugins. If this is not the case, I know that I should post the issue to the extension's Discord support channel instead. - required: true - -- type: textarea - attributes: - label: Additional context - description: Screenshots, video or any other information. Include photos of the console if possible - placeholder: | - Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. - validations: - required: false + - type: checkboxes + attributes: + label: Confirm + options: + - label: This bug report is about an issue with the extension itself. I have not modified the extension nor added any unsupported plugins. If this is not the case, I know that I should post the issue to the extension's Discord support channel instead. + required: true + + - type: textarea + attributes: + label: Additional context + description: Screenshots, video or any other information. Include photos of the console if possible + placeholder: | + Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index d2907c21..0d4d98ae 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -3,52 +3,49 @@ description: Suggest a new Feature to be added or replaced in BetterSeqtaPLUS labels: enhancement title: "[FR]" body: + - type: checkboxes + attributes: + label: Confirm + options: + - label: "Is this feature request related to a Bug report?" + required: false -- type: checkboxes - attributes: - label: Confirm - options: - - label: "Is this feature request related to a Bug report?" - required: false - -- type: input - attributes: + - type: input + attributes: label: Bug report link description: "If this feature request is related to a bug report, please insert the link to the bug report here" placeholder: "https://github.com/BetterSEQTA/BetterSEQTA-Plus/issues/..." - validations: + validations: required: false -- type: markdown - attributes: - value: | - ## Feature details - Before you request a feature, [please search](https://github.com/BetterSEQTA/BetterSEQTA-Plus/issues) if it has already been requested. (Make sure to check closed issues as well!) + - type: markdown + attributes: + value: | + ## Feature details + Before you request a feature, [please search](https://github.com/BetterSEQTA/BetterSEQTA-Plus/issues) if it has already been requested. (Make sure to check closed issues as well!) - -- type: dropdown - attributes: - label: Feature type - multiple: false - options: - - Graphical - - Functional - - Not Sure - validations: - required: true - + - type: dropdown + attributes: + label: Feature type + multiple: false + options: + - Graphical + - Functional + - Not Sure + validations: + required: true -- type: input - attributes: - label: Feature Details - description: Please write, with as much detail as possible, what you would like to see from this feature. - placeholder: it would be cool if - validations: - required: false + - type: input + attributes: + label: Feature Details + description: Please write, with as much detail as possible, what you would like to see from this feature. + placeholder: it would be cool if + validations: + required: false -- type: textarea - attributes: - label: Additional details - description: Anything else that would help describe your vision (reference images, descriptions, etc) - validations: - required: false + - type: textarea + attributes: + label: Additional details + description: Anything else that would help describe your vision (reference images, descriptions, etc) + validations: + required: false diff --git a/.github/workflows/mvp.yml b/.github/workflows/mvp.yml index cccf8204..d2e7bc14 100644 --- a/.github/workflows/mvp.yml +++ b/.github/workflows/mvp.yml @@ -2,9 +2,9 @@ name: NodeJS Build on: push: - branches: [ "main" ] + branches: ["main"] pull_request: - branches: [ "main" ] + branches: ["main"] jobs: build: @@ -15,24 +15,24 @@ jobs: node-version: [20.x] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v4 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} - - name: Build - run: | - npm install --legacy-peer-deps - npm run build + - name: Build + run: | + npm install --legacy-peer-deps + npm run build - - name: Zip dist folder - run: | - zip -r dist.zip dist + - name: Zip dist folder + run: | + zip -r dist.zip dist - - name: Upload artifact - uses: actions/upload-artifact@v4 - with: - name: dist-zip - path: dist.zip \ No newline at end of file + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: dist-zip + path: dist.zip diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 51405645..60c06fef 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -17,23 +17,23 @@ diverse, inclusive, and healthy community. Examples of behavior that contributes to a positive environment for our community include: -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience -* Focusing on what is best not just for us as individuals, but for the +- Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: -* The use of sexualized language or imagery, and sexual attention or +- The use of sexualized language or imagery, and sexual attention or advances of any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email address, without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a +- Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities @@ -106,7 +106,7 @@ Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an +standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within diff --git a/README.md b/README.md index 5f6faca1..2d2e69e2 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ - -# +# @@ -53,7 +52,7 @@ ## Creating Custom Themes -If you are looking to create custom themes, I would recommend you start at the official documentation [here](https://betterseqta.gitbook.io/betterseqta-docs). You can see some premade examples along with a compilation script that can be used to allow for CSS frameworks and libraries such as SCSS to be used [here](https://github.com/BetterSEQTA/BetterSEQTA-Theme-Generator). +If you are looking to create custom themes, I would recommend you start at the official documentation [here](https://betterseqta.gitbook.io/betterseqta-docs). You can see some premade examples along with a compilation script that can be used to allow for CSS frameworks and libraries such as SCSS to be used [here](https://github.com/BetterSEQTA/BetterSEQTA-Theme-Generator). Don't worry- if you get stuck feel free to ask around in the [discord](https://discord.gg/YzmbnCDkat). We're open and happy to help out! Happy creating :) @@ -65,8 +64,6 @@ Don't worry- if you get stuck feel free to ask around in the [discord](https://d git clone https://github.com/BetterSEQTA/BetterSEQTA-Plus ``` - - 1. Install dependencies You may install the dependencies like below: @@ -75,20 +72,20 @@ You may install the dependencies like below: npm install # or your preferred package manager like pnpm or yarn ``` -But it is recommended to do it like this: +But it is recommended to do it like this: ``` npm install --legacy-peer-deps # Only NPM supported ``` + ### Running Development + 2. Run the dev script (it updates as you save files) ``` npm run dev # or use your perferred package manager ``` - - ### Building for production 2. Run the build script @@ -102,6 +99,7 @@ npm run build # or use your perferred package manager ``` npm run zip # This REQUIRES 7-Zip to be installed in order to work. You can also use your perferred package manager ``` + 3. Load the extension into chrome - Go to `chrome://extensions` @@ -116,7 +114,7 @@ Just remember, in order to update changes to the extension if you are running in The folder structure is as follows: - The `src` folder contains source files that are compiled to the build directory. -- +- - The `src/plugins` folder contains vital loaders required for BetterSEQTA+ functionality. - The `src/interface` folder contains source React & Svelte files that are required for the Settings page. @@ -130,6 +128,7 @@ The folder structure is as follows: Want to contribute? [Click Here!](https://github.com/BetterSEQTA/BetterSEQTA-Plus/blob/main/CONTRIBUTING.md) + ## Credits This extension was initially developed by [Nulkem](https://github.com/Nulkem/betterseqta), was ported to manifest V3 by [MEGA-Dawg68](https://github.com/MEGA-Dawg68) and is currently under active development by [SethBurkart123](https://github.com/SethBurkart123) and [Crazypersonalph](https://github.com/Crazypersonalph) diff --git a/SECURITY.md b/SECURITY.md index 95316bd1..f884bbf3 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -4,12 +4,13 @@ Below here is the supported versions of BetterSEQTA+. Anything older than this is not supported and contains bugs. -| Version | Supported | -| ------- | ------------------ | -| 3.4.3 | ✅ | -| < 3.4.3 | :x: | +| Version | Supported | +| ------- | --------- | +| 3.4.3 | ✅ | +| < 3.4.3 | :x: | `*` May not work on other devices. + ## Reporting a Vulnerability If you find vulnerabilities, REPORT IT IMMEDIATELY. open the [advisories tab](https://github.com/BetterSEQTA/BetterSEQTA-Plus/security/advisories) on the left and click the green "report a vulnerability" button or use [this quick-link](https://github.com/BetterSEQTA/BetterSEQTA-Plus/security/advisories/new) to create a new report diff --git a/docs/README.md b/docs/README.md index 11d005e9..40628322 100644 --- a/docs/README.md +++ b/docs/README.md @@ -7,11 +7,13 @@ Welcome to the BetterSEQTA+ documentation! This documentation will help you unde ## Table of Contents ### Getting Started + - [Project Overview](./README.md) - This file - [Installation Guide](./installation.md) - How to install and set up BetterSEQTA+ - [Contributing Guide](../CONTRIBUTING.md) - How to contribute to BetterSEQTA+ ### Plugin System + - [Creating Your First Plugin](./plugins/README.md) - A comprehensive, beginner-friendly guide to creating plugins - [Plugin API Reference](./plugins/api-reference.md) - Detailed technical documentation of the plugin APIs @@ -47,4 +49,4 @@ To contribute to the documentation: ## License -BetterSEQTA+ is licensed under the [MIT License](../LICENSE). \ No newline at end of file +BetterSEQTA+ is licensed under the [MIT License](../LICENSE). diff --git a/docs/contributing.md b/docs/contributing.md index 8a624a9e..4f5334e3 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -22,6 +22,7 @@ Thank you for your interest in contributing to BetterSEQTA+! This document provi BetterSEQTA+ is committed to providing a welcoming and inclusive environment for all contributors. We expect all participants to adhere to our Code of Conduct, which promotes respectful and harassment-free interaction. Key points: + - Be respectful and inclusive - Focus on what is best for the community - Show empathy towards other community members @@ -105,6 +106,7 @@ git checkout -b feature/my-new-feature 2. **Write Clear Commit Messages** Follow the conventional commits format: + ``` feat: add new feature fix: resolve bug with timetable @@ -118,6 +120,7 @@ git checkout -b feature/my-new-feature 4. **Run Tests** Make sure all tests pass before submitting your PR: + ```bash npm test ``` @@ -157,6 +160,7 @@ We follow TypeScript best practices and have a consistent code style: 5. **Use Linters** We use ESLint and Prettier. Run them before submitting your PR: + ```bash npm run lint npm run format @@ -173,6 +177,7 @@ If you find a bug, please report it by creating an issue on GitHub: 2. **Use the Bug Report Template** Fill in all sections of the bug report template: + - Description - Steps to reproduce - Expected behavior @@ -195,6 +200,7 @@ We welcome feature suggestions! To suggest a new feature: 2. **Use the Feature Request Template** Fill in all sections of the feature request template: + - Description - Use case - Potential implementation @@ -259,4 +265,4 @@ If you have any questions about contributing, please: 2. Ask in the Discord server 3. Open a GitHub Discussion -Thank you for contributing to BetterSEQTA+! Your efforts help make SEQTA better for students and teachers everywhere. \ No newline at end of file +Thank you for contributing to BetterSEQTA+! Your efforts help make SEQTA better for students and teachers everywhere. diff --git a/docs/installation.md b/docs/installation.md index 10404c68..0e5c8514 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -132,6 +132,7 @@ bun install #### Extension not appearing in SEQTA Make sure: + - You're visiting a SEQTA Learn page - The extension is enabled - You've refreshed the page after installing the extension @@ -139,6 +140,7 @@ Make sure: #### Development build not updating Try: + 1. Stopping the development server 2. Clearing your browser cache 3. Removing the extension from your browser @@ -177,4 +179,4 @@ bun run dev Now that you have BetterSEQTA+ installed, you can: - [Getting Started with Plugins](./plugins/getting-started.md) -- [Contribute to the project](../CONTRIBUTING.md) \ No newline at end of file +- [Contribute to the project](../CONTRIBUTING.md) diff --git a/docs/plugins/README.md b/docs/plugins/README.md index 1af78b4e..23e9ce68 100644 --- a/docs/plugins/README.md +++ b/docs/plugins/README.md @@ -5,6 +5,7 @@ Hey there! 👋 So you want to create a plugin for BetterSEQTA+? That's awesome! ## What is a Plugin? In BetterSEQTA+, a plugin is like a mini-app that adds new features to SEQTA. Think of it as a piece of LEGO that you can snap onto SEQTA to make it do new things. For example, you could create a plugin that: + - Changes how SEQTA looks - Adds new buttons or features - Shows extra information on your timetable @@ -16,40 +17,40 @@ In BetterSEQTA+, a plugin is like a mini-app that adds new features to SEQTA. Th Let's create a super simple plugin together. We'll make one that adds a friendly message to the SEQTA homepage. Here's what we'll need: ```typescript -import type { Plugin } from '@/plugins/core/types'; +import type { Plugin } from "@/plugins/core/types"; const myFirstPlugin: Plugin = { // Every plugin needs these basic details - id: 'my-first-plugin', - name: 'My First Plugin', - description: 'Adds a friendly message to SEQTA', - version: '1.0.0', - + id: "my-first-plugin", + name: "My First Plugin", + description: "Adds a friendly message to SEQTA", + version: "1.0.0", + // This tells BetterSEQTA+ that users can turn our plugin on/off disableToggle: true, - - // This is where the magic happens! + + // This is where the magic happens! run: async (api) => { // Wait for the homepage to load - api.seqta.onMount('.home-page', (homePage) => { + api.seqta.onMount(".home-page", (homePage) => { // Create our message - const message = document.createElement('div'); - message.textContent = 'Hello from my first plugin! 🎉'; - message.style.padding = '20px'; - message.style.backgroundColor = '#e9f5ff'; - message.style.borderRadius = '8px'; - message.style.margin = '20px'; - + const message = document.createElement("div"); + message.textContent = "Hello from my first plugin! 🎉"; + message.style.padding = "20px"; + message.style.backgroundColor = "#e9f5ff"; + message.style.borderRadius = "8px"; + message.style.margin = "20px"; + // Add it to the page homePage.prepend(message); }); - + // Return a cleanup function that removes our message when the plugin is disabled return () => { - const message = document.querySelector('.home-page > div'); + const message = document.querySelector(".home-page > div"); message?.remove(); }; - } + }, }; export default myFirstPlugin; @@ -79,13 +80,13 @@ This helps you interact with SEQTA's pages: ```typescript // Wait for an element to appear on the page -api.seqta.onMount('.some-class', (element) => { +api.seqta.onMount(".some-class", (element) => { // Do something with the element }); // Know when the user changes pages api.seqta.onPageChange((page) => { - console.log('User went to:', page); + console.log("User went to:", page); }); // Get the current page @@ -97,8 +98,12 @@ const currentPage = api.seqta.getCurrentPage(); Want to let users customize your plugin? Use settings! ```typescript -import { BasePlugin } from '@/plugins/core/settings'; -import { booleanSetting, defineSettings, Setting } from '@/plugins/core/settingsHelpers'; +import { BasePlugin } from "@/plugins/core/settings"; +import { + booleanSetting, + defineSettings, + Setting, +} from "@/plugins/core/settingsHelpers"; // Define your settings const settings = defineSettings({ @@ -106,7 +111,7 @@ const settings = defineSettings({ default: true, title: "Show Welcome Message", description: "Show a friendly message on the homepage", - }) + }), }); // Create a class for your plugin @@ -121,22 +126,22 @@ const settingsInstance = new MyPluginClass(); const myPlugin: Plugin = { // ... other plugin details ... settings: settingsInstance.settings, - + run: async (api) => { // Use the setting if (api.settings.showMessage) { // Show the message } - + // Listen for setting changes - api.settings.onChange('showMessage', (newValue) => { + api.settings.onChange("showMessage", (newValue) => { if (newValue) { // Show the message } else { // Hide the message } }); - } + }, }; ``` @@ -146,14 +151,14 @@ Need to save some data? The storage API has got you covered: ```typescript // Save some data -await api.storage.set('lastVisit', new Date().toISOString()); +await api.storage.set("lastVisit", new Date().toISOString()); // Get it back later -const lastVisit = await api.storage.get('lastVisit'); +const lastVisit = await api.storage.get("lastVisit"); // Listen for changes -api.storage.onChange('lastVisit', (newValue) => { - console.log('Last visit updated:', newValue); +api.storage.onChange("lastVisit", (newValue) => { + console.log("Last visit updated:", newValue); }); ``` @@ -163,12 +168,12 @@ Want your plugin to be able to interface with other plugins? Then use events! ```typescript // Listen for an event -api.events.on('myCustomEvent', (data) => { - console.log('Got event:', data); +api.events.on("myCustomEvent", (data) => { + console.log("Got event:", data); }); // Send an event -api.events.emit('myCustomEvent', { some: 'data' }); +api.events.emit("myCustomEvent", { some: "data" }); ``` ## Adding Styles @@ -178,7 +183,7 @@ Want to make your plugin look pretty? You can add CSS styles: ```typescript const myPlugin: Plugin = { // ... other plugin details ... - + // Add your CSS here styles: ` .my-plugin-message { @@ -196,10 +201,10 @@ const myPlugin: Plugin = { to { transform: translateY(0); opacity: 1; } } `, - + run: async (api) => { // Your plugin code here - } + }, }; ``` @@ -208,28 +213,31 @@ const myPlugin: Plugin = { Here are some tips to make your plugin awesome: 1. **Always Clean Up**: When your plugin is disabled, clean up any changes you made: + ```typescript run: async (api) => { // Add stuff to the page - const element = document.createElement('div'); + const element = document.createElement("div"); document.body.appendChild(element); - + // Return a cleanup function return () => { element.remove(); }; - } + }; ``` 2. **Use TypeScript**: It helps catch errors before they happen and makes your code easier to understand. 3. **Test Your Plugin**: Make sure it works in different situations: + - When SEQTA is loading - When the user switches pages - When the plugin is enabled/disabled - When settings are changed 4. **Keep It Fast**: Don't slow down SEQTA: + - Use `onMount` instead of intervals or timeouts - Clean up event listeners when they're not needed - Don't do heavy calculations on the main thread @@ -242,6 +250,7 @@ Here are some tips to make your plugin awesome: ## Examples Want to see more examples? Check out our built-in plugins: + - [themes](../../src/plugins/built-in/themes/index.ts): Shows how to change SEQTA's appearance - [notificationCollector](../../src/plugins/built-in/notificationCollector/index.ts): Shows how to work with SEQTA's notifications - [timetable](../../src/plugins/built-in/timetable/index.ts): Shows how to modify SEQTA's timetable view @@ -250,8 +259,9 @@ Want to see more examples? Check out our built-in plugins: ## Need Help? Got stuck? No worries! Here's where you can get help: + - Join our [Discord server](https://discord.gg/YzmbnCDkat) - Check out the built-in plugins in the `src/plugins/built-in` folder - Open an issue on our [GitHub page](https://github.com/betterseqta/betterseqta-plus/issues) -Happy coding and feel free to checkout the api reference [here](./api-reference.md) \ No newline at end of file +Happy coding and feel free to checkout the api reference [here](./api-reference.md) diff --git a/docs/plugins/api-reference.md b/docs/plugins/api-reference.md index c90c3529..c2613205 100644 --- a/docs/plugins/api-reference.md +++ b/docs/plugins/api-reference.md @@ -7,9 +7,13 @@ This document provides detailed technical information about BetterSEQTA+'s plugi Here's how a plugin is structured: ```typescript -import type { Plugin } from '@/plugins/core/types'; -import { BasePlugin } from '@/plugins/core/settings'; -import { booleanSetting, defineSettings, Setting } from '@/plugins/core/settingsHelpers'; +import type { Plugin } from "@/plugins/core/types"; +import { BasePlugin } from "@/plugins/core/settings"; +import { + booleanSetting, + defineSettings, + Setting, +} from "@/plugins/core/settingsHelpers"; // First, define your settings const settings = defineSettings({ @@ -17,7 +21,7 @@ const settings = defineSettings({ default: true, title: "Enable Feature", description: "Turn this feature on or off", - }) + }), }); // Create a class to handle your settings @@ -31,28 +35,28 @@ const settingsInstance = new MyPluginClass(); // Create your plugin const myPlugin: Plugin = { - id: 'my-plugin', - name: 'My Plugin', - description: 'A cool plugin that does things', - version: '1.0.0', + id: "my-plugin", + name: "My Plugin", + description: "A cool plugin that does things", + version: "1.0.0", settings: settingsInstance.settings, disableToggle: true, run: async (api) => { - console.log('Plugin is running!'); - + console.log("Plugin is running!"); + // Do stuff when settings change - api.settings.onChange('enabled', (enabled) => { + api.settings.onChange("enabled", (enabled) => { if (enabled) { - console.log('Feature enabled!'); + console.log("Feature enabled!"); } }); // Return a cleanup function return () => { - console.log('Plugin cleanup'); + console.log("Plugin cleanup"); }; - } + }, }; export default myPlugin; @@ -63,27 +67,30 @@ export default myPlugin; The SEQTA API helps you interact with SEQTA's pages: ```typescript -import type { Plugin } from '@/plugins/core/types'; +import type { Plugin } from "@/plugins/core/types"; const seqtaPlugin: Plugin = { - id: 'seqta-example', - name: 'SEQTA Example', - description: 'Shows how to use the SEQTA API', - version: '1.0.0', + id: "seqta-example", + name: "SEQTA Example", + description: "Shows how to use the SEQTA API", + version: "1.0.0", settings: {}, disableToggle: true, run: async (api) => { // Wait for elements to appear - const { unregister: timetableUnregister } = api.seqta.onMount('.timetable', (timetable) => { - const button = document.createElement('button'); - button.textContent = 'Export'; - timetable.appendChild(button); - }); + const { unregister: timetableUnregister } = api.seqta.onMount( + ".timetable", + (timetable) => { + const button = document.createElement("button"); + button.textContent = "Export"; + timetable.appendChild(button); + }, + ); // Track page changes const { unregister: pageUnregister } = api.seqta.onPageChange((page) => { - console.log('User went to:', page); + console.log("User went to:", page); }); // Clean up when disabled @@ -91,7 +98,7 @@ const seqtaPlugin: Plugin = { timetableUnregister(); pageUnregister(); }; - } + }, }; export default seqtaPlugin; @@ -102,22 +109,29 @@ export default seqtaPlugin; Here's how to add settings to your plugin: ```typescript -import type { Plugin } from '@/plugins/core/types'; -import { BasePlugin } from '@/plugins/core/settings'; -import { booleanSetting, stringSetting, numberSetting, selectSetting, defineSettings, Setting } from '@/plugins/core/settingsHelpers'; +import type { Plugin } from "@/plugins/core/types"; +import { BasePlugin } from "@/plugins/core/settings"; +import { + booleanSetting, + stringSetting, + numberSetting, + selectSetting, + defineSettings, + Setting, +} from "@/plugins/core/settingsHelpers"; // Define your settings const settings = defineSettings({ darkMode: booleanSetting({ default: false, title: "Dark Mode", - description: "Enable dark mode" + description: "Enable dark mode", }), userName: stringSetting({ default: "", title: "User Name", description: "Your display name", - placeholder: "Enter your name..." + placeholder: "Enter your name...", }), theme: selectSetting({ default: "light", @@ -125,9 +139,9 @@ const settings = defineSettings({ description: "Choose your theme", options: [ { value: "light", label: "Light" }, - { value: "dark", label: "Dark" } - ] - }) + { value: "dark", label: "Dark" }, + ], + }), }); // Create your settings class @@ -144,29 +158,29 @@ class ThemePluginClass extends BasePlugin { // Create the plugin const themePlugin: Plugin = { - id: 'theme-example', - name: 'Theme Example', - description: 'Shows how to use settings', - version: '1.0.0', + id: "theme-example", + name: "Theme Example", + description: "Shows how to use settings", + version: "1.0.0", settings: new ThemePluginClass().settings, disableToggle: true, run: async (api) => { // Apply initial settings if (api.settings.darkMode) { - document.body.classList.add('dark'); + document.body.classList.add("dark"); } // Listen for changes - const { unregister } = api.settings.onChange('darkMode', (enabled) => { - document.body.classList.toggle('dark', enabled); + const { unregister } = api.settings.onChange("darkMode", (enabled) => { + document.body.classList.toggle("dark", enabled); }); return () => { unregister(); - document.body.classList.remove('dark'); + document.body.classList.remove("dark"); }; - } + }, }; export default themePlugin; @@ -177,13 +191,13 @@ export default themePlugin; Here's how to use storage in your plugin: ```typescript -import type { Plugin } from '@/plugins/core/types'; +import type { Plugin } from "@/plugins/core/types"; const storagePlugin: Plugin = { - id: 'storage-example', - name: 'Storage Example', - description: 'Shows how to use storage', - version: '1.0.0', + id: "storage-example", + name: "Storage Example", + description: "Shows how to use storage", + version: "1.0.0", settings: {}, disableToggle: true, @@ -192,21 +206,21 @@ const storagePlugin: Plugin = { await api.storage.loaded; // Save some data - await api.storage.set('lastVisit', new Date().toISOString()); - + await api.storage.set("lastVisit", new Date().toISOString()); + // Get saved data - const lastVisit = await api.storage.get('lastVisit'); - console.log('Last visit:', lastVisit); + const lastVisit = await api.storage.get("lastVisit"); + console.log("Last visit:", lastVisit); // Listen for changes - const { unregister } = api.storage.onChange('lastVisit', (newValue) => { - console.log('Last visit updated:', newValue); + const { unregister } = api.storage.onChange("lastVisit", (newValue) => { + console.log("Last visit updated:", newValue); }); return () => { unregister(); }; - } + }, }; export default storagePlugin; @@ -217,33 +231,39 @@ export default storagePlugin; Here's how to use events in your plugin: ```typescript -import type { Plugin } from '@/plugins/core/types'; +import type { Plugin } from "@/plugins/core/types"; const eventsPlugin: Plugin = { - id: 'events-example', - name: 'Events Example', - description: 'Shows how to use events', - version: '1.0.0', + id: "events-example", + name: "Events Example", + description: "Shows how to use events", + version: "1.0.0", settings: {}, disableToggle: true, run: async (api) => { // Listen for theme changes - const { unregister: themeListener } = api.events.on('theme.changed', (theme) => { - console.log('Theme changed to:', theme); - }); + const { unregister: themeListener } = api.events.on( + "theme.changed", + (theme) => { + console.log("Theme changed to:", theme); + }, + ); // Listen for notifications - const { unregister: notifyListener } = api.events.on('notification.new', (notification) => { - console.log('New notification:', notification); - }); + const { unregister: notifyListener } = api.events.on( + "notification.new", + (notification) => { + console.log("New notification:", notification); + }, + ); // Clean up listeners return () => { themeListener(); notifyListener(); }; - } + }, }; export default eventsPlugin; @@ -254,20 +274,20 @@ export default eventsPlugin; Here's how to write efficient plugins: ```typescript -import type { Plugin } from '@/plugins/core/types'; +import type { Plugin } from "@/plugins/core/types"; const efficientPlugin: Plugin = { - id: 'efficient-example', - name: 'Efficient Example', - description: 'Shows performance best practices', - version: '1.0.0', + id: "efficient-example", + name: "Efficient Example", + description: "Shows performance best practices", + version: "1.0.0", settings: {}, disableToggle: true, run: async (api) => { // ✅ Good: Use onMount - const { unregister } = api.seqta.onMount('.timetable', (el) => { - el.classList.add('enhanced'); + const { unregister } = api.seqta.onMount(".timetable", (el) => { + el.classList.add("enhanced"); }); // ❌ Bad: Don't use intervals @@ -277,7 +297,7 @@ const efficientPlugin: Plugin = { // }, 100); // ✅ Good: Cache DOM elements - const header = document.querySelector('.header'); + const header = document.querySelector(".header"); if (header) { // Reuse header instead of querying again } @@ -285,7 +305,7 @@ const efficientPlugin: Plugin = { // ✅ Good: Batch DOM updates const fragment = document.createDocumentFragment(); for (let i = 0; i < 10; i++) { - const div = document.createElement('div'); + const div = document.createElement("div"); fragment.appendChild(div); } document.body.appendChild(fragment); @@ -294,13 +314,14 @@ const efficientPlugin: Plugin = { unregister(); // clearInterval(interval); // If you used the bad approach }; - } + }, }; export default efficientPlugin; ``` Each plugin should be in its own file and exported as the default export. The plugin should: + 1. Import necessary types and helpers 2. Define settings if needed 3. Create a settings class if using settings @@ -308,7 +329,8 @@ Each plugin should be in its own file and exported as the default export. The pl 5. Export the plugin as default Remember to always: + - Use proper TypeScript types - Clean up when your plugin is disabled - Handle errors gracefully -- Follow the plugin structure shown above \ No newline at end of file +- Follow the plugin structure shown above diff --git a/lib/base64loader.ts b/lib/base64loader.ts index b62a7eef..6ab172a2 100644 --- a/lib/base64loader.ts +++ b/lib/base64loader.ts @@ -7,10 +7,10 @@ export const base64Loader = { const [filePath, query] = id.split("?"); if (query !== "base64") return null; - const data = fs.readFileSync(filePath, { encoding: 'base64' }); + const data = fs.readFileSync(filePath, { encoding: "base64" }); const mimeType = mime.lookup(filePath); const dataURL = `data:${mimeType};base64,${data}`; return `export default '${dataURL}';`; }, -}; \ No newline at end of file +}; diff --git a/lib/closePlugin.ts b/lib/closePlugin.ts index fe864ae2..016910f0 100644 --- a/lib/closePlugin.ts +++ b/lib/closePlugin.ts @@ -1,25 +1,25 @@ // ref: https://stackoverflow.com/a/76920975 -import type { Plugin } from 'vite'; +import type { Plugin } from "vite"; export default function ClosePlugin(): Plugin { return { - name: 'ClosePlugin', // required, will show up in warnings and errors + name: "ClosePlugin", // required, will show up in warnings and errors // use this to catch errors when building buildEnd(error) { - if(error) { - console.error('Error bundling') - console.error(error) - process.exit(1) + if (error) { + console.error("Error bundling"); + console.error(error); + process.exit(1); } else { - console.log('Build ended') + console.log("Build ended"); } }, // use this to catch the end of a build without errors closeBundle() { - console.log('Bundle closed') - process.exit(0) + console.log("Bundle closed"); + process.exit(0); }, - } + }; } diff --git a/lib/createManifest.ts b/lib/createManifest.ts index 11392a49..74843e86 100644 --- a/lib/createManifest.ts +++ b/lib/createManifest.ts @@ -1,5 +1,5 @@ -import type { Browser, BuildTarget, Manifest } from './types' -import type { AnyCase } from './utils' +import type { Browser, BuildTarget, Manifest } from "./types"; +import type { AnyCase } from "./utils"; /** * * @@ -15,7 +15,7 @@ export function createManifest( return { manifest, browser, - } + }; } /** @@ -29,5 +29,5 @@ export function createManifest( * @return {*} {@link Manifest} */ export function createManifestBase(manifest: Manifest): Manifest { - return manifest -} \ No newline at end of file + return manifest; +} diff --git a/lib/inlineWorker.ts b/lib/inlineWorker.ts index e493125d..628c420e 100644 --- a/lib/inlineWorker.ts +++ b/lib/inlineWorker.ts @@ -1,26 +1,26 @@ // vite-plugin-inline-worker-dev.ts -import { Plugin } from 'vite' -import fs from 'fs/promises' -import { build, transform } from 'esbuild' +import { Plugin } from "vite"; +import fs from "fs/promises"; +import { build, transform } from "esbuild"; export default function InlineWorkerDevPlugin(): Plugin { return { - name: 'vite:inline-worker-dev', + name: "vite:inline-worker-dev", async load(id) { - if (id.includes('?inlineWorker')) { - const [cleanPath] = id.split('?') - console.log('cleanPath', cleanPath) - const code = await fs.readFile(cleanPath, 'utf-8') + if (id.includes("?inlineWorker")) { + const [cleanPath] = id.split("?"); + console.log("cleanPath", cleanPath); + const code = await fs.readFile(cleanPath, "utf-8"); const result = await build({ entryPoints: [cleanPath], bundle: true, write: false, - platform: 'browser', - format: 'iife', - target: 'esnext', - }) - - const workerCode = result.outputFiles[0].text + platform: "browser", + format: "iife", + target: "esnext", + }); + + const workerCode = result.outputFiles[0].text; const workerBlobCode = ` const code = ${JSON.stringify(workerCode)}; @@ -28,10 +28,10 @@ export default function InlineWorkerDevPlugin(): Plugin { const blob = new Blob([code], { type: 'application/javascript' }); return new Worker(URL.createObjectURL(blob), { type: 'module' }); } - ` - return workerBlobCode + `; + return workerBlobCode; } - return null - } - } + return null; + }, + }; } diff --git a/lib/publish.js b/lib/publish.js index 0d5bea50..b4154a1b 100644 --- a/lib/publish.js +++ b/lib/publish.js @@ -1,49 +1,63 @@ -const glob = require('glob'); -const semver = require('semver'); -const { execSync } = require('child_process'); -const path = require('path'); +const glob = require("glob"); +const semver = require("semver"); +const { execSync } = require("child_process"); +const path = require("path"); function getLatestVersion(files) { - console.log('Files passed to getLatestVersion:', files); + console.log("Files passed to getLatestVersion:", files); - const versions = files.map(file => { - const match = file.match(/@([\d\.]+)-/); - console.log('Matching file:', file, 'Version found:', match ? match[1] : 'None'); + const versions = files + .map((file) => { + const match = file.match(/@([\d\.]+)-/); + console.log( + "Matching file:", + file, + "Version found:", + match ? match[1] : "None", + ); - if (!match) return null; + if (!match) return null; - const fullVersion = match[1]; // Original version (e.g., 3.4.5.1) - const semverVersion = fullVersion.split('.').slice(0, 3).join('.'); // Trim to 3.4.5 + const fullVersion = match[1]; // Original version (e.g., 3.4.5.1) + const semverVersion = fullVersion.split(".").slice(0, 3).join("."); // Trim to 3.4.5 - return { fullVersion, semverVersion }; - }).filter(Boolean); + return { fullVersion, semverVersion }; + }) + .filter(Boolean); - console.log('Extracted versions:', versions.map(v => v.semverVersion)); + console.log( + "Extracted versions:", + versions.map((v) => v.semverVersion), + ); // Find latest version using the trimmed semver format - const latestSemver = semver.maxSatisfying(versions.map(v => v.semverVersion), '*'); - console.log('Latest SemVer-compatible version:', latestSemver); + const latestSemver = semver.maxSatisfying( + versions.map((v) => v.semverVersion), + "*", + ); + console.log("Latest SemVer-compatible version:", latestSemver); // Get the full version that matches the latest SemVer version - const latestVersion = versions.find(v => v.semverVersion === latestSemver)?.fullVersion || null; + const latestVersion = + versions.find((v) => v.semverVersion === latestSemver)?.fullVersion || null; - console.log('Final selected latest version:', latestVersion); + console.log("Final selected latest version:", latestVersion); return latestVersion; } function getLatestFiles(browser) { const pattern = `dist/betterseqtaplus@*-*${browser}.zip`; - console.log('Glob pattern:', pattern); - + console.log("Glob pattern:", pattern); + const files = glob.sync(pattern); - console.log('Files found for browser', browser, ':', files); - + console.log("Files found for browser", browser, ":", files); + const latestVersion = getLatestVersion(files); // Find the exact file by matching the original full version - const latestFile = files.find(file => file.includes(`@${latestVersion}-`)); + const latestFile = files.find((file) => file.includes(`@${latestVersion}-`)); - console.log('Latest file for browser', browser, ':', latestFile); + console.log("Latest file for browser", browser, ":", latestFile); return latestFile; } @@ -51,44 +65,53 @@ function zipSources() { const zipFileName = `dist/betterseqtaplus@latest-sources.zip`; const excludePatterns = [ - 'node_modules', - 'dist', - '.env*', - '.git', - '.github', - '.vscode', - 'LICENSE', - 'package.json' - ].map(pattern => `-x!${pattern}`).join(' '); + "node_modules", + "dist", + ".env*", + ".git", + ".github", + ".vscode", + "LICENSE", + "package.json", + ] + .map((pattern) => `-x!${pattern}`) + .join(" "); const zipCommand = `7z a ${zipFileName} . ${excludePatterns}`; - console.log('Zipping project sources with command:', zipCommand); - execSync(zipCommand, { stdio: 'inherit' }); + console.log("Zipping project sources with command:", zipCommand); + execSync(zipCommand, { stdio: "inherit" }); return zipFileName; } function runPublishCommand(browsers) { - const chromeZip = browsers.includes('chrome') ? getLatestFiles('chrome') : null; - const firefoxZip = browsers.includes('firefox') ? getLatestFiles('firefox') : null; - const firefoxSourcesZip = browsers.includes('firefox') ? zipSources() : null; + const chromeZip = browsers.includes("chrome") + ? getLatestFiles("chrome") + : null; + const firefoxZip = browsers.includes("firefox") + ? getLatestFiles("firefox") + : null; + const firefoxSourcesZip = browsers.includes("firefox") ? zipSources() : null; - console.log('Chrome zip:', chromeZip); - console.log('Firefox zip:', firefoxZip); - console.log('Firefox sources zip:', firefoxSourcesZip); + console.log("Chrome zip:", chromeZip); + console.log("Firefox zip:", firefoxZip); + console.log("Firefox sources zip:", firefoxSourcesZip); if (browsers.length === 0) { - console.log('No browsers specified. Exiting.'); + console.log("No browsers specified. Exiting."); process.exit(0); } - if ((browsers.includes('chrome') && !chromeZip) || (browsers.includes('firefox') && (!firefoxZip || !firefoxSourcesZip))) { - console.error('Could not find required zip files for specified browsers.'); + if ( + (browsers.includes("chrome") && !chromeZip) || + (browsers.includes("firefox") && (!firefoxZip || !firefoxSourcesZip)) + ) { + console.error("Could not find required zip files for specified browsers."); process.exit(1); } - let command = 'publish-extension'; + let command = "publish-extension"; if (chromeZip) { command += ` --chrome-zip ${chromeZip}`; } @@ -96,13 +119,13 @@ function runPublishCommand(browsers) { command += ` --firefox-zip ${firefoxZip} --firefox-sources-zip ${firefoxSourcesZip}`; } - console.log('Running command:', command); - execSync(command, { stdio: 'inherit' }); + console.log("Running command:", command); + execSync(command, { stdio: "inherit" }); } // Parse command-line arguments const args = process.argv.slice(2); -const browserIndex = args.indexOf('--b'); +const browserIndex = args.indexOf("--b"); const browsers = browserIndex !== -1 ? args.slice(browserIndex + 1) : []; -runPublishCommand(browsers); \ No newline at end of file +runPublishCommand(browsers); diff --git a/lib/touchGlobalCSS.ts b/lib/touchGlobalCSS.ts index d01db686..644ec3fe 100644 --- a/lib/touchGlobalCSS.ts +++ b/lib/touchGlobalCSS.ts @@ -1,17 +1,17 @@ -import fs from 'fs'; +import fs from "fs"; export default function touchGlobalCSSPlugin() { return { - name: 'touch-global-css', + name: "touch-global-css", handleHotUpdate({ modules }) { // log all of the staticImportedUrls - const importers = modules[0]._clientModule.importers + const importers = modules[0]._clientModule.importers; importers.forEach((importer) => { - if (importer.file.includes('.css')) { - console.log("touching", importer.file) - fs.utimesSync(importer.file, new Date(), new Date()) + if (importer.file.includes(".css")) { + console.log("touching", importer.file); + fs.utimesSync(importer.file, new Date(), new Date()); } - }) - } + }); + }, }; } diff --git a/lib/types.ts b/lib/types.ts index ed8e3752..7d06f5a9 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -1,104 +1,104 @@ -import type { ManifestV3Export } from '@crxjs/vite-plugin' -import { type AnyCase, createEnum } from './utils' +import type { ManifestV3Export } from "@crxjs/vite-plugin"; +import { type AnyCase, createEnum } from "./utils"; export const FrameworkEnum = { - React: 'React', - Vanilla: 'Vanilla', - Preact: 'Preact', - Lit: 'Lit', - Svelte: 'Svelte', - Vue: 'Vue', -} as const + React: "React", + Vanilla: "Vanilla", + Preact: "Preact", + Lit: "Lit", + Svelte: "Svelte", + Vue: "Vue", +} as const; export const BrowserEnum = { - Chrome: 'Chrome', - Brave: 'Brave', - Opera: 'Opera', - Edge: 'Edge', - Firefox: 'Firefox', - Safari: 'Safari', -} as const + Chrome: "Chrome", + Brave: "Brave", + Opera: "Opera", + Edge: "Edge", + Firefox: "Firefox", + Safari: "Safari", +} as const; const LanguageEnum = { - TypeScript: 'TypeScript', - JavaScript: 'JavaScript', -} as const + TypeScript: "TypeScript", + JavaScript: "JavaScript", +} as const; export const StyleEnum = { - Tailwind: 'Tailwind', -} as const + Tailwind: "Tailwind", +} as const; export const PackageManagerEnum = { - Bun: 'Bun', - PnPm: 'PnPm', - Npm: 'Npm', - Yarn: 'Yarn', -} as const + Bun: "Bun", + PnPm: "PnPm", + Npm: "Npm", + Yarn: "Yarn", +} as const; // see: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/firefox-webext-browser/index.d.ts export type BrowserSpecificSettings = { browser_specific_settings?: { gecko?: { - id: string - strict_min_version?: string - strict_max_version?: string - } - } -} + id: string; + strict_min_version?: string; + strict_max_version?: string; + }; + }; +}; -export type Manifest = ManifestV3Export -export type ManifestIcons = chrome.runtime.ManifestIcons -export type ManifestBackground = chrome.runtime.ManifestV3['background'] +export type Manifest = ManifestV3Export; +export type ManifestIcons = chrome.runtime.ManifestIcons; +export type ManifestBackground = chrome.runtime.ManifestV3["background"]; export type ManifestContentScripts = - chrome.runtime.ManifestV3['content_scripts'] + chrome.runtime.ManifestV3["content_scripts"]; export type ManifestWebAccessibleResources = - chrome.runtime.ManifestV3['web_accessible_resources'] -export type ManifestCommands = chrome.runtime.ManifestV3['commands'] -export type ManifestAction = chrome.runtime.ManifestV3['action'] -export type ManifestPermissions = chrome.runtime.ManifestV3['permissions'] -export type ManifestOptionsUI = chrome.runtime.ManifestV3['options_ui'] + chrome.runtime.ManifestV3["web_accessible_resources"]; +export type ManifestCommands = chrome.runtime.ManifestV3["commands"]; +export type ManifestAction = chrome.runtime.ManifestV3["action"]; +export type ManifestPermissions = chrome.runtime.ManifestV3["permissions"]; +export type ManifestOptionsUI = chrome.runtime.ManifestV3["options_ui"]; export type ManifestURLOverrides = - chrome.runtime.ManifestV3['chrome_url_overrides'] + chrome.runtime.ManifestV3["chrome_url_overrides"]; -export type BrowserName = Capitalize | Lowercase +export type BrowserName = Capitalize | Lowercase; export type BrowserEnumType = { - [browser in BrowserName]: BrowserName -} + [browser in BrowserName]: BrowserName; +}; -export type BuildMode = AnyCase +export type BuildMode = AnyCase; export type BuildTarget = { - manifest: Manifest - browser: AnyCase -} + manifest: Manifest; + browser: AnyCase; +}; export type BuildConfig = { - command?: 'build' | 'serve' - mode?: AnyCase | string | undefined -} + command?: "build" | "serve"; + mode?: AnyCase | string | undefined; +}; export interface Repository { - type: string - url?: string - bugs?: Bugs + type: string; + url?: string; + bugs?: Bugs; } export interface Bugs { - url?: string - email?: string + url?: string; + email?: string; } -export type Browser = (typeof BrowserEnum)[keyof typeof BrowserEnum] -export const Browser: AnyCase = createEnum(BrowserEnum) +export type Browser = (typeof BrowserEnum)[keyof typeof BrowserEnum]; +export const Browser: AnyCase = createEnum(BrowserEnum); export type PackageManager = - (typeof PackageManagerEnum)[keyof typeof PackageManagerEnum] + (typeof PackageManagerEnum)[keyof typeof PackageManagerEnum]; export const PackageManager: AnyCase = - createEnum(PackageManagerEnum) + createEnum(PackageManagerEnum); -export type Framework = (typeof FrameworkEnum)[keyof typeof FrameworkEnum] -export const Framework: AnyCase = createEnum(FrameworkEnum) +export type Framework = (typeof FrameworkEnum)[keyof typeof FrameworkEnum]; +export const Framework: AnyCase = createEnum(FrameworkEnum); -export type Style = (typeof StyleEnum)[keyof typeof StyleEnum] -export const Style: AnyCase ${loadingSpinner}
v${ - browser.runtime.getManifest().version -}
`, - ); - var html = document.getElementsByTagName('html')[0]; + browser.runtime.getManifest().version + }`); + var html = document.getElementsByTagName("html")[0]; html.append(loadinghtml.firstChild!); -} \ No newline at end of file +} diff --git a/src/seqta/ui/SettingsResizer.ts b/src/seqta/ui/SettingsResizer.ts index ad654f4e..af768855 100644 --- a/src/seqta/ui/SettingsResizer.ts +++ b/src/seqta/ui/SettingsResizer.ts @@ -6,21 +6,24 @@ import { debounce } from "lodash"; export class SettingsResizer { constructor() { this.adjustPopupHeight(); - window.addEventListener('resize', debounce(this.adjustPopupHeight, 250) as EventListener); - document.addEventListener('DOMContentLoaded', this.adjustPopupHeight); + window.addEventListener( + "resize", + debounce(this.adjustPopupHeight, 250) as EventListener, + ); + document.addEventListener("DOMContentLoaded", this.adjustPopupHeight); } private adjustPopupHeight() { - const iframePopup = document.getElementById('ExtensionPopup'); + const iframePopup = document.getElementById("ExtensionPopup"); if (!iframePopup) return; const viewportHeight = window.innerHeight; const idealHeight = viewportHeight - 80 - 15; // -80px for the top of the popup if (idealHeight > 600) { - iframePopup.style.height = '600px'; + iframePopup.style.height = "600px"; } else { iframePopup.style.height = `${idealHeight}px`; } } -} \ No newline at end of file +} diff --git a/src/seqta/ui/colors/ColorLuminance.ts b/src/seqta/ui/colors/ColorLuminance.ts index ee796be5..e841d3a0 100644 --- a/src/seqta/ui/colors/ColorLuminance.ts +++ b/src/seqta/ui/colors/ColorLuminance.ts @@ -1,45 +1,48 @@ -import Color from 'color'; +import Color from "color"; function adjustLuminance(color: any, lum: any) { let adjustedColor = Color(color.toLowerCase()); const rgbObj = adjustedColor.rgb().object(); - + // Adjust luminance adjustedColor = Color.rgb( Math.min(Math.max(0, rgbObj.r + rgbObj.r * lum), 255), Math.min(Math.max(0, rgbObj.g + rgbObj.g * lum), 255), - Math.min(Math.max(0, rgbObj.b + rgbObj.b * lum), 255) + Math.min(Math.max(0, rgbObj.b + rgbObj.b * lum), 255), ); - + return adjustedColor.string(); } export default function ColorLuminance(color: any, lum = 0) { - if (color == '' || color == null) { + if (color == "" || color == null) { // light cyan blue - return '#00bfff'; + return "#00bfff"; } - const colorRegex = /rgba?\(([^)]+)\)/gi; // Case-insensitive match for rgb() or rgba() - - if (color.toLowerCase().includes('gradient')) { + const colorRegex = /rgba?\(([^)]+)\)/gi; // Case-insensitive match for rgb() or rgba() + + if (color.toLowerCase().includes("gradient")) { let gradient = color; - + let uniqueColorSet = new Set(); - + // Extract all unique color stops let match; while ((match = colorRegex.exec(color)) !== null) { uniqueColorSet.add(match[0]); } - + // Adjust luminance for each unique color stop for (let colorStop of uniqueColorSet) { const adjustedColor = adjustLuminance(colorStop, lum); - gradient = gradient.replace(new RegExp(colorStop as string, 'gi'), adjustedColor); + gradient = gradient.replace( + new RegExp(colorStop as string, "gi"), + adjustedColor, + ); } - + return gradient; } else { return adjustLuminance(color, lum); } -} \ No newline at end of file +} diff --git a/src/seqta/ui/colors/Manager.ts b/src/seqta/ui/colors/Manager.ts index 4fc18bd5..318ceb64 100644 --- a/src/seqta/ui/colors/Manager.ts +++ b/src/seqta/ui/colors/Manager.ts @@ -1,72 +1,81 @@ -import browser from 'webextension-polyfill' -import { GetThresholdOfColor } from '@/seqta/ui/colors/getThresholdColour'; -import { lightenAndPaleColor } from './lightenAndPaleColor'; -import ColorLuminance from './ColorLuminance'; -import { settingsState } from '@/seqta/utils/listeners/SettingsState'; +import browser from "webextension-polyfill"; +import { GetThresholdOfColor } from "@/seqta/ui/colors/getThresholdColour"; +import { lightenAndPaleColor } from "./lightenAndPaleColor"; +import ColorLuminance from "./ColorLuminance"; +import { settingsState } from "@/seqta/utils/listeners/SettingsState"; -import darkLogo from '@/resources/icons/betterseqta-light-full.png'; -import lightLogo from '@/resources/icons/betterseqta-dark-full.png'; +import darkLogo from "@/resources/icons/betterseqta-light-full.png"; +import lightLogo from "@/resources/icons/betterseqta-dark-full.png"; // Helper functions -const setCSSVar = (varName: any, value: any) => document.documentElement.style.setProperty(varName, value); -const applyProperties = (props: any) => Object.entries(props).forEach(([key, value]) => setCSSVar(key, value)); - +const setCSSVar = (varName: any, value: any) => + document.documentElement.style.setProperty(varName, value); +const applyProperties = (props: any) => + Object.entries(props).forEach(([key, value]) => setCSSVar(key, value)); export function updateAllColors() { // Determine the color to use - const selectedColor = settingsState.selectedColor !== '' ? settingsState.selectedColor : '#007bff'; + const selectedColor = + settingsState.selectedColor !== "" + ? settingsState.selectedColor + : "#007bff"; if (settingsState.transparencyEffects) { - document.documentElement.classList.add('transparencyEffects'); + document.documentElement.classList.add("transparencyEffects"); } // Common properties, always applied const commonProps = { - '--better-sub': '#161616', - '--better-alert-highlight': '#c61851', - '--better-main': settingsState.selectedColor + "--better-sub": "#161616", + "--better-alert-highlight": "#c61851", + "--better-main": settingsState.selectedColor, }; // Mode-based properties, applied if storedSetting is provided let modeProps = {}; - modeProps = settingsState.DarkMode ? { - '--betterseqta-logo': `url(${browser.runtime.getURL(darkLogo)})` - } : { - '--better-pale': lightenAndPaleColor(selectedColor), - '--betterseqta-logo': `url(${browser.runtime.getURL(lightLogo)})` - }; + modeProps = settingsState.DarkMode + ? { + "--betterseqta-logo": `url(${browser.runtime.getURL(darkLogo)})`, + } + : { + "--better-pale": lightenAndPaleColor(selectedColor), + "--betterseqta-logo": `url(${browser.runtime.getURL(lightLogo)})`, + }; if (settingsState.DarkMode) { - document.documentElement.style.removeProperty('--better-pale'); - document.documentElement.classList.add('dark'); + document.documentElement.style.removeProperty("--better-pale"); + document.documentElement.classList.add("dark"); } else { - document.documentElement.classList.remove('dark'); + document.documentElement.classList.remove("dark"); } // Dynamic properties, always applied const rgbThreshold = GetThresholdOfColor(selectedColor); const isBright = rgbThreshold > 210; const dynamicProps = { - '--text-color': isBright ? 'black' : 'white', - '--better-light': selectedColor === '#ffffff' ? '#b7b7b7' : ColorLuminance(selectedColor, 0.95) + "--text-color": isBright ? "black" : "white", + "--better-light": + selectedColor === "#ffffff" + ? "#b7b7b7" + : ColorLuminance(selectedColor, 0.95), }; // Apply all the properties applyProperties({ ...commonProps, ...modeProps, ...dynamicProps }); - let alliframes = document.getElementsByTagName('iframe'); + let alliframes = document.getElementsByTagName("iframe"); for (let i = 0; i < alliframes.length; i++) { const element = alliframes[i]; - - if (element.getAttribute('excludeDarkCheck') == 'true') { + + if (element.getAttribute("excludeDarkCheck") == "true") { continue; } - + if (settingsState.DarkMode) { - element.contentDocument?.documentElement.classList.add('dark'); + element.contentDocument?.documentElement.classList.add("dark"); } else { - element.contentDocument?.documentElement.classList.remove('dark'); + element.contentDocument?.documentElement.classList.remove("dark"); } } -} \ No newline at end of file +} diff --git a/src/seqta/ui/colors/getThresholdColour.ts b/src/seqta/ui/colors/getThresholdColour.ts index 626ff89d..b105169c 100644 --- a/src/seqta/ui/colors/getThresholdColour.ts +++ b/src/seqta/ui/colors/getThresholdColour.ts @@ -1,38 +1,38 @@ -import Color from "color" +import Color from "color"; export function GetThresholdOfColor(color: any) { - if (!color) return 0 - // Case-insensitive regular expression for matching RGBA colors - const rgbaRegex = /rgba?\(([^)]+)\)/gi - - // Check if the color string is a gradient (linear or radial) - if (color.includes("gradient")) { - let gradientThresholds = [] - - // Find and replace all instances of RGBA in the gradient - let match - while ((match = rgbaRegex.exec(color)) !== null) { - // Extract the individual components (r, g, b, a) - const rgbaString = match[1] - const [r, g, b] = rgbaString.split(",").map((str) => str.trim()) - - // Compute the threshold using your existing algorithm - const threshold = Math.sqrt( - parseInt(r) ** 2 + parseInt(g) ** 2 + parseInt(b) ** 2, - ) - - // Store the computed threshold - gradientThresholds.push(threshold) - } - - // Calculate the average threshold - const averageThreshold = - gradientThresholds.reduce((acc, val) => acc + val, 0) / - gradientThresholds.length - - return averageThreshold - } else { - // Handle the color as a simple RGBA (or hex, or whatever the Color library supports) - const rgb = Color.rgb(color).object() - return Math.sqrt(rgb.r ** 2 + rgb.g ** 2 + rgb.b ** 2) + if (!color) return 0; + // Case-insensitive regular expression for matching RGBA colors + const rgbaRegex = /rgba?\(([^)]+)\)/gi; + + // Check if the color string is a gradient (linear or radial) + if (color.includes("gradient")) { + let gradientThresholds = []; + + // Find and replace all instances of RGBA in the gradient + let match; + while ((match = rgbaRegex.exec(color)) !== null) { + // Extract the individual components (r, g, b, a) + const rgbaString = match[1]; + const [r, g, b] = rgbaString.split(",").map((str) => str.trim()); + + // Compute the threshold using your existing algorithm + const threshold = Math.sqrt( + parseInt(r) ** 2 + parseInt(g) ** 2 + parseInt(b) ** 2, + ); + + // Store the computed threshold + gradientThresholds.push(threshold); } - } \ No newline at end of file + + // Calculate the average threshold + const averageThreshold = + gradientThresholds.reduce((acc, val) => acc + val, 0) / + gradientThresholds.length; + + return averageThreshold; + } else { + // Handle the color as a simple RGBA (or hex, or whatever the Color library supports) + const rgb = Color.rgb(color).object(); + return Math.sqrt(rgb.r ** 2 + rgb.g ** 2 + rgb.b ** 2); + } +} diff --git a/src/seqta/ui/colors/lightenAndPaleColor.ts b/src/seqta/ui/colors/lightenAndPaleColor.ts index 1c57b6a1..23da7584 100644 --- a/src/seqta/ui/colors/lightenAndPaleColor.ts +++ b/src/seqta/ui/colors/lightenAndPaleColor.ts @@ -1,9 +1,13 @@ -import Color from 'color'; +import Color from "color"; -export function lightenAndPaleColor(inputColor: any, lightenFactor = 0.75, paleFactor = 0.55) { +export function lightenAndPaleColor( + inputColor: any, + lightenFactor = 0.75, + paleFactor = 0.55, +) { if (!inputColor) return; - if (inputColor.includes('gradient')) { + if (inputColor.includes("gradient")) { const baseColor = findMatchingColor(inputColor); return lightenAndPaleColor(baseColor, lightenFactor, paleFactor); @@ -29,27 +33,38 @@ export function lightenAndPaleColor(inputColor: any, lightenFactor = 0.75, paleF } // Utility function to average an array of Color objects function averageColors(colors: any) { - let avgR = 0, avgG = 0, avgB = 0; + let avgR = 0, + avgG = 0, + avgB = 0; colors.forEach((color: any) => { avgR += color.red(); avgG += color.green(); avgB += color.blue(); }); - return Color.rgb(avgR / colors.length, avgG / colors.length, avgB / colors.length); + return Color.rgb( + avgR / colors.length, + avgG / colors.length, + avgB / colors.length, + ); } // Main function to find a matching color for a CSS gradient function findMatchingColor(cssGradient: any) { try { // Step 1: Parse the gradient to extract color stops (case-insensitive) - const regex = /#[0-9a-fA-F]{6}|rgb\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*\)|rgba\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*,\s*[\d.]+\s*\)/gi; + const regex = + /#[0-9a-fA-F]{6}|rgb\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*\)|rgba\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*,\s*[\d.]+\s*\)/gi; const colorStops = cssGradient.match(regex); if (!colorStops) { - throw new Error('No valid color stops found in the provided CSS gradient.'); + throw new Error( + "No valid color stops found in the provided CSS gradient.", + ); } // Normalize and trim the color stops - const normalizedColorStops = colorStops.map((color: any) => color.toLowerCase().replace(/\s+/g, '')); + const normalizedColorStops = colorStops.map((color: any) => + color.toLowerCase().replace(/\s+/g, ""), + ); // Convert the color stops to Color objects const colorObjects = normalizedColorStops.map((color: any) => Color(color)); @@ -57,11 +72,10 @@ function findMatchingColor(cssGradient: any) { // Step 2: Average the color stops const baseColor = averageColors(colorObjects); - // Step 4: Return the matching color in HEX format return baseColor.hex(); } catch (err: any) { console.error(`Error: ${err.message}`); return null; } -} \ No newline at end of file +} diff --git a/src/seqta/ui/dev/hideSensitiveContent.ts b/src/seqta/ui/dev/hideSensitiveContent.ts index 5400aa9c..d9199ff4 100644 --- a/src/seqta/ui/dev/hideSensitiveContent.ts +++ b/src/seqta/ui/dev/hideSensitiveContent.ts @@ -20,191 +20,356 @@ function generateMockUserCode(): string { function getRandomDate(): Date { const start = new Date(); const end = new Date(start.getTime() + 60 * 24 * 60 * 60 * 1000); // 60 days from now - return new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime())); + return new Date( + start.getTime() + Math.random() * (end.getTime() - start.getTime()), + ); } const contentConfig: ContentConfig = { - lessonTitle: { - selector: '.day h2', - action: (element) => { element.textContent = getRandomElement(mockData.subjects); } + lessonTitle: { + selector: ".day h2", + action: (element) => { + element.textContent = getRandomElement(mockData.subjects); + }, }, - teacher: { - selector: '.day h3:first-of-type', - action: (element) => { element.textContent = getRandomElement(mockData.teachers); } + teacher: { + selector: ".day h3:first-of-type", + action: (element) => { + element.textContent = getRandomElement(mockData.teachers); + }, }, - classroom: { - selector: '.day h3:last-of-type', - action: (element) => { element.textContent = getRandomElement(mockData.classrooms); } + classroom: { + selector: ".day h3:last-of-type", + action: (element) => { + element.textContent = getRandomElement(mockData.classrooms); + }, }, - userName: { - selector: '.userInfoName, .name', - action: (element) => { element.textContent = getRandomElement(mockData.names); } + userName: { + selector: ".userInfoName, .name", + action: (element) => { + element.textContent = getRandomElement(mockData.names); + }, }, - userCode: { - selector: '.userInfoText > .userInfoCode', - action: (element) => { element.textContent = generateMockUserCode(); } + userCode: { + selector: ".userInfoText > .userInfoCode", + action: (element) => { + element.textContent = generateMockUserCode(); + }, }, - assessmentTitle: { - selector: '.upcoming-assessment .upcoming-assessment-title', - action: (element) => { element.textContent = getRandomElement(mockData.assessmentTitles); } + assessmentTitle: { + selector: ".upcoming-assessment .upcoming-assessment-title", + action: (element) => { + element.textContent = getRandomElement(mockData.assessmentTitles); + }, }, - assessmentSubject: { - selector: '.upcoming-assessment .upcoming-details h5', - action: (element) => { element.textContent = getRandomElement(mockData.subjects); } + assessmentSubject: { + selector: ".upcoming-assessment .upcoming-details h5", + action: (element) => { + element.textContent = getRandomElement(mockData.subjects); + }, }, - noticeTitle: { - selector: '.notice h3', - action: (element) => { element.textContent = getRandomElement(mockData.notices); } + noticeTitle: { + selector: ".notice h3", + action: (element) => { + element.textContent = getRandomElement(mockData.notices); + }, }, - noticeContent: { - selector: '.notice .contents', - action: (element) => { element.textContent = 'Content has been redacted for privacy.'; } + noticeContent: { + selector: ".notice .contents", + action: (element) => { + element.textContent = "Content has been redacted for privacy."; + }, }, upcomingCheckboxes: { - selector: '.upcoming-checkbox-container', - action: (element) => { element.firstChild!.textContent = 'SUBJ'; } + selector: ".upcoming-checkbox-container", + action: (element) => { + element.firstChild!.textContent = "SUBJ"; + }, }, - dates: { - selector: '.upcoming-date-title h5, input[type="date"]', + dates: { + selector: '.upcoming-date-title h5, input[type="date"]', action: (element) => { const randomDate = getRandomDate(); if (element instanceof HTMLInputElement) { - element.value = randomDate.toISOString().split('T')[0]; + element.value = randomDate.toISOString().split("T")[0]; } else { - element.textContent = randomDate.toLocaleDateString('en-US', { weekday: 'long', day: 'numeric', month: 'long' }); + element.textContent = randomDate.toLocaleDateString("en-US", { + weekday: "long", + day: "numeric", + month: "long", + }); } - } + }, }, messageSubject: { selector: '[class*="MessageList__subject___"]', - action: (element) => { element.textContent = getRandomElement(mockData.messages.subjects); } + action: (element) => { + element.textContent = getRandomElement(mockData.messages.subjects); + }, }, messageSender: { selector: '[class*="MessageList__value___"]', - action: (element) => { element.textContent = getRandomElement(mockData.messages.sender); } + action: (element) => { + element.textContent = getRandomElement(mockData.messages.sender); + }, }, messageRecipients: { - selector: '[class*="MessageList__recipients___"] [class*="MessageList__value___"]', - action: (element) => { element.textContent = 'Recipient(s) Redacted'; } + selector: + '[class*="MessageList__recipients___"] [class*="MessageList__value___"]', + action: (element) => { + element.textContent = "Recipient(s) Redacted"; + }, }, messageDate: { selector: '[class*="MessageList__date___"]', - action: (element) => { element.textContent = getRandomDate().toLocaleDateString('en-US', { weekday: 'long', day: 'numeric', month: 'long' }); } + action: (element) => { + element.textContent = getRandomDate().toLocaleDateString("en-US", { + weekday: "long", + day: "numeric", + month: "long", + }); + }, }, avatarImage: { selector: '[class*="Avatar__Avatar___"]', - action: (element) => { + action: (element) => { if (element instanceof HTMLElement) { - element.style.removeProperty('background-image'); - element.firstChild!.firstChild!.textContent = getRandomElement(mockData.names)[0]; + element.style.removeProperty("background-image"); + element.firstChild!.firstChild!.textContent = getRandomElement( + mockData.names, + )[0]; } - } + }, }, notificationCount: { selector: '[class*="notifications__bubble___"]', - action: (element) => { element.textContent = Math.floor(Math.random() * 100).toString(); } + action: (element) => { + element.textContent = Math.floor(Math.random() * 100).toString(); + }, }, schoolName: { - selector: 'title', - action: (element) => { element.textContent = 'School Portal'; } + selector: "title", + action: (element) => { + element.textContent = "School Portal"; + }, }, documentNames: { - selector: '.document td.title', - action: (element) => { element.textContent = 'Document Name Redacted'; } + selector: ".document td.title", + action: (element) => { + element.textContent = "Document Name Redacted"; + }, }, forumTopics: { - selector: '#menu .sub ul li label', - action: (element) => { element.textContent = 'Forum Topic Redacted'; } + selector: "#menu .sub ul li label", + action: (element) => { + element.textContent = "Forum Topic Redacted"; + }, }, courseNames: { - selector: '#menu .sub ul li[data-colour] label', - action: (element) => { element.textContent = 'Course Name Redacted'; } + selector: "#menu .sub ul li[data-colour] label", + action: (element) => { + element.textContent = "Course Name Redacted"; + }, }, yearGroups: { - selector: '#menu .sub > ul > li > label', - action: (element) => { element.textContent = 'Year Group Redacted'; } + selector: "#menu .sub > ul > li > label", + action: (element) => { + element.textContent = "Year Group Redacted"; + }, }, newsArticleTitle: { - selector: '.ArticleText a', - action: (element) => { element.textContent = 'News Article Title Redacted'; } + selector: ".ArticleText a", + action: (element) => { + element.textContent = "News Article Title Redacted"; + }, }, newsArticleContent: { - selector: '.ArticleText p', - action: (element) => { element.textContent = 'News Article Content Redacted'; } + selector: ".ArticleText p", + action: (element) => { + element.textContent = "News Article Content Redacted"; + }, }, userHouse: { - selector: '.userInfohouse', - action: (element) => { element.textContent = 'House'; } - } + selector: ".userInfohouse", + action: (element) => { + element.textContent = "House"; + }, + }, }; const mockData = { subjects: [ - "Mathematics", "English", "Science", "History", "Geography", - "Art", "Music", "Physical Education", "Chemistry", "Physics", - "Biology", "Economics", "Business Studies", "French", "Spanish", - "Computer Science", "Literature", "Environmental Studies", - "Political Science", "Sociology" + "Mathematics", + "English", + "Science", + "History", + "Geography", + "Art", + "Music", + "Physical Education", + "Chemistry", + "Physics", + "Biology", + "Economics", + "Business Studies", + "French", + "Spanish", + "Computer Science", + "Literature", + "Environmental Studies", + "Political Science", + "Sociology", ], teachers: [ - "Mr. Smith", "Mrs. Johnson", "Ms. Williams", "Dr. Brown", - "Mr. Davis", "Mrs. Miller", "Mr. Wilson", "Ms. Moore", - "Dr. Taylor", "Mrs. Anderson", "Mr. Garcia", "Mrs. Martinez", - "Ms. Thompson", "Dr. Lee", "Mr. Robinson", "Mrs. Hall", - "Ms. White", "Dr. Clark", "Mr. Lewis", "Mrs. King" + "Mr. Smith", + "Mrs. Johnson", + "Ms. Williams", + "Dr. Brown", + "Mr. Davis", + "Mrs. Miller", + "Mr. Wilson", + "Ms. Moore", + "Dr. Taylor", + "Mrs. Anderson", + "Mr. Garcia", + "Mrs. Martinez", + "Ms. Thompson", + "Dr. Lee", + "Mr. Robinson", + "Mrs. Hall", + "Ms. White", + "Dr. Clark", + "Mr. Lewis", + "Mrs. King", ], classrooms: [ - "A101", "B205", "C304", "D102", "E201", - "F103", "G204", "H301", "I202", "J105", - "K107", "L206", "M303", "N104", "O209" + "A101", + "B205", + "C304", + "D102", + "E201", + "F103", + "G204", + "H301", + "I202", + "J105", + "K107", + "L206", + "M303", + "N104", + "O209", ], names: [ - "John Doe", "Jane Smith", "Michael Johnson", "Emily Brown", - "David Lee", "Sarah Davis", "Robert Wilson", "Lisa Taylor", - "William Moore", "Jennifer Anderson", "Thomas Garcia", - "Olivia Martinez", "Daniel Thompson", "Sophia Lee", - "Matthew Robinson", "Ava Hall", "Jacob White", - "Mia Clark", "James Lewis", "Lily King" + "John Doe", + "Jane Smith", + "Michael Johnson", + "Emily Brown", + "David Lee", + "Sarah Davis", + "Robert Wilson", + "Lisa Taylor", + "William Moore", + "Jennifer Anderson", + "Thomas Garcia", + "Olivia Martinez", + "Daniel Thompson", + "Sophia Lee", + "Matthew Robinson", + "Ava Hall", + "Jacob White", + "Mia Clark", + "James Lewis", + "Lily King", ], assessmentTitles: [ - "Mid-term Exam", "Final Project", "Research Paper", - "Oral Presentation", "Lab Report", "Essay", - "Group Assignment", "Portfolio Review", "Quiz", - "Practical Test", "Class Presentation", - "Online Assessment", "Case Study", "Field Report", - "Peer Review", "Coding Challenge", "Math Test", - "Literary Analysis", "Debate", "Design Project" + "Mid-term Exam", + "Final Project", + "Research Paper", + "Oral Presentation", + "Lab Report", + "Essay", + "Group Assignment", + "Portfolio Review", + "Quiz", + "Practical Test", + "Class Presentation", + "Online Assessment", + "Case Study", + "Field Report", + "Peer Review", + "Coding Challenge", + "Math Test", + "Literary Analysis", + "Debate", + "Design Project", ], notices: [ - "School Assembly", "Excursion Reminder", "Fundraising Event", - "Parent-Teacher Meetings", "Sports Day", "Book Fair", - "Career Day", "Music Concert", "Art Exhibition", - "Science Fair", "Holiday Celebration", "Community Service Day", - "Graduation Ceremony", "Award Ceremony", "Workshop", - "Open House", "Seminar", "Club Meeting", - "Field Trip", "Cultural Festival" + "School Assembly", + "Excursion Reminder", + "Fundraising Event", + "Parent-Teacher Meetings", + "Sports Day", + "Book Fair", + "Career Day", + "Music Concert", + "Art Exhibition", + "Science Fair", + "Holiday Celebration", + "Community Service Day", + "Graduation Ceremony", + "Award Ceremony", + "Workshop", + "Open House", + "Seminar", + "Club Meeting", + "Field Trip", + "Cultural Festival", ], messages: { subjects: [ - "Mid-year Exams", "Science project due soon", "Mufti Day coming up!", - "School Assembly", "Excursion Reminder", "Fundraising Event", - "Parent-Teacher Meetings", "Sports Day", "Book Fair", - "Career Day", "Music Concert", "Art Exhibition", - "Science Fair", "Holiday Celebration", "Community Service Day", - "Graduation Ceremony", "Award Ceremony", "Workshop", - "Open House", "Seminar", "Club Meeting", - "Field Trip", "Cultural Festival" + "Mid-year Exams", + "Science project due soon", + "Mufti Day coming up!", + "School Assembly", + "Excursion Reminder", + "Fundraising Event", + "Parent-Teacher Meetings", + "Sports Day", + "Book Fair", + "Career Day", + "Music Concert", + "Art Exhibition", + "Science Fair", + "Holiday Celebration", + "Community Service Day", + "Graduation Ceremony", + "Award Ceremony", + "Workshop", + "Open House", + "Seminar", + "Club Meeting", + "Field Trip", + "Cultural Festival", ], sender: [ - "Mr. Smith", "Mrs. Johnson", "Ms. Williams", "Dr. Brown", - "Mr. Davis", "Mrs. Miller", "Mr. Wilson", "Ms. Moore", - "Dr. Taylor", "Mrs. Anderson", "Mr. Garcia", "Mrs. Martinez", - ] - } + "Mr. Smith", + "Mrs. Johnson", + "Ms. Williams", + "Dr. Brown", + "Mr. Davis", + "Mrs. Miller", + "Mr. Wilson", + "Ms. Moore", + "Dr. Taylor", + "Mrs. Anderson", + "Mr. Garcia", + "Mrs. Martinez", + ], + }, }; export default function hideSensitiveContent() { @@ -214,4 +379,4 @@ export default function hideSensitiveContent() { action(element); }); }); -} \ No newline at end of file +} diff --git a/src/seqta/ui/renderStore.ts b/src/seqta/ui/renderStore.ts index 7a20fa13..6f7e3703 100644 --- a/src/seqta/ui/renderStore.ts +++ b/src/seqta/ui/renderStore.ts @@ -1,35 +1,35 @@ -import renderSvelte from '@/interface/main'; -import Store from '@/interface/pages/store.svelte' +import renderSvelte from "@/interface/main"; +import Store from "@/interface/pages/store.svelte"; -import { unmount } from 'svelte' +import { unmount } from "svelte"; -let remove: () => void +let remove: () => void; export function OpenStorePage() { - remove = renderStore() + remove = renderStore(); } export function renderStore() { - const container = document.querySelector('#container'); + const container = document.querySelector("#container"); if (!container) { - throw new Error('Container not found'); + throw new Error("Container not found"); } - - const child = document.createElement('div'); - child.id = 'store'; + + const child = document.createElement("div"); + child.id = "store"; container!.appendChild(child); - const shadow = child.attachShadow({ mode: 'open' }); + const shadow = child.attachShadow({ mode: "open" }); const app = renderSvelte(Store, shadow); - return () => unmount(app) + return () => unmount(app); } export function closeStore() { - document.getElementById('store')!.classList.add('hide') + document.getElementById("store")!.classList.add("hide"); - setTimeout(() => { - remove() - document.getElementById('store')!.remove() - }, 500) + setTimeout(() => { + remove(); + document.getElementById("store")!.remove(); + }, 500); } diff --git a/src/seqta/utils/Adders/AddExtensionSettings.ts b/src/seqta/utils/Adders/AddExtensionSettings.ts index ebbc9876..6ce7c4df 100644 --- a/src/seqta/utils/Adders/AddExtensionSettings.ts +++ b/src/seqta/utils/Adders/AddExtensionSettings.ts @@ -1,36 +1,40 @@ -import { changeSettingsClicked, closeExtensionPopup, SettingsClicked } from "../Closers/closeExtensionPopup" -import renderSvelte from "@/interface/main" -import { SettingsResizer } from "@/seqta/ui/SettingsResizer" -import Settings from "@/interface/pages/settings.svelte" +import { + changeSettingsClicked, + closeExtensionPopup, + SettingsClicked, +} from "../Closers/closeExtensionPopup"; +import renderSvelte from "@/interface/main"; +import { SettingsResizer } from "@/seqta/ui/SettingsResizer"; +import Settings from "@/interface/pages/settings.svelte"; export function addExtensionSettings() { - const extensionPopup = document.createElement("div") - extensionPopup.classList.add("outside-container", "hide") - extensionPopup.id = "ExtensionPopup" - - const extensionContainer = document.querySelector( - "#container", - ) as HTMLDivElement - if (extensionContainer) extensionContainer.appendChild(extensionPopup) - - // create shadow dom and render svelte app - try { - const shadow = extensionPopup.attachShadow({ mode: "open" }) - requestIdleCallback(() => renderSvelte(Settings, shadow)) - } catch (err) { - console.error(err) + const extensionPopup = document.createElement("div"); + extensionPopup.classList.add("outside-container", "hide"); + extensionPopup.id = "ExtensionPopup"; + + const extensionContainer = document.querySelector( + "#container", + ) as HTMLDivElement; + if (extensionContainer) extensionContainer.appendChild(extensionPopup); + + // create shadow dom and render svelte app + try { + const shadow = extensionPopup.attachShadow({ mode: "open" }); + requestIdleCallback(() => renderSvelte(Settings, shadow)); + } catch (err) { + console.error(err); + } + + const container = document.getElementById("container"); + + new SettingsResizer(); + + container!.onclick = (event) => { + if (!SettingsClicked) return; + + if (!(event.target as HTMLElement).closest("#AddedSettings")) { + if (event.target == extensionPopup) return; + changeSettingsClicked(closeExtensionPopup()); } - - const container = document.getElementById("container") - - new SettingsResizer() - - container!.onclick = (event) => { - if (!SettingsClicked) return - - if (!(event.target as HTMLElement).closest("#AddedSettings")) { - if (event.target == extensionPopup) return - changeSettingsClicked(closeExtensionPopup()) - } - } - } \ No newline at end of file + }; +} diff --git a/src/seqta/utils/Adders/AddShortcuts.ts b/src/seqta/utils/Adders/AddShortcuts.ts index 6799712e..68ef8781 100644 --- a/src/seqta/utils/Adders/AddShortcuts.ts +++ b/src/seqta/utils/Adders/AddShortcuts.ts @@ -1,24 +1,24 @@ -import ShortcutLinks from "@/seqta/content/links.json" -import stringToHTML from "../stringToHTML" +import ShortcutLinks from "@/seqta/content/links.json"; +import stringToHTML from "../stringToHTML"; export function addShortcuts(shortcuts: any) { for (let i = 0; i < shortcuts.length; i++) { - const currentShortcut = shortcuts[i] + const currentShortcut = shortcuts[i]; if (currentShortcut?.enabled) { - const Itemname = (currentShortcut?.name ?? "").replace(/\s/g, "") + const Itemname = (currentShortcut?.name ?? "").replace(/\s/g, ""); const linkDetails = - ShortcutLinks?.[Itemname as keyof typeof ShortcutLinks] + ShortcutLinks?.[Itemname as keyof typeof ShortcutLinks]; if (linkDetails) { createNewShortcut( linkDetails.link, linkDetails.icon, linkDetails.viewBox, currentShortcut?.name, - ) + ); } else { - console.warn(`No link details found for '${Itemname}'`) + console.warn(`No link details found for '${Itemname}'`); } } } @@ -26,21 +26,21 @@ export function addShortcuts(shortcuts: any) { function createNewShortcut(link: any, icon: any, viewBox: any, title: any) { // Creates the stucture and element information for each seperate shortcut - let shortcut = document.createElement("a") - shortcut.setAttribute("href", link) - shortcut.setAttribute("target", "_blank") - let shortcutdiv = document.createElement("div") - shortcutdiv.classList.add("shortcut") + let shortcut = document.createElement("a"); + shortcut.setAttribute("href", link); + shortcut.setAttribute("target", "_blank"); + let shortcutdiv = document.createElement("div"); + shortcutdiv.classList.add("shortcut"); let image = stringToHTML( ``, - ).firstChild - ;(image! as HTMLElement).classList.add("shortcuticondiv") - let text = document.createElement("p") - text.textContent = title - shortcutdiv.append(image as HTMLElement) - shortcutdiv.append(text) - shortcut.append(shortcutdiv) + ).firstChild; + (image! as HTMLElement).classList.add("shortcuticondiv"); + let text = document.createElement("p"); + text.textContent = title; + shortcutdiv.append(image as HTMLElement); + shortcutdiv.append(text); + shortcut.append(shortcutdiv); - document.getElementById("shortcuts")!.appendChild(shortcut) -} \ No newline at end of file + document.getElementById("shortcuts")!.appendChild(shortcut); +} diff --git a/src/seqta/utils/Closers/closeExtensionPopup.ts b/src/seqta/utils/Closers/closeExtensionPopup.ts index 64bb79bb..fa6a845a 100644 --- a/src/seqta/utils/Closers/closeExtensionPopup.ts +++ b/src/seqta/utils/Closers/closeExtensionPopup.ts @@ -1,34 +1,34 @@ import { settingsState } from "@/seqta/utils/listeners/SettingsState"; -import { animate } from "motion" +import { animate } from "motion"; -import { settingsPopup } from "@/interface/hooks/SettingsPopup" +import { settingsPopup } from "@/interface/hooks/SettingsPopup"; -export let SettingsClicked = false +export let SettingsClicked = false; export const closeExtensionPopup = (extensionPopup?: HTMLElement) => { - if (!extensionPopup) - extensionPopup = document.getElementById("ExtensionPopup")! - - extensionPopup.classList.add("hide") - if (settingsState.animations) { - animate(1, 0, { - onUpdate: (progress) => { - extensionPopup.style.opacity = Math.max(0, progress).toString() - extensionPopup.style.transform = `scale(${Math.max(0, progress)})` - }, - type: "spring", - stiffness: 520, - damping: 20, - }) - } else { - extensionPopup.style.opacity = "0" - extensionPopup.style.transform = "scale(0)" - } - - settingsPopup.triggerClose() - return SettingsClicked = false + if (!extensionPopup) + extensionPopup = document.getElementById("ExtensionPopup")!; + + extensionPopup.classList.add("hide"); + if (settingsState.animations) { + animate(1, 0, { + onUpdate: (progress) => { + extensionPopup.style.opacity = Math.max(0, progress).toString(); + extensionPopup.style.transform = `scale(${Math.max(0, progress)})`; + }, + type: "spring", + stiffness: 520, + damping: 20, + }); + } else { + extensionPopup.style.opacity = "0"; + extensionPopup.style.transform = "scale(0)"; } - export function changeSettingsClicked(newVal: boolean) { - SettingsClicked = newVal - } \ No newline at end of file + settingsPopup.triggerClose(); + return (SettingsClicked = false); +}; + +export function changeSettingsClicked(newVal: boolean) { + SettingsClicked = newVal; +} diff --git a/src/seqta/utils/CreateEnable/CreateCustomShortcutDiv.ts b/src/seqta/utils/CreateEnable/CreateCustomShortcutDiv.ts index 7a32d1c0..75a9296c 100644 --- a/src/seqta/utils/CreateEnable/CreateCustomShortcutDiv.ts +++ b/src/seqta/utils/CreateEnable/CreateCustomShortcutDiv.ts @@ -1,13 +1,13 @@ -import stringToHTML from "../stringToHTML" +import stringToHTML from "../stringToHTML"; export function CreateCustomShortcutDiv(element: any) { // Creates the stucture and element information for each seperate shortcut - var shortcut = document.createElement("a") - shortcut.setAttribute("href", element.url) - shortcut.setAttribute("target", "_blank") - var shortcutdiv = document.createElement("div") - shortcutdiv.classList.add("shortcut") - shortcutdiv.classList.add("customshortcut") + var shortcut = document.createElement("a"); + shortcut.setAttribute("href", element.url); + shortcut.setAttribute("target", "_blank"); + var shortcutdiv = document.createElement("div"); + shortcutdiv.classList.add("shortcut"); + shortcutdiv.classList.add("customshortcut"); let image = stringToHTML( ` @@ -25,13 +25,13 @@ export function CreateCustomShortcutDiv(element: any) { `, - ).firstChild - ;(image as HTMLElement).classList.add("shortcuticondiv") - var text = document.createElement("p") - text.textContent = element.name - shortcutdiv.append(image!) - shortcutdiv.append(text) - shortcut.append(shortcutdiv) + ).firstChild; + (image as HTMLElement).classList.add("shortcuticondiv"); + var text = document.createElement("p"); + text.textContent = element.name; + shortcutdiv.append(image!); + shortcutdiv.append(text); + shortcut.append(shortcutdiv); - document.getElementById("shortcuts")!.append(shortcut) -} \ No newline at end of file + document.getElementById("shortcuts")!.append(shortcut); +} diff --git a/src/seqta/utils/CreateEnable/CreateElement.ts b/src/seqta/utils/CreateEnable/CreateElement.ts index ff5af4b7..b8caca5e 100644 --- a/src/seqta/utils/CreateEnable/CreateElement.ts +++ b/src/seqta/utils/CreateEnable/CreateElement.ts @@ -1,26 +1,26 @@ export function CreateElement( - type: string, - class_?: any, - id?: any, - innerText?: string, - innerHTML?: string, - style?: string, - ) { - let element = document.createElement(type) - if (class_ !== undefined) { - element.classList.add(class_) - } - if (id !== undefined) { - element.id = id - } - if (innerText !== undefined) { - element.innerText = innerText - } - if (innerHTML !== undefined) { - element.innerHTML = innerHTML - } - if (style !== undefined) { - element.style.cssText = style - } - return element - } \ No newline at end of file + type: string, + class_?: any, + id?: any, + innerText?: string, + innerHTML?: string, + style?: string, +) { + let element = document.createElement(type); + if (class_ !== undefined) { + element.classList.add(class_); + } + if (id !== undefined) { + element.id = id; + } + if (innerText !== undefined) { + element.innerText = innerText; + } + if (innerHTML !== undefined) { + element.innerHTML = innerHTML; + } + if (style !== undefined) { + element.style.cssText = style; + } + return element; +} diff --git a/src/seqta/utils/DisableRemove/RemoveShortcutDiv.ts b/src/seqta/utils/DisableRemove/RemoveShortcutDiv.ts index a0b607a0..4c1d8c9c 100644 --- a/src/seqta/utils/DisableRemove/RemoveShortcutDiv.ts +++ b/src/seqta/utils/DisableRemove/RemoveShortcutDiv.ts @@ -1,24 +1,24 @@ export function RemoveShortcutDiv(elements: any) { - if (elements.length === 0) return + if (elements.length === 0) return; elements.forEach((element: any) => { - const shortcuts = document.querySelectorAll(".shortcut") + const shortcuts = document.querySelectorAll(".shortcut"); shortcuts.forEach((shortcut) => { - const anchorElement = shortcut.parentElement // the element is the parent - const textElement = shortcut.querySelector("p") //

is a direct child of .shortcut - const title = textElement ? textElement.textContent : "" + const anchorElement = shortcut.parentElement; // the element is the parent + const textElement = shortcut.querySelector("p"); //

is a direct child of .shortcut + const title = textElement ? textElement.textContent : ""; - let shouldRemove = title === element.name + let shouldRemove = title === element.name; // Check href only if element.url exists if (element.url) { shouldRemove = - shouldRemove && anchorElement!.getAttribute("href") === element.url + shouldRemove && anchorElement!.getAttribute("href") === element.url; } if (shouldRemove) { - anchorElement!.remove() + anchorElement!.remove(); } - }) - }) -} \ No newline at end of file + }); + }); +} diff --git a/src/seqta/utils/FileUpload.ts b/src/seqta/utils/FileUpload.ts index d319f482..88eb2a32 100644 --- a/src/seqta/utils/FileUpload.ts +++ b/src/seqta/utils/FileUpload.ts @@ -19,25 +19,25 @@ export async function UploadImage(file: File): Promise { // Setting up the request options const requestOptions = { - method: 'POST', + method: "POST", headers: { - 'Cookie': cookies, - 'X-File-Name': fileName + Cookie: cookies, + "X-File-Name": fileName, }, - body: file // Binary file data + body: file, // Binary file data }; // Making the fetch request and returning the promise - return await fetch('/seqta/student/file/upload/xhr2', requestOptions) - .then(async response => { + return await fetch("/seqta/student/file/upload/xhr2", requestOptions) + .then(async (response) => { if (!response.ok) { throw new Error(`HTTP error! Status: ${response.status}`); } const json = await response.json(); return `/seqta/student/load/file?type=message&file=${json.uuid}`; }) - .catch(error => { - console.error('Error during file upload:', error); + .catch((error) => { + console.error("Error during file upload:", error); throw error; }); } diff --git a/src/seqta/utils/FilterUpcomingAssessments.ts b/src/seqta/utils/FilterUpcomingAssessments.ts index bacf9861..fe674372 100644 --- a/src/seqta/utils/FilterUpcomingAssessments.ts +++ b/src/seqta/utils/FilterUpcomingAssessments.ts @@ -1,25 +1,25 @@ -import { CreateElement } from "@/seqta/utils/CreateEnable/CreateElement" +import { CreateElement } from "@/seqta/utils/CreateEnable/CreateElement"; export function FilterUpcomingAssessments(subjectoptions: any) { for (var item in subjectoptions) { - let subjectdivs = document.querySelectorAll(`[data-subject="${item}"]`) + let subjectdivs = document.querySelectorAll(`[data-subject="${item}"]`); for (let i = 0; i < subjectdivs.length; i++) { - const element = subjectdivs[i] + const element = subjectdivs[i]; if (!subjectoptions[item]) { - element.classList.add("hidden") + element.classList.add("hidden"); } if (subjectoptions[item]) { - element.classList.remove("hidden") + element.classList.remove("hidden"); } - (element.parentNode! as HTMLElement).classList.remove("hidden") + (element.parentNode! as HTMLElement).classList.remove("hidden"); - let children = element.parentNode!.parentNode!.children + let children = element.parentNode!.parentNode!.children; for (let i = 0; i < children.length; i++) { - const element = children[i] + const element = children[i]; if (element.hasAttribute("data-hidden")) { - element.remove() + element.remove(); } } @@ -35,33 +35,33 @@ export function FilterUpcomingAssessments(subjectoptions: any) { ) { (element.parentNode!.parentNode! as HTMLElement).classList.add( "hidden", - ) + ); } else { AddPlaceHolderToParent( element.parentNode!.parentNode, element.parentNode!.querySelectorAll(".hidden").length, - ) + ); } } } else { (element.parentNode!.parentNode! as HTMLElement).classList.remove( "hidden", - ) + ); } } } } function AddPlaceHolderToParent(parent: any, numberofassessments: any) { - let textcontainer = CreateElement("div", "upcoming-blank") - let textblank = CreateElement("p", "upcoming-hiddenassessment") - let s = "" + let textcontainer = CreateElement("div", "upcoming-blank"); + let textblank = CreateElement("p", "upcoming-hiddenassessment"); + let s = ""; if (numberofassessments > 1) { - s = "s" + s = "s"; } - textblank.innerText = `${numberofassessments} hidden assessment${s} due` - textcontainer.append(textblank) - textcontainer.setAttribute("data-hidden", "true") + textblank.innerText = `${numberofassessments} hidden assessment${s} due`; + textcontainer.append(textblank); + textcontainer.setAttribute("data-hidden", "true"); - parent.append(textcontainer) -} \ No newline at end of file + parent.append(textcontainer); +} diff --git a/src/seqta/utils/Loaders/LoadHomePage.ts b/src/seqta/utils/Loaders/LoadHomePage.ts index c3664fe1..3d399cdb 100644 --- a/src/seqta/utils/Loaders/LoadHomePage.ts +++ b/src/seqta/utils/Loaders/LoadHomePage.ts @@ -1,58 +1,58 @@ -import { delay } from "../delay" -import stringToHTML from "../stringToHTML" -import { animate, stagger } from "motion" -import { settingsState } from "../listeners/SettingsState" +import { delay } from "../delay"; +import stringToHTML from "../stringToHTML"; +import { animate, stagger } from "motion"; +import { settingsState } from "../listeners/SettingsState"; -import { addShortcuts } from "../Adders/AddShortcuts" +import { addShortcuts } from "../Adders/AddShortcuts"; -import browser from "webextension-polyfill" -import { GetThresholdOfColor } from "@/seqta/ui/colors/getThresholdColour" -import LogoLight from "@/resources/icons/betterseqta-light-icon.png" -import { CreateCustomShortcutDiv } from "@/seqta/utils/CreateEnable/CreateCustomShortcutDiv" +import browser from "webextension-polyfill"; +import { GetThresholdOfColor } from "@/seqta/ui/colors/getThresholdColour"; +import LogoLight from "@/resources/icons/betterseqta-light-icon.png"; +import { CreateCustomShortcutDiv } from "@/seqta/utils/CreateEnable/CreateCustomShortcutDiv"; -import assessmentsicon from "@/seqta/icons/assessmentsIcon" -import coursesicon from "@/seqta/icons/coursesIcon" +import assessmentsicon from "@/seqta/icons/assessmentsIcon"; +import coursesicon from "@/seqta/icons/coursesIcon"; -import { FilterUpcomingAssessments } from "@/seqta/utils/FilterUpcomingAssessments" +import { FilterUpcomingAssessments } from "@/seqta/utils/FilterUpcomingAssessments"; -import { CreateElement } from "@/seqta/utils/CreateEnable/CreateElement" +import { CreateElement } from "@/seqta/utils/CreateEnable/CreateElement"; -import { convertTo12HourFormat } from "../convertTo12HourFormat" +import { convertTo12HourFormat } from "../convertTo12HourFormat"; -let LessonInterval: any -let currentSelectedDate = new Date() +let LessonInterval: any; +let currentSelectedDate = new Date(); export async function loadHomePage() { - console.info("[BetterSEQTA+] Started Loading Home Page") - - // Wait for the DOM to finish clearing - await delay(10) - - document.title = "Home ― SEQTA Learn" - const element = document.querySelector("[data-key=home]") - element?.classList.add("active") - - // Cache DOM queries - const main = document.getElementById("main") - if (!main) { - console.error("[BetterSEQTA+] Main element not found.") - return - } - - // Create root container first - const homeRoot = stringToHTML( - /* html */ `

`, - ) - - // Clear main and add home root - main.innerHTML = "" - main.appendChild(homeRoot?.firstChild!) - - // Get reference to home container for all subsequent additions - const homeContainer = document.getElementById("home-root") - if (!homeContainer) return - - const skeletonStructure = stringToHTML(/* html */ ` + console.info("[BetterSEQTA+] Started Loading Home Page"); + + // Wait for the DOM to finish clearing + await delay(10); + + document.title = "Home ― SEQTA Learn"; + const element = document.querySelector("[data-key=home]"); + element?.classList.add("active"); + + // Cache DOM queries + const main = document.getElementById("main"); + if (!main) { + console.error("[BetterSEQTA+] Main element not found."); + return; + } + + // Create root container first + const homeRoot = stringToHTML( + /* html */ `
`, + ); + + // Clear main and add home root + main.innerHTML = ""; + main.appendChild(homeRoot?.firstChild!); + + // Get reference to home container for all subsequent additions + const homeContainer = document.getElementById("home-root"); + if (!homeContainer) return; + + const skeletonStructure = stringToHTML(/* html */ `
@@ -88,956 +88,958 @@ export async function loadHomePage() {
-
`) - - // Add skeleton structure - homeContainer.appendChild(skeletonStructure.firstChild!) - - // Run animations if enabled - if (settingsState.animations) { - animate( - ".home-container > div", - { opacity: [0, 1], y: [10, 0], scale: [0.99, 1] }, - { - delay: stagger(0.15, { startDelay: 0.1 }), - type: "spring", - stiffness: 341, - damping: 20, - mass: 1, - }, - ) + `); + + // Add skeleton structure + homeContainer.appendChild(skeletonStructure.firstChild!); + + // Run animations if enabled + if (settingsState.animations) { + animate( + ".home-container > div", + { opacity: [0, 1], y: [10, 0], scale: [0.99, 1] }, + { + delay: stagger(0.15, { startDelay: 0.1 }), + type: "spring", + stiffness: 341, + damping: 20, + mass: 1, + }, + ); + } + + // Setup event listeners with cleanup + const cleanup = setupTimetableListeners(); + + // Initialize shortcuts immediately + try { + addShortcuts(settingsState.shortcuts); + } catch (err: any) { + console.error("[BetterSEQTA+] Error adding shortcuts:", err.message || err); + } + AddCustomShortcutsToPage(); + + // Get current date + const date = new Date(); + const TodayFormatted = formatDate(date); + + // Start all data fetching in parallel + const [timetablePromise, assessmentsPromise, classesPromise, prefsPromise] = [ + // Timetable data + fetch(`${location.origin}/seqta/student/load/timetable?`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + from: TodayFormatted, + until: TodayFormatted, + student: 69, + }), + }).then((res) => res.json()), + + // Assessments data + GetUpcomingAssessments(), + + // Classes data + GetActiveClasses(), + + // Preferences data + fetch(`${location.origin}/seqta/student/load/prefs?`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ asArray: true, request: "userPrefs" }), + }).then((res) => res.json()), + ]; + + // Process all data in parallel + const [timetableData, assessments, classes, prefs] = await Promise.all([ + timetablePromise, + assessmentsPromise, + classesPromise, + prefsPromise, + ]); + + // Process timetable data + const dayContainer = document.getElementById("day-container"); + if (dayContainer && timetableData.payload.items.length > 0) { + const lessonArray = timetableData.payload.items.sort((a: any, b: any) => + a.from.localeCompare(b.from), + ); + const colours = await GetLessonColours(); + + // Process and display lessons + dayContainer.innerHTML = ""; + for (let i = 0; i < lessonArray.length; i++) { + const lesson = lessonArray[i]; + const subjectname = `timetable.subject.colour.${lesson.code}`; + const subject = colours.find( + (element: any) => element.name === subjectname, + ); + + lesson.colour = subject + ? `--item-colour: ${subject.value};` + : "--item-colour: #8e8e8e;"; + lesson.from = lesson.from.substring(0, 5); + lesson.until = lesson.until.substring(0, 5); + + if (settingsState.timeFormat === "12") { + lesson.from = convertTo12HourFormat(lesson.from); + lesson.until = convertTo12HourFormat(lesson.until); + } + + lesson.attendanceTitle = CheckUnmarkedAttendance(lesson.attendance); + + const div = makeLessonDiv(lesson, i + 1); + if (GetThresholdOfColor(subject?.value) > 300) { + const firstChild = div.firstChild as HTMLElement; + if (firstChild) { + firstChild.classList.add("day-inverted"); + } + } + dayContainer.appendChild(div.firstChild!); } - - // Setup event listeners with cleanup - const cleanup = setupTimetableListeners() - - // Initialize shortcuts immediately - try { - addShortcuts(settingsState.shortcuts) - } catch (err: any) { - console.error("[BetterSEQTA+] Error adding shortcuts:", err.message || err) - } - AddCustomShortcutsToPage() - - // Get current date - const date = new Date() - const TodayFormatted = formatDate(date) - - // Start all data fetching in parallel - const [timetablePromise, assessmentsPromise, classesPromise, prefsPromise] = [ - // Timetable data - fetch(`${location.origin}/seqta/student/load/timetable?`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - from: TodayFormatted, - until: TodayFormatted, - student: 69, - }), - }).then((res) => res.json()), - - // Assessments data - GetUpcomingAssessments(), - - // Classes data - GetActiveClasses(), - - // Preferences data - fetch(`${location.origin}/seqta/student/load/prefs?`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ asArray: true, request: "userPrefs" }), - }).then((res) => res.json()), - ] - - // Process all data in parallel - const [timetableData, assessments, classes, prefs] = await Promise.all([ - timetablePromise, - assessmentsPromise, - classesPromise, - prefsPromise, - ]) - - // Process timetable data - const dayContainer = document.getElementById("day-container") - if (dayContainer && timetableData.payload.items.length > 0) { - const lessonArray = timetableData.payload.items.sort((a: any, b: any) => - a.from.localeCompare(b.from), - ) - const colours = await GetLessonColours() - - // Process and display lessons - dayContainer.innerHTML = "" + + // Check current lessons + if (currentSelectedDate.getDate() === date.getDate()) { for (let i = 0; i < lessonArray.length; i++) { - const lesson = lessonArray[i] - const subjectname = `timetable.subject.colour.${lesson.code}` - const subject = colours.find( - (element: any) => element.name === subjectname, - ) - - lesson.colour = subject - ? `--item-colour: ${subject.value};` - : "--item-colour: #8e8e8e;" - lesson.from = lesson.from.substring(0, 5) - lesson.until = lesson.until.substring(0, 5) - - if (settingsState.timeFormat === "12") { - lesson.from = convertTo12HourFormat(lesson.from) - lesson.until = convertTo12HourFormat(lesson.until) - } - - lesson.attendanceTitle = CheckUnmarkedAttendance(lesson.attendance) - - const div = makeLessonDiv(lesson, i + 1) - if (GetThresholdOfColor(subject?.value) > 300) { - const firstChild = div.firstChild as HTMLElement - if (firstChild) { - firstChild.classList.add("day-inverted") - } - } - dayContainer.appendChild(div.firstChild!) + CheckCurrentLesson(lessonArray[i], i + 1); } - - // Check current lessons - if (currentSelectedDate.getDate() === date.getDate()) { - for (let i = 0; i < lessonArray.length; i++) { - CheckCurrentLesson(lessonArray[i], i + 1) - } - CheckCurrentLessonAll(lessonArray) - } - } else if (dayContainer) { - dayContainer.innerHTML = /* html */ ` + CheckCurrentLessonAll(lessonArray); + } + } else if (dayContainer) { + dayContainer.innerHTML = /* html */ `

No lessons available.

-
` - } - dayContainer?.classList.remove("loading") - - // Process assessments data - const activeClass = classes.find((c: any) => c.hasOwnProperty("active")) - const activeSubjects = activeClass?.subjects || [] - const activeSubjectCodes = activeSubjects.map((s: any) => s.code) - const currentAssessments = assessments - .filter((a: any) => activeSubjectCodes.includes(a.code)) - .sort(comparedate) - - const upcomingItems = document.getElementById("upcoming-items") - if (upcomingItems) { - await CreateUpcomingSection(currentAssessments, activeSubjects) - upcomingItems.classList.remove("loading") - } - - // Process notices data - const labelArray = prefs.payload - .filter((item: any) => item.name === "notices.filters") - .map((item: any) => item.value) - - if (labelArray.length > 0) { - const noticeContainer = document.getElementById("notice-container") - if (noticeContainer) { - const dateControl = document.querySelector( - 'input[type="date"]', - ) as HTMLInputElement - if (dateControl) { - dateControl.value = TodayFormatted - setupNotices(labelArray[0].split(" "), TodayFormatted) - } - noticeContainer.classList.remove("loading") - } - } - - return cleanup + `; + } + dayContainer?.classList.remove("loading"); + + // Process assessments data + const activeClass = classes.find((c: any) => c.hasOwnProperty("active")); + const activeSubjects = activeClass?.subjects || []; + const activeSubjectCodes = activeSubjects.map((s: any) => s.code); + const currentAssessments = assessments + .filter((a: any) => activeSubjectCodes.includes(a.code)) + .sort(comparedate); + + const upcomingItems = document.getElementById("upcoming-items"); + if (upcomingItems) { + await CreateUpcomingSection(currentAssessments, activeSubjects); + upcomingItems.classList.remove("loading"); } - async function GetUpcomingAssessments() { - let func = fetch( - `${location.origin}/seqta/student/assessment/list/upcoming?`, + // Process notices data + const labelArray = prefs.payload + .filter((item: any) => item.name === "notices.filters") + .map((item: any) => item.value); + + if (labelArray.length > 0) { + const noticeContainer = document.getElementById("notice-container"); + if (noticeContainer) { + const dateControl = document.querySelector( + 'input[type="date"]', + ) as HTMLInputElement; + if (dateControl) { + dateControl.value = TodayFormatted; + setupNotices(labelArray[0].split(" "), TodayFormatted); + } + noticeContainer.classList.remove("loading"); + } + } + + return cleanup; +} + +async function GetUpcomingAssessments() { + let func = fetch( + `${location.origin}/seqta/student/assessment/list/upcoming?`, + { + method: "POST", + headers: { + "Content-Type": "application/json; charset=utf-8", + }, + body: JSON.stringify({ student: 69 }), + }, + ); + + return func + .then((result) => result.json()) + .then((response) => response.payload); +} + +function setupTimetableListeners() { + const listeners: Array<() => void> = []; + const timetableBack = document.getElementById("home-timetable-back"); + const timetableForward = document.getElementById("home-timetable-forward"); + + function changeTimetable(value: number) { + currentSelectedDate.setDate(currentSelectedDate.getDate() + value); + const formattedDate = formatDate(currentSelectedDate); + callHomeTimetable(formattedDate, true); + SetTimetableSubtitle(); + } + + const backHandler = () => changeTimetable(-1); + const forwardHandler = () => changeTimetable(1); + + timetableBack?.addEventListener("click", backHandler); + timetableForward?.addEventListener("click", forwardHandler); + + listeners.push( + () => timetableBack?.removeEventListener("click", backHandler), + () => timetableForward?.removeEventListener("click", forwardHandler), + ); + + return () => listeners.forEach((cleanup) => cleanup()); +} + +function formatDate(date: Date): string { + const year = date.getFullYear(); + const month = (date.getMonth() + 1).toString().padStart(2, "0"); + const day = date.getDate().toString().padStart(2, "0"); + return `${year}-${month}-${day}`; +} + +async function GetActiveClasses() { + try { + const response = await fetch( + `${location.origin}/seqta/student/load/subjects?`, { method: "POST", - headers: { - "Content-Type": "application/json; charset=utf-8", - }, - body: JSON.stringify({ student: 69 }), + headers: { "Content-Type": "application/json; charset=utf-8" }, + body: JSON.stringify({}), }, - ) - - return func - .then((result) => result.json()) - .then((response) => response.payload) - } + ); - function setupTimetableListeners() { - const listeners: Array<() => void> = [] - const timetableBack = document.getElementById("home-timetable-back") - const timetableForward = document.getElementById("home-timetable-forward") - - function changeTimetable(value: number) { - currentSelectedDate.setDate(currentSelectedDate.getDate() + value) - const formattedDate = formatDate(currentSelectedDate) - callHomeTimetable(formattedDate, true) - SetTimetableSubtitle() + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); } - - const backHandler = () => changeTimetable(-1) - const forwardHandler = () => changeTimetable(1) - - timetableBack?.addEventListener("click", backHandler) - timetableForward?.addEventListener("click", forwardHandler) - - listeners.push( - () => timetableBack?.removeEventListener("click", backHandler), - () => timetableForward?.removeEventListener("click", forwardHandler), - ) - - return () => listeners.forEach((cleanup) => cleanup()) - } - function formatDate(date: Date): string { - const year = date.getFullYear() - const month = (date.getMonth() + 1).toString().padStart(2, "0") - const day = date.getDate().toString().padStart(2, "0") - return `${year}-${month}-${day}` + const data = await response.json(); + return data.payload; + } catch (error) { + console.error("Oops! There was a problem fetching active classes:", error); } +} - async function GetActiveClasses() { - try { - const response = await fetch( - `${location.origin}/seqta/student/load/subjects?`, - { - method: "POST", - headers: { "Content-Type": "application/json; charset=utf-8" }, - body: JSON.stringify({}), - }, - ) - - if (!response.ok) { - throw new Error(`HTTP error! Status: ${response.status}`) - } - - const data = await response.json() - return data.payload - } catch (error) { - console.error("Oops! There was a problem fetching active classes:", error) +function setupNotices(labelArray: string[], date: string) { + const dateControl = document.querySelector( + 'input[type="date"]', + ) as HTMLInputElement; + + const fetchNotices = async (date: string) => { + const response = await fetch( + `${location.origin}/seqta/student/load/notices?`, + { + method: "POST", + headers: { "Content-Type": "application/json; charset=utf-8" }, + body: JSON.stringify({ date }), + }, + ); + const data = await response.json(); + processNotices(data, labelArray); + }; + + // Debounce the input handler + const debouncedInputChange = debounce((e: Event) => { + const target = e.target as HTMLInputElement; + fetchNotices(target.value); + }, 250); + + dateControl?.addEventListener("input", debouncedInputChange); + fetchNotices(date); + + return () => dateControl?.removeEventListener("input", debouncedInputChange); +} + +function debounce any>( + func: T, + wait: number, +): (...args: Parameters) => void { + let timeout: any; + return (...args: Parameters) => { + clearTimeout(timeout); + timeout = setTimeout(() => func(...args), wait); + }; +} + +function comparedate(obj1: any, obj2: any) { + if (obj1.date < obj2.date) { + return -1; + } + if (obj1.date > obj2.date) { + return 1; + } + return 0; +} + +async function AddCustomShortcutsToPage() { + let customshortcuts: any = settingsState.customshortcuts; + if (customshortcuts.length > 0) { + for (let i = 0; i < customshortcuts.length; i++) { + const element = customshortcuts[i]; + CreateCustomShortcutDiv(element); } } +} - function setupNotices(labelArray: string[], date: string) { - const dateControl = document.querySelector( - 'input[type="date"]', - ) as HTMLInputElement - - const fetchNotices = async (date: string) => { - const response = await fetch( - `${location.origin}/seqta/student/load/notices?`, - { - method: "POST", - headers: { "Content-Type": "application/json; charset=utf-8" }, - body: JSON.stringify({ date }), - }, - ) - const data = await response.json() - processNotices(data, labelArray) - } - - // Debounce the input handler - const debouncedInputChange = debounce((e: Event) => { - const target = e.target as HTMLInputElement - fetchNotices(target.value) - }, 250) - - dateControl?.addEventListener("input", debouncedInputChange) - fetchNotices(date) - - return () => dateControl?.removeEventListener("input", debouncedInputChange) +function processNotices(response: any, labelArray: string[]) { + const NoticeContainer = document.getElementById("notice-container"); + if (!NoticeContainer) return; + + // Clear existing notices + NoticeContainer.innerHTML = ""; + + const notices = response.payload; + if (!notices.length) { + const dummyNotice = document.createElement("div"); + dummyNotice.textContent = "No notices for today."; + dummyNotice.classList.add("dummynotice"); + NoticeContainer.append(dummyNotice); + return; } - function debounce any>( - func: T, - wait: number, - ): (...args: Parameters) => void { - let timeout: any - return (...args: Parameters) => { - clearTimeout(timeout) - timeout = setTimeout(() => func(...args), wait) + // Create document fragment for batch DOM updates + const fragment = document.createDocumentFragment(); + + // Process notices in batch + notices.forEach((notice: any) => { + if (labelArray.includes(JSON.stringify(notice.label))) { + const colour = processNoticeColor(notice.colour); + const noticeElement = createNoticeElement(notice, colour); + fragment.appendChild(noticeElement); + } + }); + + // Single DOM update + NoticeContainer.appendChild(fragment); +} + +function processNoticeColor(colour: string): string | undefined { + if (typeof colour === "string") { + const rgb = GetThresholdOfColor(colour); + if (rgb < 100 && settingsState.DarkMode) { + return undefined; } } + return colour; +} - function comparedate(obj1: any, obj2: any) { - if (obj1.date < obj2.date) { - return -1 - } - if (obj1.date > obj2.date) { - return 1 - } - return 0 - } - - async function AddCustomShortcutsToPage() { - let customshortcuts: any = settingsState.customshortcuts - if (customshortcuts.length > 0) { - for (let i = 0; i < customshortcuts.length; i++) { - const element = customshortcuts[i] - CreateCustomShortcutDiv(element) - } - } - } - - function processNotices(response: any, labelArray: string[]) { - const NoticeContainer = document.getElementById("notice-container") - if (!NoticeContainer) return - - // Clear existing notices - NoticeContainer.innerHTML = "" - - const notices = response.payload - if (!notices.length) { - const dummyNotice = document.createElement("div") - dummyNotice.textContent = "No notices for today." - dummyNotice.classList.add("dummynotice") - NoticeContainer.append(dummyNotice) - return - } - - // Create document fragment for batch DOM updates - const fragment = document.createDocumentFragment() - - // Process notices in batch - notices.forEach((notice: any) => { - if (labelArray.includes(JSON.stringify(notice.label))) { - const colour = processNoticeColor(notice.colour) - const noticeElement = createNoticeElement(notice, colour) - fragment.appendChild(noticeElement) - } - }) - - // Single DOM update - NoticeContainer.appendChild(fragment) - } - - function processNoticeColor(colour: string): string | undefined { - if (typeof colour === "string") { - const rgb = GetThresholdOfColor(colour) - if (rgb < 100 && settingsState.DarkMode) { - return undefined - } - } - return colour - } - - function createNoticeElement(notice: any, colour: string | undefined): Node { - const htmlContent = ` +function createNoticeElement(notice: any, colour: string | undefined): Node { + const htmlContent = `

${notice.title}

${notice.label_title !== undefined ? `
${notice.label_title}
` : ""}
${notice.staff}
${notice.contents.replace(/\[\[[\w]+[:][\w]+[\]\]]+/g, "").replace(/ +/, " ")}
-
` - - const element = stringToHTML(htmlContent).firstChild - if (element instanceof HTMLElement) { - element.style.setProperty("--colour", colour ?? "") - } - return element! - } + `; - function callHomeTimetable(date: string, change?: any) { - // Creates a HTTP Post Request to the SEQTA page for the students timetable - var xhr = new XMLHttpRequest() - xhr.open("POST", `${location.origin}/seqta/student/load/timetable?`, true) - // Sets the response type to json - xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8") - - xhr.onreadystatechange = function () { - // Once the response is ready - if (xhr.readyState === 4) { - var serverResponse = JSON.parse(xhr.response) - let lessonArray: Array = [] - const DayContainer = document.getElementById("day-container")! - // If items in response: - if (serverResponse.payload.items.length > 0) { - if (DayContainer.innerText || change) { - for (let i = 0; i < serverResponse.payload.items.length; i++) { - lessonArray.push(serverResponse.payload.items[i]) - } - lessonArray.sort(function (a, b) { - return a.from.localeCompare(b.from) - }) - // If items in the response, set each corresponding value into divs - // lessonArray = lessonArray.splice(1) - GetLessonColours().then((colours) => { - let subjects = colours - for (let i = 0; i < lessonArray.length; i++) { - let subjectname = `timetable.subject.colour.${lessonArray[i].code}` - - let subject = subjects.find( - (element: any) => element.name === subjectname, - ) - if (!subject) { - lessonArray[i].colour = "--item-colour: #8e8e8e;" - } else { - lessonArray[i].colour = `--item-colour: ${subject.value};` - let result = GetThresholdOfColor(subject.value) - - if (result > 300) { - lessonArray[i].invert = true - } - } - // Removes seconds from the start and end times - lessonArray[i].from = lessonArray[i].from.substring(0, 5) - lessonArray[i].until = lessonArray[i].until.substring(0, 5) - - if (settingsState.timeFormat === "12") { - lessonArray[i].from = convertTo12HourFormat(lessonArray[i].from) - lessonArray[i].until = convertTo12HourFormat( - lessonArray[i].until, - ) - } - - // Checks if attendance is unmarked, and sets the string to " ". - lessonArray[i].attendanceTitle = CheckUnmarkedAttendance( - lessonArray[i].attendance, - ) - } - // If on home page, apply each lesson to HTML with information in each div - DayContainer.innerText = "" - for (let i = 0; i < lessonArray.length; i++) { - var div = makeLessonDiv(lessonArray[i], i + 1) - // Append each of the lessons into the day-container - if (lessonArray[i].invert) { - const div1 = div.firstChild! as HTMLElement - div1.classList.add("day-inverted") - } - - DayContainer.append(div.firstChild as HTMLElement) - } - - const today = new Date() - if (currentSelectedDate.getDate() == today.getDate()) { - for (let i = 0; i < lessonArray.length; i++) { - CheckCurrentLesson(lessonArray[i], i + 1) - } - // For each lesson, check the start and end times - CheckCurrentLessonAll(lessonArray) - } - }) + const element = stringToHTML(htmlContent).firstChild; + if (element instanceof HTMLElement) { + element.style.setProperty("--colour", colour ?? ""); + } + return element!; +} + +function callHomeTimetable(date: string, change?: any) { + // Creates a HTTP Post Request to the SEQTA page for the students timetable + var xhr = new XMLHttpRequest(); + xhr.open("POST", `${location.origin}/seqta/student/load/timetable?`, true); + // Sets the response type to json + xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8"); + + xhr.onreadystatechange = function () { + // Once the response is ready + if (xhr.readyState === 4) { + var serverResponse = JSON.parse(xhr.response); + let lessonArray: Array = []; + const DayContainer = document.getElementById("day-container")!; + // If items in response: + if (serverResponse.payload.items.length > 0) { + if (DayContainer.innerText || change) { + for (let i = 0; i < serverResponse.payload.items.length; i++) { + lessonArray.push(serverResponse.payload.items[i]); } - } else { - DayContainer.innerHTML = "" - var dummyDay = document.createElement("div") - dummyDay.classList.add("day-empty") - let img = document.createElement("img") - img.src = browser.runtime.getURL(LogoLight) - let text = document.createElement("p") - text.innerText = "No lessons available." - dummyDay.append(img) - dummyDay.append(text) - DayContainer.append(dummyDay) - } - } - } - xhr.send( - JSON.stringify({ - // Information sent to SEQTA page as a request with the dates and student number - from: date, - until: date, - // Funny number - student: 69, - }), - ) - } + lessonArray.sort(function (a, b) { + return a.from.localeCompare(b.from); + }); + // If items in the response, set each corresponding value into divs + // lessonArray = lessonArray.splice(1) + GetLessonColours().then((colours) => { + let subjects = colours; + for (let i = 0; i < lessonArray.length; i++) { + let subjectname = `timetable.subject.colour.${lessonArray[i].code}`; - function CheckCurrentLessonAll(lessons: any) { - // Checks each lesson and sets an interval to run every 60 seconds to continue updating - LessonInterval = setInterval( - function () { - for (let i = 0; i < lessons.length; i++) { - CheckCurrentLesson(lessons[i], i + 1) - } - }.bind(lessons), - 60000, - ) - } + let subject = subjects.find( + (element: any) => element.name === subjectname, + ); + if (!subject) { + lessonArray[i].colour = "--item-colour: #8e8e8e;"; + } else { + lessonArray[i].colour = `--item-colour: ${subject.value};`; + let result = GetThresholdOfColor(subject.value); - async function CheckCurrentLesson(lesson: any, num: number) { - const { - from: startTime, - until: endTime, - code, - description, - room, - staff, - } = lesson - const currentDate = new Date() - - // Create Date objects for start and end times - const [startHour, startMinute] = startTime.split(":").map(Number) - const [endHour, endMinute] = endTime.split(":").map(Number) - - const startDate = new Date(currentDate) - startDate.setHours(startHour, startMinute, 0) - - const endDate = new Date(currentDate) - endDate.setHours(endHour, endMinute, 0) - - // Check if the current time is within the lesson time range - const isValidTime = startDate < currentDate && endDate > currentDate - - const elementId = `${code}${num}` - const element = document.getElementById(elementId) - - if (!element) { - clearInterval(LessonInterval) - return - } - - const isCurrentDate = - currentSelectedDate.toLocaleDateString("en-au") === - currentDate.toLocaleDateString("en-au") - - if (isCurrentDate) { - if (isValidTime) { - element.classList.add("activelesson") + if (result > 300) { + lessonArray[i].invert = true; + } + } + // Removes seconds from the start and end times + lessonArray[i].from = lessonArray[i].from.substring(0, 5); + lessonArray[i].until = lessonArray[i].until.substring(0, 5); + + if (settingsState.timeFormat === "12") { + lessonArray[i].from = convertTo12HourFormat( + lessonArray[i].from, + ); + lessonArray[i].until = convertTo12HourFormat( + lessonArray[i].until, + ); + } + + // Checks if attendance is unmarked, and sets the string to " ". + lessonArray[i].attendanceTitle = CheckUnmarkedAttendance( + lessonArray[i].attendance, + ); + } + // If on home page, apply each lesson to HTML with information in each div + DayContainer.innerText = ""; + for (let i = 0; i < lessonArray.length; i++) { + var div = makeLessonDiv(lessonArray[i], i + 1); + // Append each of the lessons into the day-container + if (lessonArray[i].invert) { + const div1 = div.firstChild! as HTMLElement; + div1.classList.add("day-inverted"); + } + + DayContainer.append(div.firstChild as HTMLElement); + } + + const today = new Date(); + if (currentSelectedDate.getDate() == today.getDate()) { + for (let i = 0; i < lessonArray.length; i++) { + CheckCurrentLesson(lessonArray[i], i + 1); + } + // For each lesson, check the start and end times + CheckCurrentLessonAll(lessonArray); + } + }); + } } else { - element.classList.remove("activelesson") + DayContainer.innerHTML = ""; + var dummyDay = document.createElement("div"); + dummyDay.classList.add("day-empty"); + let img = document.createElement("img"); + img.src = browser.runtime.getURL(LogoLight); + let text = document.createElement("p"); + text.innerText = "No lessons available."; + dummyDay.append(img); + dummyDay.append(text); + DayContainer.append(dummyDay); } } - - const minutesUntilStart = Math.floor( - (startDate.getTime() - currentDate.getTime()) / 60000, - ) - - if ( - minutesUntilStart !== 5 || - settingsState.lessonalert || - !window.Notification - ) - return - - if (Notification.permission !== "granted") - await Notification.requestPermission() - - try { - new Notification("Next Lesson in 5 Minutes:", { - body: `Subject: ${description}${room ? `\nRoom: ${room}` : ""}${staff ? `\nTeacher: ${staff}` : ""}`, - }) - } catch (error) { - console.error(error) + }; + xhr.send( + JSON.stringify({ + // Information sent to SEQTA page as a request with the dates and student number + from: date, + until: date, + // Funny number + student: 69, + }), + ); +} + +function CheckCurrentLessonAll(lessons: any) { + // Checks each lesson and sets an interval to run every 60 seconds to continue updating + LessonInterval = setInterval( + function () { + for (let i = 0; i < lessons.length; i++) { + CheckCurrentLesson(lessons[i], i + 1); + } + }.bind(lessons), + 60000, + ); +} + +async function CheckCurrentLesson(lesson: any, num: number) { + const { + from: startTime, + until: endTime, + code, + description, + room, + staff, + } = lesson; + const currentDate = new Date(); + + // Create Date objects for start and end times + const [startHour, startMinute] = startTime.split(":").map(Number); + const [endHour, endMinute] = endTime.split(":").map(Number); + + const startDate = new Date(currentDate); + startDate.setHours(startHour, startMinute, 0); + + const endDate = new Date(currentDate); + endDate.setHours(endHour, endMinute, 0); + + // Check if the current time is within the lesson time range + const isValidTime = startDate < currentDate && endDate > currentDate; + + const elementId = `${code}${num}`; + const element = document.getElementById(elementId); + + if (!element) { + clearInterval(LessonInterval); + return; + } + + const isCurrentDate = + currentSelectedDate.toLocaleDateString("en-au") === + currentDate.toLocaleDateString("en-au"); + + if (isCurrentDate) { + if (isValidTime) { + element.classList.add("activelesson"); + } else { + element.classList.remove("activelesson"); } } - function makeLessonDiv(lesson: any, num: number) { - if (!lesson) throw new Error("No lesson provided.") - - const { - code, - colour, - description, - staff, - room, - from, - until, - attendanceTitle, - programmeID, - metaID, - assessments, - } = lesson - - // Construct the base lesson string with default values using ternary operators - let lessonString = /* html */ ` + const minutesUntilStart = Math.floor( + (startDate.getTime() - currentDate.getTime()) / 60000, + ); + + if ( + minutesUntilStart !== 5 || + settingsState.lessonalert || + !window.Notification + ) + return; + + if (Notification.permission !== "granted") + await Notification.requestPermission(); + + try { + new Notification("Next Lesson in 5 Minutes:", { + body: `Subject: ${description}${room ? `\nRoom: ${room}` : ""}${staff ? `\nTeacher: ${staff}` : ""}`, + }); + } catch (error) { + console.error(error); + } +} + +function makeLessonDiv(lesson: any, num: number) { + if (!lesson) throw new Error("No lesson provided."); + + const { + code, + colour, + description, + staff, + room, + from, + until, + attendanceTitle, + programmeID, + metaID, + assessments, + } = lesson; + + // Construct the base lesson string with default values using ternary operators + let lessonString = /* html */ `

${description || "Unknown"}

${staff || "Unknown"}

${room || "Unknown"}

${from || "Unknown"} - ${until || "Unknown"}

${attendanceTitle || "Unknown"}
- ` - - // Add buttons for assessments and courses if applicable - if (programmeID !== 0) { - lessonString += /* html */ ` + `; + + // Add buttons for assessments and courses if applicable + if (programmeID !== 0) { + lessonString += /* html */ `
${assessmentsicon}
${coursesicon}
- ` - } - - // Add assessments if they exist - if (assessments && assessments.length > 0) { - const assessmentString = assessments - .map( - (element: any) => - `

${element.title}

`, - ) - .join("") - - lessonString += /* html */ ` + `; + } + + // Add assessments if they exist + if (assessments && assessments.length > 0) { + const assessmentString = assessments + .map( + (element: any) => + `

${element.title}

`, + ) + .join(""); + + lessonString += /* html */ `
${assessmentString}
- ` + `; + } + + lessonString += "
"; + + return stringToHTML(lessonString); +} + +function buildAssessmentURL(programmeID: any, metaID: any, itemID = "") { + const base = "../#?page=/assessments/"; + return itemID + ? `${base}${programmeID}:${metaID}&item=${itemID}` + : `${base}${programmeID}:${metaID}`; +} + +function CheckUnmarkedAttendance(lessonattendance: any) { + if (lessonattendance) { + var lesson = lessonattendance.label; + } else { + lesson = " "; + } + return lesson; +} + +async function CreateUpcomingSection(assessments: any, activeSubjects: any) { + let upcomingitemcontainer = document.querySelector("#upcoming-items"); + let overdueDates = []; + let upcomingDates = {}; + + var Today = new Date(); + + // Removes overdue assessments from the upcoming assessments array and pushes to overdue array + for (let i = 0; i < assessments.length; i++) { + const assessment = assessments[i]; + let assessmentdue = new Date(assessment.due); + + CheckSpecialDay(Today, assessmentdue); + if (assessmentdue < Today) { + if (!CheckSpecialDay(Today, assessmentdue)) { + overdueDates.push(assessment); + assessments.splice(i, 1); + i--; + } } - - lessonString += "" - - return stringToHTML(lessonString) } - function buildAssessmentURL(programmeID: any, metaID: any, itemID = "") { - const base = "../#?page=/assessments/" - return itemID - ? `${base}${programmeID}:${metaID}&item=${itemID}` - : `${base}${programmeID}:${metaID}` - } + var TomorrowDate = new Date(); + TomorrowDate.setDate(TomorrowDate.getDate() + 1); - function CheckUnmarkedAttendance(lessonattendance: any) { - if (lessonattendance) { - var lesson = lessonattendance.label + const colours = await GetLessonColours(); + + let subjects = colours; + for (let i = 0; i < assessments.length; i++) { + let subjectname = `timetable.subject.colour.${assessments[i].code}`; + + let subject = subjects.find((element: any) => element.name === subjectname); + + if (!subject) { + assessments[i].colour = "--item-colour: #8e8e8e;"; } else { - lesson = " " + assessments[i].colour = `--item-colour: ${subject.value};`; + GetThresholdOfColor(subject.value); // result (originally) result = GetThresholdOfColor } - return lesson } - async function CreateUpcomingSection(assessments: any, activeSubjects: any) { - let upcomingitemcontainer = document.querySelector("#upcoming-items") - let overdueDates = [] - let upcomingDates = {} - - var Today = new Date() - - // Removes overdue assessments from the upcoming assessments array and pushes to overdue array - for (let i = 0; i < assessments.length; i++) { - const assessment = assessments[i] - let assessmentdue = new Date(assessment.due) - - CheckSpecialDay(Today, assessmentdue) - if (assessmentdue < Today) { - if (!CheckSpecialDay(Today, assessmentdue)) { - overdueDates.push(assessment) - assessments.splice(i, 1) - i-- - } + for (let i = 0; i < activeSubjects.length; i++) { + const element = activeSubjects[i]; + let subjectname = `timetable.subject.colour.${element.code}`; + let colour = colours.find((element: any) => element.name === subjectname); + if (!colour) { + element.colour = "--item-colour: #8e8e8e;"; + } else { + element.colour = `--item-colour: ${colour.value};`; + let result = GetThresholdOfColor(colour.value); + if (result > 300) { + element.invert = true; } } - - var TomorrowDate = new Date() - TomorrowDate.setDate(TomorrowDate.getDate() + 1) - - const colours = await GetLessonColours() - - let subjects = colours - for (let i = 0; i < assessments.length; i++) { - let subjectname = `timetable.subject.colour.${assessments[i].code}` - - let subject = subjects.find((element: any) => element.name === subjectname) - - if (!subject) { - assessments[i].colour = "--item-colour: #8e8e8e;" - } else { - assessments[i].colour = `--item-colour: ${subject.value};` - GetThresholdOfColor(subject.value) // result (originally) result = GetThresholdOfColor - } - } - - for (let i = 0; i < activeSubjects.length; i++) { - const element = activeSubjects[i] - let subjectname = `timetable.subject.colour.${element.code}` - let colour = colours.find((element: any) => element.name === subjectname) - if (!colour) { - element.colour = "--item-colour: #8e8e8e;" - } else { - element.colour = `--item-colour: ${colour.value};` - let result = GetThresholdOfColor(colour.value) - if (result > 300) { - element.invert = true - } - } - } - - CreateFilters(activeSubjects) - - // @ts-ignore - let type - // @ts-ignore - let class_ - - for (let i = 0; i < assessments.length; i++) { - const element: any = assessments[i] - if (!upcomingDates[element.due as keyof typeof upcomingDates]) { - let dateObj: any = new Object() - dateObj.div = CreateElement( - // TODO: not sure whats going on here? - // eslint-disable-next-line - (type = "div"), - // eslint-disable-next-line - (class_ = "upcoming-date-container"), - ) - dateObj.assessments = [] - ;(upcomingDates[element.due as keyof typeof upcomingDates] as any) = - dateObj - } - let assessmentDateDiv = - upcomingDates[element.due as keyof typeof upcomingDates] - - if (assessmentDateDiv) { - (assessmentDateDiv as any).assessments.push(element) - } - } - - for (var date in upcomingDates) { - let assessmentdue = new Date( - ( - upcomingDates[date as keyof typeof upcomingDates] as any - ).assessments[0].due, - ) - let specialcase = CheckSpecialDay(Today, assessmentdue) - let assessmentDate - - if (specialcase) { - let datecase: string = specialcase! - assessmentDate = createAssessmentDateDiv( - date, - upcomingDates[date as keyof typeof upcomingDates], - // eslint-disable-next-line - datecase, - ) - } else { - assessmentDate = createAssessmentDateDiv( - date, - upcomingDates[date as keyof typeof upcomingDates], - ) - } - - if (specialcase === "Yesterday") { - upcomingitemcontainer!.insertBefore( - assessmentDate, - upcomingitemcontainer!.firstChild, - ) - } else { - upcomingitemcontainer!.append(assessmentDate) - } - } - FilterUpcomingAssessments(settingsState.subjectfilters) } - function createAssessmentDateDiv(date: string, value: any, datecase?: any) { - var options = { - weekday: "long" as "long", - month: "long" as "long", - day: "numeric" as "numeric", + CreateFilters(activeSubjects); + + // @ts-ignore + let type; + // @ts-ignore + let class_; + + for (let i = 0; i < assessments.length; i++) { + const element: any = assessments[i]; + if (!upcomingDates[element.due as keyof typeof upcomingDates]) { + let dateObj: any = new Object(); + dateObj.div = CreateElement( + // TODO: not sure whats going on here? + // eslint-disable-next-line + (type = "div"), + // eslint-disable-next-line + (class_ = "upcoming-date-container"), + ); + dateObj.assessments = []; + (upcomingDates[element.due as keyof typeof upcomingDates] as any) = + dateObj; } - const FormattedDate = new Date(date) - - const assessments = value.assessments - const container = value.div - - let DateTitleDiv = document.createElement("div") - DateTitleDiv.classList.add("upcoming-date-title") - - if (datecase) { - let datetitle = document.createElement("h5") - datetitle.classList.add("upcoming-special-day") - datetitle.innerText = datecase - DateTitleDiv.append(datetitle) - container.setAttribute("data-day", datecase) + let assessmentDateDiv = + upcomingDates[element.due as keyof typeof upcomingDates]; + + if (assessmentDateDiv) { + (assessmentDateDiv as any).assessments.push(element); } - - let DateTitle = document.createElement("h5") - DateTitle.innerText = FormattedDate.toLocaleDateString("en-AU", options) - DateTitleDiv.append(DateTitle) - - container.append(DateTitleDiv) - - let assessmentContainer = document.createElement("div") - assessmentContainer.classList.add("upcoming-date-assessments") - - for (let i = 0; i < assessments.length; i++) { - const element = assessments[i] - let item = document.createElement("div") - item.classList.add("upcoming-assessment") - item.setAttribute("data-subject", element.code) - item.id = `assessment${element.id}` - - item.style.cssText = element.colour - - let titlediv = document.createElement("div") - titlediv.classList.add("upcoming-subject-title") - - let titlesvg = - stringToHTML(` + } + + for (var date in upcomingDates) { + let assessmentdue = new Date( + ( + upcomingDates[date as keyof typeof upcomingDates] as any + ).assessments[0].due, + ); + let specialcase = CheckSpecialDay(Today, assessmentdue); + let assessmentDate; + + if (specialcase) { + let datecase: string = specialcase!; + assessmentDate = createAssessmentDateDiv( + date, + upcomingDates[date as keyof typeof upcomingDates], + // eslint-disable-next-line + datecase, + ); + } else { + assessmentDate = createAssessmentDateDiv( + date, + upcomingDates[date as keyof typeof upcomingDates], + ); + } + + if (specialcase === "Yesterday") { + upcomingitemcontainer!.insertBefore( + assessmentDate, + upcomingitemcontainer!.firstChild, + ); + } else { + upcomingitemcontainer!.append(assessmentDate); + } + } + FilterUpcomingAssessments(settingsState.subjectfilters); +} + +function createAssessmentDateDiv(date: string, value: any, datecase?: any) { + var options = { + weekday: "long" as "long", + month: "long" as "long", + day: "numeric" as "numeric", + }; + const FormattedDate = new Date(date); + + const assessments = value.assessments; + const container = value.div; + + let DateTitleDiv = document.createElement("div"); + DateTitleDiv.classList.add("upcoming-date-title"); + + if (datecase) { + let datetitle = document.createElement("h5"); + datetitle.classList.add("upcoming-special-day"); + datetitle.innerText = datecase; + DateTitleDiv.append(datetitle); + container.setAttribute("data-day", datecase); + } + + let DateTitle = document.createElement("h5"); + DateTitle.innerText = FormattedDate.toLocaleDateString("en-AU", options); + DateTitleDiv.append(DateTitle); + + container.append(DateTitleDiv); + + let assessmentContainer = document.createElement("div"); + assessmentContainer.classList.add("upcoming-date-assessments"); + + for (let i = 0; i < assessments.length; i++) { + const element = assessments[i]; + let item = document.createElement("div"); + item.classList.add("upcoming-assessment"); + item.setAttribute("data-subject", element.code); + item.id = `assessment${element.id}`; + + item.style.cssText = element.colour; + + let titlediv = document.createElement("div"); + titlediv.classList.add("upcoming-subject-title"); + + let titlesvg = + stringToHTML(` - `).firstChild - titlediv.append(titlesvg!) - - let detailsdiv = document.createElement("div") - detailsdiv.classList.add("upcoming-details") - let detailstitle = document.createElement("h5") - detailstitle.innerText = `${element.subject} assessment` - let subject = document.createElement("p") - subject.innerText = element.title - subject.classList.add("upcoming-assessment-title") - subject.onclick = function () { - document.querySelector("#menu ul")!.classList.add("noscroll") - location.href = `../#?page=/assessments/${element.programmeID}:${element.metaclassID}&item=${element.id}` - } - detailsdiv.append(detailstitle) - detailsdiv.append(subject) - - item.append(titlediv) - item.append(detailsdiv) - assessmentContainer.append(item) - - fetch(`${location.origin}/seqta/student/assessment/submissions/get`, { - method: "POST", - headers: { - "Content-Type": "application/json; charset=utf-8", - }, - body: JSON.stringify({ - assessment: element.id, - metaclass: element.metaclassID, - student: 69, - }), - }) - .then((result) => result.json()) - .then((response) => { - if (response.payload.length > 0) { - const assessment = document.querySelector(`#assessment${element.id}`) - - // ticksvg = stringToHTML(``).firstChild - // ticksvg.classList.add('upcoming-tick') - // assessment.append(ticksvg) - let submittedtext = document.createElement("div") - submittedtext.classList.add("upcoming-submittedtext") - submittedtext.innerText = "Submitted" - assessment!.append(submittedtext) - } - }) - } - - container.append(assessmentContainer) - - return container - } - - function CheckSpecialDay(date1: Date, date2: Date) { - if ( - date1.getFullYear() === date2.getFullYear() && - date1.getMonth() === date2.getMonth() && - date1.getDate() - 1 === date2.getDate() - ) { - return "Yesterday" - } - if ( - date1.getFullYear() === date2.getFullYear() && - date1.getMonth() === date2.getMonth() && - date1.getDate() === date2.getDate() - ) { - return "Today" - } - if ( - date1.getFullYear() === date2.getFullYear() && - date1.getMonth() === date2.getMonth() && - date1.getDate() + 1 === date2.getDate() - ) { - return "Tomorrow" - } - } + `).firstChild; + titlediv.append(titlesvg!); - async function GetLessonColours() { - let func = fetch(`${location.origin}/seqta/student/load/prefs?`, { + let detailsdiv = document.createElement("div"); + detailsdiv.classList.add("upcoming-details"); + let detailstitle = document.createElement("h5"); + detailstitle.innerText = `${element.subject} assessment`; + let subject = document.createElement("p"); + subject.innerText = element.title; + subject.classList.add("upcoming-assessment-title"); + subject.onclick = function () { + document.querySelector("#menu ul")!.classList.add("noscroll"); + location.href = `../#?page=/assessments/${element.programmeID}:${element.metaclassID}&item=${element.id}`; + }; + detailsdiv.append(detailstitle); + detailsdiv.append(subject); + + item.append(titlediv); + item.append(detailsdiv); + assessmentContainer.append(item); + + fetch(`${location.origin}/seqta/student/assessment/submissions/get`, { method: "POST", headers: { "Content-Type": "application/json; charset=utf-8", }, - body: JSON.stringify({ request: "userPrefs", asArray: true, user: 69 }), + body: JSON.stringify({ + assessment: element.id, + metaclass: element.metaclassID, + student: 69, + }), }) - return func .then((result) => result.json()) - .then((response) => response.payload) + .then((response) => { + if (response.payload.length > 0) { + const assessment = document.querySelector(`#assessment${element.id}`); + + // ticksvg = stringToHTML(``).firstChild + // ticksvg.classList.add('upcoming-tick') + // assessment.append(ticksvg) + let submittedtext = document.createElement("div"); + submittedtext.classList.add("upcoming-submittedtext"); + submittedtext.innerText = "Submitted"; + assessment!.append(submittedtext); + } + }); } - function CreateFilters(subjects: any) { - let filteroptions = settingsState.subjectfilters - - let filterdiv = document.querySelector("#upcoming-filters") - for (let i = 0; i < subjects.length; i++) { - const element = subjects[i] - // eslint-disable-next-line - if (!Object.prototype.hasOwnProperty.call(filteroptions, element.code)) { - filteroptions[element.code] = true - settingsState.subjectfilters = filteroptions - } - let elementdiv = CreateSubjectFilter( - element.code, - element.colour, - filteroptions[element.code], - ) - - filterdiv!.append(elementdiv) - } - } + container.append(assessmentContainer); - function CreateSubjectFilter( - subjectcode: any, - itemcolour: string, - checked: any, + return container; +} + +function CheckSpecialDay(date1: Date, date2: Date) { + if ( + date1.getFullYear() === date2.getFullYear() && + date1.getMonth() === date2.getMonth() && + date1.getDate() - 1 === date2.getDate() ) { - let label = CreateElement("label", "upcoming-checkbox-container") - label.innerText = subjectcode - let input1 = CreateElement("input") - const input = input1 as HTMLInputElement - input.type = "checkbox" - input.checked = checked - input.id = `filter-${subjectcode}` - label.style.cssText = itemcolour - let span = CreateElement("span", "upcoming-checkmark") - label.append(input) - label.append(span) - - input.addEventListener("change", function (change) { - let filters = settingsState.subjectfilters - let id = (change.target as HTMLInputElement)!.id.split("-")[1] - filters[id] = (change.target as HTMLInputElement)!.checked - - settingsState.subjectfilters = filters - }) - - return label + return "Yesterday"; } + if ( + date1.getFullYear() === date2.getFullYear() && + date1.getMonth() === date2.getMonth() && + date1.getDate() === date2.getDate() + ) { + return "Today"; + } + if ( + date1.getFullYear() === date2.getFullYear() && + date1.getMonth() === date2.getMonth() && + date1.getDate() + 1 === date2.getDate() + ) { + return "Tomorrow"; + } +} - function SetTimetableSubtitle() { - const homelessonsubtitle = document.getElementById("home-lesson-subtitle") - if (!homelessonsubtitle) return - - const date = new Date() - const isSameMonth = - date.getFullYear() === currentSelectedDate.getFullYear() && - date.getMonth() === currentSelectedDate.getMonth() - - if (isSameMonth) { - const dayDiff = date.getDate() - currentSelectedDate.getDate() - switch (dayDiff) { - case 0: - homelessonsubtitle.innerText = "Today's Lessons" - break - case 1: - homelessonsubtitle.innerText = "Yesterday's Lessons" - break - case -1: - homelessonsubtitle.innerText = "Tomorrow's Lessons" - break - default: - homelessonsubtitle.innerText = formatDateString(currentSelectedDate) - } - } else { - homelessonsubtitle.innerText = formatDateString(currentSelectedDate) +async function GetLessonColours() { + let func = fetch(`${location.origin}/seqta/student/load/prefs?`, { + method: "POST", + headers: { + "Content-Type": "application/json; charset=utf-8", + }, + body: JSON.stringify({ request: "userPrefs", asArray: true, user: 69 }), + }); + return func + .then((result) => result.json()) + .then((response) => response.payload); +} + +function CreateFilters(subjects: any) { + let filteroptions = settingsState.subjectfilters; + + let filterdiv = document.querySelector("#upcoming-filters"); + for (let i = 0; i < subjects.length; i++) { + const element = subjects[i]; + // eslint-disable-next-line + if (!Object.prototype.hasOwnProperty.call(filteroptions, element.code)) { + filteroptions[element.code] = true; + settingsState.subjectfilters = filteroptions; } - } + let elementdiv = CreateSubjectFilter( + element.code, + element.colour, + filteroptions[element.code], + ); - function formatDateString(date: Date): string { - return `${date.toLocaleString("en-us", { weekday: "short" })} ${date.toLocaleDateString("en-au")}` - } \ No newline at end of file + filterdiv!.append(elementdiv); + } +} + +function CreateSubjectFilter( + subjectcode: any, + itemcolour: string, + checked: any, +) { + let label = CreateElement("label", "upcoming-checkbox-container"); + label.innerText = subjectcode; + let input1 = CreateElement("input"); + const input = input1 as HTMLInputElement; + input.type = "checkbox"; + input.checked = checked; + input.id = `filter-${subjectcode}`; + label.style.cssText = itemcolour; + let span = CreateElement("span", "upcoming-checkmark"); + label.append(input); + label.append(span); + + input.addEventListener("change", function (change) { + let filters = settingsState.subjectfilters; + let id = (change.target as HTMLInputElement)!.id.split("-")[1]; + filters[id] = (change.target as HTMLInputElement)!.checked; + + settingsState.subjectfilters = filters; + }); + + return label; +} + +function SetTimetableSubtitle() { + const homelessonsubtitle = document.getElementById("home-lesson-subtitle"); + if (!homelessonsubtitle) return; + + const date = new Date(); + const isSameMonth = + date.getFullYear() === currentSelectedDate.getFullYear() && + date.getMonth() === currentSelectedDate.getMonth(); + + if (isSameMonth) { + const dayDiff = date.getDate() - currentSelectedDate.getDate(); + switch (dayDiff) { + case 0: + homelessonsubtitle.innerText = "Today's Lessons"; + break; + case 1: + homelessonsubtitle.innerText = "Yesterday's Lessons"; + break; + case -1: + homelessonsubtitle.innerText = "Tomorrow's Lessons"; + break; + default: + homelessonsubtitle.innerText = formatDateString(currentSelectedDate); + } + } else { + homelessonsubtitle.innerText = formatDateString(currentSelectedDate); + } +} + +function formatDateString(date: Date): string { + return `${date.toLocaleString("en-us", { weekday: "short" })} ${date.toLocaleDateString("en-au")}`; +} diff --git a/src/seqta/utils/Openers/OpenAboutPage.ts b/src/seqta/utils/Openers/OpenAboutPage.ts index c0325608..a9a588dd 100644 --- a/src/seqta/utils/Openers/OpenAboutPage.ts +++ b/src/seqta/utils/Openers/OpenAboutPage.ts @@ -1,26 +1,26 @@ -import stringToHTML from "../stringToHTML" -import browser from "webextension-polyfill" -import { settingsState } from "../listeners/SettingsState" -import { animate, stagger } from "motion" -import { DeleteWhatsNew } from "../Whatsnew" +import stringToHTML from "../stringToHTML"; +import browser from "webextension-polyfill"; +import { settingsState } from "../listeners/SettingsState"; +import { animate, stagger } from "motion"; +import { DeleteWhatsNew } from "../Whatsnew"; export function OpenAboutPage() { - const background = document.createElement("div") - background.id = "whatsnewbk" - background.classList.add("whatsnewBackground") - - const container = document.createElement("div") - container.classList.add("whatsnewContainer") - - var header: any = stringToHTML( - /* html */ - `
+ const background = document.createElement("div"); + background.id = "whatsnewbk"; + background.classList.add("whatsnewBackground"); + + const container = document.createElement("div"); + container.classList.add("whatsnewContainer"); + + var header: any = stringToHTML( + /* html */ + `

About

BetterSEQTA+ V${browser.runtime.getManifest().version}

`, - ).firstChild - - let text = stringToHTML(/* html */ ` + ).firstChild; + + let text = stringToHTML(/* html */ `
@@ -29,9 +29,9 @@ export function OpenAboutPage() {

Credits

Nulkem created the original extension, was ported to Manifest V3 by MEGA-Dawg68, and is under active development by Crazypersonalph and SethBurkart123.

- `).firstChild - - let footer = stringToHTML(/* html */ ` + `).firstChild; + + let footer = stringToHTML(/* html */ `
- `).firstChild - - let exitbutton = document.createElement("div") - exitbutton.id = "whatsnewclosebutton" - - container.append(header) - container.append(text as ChildNode) - container.append(footer as ChildNode) - container.append(exitbutton) - - background.append(container) - - document.getElementById("container")!.append(background) - - let bkelement = document.getElementById("whatsnewbk") - let popup = document.getElementsByClassName("whatsnewContainer")[0] - - if (settingsState.animations) { - animate( - [popup, bkelement as HTMLElement], - { scale: [0, 1] }, - { - type: "spring", - stiffness: 220, - damping: 18, - }, - ) - - animate( - ".whatsnewTextContainer *", - { opacity: [0, 1], y: [10, 0] }, - { - delay: stagger(0.05, { startDelay: 0.1 }), - duration: 0.5, - ease: [0.22, 0.03, 0.26, 1], - }, - ) + `).firstChild; + + let exitbutton = document.createElement("div"); + exitbutton.id = "whatsnewclosebutton"; + + container.append(header); + container.append(text as ChildNode); + container.append(footer as ChildNode); + container.append(exitbutton); + + background.append(container); + + document.getElementById("container")!.append(background); + + let bkelement = document.getElementById("whatsnewbk"); + let popup = document.getElementsByClassName("whatsnewContainer")[0]; + + if (settingsState.animations) { + animate( + [popup, bkelement as HTMLElement], + { scale: [0, 1] }, + { + type: "spring", + stiffness: 220, + damping: 18, + }, + ); + + animate( + ".whatsnewTextContainer *", + { opacity: [0, 1], y: [10, 0] }, + { + delay: stagger(0.05, { startDelay: 0.1 }), + duration: 0.5, + ease: [0.22, 0.03, 0.26, 1], + }, + ); + } + + delete settingsState.justupdated; + + bkelement!.addEventListener("click", function (event) { + // Check if the click event originated from the element itself and not any of its children + if (event.target === bkelement) { + DeleteWhatsNew(); } - - delete settingsState.justupdated - - bkelement!.addEventListener("click", function (event) { - // Check if the click event originated from the element itself and not any of its children - if (event.target === bkelement) { - DeleteWhatsNew() - } - }) - - var closeelement = document.getElementById("whatsnewclosebutton") - closeelement!.addEventListener("click", function () { - DeleteWhatsNew() - }) - } \ No newline at end of file + }); + + var closeelement = document.getElementById("whatsnewclosebutton"); + closeelement!.addEventListener("click", function () { + DeleteWhatsNew(); + }); +} diff --git a/src/seqta/utils/Openers/OpenMenuOptions.ts b/src/seqta/utils/Openers/OpenMenuOptions.ts index bcef5bc8..933cae0e 100644 --- a/src/seqta/utils/Openers/OpenMenuOptions.ts +++ b/src/seqta/utils/Openers/OpenMenuOptions.ts @@ -1,259 +1,253 @@ +import type { SettingsState } from "@/types/storage"; +import { settingsState } from "../listeners/SettingsState"; +import stringToHTML from "../stringToHTML"; +import Sortable from "sortablejs"; -import type { SettingsState } from "@/types/storage" -import { settingsState } from "../listeners/SettingsState" -import stringToHTML from "../stringToHTML" -import Sortable from "sortablejs" - -export let MenuOptionsOpen = false - +export let MenuOptionsOpen = false; export function OpenMenuOptions() { - var container = document.getElementById("container") - var menu = document.getElementById("menu") + var container = document.getElementById("container"); + var menu = document.getElementById("menu"); if (settingsState.defaultmenuorder.length == 0) { - let childnodes = menu!.firstChild!.childNodes - let newdefaultmenuorder = [] + let childnodes = menu!.firstChild!.childNodes; + let newdefaultmenuorder = []; for (let i = 0; i < childnodes.length; i++) { - const element = childnodes[i] - newdefaultmenuorder.push((element as HTMLElement).dataset.key) - settingsState.defaultmenuorder = newdefaultmenuorder + const element = childnodes[i]; + newdefaultmenuorder.push((element as HTMLElement).dataset.key); + settingsState.defaultmenuorder = newdefaultmenuorder; } } - let childnodes = menu!.firstChild!.childNodes + let childnodes = menu!.firstChild!.childNodes; if (settingsState.defaultmenuorder.length != childnodes.length) { for (let i = 0; i < childnodes.length; i++) { - const element = childnodes[i] + const element = childnodes[i]; if ( !settingsState.defaultmenuorder.indexOf( (element as HTMLElement).dataset.key, ) ) { - let newdefaultmenuorder = settingsState.defaultmenuorder - newdefaultmenuorder.push((element as HTMLElement).dataset.key) - settingsState.defaultmenuorder = newdefaultmenuorder + let newdefaultmenuorder = settingsState.defaultmenuorder; + newdefaultmenuorder.push((element as HTMLElement).dataset.key); + settingsState.defaultmenuorder = newdefaultmenuorder; } } } - MenuOptionsOpen = true + MenuOptionsOpen = true; - var cover = document.createElement("div") - cover.classList.add("notMenuCover") - menu!.style.zIndex = "20" - menu!.style.setProperty("--menuHidden", "flex") - container!.append(cover) + var cover = document.createElement("div"); + cover.classList.add("notMenuCover"); + menu!.style.zIndex = "20"; + menu!.style.setProperty("--menuHidden", "flex"); + container!.append(cover); - var menusettings = document.createElement("div") - menusettings.classList.add("editmenuoption-container") + var menusettings = document.createElement("div"); + menusettings.classList.add("editmenuoption-container"); - var defaultbutton = document.createElement("div") - defaultbutton.classList.add("editmenuoption") - defaultbutton.innerText = "Restore Default" - defaultbutton.id = "restoredefaultoption" + var defaultbutton = document.createElement("div"); + defaultbutton.classList.add("editmenuoption"); + defaultbutton.innerText = "Restore Default"; + defaultbutton.id = "restoredefaultoption"; - var savebutton = document.createElement("div") - savebutton.classList.add("editmenuoption") - savebutton.innerText = "Save" - savebutton.id = "restoredefaultoption" + var savebutton = document.createElement("div"); + savebutton.classList.add("editmenuoption"); + savebutton.innerText = "Save"; + savebutton.id = "restoredefaultoption"; - menusettings.appendChild(defaultbutton) - menusettings.appendChild(savebutton) + menusettings.appendChild(defaultbutton); + menusettings.appendChild(savebutton); - menu!.appendChild(menusettings) + menu!.appendChild(menusettings); - var ListItems = menu!.firstChild!.childNodes + var ListItems = menu!.firstChild!.childNodes; for (let i = 0; i < ListItems.length; i++) { - const element1 = ListItems[i] - const element = element1 as HTMLElement + const element1 = ListItems[i]; + const element = element1 as HTMLElement; - ;(element as HTMLElement).classList.add("draggable") + (element as HTMLElement).classList.add("draggable"); if ((element as HTMLElement).classList.contains("hasChildren")) { - (element as HTMLElement).classList.remove("active") - ;(element.firstChild as HTMLElement).classList.remove("noscroll") + (element as HTMLElement).classList.remove("active"); + (element.firstChild as HTMLElement).classList.remove("noscroll"); } let MenuItemToggle = stringToHTML( `
`, - ).firstChild - ;(element as HTMLElement).append(MenuItemToggle!) + ).firstChild; + (element as HTMLElement).append(MenuItemToggle!); if (!element.dataset.betterseqta) { - const a = document.createElement("section") - a.innerHTML = element.innerHTML - cloneAttributes(a, element) - menu!.firstChild!.insertBefore(a, element) - element.remove() + const a = document.createElement("section"); + a.innerHTML = element.innerHTML; + cloneAttributes(a, element); + menu!.firstChild!.insertBefore(a, element); + element.remove(); } } if (Object.keys(settingsState.menuitems).length == 0) { - menubuttons = menu!.firstChild!.childNodes - let menuItems = {} as any + menubuttons = menu!.firstChild!.childNodes; + let menuItems = {} as any; for (var i = 0; i < menubuttons.length; i++) { - var id = (menubuttons[i] as HTMLElement).dataset.key - const element: any = {} - element.toggle = true - ;(menuItems[id as keyof typeof menuItems] as any) = element + var id = (menubuttons[i] as HTMLElement).dataset.key; + const element: any = {}; + element.toggle = true; + (menuItems[id as keyof typeof menuItems] as any) = element; } - settingsState.menuitems = menuItems + settingsState.menuitems = menuItems; } - var menubuttons: any = document.getElementsByClassName("menuitem") + var menubuttons: any = document.getElementsByClassName("menuitem"); - let menuItems = settingsState.menuitems as any - let buttons = document.getElementsByClassName("menuitem") + let menuItems = settingsState.menuitems as any; + let buttons = document.getElementsByClassName("menuitem"); for (let i = 0; i < buttons.length; i++) { - let id = buttons[i].id as string | undefined + let id = buttons[i].id as string | undefined; if (menuItems[id as keyof typeof menuItems]) { (buttons[i] as HTMLInputElement).checked = - menuItems[id as keyof typeof menuItems].toggle + menuItems[id as keyof typeof menuItems].toggle; } else { - (buttons[i] as HTMLInputElement).checked = true + (buttons[i] as HTMLInputElement).checked = true; } - (buttons[i] as HTMLInputElement).checked = true + (buttons[i] as HTMLInputElement).checked = true; } try { - var el = document.querySelector("#menu > ul") + var el = document.querySelector("#menu > ul"); var sortable = Sortable.create(el as HTMLElement, { draggable: ".draggable", dataIdAttr: "data-key", animation: 150, easing: "cubic-bezier(.5,0,.5,1)", onEnd: function () { - saveNewOrder(sortable) + saveNewOrder(sortable); }, - }) + }); } catch (err) { - console.error(err) + console.error(err); } function changeDisplayProperty(element: any) { if (!element.checked) { - element.parentNode.parentNode.style.display = "var(--menuHidden)" + element.parentNode.parentNode.style.display = "var(--menuHidden)"; } if (element.checked) { element.parentNode.parentNode.style.setProperty( "display", "flex", "important", - ) + ); } } function StoreMenuSettings() { - let menu = document.getElementById("menu") - const menuItems: any = {} - let menubuttons = menu!.firstChild!.childNodes - const button = document.getElementsByClassName("menuitem") + let menu = document.getElementById("menu"); + const menuItems: any = {}; + let menubuttons = menu!.firstChild!.childNodes; + const button = document.getElementsByClassName("menuitem"); for (let i = 0; i < menubuttons.length; i++) { - const id = (menubuttons[i] as HTMLElement).dataset.key - const element: any = {} - element.toggle = (button[i] as HTMLInputElement).checked + const id = (menubuttons[i] as HTMLElement).dataset.key; + const element: any = {}; + element.toggle = (button[i] as HTMLInputElement).checked; - menuItems[id as keyof typeof menuItems] = element + menuItems[id as keyof typeof menuItems] = element; } - settingsState.menuitems = menuItems + settingsState.menuitems = menuItems; } for (let i = 0; i < menubuttons.length; i++) { - const element = menubuttons[i] + const element = menubuttons[i]; element.addEventListener("change", () => { - element.parentElement.parentElement.getAttribute("data-key") - StoreMenuSettings() - changeDisplayProperty(element) - }) + element.parentElement.parentElement.getAttribute("data-key"); + StoreMenuSettings(); + changeDisplayProperty(element); + }); } function closeAll() { - menusettings?.remove() - cover?.remove() - MenuOptionsOpen = false - menu!.style.setProperty("--menuHidden", "none") + menusettings?.remove(); + cover?.remove(); + MenuOptionsOpen = false; + menu!.style.setProperty("--menuHidden", "none"); for (let i = 0; i < ListItems.length; i++) { - const element1 = ListItems[i] - const element = element1 as HTMLElement - element.classList.remove("draggable") - element.setAttribute("draggable", "false") + const element1 = ListItems[i]; + const element = element1 as HTMLElement; + element.classList.remove("draggable"); + element.setAttribute("draggable", "false"); if (!element.dataset.betterseqta) { - const a = document.createElement("li") - a.innerHTML = element.innerHTML - cloneAttributes(a, element) - menu!.firstChild!.insertBefore(a, element) - element.remove() + const a = document.createElement("li"); + a.innerHTML = element.innerHTML; + cloneAttributes(a, element); + menu!.firstChild!.insertBefore(a, element); + element.remove(); } } - let switches = menu!.querySelectorAll(".onoffswitch") + let switches = menu!.querySelectorAll(".onoffswitch"); for (let i = 0; i < switches.length; i++) { - switches[i].remove() + switches[i].remove(); } } - cover?.addEventListener("click", closeAll) - savebutton?.addEventListener("click", closeAll) + cover?.addEventListener("click", closeAll); + savebutton?.addEventListener("click", closeAll); defaultbutton?.addEventListener("click", function () { - const options = settingsState.defaultmenuorder - settingsState.menuorder = options + const options = settingsState.defaultmenuorder; + settingsState.menuorder = options; - ChangeMenuItemPositions(options) + ChangeMenuItemPositions(options); for (let i = 0; i < menubuttons.length; i++) { - const element = menubuttons[i] - element.checked = true + const element = menubuttons[i]; + element.checked = true; element.parentNode.parentNode.style.setProperty( "display", "flex", "important", - ) + ); } - saveNewOrder(sortable) - }) + saveNewOrder(sortable); + }); } - - function saveNewOrder(sortable: any) { - var order = sortable.toArray() - settingsState.menuorder = order + var order = sortable.toArray(); + settingsState.menuorder = order; } function cloneAttributes(target: any, source: any) { [...source.attributes].forEach((attr) => { - target.setAttribute(attr.nodeName, attr.nodeValue) - }) + target.setAttribute(attr.nodeName, attr.nodeValue); + }); } - - export function ChangeMenuItemPositions(menuorder: SettingsState["menuorder"]) { - var menuList = document.querySelector("#menu")!.firstChild!.childNodes + var menuList = document.querySelector("#menu")!.firstChild!.childNodes; - let listorder = [] + let listorder = []; for (let i = 0; i < menuList.length; i++) { - const menu = menuList[i] as HTMLElement + const menu = menuList[i] as HTMLElement; - let a = menuorder.indexOf(menu.dataset.key) + let a = menuorder.indexOf(menu.dataset.key); - listorder.push(a) + listorder.push(a); } - var newArr = [] + var newArr = []; for (var i = 0; i < listorder.length; i++) { - newArr[listorder[i]] = menuList[i] + newArr[listorder[i]] = menuList[i]; } - let listItemsDOM = document.getElementById("menu")!.firstChild + let listItemsDOM = document.getElementById("menu")!.firstChild; for (let i = 0; i < newArr.length; i++) { - const element = newArr[i] + const element = newArr[i]; if (element) { - const elem = element as HTMLElement - elem.setAttribute("data-checked", "true") - listItemsDOM!.appendChild(element) + const elem = element as HTMLElement; + elem.setAttribute("data-checked", "true"); + listItemsDOM!.appendChild(element); } } -} \ No newline at end of file +} diff --git a/src/seqta/utils/ReactFiber.ts b/src/seqta/utils/ReactFiber.ts index 65f49ead..a2ab87c9 100644 --- a/src/seqta/utils/ReactFiber.ts +++ b/src/seqta/utils/ReactFiber.ts @@ -3,20 +3,26 @@ class ReactFiber { private debug: boolean; private messageIdCounter: number = 0; // Counter for unique message IDs - constructor(selector: string, options: { - debug ? : boolean - } = {}) { + constructor( + selector: string, + options: { + debug?: boolean; + } = {}, + ) { this.selector = selector; this.debug = options.debug || false; } - static find(selector: string, options: { - debug ? : boolean - } = {}) { + static find( + selector: string, + options: { + debug?: boolean; + } = {}, + ) { return new ReactFiber(selector, options); } - private async sendMessage(action: string, payload: any = {}): Promise < any > { + private async sendMessage(action: string, payload: any = {}): Promise { return new Promise((resolve, _) => { const messageId = this.messageIdCounter++; const message = { @@ -29,56 +35,58 @@ class ReactFiber { }; const listener = (response: any) => { - if (response.data?.type === 'reactFiberResponse' && response.data?.messageId === messageId) { + if ( + response.data?.type === "reactFiberResponse" && + response.data?.messageId === messageId + ) { if (this.debug) { console.log("Content Received Response:", response.data.response); } resolve(response.data.response); - window.removeEventListener("message", listener) + window.removeEventListener("message", listener); } }; - window.addEventListener('message', listener); + window.addEventListener("message", listener); window.postMessage(message, "*"); }); } - - async getState(key ? : string | string[]): Promise < any > { + async getState(key?: string | string[]): Promise { return this.sendMessage("getState", { - key + key, }); } - async setState(update: any | ((prevState: any) => any)): Promise < ReactFiber > { - const updateFnString = typeof update === 'function' ? update.toString() : null; - const updateObject = typeof update !== 'function' ? update : null; + async setState(update: any | ((prevState: any) => any)): Promise { + const updateFnString = + typeof update === "function" ? update.toString() : null; + const updateObject = typeof update !== "function" ? update : null; await this.sendMessage("setState", { updateFn: updateFnString, - updateObject + updateObject, }); return this; } - - async getProps(propName ? : string): Promise < any > { + async getProps(propName?: string): Promise { return this.sendMessage("getProp", { - propName + propName, }); } - async setProp(propName: string, value: any): Promise < ReactFiber > { + async setProp(propName: string, value: any): Promise { await this.sendMessage("setProp", { propName, - value + value, }); return this; } - async forceUpdate(): Promise < ReactFiber > { + async forceUpdate(): Promise { await this.sendMessage("forceUpdate"); return this; } } -export default ReactFiber; \ No newline at end of file +export default ReactFiber; diff --git a/src/seqta/utils/SendNewsPage.ts b/src/seqta/utils/SendNewsPage.ts index 5d2ba67f..02ccfd1e 100644 --- a/src/seqta/utils/SendNewsPage.ts +++ b/src/seqta/utils/SendNewsPage.ts @@ -1,93 +1,93 @@ -import { AppendLoadingSymbol } from "@/seqta/ui/Loading" -import stringToHTML from "./stringToHTML" -import { delay } from "./delay" -import { settingsState } from "./listeners/SettingsState" -import browser from "webextension-polyfill" -import LogoLightOutline from "@/resources/icons/betterseqta-light-outline.png" -import { animate, stagger } from "motion" +import { AppendLoadingSymbol } from "@/seqta/ui/Loading"; +import stringToHTML from "./stringToHTML"; +import { delay } from "./delay"; +import { settingsState } from "./listeners/SettingsState"; +import browser from "webextension-polyfill"; +import LogoLightOutline from "@/resources/icons/betterseqta-light-outline.png"; +import { animate, stagger } from "motion"; export async function SendNewsPage() { - console.info("[BetterSEQTA+] Started Loading News Page") - document.title = "News ― SEQTA Learn" - await delay(100) + console.info("[BetterSEQTA+] Started Loading News Page"); + document.title = "News ― SEQTA Learn"; + await delay(100); - const element = document.querySelector("[data-key=news]") - element!.classList.add("active") + const element = document.querySelector("[data-key=news]"); + element!.classList.add("active"); // Remove all current elements in the main div to add new elements - const main = document.getElementById("main") - main!.innerHTML = "" + const main = document.getElementById("main"); + main!.innerHTML = ""; const html = stringToHTML(/* html */ `

Latest Headlines in ${settingsState.newsSource ? settingsState.newsSource.charAt(0).toUpperCase() + settingsState.newsSource.slice(1) : "Australia"}

-
`) +
`); - main!.append(html.firstChild!) + main!.append(html.firstChild!); - const titlediv = document.getElementById("title")!.firstChild - ;(titlediv! as HTMLElement).innerText = "News" - AppendLoadingSymbol("newsloading", "#news-container") + const titlediv = document.getElementById("title")!.firstChild; + (titlediv! as HTMLElement).innerText = "News"; + AppendLoadingSymbol("newsloading", "#news-container"); const response = (await browser.runtime.sendMessage({ type: "sendNews", source: settingsState.newsSource, - })) as any - const newscontainer = document.querySelector("#news-container") - document.getElementById("newsloading")?.remove() + })) as any; + const newscontainer = document.querySelector("#news-container"); + document.getElementById("newsloading")?.remove(); // Create a document fragment to batch DOM operations - const fragment = document.createDocumentFragment() + const fragment = document.createDocumentFragment(); // Map over articles to create elements response.news.articles.forEach((article: any) => { - const newsarticle = document.createElement("a") - newsarticle.classList.add("NewsArticle") - newsarticle.href = article.url - newsarticle.target = "_blank" + const newsarticle = document.createElement("a"); + newsarticle.classList.add("NewsArticle"); + newsarticle.href = article.url; + newsarticle.target = "_blank"; - const articleimage = document.createElement("div") - articleimage.classList.add("articleimage") + const articleimage = document.createElement("div"); + articleimage.classList.add("articleimage"); if (article.urlToImage == "null" || article.urlToImage == null) { articleimage.style.cssText = ` background-image: url(${browser.runtime.getURL(LogoLightOutline)}); width: 20%; margin: 0 7.5%; - ` + `; } else { - articleimage.style.backgroundImage = `url(${article.urlToImage})` + articleimage.style.backgroundImage = `url(${article.urlToImage})`; } - const articletext = document.createElement("div") - articletext.classList.add("ArticleText") + const articletext = document.createElement("div"); + articletext.classList.add("ArticleText"); - const title = document.createElement("a") - title.innerText = article.title - title.href = article.url - title.target = "_blank" + const title = document.createElement("a"); + title.innerText = article.title; + title.href = article.url; + title.target = "_blank"; - const description = document.createElement("p") + const description = document.createElement("p"); article.description = article.description.length > 400 ? article.description.substring(0, 400) + "..." - : article.description - description.innerHTML = article.description + : article.description; + description.innerHTML = article.description; - articletext.append(title, description) - newsarticle.append(articleimage, articletext) - fragment.append(newsarticle) - }) + articletext.append(title, description); + newsarticle.append(articleimage, articletext); + fragment.append(newsarticle); + }); // Single DOM update to append all articles - newscontainer?.append(fragment) + newscontainer?.append(fragment); - if (!settingsState.animations) return + if (!settingsState.animations) return; - const articles = Array.from(document.querySelectorAll(".NewsArticle")) + const articles = Array.from(document.querySelectorAll(".NewsArticle")); animate( articles.slice(0, 20), @@ -99,5 +99,5 @@ export async function SendNewsPage() { damping: 20, mass: 1, }, - ) -} \ No newline at end of file + ); +} diff --git a/src/seqta/utils/Whatsnew.ts b/src/seqta/utils/Whatsnew.ts index 86a16f59..b2d9ccf5 100644 --- a/src/seqta/utils/Whatsnew.ts +++ b/src/seqta/utils/Whatsnew.ts @@ -1,16 +1,16 @@ -import { settingsState } from "./listeners/SettingsState" -import { animate, stagger } from "motion" -import stringToHTML from "./stringToHTML" -import browser from "webextension-polyfill" -import kofi from "@/resources/kofi.png?base64" +import { settingsState } from "./listeners/SettingsState"; +import { animate, stagger } from "motion"; +import stringToHTML from "./stringToHTML"; +import browser from "webextension-polyfill"; +import kofi from "@/resources/kofi.png?base64"; export async function DeleteWhatsNew() { - const bkelement = document.getElementById("whatsnewbk") - const popup = document.getElementsByClassName("whatsnewContainer")[0] + const bkelement = document.getElementById("whatsnewbk"); + const popup = document.getElementsByClassName("whatsnewContainer")[0]; if (!settingsState.animations) { - bkelement?.remove() - return + bkelement?.remove(); + return; } animate( @@ -18,47 +18,47 @@ export async function DeleteWhatsNew() { { opacity: [1, 0], scale: [1, 0] }, { ease: [0.22, 0.03, 0.26, 1] }, ).then(() => { - bkelement?.remove() - }) + bkelement?.remove(); + }); } export function OpenWhatsNewPopup() { - const background = document.createElement("div") - background.id = "whatsnewbk" - background.classList.add("whatsnewBackground") - - const container = document.createElement("div") - container.classList.add("whatsnewContainer") - - var header: any = stringToHTML( - /* html */ - `
+ const background = document.createElement("div"); + background.id = "whatsnewbk"; + background.classList.add("whatsnewBackground"); + + const container = document.createElement("div"); + container.classList.add("whatsnewContainer"); + + var header: any = stringToHTML( + /* html */ + `

What's New

BetterSEQTA+ V${browser.runtime.getManifest().version}

`, - ).firstChild - - let imagecont = document.createElement("div") - imagecont.classList.add("whatsnewImgContainer") - - let video = document.createElement("video") - let source = document.createElement("source") - - source.setAttribute( - "src", - "https://raw.githubusercontent.com/BetterSEQTA/BetterSEQTA-Plus/main/src/resources/update-video.mp4", - ) - video.autoplay = true - video.muted = true - video.loop = true - video.appendChild(source) - video.classList.add("whatsnewImg") - imagecont.appendChild(video) - - let textcontainer = document.createElement("div") - textcontainer.classList.add("whatsnewTextContainer") - - let text = stringToHTML(/* html */ ` + ).firstChild; + + let imagecont = document.createElement("div"); + imagecont.classList.add("whatsnewImgContainer"); + + let video = document.createElement("video"); + let source = document.createElement("source"); + + source.setAttribute( + "src", + "https://raw.githubusercontent.com/BetterSEQTA/BetterSEQTA-Plus/main/src/resources/update-video.mp4", + ); + video.autoplay = true; + video.muted = true; + video.loop = true; + video.appendChild(source); + video.classList.add("whatsnewImg"); + imagecont.appendChild(video); + + let textcontainer = document.createElement("div"); + textcontainer.classList.add("whatsnewTextContainer"); + + let text = stringToHTML(/* html */ `

3.4.6.1 - Hot patch!

  • Fixed storage not updating and sometimes being replaced with default values
  • @@ -238,9 +238,9 @@ export function OpenWhatsNewPopup() {

    Create Custom Shortcuts

  • Found in the BetterSEQTA+ Settings menu, custom shortcuts can now be created with a name and URL of your choice.
  • - `).firstChild - - let footer = stringToHTML(/* html */ ` + `).firstChild; + + let footer = stringToHTML(/* html */ `
    Report bugs and feedback: @@ -267,58 +267,58 @@ export function OpenWhatsNewPopup() {
    - `).firstChild - - let exitbutton = document.createElement("div") - exitbutton.id = "whatsnewclosebutton" - - container.append(header) - container.append(imagecont) - container.append(textcontainer) - container.append(text as ChildNode) - container.append(footer as ChildNode) - container.append(exitbutton) - - background.append(container) - - document.getElementById("container")!.append(background) - - let bkelement = document.getElementById("whatsnewbk") - let popup = document.getElementsByClassName("whatsnewContainer")[0] - - if (settingsState.animations) { - animate( - [popup, bkelement as HTMLElement], - { scale: [0, 1] }, - { - type: "spring", - stiffness: 220, - damping: 18, - }, - ) - - animate( - ".whatsnewTextContainer *", - { opacity: [0, 1], y: [10, 0] }, - { - delay: stagger(0.05, { startDelay: 0.1 }), - duration: 0.5, - ease: [0.22, 0.03, 0.26, 1], - }, - ) + `).firstChild; + + let exitbutton = document.createElement("div"); + exitbutton.id = "whatsnewclosebutton"; + + container.append(header); + container.append(imagecont); + container.append(textcontainer); + container.append(text as ChildNode); + container.append(footer as ChildNode); + container.append(exitbutton); + + background.append(container); + + document.getElementById("container")!.append(background); + + let bkelement = document.getElementById("whatsnewbk"); + let popup = document.getElementsByClassName("whatsnewContainer")[0]; + + if (settingsState.animations) { + animate( + [popup, bkelement as HTMLElement], + { scale: [0, 1] }, + { + type: "spring", + stiffness: 220, + damping: 18, + }, + ); + + animate( + ".whatsnewTextContainer *", + { opacity: [0, 1], y: [10, 0] }, + { + delay: stagger(0.05, { startDelay: 0.1 }), + duration: 0.5, + ease: [0.22, 0.03, 0.26, 1], + }, + ); + } + + delete settingsState.justupdated; + + bkelement!.addEventListener("click", function (event) { + // Check if the click event originated from the element itself and not any of its children + if (event.target === bkelement) { + DeleteWhatsNew(); } - - delete settingsState.justupdated - - bkelement!.addEventListener("click", function (event) { - // Check if the click event originated from the element itself and not any of its children - if (event.target === bkelement) { - DeleteWhatsNew() - } - }) - - var closeelement = document.getElementById("whatsnewclosebutton") - closeelement!.addEventListener("click", function () { - DeleteWhatsNew() - }) - } \ No newline at end of file + }); + + var closeelement = document.getElementById("whatsnewclosebutton"); + closeelement!.addEventListener("click", function () { + DeleteWhatsNew(); + }); +} diff --git a/src/seqta/utils/base64ToBlob.ts b/src/seqta/utils/base64ToBlob.ts index 3a428e3b..c61b7d2d 100644 --- a/src/seqta/utils/base64ToBlob.ts +++ b/src/seqta/utils/base64ToBlob.ts @@ -1,4 +1,4 @@ -const base64ToBlob = (base64: string, contentType: string = ''): Blob => { +const base64ToBlob = (base64: string, contentType: string = ""): Blob => { const byteCharacters = atob(base64); const byteArrays: Uint8Array[] = []; @@ -14,4 +14,4 @@ const base64ToBlob = (base64: string, contentType: string = ''): Blob => { return new Blob(byteArrays, { type: contentType }); }; -export default base64ToBlob; \ No newline at end of file +export default base64ToBlob; diff --git a/src/seqta/utils/convertTo12HourFormat.ts b/src/seqta/utils/convertTo12HourFormat.ts index 3831ba46..8dd6670e 100644 --- a/src/seqta/utils/convertTo12HourFormat.ts +++ b/src/seqta/utils/convertTo12HourFormat.ts @@ -1,21 +1,21 @@ export function convertTo12HourFormat( - time: string, - noMinutes: boolean = false, - ): string { - let [hours, minutes] = time.split(":").map(Number) - let period = "AM" - - if (hours >= 12) { - period = "PM" - if (hours > 12) hours -= 12 - } else if (hours === 0) { - hours = 12 - } - - let hoursStr = hours.toString() - if (hoursStr.length === 2 && hoursStr.startsWith("0")) { - hoursStr = hoursStr.substring(1) - } - - return `${hoursStr}${noMinutes ? "" : `:${minutes.toString().padStart(2, "0")}`} ${period}` - } \ No newline at end of file + time: string, + noMinutes: boolean = false, +): string { + let [hours, minutes] = time.split(":").map(Number); + let period = "AM"; + + if (hours >= 12) { + period = "PM"; + if (hours > 12) hours -= 12; + } else if (hours === 0) { + hours = 12; + } + + let hoursStr = hours.toString(); + if (hoursStr.length === 2 && hoursStr.startsWith("0")) { + hoursStr = hoursStr.substring(1); + } + + return `${hoursStr}${noMinutes ? "" : `:${minutes.toString().padStart(2, "0")}`} ${period}`; +} diff --git a/src/seqta/utils/debounce.ts b/src/seqta/utils/debounce.ts index 916e8933..41a2a4fa 100644 --- a/src/seqta/utils/debounce.ts +++ b/src/seqta/utils/debounce.ts @@ -1,6 +1,9 @@ -export default function debounce void>(fn: T, delay: number): (...args: Parameters) => void { +export default function debounce void>( + fn: T, + delay: number, +): (...args: Parameters) => void { let timeout: ReturnType; - return function(this: ThisParameterType, ...args: Parameters) { + return function (this: ThisParameterType, ...args: Parameters) { clearTimeout(timeout); timeout = setTimeout(() => fn.apply(this, args), delay); }; diff --git a/src/seqta/utils/imageConversions.ts b/src/seqta/utils/imageConversions.ts index 3fdb4f62..d8233490 100644 --- a/src/seqta/utils/imageConversions.ts +++ b/src/seqta/utils/imageConversions.ts @@ -1,6 +1,6 @@ export function base64toblobURL(base64: string) { // Extract base64 data from the data URI - const base64Index = base64.indexOf(',') + 1; + const base64Index = base64.indexOf(",") + 1; const imageBase64 = base64.substring(base64Index); // Convert base64 to blob @@ -10,10 +10,10 @@ export function base64toblobURL(base64: string) { byteNumbers[i] = byteCharacters.charCodeAt(i); } const byteArray = new Uint8Array(byteNumbers); - const blob = new Blob([byteArray], { type: 'image/png' }); + const blob = new Blob([byteArray], { type: "image/png" }); // Convert blob to blob URL const imageUrl = URL.createObjectURL(blob); return imageUrl; -} \ No newline at end of file +} diff --git a/src/seqta/utils/listeners/ClickListeners.ts b/src/seqta/utils/listeners/ClickListeners.ts index ea7baaa0..1b9c3a95 100644 --- a/src/seqta/utils/listeners/ClickListeners.ts +++ b/src/seqta/utils/listeners/ClickListeners.ts @@ -1,28 +1,37 @@ -import { waitForElm } from "@/seqta/utils/waitForElm" +import { waitForElm } from "@/seqta/utils/waitForElm"; import ReactFiber from "../ReactFiber"; const handleNotificationClick = async (target: HTMLElement) => { - const notificationItem = target.closest('[class*="notifications__item___"]') as HTMLElement | null; + const notificationItem = target.closest( + '[class*="notifications__item___"]', + ) as HTMLElement | null; if (!notificationItem) return; - const buttonType = notificationItem.getAttribute('data-type'); - if (buttonType !== 'message') return; + const buttonType = notificationItem.getAttribute("data-type"); + if (buttonType !== "message") return; - const notificationList = await ReactFiber.find('[class*="notifications__list___"]').getState(); - const buttonId = notificationItem.getAttribute('data-id'); + const notificationList = await ReactFiber.find( + '[class*="notifications__list___"]', + ).getState(); + const buttonId = notificationItem.getAttribute("data-id"); if (!buttonId) return; - const matchingNotification = notificationList.storeState.notifications.items.find( - (item: any) => item.notificationID === parseInt(buttonId) - ); + const matchingNotification = + notificationList.storeState.notifications.items.find( + (item: any) => item.notificationID === parseInt(buttonId), + ); await waitForElm('[class*="Viewer__Viewer___"] > div', true, 20); // Select the specific direct message - ReactFiber.find('[class*="Viewer__Viewer___"] > div').setState({ selected: new Set([matchingNotification.message.messageID]) }); + ReactFiber.find('[class*="Viewer__Viewer___"] > div').setState({ + selected: new Set([matchingNotification.message.messageID]), + }); // Close the notifications panel - const notificationButton = document.querySelector('[class*="notifications__notifications___"] > button') as HTMLButtonElement | null; + const notificationButton = document.querySelector( + '[class*="notifications__notifications___"] > button', + ) as HTMLButtonElement | null; notificationButton?.click(); }; @@ -33,10 +42,10 @@ const clickListeners = [ }, ]; -const registerClickListeners = () => { - document.addEventListener('click', (e) => { +const registerClickListeners = () => { + document.addEventListener("click", (e) => { const target = e.target as HTMLElement; - + clickListeners.forEach(({ selector, handler }) => { if (target.closest(selector)) { handler(target); diff --git a/src/seqta/utils/listeners/EventManager.ts b/src/seqta/utils/listeners/EventManager.ts index 948cf372..f5a33d61 100644 --- a/src/seqta/utils/listeners/EventManager.ts +++ b/src/seqta/utils/listeners/EventManager.ts @@ -39,28 +39,37 @@ class EventManager { return instance; } - public register(event: string, options: EventListenerOptions, callback: (element: Element) => void): { unregister: () => void } { + public register( + event: string, + options: EventListenerOptions, + callback: (element: Element) => void, + ): { unregister: () => void } { const id = this.generateUniqueId(); if (!this.listeners.has(event)) { this.listeners.set(event, []); } const unregister = () => this.unregisterById(event, id); this.listeners.get(event)!.push({ id, options, callback, unregister }); - + this.scanExistingElements(options, callback); - + this.startObserving(options.parentElement); return { unregister }; } - private async scanExistingElements(options: EventListenerOptions, callback: (element: Element) => void): Promise { + private async scanExistingElements( + options: EventListenerOptions, + callback: (element: Element) => void, + ): Promise { const root = options.parentElement || document.documentElement; - const elements = Array.from(root.getElementsByTagName('*')); + const elements = Array.from(root.getElementsByTagName("*")); elements.unshift(root); - + for (let i = 0; i < elements.length; i += this.chunkSize) { const chunk = elements.slice(i, i + this.chunkSize); - const filteredChunk = chunk.filter(element => this.matchesOptions(element, options)); + const filteredChunk = chunk.filter((element) => + this.matchesOptions(element, options), + ); for (const element of filteredChunk) { callback(element); } @@ -76,7 +85,10 @@ class EventManager { private unregisterById(event: string, id: string): void { if (this.listeners.has(event)) { const listeners = this.listeners.get(event)!; - this.listeners.set(event, listeners.filter(listener => listener.id !== id)); + this.listeners.set( + event, + listeners.filter((listener) => listener.id !== id), + ); } } @@ -93,9 +105,9 @@ class EventManager { } private handleMutations(mutations: MutationRecord[]): void { - mutations.forEach(mutation => { - if (mutation.type === 'childList') { - mutation.addedNodes.forEach(node => { + mutations.forEach((mutation) => { + if (mutation.type === "childList") { + mutation.addedNodes.forEach((node) => { if (node.nodeType === Node.ELEMENT_NODE) { this.pendingElements.add(node as Element); } @@ -148,19 +160,29 @@ class EventManager { } } - private matchesOptions(element: Element, options: EventListenerOptions): boolean { - if (options.elementType && element.tagName.toLowerCase() !== options.elementType.toLowerCase()) return false; - if (options.textContent && element.textContent !== options.textContent) return false; - if (options.className && !element.classList.contains(options.className)) return false; + private matchesOptions( + element: Element, + options: EventListenerOptions, + ): boolean { + if ( + options.elementType && + element.tagName.toLowerCase() !== options.elementType.toLowerCase() + ) + return false; + if (options.textContent && element.textContent !== options.textContent) + return false; + if (options.className && !element.classList.contains(options.className)) + return false; if (options.id && element.id !== options.id) return false; if (options.customCheck && !options.customCheck(element)) return false; return true; } private generateUniqueId(): string { - return '_' + Math.random().toString(36).substr(2, 9); + return "_" + Math.random().toString(36).substr(2, 9); } } export const eventManager = EventManager.getInstance(); -export const initializeEventManager = async () => await EventManager.initialize(); +export const initializeEventManager = async () => + await EventManager.initialize(); diff --git a/src/seqta/utils/listeners/MessageListener.ts b/src/seqta/utils/listeners/MessageListener.ts index ac791882..b0a32ff7 100644 --- a/src/seqta/utils/listeners/MessageListener.ts +++ b/src/seqta/utils/listeners/MessageListener.ts @@ -1,12 +1,18 @@ -import browser from 'webextension-polyfill' +import browser from "webextension-polyfill"; -import { closeExtensionPopup } from "@/seqta/utils/Closers/closeExtensionPopup" -import { MenuOptionsOpen, OpenMenuOptions } from "@/seqta/utils/Openers/OpenMenuOptions" +import { closeExtensionPopup } from "@/seqta/utils/Closers/closeExtensionPopup"; +import { + MenuOptionsOpen, + OpenMenuOptions, +} from "@/seqta/utils/Openers/OpenMenuOptions"; -import { CloseThemeCreator, OpenThemeCreator } from '@/plugins/built-in/themes/ThemeCreator'; -import sendThemeUpdate from '@/seqta/utils/sendThemeUpdate'; -import hideSensitiveContent from '@/seqta/ui/dev/hideSensitiveContent'; -import { ThemeManager } from '@/plugins/built-in/themes/theme-manager'; +import { + CloseThemeCreator, + OpenThemeCreator, +} from "@/plugins/built-in/themes/ThemeCreator"; +import sendThemeUpdate from "@/seqta/utils/sendThemeUpdate"; +import hideSensitiveContent from "@/seqta/ui/dev/hideSensitiveContent"; +import { ThemeManager } from "@/plugins/built-in/themes/theme-manager"; const themeManager = ThemeManager.getInstance(); @@ -16,93 +22,93 @@ export class MessageHandler { browser.runtime.onMessage.addListener(this.routeMessage.bind(this)); } routeMessage(request: any, _sender: any, sendResponse: any) { - console.debug('Message received:', request) - + console.debug("Message received:", request); + switch (request.info) { - case 'EditSidebar': + case "EditSidebar": this.editSidebar(); closeExtensionPopup(); - sendResponse({ status: 'success' }); + sendResponse({ status: "success" }); break; - - case 'UpdateThemePreview': + + case "UpdateThemePreview": if (request?.save == true) { const save = async () => { - await themeManager.saveTheme(request.body) + await themeManager.saveTheme(request.body); if (request.body.enableTheme) { - await themeManager.setTheme(request.body.id) + await themeManager.setTheme(request.body.id); } - sendResponse({ status: 'success' }) - sendThemeUpdate() - } - save() + sendResponse({ status: "success" }); + sendThemeUpdate(); + }; + save(); } else { themeManager.updatePreview(request.body); - sendResponse({ status: 'success' }); + sendResponse({ status: "success" }); } return true; - - case 'GetTheme': + + case "GetTheme": themeManager.getTheme(request.body.themeID).then((theme) => { sendResponse(theme); }); return true; - - case 'SetTheme': + + case "SetTheme": themeManager.setTheme(request.body.themeID).then(() => { - sendResponse({ status: 'success' }); - }); - break; - - case 'DisableTheme': - themeManager.disableTheme().then(() => { - sendResponse({ status: 'success' }); - }); - break; - - case 'DeleteTheme': - themeManager.deleteTheme(request.body.themeID).then(() => { - sendResponse({ status: 'success' }); + sendResponse({ status: "success" }); }); break; - case 'ListThemes': + case "DisableTheme": + themeManager.disableTheme().then(() => { + sendResponse({ status: "success" }); + }); + break; + + case "DeleteTheme": + themeManager.deleteTheme(request.body.themeID).then(() => { + sendResponse({ status: "success" }); + }); + break; + + case "ListThemes": themeManager.getAvailableThemes().then((themes) => { sendResponse(themes); }); return true; - case 'OpenThemeCreator': + case "OpenThemeCreator": const themeID = request?.body?.themeID; - OpenThemeCreator( themeID ? themeID : '' ); + OpenThemeCreator(themeID ? themeID : ""); closeExtensionPopup(); - sendResponse({ status: 'success' }); + sendResponse({ status: "success" }); break; - - case 'ShareTheme': + + case "ShareTheme": themeManager.shareTheme(request.body.themeID).then((id) => { - sendResponse({ status: 'success', id }); + sendResponse({ status: "success", id }); }); return true; - case 'CloseThemeCreator': + case "CloseThemeCreator": try { CloseThemeCreator(); } catch (error) { - console.error('Error closing theme creator:', error); - sendResponse({ status: 'error' }); + console.error("Error closing theme creator:", error); + sendResponse({ status: "error" }); } - sendResponse({ status: 'success' }); + sendResponse({ status: "success" }); break; - case 'HideSensitive': + case "HideSensitive": hideSensitiveContent(); - sendResponse({ status: 'success' }); + sendResponse({ status: "success" }); break; - + default: - console.debug('Unknown request info:', request.info); - } + console.debug("Unknown request info:", request.info); + } } editSidebar() { @@ -110,4 +116,4 @@ export class MessageHandler { OpenMenuOptions(); } } -} \ No newline at end of file +} diff --git a/src/seqta/utils/listeners/SettingsState.ts b/src/seqta/utils/listeners/SettingsState.ts index 04b42a7f..52c07e50 100644 --- a/src/seqta/utils/listeners/SettingsState.ts +++ b/src/seqta/utils/listeners/SettingsState.ts @@ -1,6 +1,6 @@ -import browser from 'webextension-polyfill'; -import type { SettingsState } from '@/types/storage'; -import type { Subscriber, Unsubscriber } from 'svelte/store'; +import browser from "webextension-polyfill"; +import type { SettingsState } from "@/types/storage"; +import type { Subscriber, Unsubscriber } from "svelte/store"; type ChangeListener = (newValue: any, oldValue: any) => void; type GlobalChangeListener = (newValue: any, oldValue: any, key: string) => void; @@ -19,7 +19,7 @@ class StorageManager { this.loadFromStorage(); const handler: ProxyHandler = { - get: (target, prop: keyof SettingsState | 'register' | 'initialize') => { + get: (target, prop: keyof SettingsState | "register" | "initialize") => { if (prop in target) { return (target as any)[prop]; } @@ -42,7 +42,7 @@ class StorageManager { } } return true; - } + }, }; this.initStorageListener(); @@ -63,7 +63,10 @@ class StorageManager { return instance; } - public setKey(key: K, value: SettingsState[K]): void { + public setKey( + key: K, + value: SettingsState[K], + ): void { this.data[key] = value; this.saveToStorage(); } @@ -87,7 +90,7 @@ class StorageManager { private initStorageListener(): void { browser.storage.onChanged.addListener((changes, areaName) => { - if (areaName === 'local') { + if (areaName === "local") { for (const [key, { oldValue, newValue }] of Object.entries(changes)) { if (newValue !== undefined) { (this.data as any)[key] = newValue; @@ -151,4 +154,5 @@ class StorageManager { } export const settingsState = StorageManager.getInstance(); -export const initializeSettingsState = async () => await StorageManager.initialize(); +export const initializeSettingsState = async () => + await StorageManager.initialize(); diff --git a/src/seqta/utils/listeners/StorageChanges.ts b/src/seqta/utils/listeners/StorageChanges.ts index 75fcb076..a7bd8f76 100644 --- a/src/seqta/utils/listeners/StorageChanges.ts +++ b/src/seqta/utils/listeners/StorageChanges.ts @@ -1,15 +1,13 @@ -import { settingsState } from './SettingsState'; -import { updateAllColors } from '@/seqta/ui/colors/Manager'; - +import { settingsState } from "./SettingsState"; +import { updateAllColors } from "@/seqta/ui/colors/Manager"; import { addShortcuts } from "@/seqta/utils/Adders/AddShortcuts"; import { CreateCustomShortcutDiv } from "@/seqta/utils/CreateEnable/CreateCustomShortcutDiv"; import { FilterUpcomingAssessments } from "@/seqta/utils/FilterUpcomingAssessments"; import { RemoveShortcutDiv } from "@/seqta/utils/DisableRemove/RemoveShortcutDiv"; - -import browser from 'webextension-polyfill'; -import type { CustomShortcut } from '@/types/storage'; +import browser from "webextension-polyfill"; +import type { CustomShortcut } from "@/types/storage"; export class StorageChangeHandler { constructor() { @@ -17,13 +15,22 @@ export class StorageChangeHandler { } private registerHandlers() { - settingsState.register('selectedColor', updateAllColors.bind(this)); - settingsState.register('DarkMode', this.handleDarkModeChange.bind(this)); - settingsState.register('onoff', this.handleOnOffChange.bind(this)); - settingsState.register('shortcuts', this.handleShortcutsChange.bind(this)); - settingsState.register('customshortcuts', this.handleCustomShortcutsChange.bind(this)); - settingsState.register('transparencyEffects', this.handleTransparencyEffectsChange.bind(this)); - settingsState.register('subjectfilters', FilterUpcomingAssessments.bind(this)); + settingsState.register("selectedColor", updateAllColors.bind(this)); + settingsState.register("DarkMode", this.handleDarkModeChange.bind(this)); + settingsState.register("onoff", this.handleOnOffChange.bind(this)); + settingsState.register("shortcuts", this.handleShortcutsChange.bind(this)); + settingsState.register( + "customshortcuts", + this.handleCustomShortcutsChange.bind(this), + ); + settingsState.register( + "transparencyEffects", + this.handleTransparencyEffectsChange.bind(this), + ); + settingsState.register( + "subjectfilters", + FilterUpcomingAssessments.bind(this), + ); } private handleDarkModeChange() { @@ -32,16 +39,23 @@ export class StorageChangeHandler { private handleOnOffChange(newValue: boolean) { if (newValue) return; - browser.runtime.sendMessage({ type: 'reloadTabs' }); + browser.runtime.sendMessage({ type: "reloadTabs" }); } - private handleCustomShortcutsChange(newValue: CustomShortcut[], oldValue: CustomShortcut[]) { + private handleCustomShortcutsChange( + newValue: CustomShortcut[], + oldValue: CustomShortcut[], + ) { if (newValue) { if (newValue.length > oldValue.length) { CreateCustomShortcutDiv(newValue[oldValue.length]); } else if (newValue.length < oldValue.length) { const removedElement = oldValue.find( - (oldItem: any) => !newValue.some((newItem: any) => JSON.stringify(oldItem) === JSON.stringify(newItem)) + (oldItem: any) => + !newValue.some( + (newItem: any) => + JSON.stringify(oldItem) === JSON.stringify(newItem), + ), ); if (removedElement) { @@ -51,7 +65,10 @@ export class StorageChangeHandler { } } - private handleShortcutsChange(newValue: { enabled: boolean, name: string }[], oldValue: { enabled: boolean, name: string }[]) { + private handleShortcutsChange( + newValue: { enabled: boolean; name: string }[], + oldValue: { enabled: boolean; name: string }[], + ) { const addedShortcuts = newValue.filter((newItem: any) => { const isAdded = oldValue.some((oldItem: any) => { const match = oldItem.name === newItem.name; @@ -80,9 +97,9 @@ export class StorageChangeHandler { private handleTransparencyEffectsChange(newValue: boolean) { if (newValue) { - document.documentElement.classList.add('transparencyEffects'); + document.documentElement.classList.add("transparencyEffects"); } else { - document.documentElement.classList.remove('transparencyEffects'); + document.documentElement.classList.remove("transparencyEffects"); } } -} \ No newline at end of file +} diff --git a/src/seqta/utils/migration/migrate.html b/src/seqta/utils/migration/migrate.html index 94c36953..9eff2e31 100644 --- a/src/seqta/utils/migration/migrate.html +++ b/src/seqta/utils/migration/migrate.html @@ -7,4 +7,4 @@ - \ No newline at end of file + diff --git a/src/seqta/utils/migration/migration-iframe.ts b/src/seqta/utils/migration/migration-iframe.ts index f7716591..c431ea5b 100644 --- a/src/seqta/utils/migration/migration-iframe.ts +++ b/src/seqta/utils/migration/migration-iframe.ts @@ -2,12 +2,12 @@ interface Data { id: string; blob: Blob; - type: 'image' | 'video'; + type: "image" | "video"; } const openDB = (): Promise => { return new Promise((resolve, reject) => { - const request = indexedDB.open('MyDatabase', 1); + const request = indexedDB.open("MyDatabase", 1); request.onerror = () => reject(request.error); request.onsuccess = () => resolve(request.result); }); @@ -18,7 +18,7 @@ const blobToBase64 = (blob: Blob): Promise => { const reader = new FileReader(); reader.onloadend = () => { const base64 = reader.result as string; - resolve(base64.split(',')[1]); // Remove data URL prefix + resolve(base64.split(",")[1]); // Remove data URL prefix }; reader.onerror = () => reject(reader.error); reader.readAsDataURL(blob); @@ -27,8 +27,8 @@ const blobToBase64 = (blob: Blob): Promise => { const getAllBackgrounds = async (): Promise => { const db = await openDB(); - const tx = db.transaction('backgrounds', 'readonly'); - const store = tx.objectStore('backgrounds'); + const tx = db.transaction("backgrounds", "readonly"); + const store = tx.objectStore("backgrounds"); const request = store.getAll(); return new Promise((resolve, reject) => { @@ -38,88 +38,100 @@ const getAllBackgrounds = async (): Promise => { }; const getSelectedBackground = (): string | null => { - return localStorage.getItem('selectedBackground'); + return localStorage.getItem("selectedBackground"); }; const startMigration = async () => { try { - console.info('Starting background extraction...'); + console.info("Starting background extraction..."); let backgrounds: Data[]; try { backgrounds = await getAllBackgrounds(); if (!backgrounds || backgrounds.length === 0) { - console.info('No backgrounds to migrate'); - window.parent.postMessage({ type: 'MIGRATION_COMPLETE' }, '*'); + console.info("No backgrounds to migrate"); + window.parent.postMessage({ type: "MIGRATION_COMPLETE" }, "*"); return; } } catch (error: any) { - if (error.name === 'NotFoundError' && error.message.includes('object stores was not found')) { - console.info('No backgrounds to migrate: object store not found'); - window.parent.postMessage({ type: 'MIGRATION_COMPLETE' }, '*'); + if ( + error.name === "NotFoundError" && + error.message.includes("object stores was not found") + ) { + console.info("No backgrounds to migrate: object store not found"); + window.parent.postMessage({ type: "MIGRATION_COMPLETE" }, "*"); return; } - console.error('Error fetching backgrounds:', error); - throw new Error('Failed to fetch backgrounds'); + console.error("Error fetching backgrounds:", error); + throw new Error("Failed to fetch backgrounds"); } const selectedBackground = getSelectedBackground(); console.info(`Found ${backgrounds.length} backgrounds`); - window.parent.postMessage({ type: 'GET_LAST_PROCESSED_ID' }, '*'); + window.parent.postMessage({ type: "GET_LAST_PROCESSED_ID" }, "*"); - const lastProcessedId = await new Promise(resolve => { + const lastProcessedId = await new Promise((resolve) => { const handler = (event: MessageEvent) => { - if (event.data.type === 'LAST_PROCESSED_ID') { - window.removeEventListener('message', handler); + if (event.data.type === "LAST_PROCESSED_ID") { + window.removeEventListener("message", handler); resolve(event.data.id); } }; - window.addEventListener('message', handler); + window.addEventListener("message", handler); }); - const remainingBackgrounds = lastProcessedId - ? backgrounds.slice(backgrounds.findIndex(b => b.id === lastProcessedId) + 1) + const remainingBackgrounds = lastProcessedId + ? backgrounds.slice( + backgrounds.findIndex((b) => b.id === lastProcessedId) + 1, + ) : backgrounds; - console.info(`Processing ${remainingBackgrounds.length} remaining backgrounds`); + console.info( + `Processing ${remainingBackgrounds.length} remaining backgrounds`, + ); for (let i = 0; i < remainingBackgrounds.length; i++) { const background = remainingBackgrounds[i]; const base64Data = await blobToBase64(background.blob); - - window.parent.postMessage({ - type: 'BACKGROUND_DATA', - payload: { - id: background.id, - data: base64Data, - mediaType: background.type, - total: backgrounds.length, - processed: i + 1, - isSelected: background.id === selectedBackground - } - }, '*'); - await new Promise(resolve => setTimeout(resolve, 100)); + window.parent.postMessage( + { + type: "BACKGROUND_DATA", + payload: { + id: background.id, + data: base64Data, + mediaType: background.type, + total: backgrounds.length, + processed: i + 1, + isSelected: background.id === selectedBackground, + }, + }, + "*", + ); + + await new Promise((resolve) => setTimeout(resolve, 100)); } - window.parent.postMessage({ type: 'MIGRATION_COMPLETE' }, '*'); - + window.parent.postMessage({ type: "MIGRATION_COMPLETE" }, "*"); } catch (error: any) { - console.error('Extraction failed:', error); - window.parent.postMessage({ - type: 'MIGRATION_ERROR', - error: error.message || 'Unknown error' - }, '*'); + console.error("Extraction failed:", error); + window.parent.postMessage( + { + type: "MIGRATION_ERROR", + error: error.message || "Unknown error", + }, + "*", + ); } }; -window.addEventListener('message', (event) => { +window.addEventListener("message", (event) => { switch (event.data.type) { - case 'PING': - window.parent.postMessage({ type: 'PONG' }, '*'); + case "PING": + window.parent.postMessage({ type: "PONG" }, "*"); break; - - case 'START_MIGRATION': + + case "START_MIGRATION": startMigration(); break; } -}); \ No newline at end of file +}); diff --git a/src/seqta/utils/mutex.ts b/src/seqta/utils/mutex.ts index d04cbf13..1e157f60 100644 --- a/src/seqta/utils/mutex.ts +++ b/src/seqta/utils/mutex.ts @@ -7,6 +7,6 @@ export class Mutex { this.mutex = this.mutex.then(() => new Promise(begin)); - return new Promise(res => begin = res); + return new Promise((res) => (begin = res)); } -} \ No newline at end of file +} diff --git a/src/seqta/utils/sendThemeUpdate.ts b/src/seqta/utils/sendThemeUpdate.ts index 8e895b8e..3f0dda4b 100644 --- a/src/seqta/utils/sendThemeUpdate.ts +++ b/src/seqta/utils/sendThemeUpdate.ts @@ -3,4 +3,4 @@ export default function sendThemeUpdate() { if (iframe) { iframe.contentWindow?.postMessage({ type: 'themeChanged' }, '*'); } */ -} \ No newline at end of file +} diff --git a/src/seqta/utils/setupSettingsButton.ts b/src/seqta/utils/setupSettingsButton.ts index 4c034798..ecf340aa 100644 --- a/src/seqta/utils/setupSettingsButton.ts +++ b/src/seqta/utils/setupSettingsButton.ts @@ -1,33 +1,37 @@ -import { changeSettingsClicked, closeExtensionPopup, SettingsClicked } from "./Closers/closeExtensionPopup" -import { animate } from "motion" -import { settingsState } from "./listeners/SettingsState" +import { + changeSettingsClicked, + closeExtensionPopup, + SettingsClicked, +} from "./Closers/closeExtensionPopup"; +import { animate } from "motion"; +import { settingsState } from "./listeners/SettingsState"; export function setupSettingsButton() { - var AddedSettings = document.getElementById("AddedSettings") - var extensionPopup = document.getElementById("ExtensionPopup") - - AddedSettings!.addEventListener("click", async () => { - if (SettingsClicked) { - closeExtensionPopup(extensionPopup as HTMLElement) + var AddedSettings = document.getElementById("AddedSettings"); + var extensionPopup = document.getElementById("ExtensionPopup"); + + AddedSettings!.addEventListener("click", async () => { + if (SettingsClicked) { + closeExtensionPopup(extensionPopup as HTMLElement); + } else { + if (settingsState.animations) { + animate(0, 1, { + onUpdate: (progress) => { + extensionPopup!.style.opacity = progress.toString(); + extensionPopup!.style.transform = `scale(${progress})`; + }, + type: "spring", + stiffness: 280, + damping: 20, + }); } else { - if (settingsState.animations) { - animate(0, 1, { - onUpdate: (progress) => { - extensionPopup!.style.opacity = progress.toString() - extensionPopup!.style.transform = `scale(${progress})` - }, - type: "spring", - stiffness: 280, - damping: 20, - }) - } else { - extensionPopup!.style.opacity = "1" - extensionPopup!.style.transform = "scale(1)" - extensionPopup!.style.transition = - "opacity 0s linear, transform 0s linear" - } - extensionPopup!.classList.remove("hide") - changeSettingsClicked(true) + extensionPopup!.style.opacity = "1"; + extensionPopup!.style.transform = "scale(1)"; + extensionPopup!.style.transition = + "opacity 0s linear, transform 0s linear"; } - }) - } \ No newline at end of file + extensionPopup!.classList.remove("hide"); + changeSettingsClicked(true); + } + }); +} diff --git a/src/seqta/utils/stringToHTML.ts b/src/seqta/utils/stringToHTML.ts index 0c1a8d7d..d31fd61d 100644 --- a/src/seqta/utils/stringToHTML.ts +++ b/src/seqta/utils/stringToHTML.ts @@ -1,20 +1,20 @@ -import DOMPurify from 'dompurify'; +import DOMPurify from "dompurify"; export default function stringToHTML(str: string, styles = false) { const parser = new DOMParser(); - str = DOMPurify.sanitize(str, { - ADD_ATTR: ['onclick'], - ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|chrome-extension):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i + ADD_ATTR: ["onclick"], + ALLOWED_URI_REGEXP: + /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|chrome-extension):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i, }); - const doc = parser.parseFromString(str, 'text/html'); + const doc = parser.parseFromString(str, "text/html"); if (styles) { doc.body.style.cssText = - 'height: auto; overflow: scroll; margin: 0px; background: var(--background-primary);'; + "height: auto; overflow: scroll; margin: 0px; background: var(--background-primary);"; } return doc.body; -} \ No newline at end of file +} diff --git a/src/seqta/utils/waitForElm.ts b/src/seqta/utils/waitForElm.ts index 46a3231e..78eb4512 100644 --- a/src/seqta/utils/waitForElm.ts +++ b/src/seqta/utils/waitForElm.ts @@ -1,75 +1,75 @@ -import { eventManager } from "@/seqta/utils/listeners/EventManager" -import { delay } from "@/seqta/utils/delay" +import { eventManager } from "@/seqta/utils/listeners/EventManager"; +import { delay } from "@/seqta/utils/delay"; export async function waitForElm( - selector: string, - usePolling: boolean = false, - interval: number = 100, - maxIterations?: number - ): Promise { - if (usePolling) { - return new Promise((resolve, reject) => { - let iterations = 0; - if (maxIterations) { - iterations = 0; - } - const checkForElement = () => { - const element = document.querySelector(selector) - if (element) { - resolve(element) - } else { - if (maxIterations) { - iterations++; - if (iterations >= maxIterations) { - reject(new Error("Element not found")); - } - } - setTimeout(checkForElement, interval) - } - } - - if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", checkForElement) - } else { - checkForElement() - } - }) - } else { - return new Promise((resolve) => { - const registerObserver = () => { - const { unregister } = eventManager.register( - `${selector}`, - { - customCheck: (element) => element.matches(selector), - }, - async (element) => { - resolve(element) - await delay(1) - unregister() // Remove the listener once the element is found - }, - ) - return unregister - } - - let unregister = null - - if (document.readyState === "loading") { - // DOM is still loading, wait for it to be ready - document.addEventListener("DOMContentLoaded", () => { - unregister = registerObserver() - }) - } else { - unregister = registerObserver() - } - - const querySelector = () => document.querySelector(selector) - const element = querySelector() - + selector: string, + usePolling: boolean = false, + interval: number = 100, + maxIterations?: number, +): Promise { + if (usePolling) { + return new Promise((resolve, reject) => { + let iterations = 0; + if (maxIterations) { + iterations = 0; + } + const checkForElement = () => { + const element = document.querySelector(selector); if (element) { - if (unregister) unregister() - resolve(element) - return + resolve(element); + } else { + if (maxIterations) { + iterations++; + if (iterations >= maxIterations) { + reject(new Error("Element not found")); + } + } + setTimeout(checkForElement, interval); } - }) - } - } \ No newline at end of file + }; + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", checkForElement); + } else { + checkForElement(); + } + }); + } else { + return new Promise((resolve) => { + const registerObserver = () => { + const { unregister } = eventManager.register( + `${selector}`, + { + customCheck: (element) => element.matches(selector), + }, + async (element) => { + resolve(element); + await delay(1); + unregister(); // Remove the listener once the element is found + }, + ); + return unregister; + }; + + let unregister = null; + + if (document.readyState === "loading") { + // DOM is still loading, wait for it to be ready + document.addEventListener("DOMContentLoaded", () => { + unregister = registerObserver(); + }); + } else { + unregister = registerObserver(); + } + + const querySelector = () => document.querySelector(selector); + const element = querySelector(); + + if (element) { + if (unregister) unregister(); + resolve(element); + return; + } + }); + } +} diff --git a/src/svelte.config.js b/src/svelte.config.js index c099a835..de2ddd65 100644 --- a/src/svelte.config.js +++ b/src/svelte.config.js @@ -1,7 +1,7 @@ -import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; +import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"; export default { // Consult https://svelte.dev/docs#compile-time-svelte-preprocess // for more information about preprocessors preprocess: vitePreprocess(), -} +}; diff --git a/src/types/CustomThemes.ts b/src/types/CustomThemes.ts index 00202ede..a8620db1 100644 --- a/src/types/CustomThemes.ts +++ b/src/types/CustomThemes.ts @@ -13,7 +13,7 @@ export type CustomTheme = { webURL?: string; selectedColor?: string; forceDark?: boolean; -} +}; export type LoadedCustomTheme = CustomTheme & { CustomImages: { @@ -25,15 +25,15 @@ export type LoadedCustomTheme = CustomTheme & { export type DownloadedTheme = CustomTheme & { webURL: string; -} +}; export type CustomImage = { id: string; blob: Blob; variableName: string; -} +}; export type ThemeList = { themes: CustomTheme[]; selectedTheme: string; -} \ No newline at end of file +}; diff --git a/src/types/storage.ts b/src/types/storage.ts index 4195f00c..0ee0a408 100644 --- a/src/types/storage.ts +++ b/src/types/storage.ts @@ -36,7 +36,7 @@ export interface SettingsState { devMode?: boolean; originalDarkMode?: boolean; newsSource?: string; - + // depreciated keys animatedbk: boolean; bksliderinput: string; diff --git a/tailwind.config.js b/tailwind.config.js index a7738abc..bbcd5564 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,17 +1,15 @@ /** @type {import('tailwindcss').Config} */ export default { - content: [ - "./src/**/*.{js,ts,jsx,tsx,html,svelte}", - ], + content: ["./src/**/*.{js,ts,jsx,tsx,html,svelte}"], darkMode: "class", theme: { fontSize: { - "xs": ".65rem", - "sm": ".775rem", - "base": "0.65rem", - "md": "0.65rem", - "lg": "1rem", - "xl": "1.25rem", + xs: ".65rem", + sm: ".775rem", + base: "0.65rem", + md: "0.65rem", + lg: "1rem", + xl: "1.25rem", "2xl": "1.5rem", "3xl": "1.875rem", "4xl": "2.25rem", @@ -28,18 +26,15 @@ export default { }, extend: { fontFamily: { - "IconFamily": "IconFamily" + IconFamily: "IconFamily", }, animation: { - 'spin-fast': 'spin 0.4s linear infinite', + "spin-fast": "spin 0.4s linear infinite", }, aspectRatio: { - "theme": "5 / 1" - } - } + theme: "5 / 1", + }, + }, }, - plugins: [ - require('@tailwindcss/forms'), - ], + plugins: [require("@tailwindcss/forms")], }; - \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index fc2ff7ba..55f06b66 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -28,10 +28,13 @@ "paths": { "@/*": ["./src/*"] }, - "types": [ - "vite/client", - "node" - ] + "types": ["vite/client", "node"] }, - "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte", "src/interface/+layout.svelte", "declarations.d.ts"] + "include": [ + "src/**/*.ts", + "src/**/*.js", + "src/**/*.svelte", + "src/interface/+layout.svelte", + "declarations.d.ts" + ] } diff --git a/vite.config.ts b/vite.config.ts index d807e3f5..f3e9d1fd 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,49 +1,49 @@ -import { defineConfig } from 'vite'; -import { join, resolve } from 'path'; +import { defineConfig } from "vite"; +import { join, resolve } from "path"; -import touchGlobalCSSPlugin from './lib/touchGlobalCSS'; -import InlineWorkerPlugin from './lib/inlineWorker'; -import { base64Loader } from './lib/base64loader'; -import type { BuildTarget } from './lib/types'; -import ClosePlugin from './lib/closePlugin'; +import touchGlobalCSSPlugin from "./lib/touchGlobalCSS"; +import InlineWorkerPlugin from "./lib/inlineWorker"; +import { base64Loader } from "./lib/base64loader"; +import type { BuildTarget } from "./lib/types"; +import ClosePlugin from "./lib/closePlugin"; import million from "million/compiler"; -import { svelte } from '@sveltejs/vite-plugin-svelte' +import { svelte } from "@sveltejs/vite-plugin-svelte"; -import { chrome } from './src/manifests/chrome'; -import { brave } from './src/manifests/brave'; -import { edge } from './src/manifests/edge'; -import { firefox } from './src/manifests/firefox'; -import { opera } from './src/manifests/opera'; -import { safari } from './src/manifests/safari'; -import { crx } from '@crxjs/vite-plugin'; +import { chrome } from "./src/manifests/chrome"; +import { brave } from "./src/manifests/brave"; +import { edge } from "./src/manifests/edge"; +import { firefox } from "./src/manifests/firefox"; +import { opera } from "./src/manifests/opera"; +import { safari } from "./src/manifests/safari"; +import { crx } from "@crxjs/vite-plugin"; -const targets: BuildTarget[] = [ - chrome, brave, edge, firefox, opera, safari -] +const targets: BuildTarget[] = [chrome, brave, edge, firefox, opera, safari]; -const mode = process.env.MODE || 'chrome'; // Check the environment variable to determine which build type to use. +const mode = process.env.MODE || "chrome"; // Check the environment variable to determine which build type to use. //const sourcemap = (process.env.SOURCEMAP === "true") || false; // Check whether we want sourcemaps. export default defineConfig(({ command }) => ({ plugins: [ base64Loader, InlineWorkerPlugin(), svelte({ - emitCss: false + emitCss: false, }), million.vite({ auto: true }), crx({ - manifest: targets.find(t => t.browser === mode.toLowerCase())?.manifest ?? chrome.manifest, - browser: mode.toLowerCase() === "firefox" ? "firefox" : "chrome" + manifest: + targets.find((t) => t.browser === mode.toLowerCase())?.manifest ?? + chrome.manifest, + browser: mode.toLowerCase() === "firefox" ? "firefox" : "chrome", }), touchGlobalCSSPlugin(), - ...(command === 'build' ? [ClosePlugin()] : []) + ...(command === "build" ? [ClosePlugin()] : []), ], - root: resolve(__dirname, './src'), + root: resolve(__dirname, "./src"), resolve: { alias: { - '@': resolve(__dirname, './src') + "@": resolve(__dirname, "./src"), }, }, server: { @@ -51,36 +51,46 @@ export default defineConfig(({ command }) => ({ hmr: { host: "localhost", protocol: "ws", - port: 5173 - } + port: 5173, + }, }, css: { preprocessorOptions: { scss: { - api: 'modern' - } - } + api: "modern", + }, + }, }, optimizeDeps: { - include: ['@babel/runtime/helpers/extends', '@babel/runtime/helpers/interopRequireDefault'], + include: [ + "@babel/runtime/helpers/extends", + "@babel/runtime/helpers/interopRequireDefault", + ], }, legacy: { skipWebSocketTokenCheck: true, }, worker: { - format: 'es', + format: "es", }, build: { - outDir: resolve(__dirname, 'dist', mode), + outDir: resolve(__dirname, "dist", mode), emptyOutDir: false, minify: false, //sourcemap: sourcemap, rollupOptions: { input: { - settings: join(__dirname, 'src', 'interface', 'index.html'), - migration: join(__dirname, 'src', 'seqta', 'utils', 'migration', 'migrate.html'), - pageState: join(__dirname, 'src', 'pageState.js'), - } - } - } -})); \ No newline at end of file + settings: join(__dirname, "src", "interface", "index.html"), + migration: join( + __dirname, + "src", + "seqta", + "utils", + "migration", + "migrate.html", + ), + pageState: join(__dirname, "src", "pageState.js"), + }, + }, + }, +}));