Compare commits

...

127 Commits

Author SHA1 Message Date
SethBurkart123 3277b02dfb feat: kofi + update dompurify function 2025-02-27 17:20:27 +11:00
SethBurkart123 4703d68bac bump(version): 3.4.5 + changelog 2025-02-27 16:58:14 +11:00
SethBurkart123 696043e01a feat: add alternative news feed sources 2025-02-24 18:41:03 +11:00
SethBurkart123 24ef85c39e feat: add warning if betterseqta is installed 2025-02-24 17:49:17 +11:00
SethBurkart123 35fc996e37 style: improved dark mode colour picker inputs in theme creator 2025-02-24 16:57:20 +11:00
SethBurkart123 edb0a0f929 feat: add fullscreen custom css editor 2025-02-24 16:51:40 +11:00
SethBurkart123 5051d04451 feat: remove maximum width from theme creator 2025-02-24 16:11:34 +11:00
SethBurkart123 ffc695f022 style: improved compose UI tweaks 2025-02-24 16:06:10 +11:00
SethBurkart123 ca5d232e47 feat: reduce permissions for pageState script 2025-02-24 15:52:29 +11:00
Seth Burkart 44325f0d49 fix: update bug_report.md #214 2025-02-24 15:43:22 +11:00
SethBurkart123 c446217916 feat: notifications open the message #10 2025-02-23 21:54:59 +11:00
SethBurkart123 a51049154b feat: complete react fiber control loop 2025-02-23 20:49:09 +11:00
SethBurkart123 f41da95f7e feat: magic button that crashes chrome tabs (yes rly idk why) 2025-02-23 17:54:58 +11:00
SethBurkart123 3e405cc453 feat: html syntax highlighting for more strings 2025-02-21 17:41:09 +11:00
SethBurkart123 d3ae21b7fa fix: page may fail to load due to shortcut links function failing 2025-02-21 17:40:38 +11:00
SethBurkart123 6247e17d70 dev: add dependency cruiser to help visualise and cleanup imports 2025-02-21 17:37:42 +11:00
Seth Burkart 550f2cab54 Merge pull request #208 from ar-cyber/patch-21
Fix subject averages
2025-02-19 17:31:08 +11:00
Andrew R ddb94e6b07 Update SEQTA.ts 2025-02-19 16:52:16 +10:30
Andrew R 12270d28b9 Update package.json 2025-02-19 16:51:45 +10:30
Andrew R 639d35b2f5 mod: change name of toggle to "Letter Grade Averages" 2025-02-19 16:51:04 +10:30
Andrew R 410bd0e54e Update SEQTA.ts 2025-02-19 11:53:10 +10:30
Andrew R c1bc3d3d22 Update package.json 2025-02-19 08:46:47 +10:30
Andrew R 17b093b5ea fix: A BLOODY PERCENT WAS MISSING 2025-02-19 06:51:16 +10:30
Andrew R c8330091ca Update SEQTA.ts 2025-02-19 06:46:03 +10:30
Andrew R 2a00344243 fix incorrect variable 2025-02-19 06:41:48 +10:30
Andrew R cd2c98bd65 fix: implement toggle for letter/number averages 2025-02-19 06:39:55 +10:30
Andrew R 81b690ec9a Update general.svelte 2025-02-19 06:36:36 +10:30
Andrew R 13095cef19 fix: fix codefactor complaints 2025-02-18 21:14:10 +10:30
Andrew R 36ecbd37ed Update SEQTA.ts 2025-02-18 21:00:26 +10:30
Seth Burkart 2cc5ce3f1a Update feature_request.md 2025-02-18 17:06:32 +11:00
Seth Burkart 14aa511198 Update bug_report.md 2025-02-18 17:06:19 +11:00
Andrew R 4e397e3c57 fix: comp errors 2025-02-18 11:32:30 +10:30
Andrew R af311d9b3e Update SEQTA.ts 2025-02-18 10:44:35 +10:30
Andrew R 3af28f574b Update SEQTA.ts 2025-02-18 10:41:34 +10:30
Andrew R 9f1c3e3bc8 fix: only just figured out that parseFloat is for str property only 2025-02-18 10:38:33 +10:30
Andrew R e7df2abc6d fix: add logic for diving / grades 2025-02-18 10:35:18 +10:30
Andrew R c7ae2e1ab6 fix: round the average to 5 so indexing works with other numbers 2025-02-18 10:26:08 +10:30
Andrew R a0888eb091 Update SEQTA.ts 2025-02-18 10:18:17 +10:30
Andrew R e8d9dc7a6b whoops im still in python mode 2025-02-18 10:17:27 +10:30
Andrew R 32934593d8 edit: need a debugger because of issues 2025-02-18 10:13:56 +10:30
Andrew R 855d979b7f Update SEQTA.ts 2025-02-18 10:06:57 +10:30
Andrew R 083dfad5c2 Update SEQTA.ts 2025-02-18 10:05:16 +10:30
Andrew R 9de863be02 incl: add the letter grades to the subject average 2025-02-18 10:03:22 +10:30
SethBurkart123 9d7dab84f1 feat: remove background migration 2025-02-14 18:04:38 +11:00
SethBurkart123 a6999051c4 fix: add back publish-browser-extension for auto publishing 2025-02-14 17:58:39 +11:00
SethBurkart123 c35855559b feat: update changelog 2025-02-14 17:50:37 +11:00
SethBurkart123 8972a5a8bf fix: theme exports and imports sometimes failing due to attached images 2025-02-14 17:49:31 +11:00
SethBurkart123 a321a482cc feat: show manual colour picker tools only during theme creation 2025-02-14 17:15:45 +11:00
SethBurkart123 5f561f516c feat: rely on package.json for version and description 2025-02-14 17:11:33 +11:00
SethBurkart123 395ec3291e fix: custom sidebar layouts not applying on page load #205 2025-02-14 17:07:45 +11:00
SethBurkart123 96b17c7eeb feat: update changelog 2025-02-14 16:37:04 +11:00
SethBurkart123 fad50e6eba feat: change order of zoom timetable buttons 2025-02-14 16:36:17 +11:00
Alphons Joseph f74ad97c0a Merge branch 'main' of https://github.com/BetterSEQTA/BetterSEQTA-Plus 2025-02-11 19:42:46 +08:00
Alphons Joseph 7f4e6cf5ec create function to collapse sidebar 2025-02-11 19:42:41 +08:00
SethBurkart123 677f17c418 fix: colour of timetable buttons in dark mode 2025-02-11 22:06:01 +11:00
Alphons Joseph e58584a55a Merge branch 'main' of https://github.com/BetterSEQTA/BetterSEQTA-Plus 2025-02-11 19:04:04 +08:00
Alphons Joseph 59444dc904 patch timetable not being centred upon zoom 2025-02-11 19:03:47 +08:00
SethBurkart123 178c4fdef4 feat: update changelog 2025-02-11 22:02:01 +11:00
SethBurkart123 cdaaceade7 fix: vite hanging after completing builds 2025-02-11 21:53:37 +11:00
SethBurkart123 d65bfa8c46 feat: add zoom scaling to timetable page #202 2025-02-11 21:40:57 +11:00
SethBurkart123 694d11477d fix: timetable quickbar arrow when placed above recieving incorrect colour 2025-02-11 19:23:11 +11:00
SethBurkart123 61e1bcdae9 fix: timetable clipped at 4pm 2025-02-11 19:05:25 +11:00
SethBurkart123 23a09004d8 feat: keep theme enabled after editing 2025-02-11 19:03:19 +11:00
SethBurkart123 3ce075cd47 fix: theme disabling when opening editor #204 2025-02-11 18:59:57 +11:00
SethBurkart123 92a51daf36 fix: codemirror failing to run 2025-02-11 18:55:57 +11:00
SethBurkart123 479b2878a9 fix: builds failing in some cases + extension failing to load due to vite legacy api 2025-02-11 18:19:07 +11:00
SethBurkart123 18ffa1b47d feat: clean up eventmanager class 2025-02-11 18:03:48 +11:00
Alphons Joseph 6098cf9608 FR #203 2025-02-10 19:49:43 +08:00
Alphons Joseph 5fde2a3660 fix broken build process 2025-02-06 21:17:26 +08:00
SethBurkart123 e4d5f7fd3f chore: remove unused dependencies 2025-02-06 17:45:44 +11:00
Alphons Joseph 31b069056d General updates and 2025-02-05 19:43:51 +08:00
SethBurkart123 3e5ebe8ef4 feat: change theme store to use marquee image for everything 2025-02-05 17:13:36 +11:00
SethBurkart123 338292ac15 style: remove gradient colours on toolbar buttons 2025-02-05 11:20:54 +11:00
SethBurkart123 187c484901 feat: add parsing for letter grades #191 2025-02-05 11:08:26 +11:00
SethBurkart123 24d0616110 feat: clean up compose buttons 2025-02-04 09:58:46 +11:00
SethBurkart123 260ac4aaea style: improved compose UI 2025-02-04 09:48:06 +11:00
SethBurkart123 4311a8fe76 chore: minor css cleanup 2025-02-04 09:09:20 +11:00
SethBurkart123 251e09941b chore: remove unused notices network request and add Svelte module declaration 2025-02-04 09:05:54 +11:00
Seth Burkart bb1541ab2d Merge pull request #195 from ar-cyber/patch-18
fix: anything before this update is now unusable
2025-02-03 16:41:31 +11:00
Andrew R 1c6ec3ee91 fix: anything before this update is now unusable
As a major bug was found impacting some functionality, it is reasonable to push this to make every other version deprecated.
2025-02-03 12:35:19 +10:30
SethBurkart123 0ef0078fb7 bump(version): 3.4.3 + changelog 2025-02-03 13:00:09 +11:00
SethBurkart123 834b8b41af chore: clean up source files 2024-12-05 18:10:32 +11:00
SethBurkart123 f1512ba6e1 chore: clean up code 2024-12-05 14:43:12 +11:00
SethBurkart123 13fc077686 bump(version): 3.4.2 + changelog 2024-12-05 14:38:02 +11:00
SethBurkart123 7cf765121c fix(style): z-index of panels increased 2024-12-05 14:36:04 +11:00
SethBurkart123 4e393f14bb fix: enable assessments average by default 2024-12-05 14:35:23 +11:00
Seth Burkart 98347e038d Merge pull request #192 from ar-cyber/patch-16
Add mention for grades calc in readme
2024-12-03 17:23:35 +11:00
Andrew R f2bdb22ea8 fix(doc): make the readme mention the grade calculator 2024-12-03 13:52:16 +10:30
SethBurkart123 4afab2c52a perf: refactor AddBetterSEQTAElements for improved performance 2024-12-03 07:15:50 +11:00
SethBurkart123 4c6b43d7c7 fix(initial): make assessments average enabled by default 2024-12-03 06:59:06 +11:00
SethBurkart123 9e26d2c192 fix(changelog): versions and changelog fixed 2024-12-02 17:34:39 +11:00
SethBurkart123 7445e8be78 feat(changelog): update changelog + bump version 2024-12-02 12:30:25 +11:00
SethBurkart123 d1a876ff22 fix(home): sidebar button sometimes not selecting 2024-12-02 12:25:14 +11:00
SethBurkart123 e2176ea2fa fix(assessments): placing subject average trice 2024-12-02 12:23:47 +11:00
SethBurkart123 a999e4384b feat(notices): add animations to notices 2024-12-02 12:17:39 +11:00
SethBurkart123 4bf5420140 fix(eventManager): not triggering for already created elements' 2024-12-02 12:17:28 +11:00
SethBurkart123 8fb29f7f21 feat(settings): add subject average setting 2024-12-02 12:01:03 +11:00
SethBurkart123 44e3ed34d0 feat(assessments): add subject averages 2024-12-02 11:58:42 +11:00
SethBurkart123 32228ee4db style(changelog): reduce video padding for better screen realestate 2024-12-02 11:11:49 +11:00
SethBurkart123 1692bd3e92 fix(home): notices date picker not working 2024-12-02 11:11:03 +11:00
SethBurkart123 71cf9dbca8 feat(messages): improvements to direct message animations 2024-12-02 11:01:54 +11:00
SethBurkart123 372b591b16 feat(home): async home page loading 2024-12-02 10:58:24 +11:00
SethBurkart123 7578ecee74 chore(logging): improve logging 2024-12-02 09:27:33 +11:00
SethBurkart123 a2b4f81b86 feat(home): add skeleton loaders to homepage 2024-12-02 09:26:49 +11:00
SethBurkart123 fcd95f6823 perf(homePage): add fragmentation and refactor code 2024-11-30 21:55:10 +11:00
SethBurkart123 f2ea7c8104 fix: builds failing 2024-11-29 17:53:47 +11:00
SethBurkart123 379a3ebda0 chore: code cleanup 2024-11-29 17:34:16 +11:00
SethBurkart123 d1850e8ddb chore: fix CI/CD pipeline 2024-11-29 17:24:02 +11:00
SethBurkart123 88a87692cd fix(icon): discord icon scaled incorrectly 2024-11-29 17:18:42 +11:00
Seth Burkart 4e6e4870b0 Merge pull request #189 from MEGA-Dawg68/main
Added Discord icon
2024-11-29 17:15:53 +11:00
SethBurkart123 18f215fa5f chore: code cleanup 2024-11-29 17:14:32 +11:00
SethBurkart123 2ea8ada439 fix(popups): backdrop filter failing to appear during animation 2024-11-29 17:13:59 +11:00
SethBurkart123 34b2501617 chore(changelog): update changelog 2024-11-29 14:57:41 +11:00
SethBurkart123 88d4d3aa11 feat(news): add animations to news page 2024-11-29 14:57:17 +11:00
SethBurkart123 5ed3a05f6a perf(news): improved news page performance 2024-11-29 14:52:26 +11:00
SethBurkart123 430f158957 style(news): improved news styles 2024-11-29 14:38:41 +11:00
SethBurkart123 547caabc45 bump(version): 3.4.1 + changelog 2024-11-29 14:25:58 +11:00
SethBurkart123 f6e549c5da feat(animations): refine home page animations 2024-11-29 11:40:37 +11:00
SethBurkart123 dc1ae9c0a1 feat(animations): migrate to motion-one@11 2024-11-29 11:32:43 +11:00
SethBurkart123 34306e77cf fix(studentInfo): some cases year may be undefined 2024-11-29 10:29:35 +11:00
SethBurkart123 00c9f03827 bump: version to 3.4.0.4 2024-11-27 09:23:40 +11:00
SethBurkart123 6c93477998 fix: background migration not working properly 2024-11-27 09:22:34 +11:00
MEGA_Dawg68 9784b6162f Added Discord icon 2024-11-25 19:57:45 +08:00
SethBurkart123 96cf8e3eac feat: clean up CI/CD piplelines 2024-11-15 17:24:26 +11:00
SethBurkart123 0e23ea0cc3 fix: build running twice in CI/CD 2024-11-15 17:23:34 +11:00
SethBurkart123 7dfe347562 chore: move install to build section 2024-11-15 17:14:15 +11:00
SethBurkart123 21a8472c94 fix: themes failing to install from coverImage decoding error 2024-11-15 16:35:28 +11:00
53 changed files with 2602 additions and 1047 deletions
+414
View File
@@ -0,0 +1,414 @@
/** @type {import('dependency-cruiser').IConfiguration} */
module.exports = {
forbidden: [
{
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) ',
from: {},
to: {
circular: true
}
},
{
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',
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
]
},
to: {},
},
{
name: 'no-deprecated-core',
comment:
'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',
from: {},
to: {
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$'
],
}
},
{
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',
from: {},
to: {
dependencyTypes: [
'deprecated'
]
}
},
{
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 " +
"available on live with an non-guaranteed version. Fix it by adding the package to the dependencies " +
"in your package.json.",
from: {},
to: {
dependencyTypes: [
'npm-no-pkg',
'npm-unknown'
]
}
},
{
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',
from: {},
to: {
couldNotResolve: true
}
},
{
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',
from: {},
to: {
moreThanOneDependencyType: true,
// 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"]
}
},
/* rules you might want to tweak for your specific situation: */
{
name: 'not-to-spec',
comment:
'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',
from: {},
to: {
path: '[.](?:spec|test)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$'
}
},
{
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 ' +
"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',
from: {
path: '^(src)',
pathNot: '[.](?:spec|test)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$'
},
to: {
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/'
]
}
},
{
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. " +
"If you're using an optional dependency here by design - add an exception to your" +
"dependency-cruiser configuration.",
from: {},
to: {
dependencyTypes: [
'npm-optional'
]
}
},
{
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',
from: {},
to: {
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']
},
/* Which modules to exclude */
// exclude : {
// /* path: an array of regular expressions in strings to match against */
// path: '',
// },
/* Which modules to exclusively include (array of regular expressions in strings)
dependency-cruiser will skip everything not matching this pattern
*/
// includeOnly : [''],
/* List of module systems to cruise.
When left out dependency-cruiser will fall back to the list of _all_
module systems it knows of. It's the default because it's the safe option
It might come at a performance penalty, though.
moduleSystems: ['amd', 'cjs', 'es6', 'tsd']
As in practice only commonjs ('cjs') and ecmascript modules ('es6')
are widely used, you can limit the moduleSystems to those.
*/
// moduleSystems: ['cjs', 'es6'],
/*
false: don't look at JSDoc imports (the default)
true: dependency-cruiser will detect dependencies in JSDoc-style
import statements. Implies "parser": "tsc", so the dependency-cruiser
will use the typescript parser for JavaScript files.
For this to work the typescript compiler will need to be installed in the
same spot as you're running dependency-cruiser from.
*/
// detectJSDocImports: true,
/* prefix for links in html and svg output (e.g. 'https://github.com/you/yourrepo/blob/main/'
to open it on your online repo or `vscode://file/${process.cwd()}/` to
open it in visual studio code),
*/
// prefix: `vscode://file/${process.cwd()}/`,
/* false (the default): ignore dependencies that only exist before typescript-to-javascript compilation
true: also detect dependencies that only exist before typescript-to-javascript compilation
"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.
*/
// extraExtensionsToScan: [".json", ".jpg", ".png", ".svg", ".webp"],
/* if true combines the package.jsons found from the module up to the base
folder the cruise is initiated from. Useful for how (some) mono-repos
manage dependencies & dependency definitions.
*/
// combinedDependencies: false,
/* if true leave symlinks untouched, otherwise use the realpath */
// preserveSymlinks: false,
/* TypeScript project file ('tsconfig.json') to use for
(1) compilation and
(2) resolution (e.g. with the paths property)
The (optional) fileName attribute specifies which file to take (relative to
dependency-cruiser's current working directory). When not provided
defaults to './tsconfig.json'.
*/
tsConfig: {
fileName: 'tsconfig.json'
},
/* Webpack configuration to use to get resolve options from.
The (optional) fileName attribute specifies which file to take (relative
to dependency-cruiser's current working directory. When not provided defaults
to './webpack.conf.js'.
The (optional) `env` and `arguments` attributes contain the parameters
to be passed if your webpack config is a function and takes them (see
webpack documentation for details)
*/
// webpackConfig: {
// fileName: 'webpack.config.js',
// env: {},
// arguments: {}
// },
/* Babel config ('.babelrc', '.babelrc.json', '.babelrc.json5', ...) to use
for compilation
*/
// babelConfig: {
// fileName: '.babelrc',
// },
/* List of strings you have in use in addition to cjs/ es6 requires
& imports to declare module dependencies. Use this e.g. if you've
re-declared require, use a require-wrapper or use window.require as
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
If you use webpack: you can also set these in webpack.conf.js. The set
there will override the ones specified here.
*/
enhancedResolveOptions: {
/* 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.
*/
conditionNames: ["import", "require", "node", "default", "types"],
/* The extensions, by default are the same as the ones dependency-cruiser
can access (run `npx depcruise --info` to see which ones that are in
_your_ environment). If that list is larger than you need you can pass
the extensions you actually use (e.g. [".js", ".jsx"]). This can speed
up module resolution, which is the most expensive step.
*/
// extensions: [".js", ".jsx", ".ts", ".tsx", ".d.ts"],
/* What to consider a 'main' field in package.json */
mainFields: ["module", "main", "types", "typings"],
/* A list of alias fields in package.jsons
See [this specification](https://github.com/defunctzombie/package-browser-field-spec) and
the webpack [resolve.alias](https://webpack.js.org/configuration/resolve/#resolvealiasfields)
documentation.
Defaults to an empty array (= don't use alias fields).
*/
// aliasFields: ["browser"],
},
/* skipAnalysisNotInRules will make dependency-cruiser execute
analysis strictly necessary for checking the rule set only.
See https://github.com/sverweij/dependency-cruiser/blob/main/doc/options-reference.md#skipanalysisnotinrules
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: {
add: [
"bun",
"bun:ffi",
"bun:jsc",
"bun:sqlite",
"bun:test",
"bun:wrap",
"detect-libc",
"undici",
"ws"
]
},
reporterOptions: {
dot: {
/* pattern of modules that can be consolidated in the detailed
graphical dependency graph. The default pattern in this configuration
collapses everything in node_modules to one folder deep so you see
the external modules, but their innards.
*/
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
for details and some examples. If you don't specify a theme
dependency-cruiser falls back to a built-in one.
*/
// theme: {
// graph: {
// /* splines: "ortho" gives straight lines, but is slow on big graphs
// splines: "true" gives bezier curves (fast, not as nice as ortho)
// */
// splines: "true"
// },
// }
},
archi: {
/* pattern of modules that can be consolidated in the high level
graphical dependency graph. If you use the high level graphical
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/(?:@[^/]+/[^/]+|[^/]+)',
/* 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
dot section above and otherwise use the default one.
*/
// theme: { },
},
"text": {
"highlightFocused": true
},
}
}
};
// generated: dependency-cruiser@16.10.0 on 2025-02-16T22:32:01.621Z
+9 -13
View File
@@ -7,21 +7,17 @@ assignees: ''
--- ---
**Describe the bug** **Bug Description**
A clear and concise description of what the bug is. Please provide a clear and concise description of the bug.
**To Reproduce** **Steps to Reproduce**
Please indicate how did you make this happen. Please list the steps taken to reproduce the issue.
**Expected behaviuor** **Expected Behavior**
Please add a clear and concise description of what you expected to happen. Please describe the expected behaviour clearly and concisely.
**Screenshots** **Screenshots**
If applicable, add screenshots to help explain your problem. If applicable, please include any screenshots that may help clarify the issue.
**Desktop (please complete the following information):** **Additional Context**
- OS: [e.g. iOS] Feel free to provide any additional context or information relevant to the problem.
- If using Windows, the build number. Find this by using ```winver``` and copying down the build id.
**Additional context**
Add any other context about the problem here.
@@ -12,9 +12,3 @@ A clear and concise description of what the problem is. Ex. I'm always frustrate
**Describe the solution you'd like** **Describe the solution you'd like**
A clear and concise description of what you want to happen. A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
+24 -59
View File
@@ -1,73 +1,38 @@
name: MVP - make, version & publish name: NodeJS Build
on: on:
push: push:
branches: branches: [ "main" ]
- main pull_request:
workflow_dispatch: # This line adds manual triggering from the GitHub UI branches: [ "main" ]
concurrency: ${{ github.workflow }}-${{ github.ref }}
jobs: jobs:
make_version_publish: build:
name: Make, Version & Publish
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@v4
- name: Setup Node 20.x strategy:
matrix:
node-version: [20.x]
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: 20.x node-version: ${{ matrix.node-version }}
- name: Install bun & Deps - name: Build
run: | run: |
npm install bun -g npm install --legacy-peer-deps
bun install npm run build
- name: 'Build - all browsers' - name: Zip dist folder
id: buildProject run: |
run: MODE=chrome vite build && MODE=firefox vite build zip -r dist.zip dist
- name: '[ V E R S I O N ] : Create or Update Release Pull Request - Version Changes' - name: Upload artifact
id: changesets uses: actions/upload-artifact@v4
uses: changesets/action@v1
with: with:
version: bun run version name: dist-zip
env: path: dist.zip
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: 'Get current version info from package.json'
if: steps.changesets.outputs.hasChangesets == 'false'
id: package
run: |
echo "::set-output name=PACKAGE_NAME::$(jq -r .name package.json)"
echo "::set-output name=PACKAGE_VERSION::$(jq -r .version package.json)"
working-directory: ${{ github.workspace }}
- name: 'Check if a git release already exists for current version'
if: steps.changesets.outputs.hasChangesets == 'false'
id: checkRelease
run: |
TAG_NAME=${{ steps.package.outputs.PACKAGE_NAME }}@${{ steps.package.outputs.PACKAGE_VERSION }}
if gh release view $TAG_NAME &>/dev/null; then
echo "Release $TAG_NAME already exists."
echo "RELEASE_EXISTS=true" >> $GITHUB_ENV
else
echo "RELEASE_EXISTS=false" >> $GITHUB_ENV
fi
- name: 'Create Release Archive(s) - zip 🫰 it 🫰 up 🫰 !'
id: zip
if: steps.changesets.outputs.hasChangesets == 'false'
run: bun run zip
- name: 'Create a git release w/ notes & release archive(s)'
id: gitRelease
if: steps.changesets.outputs.hasChangesets == 'false' && env.RELEASE_EXISTS != 'true'
run: bun run release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PACKAGE_NAME: ${{ steps.package.outputs.PACKAGE_NAME }}
PACKAGE_VERSION: ${{ steps.package.outputs.PACKAGE_VERSION }}
-38
View File
@@ -1,38 +0,0 @@
name: NodeJS Build
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20.x]
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Build
run: |
npm install
npm run build
- 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
+2
View File
@@ -9,6 +9,8 @@ yarn.lock
.env .env
.env.submit .env.submit
dependency-graph.svg
# Build # Build
extension.zip extension.zip
build/ build/
+1
View File
@@ -43,6 +43,7 @@
- Easier Access Notices - Easier Access Notices
- Assessments - Assessments
- Options to remove certain items from the side menu - Options to remove certain items from the side menu
- Grades calculator
- Fully customisable themes and an offical theme store - Fully customisable themes and an offical theme store
- Notification for next lesson (sent 5 minutes before end of the lesson) - Notification for next lesson (sent 5 minutes before end of the lesson)
- Browser Support - Browser Support
+2 -3
View File
@@ -6,11 +6,10 @@ Below here is the supported versions of BetterSEQTA+. Anything older than this i
| Version | Supported | | Version | Supported |
| ------- | ------------------ | | ------- | ------------------ |
| 3.4.0 | :white_check_mark: | | 3.4.3 | |
| <= 3.3 | :x: | | < 3.4.3 | :x: |
`*` May not work on other devices. `*` May not work on other devices.
## Reporting a Vulnerability ## Reporting a Vulnerability
If you find vulnerabilities, REPORT IT IMMEDIATELY. Make an issue and use the template provided for vulnerabilities. If you find vulnerabilities, REPORT IT IMMEDIATELY. Make an issue and use the template provided for vulnerabilities.
+25
View File
@@ -0,0 +1,25 @@
// ref: https://stackoverflow.com/a/76920975
import type { Plugin } from 'vite';
export default function ClosePlugin(): Plugin {
return {
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)
} else {
console.log('Build ended')
}
},
// use this to catch the end of a build without errors
closeBundle() {
console.log('Bundle closed')
process.exit(0)
},
}
}
+16 -1
View File
@@ -25,17 +25,32 @@ export function updateManifestPlugin(): PluginOption {
console.log('** updated **'); console.log('** updated **');
} }
// Implement retry mechanism for file watching
const watchWithRetry = () => {
if (!fs.existsSync(manifestPath)) {
console.log('Manifest not found, retrying in 1 second...');
setTimeout(watchWithRetry, 1000);
return;
}
fs.watchFile(manifestPath, () => { fs.watchFile(manifestPath, () => {
console.log('** watchFile **'); console.log('** watchFile **');
try {
const manifestContents = JSON.parse(fs.readFileSync(manifestPath, 'utf8')); const manifestContents = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
if (manifestContents.web_accessible_resources.some((resource: any) => resource.use_dynamic_url)) { if (manifestContents.web_accessible_resources?.some((resource: any) => resource.use_dynamic_url)) {
const updated = forceDisableUseDynamicUrl(); const updated = forceDisableUseDynamicUrl();
if (updated) { if (updated) {
server.ws.send({ type: 'full-reload' }); server.ws.send({ type: 'full-reload' });
console.log('** updated **'); console.log('** updated **');
} }
} }
} catch (error) {
console.log('Error reading manifest, will retry on next change:', error.message);
}
}); });
};
watchWithRetry();
}); });
}, },
+25 -24
View File
@@ -1,8 +1,8 @@
{ {
"name": "betterseqtaplus", "name": "betterseqtaplus",
"version": "3.4.0", "version": "3.4.5",
"type": "module", "type": "module",
"description": "Enhance SEQTA Learn's usability and aesthetics! A fork of BetterSEQTA to continue development, while incorporating a plethora of new and improved features!", "description": "Enhance SEQTA Learn's usability and aesthetics! A fork of BetterSEQTA to continue development add add heaps more features!",
"browserslist": "> 0.5%, last 2 versions, not dead", "browserslist": "> 0.5%, last 2 versions, not dead",
"scripts": { "scripts": {
"dev": "cross-env MODE=chrome vite dev", "dev": "cross-env MODE=chrome vite dev",
@@ -12,6 +12,7 @@
"build:firefox": "cross-env MODE=firefox vite build", "build:firefox": "cross-env MODE=firefox vite build",
"build:safari": "cross-env MODE=safari vite build", "build:safari": "cross-env MODE=safari vite build",
"convert:safari": "xcrun safari-web-extension-converter dist/safari --project-location . --app-name $npm_package_name-safari", "convert:safari": "xcrun safari-web-extension-converter dist/safari --project-location . --app-name $npm_package_name-safari",
"dependency-graph": "depcruise src --include-only \"^src\" --output-type dot | dot -T svg > dependency-graph.svg",
"release": "gh release create $npm_package_name@$npm_package_version ./dist/*.zip --generate-notes", "release": "gh release create $npm_package_name@$npm_package_version ./dist/*.zip --generate-notes",
"publish": "bun lib/publish.js --b", "publish": "bun lib/publish.js --b",
"zip": "bedframe zip" "zip": "bedframe zip"
@@ -31,43 +32,44 @@
}, },
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@babel/plugin-transform-runtime": "^7.25.9",
"@babel/runtime": "^7.26.7",
"@bedframe/cli": "^0.0.85",
"@crxjs/vite-plugin": "2.0.0-beta.25", "@crxjs/vite-plugin": "2.0.0-beta.25",
"@types/mime-types": "^2.1.4", "@types/mime-types": "^2.1.4",
"@vitejs/plugin-react-swc": "^3.7.0", "@vitejs/plugin-react-swc": "^3.7.2",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"eslint": "^8.57.0", "dependency-cruiser": "^16.10.0",
"glob": "^11.0.0", "eslint": "^8.57.1",
"glob": "^11.0.1",
"mime-types": "^2.1.35", "mime-types": "^2.1.35",
"prettier": "^3.3.3", "prettier": "^3.4.2",
"process": "^0.11.10", "process": "^0.11.10",
"sass": "^1.78.0", "publish-browser-extension": "^3.0.0",
"sass": "^1.83.4",
"sass-loader": "^13.3.3", "sass-loader": "^13.3.3",
"semver": "^7.6.3", "semver": "^7.7.1",
"url": "^0.11.4" "url": "^0.11.4"
}, },
"dependencies": { "dependencies": {
"@bedframe/cli": "^0.0.85",
"@codemirror/lang-css": "^6.3.0", "@codemirror/lang-css": "^6.3.0",
"@codemirror/lang-less": "^6.0.2",
"@codemirror/theme-one-dark": "^6.1.2",
"@sveltejs/vite-plugin-svelte": "^4.0.0", "@sveltejs/vite-plugin-svelte": "^4.0.0",
"@tailwindcss/forms": "^0.5.9", "@tailwindcss/forms": "^0.5.9",
"@tsconfig/svelte": "^5.0.4", "@tsconfig/svelte": "^5.0.4",
"@types/chrome": "^0.0.270", "@types/chrome": "^0.0.270",
"@types/color": "^3.0.6", "@types/color": "^3.0.6",
"@types/dompurify": "^3.0.5", "@types/dompurify": "^3.2.0",
"@types/lodash": "^4.17.7", "@types/lodash": "^4.17.15",
"@types/node": "^20.16.5", "@types/node": "^20.17.17",
"@types/react": "17", "@types/react": "^17.0.83",
"@types/react-dom": "17", "@types/react-dom": "^17.0.26",
"@types/sortablejs": "^1.15.8", "@types/sortablejs": "^1.15.8",
"@types/uuid": "^9.0.8", "@types/uuid": "^9.0.8",
"@types/webextension-polyfill": "^0.10.7", "@types/webextension-polyfill": "^0.10.7",
"@uiw/codemirror-extensions-color": "^4.23.3", "@uiw/codemirror-extensions-color": "^4.23.8",
"@uiw/codemirror-theme-github": "^4.23.3", "@uiw/codemirror-theme-github": "^4.23.8",
"@vitejs/plugin-react": "^4.3.1", "@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"classnames": "^2.5.1",
"codemirror": "^6.0.1", "codemirror": "^6.0.1",
"color": "^4.2.3", "color": "^4.2.3",
"dompurify": "^3.1.6", "dompurify": "^3.1.6",
@@ -75,22 +77,21 @@
"embla-carousel-svelte": "^8.3.1", "embla-carousel-svelte": "^8.3.1",
"fuse.js": "^7.0.0", "fuse.js": "^7.0.0",
"idb": "^8.0.0", "idb": "^8.0.0",
"kolorist": "^1.8.0",
"localforage": "^1.10.0", "localforage": "^1.10.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"million": "^3.1.11", "million": "^3.1.11",
"motion": "^10.18.0", "motion": "^11.12.0",
"postcss": "^8.4.45", "postcss": "^8.4.45",
"publish-browser-extension": "^2.2.1",
"react": "17", "react": "17",
"react-best-gradient-color-picker": "^3.0.10", "react-best-gradient-color-picker": "^3.0.10",
"react-dom": "17", "react-dom": "17",
"rss-parser": "^3.13.0",
"sortablejs": "^1.15.3", "sortablejs": "^1.15.3",
"svelte": "^5.1.9", "svelte": "^5.1.9",
"tailwindcss": "^3.4.11", "tailwindcss": "^3.4.11",
"typescript": "^5.6.2", "typescript": "^5.6.2",
"uuid": "^9.0.1", "uuid": "^9.0.1",
"vite": "^5.4.4", "vite": "^5.4.14",
"webextension-polyfill": "^0.10.0" "webextension-polyfill": "^0.10.0"
} }
} }
+911 -354
View File
File diff suppressed because it is too large Load Diff
+8 -81
View File
@@ -1,61 +1,6 @@
import browser from 'webextension-polyfill' import browser from 'webextension-polyfill'
import type { SettingsState } from "@/types/storage"; import type { SettingsState } from "@/types/storage";
import { fetchNews } from './background/news';
export const openDB = () => {
return new Promise((resolve, reject) => {
const request = indexedDB.open('MyDatabase', 1);
request.onupgradeneeded = (event: any) => {
const db = event.target.result;
db.createObjectStore('backgrounds', { keyPath: 'id' });
};
request.onsuccess = () => {
resolve(request.result);
};
request.onerror = (event: any) => {
reject('Error opening database: ' + event.target.errorCode);
};
});
};
export const writeData = async (type: any, data: any) => {
const db: any = await openDB();
const tx = db.transaction('backgrounds', 'readwrite');
const store = tx.objectStore('backgrounds');
const request = await store.put({ id: 'customBackground', type, data });
return request.result;
};
export const readData = () => {
return new Promise((resolve, reject) => {
openDB()
.then((db: any) => {
const tx = db.transaction('backgrounds', 'readonly');
const store = tx.objectStore('backgrounds');
// Retrieve the custom background
const getRequest = store.get('customBackground');
// Attach success and error event handlers
getRequest.onsuccess = function(event: any) {
resolve(event.target.result);
};
getRequest.onerror = function(event: any) {
console.error('An error occurred:', event);
reject(event);
};
})
.catch(error => {
console.error('An error occurred:', error);
reject(error);
});
});
};
function reloadSeqtaPages() { function reloadSeqtaPages() {
const result = browser.tabs.query({}) const result = browser.tabs.query({})
@@ -70,7 +15,8 @@ function reloadSeqtaPages() {
} }
// Main message listener // Main message listener
browser.runtime.onMessage.addListener((request: any, _sender: any, sendResponse: any) => { browser.runtime.onMessage.addListener((request: any, _: any, sendResponse: (response?: any) => void) => {
switch (request.type) { switch (request.type) {
case 'reloadTabs': case 'reloadTabs':
reloadSeqtaPages(); reloadSeqtaPages();
@@ -92,7 +38,7 @@ browser.runtime.onMessage.addListener((request: any, _sender: any, sendResponse:
sendResponse(response); sendResponse(response);
}); });
}); });
return true; return true; // Keep message channel open for async response
case 'githubTab': case 'githubTab':
browser.tabs.create({ url: 'github.com/BetterSEQTA/BetterSEQTA-Plus' }); browser.tabs.create({ url: 'github.com/BetterSEQTA/BetterSEQTA-Plus' });
@@ -103,18 +49,8 @@ browser.runtime.onMessage.addListener((request: any, _sender: any, sendResponse:
break; break;
case 'sendNews': case 'sendNews':
const date = new Date();
const from = fetchNews(request.source ?? 'australia', sendResponse);
date.getFullYear() +
'-' +
(date.getMonth() + 1) +
'-' +
(date.getDate() - 5);
const url = `https://newsapi.org/v2/everything?domains=abc.net.au&from=${from}&apiKey=17c0da766ba347c89d094449504e3080`;
GetNews(sendResponse, url);
return true; return true;
default: default:
@@ -122,18 +58,6 @@ browser.runtime.onMessage.addListener((request: any, _sender: any, sendResponse:
} }
}); });
function GetNews(sendResponse: any, url: string) {
fetch(url)
.then((result) => result.json())
.then((response) => {
if (response.code == 'rateLimited') {
GetNews(sendResponse, url += '%00');
} else {
sendResponse({ news: response });
}
});
}
const DefaultValues: SettingsState = { const DefaultValues: SettingsState = {
onoff: true, onoff: true,
animatedbk: true, animatedbk: true,
@@ -167,6 +91,7 @@ const DefaultValues: SettingsState = {
originalSelectedColor: '', originalSelectedColor: '',
DarkMode: true, DarkMode: true,
animations: true, animations: true,
assessmentsAverage: true,
defaultPage: 'home', defaultPage: 'home',
shortcuts: [ shortcuts: [
{ {
@@ -219,6 +144,8 @@ const DefaultValues: SettingsState = {
}, },
], ],
customshortcuts: [], customshortcuts: [],
lettergrade: false,
newsSource: 'australia',
}; };
function SetStorageValue(object: any) { function SetStorageValue(object: any) {
+107
View File
@@ -0,0 +1,107 @@
import Parser from 'rss-parser';
const fetchAustraliaNews = async (url: string, sendResponse: any) => {
fetch(url)
.then((result) => result.json())
.then((response) => {
if (response.code == 'rateLimited') {
fetchAustraliaNews(url += '%00', sendResponse);
} else {
sendResponse({ news: response });
}
});
};
const rssFeedsByCountry: Record<string, string[]> = {
usa: [
"https://rss.nytimes.com/services/xml/rss/nyt/HomePage.xml",
"https://www.huffpost.com/section/front-page/feed",
"https://www.npr.org/rss/rss.php",
],
taiwan: [
"https://focustaiwan.tw/rss",
"https://www.taipeitimes.com/rss/all.xml",
"https://international.thenewslens.com/rss",
],
hong_kong: [
"https://news.rthk.hk/rthk/en/rss.htm",
"https://www.scmp.com/rss/91/feed",
],
panama: [
"http://www.panama-guide.com/backend.php",
],
canada: [
"https://www.cbc.ca/cmlink/rss-topstories",
"https://www.theglobeandmail.com/?service=rss",
],
singapore: [
"https://www.straitstimes.com/news/singapore/rss.xml",
"https://www.channelnewsasia.com/rssfeeds/8395986",
],
uk: [
"http://feeds.bbci.co.uk/news/rss.xml",
"https://www.theguardian.com/uk/rss",
],
japan: [
"https://www.japantimes.co.jp/feed/topstories.xml",
"https://www3.nhk.or.jp/nhkworld/en/news/feeds/",
],
netherlands: [
"https://www.dutchnews.nl/feed/",
"http://feeds.nos.nl/nosnieuwsalgemeen",
],
};
export async function fetchNews(source: string, sendResponse: any) {
const parser = new Parser();
let feeds: string[];
if (source === "australia") {
const date = new Date();
const from =
date.getFullYear() +
'-' +
(date.getMonth() + 1) +
'-' +
(date.getDate() - 5);
const url = `https://newsapi.org/v2/everything?domains=abc.net.au&from=${from}&apiKey=17c0da766ba347c89d094449504e3080`;
fetchAustraliaNews(url, sendResponse);
return;
}
if (rssFeedsByCountry[source.toLowerCase()]) {
// If the source is a country, fetch from predefined feeds
feeds = rssFeedsByCountry[source.toLowerCase()];
} else if (source.startsWith("http")) {
// If the source is a URL, use it directly
feeds = [source];
} else {
throw new Error("Invalid source. Provide a country code or a valid RSS feed URL.");
}
const articlesPromises = feeds.map(async (feedUrl) => {
try {
const response = await fetch(feedUrl);
const feedString = await response.text();
const feed = await parser.parseString(feedString);
return feed.items.map((item) => ({
title: item.title || "",
description: item.contentSnippet || "",
url: item.link || "",
urlToImage: null,
}));
} catch (error) {
console.error(`Failed to fetch RSS feed: ${feedUrl}`, error);
return [];
}
});
const articlesArray = await Promise.all(articlesPromises);
const articles = articlesArray.flat();
sendResponse({ news: { articles } });
}
+1 -1
View File
@@ -15,7 +15,7 @@
* along with EvenBetterSEQTA. If not, see <https://www.gnu.org/licenses/>. * along with EvenBetterSEQTA. If not, see <https://www.gnu.org/licenses/>.
*/ */
@import './injected/popup.scss'; @use 'injected/popup.scss';
html { html {
background: #161616 !important; background: #161616 !important;
+192 -40
View File
@@ -1,9 +1,9 @@
@charset "UTF-8"; @use "sass:meta";
@import url("https://fonts.googleapis.com/css?family=Rubik:300,400,500,600"); @import url("https://fonts.googleapis.com/css?family=Rubik:300,400,500,600");
@import "./injected/sidebar-animation.scss"; @include meta.load-css("injected/sidebar-animation.scss");
@import "./injected/theme.scss"; @include meta.load-css("injected/theme.scss");
@import "./injected/transparency.scss"; @include meta.load-css("injected/transparency.scss");
:root { :root {
background: var(--better-main) !important; background: var(--better-main) !important;
@@ -11,10 +11,16 @@
--auto-background: var(--better-pale, var(--background-secondary)) !important; --auto-background: var(--better-pale, var(--background-secondary)) !important;
font-family: Rubik, sans-serif !important; font-family: Rubik, sans-serif !important;
} }
.hidden { .hidden {
display: none; display: none;
} }
button.uiButton.timetable-zoom.iconFamily,
.iconFamily {
font-family: "IconFamily" !important;
}
body, body,
.legacy-root input, .legacy-root input,
.legacy-root textarea, .legacy-root textarea,
@@ -120,6 +126,7 @@ html {
.modaliser-container { .modaliser-container {
backdrop-filter: none !important; backdrop-filter: none !important;
pointer-events: none !important;
} }
.connectedNotificationsWrapper > div > button > svg > g { .connectedNotificationsWrapper > div > button > svg > g {
@@ -202,7 +209,13 @@ html {
.cke_panel { .cke_panel {
border-radius: 16px !important; border-radius: 16px !important;
margin-top: 8px !important; margin-top: 8px !important;
background: unset; background: var(--background-primary) !important;
border: var(--background-secondary) !important;
overflow: clip;
iframe {
background: transparent !important;
}
} }
.legacy-root button:active, .legacy-root button:active,
@@ -222,6 +235,10 @@ html {
} }
} }
.timetable-zoom {
font-size: 14px !important;
}
#main > .dashboard { #main > .dashboard {
grid-template-columns: repeat(autofit, minmax(200px, 400px)) !important; grid-template-columns: repeat(autofit, minmax(200px, 400px)) !important;
background: unset; background: unset;
@@ -243,8 +260,23 @@ html {
color: var(--text-primary); color: var(--text-primary);
} }
.ais-btnSearch .material-icons { .ais-btnSearch {
transition: background 200ms, color 200ms, box-shadow 200ms;
&:hover {
background: rgba(0, 0, 0, 0.2) !important;
color: var(--text-primary) !important;
box-shadow: unset !important;
}
.material-icons {
font-size: 0px !important;
font-family: Rubik, sans-serif !important;
&::before {
font-size: 18px !important; font-size: 18px !important;
content: 'Search' !important;
}
}
} }
} }
@@ -458,6 +490,10 @@ ol:has(.MessageList__avatar___2wxyb svg) {
.content [autocomplete="off"] { .content [autocomplete="off"] {
background: var(--background-primary) !important; background: var(--background-primary) !important;
} }
.coneqtMessage .body .wrapper .iframeWrapper {
background: var(--background-primary) !important;
border-radius: 16px;
}
.MessageList__MessageList___3DxoC .footer { .MessageList__MessageList___3DxoC .footer {
background: var(--background-secondary) !important; background: var(--background-secondary) !important;
} }
@@ -484,9 +520,24 @@ ol:has(.MessageList__avatar___2wxyb svg) {
} }
.singleSelect { .singleSelect {
border-radius: 16px !important; border-radius: 16px !important;
padding: 4px !important;
padding-left: 12px !important; &[style*="absolute"] {
box-shadow: 0px 10px 15px -3px rgba(0, 0, 0, 0.2) !important; box-shadow: 0px 10px 15px -3px rgba(0, 0, 0, 0.2) !important;
padding: 0 2px !important;
outline: 2px solid rgba(0, 0, 0, 0.01) !important;
}
> li {
border-radius: 12px !important;
transition: background 150ms;
margin-bottom: 2px !important;
margin-top: 2px !important;
border-bottom: unset !important;
&:hover {
background: rgba(0, 0, 0, 0.1) !important;
}
}
} }
.quickbar .actions a > svg { .quickbar .actions a > svg {
scale: 0.95; scale: 0.95;
@@ -539,30 +590,43 @@ ol:has(.MessageList__avatar___2wxyb svg) {
clip-path: polygon(50% 40%, 0 100%, 100% 100%); clip-path: polygon(50% 40%, 0 100%, 100% 100%);
border-bottom-color: transparent !important; border-bottom-color: transparent !important;
} }
#main > .timetablepage > .quickbar.below::before { #main > .timetablepage > .quickbar {
&.below::before {
top: -23px; top: -23px;
background-color: inherit; background-color: inherit;
clip-path: polygon(50% 40%, 0 100%, 100% 100%); clip-path: polygon(50% 40%, 0 100%, 100% 100%);
border-bottom-color: transparent !important; border-bottom-color: transparent !important;
} }
#main > .timetablepage > .quickbar.above::after {
&.above[data-yiq="light"]::after {
background-color: rgba(0, 0, 0, 0.2);
}
&.above[data-yiq="dark"]::after {
background-color: rgba(255, 255, 255, 0.2);
}
&.above::after {
content: ""; content: "";
position: absolute; position: absolute;
bottom: -23px; bottom: -24px;
z-index: 2; z-index: 0;
left: 50%; left: 50%;
margin: 0 0 0 -12px; margin: 0 0 0 -12px;
background-color: rgba(255, 255, 255, 0.2);
clip-path: polygon(50% 40%, 0 0, 100% 0); clip-path: polygon(50% 40%, 0 0, 100% 0);
border: 12px solid rgba(255, 255, 255, 0); border: 12px solid rgba(255, 255, 255, 0);
border-top-color: transparent; border-top-color: transparent;
} }
#main > .timetablepage > .quickbar.above::before {
&.above::before {
border-bottom-color: transparent !important; border-bottom-color: transparent !important;
bottom: -23px !important; border-top-color: transparent !important;
bottom: -24px !important;
z-index: -1 !important;
background-color: inherit; background-color: inherit;
clip-path: polygon(50% 40%, 0 0, 100% 0); clip-path: polygon(50% 40%, 0 0, 100% 0);
} }
}
#main .timetablepage .actions a, #main .timetablepage .actions a,
#main .timetablepage .actions button { #main .timetablepage .actions button {
background-color: transparent; background-color: transparent;
@@ -624,9 +688,17 @@ td.colourBar {
#container #content .uiButton { #container #content .uiButton {
border-radius: 16px; border-radius: 16px;
} }
.dark {
#toolbar button.toggled, #toolbar button.toggled,
#toolbar button.depressed { #toolbar button.depressed {
background: var(--better-main); background: #333333;
color: white;
}
}
#toolbar button.toggled,
#toolbar button.depressed {
background: #f3f3f3;
color: black;
} }
ul.buttonChecklist { ul.buttonChecklist {
border-radius: 16px; border-radius: 16px;
@@ -648,14 +720,19 @@ ul.buttonChecklist {
border-radius: 8px !important; border-radius: 8px !important;
} }
&:has(.item.checked) button:nth-child(2) { &:has(.item.checked) button:nth-child(1) {
background: var(--background-secondary) !important; background: var(--background-secondary) !important;
} }
&:has(.item.unchecked) button:nth-child(1) { &:has(.item.unchecked) button:nth-child(2) {
background: var(--background-secondary) !important; background: var(--background-secondary) !important;
} }
} }
.dark ul.buttonChecklist {
li.item.checked {
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="white" viewBox="0 0 24 24"><path d="M9 16.172l10.594-10.594 1.406 1.406-12 12-5.578-5.578 1.406-1.406z"/></svg>');
}
}
#toolbar > span:has(input) { #toolbar > span:has(input) {
flex: 1 1 0%; flex: 1 1 0%;
} }
@@ -1110,9 +1187,9 @@ div > ol:has(.uiFileHandlerWrapper) {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background: var(--background-primary); background: var(--background-primary);
transition: 200ms;
box-shadow: inset 0px 5px 20px 1px rgba(0, 0, 0, 0.3); box-shadow: inset 0px 5px 20px 1px rgba(0, 0, 0, 0.3);
padding-bottom: 25px; padding-bottom: 25px;
transition: none;
color: var(--text-primary); color: var(--text-primary);
} }
.dummynotice { .dummynotice {
@@ -1551,6 +1628,10 @@ iframe.userHTML {
.Collapsible__Collapsible___3O8P3 > .Collapsible__header___-Afvq { .Collapsible__Collapsible___3O8P3 > .Collapsible__header___-Afvq {
background: none; background: none;
} }
.Collapsible__Collapsible___3O8P3 > .Collapsible__content___2c6of.Collapsible__enterActive___3b2ow,
.Collapsible__Collapsible___3O8P3 > .Collapsible__content___2c6of.Collapsible__exitActive___3rFL1 {
animation-timing-function: ease-out !important;
}
.AssessmentList__AssessmentList___1GdCl .AssessmentList__AssessmentList___1GdCl
> .AssessmentList__searchFilter___3N70o > .AssessmentList__searchFilter___3N70o
+ .AssessmentList__items___3LcmQ { + .AssessmentList__items___3LcmQ {
@@ -1752,7 +1833,6 @@ ul {
} }
.content > .wrapper .days tbody tr > td { .content > .wrapper .days tbody tr > td {
overflow: hidden; overflow: hidden;
height: 1440px !important;
} }
.title { .title {
color: var(--text-primary) !important; color: var(--text-primary) !important;
@@ -1928,6 +2008,7 @@ div.bar.flat {
transition: background-color 0.5s ease-in-out; transition: background-color 0.5s ease-in-out;
background-color: rgba(0, 0, 0, 0); background-color: rgba(0, 0, 0, 0);
transition-duration: 500ms !important; transition-duration: 500ms !important;
z-index: 22 !important;
} }
.uiSlidePane.shown > .pane { .uiSlidePane.shown > .pane {
transform: translatey(0%) !important; transform: translatey(0%) !important;
@@ -1961,6 +2042,11 @@ div.bar.flat {
background: unset !important; background: unset !important;
gap: 0 8px; gap: 0 8px;
} }
.cke_toolbar:has(.cke_toolgroup) {
.cke_combo {
margin-right: 8px !important;
}
}
.cke_toolbox > .cke_toolbar > .cke_toolgroup { .cke_toolbox > .cke_toolbar > .cke_toolgroup {
margin: 0 !important; margin: 0 !important;
} }
@@ -1977,23 +2063,50 @@ div.bar.flat {
} }
.cke_toolbox > .cke_toolbar .cke_combo_on > .cke_combo_button, .cke_toolbox > .cke_toolbar .cke_combo_on > .cke_combo_button,
.cke_toolbox > .cke_toolbar .cke_button_on { .cke_toolbox > .cke_toolbar .cke_button_on {
background-color: #797979 !important; background-color: #d5d5d6 !important;
&::after {
background: black !important;
}
}
.quicktable {
border-radius: 12px;
} }
.dark { .dark {
.cke_toolbox > .cke_toolbar .cke_combo_on > .cke_combo_button, .cke_toolbox > .cke_toolbar .cke_combo_on > .cke_combo_button,
.cke_toolbox > .cke_toolbar .cke_button_on { .cke_toolbox > .cke_toolbar .cke_button_on {
background-color: #3d3d3e !important; background-color: #3d3d3e !important;
&::after {
background: rgb(207, 207, 207) !important;
} }
} }
.legacy-root input.singleSelect:focus { }
.legacy-root input.singleSelect {
padding-left: 8px;
&:focus {
background: var(--auto-background); background: var(--auto-background);
color: var(--text-primary) !important; color: var(--text-primary) !important;
} }
}
button.buttonMenu.depressed:hover {
border-radius: 100px;
border-bottom-left-radius: 100px !important;
border-bottom-right-radius: 100px !important;
}
ul.buttonMenu {
border-radius: 16px;
padding: 0 !important;
}
ul.singleSelect, ul.singleSelect,
ul.buttonChecklist, ul.buttonChecklist,
ul.buttonMenu, ul.buttonMenu,
ul.colourButtonOptions, ul.colourButtonOptions,
ul.uiSplitButtonList, ul.uiSplitButtonList,
ul.buttonMenu,
.contactFormPanel { .contactFormPanel {
background: var(--background-primary) !important; background: var(--background-primary) !important;
border: solid 4px var(--background-primary); border: solid 4px var(--background-primary);
@@ -2188,24 +2301,19 @@ li.MessageList__unread___3imtO {
overflow: hidden; overflow: hidden;
width: 100%; width: 100%;
padding: 0 !important; padding: 0 !important;
margin: 0px auto 30px !important;
background: var(--background-primary) !important; background: var(--background-primary) !important;
color: var(--text-primary); color: var(--text-primary);
display: flex; display: flex;
flex-direction: row; flex-direction: row;
-webkit-box-shadow: 0px 5px 16px 6px rgba(0, 0, 0, 0.3); -webkit-box-shadow: 0px 5px 16px 6px rgba(0, 0, 0, 0.3);
box-shadow: 0px 5px 16px 6px rgba(0, 0, 0, 0.3); box-shadow: 0px 5px 16px 6px rgba(0, 0, 0, 0.3);
&:hover {
.ArticleText a {
text-decoration: underline;
} }
.articleimage {
width: 35%;
background-position: center;
background-size: cover;
min-height: 18em;
background-repeat: no-repeat;
}
.NewsArticle img {
width: 35%;
} }
.ArticleText a { .ArticleText a {
padding: 10px 20px; padding: 10px 20px;
margin: 0; margin: 0;
@@ -2213,20 +2321,36 @@ li.MessageList__unread___3imtO {
font-size: 2em; font-size: 2em;
background: none; background: none;
} }
.NewsArticle:hover > .ArticleText a {
text-decoration: underline;
}
.ArticleText p { .ArticleText p {
padding: 10px 20px; padding: 10px 20px;
margin: 0; margin: 0;
font-size: 1.5em; font-size: 1.5em;
} }
.ArticleText { .ArticleText {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 65%; width: 65%;
height: 100%; height: 100%;
} }
.articleimage {
width: 35%;
background-position: center;
background-size: cover;
min-height: 18em;
background-repeat: no-repeat;
}
}
#news-container {
gap: 16px;
> h1 {
margin-bottom: 12px;
}
}
.editmenu { .editmenu {
position: absolute; position: absolute;
right: 0; right: 0;
@@ -2632,11 +2756,15 @@ li.MessageList__unread___3imtO {
} }
.calendar { .calendar {
background: var(--better-main) !important; background: var(--background-primary) !important;
color: var(--text-color) !important; color: var(--text-primary) !important;
border-radius: 16px !important; border-radius: 16px !important;
margin-top: 4px; margin-top: 4px;
&.container {
box-shadow: -2px 2px 30px 0px rgba(0,0,0,0.3) !important;
}
table { table {
background: transparent !important; background: transparent !important;
} }
@@ -2712,7 +2840,7 @@ li.MessageList__unread___3imtO {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
color: var(--text-primary); color: var(--text-primary);
transition: 200ms; transition: 200ms, background-color 0s;
border-radius: 16px; border-radius: 16px;
} }
.dark .upcoming-items { .dark .upcoming-items {
@@ -2956,7 +3084,6 @@ li.MessageList__unread___3imtO {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
color: var(--text-primary); color: var(--text-primary);
animation-fill-mode: forwards;
transform-origin: center center; transform-origin: center center;
} }
.whatsnewHeader { .whatsnewHeader {
@@ -2989,7 +3116,6 @@ li.MessageList__unread___3imtO {
width: 96%; width: 96%;
display: flex; display: flex;
margin: 0 auto; margin: 0 auto;
padding-bottom: 16px;
} }
.whatsnewImg { .whatsnewImg {
margin: 8px auto; margin: 8px auto;
@@ -3082,3 +3208,29 @@ li.MessageList__unread___3imtO {
object-fit: cover; object-fit: cover;
margin-bottom: 12px; margin-bottom: 12px;
} }
@keyframes shimmer {
0% {
background-position: -1000px 0;
}
100% {
background-position: 1000px 0;
}
}
.loading {
&.upcoming-items,
&.day-container {
background: linear-gradient(90deg,
var(--background-primary) 0%,
var(--background-secondary) 50%,
var(--background-primary) 100%
);
background-size: 1000px 100%;
animation: shimmer 2s infinite linear;
}
&.upcoming-items {
height: 35em;
}
}
+1
View File
@@ -3,6 +3,7 @@ declare module '*.woff';
declare module '*.scss'; declare module '*.scss';
declare module '*.png'; declare module '*.png';
declare module '*.html'; declare module '*.html';
declare module '*.svelte';
declare module "*.png?base64" { declare module "*.png?base64" {
const value: string; const value: string;
+2 -2
View File
@@ -20,7 +20,7 @@
let editor = $state<HTMLDivElement | null>(null) let editor = $state<HTMLDivElement | null>(null)
let view: EditorView | null = null; let view: EditorView | null = null;
let editorTheme = new Compartment(); let editorTheme = new Compartment();
let { value, onChange } = $props<{value: string, onChange: (value: string) => void}>() let { value, onChange, className } = $props<{value: string, onChange: (value: string) => void, className?: string}>()
function createEditorState(initialContents: string) { function createEditorState(initialContents: string) {
let extensions = [ let extensions = [
@@ -91,4 +91,4 @@
}) })
</script> </script>
<div class="rounded-lg text-[13px] overflow-clip w-full bg-white dark:bg-zinc-900" bind:this={editor}></div> <div class={`rounded-lg text-[13px] overflow-clip w-full bg-white dark:bg-zinc-900 ${className}`} bind:this={editor}></div>
@@ -8,6 +8,13 @@ div:has(> #rbgcp-wrapper) {
color: white !important; color: white !important;
} }
#rbgcp-inputs-wrap #rbgcp-hex-input,
#rbgcp-inputs-wrap #rbgcp-input {
color: white !important;
background-color: #37373b !important;
border: none !important;
}
div:has(> #rbgcp-solid-btn), div:has(> #rbgcp-solid-btn),
div:has(> #rbgcp-advanced-btn), div:has(> #rbgcp-advanced-btn),
#rbgcp-color-model-btn > div, #rbgcp-color-model-btn > div,
+13 -5
View File
@@ -2,7 +2,7 @@
import { onMount } from 'svelte' import { onMount } from 'svelte'
import ColourPicker from './ColourPicker.tsx'; import ColourPicker from './ColourPicker.tsx';
import ReactAdapter from './utils/ReactAdapter.svelte'; import ReactAdapter from './utils/ReactAdapter.svelte';
import { animate, spring } from 'motion'; import { animate } from 'motion';
import { delay } from '@/seqta/utils/delay.ts' import { delay } from '@/seqta/utils/delay.ts'
const { hidePicker, standalone = false, savePresets = true, customOnChange = null, customState = null } = $props<{ const { hidePicker, standalone = false, savePresets = true, customOnChange = null, customState = null } = $props<{
@@ -23,13 +23,17 @@
animate( animate(
content, content,
{ scale: [1, 0.4], opacity: [1, 0] }, { scale: [1, 0.4], opacity: [1, 0] },
{ easing: spring({ stiffness: 400, damping: 30 }) } {
type: 'spring',
stiffness: 400,
damping: 30
}
); );
animate( animate(
background, background,
{ opacity: [1, 0] }, { opacity: [1, 0] },
{ easing: [0.4, 0, 0.2, 1] } { ease: [0.4, 0, 0.2, 1] }
); );
await delay(400); await delay(400);
@@ -43,13 +47,17 @@
animate( animate(
background, background,
{ opacity: [0, 1] }, { opacity: [0, 1] },
{ duration: 0.3, easing: [0.4, 0, 0.2, 1] } { duration: 0.3, ease: [0.4, 0, 0.2, 1] }
); );
animate( animate(
content, content,
{ scale: [0.4, 1], opacity: [0, 1] }, { scale: [0.4, 1], opacity: [0, 1] },
{ easing: spring({ stiffness: 400, damping: 30 }) } {
type: 'spring',
stiffness: 400,
damping: 30
}
); );
const handleEscapeKey = (e: KeyboardEvent) => { const handleEscapeKey = (e: KeyboardEvent) => {
+1 -1
View File
@@ -93,7 +93,7 @@ export default function Picker({
<ColorPicker <ColorPicker
disableDarkMode={true} disableDarkMode={true}
presets={presets} presets={presets}
hideInputs={true} hideInputs={customOnChange ? false : true}
value={customThemeColor ?? ""} value={customThemeColor ?? ""}
onChange={(color: string) => { onChange={(color: string) => {
if (customOnChange) { if (customOnChange) {
+13 -17
View File
@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { onMount, onDestroy, createEventDispatcher } from 'svelte'; import { onMount, onDestroy } from 'svelte';
import { animate as motionAnimate, spring } from 'motion'; import { animate as motionAnimate } from 'motion';
let { initial, animate, exit, transition, children, class: className } = $props<{ let { initial, animate, exit, transition, children, class: className } = $props<{
initial?: any, initial?: any,
@@ -12,11 +12,9 @@
}>(); }>();
let divElement: HTMLElement; let divElement: HTMLElement;
const dispatch = createEventDispatcher();
const playAnimation = (keyframe: any) => { const playAnimation = (keyframe: any) => {
if (divElement && keyframe) { if (divElement && keyframe) {
let animationOptions = transition;
let finalKeyframe = { ...keyframe }; let finalKeyframe = { ...keyframe };
if (finalKeyframe.height === 'auto') { if (finalKeyframe.height === 'auto') {
@@ -36,16 +34,18 @@
finalKeyframe.height = `${autoHeight}px`; finalKeyframe.height = `${autoHeight}px`;
} }
if (!transition || transition.type === 'spring') { const defaultSpringConfig = { stiffness: 250, damping: 25 };
const springConfig = transition?.config || { stiffness: 250, damping: 25 };
animationOptions = {
...transition,
easing: spring(springConfig)
};
}
const animation = motionAnimate(divElement, finalKeyframe, animationOptions); const animation = motionAnimate(
return animation.finished; [divElement],
finalKeyframe,
{
type: 'spring',
stiffness: transition?.stiffness || defaultSpringConfig.stiffness,
damping: transition?.damping || defaultSpringConfig.damping
}
);
return animation;
} }
return Promise.resolve(); return Promise.resolve();
}; };
@@ -57,16 +57,12 @@
} else if (animate) { } else if (animate) {
await playAnimation(animate); await playAnimation(animate);
} }
dispatch('animationend');
}); });
$effect(() => { $effect(() => {
if (animate) { if (animate) {
playAnimation(animate); playAnimation(animate);
} }
dispatch('animationend');
}); });
onDestroy(async () => { onDestroy(async () => {
+4 -2
View File
@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { animate, spring } from 'motion'; import { animate } from 'motion';
import { standalone } from '../utils/standalone.svelte' import { standalone } from '../utils/standalone.svelte'
let { state, onChange } = $props<{ state: boolean, onChange: (newState: boolean) => void }>(); let { state, onChange } = $props<{ state: boolean, onChange: (newState: boolean) => void }>();
@@ -18,7 +18,9 @@
x: enabled ? (standalone.standalone ? 24 : 20) : 0, x: enabled ? (standalone.standalone ? 24 : 20) : 0,
}, },
{ {
easing: spring(springParams), type: 'spring',
stiffness: springParams.stiffness,
damping: springParams.damping,
} }
); );
}; };
@@ -1,9 +1,5 @@
<script lang="ts"> <script lang="ts">
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
import type { Background } from './types';
export let filteredBackgrounds: Background[];
let dispatch = createEventDispatcher(); let dispatch = createEventDispatcher();
let filters = $state({ let filters = $state({
@@ -13,9 +9,9 @@
orientation: [] as string[] orientation: [] as string[]
}); });
$: { $effect(() => {
dispatch('filter', filters); dispatch('filter', filters);
} });
function toggleFilter(category: keyof typeof filters, value: string) { function toggleFilter(category: keyof typeof filters, value: string) {
if (filters[category].includes(value)) { if (filters[category].includes(value)) {
@@ -42,11 +38,11 @@
<h3 class="mb-2 font-medium">Type</h3> <h3 class="mb-2 font-medium">Type</h3>
<div class="space-y-2"> <div class="space-y-2">
<label class="flex items-center"> <label class="flex items-center">
<input type="checkbox" checked={filters.type.includes('image')} on:change={() => toggleFilter('type', 'image')}> <input type="checkbox" checked={filters.type.includes('image')} onchange={() => toggleFilter('type', 'image')}>
<span class="ml-2">Image</span> <span class="ml-2">Image</span>
</label> </label>
<label class="flex items-center"> <label class="flex items-center">
<input type="checkbox" checked={filters.type.includes('video')} on:change={() => toggleFilter('type', 'video')}> <input type="checkbox" checked={filters.type.includes('video')} onchange={() => toggleFilter('type', 'video')}>
<span class="ml-2">Video</span> <span class="ml-2">Video</span>
</label> </label>
</div> </div>
@@ -56,7 +52,7 @@
<button <button
class="px-4 py-2 mt-4 text-white bg-red-500 rounded hover:bg-red-600" class="px-4 py-2 mt-4 text-white bg-red-500 rounded hover:bg-red-600"
on:click={clearFilters} onclick={clearFilters}
> >
Clear Filters Clear Filters
</button> </button>
@@ -1,4 +1,6 @@
<script lang="ts"> <script lang="ts">
import type { Theme } from '@/interface/types/Theme'
let { theme, onClick } = $props<{ theme: Theme; onClick: () => void }>(); let { theme, onClick } = $props<{ theme: Theme; onClick: () => void }>();
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
@@ -6,12 +8,12 @@
<div class="w-full cursor-pointer" role="button" tabindex="-1" onkeydown={onClick} onclick={onClick}> <div class="w-full cursor-pointer" role="button" tabindex="-1" onkeydown={onClick} onclick={onClick}>
<div class="bg-gray-50 w-full transition-all hover:scale-105 duration-500 relative group flex flex-col hover:shadow-2xl dark:hover:shadow-white/[0.1] hover:shadow-white/[0.8] dark:bg-zinc-800 dark:border-white/[0.1] h-auto rounded-xl overflow-clip border" transition:fade> <div class="bg-gray-50 w-full transition-all hover:scale-105 duration-500 relative group flex flex-col hover:shadow-2xl dark:hover:shadow-white/[0.1] hover:shadow-white/[0.8] dark:bg-zinc-800 dark:border-white/[0.1] h-auto rounded-xl overflow-clip border" transition:fade>
<div class="absolute z-10 mb-1 text-xl font-bold text-white bottom-1 left-3"> <div class="absolute bottom-1 left-3 z-10 mb-1 text-xl font-bold text-white">
{theme.name} {theme.name}
</div> </div>
<div class='absolute bottom-0 z-0 w-full h-3/4 bg-gradient-to-t from-black/80 to-transparent'></div> <div class='absolute bottom-0 z-0 w-full h-3/4 bg-gradient-to-t to-transparent from-black/80'></div>
<div class='w-full'> <div class='w-full'>
<img src={theme.coverImage} alt="Theme Preview" class="object-cover w-full h-48 rounded-md" /> <img src={theme.marqueeImage} alt="Theme Preview" class="object-cover w-full h-48 rounded-md" />
</div> </div>
</div> </div>
</div> </div>
@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import type { Theme } from '@/interface/types/Theme' import type { Theme } from '@/interface/types/Theme'
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
import { animate, spring } from 'motion'; import { animate } from 'motion';
let { theme, currentThemes, setDisplayTheme, onInstall, onRemove, allThemes, displayTheme } = $props<{ let { theme, currentThemes, setDisplayTheme, onInstall, onRemove, allThemes, displayTheme } = $props<{
theme: Theme | null; theme: Theme | null;
@@ -28,7 +28,11 @@
animate( animate(
modalElement, modalElement,
{ y: [500, 0], opacity: [0, 1] }, { y: [500, 0], opacity: [0, 1] },
{ easing: spring({ stiffness: 150, damping: 20 }) } {
type: 'spring',
stiffness: 150,
damping: 20
}
); );
} }
}); });
@@ -37,7 +41,11 @@
animate( animate(
modalElement, modalElement,
{ y: [10, 500], opacity: [1, 0] }, { y: [10, 500], opacity: [1, 0] },
{ easing: spring({ stiffness: 150, damping: 20 }) } {
type: 'spring',
stiffness: 150,
damping: 20
}
); );
setTimeout(() => { setTimeout(() => {
setDisplayTheme(relatedTheme ?? null); setDisplayTheme(relatedTheme ?? null);
@@ -46,7 +54,7 @@
</script> </script>
<div <div
class="fixed inset-0 z-50 flex items-end justify-center bg-black bg-opacity-70" class="flex fixed inset-0 z-50 justify-center items-end bg-black bg-opacity-70"
onclick={(e) => { onclick={(e) => {
if (e.target === e.currentTarget) hideModal(); if (e.target === e.currentTarget) hideModal();
}} }}
@@ -71,12 +79,12 @@
<h2 class="mb-4 text-2xl font-bold"> <h2 class="mb-4 text-2xl font-bold">
{theme.name} {theme.name}
</h2> </h2>
<img src={theme.marqueeImage} alt="Theme Cover" class="object-cover w-full mb-4 rounded-md" /> <img src={theme.marqueeImage} alt="Theme Cover" class="object-cover mb-4 w-full rounded-md" />
<p class="mb-4 text-gray-700 dark:text-gray-300"> <p class="mb-4 text-gray-700 dark:text-gray-300">
{theme.description} {theme.description}
</p> </p>
{#if currentThemes.includes(theme.id)} {#if currentThemes.includes(theme.id)}
<button onclick={async () => {installing = true; await onRemove(theme.id); installing = false}} class="relative flex items-center justify-center w-32 px-4 py-2 mt-4 ml-auto text-black rounded-full dark:text-white bg-zinc-300 dark:bg-zinc-700 dark:hover:bg-zinc-600/50 hover:bg-zinc-200"> <button onclick={async () => {installing = true; await onRemove(theme.id); installing = false}} class="flex relative justify-center items-center px-4 py-2 mt-4 ml-auto w-32 text-black rounded-full dark:text-white bg-zinc-300 dark:bg-zinc-700 dark:hover:bg-zinc-600/50 hover:bg-zinc-200">
{#if installing} {#if installing}
<svg class="absolute w-4 h-4 { installing ? 'opacity-100' : 'opacity-0' }" width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> <svg class="absolute w-4 h-4 { installing ? 'opacity-100' : 'opacity-0' }" width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke="currentColor" fill="currentColor" class="origin-center animate-spin-fast" d="M2,12A11.2,11.2,0,0,1,13,1.05C12.67,1,12.34,1,12,1a11,11,0,0,0,0,22c.34,0,.67,0,1-.05C6,23,2,17.74,2,12Z"/> <path stroke="currentColor" fill="currentColor" class="origin-center animate-spin-fast" d="M2,12A11.2,11.2,0,0,1,13,1.05C12.67,1,12.34,1,12,1a11,11,0,0,0,0,22c.34,0,.67,0,1-.05C6,23,2,17.74,2,12Z"/>
@@ -85,7 +93,7 @@
<span class="{ installing ? 'opacity-0' : 'opacity-100' }">Remove</span> <span class="{ installing ? 'opacity-0' : 'opacity-100' }">Remove</span>
</button> </button>
{:else} {:else}
<button onclick={async () => {installing = true; await onInstall(theme.id); installing = false}} class="relative flex items-center justify-center w-32 px-4 py-2 mt-4 ml-auto text-black rounded-full dark:text-white bg-zinc-300 dark:bg-zinc-700 dark:hover:bg-zinc-600/50 hover:bg-zinc-200"> <button onclick={async () => {installing = true; await onInstall(theme.id); installing = false}} class="flex relative justify-center items-center px-4 py-2 mt-4 ml-auto w-32 text-black rounded-full dark:text-white bg-zinc-300 dark:bg-zinc-700 dark:hover:bg-zinc-600/50 hover:bg-zinc-200">
{#if installing} {#if installing}
<svg class="absolute w-4 h-4 { installing ? 'opacity-100' : 'opacity-0' }" width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> <svg class="absolute w-4 h-4 { installing ? 'opacity-100' : 'opacity-0' }" width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke="currentColor" fill="currentColor" class="origin-center animate-spin-fast" d="M2,12A11.2,11.2,0,0,1,13,1.05C12.67,1,12.34,1,12,1a11,11,0,0,0,0,22c.34,0,.67,0,1-.05C6,23,2,17.74,2,12Z"/> <path stroke="currentColor" fill="currentColor" class="origin-center animate-spin-fast" d="M2,12A11.2,11.2,0,0,1,13,1.05C12.67,1,12.34,1,12,1a11,11,0,0,0,0,22c.34,0,.67,0,1-.05C6,23,2,17.74,2,12Z"/>
@@ -104,11 +112,11 @@
{#each getRelatedThemes() as relatedTheme (relatedTheme.id)} {#each getRelatedThemes() as relatedTheme (relatedTheme.id)}
<button onclick={() => { hideModal(relatedTheme) }} class="w-full cursor-pointer"> <button onclick={() => { hideModal(relatedTheme) }} class="w-full cursor-pointer">
<div class="bg-gray-50 w-full transition-all hover:scale-105 duration-500 relative group group/card flex flex-col hover:shadow-2xl dark:hover:shadow-white/[0.1] hover:shadow-white/[0.8] dark:bg-zinc-800 dark:border-white/[0.1] h-auto rounded-xl overflow-clip border"> <div class="bg-gray-50 w-full transition-all hover:scale-105 duration-500 relative group group/card flex flex-col hover:shadow-2xl dark:hover:shadow-white/[0.1] hover:shadow-white/[0.8] dark:bg-zinc-800 dark:border-white/[0.1] h-auto rounded-xl overflow-clip border">
<div class="absolute z-10 mb-1 text-xl font-bold text-white transition-all duration-500 group-hover:-translate-y-0.5 bottom-1 left-3"> <div class="absolute bottom-1 left-3 z-10 mb-1 text-xl font-bold text-white transition-all duration-500 group-hover:-translate-y-0.5">
{relatedTheme.name} {relatedTheme.name}
</div> </div>
<div class="absolute bottom-0 z-0 w-full h-3/4 bg-gradient-to-t from-black/80 to-transparent"></div> <div class="absolute bottom-0 z-0 w-full h-3/4 bg-gradient-to-t to-transparent from-black/80"></div>
<img src={relatedTheme.coverImage} alt="Theme Preview" class="object-cover w-full h-48" /> <img src={relatedTheme.marqueeImage} alt="Theme Preview" class="object-cover w-full h-48" />
</div> </div>
</button> </button>
{/each} {/each}
@@ -98,7 +98,7 @@
</script> </script>
<div <div
class="w-full pt-5 mb-1" class="pt-5 mb-1 w-full"
role="list" role="list"
tabindex="-1" tabindex="-1"
ondragover={handleDragOver} ondragover={handleDragOver}
@@ -106,9 +106,9 @@
ondrop={handleDrop} ondrop={handleDrop}
> >
<div class="{isDragging ? 'opacity-100' : 'opacity-0'} transition pointer-events-none absolute w-full p-2 z-50"> <div class="{isDragging ? 'opacity-100' : 'opacity-0'} transition pointer-events-none absolute w-full p-2 z-50">
<div class="sticky w-full h-64 bg-white shadow-xl dark:bg-zinc-900 top-5 dark:text-white rounded-xl outline-dashed outline-4 outline-zinc-200 dark:outline-zinc-700"> <div class="sticky top-5 w-full h-64 bg-white rounded-xl shadow-xl dark:bg-zinc-900 dark:text-white outline-dashed outline-4 outline-zinc-200 dark:outline-zinc-700">
<div class="flex items-center justify-center h-full"> <div class="flex justify-center items-center h-full">
<div class="flex flex-col items-center justify-center"> <div class="flex flex-col justify-center items-center">
<svg height="48" width="48" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg"> <svg height="48" width="48" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
<g fill="currentColor"> <g fill="currentColor">
<path d="M44,31a1,1,0,0,0-1,1v8a3,3,0,0,1-3,3H8a3,3,0,0,1-3-3V32a1,1,0,0,0-2,0v8a5.006,5.006,0,0,0,5,5H40a5.006,5.006,0,0,0,5-5V32A1,1,0,0,0,44,31Z" fill="currentColor"/> <path d="M44,31a1,1,0,0,0-1,1v8a3,3,0,0,1-3,3H8a3,3,0,0,1-3-3V32a1,1,0,0,0-2,0v8a5.006,5.006,0,0,0,5,5H40a5.006,5.006,0,0,0,5-5V32A1,1,0,0,0,44,31Z" fill="currentColor"/>
@@ -130,7 +130,7 @@
> >
{#if isEditMode} {#if isEditMode}
<div <div
class="absolute z-20 flex w-6 h-6 p-2 text-white bg-red-600 rounded-full opacity-100 right-2 place-items-center top-2" class="flex absolute top-2 right-2 z-20 place-items-center p-2 w-6 h-6 text-white bg-red-600 rounded-full opacity-100"
onclick={(event) => { event.stopPropagation(); handleThemeDelete(theme.id) }} onclick={(event) => { event.stopPropagation(); handleThemeDelete(theme.id) }}
onkeydown={(event) => { if (event.key === 'Enter' || event.key === ' ') handleThemeDelete(theme.id) }} onkeydown={(event) => { if (event.key === 'Enter' || event.key === ' ') handleThemeDelete(theme.id) }}
role="button" role="button"
@@ -152,7 +152,7 @@
</div> </div>
<div <div
class="absolute z-20 flex w-8 h-8 p-2 text-center transition-all -translate-y-1/2 rounded-full opacity-0 text-white/80 top-1/4 right-12 bg-black/50 place-items-center group-hover:opacity-100 group-hover:top-1/2" class="flex absolute right-12 top-1/4 z-20 place-items-center p-2 w-8 h-8 text-center rounded-full opacity-0 transition-all -translate-y-1/2 text-white/80 bg-black/50 group-hover:opacity-100 group-hover:top-1/2"
onclick={(event) => { event.stopPropagation(); handleShareTheme(theme) }} onclick={(event) => { event.stopPropagation(); handleShareTheme(theme) }}
onkeydown={(event) => { if (event.key === 'Enter' || event.key === ' ') handleShareTheme(theme) }} onkeydown={(event) => { if (event.key === 'Enter' || event.key === ' ') handleShareTheme(theme) }}
role="button" role="button"
@@ -167,7 +167,7 @@
<img <img
src={typeof theme.coverImage === 'string' ? theme.coverImage : URL.createObjectURL(theme.coverImage)} src={typeof theme.coverImage === 'string' ? theme.coverImage : URL.createObjectURL(theme.coverImage)}
alt={theme.name} alt={theme.name}
class="absolute inset-0 z-0 object-cover w-full h-full pointer-events-none" class="object-cover absolute inset-0 z-0 w-full h-full pointer-events-none"
/> />
{/if} {/if}
{#if !theme.hideThemeName} {#if !theme.hideThemeName}
@@ -179,7 +179,7 @@
{/if} {/if}
{#if tempTheme} {#if tempTheme}
<div class="flex justify-center w-full bg-gray-200 rounded-xl dark:bg-zinc-700/50 place-items-center aspect-theme animate-pulse"> <div class="flex justify-center place-items-center w-full bg-gray-200 rounded-xl animate-pulse dark:bg-zinc-700/50 aspect-theme">
<svg class="w-5 h-5 text-white animate-spin" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> <svg class="w-5 h-5 text-white animate-spin" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
@@ -193,7 +193,7 @@
<button <button
onclick={() => OpenStorePage()} onclick={() => OpenStorePage()}
class="flex items-center justify-center w-full transition aspect-theme rounded-xl bg-zinc-100 dark:bg-zinc-900 dark:text-white" class="flex justify-center items-center w-full rounded-xl transition aspect-theme bg-zinc-100 dark:bg-zinc-900 dark:text-white"
> >
<span class="text-xl font-IconFamily">&#xecc5;</span> <span class="text-xl font-IconFamily">&#xecc5;</span>
<span class="ml-2">Theme Store</span> <span class="ml-2">Theme Store</span>
@@ -201,7 +201,7 @@
<button <button
onclick={() => { OpenThemeCreator(); closeExtensionPopup() }} onclick={() => { OpenThemeCreator(); closeExtensionPopup() }}
class="flex items-center justify-center w-full transition aspect-theme rounded-xl bg-zinc-100 dark:bg-zinc-900 dark:text-white" class="flex justify-center items-center w-full rounded-xl transition aspect-theme bg-zinc-100 dark:bg-zinc-900 dark:text-white"
> >
<span class="text-xl font-IconFamily">&#xec60;</span> <span class="text-xl font-IconFamily">&#xec60;</span>
<span class="ml-2">Create your own</span> <span class="ml-2">Create your own</span>
+5 -1
View File
@@ -48,5 +48,9 @@ input {
.cm-editor { .cm-editor {
width: 100%; width: 100%;
min-height: 100px; min-height: 100px;
max-height: 400px; height: inherit;
}
.editorHeight {
height: calc(100vh - 58px);
} }
+4 -4
View File
@@ -61,8 +61,8 @@
</script> </script>
<div class="w-[384px] no-scrollbar shadow-2xl {$settingsState.DarkMode ? 'dark' : ''} { standalone ? 'h-[600px]' : 'h-full rounded-xl' } overflow-clip"> <div class="w-[384px] no-scrollbar shadow-2xl {$settingsState.DarkMode ? 'dark' : ''} { standalone ? 'h-[600px]' : 'h-full rounded-xl' } overflow-clip">
<div class="relative flex flex-col h-full gap-2 bg-white overflow-clip dark:bg-zinc-800 dark:text-white"> <div class="flex relative flex-col gap-2 h-full overflow-clip bg-white dark:bg-zinc-800 dark:text-white">
<div class="grid border-b border-b-zinc-200/40 place-items-center"> <div class="grid place-items-center border-b border-b-zinc-200/40">
<!-- svelte-ignore a11y_no_noninteractive_element_interactions --> <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
<!-- svelte-ignore a11y_click_events_have_key_events --> <!-- svelte-ignore a11y_click_events_have_key_events -->
<img src={browser.runtime.getURL('resources/icons/betterseqta-dark-full.png')} class="w-4/5 dark:hidden" alt="Light logo" onclick={handleDevModeToggle} /> <img src={browser.runtime.getURL('resources/icons/betterseqta-dark-full.png')} class="w-4/5 dark:hidden" alt="Light logo" onclick={handleDevModeToggle} />
@@ -71,8 +71,8 @@
<img src={browser.runtime.getURL('resources/icons/betterseqta-light-full.png')} class="hidden w-4/5 dark:block" alt="Dark logo" onclick={handleDevModeToggle} /> <img src={browser.runtime.getURL('resources/icons/betterseqta-light-full.png')} class="hidden w-4/5 dark:block" alt="Dark logo" onclick={handleDevModeToggle} />
{#if !standalone} {#if !standalone}
<button onclick={openChangelog} class="absolute w-8 h-8 text-lg rounded-xl font-IconFamily top-1 right-1 bg-zinc-100 dark:bg-zinc-700">{'\ue929'}</button> <button onclick={openChangelog} class="absolute top-1 right-1 w-8 h-8 text-lg rounded-xl font-IconFamily bg-zinc-100 dark:bg-zinc-700">{'\ue929'}</button>
<button onclick={openAbout} class="absolute w-8 h-8 text-lg rounded-xl font-IconFamily top-1 right-10 bg-zinc-100 dark:bg-zinc-700">{'\ueb73'}</button> <button onclick={openAbout} class="absolute top-1 right-10 w-8 h-8 text-lg rounded-xl font-IconFamily bg-zinc-100 dark:bg-zinc-700">{'\ueb73'}</button>
{/if} {/if}
</div> </div>
+46 -3
View File
@@ -15,7 +15,7 @@
</script> </script>
{#snippet Setting({ title, description, Component, props }: SettingsList) } {#snippet Setting({ title, description, Component, props }: SettingsList) }
<div class="flex items-center justify-between px-4 py-3"> <div class="flex justify-between items-center px-4 py-3">
<div class="pr-4"> <div class="pr-4">
<h2 class="text-sm font-bold">{title}</h2> <h2 class="text-sm font-bold">{title}</h2>
<p class="text-xs">{description}</p> <p class="text-xs">{description}</p>
@@ -28,6 +28,7 @@
<div class="flex flex-col divide-y divide-zinc-100 dark:divide-zinc-700"> <div class="flex flex-col divide-y divide-zinc-100 dark:divide-zinc-700">
{#each [ {#each [
{ {
title: "Transparency Effects", title: "Transparency Effects",
description: "Enables transparency effects on certain elements such as blur. (May impact battery life)", description: "Enables transparency effects on certain elements such as blur. (May impact battery life)",
@@ -97,6 +98,26 @@
onChange: (isOn: boolean) => settingsState.notificationcollector = isOn onChange: (isOn: boolean) => settingsState.notificationcollector = isOn
} }
}, },
{
title: "Assessment Average",
description: "Shows your subject average for assessments.",
id: 8,
Component: Switch,
props: {
state: $settingsState.assessmentsAverage,
onChange: (isOn: boolean) => settingsState.assessmentsAverage = isOn
}
},
{
title: "Letter Grade Averages",
description: "Shows the letter grade instead of the percentage in subject averages.",
id: 8,
Component: Switch,
props: {
state: $settingsState.lettergrade,
onChange: (isOn: boolean) => settingsState.lettergrade = isOn
}
},
{ {
title: "Lesson Alerts", title: "Lesson Alerts",
description: "Sends a native browser notification ~5 minutes prior to lessons.", description: "Sends a native browser notification ~5 minutes prior to lessons.",
@@ -136,10 +157,32 @@
] ]
} }
}, },
{
title: "News Feed Source",
description: "Choose sources of your news feed.",
id: 11,
Component: Select,
props: {
state: $settingsState.newsSource,
onChange: (value: string) => settingsState.newsSource = value,
options: [
{ value: "australia", label: "Australia" },
{ value: "usa", label: "USA" },
{ value: "taiwan", label: "Taiwan" },
{ value: "hong_kong", label: "Hong Kong" },
{ value: "panama", label: "Panama" },
{ value: "canada", label: "Canada" },
{ value: "singapore", label: "Singapore" },
{ value: "uk", label: "UK" },
{ value: "japan", label: "Japan" },
{ value: "netherlands", label: "Netherlands" }
]
}
},
{ {
title: "BetterSEQTA+", title: "BetterSEQTA+",
description: "Enables BetterSEQTA+ features", description: "Enables BetterSEQTA+ features",
id: 11, id: 12,
Component: Switch, Component: Switch,
props: { props: {
state: $settingsState.onoff, state: $settingsState.onoff,
@@ -160,7 +203,7 @@
<Switch state={$settingsState.devMode} onChange={(isOn: boolean) => settingsState.devMode = isOn} /> <Switch state={$settingsState.devMode} onChange={(isOn: boolean) => settingsState.devMode = isOn} />
</div> </div>
</div> </div>
<div class="flex items-center justify-between px-4 py-3"> <div class="flex justify-between items-center px-4 py-3">
<div class="pr-4"> <div class="pr-4">
<h2 class="text-sm font-bold">Sensitive Hider</h2> <h2 class="text-sm font-bold">Sensitive Hider</h2>
<p class="text-xs">Replace sensitive content with mock data</p> <p class="text-xs">Replace sensitive content with mock data</p>
+45 -14
View File
@@ -27,6 +27,7 @@
import { CloseThemeCreator } from '@/seqta/ui/ThemeCreator' import { CloseThemeCreator } from '@/seqta/ui/ThemeCreator'
import { themeUpdates } from '../hooks/ThemeUpdates' import { themeUpdates } from '../hooks/ThemeUpdates'
import { disableTheme } from '@/seqta/ui/themes/disableTheme' import { disableTheme } from '@/seqta/ui/themes/disableTheme'
import { setTheme } from '@/seqta/ui/themes/setTheme'
const { themeID } = $props<{ themeID: string }>() const { themeID } = $props<{ themeID: string }>()
let theme = $state<LoadedCustomTheme>({ let theme = $state<LoadedCustomTheme>({
@@ -45,6 +46,12 @@
}) })
let closedAccordions = $state<string[]>([]) let closedAccordions = $state<string[]>([])
let themeLoaded = $state(false); let themeLoaded = $state(false);
let codeEditorFullscreen = $state(false);
function toggleCodeEditorFullscreen(e: MouseEvent) {
e.preventDefault();
codeEditorFullscreen = !codeEditorFullscreen;
}
function toggleAccordion(title: string) { function toggleAccordion(title: string) {
if (closedAccordions.includes(title)) { if (closedAccordions.includes(title)) {
@@ -55,7 +62,7 @@
} }
onMount(async () => { onMount(async () => {
disableTheme(); await disableTheme();
if (themeID) { if (themeID) {
const tempTheme = await getTheme(themeID) const tempTheme = await getTheme(themeID)
@@ -111,6 +118,7 @@
ClearThemePreview(); ClearThemePreview();
saveTheme(themeClone); saveTheme(themeClone);
setTheme(themeClone.id);
themeUpdates.triggerUpdate(); themeUpdates.triggerUpdate();
CloseThemeCreator(); CloseThemeCreator();
} }
@@ -166,7 +174,14 @@
</div> </div>
{#if item.direction === 'vertical'} {#if item.direction === 'vertical'}
<div class="flex items-center justify-center h-full text-xl font-light text-zinc-500 dark:text-zinc-300"> <div class="flex justify-center items-center h-full text-xl font-light text-zinc-500 dark:text-zinc-300">
{#if item.type === 'codeEditor'}
<!-- Fullscreen toggle button -->
<button onclick={toggleCodeEditorFullscreen} class="mr-2 text-lg font-IconFamily">
{'\uebdb'}
</button>
{/if}
<span class='font-IconFamily transition-transform duration-300 {closedAccordions.includes(item.title) ? 'rotate-180' : ''}'>{'\ue9e6'}</span> <span class='font-IconFamily transition-transform duration-300 {closedAccordions.includes(item.title) ? 'rotate-180' : ''}'>{'\ue9e6'}</span>
</div> </div>
{/if} {/if}
@@ -185,12 +200,15 @@
<ColourPicker savePresets={false} standalone={true} {...(item.props)} /> <ColourPicker savePresets={false} standalone={true} {...(item.props)} />
{/key} {/key}
{:else if item.type === 'codeEditor'} {:else if item.type === 'codeEditor'}
{#if !codeEditorFullscreen}
{#key themeLoaded} {#key themeLoaded}
<CodeEditor {...(item.props as CodeEditorProps)} /> <!-- Only render inline if not fullscreen -->
<CodeEditor className="h-[400px]" {...(item.props as CodeEditorProps)} />
{/key} {/key}
{/if}
{:else if item.type === 'imageUpload'} {:else if item.type === 'imageUpload'}
{#each theme.CustomImages as image (image.id)} {#each theme.CustomImages as image (image.id)}
<div class="flex items-center h-16 gap-2 px-2 py-2 mb-4 bg-white rounded-lg shadow-lg dark:bg-zinc-700"> <div class="flex gap-2 items-center px-2 py-2 mb-4 h-16 bg-white rounded-lg shadow-lg dark:bg-zinc-700">
<div class="h-full"> <div class="h-full">
<img src={image.url} alt={image.variableName} class="object-contain h-full rounded" /> <img src={image.url} alt={image.variableName} class="object-contain h-full rounded" />
</div> </div>
@@ -207,14 +225,14 @@
</div> </div>
{/each} {/each}
<div class="relative flex justify-center w-full h-8 gap-1 overflow-hidden transition rounded-lg place-items-center bg-zinc-200 dark:bg-zinc-700"> <div class="flex overflow-hidden relative gap-1 justify-center place-items-center w-full h-8 rounded-lg transition bg-zinc-200 dark:bg-zinc-700">
<span class='font-IconFamily'>{'\uec60'}</span> <span class='font-IconFamily'>{'\uec60'}</span>
<span class='dark:text-white'>Add image</span> <span class='dark:text-white'>Add image</span>
<input type="file" accept='image/*' onchange={onImageUpload} class="absolute inset-0 w-full h-full opacity-0 cursor-pointer" /> <input type="file" accept='image/*' onchange={onImageUpload} class="absolute inset-0 w-full h-full opacity-0 cursor-pointer" />
</div> </div>
{:else if item.type === 'lightDarkToggle'} {:else if item.type === 'lightDarkToggle'}
<button <button
class="relative px-4 py-1 overflow-hidden text-xl font-medium transition rounded-lg bg-zinc-200 dark:bg-zinc-700 hover:bg-zinc-300 dark:hover:bg-zinc-600 font-IconFamily" class="overflow-hidden relative px-4 py-1 text-xl font-medium rounded-lg transition bg-zinc-200 dark:bg-zinc-700 hover:bg-zinc-300 dark:hover:bg-zinc-600 font-IconFamily"
onclick={() => (item.props as LightDarkToggleProps).onChange(!(item.props as LightDarkToggleProps).state)} onclick={() => (item.props as LightDarkToggleProps).onChange(!(item.props as LightDarkToggleProps).state)}
> >
{#key (item.props as LightDarkToggleProps).state} {#key (item.props as LightDarkToggleProps).state}
@@ -236,10 +254,23 @@
{/snippet} {/snippet}
<div class='h-screen overflow-y-scroll {$settingsState.DarkMode && "dark"} no-scrollbar'> <div class='h-screen overflow-y-scroll {$settingsState.DarkMode && "dark"} no-scrollbar'>
<div class='flex flex-col w-full min-h-screen p-2 bg-zinc-100 dark:bg-zinc-800 dark:text-white'> {#if codeEditorFullscreen}
<div class="absolute inset-0 z-[10000] bg-white dark:bg-zinc-900 dark:text-white">
<div class="sticky top-0 px-2 h-screen">
<div class="flex justify-between items-center my-4">
<h2 class="text-xl font-bold">Custom CSS</h2>
<button onclick={toggleCodeEditorFullscreen} class="pr-14 text-xl font-IconFamily">{'\uec06'}</button>
</div>
<CodeEditor className="editorHeight" value={theme.CustomCSS} onChange={(value: string) => { theme = { ...theme, CustomCSS: value } }} />
</div>
</div>
{/if}
<div class='flex relative flex-col p-2 w-full min-h-screen bg-zinc-100 dark:bg-zinc-800 dark:text-white'>
<h1 class='text-xl font-semibold'>Theme Creator</h1> <h1 class='text-xl font-semibold'>Theme Creator</h1>
<a href='https://betterseqta.gitbook.io/betterseqta-docs' target='_blank' class='text-sm font-light text-zinc-500 dark:text-zinc-400'> <a href='https://betterseqta.gitbook.io/betterseqta-docs' target='_blank' class='text-sm font-light text-zinc-500 dark:text-zinc-400'>
<span class='no-underline font-IconFamily pr-0.5'>{'\ueb44'}</span> <span class='pr-0.5 no-underline font-IconFamily'>{'\ueb44'}</span>
<span class='underline'> <span class='underline'>
Need help? Check out the docs! Need help? Check out the docs!
</span> </span>
@@ -254,7 +285,7 @@
type='text' type='text'
placeholder='What is your theme called?' placeholder='What is your theme called?'
bind:value={theme.name} bind:value={theme.name}
class='w-full p-2 mb-4 transition border-0 rounded-lg dark:placeholder-zinc-300 bg-zinc-200 dark:bg-zinc-700 focus:bg-zinc-300/50 dark:focus:bg-zinc-600' /> class='p-2 mb-4 w-full rounded-lg border-0 transition dark:placeholder-zinc-300 bg-zinc-200 dark:bg-zinc-700 focus:bg-zinc-300/50 dark:focus:bg-zinc-600' />
</div> </div>
<div> <div>
@@ -263,23 +294,23 @@
id='themeDescription' id='themeDescription'
placeholder="Don't worry, this one's optional!" placeholder="Don't worry, this one's optional!"
bind:value={theme.description} bind:value={theme.description}
class='w-full p-2 transition border-0 rounded-lg dark:placeholder-zinc-300 bg-zinc-200 dark:bg-zinc-700 focus:outline-none focus:ring-1 focus:ring-zinc-100 dark:focus:ring-zinc-700 focus:bg-zinc-300/50 dark:focus:bg-zinc-600'></textarea> class='p-2 w-full rounded-lg border-0 transition dark:placeholder-zinc-300 bg-zinc-200 dark:bg-zinc-700 focus:outline-none focus:ring-1 focus:ring-zinc-100 dark:focus:ring-zinc-700 focus:bg-zinc-300/50 dark:focus:bg-zinc-600'></textarea>
</div> </div>
<Divider /> <Divider />
<div class="relative flex justify-center w-full gap-1 overflow-hidden transition rounded-lg aspect-theme group place-items-center bg-zinc-200 dark:bg-zinc-700"> <div class="flex overflow-hidden relative gap-1 justify-center place-items-center w-full rounded-lg transition aspect-theme group bg-zinc-200 dark:bg-zinc-700">
<div class={`transition pointer-events-none z-30 font-IconFamily ${ theme.coverImage ? 'opacity-0 group-hover:opacity-100' : ''}`}> <div class={`transition pointer-events-none z-30 font-IconFamily ${ theme.coverImage ? 'opacity-0 group-hover:opacity-100' : ''}`}>
{'\uec60'} {'\uec60'}
</div> </div>
<span class={`dark:text-white pointer-events-none z-30 transition ${ theme.coverImage ? 'opacity-0 group-hover:opacity-100' : ''}`}>{theme.coverImage ? 'Change' : 'Add'} cover image</span> <span class={`dark:text-white pointer-events-none z-30 transition ${ theme.coverImage ? 'opacity-0 group-hover:opacity-100' : ''}`}>{theme.coverImage ? 'Change' : 'Add'} cover image</span>
<input type="file" accept='image/*' onchange={onCoverImageUpload} class="absolute inset-0 z-10 w-full h-full opacity-0 cursor-pointer" /> <input type="file" accept='image/*' onchange={onCoverImageUpload} class="absolute inset-0 z-10 w-full h-full opacity-0 cursor-pointer" />
{#if !theme.hideThemeName && theme.coverImage} {#if !theme.hideThemeName && theme.coverImage}
<div class="absolute z-30 transition-opacity opacity-100 pointer-events-none group-hover:opacity-0">{theme.name}</div> <div class="absolute z-30 opacity-100 transition-opacity pointer-events-none group-hover:opacity-0">{theme.name}</div>
{/if} {/if}
{#if theme.coverImage} {#if theme.coverImage}
<div class="absolute z-20 w-full h-full transition-opacity opacity-0 pointer-events-none group-hover:opacity-100 bg-black/20"></div> <div class="absolute z-20 w-full h-full opacity-0 transition-opacity pointer-events-none group-hover:opacity-100 bg-black/20"></div>
<img src={theme.coverImageUrl} alt='Cover' class="absolute z-0 object-cover w-full h-full rounded" /> <img src={theme.coverImageUrl} alt='Cover' class="object-cover absolute z-0 w-full h-full rounded" />
{/if} {/if}
</div> </div>
+6 -1
View File
@@ -1,4 +1,9 @@
import { createManifest } from '../../lib/createManifest' import { createManifest } from '../../lib/createManifest'
import baseManifest from './manifest.json' import baseManifest from './manifest.json'
import pkg from '../../package.json'
export const brave = createManifest(baseManifest, 'brave') export const brave = createManifest({
...baseManifest,
version: pkg.version,
description: pkg.description,
}, 'brave')
+6 -1
View File
@@ -1,4 +1,9 @@
import { createManifest } from '../../lib/createManifest' import { createManifest } from '../../lib/createManifest'
import baseManifest from './manifest.json' import baseManifest from './manifest.json'
import pkg from '../../package.json'
export const chrome = createManifest(baseManifest, 'chrome') export const chrome = createManifest({
...baseManifest,
version: pkg.version,
description: pkg.description,
}, 'chrome')
+6 -1
View File
@@ -1,4 +1,9 @@
import { createManifest } from '../../lib/createManifest' import { createManifest } from '../../lib/createManifest'
import baseManifest from './manifest.json' import baseManifest from './manifest.json'
import pkg from '../../package.json'
export const edge = createManifest(baseManifest, 'edge') export const edge = createManifest({
...baseManifest,
version: pkg.version,
description: pkg.description,
}, 'edge')
+2
View File
@@ -4,6 +4,8 @@ import pkg from '../../package.json'
const updatedFirefoxManifest = { const updatedFirefoxManifest = {
...baseManifest, ...baseManifest,
version: pkg.version,
description: pkg.description,
background: { background: {
scripts: [baseManifest.background.service_worker], scripts: [baseManifest.background.service_worker],
}, },
+4 -2
View File
@@ -1,8 +1,6 @@
{ {
"manifest_version": 3, "manifest_version": 3,
"name": "BetterSEQTA+", "name": "BetterSEQTA+",
"version": "3.4.0",
"description": "Enhance SEQTA Learn's usability and aesthetics! A fork of BetterSEQTA to continue development add add heaps more features!",
"icons": { "icons": {
"32": "resources/icons/icon-32.png", "32": "resources/icons/icon-32.png",
"48": "resources/icons/icon-48.png", "48": "resources/icons/icon-48.png",
@@ -40,6 +38,10 @@
{ {
"resources": ["resources/icons/*"], "resources": ["resources/icons/*"],
"matches": ["*://*/*"] "matches": ["*://*/*"]
},
{
"resources": ["seqta/utils/migration/migrate.html"],
"matches": ["*://*/*"]
} }
] ]
} }
+6 -1
View File
@@ -1,4 +1,9 @@
import { createManifest } from '../../lib/createManifest' import { createManifest } from '../../lib/createManifest'
import baseManifest from './manifest.json' import baseManifest from './manifest.json'
import pkg from '../../package.json'
export const opera = createManifest(baseManifest, 'opera') export const opera = createManifest({
...baseManifest,
version: pkg.version,
description: pkg.description,
}, 'opera')
+3
View File
@@ -1,8 +1,11 @@
import { createManifest } from '../../lib/createManifest' import { createManifest } from '../../lib/createManifest'
import baseManifest from './manifest.json' import baseManifest from './manifest.json'
import pkg from '../../package.json'
const updatedSafariManifest = { const updatedSafariManifest = {
...baseManifest, ...baseManifest,
version: pkg.version,
description: pkg.description,
browser_specific_settings: { browser_specific_settings: {
safari: { safari: {
strict_min_version: '15.4', strict_min_version: '15.4',
+204
View File
@@ -0,0 +1,204 @@
class ReactFiber {
constructor(selector, options = {}) {
this.selector = selector;
this.debug = options.debug || false;
this.nodes = [...document.querySelectorAll(selector)]; // Support multiple elements
this.fibers = this.nodes.map(node => this.getFiberNode(node));
this.components = this.fibers.map(fiber => this.getOwnerComponent(fiber));
if (this.debug) {
console.log("Selected Nodes:", this.nodes);
console.log("🔍 Found Fibers:", this.fibers);
console.log("🛠 Found Components:", this.components);
}
}
static find(selector, options = {}) {
return new ReactFiber(selector, options);
}
getFiberNode(node) {
if (!node) return null;
const fiberKey = Object.getOwnPropertyNames(node).find(name =>
name.startsWith('__reactFiber') || name.startsWith('__reactInternalInstance')
);
return fiberKey ? node[fiberKey] : null;
}
getOwnerComponent(fiberNode) {
let current = fiberNode;
while (current) {
if (current.stateNode && (current.stateNode.setState || current.stateNode.forceUpdate)) {
return current.stateNode;
}
current = current.return;
}
return null;
}
getState(key) {
if (!this.components.length) return null;
const state = this.components[0]?.state || null;
if (key === undefined) {
return state;
} else if (typeof key === 'string') {
return state?.[key];
} else if (Array.isArray(key)) {
const filteredState = {};
for (const k of key) {
if (state && Object.hasOwn(state, k)) {
filteredState[k] = state[k];
}
}
return filteredState;
}
return null;
}
setState(update) {
this.components.forEach(component => {
if (component?.setState) {
if (typeof update === 'function') {
// Functional update
component.setState(prevState => {
const newState = update(prevState);
if (this.debug) console.log("✅ Updated State (Functional):", newState);
return newState;
});
} else {
// Object update (merge with existing state)
component.setState(prevState => {
const newState = {
...prevState,
...update
};
if (this.debug) console.log("✅ Updated State (Object Merge):", newState);
return newState;
});
}
}
});
return this;
}
getProp(propName) {
if (!this.fibers.length) return null;
if (propName === undefined) {
return this.fibers[0]?.memoizedProps;
}
return this.fibers[0]?.memoizedProps?.[propName];
}
setProp(propName) {
this.fibers.forEach(fiber => {
if (fiber?.memoizedProps) {
fiber.memoizedProps[propName] = value;
}
});
return this; // Enable chaining
}
forceUpdate() {
this.components.forEach(component => {
if (component?.forceUpdate) {
component.forceUpdate();
if (this.debug) console.log("🔄 Forced React Re-render");
}
});
return this; // Enable chaining
}
}
function makeSerializable(obj) {
if (typeof obj !== 'object' || obj === null) {
return obj;
}
if (Array.isArray(obj)) {
return obj.map(item => makeSerializable(item));
}
const serializableObj = {};
for (const key in obj) {
if (Object.hasOwn(obj, key)) {
let value = obj[key];
if (typeof value === 'function') {
value = '[Function]';
} else if (value instanceof HTMLElement) {
value = {
type: 'HTMLElement',
id: value.id,
tagName: value.tagName
}; // Replace DOM node with ID/tag info
} else if (typeof value === 'symbol') {
value = value.toString();
} else if (typeof value === 'object' && value !== null) {
value = makeSerializable(value);
}
serializableObj[key] = value;
}
}
return serializableObj;
}
window.addEventListener('message', (event) => {
if (event.data.type === "reactFiberRequest") {
const {
selector,
action,
payload,
debug,
messageId
} = event.data;
const fiberInstance = ReactFiber.find(selector, {
debug
});
let response;
switch (action) {
case "getState":
response = fiberInstance.getState(payload.key);
break;
case "setState":
// Handle both function and object updates
if (payload.updateFn) {
const updateFn = eval(`(${payload.updateFn})`);
fiberInstance.setState(updateFn);
} else {
fiberInstance.setState(payload.updateObject);
}
response = {};
break;
case "getProp":
response = fiberInstance.getProp(payload.propName);
break;
case "setProp":
fiberInstance.setProp(payload.propName, payload.value);
response = {};
break;
case "forceUpdate":
fiberInstance.forceUpdate();
response = {};
break;
default:
console.warn(`[pageState] Unknown action: ${action}`);
response = null;
}
if (response !== null && typeof response === 'object') {
response = makeSerializable(response);
}
window.postMessage({
type: "reactFiberResponse",
response,
messageId,
}, "*");
}
});
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

+74 -53
View File
@@ -6,17 +6,59 @@ import { settingsState } from "@/seqta/utils/listeners/SettingsState";
import { updateAllColors } from "./colors/Manager"; import { updateAllColors } from "./colors/Manager";
import { delay } from "@/seqta/utils/delay"; import { delay } from "@/seqta/utils/delay";
let cachedUserInfo: any = null;
async function getUserInfo() {
if (cachedUserInfo) return cachedUserInfo;
try {
const response = await fetch(`${location.origin}/seqta/student/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=utf-8',
},
body: JSON.stringify({
mode: 'normal',
query: null,
redirect_url: location.origin,
}),
});
const responseData = await response.json();
cachedUserInfo = responseData.payload;
return cachedUserInfo;
} catch (error) {
console.error('Error fetching user info:', error);
throw error;
}
}
export async function AddBetterSEQTAElements() { export async function AddBetterSEQTAElements() {
if (settingsState.onoff) { if (settingsState.onoff) {
initializeSettings(); initializeSettings();
if (settingsState.DarkMode) { if (settingsState.DarkMode) {
document.documentElement.classList.add('dark'); document.documentElement.classList.add('dark');
} }
createHomeButton();
await appendBackgroundToUI(); const fragment = document.createDocumentFragment();
await handleUserInfo(); const menu = document.getElementById('menu')!;
handleStudentData(); const menuList = menu.firstChild as HTMLElement;
createNewsButton();
createHomeButton(fragment, menuList);
createNewsButton(fragment, menu);
menuList.insertBefore(fragment, menuList.firstChild);
try {
await Promise.all([
appendBackgroundToUI(),
handleUserInfo(),
handleStudentData()
]);
} catch (error) {
console.error('Error initializing UI elements:', error);
}
setupEventListeners(); setupEventListeners();
await addDarkLightToggle(); await addDarkLightToggle();
customizeMenuToggle(); customizeMenuToggle();
@@ -24,7 +66,6 @@ export async function AddBetterSEQTAElements() {
addExtensionSettings(); addExtensionSettings();
await createSettingsButton(); await createSettingsButton();
setupSettingsButton(); setupSettingsButton();
} }
@@ -33,18 +74,15 @@ function initializeSettings() {
updateBgDurations(); updateBgDurations();
} }
function createHomeButton() { function createHomeButton(fragment: DocumentFragment, menuList: HTMLElement) {
const container = document.getElementById('content')!; const container = document.getElementById('content')!;
const div = document.createElement('div'); const div = document.createElement('div');
div.classList.add('titlebar'); div.classList.add('titlebar');
container.append(div); container.append(div);
const NewButton = stringToHTML('<li class="item" data-key="home" id="homebutton" data-path="/home" data-betterseqta="true"><label><svg style="width:24px;height:24px" viewBox="0 0 24 24"><path fill="currentColor" d="M10,20V14H14V20H19V12H22L12,3L2,12H5V20H10Z" /></svg><span>Home</span></label></li>'); const NewButton = stringToHTML('<li class="item" data-key="home" id="homebutton" data-path="/home" data-betterseqta="true"><label><svg style="width:24px;height:24px" viewBox="0 0 24 24"><path fill="currentColor" d="M10,20V14H14V20H19V12H22L12,3L2,12H5V20H10Z" /></svg><span>Home</span></label></li>');
const menu = document.getElementById('menu')!;
const List = menu.firstChild! as HTMLElement;
if (NewButton.firstChild) { if (NewButton.firstChild) {
List.insertBefore(NewButton.firstChild, List.firstChild); fragment.appendChild(NewButton.firstChild);
} }
} }
@@ -121,30 +159,8 @@ async function handleStudentData() {
} }
} }
async function getUserInfo() {
try {
const response = await fetch(`${location.origin}/seqta/student/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=utf-8',
},
body: JSON.stringify({
mode: 'normal',
query: null,
redirect_url: location.origin,
}),
});
const responseData = await response.json();
return responseData.payload;
} catch (error) {
console.error('Error fetching user info:', error);
throw error; // Rethrow the error after logging it
}
}
async function updateStudentInfo(students: any) { async function updateStudentInfo(students: any) {
const info = await getUserInfo(); // You would need to implement this to fetch or pass the user info const info = await getUserInfo();
var index = students.findIndex(function (person: any) { var index = students.findIndex(function (person: any) {
return ( return (
person.firstname == info.userDesc.split(' ')[0] && person.firstname == info.userDesc.split(' ')[0] &&
@@ -167,45 +183,50 @@ async function updateStudentInfo(students: any) {
} }
} }
} else { } else {
try {
houseelement.innerText = students[index].year; houseelement.innerText = students[index].year;
} catch(err) {
houseelement.innerText = 'N/A';
}
} }
} }
function createNewsButton() { function createNewsButton(fragment: DocumentFragment, menu: HTMLElement) {
const NewsButtonStr = '<li class="item" data-key="news" id="newsbutton" data-path="/news" data-betterseqta="true"><label><svg style="width:24px;height:24px" viewBox="0 0 24 24"><path fill="currentColor" d="M20 3H4C2.89 3 2 3.89 2 5V19C2 20.11 2.89 21 4 21H20C21.11 21 22 20.11 22 19V5C22 3.89 21.11 3 20 3M5 7H10V13H5V7M19 17H5V15H19V17M19 13H12V11H19V13M19 9H12V7H19V9Z" /></svg><span>News</span></label></li>'; const NewsButtonStr = '<li class="item" data-key="news" id="newsbutton" data-path="/news" data-betterseqta="true"><label><svg style="width:24px;height:24px" viewBox="0 0 24 24"><path fill="currentColor" d="M20 3H4C2.89 3 2 3.89 2 5V19C2 20.11 2.89 21 4 21H20C21.11 21 22 20.11 22 19V5C22 3.89 21.11 3 20 3M5 7H10V13H5V7M19 17H5V15H19V17M19 13H12V11H19V13M19 9H12V7H19V9Z" /></svg><span>News</span></label></li>';
const NewsButton = stringToHTML(NewsButtonStr); const NewsButton = stringToHTML(NewsButtonStr);
const menu = document.getElementById('menu')!;
const List = menu.firstChild! as HTMLElement;
List!.appendChild(NewsButton.firstChild!); if (NewsButton.firstChild) {
fragment.appendChild(NewsButton.firstChild);
}
let a = document.createElement('div'); let iconCover = document.createElement('div');
a.classList.add('icon-cover'); iconCover.classList.add('icon-cover');
a.id = 'icon-cover'; iconCover.id = 'icon-cover';
menu!.appendChild(a); menu.appendChild(iconCover);
} }
function setupEventListeners() { function setupEventListeners() {
const menuCover = document.querySelector('#icon-cover'); const menuCover = document.querySelector('#icon-cover');
menuCover!.addEventListener('click', function () {
location.href = '../#?page=/home';
loadHomePage();
(document!.getElementById('menu')!.firstChild! as HTMLElement).classList.remove('noscroll');
});
const homebutton = document.getElementById('homebutton'); const homebutton = document.getElementById('homebutton');
homebutton!.addEventListener('click', function () { const newsbutton = document.getElementById('newsbutton');
if (!homebutton?.classList.contains('draggable') && !homebutton?.classList.contains('active')) {
homebutton?.addEventListener('click', function() {
if (!homebutton.classList.contains('draggable') && !homebutton.classList.contains('active')) {
loadHomePage(); loadHomePage();
} }
}); });
const newsbutton = document.getElementById('newsbutton'); newsbutton?.addEventListener('click', function() {
newsbutton!.addEventListener('click', function () { if (!newsbutton.classList.contains('draggable') && !newsbutton.classList.contains('active')) {
if (!newsbutton?.classList.contains('draggable') && !newsbutton?.classList.contains('active')) {
SendNewsPage(); SendNewsPage();
} }
}); });
menuCover?.addEventListener('click', function() {
location.href = '../#?page=/home';
loadHomePage();
(document.getElementById('menu')!.firstChild! as HTMLElement).classList.remove('noscroll');
});
} }
async function createSettingsButton() { async function createSettingsButton() {
+1 -1
View File
@@ -55,7 +55,7 @@ export function OpenThemeCreator(themeID: string = "") {
const mouseMoveHandler = (e: MouseEvent) => { const mouseMoveHandler = (e: MouseEvent) => {
if (!isDragging) return if (!isDragging) return
const windowWidth = window.innerWidth const windowWidth = window.innerWidth
const newWidth = Math.min(Math.max(310, windowWidth - e.clientX), 600) const newWidth = Math.max(310, windowWidth - e.clientX)
themeCreatorDiv.style.width = `${newWidth}px` themeCreatorDiv.style.width = `${newWidth}px`
mainContent.style.width = `calc(100% - ${newWidth}px)` mainContent.style.width = `calc(100% - ${newWidth}px)`
resizeBar.style.right = `${newWidth - 2.5}px` resizeBar.style.right = `${newWidth - 2.5}px`
+29 -3
View File
@@ -1,7 +1,14 @@
import localforage from 'localforage'; import localforage from 'localforage';
import type { Theme } from '@/old-interface/pages/Store';
import base64ToBlob from '@/seqta/utils/base64ToBlob'; import base64ToBlob from '@/seqta/utils/base64ToBlob';
type Theme = {
name: string;
description: string;
coverImage: string;
marqueeImage: string;
id: string;
}
type ThemeContent = { type ThemeContent = {
id: string; id: string;
name: string; name: string;
@@ -14,6 +21,23 @@ type ThemeContent = {
images: { id: string, variableName: string, data: string }[]; // data: base64 images: { id: string, variableName: string, data: string }[]; // data: base64
}; };
function stripBase64Prefix(base64String: string): string {
if (!base64String) return '';
const prefixRegex = /^data:[^;]+;base64,/;
try {
// Check if the string actually has a base64 prefix
if (prefixRegex.test(base64String)) {
return base64String.replace(prefixRegex, '');
}
// If no prefix found, return the original string (assuming it's already base64)
return base64String;
} catch(err) {
console.error('Error stripping base64 prefix:', err);
return '';
}
}
export const StoreDownloadTheme = async (theme: { themeContent: Theme }) => { export const StoreDownloadTheme = async (theme: { themeContent: Theme }) => {
if (!theme.themeContent.id) return; if (!theme.themeContent.id) return;
@@ -24,11 +48,13 @@ export const StoreDownloadTheme = async (theme: { themeContent: Theme }) => {
}; };
export const InstallTheme = async (themeData: ThemeContent) => { export const InstallTheme = async (themeData: ThemeContent) => {
const coverImageBlob = base64ToBlob(themeData.coverImage); const strippedCoverImage = stripBase64Prefix(themeData.coverImage);
const coverImageBlob = base64ToBlob(strippedCoverImage);
const images = themeData.images.map((image) => ({ const images = themeData.images.map((image) => ({
...image, ...image,
blob: base64ToBlob(image.data) blob: base64ToBlob(stripBase64Prefix(image.data))
})); }));
let availableThemes = await localforage.getItem('customThemes') as string[]; let availableThemes = await localforage.getItem('customThemes') as string[];
+6 -1
View File
@@ -28,7 +28,12 @@ const shareTheme = async (themeID: string) => {
// Helper function to convert Blob to Base64 // Helper function to convert Blob to Base64
const blobToBase64 = (blob: Blob) => new Promise<string>((resolve, reject) => { const blobToBase64 = (blob: Blob) => new Promise<string>((resolve, reject) => {
const reader = new FileReader(); const reader = new FileReader();
reader.onloadend = () => resolve(reader.result as string); reader.onloadend = () => {
const base64String = reader.result as string;
// Extract just the base64 data without the data URL prefix
const base64Data = base64String.split(',')[1];
resolve(base64Data);
};
reader.onerror = reject; reader.onerror = reject;
reader.readAsDataURL(blob); reader.readAsDataURL(blob);
}); });
+84
View File
@@ -0,0 +1,84 @@
class ReactFiber {
private selector: string;
private debug: boolean;
private messageIdCounter: number = 0; // Counter for unique message IDs
constructor(selector: string, options: {
debug ? : boolean
} = {}) {
this.selector = selector;
this.debug = options.debug || false;
}
static find(selector: string, options: {
debug ? : boolean
} = {}) {
return new ReactFiber(selector, options);
}
private async sendMessage(action: string, payload: any = {}): Promise < any > {
return new Promise((resolve, _) => {
const messageId = this.messageIdCounter++;
const message = {
type: "reactFiberRequest",
selector: this.selector,
action,
payload,
debug: this.debug,
messageId,
};
const listener = (response: any) => {
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.addEventListener('message', listener);
window.postMessage(message, "*");
});
}
async getState(key ? : string | string[]): Promise < any > {
return this.sendMessage("getState", {
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;
await this.sendMessage("setState", {
updateFn: updateFnString,
updateObject
});
return this;
}
async getProps(propName ? : string): Promise < any > {
return this.sendMessage("getProp", {
propName
});
}
async setProp(propName: string, value: any): Promise < ReactFiber > {
await this.sendMessage("setProp", {
propName,
value
});
return this;
}
async forceUpdate(): Promise < ReactFiber > {
await this.sendMessage("forceUpdate");
return this;
}
}
export default ReactFiber;
@@ -0,0 +1,48 @@
import { waitForElm } from "@/SEQTA";
import ReactFiber from "../ReactFiber";
const handleNotificationClick = async (target: HTMLElement) => {
const notificationItem = target.closest('.notifications__item___2ErJN') as HTMLElement | null;
if (!notificationItem) return;
const buttonType = notificationItem.getAttribute('data-type');
if (buttonType !== 'message') return;
const notificationList = await ReactFiber.find('.notifications__list___rp2L2').getState();
const buttonId = notificationItem.getAttribute('data-id');
if (!buttonId) return;
const matchingNotification = notificationList.storeState.notifications.items.find(
(item: any) => item.notificationID === parseInt(buttonId)
);
await waitForElm('.Viewer__Viewer___32BH-', true, 20);
// Select the specific direct message
ReactFiber.find('.Viewer__Viewer___32BH-').setState({ selected: new Set([matchingNotification.message.messageID]) });
// Close the notifications panel
const notificationButton = document.querySelector('.notifications__notifications___3mmLY > button') as HTMLButtonElement | null;
notificationButton?.click();
};
const clickListeners = [
{
selector: '.notifications__item___2ErJN',
handler: handleNotificationClick,
},
];
const registerClickListeners = () => {
document.addEventListener('click', (e) => {
const target = e.target as HTMLElement;
clickListeners.forEach(({ selector, handler }) => {
if (target.closest(selector)) {
handler(target);
}
});
});
};
export default registerClickListeners;
+18 -2
View File
@@ -46,10 +46,27 @@ class EventManager {
} }
const unregister = () => this.unregisterById(event, id); const unregister = () => this.unregisterById(event, id);
this.listeners.get(event)!.push({ id, options, callback, unregister }); this.listeners.get(event)!.push({ id, options, callback, unregister });
this.scanExistingElements(options, callback);
this.startObserving(options.parentElement); this.startObserving(options.parentElement);
return { unregister }; return { unregister };
} }
private async scanExistingElements(options: EventListenerOptions, callback: (element: Element) => void): Promise<void> {
const root = options.parentElement || document.documentElement;
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));
for (const element of filteredChunk) {
callback(element);
}
}
}
public unregister(event: string): void { public unregister(event: string): void {
if (this.listeners.has(event)) { if (this.listeners.has(event)) {
this.listeners.delete(event); this.listeners.delete(event);
@@ -119,11 +136,10 @@ class EventManager {
} }
private async checkElement(element: Element): Promise<void> { private async checkElement(element: Element): Promise<void> {
if (element.classList.contains('code')) console.log('Code Detected!');
for (const [event, listeners] of this.listeners.entries()) { for (const [event, listeners] of this.listeners.entries()) {
for (const { id, options, callback } of listeners) { for (const { id, options, callback } of listeners) {
if (this.matchesOptions(element, options)) { if (this.matchesOptions(element, options)) {
await callback(element); callback(element);
if (options.once) { if (options.once) {
this.unregisterById(event, id); this.unregisterById(event, id);
} }
-127
View File
@@ -1,127 +0,0 @@
import browser from 'webextension-polyfill';
import base64ToBlob from './base64ToBlob';
import { openDatabase, writeData } from '@/interface/hooks/BackgroundDataLoader';
import { backgroundUpdates } from '@/interface/hooks/BackgroundUpdates';
import { loadBackground } from '@/seqta/ui/ImageBackgrounds';
const MIGRATION_STATE_KEY = 'background_migration_state';
interface MigrationState {
lastProcessedId: string | null;
total: number;
processed: number;
completed: boolean;
}
export const migrateBackgrounds = async (): Promise<void> => {
console.info('Migrating backgrounds...');
const savedState = localStorage.getItem(MIGRATION_STATE_KEY);
const migrationState: MigrationState = savedState
? JSON.parse(savedState)
: { lastProcessedId: null, total: 0, processed: 0, completed: false };
if (migrationState.completed) {
console.info('Migration already completed');
return;
}
return new Promise((resolve, reject) => {
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
const handleMessage = async (event: MessageEvent) => {
if (event.source !== iframe.contentWindow) return;
switch (event.data.type) {
case 'GET_LAST_PROCESSED_ID':
iframe.contentWindow?.postMessage({
type: 'LAST_PROCESSED_ID',
id: migrationState.lastProcessedId
}, '*');
break;
case 'BACKGROUND_DATA':
try {
const { id, data, mediaType, total, processed, isSelected } = event.data.payload;
const mimeType = mediaType === 'image' ? 'image/png' : 'video/mp4';
const blob = base64ToBlob(data, mimeType);
await storeBackground({
id,
blob,
type: mediaType
});
if (isSelected) {
localStorage.setItem('selectedBackground', id);
await loadBackground();
}
migrationState.lastProcessedId = id;
migrationState.total = total;
migrationState.processed = processed;
localStorage.setItem(MIGRATION_STATE_KEY, JSON.stringify(migrationState));
console.log(`Migrated background: ${id} (${processed}/${total})`);
} catch (error) {
console.error('Error handling background data:', error);
}
break;
case 'MIGRATION_COMPLETE':
console.info('Migration completed successfully');
migrationState.completed = true;
localStorage.setItem(MIGRATION_STATE_KEY, JSON.stringify(migrationState));
window.removeEventListener('message', handleMessage);
iframe.remove();
backgroundUpdates.triggerUpdate();
resolve();
break;
case 'MIGRATION_ERROR':
console.error('Migration failed:', event.data.error);
window.removeEventListener('message', handleMessage);
iframe.remove();
reject(new Error(event.data.error));
break;
}
};
window.addEventListener('message', handleMessage);
const startPinging = () => {
const pingInterval = setInterval(() => {
iframe.contentWindow?.postMessage({ type: 'PING' }, '*');
}, 500);
const messageHandler = (event: MessageEvent) => {
if (event.source === iframe.contentWindow) {
clearInterval(pingInterval);
window.removeEventListener('message', messageHandler);
iframe.contentWindow?.postMessage({ type: 'START_MIGRATION' }, '*');
}
};
window.addEventListener('message', messageHandler);
};
iframe.src = browser.runtime.getURL('seqta/utils/migration/migrate.html');
document.body.appendChild(iframe);
startPinging();
});
};
const storeBackground = async (data: {
id: string;
blob: Blob;
type: 'image' | 'video';
}): Promise<void> => {
try {
await openDatabase();
await writeData(data.id, data.type, data.blob);
} catch (error) {
console.error('Error storing background:', error);
throw error;
}
};
+17 -1
View File
@@ -44,7 +44,23 @@ const getSelectedBackground = (): string | null => {
const startMigration = async () => { const startMigration = async () => {
try { try {
console.info('Starting background extraction...'); console.info('Starting background extraction...');
const backgrounds = await getAllBackgrounds(); let backgrounds: Data[];
try {
backgrounds = await getAllBackgrounds();
if (!backgrounds || backgrounds.length === 0) {
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' }, '*');
return;
}
console.error('Error fetching backgrounds:', error);
throw new Error('Failed to fetch backgrounds');
}
const selectedBackground = getSelectedBackground(); const selectedBackground = getSelectedBackground();
console.info(`Found ${backgrounds.length} backgrounds`); console.info(`Found ${backgrounds.length} backgrounds`);
+11 -3
View File
@@ -1,12 +1,20 @@
import DOMPurify from 'dompurify'; import DOMPurify from 'dompurify';
export default function stringToHTML(str: string, styles = false) { export default function stringToHTML(str: string, styles = false) {
var parser = new DOMParser(); const parser = new DOMParser();
str = DOMPurify.sanitize(str, { ADD_ATTR: ['onclick'] });
var doc = parser.parseFromString(str, 'text/html');
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
});
const doc = parser.parseFromString(str, 'text/html');
if (styles) { if (styles) {
doc.body.style.cssText = 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; return doc.body;
} }
+3
View File
@@ -38,6 +38,9 @@ export interface SettingsState {
defaultPage: string; defaultPage: string;
devMode?: boolean; devMode?: boolean;
originalDarkMode?: boolean; originalDarkMode?: boolean;
assessmentsAverage?: boolean;
lettergrade: boolean;
newsSource?: string;
} }
interface ToggleItem { interface ToggleItem {
+1 -8
View File
@@ -1,17 +1,10 @@
const { import flattenColorPalette from "tailwindcss/lib/util/flattenColorPalette";
default: flattenColorPalette,
} = require("tailwindcss/lib/util/flattenColorPalette");
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
export default { export default {
content: [ content: [
"./src/**/*.{js,ts,jsx,tsx,html,svelte}", "./src/**/*.{js,ts,jsx,tsx,html,svelte}",
], ],
//safelist: [
//{
// pattern: / */,
//}
//],
darkMode: "class", darkMode: "class",
theme: { theme: {
fontSize: { fontSize: {
+20 -4
View File
@@ -4,6 +4,7 @@ import { join, resolve } from 'path';
import { updateManifestPlugin } from './lib/patchPackage'; import { updateManifestPlugin } from './lib/patchPackage';
import { base64Loader } from './lib/base64loader'; import { base64Loader } from './lib/base64loader';
import type { BuildTarget } from './lib/types'; import type { BuildTarget } from './lib/types';
import ClosePlugin from './lib/closePlugin';
import react from '@vitejs/plugin-react'; import react from '@vitejs/plugin-react';
import million from "million/compiler"; import million from "million/compiler";
@@ -25,7 +26,7 @@ const targets: BuildTarget[] = [
const mode = process.env.MODE || 'chrome'; const mode = process.env.MODE || 'chrome';
export default defineConfig({ export default defineConfig(({ command }) => ({
plugins: [ plugins: [
base64Loader, base64Loader,
react(), react(),
@@ -38,7 +39,8 @@ export default defineConfig({
manifest: targets.find(t => t.browser === mode.toLowerCase())?.manifest ?? chrome.manifest, manifest: targets.find(t => t.browser === mode.toLowerCase())?.manifest ?? chrome.manifest,
browser: mode.toLowerCase() === "firefox" ? "firefox" : "chrome" browser: mode.toLowerCase() === "firefox" ? "firefox" : "chrome"
}), }),
updateManifestPlugin() updateManifestPlugin(),
...(command === 'build' ? [ClosePlugin()] : [])
], ],
root: resolve(__dirname, './src'), root: resolve(__dirname, './src'),
resolve: { resolve: {
@@ -54,6 +56,19 @@ export default defineConfig({
port: 5173 port: 5173
} }
}, },
css: {
preprocessorOptions: {
scss: {
api: 'modern'
}
}
},
optimizeDeps: {
include: ['@babel/runtime/helpers/extends', '@babel/runtime/helpers/interopRequireDefault'],
},
legacy: {
skipWebSocketTokenCheck: true,
},
build: { build: {
outDir: resolve(__dirname, 'dist', mode), outDir: resolve(__dirname, 'dist', mode),
emptyOutDir: false, emptyOutDir: false,
@@ -61,8 +76,9 @@ export default defineConfig({
rollupOptions: { rollupOptions: {
input: { input: {
settings: join(__dirname, 'src', 'interface', 'index.html'), settings: join(__dirname, 'src', 'interface', 'index.html'),
migration: join(__dirname, 'src', 'seqta', 'utils', 'migration', 'migrate.html') migration: join(__dirname, 'src', 'seqta', 'utils', 'migration', 'migrate.html'),
pageState: join(__dirname, 'src', 'pageState.js'),
} }
} }
} }
}); }));