Compare commits

...

83 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
45 changed files with 1741 additions and 1122 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
+10 -14
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.
+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/
+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.
-244
View File
@@ -1,244 +0,0 @@
<html class="reveal-full-page"><head>
<script>
(function() {
const originalConsole = window.console;
window.console = {
log: (...args) => {
originalConsole.log(...args);
window.parent.postMessage({ type: 'console', message: args.join(' ') }, '*');
},
error: (...args) => {
originalConsole.error(...args);
window.parent.postMessage({ type: 'console', message: 'Error: ' + args.join(' ') }, '*');
},
warn: (...args) => {
originalConsole.warn(...args);
window.parent.postMessage({ type: 'console', message: 'Warning: ' + args.join(' ') }, '*');
}
};
window.addEventListener('error', (event) => {
window.parent.postMessage({ type: 'console', message: 'Uncaught Error: ' + event.message }, '*');
});
})();
</script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/4.3.1/reveal.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/4.3.1/theme/night.min.css">
<link rel="stylesheet" href="https://sethburkart123.github.io/sf-pro-fonts/fonts.css" />
<style>
* {
font-family: 'SF Pro Display', sans-serif !important;
}
.reveal section img {
border: none !important;
box-shadow: none !important;
}
.custom-fragment {
opacity: 0;
transition: opacity 0.8s ease;
}
.visible {
opacity: 1;
}
.reveal .slides section {
text-align: left;
}
.reveal h1, .reveal h2 {
color: #58a6ff;
font-weight: 800;
}
.reveal h3 {
color: #79c0ff;
}
.reveal .highlight {
color: #7ee787;
}
.reveal .container {
display: flex;
justify-content: space-between;
align-items: center;
}
.reveal .box {
background: rgba(255,255,255,0.1);
padding: 20px;
border-radius: 10px;
margin: 10px;
}
.reveal code {
background: #1f2937;
padding: 3px 5px;
border-radius: 4px;
}
</style>
</head>
<body class="reveal-viewport" style="--slide-width: 960px; --slide-height: 700px;">
<div class="reveal slide center focused has-vertical-slides has-horizontal-slides" role="application" data-transition-speed="default" data-background-transition="fade" style="">
<div class="slides no-transition" style="width: 960px; height: 700px; inset: 50% auto auto 50%; transform: translate(-50%, -50%) scale(0.527);">
<!-- Title Slide -->
<section style="top: 0px; display: block;" class="present">
<h1>SHA-256: The Digital Fingerprint Maker</h1>
<h3>A Journey into Modern Cryptographic Security</h3>
<p>An interactive exploration of how SHA-256 keeps our digital world secure</p>
</section>
<!-- What is SHA-256? -->
<section style="top: 0px; display: block;" hidden="" aria-hidden="true" class="stack future">
<section style="top: 97.5px; display: block;">
<h2>What is SHA-256?</h2>
<p>Think of SHA-256 as a magical fingerprint machine for digital data:</p>
<ul>
<li class="fragment" data-fragment-index="0">Takes <em>any</em> digital input (like a message or file)</li>
<li class="fragment" data-fragment-index="1">Always produces a unique 256-bit (64 character) fingerprint</li>
<li class="fragment" data-fragment-index="2">Even a tiny change creates a completely different fingerprint!</li>
</ul>
</section>
</section>
<!-- How it Works -->
<section style="top: 0px; display: block;" hidden="" aria-hidden="true" class="stack future">
<section style="top: 62px; display: block;">
<h2>How SHA-256 Works 🔨</h2>
<p>Imagine a complex assembly line that processes your data:</p>
<div class="container">
<div class="box fragment" data-fragment-index="0">
<h3>1. Preparation</h3>
<p>Data is padded like fitting puzzle pieces into fixed 512-bit blocks</p>
</div>
<div class="box fragment" data-fragment-index="1">
<h3>2. Processing</h3>
<p>64 rounds of mathematical "mixing" operations</p>
</div>
</div>
</section>
<section class="future" aria-hidden="true" style="top: 350px; display: none;">
<h3>The SHA-256 Process Visualized</h3>
<div style="text-align: center;">
<svg viewBox="0 0 800 300" style="max-width: 800px;">
<!-- Input -->
<rect x="50" y="50" width="150" height="60" fill="#58a6ff" opacity="0.8"></rect>
<text x="125" y="85" text-anchor="middle" fill="white">Input Data</text>
<!-- Arrow 1 -->
<path d="M200 80 L300 80" stroke="white" stroke-width="2" marker-end="url(#arrowhead)"></path>
<!-- Processing -->
<rect x="300" y="40" width="200" height="80" fill="#7ee787" opacity="0.8"></rect>
<text x="400" y="85" text-anchor="middle" fill="white">SHA-256 Processing</text>
<!-- Arrow 2 -->
<path d="M500 80 L600 80" stroke="white" stroke-width="2" marker-end="url(#arrowhead)"></path>
<!-- Output -->
<rect x="600" y="50" width="150" height="60" fill="#ff7b72" opacity="0.8"></rect>
<text x="675" y="85" text-anchor="middle" fill="white">256-bit Hash</text>
<!-- Arrow Marker -->
<defs>
<marker id="arrowhead" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="white"></polygon>
</marker>
</defs>
</svg>
</div>
</section>
</section>
<!-- Real-world Applications -->
<section style="top: 350px; display: none;" hidden="" aria-hidden="true" class="stack future">
<section style="top: 350px; display: none;">
<h2>Where is SHA-256 Used?</h2>
<div class="container">
<div class="box fragment" data-fragment-index="0">
<h3>Password Storage</h3>
<p>Websites store password fingerprints, not actual passwords</p>
</div>
<div class="box fragment" data-fragment-index="1">
<h3>⛓️ Blockchain</h3>
<p>Powers cryptocurrency mining and verification</p>
</div>
</div>
</section>
<section class="future" aria-hidden="true" style="top: 350px; display: none;">
<h3>More Applications</h3>
<div class="container">
<div class="box fragment" data-fragment-index="0">
<h3>Digital Signatures</h3>
<p>Verify document authenticity</p>
</div>
<div class="box fragment" data-fragment-index="1">
<h3>File Integrity</h3>
<p>Ensure downloads aren't tampered with</p>
</div>
</div>
</section>
</section>
<!-- Strengths -->
<section style="top: 350px; display: none;" hidden="" aria-hidden="true" class="future">
<h2>Why SHA-256 is Strong 💪</h2>
<ul>
<li class="fragment" data-fragment-index="0">Collision Resistance: Like finding two people with identical fingerprints</li>
<li class="fragment" data-fragment-index="1">One-way Function: Can't reconstruct original data from hash</li>
<li class="fragment" data-fragment-index="2">Avalanche Effect: Tiny changes cause completely different outputs</li>
</ul>
</section>
<!-- Interactive Demo -->
<section style="top: 350px; display: none;" hidden="" aria-hidden="true" class="future">
<h2>The Avalanche Effect 🌊</h2>
<div class="box">
<p>Original message: "Hello, World!"</p>
<code>a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e</code>
</div>
<div class="box fragment" data-fragment-index="0">
<p>Changed to: "Hello, World?"</p>
<code>7d1a54127b222502f5b79b5fb0803061152a44f92b37e23c6527baf665d4da9a</code>
</div>
<p class="fragment" data-fragment-index="1">Notice how one character change creates a completely different hash!</p>
</section>
<!-- Limitations -->
<section style="top: 350px; display: none;" hidden="" aria-hidden="true" class="future">
<h2>Challenges &amp; Future 🔮</h2>
<ul>
<li class="fragment" data-fragment-index="0">Requires more computing power than older algorithms</li>
<li class="fragment" data-fragment-index="1">Theoretical vulnerability to quantum computers (but not yet practical)</li>
<li class="fragment" data-fragment-index="2">Still considered very secure for current and near-future use</li>
</ul>
</section>
<!-- Summary -->
<section style="top: 350px; display: none;" hidden="" aria-hidden="true" class="future">
<h2>Key Takeaways 🎯</h2>
<ul>
<li class="fragment" data-fragment-index="0">SHA-256 creates unique digital fingerprints</li>
<li class="fragment" data-fragment-index="1">Powers modern security in passwords, blockchain, and more</li>
<li class="fragment" data-fragment-index="2">Extremely secure against current technology</li>
<li class="fragment" data-fragment-index="3">Essential part of our digital infrastructure</li>
</ul>
</section>
</div>
<div class="backgrounds"><div class="slide-background present" data-loaded="true" style="display: block;"><div class="slide-background-content"></div></div><div class="slide-background stack future" data-loaded="true" style="display: block;"><div class="slide-background-content"></div><div class="slide-background present" data-loaded="true" style="display: block;"><div class="slide-background-content"></div></div></div><div class="slide-background stack future" data-loaded="true" style="display: block;"><div class="slide-background-content"></div><div class="slide-background present" data-loaded="true" style="display: block;"><div class="slide-background-content"></div></div><div class="slide-background present" style="display: none;"><div class="slide-background-content"></div></div></div><div class="slide-background stack future" style="display: none;"><div class="slide-background-content"></div><div class="slide-background present" style="display: none;"><div class="slide-background-content"></div></div><div class="slide-background present" style="display: none;"><div class="slide-background-content"></div></div></div><div class="slide-background future" style="display: none;"><div class="slide-background-content"></div></div><div class="slide-background future" style="display: none;"><div class="slide-background-content"></div></div><div class="slide-background future" style="display: none;"><div class="slide-background-content"></div></div><div class="slide-background future" style="display: none;"><div class="slide-background-content"></div></div></div><div class="slide-number" style="display: block;"><a href="#/">
<span class="slide-number-a">1</span>
</a></div><aside class="controls" data-controls-layout="bottom-right" data-controls-back-arrows="faded" style="display: block;"><button class="navigate-left" aria-label="previous slide" disabled="disabled"><div class="controls-arrow"></div></button>
<button class="navigate-right enabled highlight" aria-label="next slide"><div class="controls-arrow"></div></button>
<button class="navigate-up" aria-label="above slide" disabled="disabled"><div class="controls-arrow"></div></button>
<button class="navigate-down" aria-label="below slide" disabled="disabled"><div class="controls-arrow"></div></button></aside><div class="progress" style="display: block;"><span style="transform: scaleX(0);"></span></div><div class="speaker-notes" data-prevent-swipe="" tabindex="0"></div><div class="pause-overlay"><button class="resume-button">Resume presentation</button></div><div class="aria-status" aria-live="polite" aria-atomic="true" style="position: absolute; height: 1px; width: 1px; overflow: hidden; clip: rect(1px, 1px, 1px, 1px);">SHA-256: The Digital Fingerprint Maker 🔐 A Journey into Modern Cryptographic Security An interactive exploration of how SHA-256 keeps our digital world secure </div></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/4.3.1/reveal.js"></script>
<script>
Reveal.initialize({
hash: true,
slideNumber: true,
transition: 'slide',
controls: true,
progress: true
});
</script>
</body></html>
+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)
},
}
}
+25 -10
View File
@@ -25,17 +25,32 @@ export function updateManifestPlugin(): PluginOption {
console.log('** updated **'); console.log('** updated **');
} }
fs.watchFile(manifestPath, () => { // Implement retry mechanism for file watching
console.log('** watchFile ** '); const watchWithRetry = () => {
const manifestContents = JSON.parse(fs.readFileSync(manifestPath, 'utf8')); if (!fs.existsSync(manifestPath)) {
if (manifestContents.web_accessible_resources.some((resource: any) => resource.use_dynamic_url)) { console.log('Manifest not found, retrying in 1 second...');
const updated = forceDisableUseDynamicUrl(); setTimeout(watchWithRetry, 1000);
if (updated) { return;
server.ws.send({ type: 'full-reload' });
console.log('** updated **');
}
} }
});
fs.watchFile(manifestPath, () => {
console.log('** watchFile **');
try {
const manifestContents = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
if (manifestContents.web_accessible_resources?.some((resource: any) => resource.use_dynamic_url)) {
const updated = forceDisableUseDynamicUrl();
if (updated) {
server.ws.send({ type: 'full-reload' });
console.log('** updated **');
}
}
} catch (error) {
console.log('Error reading manifest, will retry on next change:', error.message);
}
});
};
watchWithRetry();
}); });
}, },
+23 -25
View File
@@ -1,8 +1,8 @@
{ {
"name": "betterseqtaplus", "name": "betterseqtaplus",
"version": "3.4.2", "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"
@@ -32,45 +33,43 @@
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@babel/plugin-transform-runtime": "^7.25.9", "@babel/plugin-transform-runtime": "^7.25.9",
"@babel/runtime": "^7.26.0", "@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",
"caniuse-lite": "^1.0.30001684",
"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",
@@ -78,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": "^11.12.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"
} }
} }
+399 -59
View File
@@ -13,11 +13,13 @@ import { StorageChangeHandler } from '@/seqta/utils/listeners/StorageChanges'
import { eventManager } from '@/seqta/utils/listeners/EventManager' import { eventManager } from '@/seqta/utils/listeners/EventManager'
// UI and theme management // UI and theme management
import loading, { AppendLoadingSymbol } from '@/seqta/ui/Loading' import RegisterClickListeners from './seqta/utils/listeners/ClickListeners'
import { enableCurrentTheme } from '@/seqta/ui/themes/enableCurrent'
import { updateAllColors } from '@/seqta/ui/colors/Manager'
import { SettingsResizer } from '@/seqta/ui/SettingsResizer'
import { AddBetterSEQTAElements } from '@/seqta/ui/AddBetterSEQTAElements' import { AddBetterSEQTAElements } from '@/seqta/ui/AddBetterSEQTAElements'
import { enableCurrentTheme } from '@/seqta/ui/themes/enableCurrent'
import loading, { AppendLoadingSymbol } from '@/seqta/ui/Loading'
import { SettingsResizer } from '@/seqta/ui/SettingsResizer'
import { updateAllColors } from '@/seqta/ui/colors/Manager'
import pageState from '@/pageState.js?url'
// JSON content // JSON content
import MenuitemSVGKey from '@/seqta/content/MenuItemSVGKey.json' import MenuitemSVGKey from '@/seqta/content/MenuItemSVGKey.json'
@@ -30,6 +32,7 @@ import LogoLightOutline from '@/resources/icons/betterseqta-light-outline.png'
import icon48 from '@/resources/icons/icon-48.png?base64' import icon48 from '@/resources/icons/icon-48.png?base64'
import assessmentsicon from '@/seqta/icons/assessmentsIcon' import assessmentsicon from '@/seqta/icons/assessmentsIcon'
import coursesicon from '@/seqta/icons/coursesIcon' import coursesicon from '@/seqta/icons/coursesIcon'
import kofi from '@/resources/kofi.png'
// Stylesheets // Stylesheets
import iframeCSS from '@/css/iframe.scss?raw' import iframeCSS from '@/css/iframe.scss?raw'
@@ -38,7 +41,6 @@ import documentLoadCSS from '@/css/documentload.scss?inline'
import renderSvelte from '@/interface/main' import renderSvelte from '@/interface/main'
import Settings from '@/interface/pages/settings.svelte' import Settings from '@/interface/pages/settings.svelte'
import { settingsPopup } from './interface/hooks/SettingsPopup' import { settingsPopup } from './interface/hooks/SettingsPopup'
import { migrateBackgrounds } from './seqta/utils/migrateBackgrounds'
let SettingsClicked = false let SettingsClicked = false
export let MenuOptionsOpen = false export let MenuOptionsOpen = false
@@ -74,6 +76,7 @@ async function init() {
await initializeSettingsState(); await initializeSettingsState();
if (settingsState.onoff) { if (settingsState.onoff) {
injectMainScript();
enableCurrentTheme() enableCurrentTheme()
if (typeof settingsState.assessmentsAverage == 'undefined') { if (typeof settingsState.assessmentsAverage == 'undefined') {
@@ -164,7 +167,35 @@ export function OpenWhatsNewPopup() {
let text = stringToHTML( let text = stringToHTML(
/* html */ ` /* html */ `
<div class="whatsnewTextContainer" style="height: 50%;overflow-y: scroll;"> <div class="whatsnewTextContainer" style="height: 50%;overflow-y: scroll;">
<h1>3.4.5 - News, Bug Fixes, and improvements!</h1>
<li>Added alternative news sources</li>
<li>Notifications now open direct messages</li>
<li>Added Toggle for Letter/Percent Grades</li>
<li>Added fullscreen to the theme creator CSS editor</li>
<li>Added warning if BetterSEQTA is installed</li>
<li>Removed max width from theme creator</li>
<li>Fixed discord icon colour in light mode</li>
<li>Fixed subject averages not showing up with letter grades</li>
<li>Tweaked compose UI</li>
<h1>3.4.4 - Bug Fixes and Improvements</h1>
<li>Added vertical zoom to the timetable</li>
<li>Fixed theme importing failing when images were included</li>
<li>Removed broken gradients on the backgrounds of certain buttons</li>
<li>Fixed timetable quickbar arrow receiving the wrong colour</li>
<li>Auto-applied selected theme after saving in theme creator</li>
<li>Fixed a bug where timetable was clipped at certain times</li>
<li>Fixed custom sidebar layouts not applying on page load</li>
<li>Improved spacing of the message editor buttons</li>
<li>Added HEX colour input to the theme creator</li>
<li>Fixed theme application in the creator</li>
<li>Performance improvements</li>
<li>Other minor bug fixes</li>
<h1>3.4.3 - Minor Bug Fixes</h1>
<li>Fixed a bug where timetable colours couldn't be changed</li>
<li>Other minor bug fixes</li>
<h1>3.4.2 - Minor Bug Fixes</h1> <h1>3.4.2 - Minor Bug Fixes</h1>
<li>Fixed a bug where Assessment Average wasn't enabled by default</li> <li>Fixed a bug where Assessment Average wasn't enabled by default</li>
<li>Fixed floating menus would sometimes be placed behind other elements</li> <li>Fixed floating menus would sometimes be placed behind other elements</li>
@@ -307,6 +338,10 @@ export function OpenWhatsNewPopup() {
`, `,
).firstChild ).firstChild
const kofi_url = browser.runtime.getURL(kofi)
console.log(kofi_url)
let footer = stringToHTML( let footer = stringToHTML(
/* html */ ` /* html */ `
<div class="whatsnewFooter"> <div class="whatsnewFooter">
@@ -324,10 +359,16 @@ export function OpenWhatsNewPopup() {
</a> </a>
<a class="socials" href="https://discord.gg/YzmbnCDkat" style="background: none !important; margin: 0 5px; padding: 0;"> <a class="socials" href="https://discord.gg/YzmbnCDkat" style="background: none !important; margin: 0 5px; padding: 0;">
<svg style="width: 25px; height: 25px;" viewBox="0 0 16 16"> <svg style="width: 25px; height: 25px;" viewBox="0 0 16 16">
<path d="M13.545 2.907a13.2 13.2 0 0 0-3.257-1.011.05.05 0 0 0-.052.025c-.141.25-.297.577-.406.833a12.2 12.2 0 0 0-3.658 0 8 8 0 0 0-.412-.833.05.05 0 0 0-.052-.025c-1.125.194-2.22.534-3.257 1.011a.04.04 0 0 0-.021.018C.356 6.024-.213 9.047.066 12.032q.003.022.021.037a13.3 13.3 0 0 0 3.995 2.02.05.05 0 0 0 .056-.019q.463-.63.818-1.329a.05.05 0 0 0-.01-.059l-.018-.011a9 9 0 0 1-1.248-.595.05.05 0 0 1-.02-.066l.015-.019q.127-.095.248-.195a.05.05 0 0 1 .051-.007c2.619 1.196 5.454 1.196 8.041 0a.05.05 0 0 1 .053.007q.121.1.248.195a.05.05 0 0 1-.004.085 8 8 0 0 1-1.249.594.05.05 0 0 0-.03.03.05.05 0 0 0 .003.041c.24.465.515.909.817 1.329a.05.05 0 0 0 .056.019 13.2 13.2 0 0 0 4.001-2.02.05.05 0 0 0 .021-.037c.334-3.451-.559-6.449-2.366-9.106a.03.03 0 0 0-.02-.019m-8.198 7.307c-.789 0-1.438-.724-1.438-1.612s.637-1.613 1.438-1.613c.807 0 1.45.73 1.438 1.613 0 .888-.637 1.612-1.438 1.612m5.316 0c-.788 0-1.438-.724-1.438-1.612s.637-1.613 1.438-1.613c.807 0 1.451.73 1.438 1.613 0 .888-.631 1.612-1.438 1.612" fill="white"/> <path d="M13.545 2.907a13.2 13.2 0 0 0-3.257-1.011.05.05 0 0 0-.052.025c-.141.25-.297.577-.406.833a12.2 12.2 0 0 0-3.658 0 8 8 0 0 0-.412-.833.05.05 0 0 0-.052-.025c-1.125.194-2.22.534-3.257 1.011a.04.04 0 0 0-.021.018C.356 6.024-.213 9.047.066 12.032q.003.022.021.037a13.3 13.3 0 0 0 3.995 2.02.05.05 0 0 0 .056-.019q.463-.63.818-1.329a.05.05 0 0 0-.01-.059l-.018-.011a9 9 0 0 1-1.248-.595.05.05 0 0 1-.02-.066l.015-.019q.127-.095.248-.195a.05.05 0 0 1 .051-.007c2.619 1.196 5.454 1.196 8.041 0a.05.05 0 0 1 .053.007q.121.1.248.195a.05.05 0 0 1-.004.085 8 8 0 0 1-1.249.594.05.05 0 0 0-.03.03.05.05 0 0 0 .003.041c.24.465.515.909.817 1.329a.05.05 0 0 0 .056.019 13.2 13.2 0 0 0 4.001-2.02.05.05 0 0 0 .021-.037c.334-3.451-.559-6.449-2.366-9.106a.03.03 0 0 0-.02-.019m-8.198 7.307c-.789 0-1.438-.724-1.438-1.612s.637-1.613 1.438-1.613c.807 0 1.45.73 1.438 1.613 0 .888-.637 1.612-1.438 1.612m5.316 0c-.788 0-1.438-.724-1.438-1.612s.637-1.613 1.438-1.613c.807 0 1.451.73 1.438 1.613 0 .888-.631 1.612-1.438 1.612" fill="currentColor"/>
</svg> </svg>
</a> </a>
</div> </div>
<div>
<a href="https://ko-fi.com/sethburkart" target="_blank" style="background: none !important; margin:0;margin-left:6px; padding:0;">
<img height="25" style="border:0px;height:25px;" src="chrome-extension://gkgllhboiibhncnhlijhkbnamfpomjph/resources/kofi.png" border="0" alt="Buy Me a Coffee at ko-fi.com" />
</a>
</div>
</div> </div>
`).firstChild `).firstChild
@@ -385,6 +426,33 @@ export function OpenWhatsNewPopup() {
}) })
} }
function injectMainScript() {
const mainScript = document.createElement('script')
mainScript.src = browser.runtime.getURL(pageState)
document.head.appendChild(mainScript)
}
export function hideSideBar() {
const sidebar = document.getElementById('menu') // The sidebar element to be closed
const main = document.getElementById('main') // The main content element that must be resized to fill the page
const currentMenuWidth = window.getComputedStyle(sidebar!).width // Get the styles of the different elements
const currentContentPosition = window.getComputedStyle(main!).position
if (currentMenuWidth != "0") { // Actually modify it to collapse the sidebar
sidebar!.style.width = "0";
} else {
sidebar!.style.width = "100%";
}
if (currentContentPosition != "relative") {
main!.style.position = 'relative';
} else {
main!.style.position = 'absolute';
}
}
export function OpenAboutPage() { export function OpenAboutPage() {
const background = document.createElement('div') const background = document.createElement('div')
background.id = 'whatsnewbk' background.id = 'whatsnewbk'
@@ -431,7 +499,7 @@ export function OpenAboutPage() {
</a> </a>
<a class="socials" href="https://discord.gg/YzmbnCDkat" style="background: none !important; margin: 0 5px; padding: 0;"> <a class="socials" href="https://discord.gg/YzmbnCDkat" style="background: none !important; margin: 0 5px; padding: 0;">
<svg style="width: 25px; height: 25px;" viewBox="0 0 16 16"> <svg style="width: 25px; height: 25px;" viewBox="0 0 16 16">
<path d="M13.545 2.907a13.2 13.2 0 0 0-3.257-1.011.05.05 0 0 0-.052.025c-.141.25-.297.577-.406.833a12.2 12.2 0 0 0-3.658 0 8 8 0 0 0-.412-.833.05.05 0 0 0-.052-.025c-1.125.194-2.22.534-3.257 1.011a.04.04 0 0 0-.021.018C.356 6.024-.213 9.047.066 12.032q.003.022.021.037a13.3 13.3 0 0 0 3.995 2.02.05.05 0 0 0 .056-.019q.463-.63.818-1.329a.05.05 0 0 0-.01-.059l-.018-.011a9 9 0 0 1-1.248-.595.05.05 0 0 1-.02-.066l.015-.019q.127-.095.248-.195a.05.05 0 0 1 .051-.007c2.619 1.196 5.454 1.196 8.041 0a.05.05 0 0 1 .053.007q.121.1.248.195a.05.05 0 0 1-.004.085 8 8 0 0 1-1.249.594.05.05 0 0 0-.03.03.05.05 0 0 0 .003.041c.24.465.515.909.817 1.329a.05.05 0 0 0 .056.019 13.2 13.2 0 0 0 4.001-2.02.05.05 0 0 0 .021-.037c.334-3.451-.559-6.449-2.366-9.106a.03.03 0 0 0-.02-.019m-8.198 7.307c-.789 0-1.438-.724-1.438-1.612s.637-1.613 1.438-1.613c.807 0 1.45.73 1.438 1.613 0 .888-.637 1.612-1.438 1.612m5.316 0c-.788 0-1.438-.724-1.438-1.612s.637-1.613 1.438-1.613c.807 0 1.451.73 1.438 1.613 0 .888-.631 1.612-1.438 1.612" fill="white"/> <path d="M13.545 2.907a13.2 13.2 0 0 0-3.257-1.011.05.05 0 0 0-.052.025c-.141.25-.297.577-.406.833a12.2 12.2 0 0 0-3.658 0 8 8 0 0 0-.412-.833.05.05 0 0 0-.052-.025c-1.125.194-2.22.534-3.257 1.011a.04.04 0 0 0-.021.018C.356 6.024-.213 9.047.066 12.032q.003.022.021.037a13.3 13.3 0 0 0 3.995 2.02.05.05 0 0 0 .056-.019q.463-.63.818-1.329a.05.05 0 0 0-.01-.059l-.018-.011a9 9 0 0 1-1.248-.595.05.05 0 0 1-.02-.066l.015-.019q.127-.095.248-.195a.05.05 0 0 1 .051-.007c2.619 1.196 5.454 1.196 8.041 0a.05.05 0 0 1 .053.007q.121.1.248.195a.05.05 0 0 1-.004.085 8 8 0 0 1-1.249.594.05.05 0 0 0-.03.03.05.05 0 0 0 .003.041c.24.465.515.909.817 1.329a.05.05 0 0 0 .056.019 13.2 13.2 0 0 0 4.001-2.02.05.05 0 0 0 .021-.037c.334-3.451-.559-6.449-2.366-9.106a.03.03 0 0 0-.02-.019m-8.198 7.307c-.789 0-1.438-.724-1.438-1.612s.637-1.613 1.438-1.613c.807 0 1.45.73 1.438 1.613 0 .888-.637 1.612-1.438 1.612m5.316 0c-.788 0-1.438-.724-1.438-1.612s.637-1.613 1.438-1.613c.807 0 1.451.73 1.438 1.613 0 .888-.631 1.612-1.438 1.612" fill="currentColor"/>
</svg> </svg>
</a> </a>
</div> </div>
@@ -504,10 +572,7 @@ export async function finishLoad() {
if (settingsState.justupdated && !document.getElementById('whatsnewbk')) { if (settingsState.justupdated && !document.getElementById('whatsnewbk')) {
OpenWhatsNewPopup(); OpenWhatsNewPopup();
/* Background Migration script */
} }
migrateBackgrounds();
} }
async function DeleteWhatsNew() { async function DeleteWhatsNew() {
@@ -584,10 +649,11 @@ export async function waitForElm(selector: string, usePolling: boolean = false,
} else { } else {
return new Promise((resolve) => { return new Promise((resolve) => {
const registerObserver = () => { const registerObserver = () => {
const { unregister } = eventManager.register(`${selector}`, { const { unregister } = eventManager.register(`${selector}`, {
customCheck: (element) => element.matches(selector) customCheck: (element) => element.matches(selector)
}, (element) => { }, async (element) => {
resolve(element); resolve(element);
await delay(1);
unregister(); // Remove the listener once the element is found unregister(); // Remove the listener once the element is found
}); });
return unregister; return unregister;
@@ -738,6 +804,7 @@ async function LoadPageElements(): Promise<void> {
className: 'notice', className: 'notice',
}, handleNotices); }, handleNotices);
if (settingsState.assessmentsAverage) { if (settingsState.assessmentsAverage) {
eventManager.register('assessmentsAdded', { eventManager.register('assessmentsAdded', {
elementType: 'div', elementType: 'div',
@@ -745,9 +812,125 @@ async function LoadPageElements(): Promise<void> {
}, handleAssessments); }, handleAssessments);
} }
RegisterClickListeners();
await handleSublink(sublink); await handleSublink(sublink);
} }
function handleTimetableZoom(): void {
console.log('Initializing timetable zoom controls');
// Lazy initialize state variables only when function is first called
let timetableZoomLevel = 1;
let baseContainerHeight: number | null = null;
const originalEntryPositions = new Map<Element, { topRatio: number; heightRatio: number }>();
// Create zoom controls
const zoomControls = document.createElement('div');
zoomControls.className = 'timetable-zoom-controls';
const zoomIn = document.createElement('button');
zoomIn.className = 'uiButton timetable-zoom iconFamily';
zoomIn.innerHTML = '&#xed93;'; // Using unicode for zoom in icon
const zoomOut = document.createElement('button');
zoomOut.className = 'uiButton timetable-zoom iconFamily';
zoomOut.innerHTML = '&#xed94;'; // Using unicode for zoom out icon
zoomControls.appendChild(zoomOut);
zoomControls.appendChild(zoomIn);
const toolbar = document.getElementById('toolbar');
toolbar?.appendChild(zoomControls);
const initializePositions = () => {
// Get the base container height from the first TD
const firstDayColumn = document.querySelector('.dailycal .content .days td') as HTMLElement;
if (!firstDayColumn) return false;
baseContainerHeight = parseInt(firstDayColumn.style.height) || firstDayColumn.offsetHeight;
// Store original ratios
const entries = document.querySelectorAll('.entriesWrapper .entry');
entries.forEach((entry: Element) => {
const entryEl = entry as HTMLElement;
// Calculate ratios relative to detected base height
if (baseContainerHeight === null) return;
const topRatio = parseInt(entryEl.style.top) / baseContainerHeight;
const heightRatio = parseInt(entryEl.style.height) / baseContainerHeight;
originalEntryPositions.set(entry, { topRatio, heightRatio });
});
return true;
};
const updateZoom = () => {
// Initialize positions if not already done
if (baseContainerHeight === null && !initializePositions()) {
console.error('Failed to initialize positions');
return;
}
console.debug(`Updating zoom level to: ${timetableZoomLevel}`);
// Calculate new container height
if (baseContainerHeight === null) return;
const newContainerHeight = baseContainerHeight * timetableZoomLevel;
// Update all day columns (TDs)
const dayColumns = document.querySelectorAll('.dailycal .content .days td');
dayColumns.forEach((td: Element) => {
(td as HTMLElement).style.height = `${newContainerHeight}px`;
});
// Update all entries using stored ratios
const entries = document.querySelectorAll('.entriesWrapper .entry');
entries.forEach((entry: Element) => {
const entryEl = entry as HTMLElement;
const originalRatios = originalEntryPositions.get(entry);
if (originalRatios) {
// Calculate new positions from original ratios
const newTop = originalRatios.topRatio * newContainerHeight;
const newHeight = originalRatios.heightRatio * newContainerHeight;
// Apply new values
entryEl.style.top = `${Math.round(newTop)}px`;
entryEl.style.height = `${Math.round(newHeight)}px`;
}
});
// Update time column to match
const timeColumn = document.querySelector('.times');
if (timeColumn) {
const times = timeColumn.querySelectorAll('.time');
const timeHeight = newContainerHeight / times.length;
times.forEach((time: Element) => {
(time as HTMLElement).style.height = `${timeHeight}px`;
});
}
entries[Math.round((entries.length - 1) / 2)].scrollIntoView({ behavior: 'instant', block: 'center' });
};
zoomIn.addEventListener('click', () => {
if (timetableZoomLevel < 2) {
timetableZoomLevel += 0.2;
updateZoom();
}
});
zoomOut.addEventListener('click', () => {
if (timetableZoomLevel > 0.6) {
timetableZoomLevel -= 0.2;
updateZoom();
}
});
}
async function handleNotices(node: Element): Promise<void> { async function handleNotices(node: Element): Promise<void> {
if (!(node instanceof HTMLElement)) return; if (!(node instanceof HTMLElement)) return;
if (!settingsState.animations) return; if (!settingsState.animations) return;
@@ -798,15 +981,25 @@ async function handleSublink(sublink: string | undefined): Promise<void> {
} }
async function handleTimetable(): Promise<void> { async function handleTimetable(): Promise<void> {
await waitForElm('.time', true, 10) await waitForElm('.time', true, 10);
// Store original heights when timetable loads
const lessons = document.querySelectorAll('.dailycal .lesson');
lessons.forEach((lesson: Element) => {
const lessonEl = lesson as HTMLElement;
lessonEl.setAttribute('data-original-height', lessonEl.offsetHeight.toString());
});
// Existing time format code
if (settingsState.timeFormat == '12') { if (settingsState.timeFormat == '12') {
const times = document.querySelectorAll('.timetablepage .times .time') const times = document.querySelectorAll('.timetablepage .times .time');
for (const time of times) { for (const time of times) {
if (!time.textContent) continue if (!time.textContent) continue;
time.textContent = convertTo12HourFormat(time.textContent, true) time.textContent = convertTo12HourFormat(time.textContent, true);
} }
} }
handleTimetableZoom();
} }
async function handleNewsPage(): Promise<void> { async function handleNewsPage(): Promise<void> {
@@ -988,6 +1181,16 @@ function ChangeMenuItemPositions(storage: any) {
} }
} }
function ReplaceMenuSVG(element: HTMLElement, svg: string) {
let item = element.firstChild as HTMLElement
item!.firstChild!.remove()
item.innerHTML = `<span>${item.innerHTML}</span>`
let newsvg = stringToHTML(svg).firstChild
item.insertBefore((newsvg as Node), item.firstChild)
}
export async function ObserveMenuItemPosition() { export async function ObserveMenuItemPosition() {
await waitForElm('#menu > ul > li') await waitForElm('#menu > ul > li')
await delay(100) await delay(100)
@@ -1019,6 +1222,69 @@ export async function ObserveMenuItemPosition() {
}); });
} }
export function showConflictPopup() {
if (document.getElementById('conflict-popup')) return;
document.body.classList.remove('hidden');
const background = document.createElement('div');
background.id = 'conflict-popup';
background.classList.add('whatsnewBackground');
background.style.zIndex = '10000000';
const container = document.createElement('div');
container.classList.add('whatsnewContainer');
container.style.height = 'auto';
const headerHTML = /* html */`
<div class="whatsnewHeader">
<h1>Extension Conflict Detected</h1>
<p>Legacy BetterSEQTA Installed</p>
</div>
`;
const header = stringToHTML(headerHTML).firstChild;
const textHTML = /* html */`
<div class="whatsnewTextContainer" style="overflow-y: auto; font-size: 1.3rem;">
<p>
It appears that you have the legacy BetterSEQTA extension installed alongside BetterSEQTA+.
This conflict may cause unexpected behavior. (and breaks the extension)
</p>
<p>
Please remove the older BetterSEQTA extension to ensure that BetterSEQTA+ works correctly.
</p>
</div>
`;
const text = stringToHTML(textHTML).firstChild;
const exitButton = document.createElement('div');
exitButton.id = 'whatsnewclosebutton';
if (header) container.append(header);
if (text) container.append(text);
container.append(exitButton);
background.append(container);
document.getElementById('container')?.append(background);
if (settingsState.animations) {
animate(
[background as HTMLElement],
{ opacity: [0, 1] }
);
}
background.addEventListener('click', (event) => {
if (event.target === background) {
background.remove();
}
});
exitButton.addEventListener('click', () => {
background.remove();
});
}
function main() { function main() {
if (typeof settingsState.onoff === 'undefined') { if (typeof settingsState.onoff === 'undefined') {
browser.runtime.sendMessage({ type: 'setDefaultStorage' }) browser.runtime.sendMessage({ type: 'setDefaultStorage' })
@@ -1042,6 +1308,14 @@ function main() {
InjectCustomIcons() InjectCustomIcons()
HideMenuItems() HideMenuItems()
tryLoad() tryLoad()
setTimeout(() => {
const legacyElement = document.querySelector('.outside-container .bottom-container');
if (legacyElement) {
console.log('Legacy extension detected');
showConflictPopup();
}
}, 1000);
} else { } else {
handleDisabled() handleDisabled()
window.addEventListener('load', handleDisabled) window.addEventListener('load', handleDisabled)
@@ -1364,16 +1638,6 @@ function cloneAttributes(target: any, source: any) {
}) })
} }
function ReplaceMenuSVG(element: HTMLElement, svg: string) {
let item = element.firstChild as HTMLElement
item!.firstChild!.remove()
item.innerHTML = `<span>${item.innerHTML}</span>`
let newsvg = stringToHTML(svg).firstChild
item.insertBefore((newsvg as Node), item.firstChild)
}
export function setupSettingsButton() { export function setupSettingsButton() {
var AddedSettings = document.getElementById('AddedSettings'); var AddedSettings = document.getElementById('AddedSettings');
var extensionPopup = document.getElementById('ExtensionPopup'); var extensionPopup = document.getElementById('ExtensionPopup');
@@ -1514,7 +1778,7 @@ function makeLessonDiv(lesson: any, num: number) {
const { code, colour, description, staff, room, from, until, attendanceTitle, programmeID, metaID, assessments } = lesson const { code, colour, description, staff, room, from, until, attendanceTitle, programmeID, metaID, assessments } = lesson
// Construct the base lesson string with default values using ternary operators // Construct the base lesson string with default values using ternary operators
let lessonString = ` let lessonString = /* html */`
<div class="day" id="${code + num}" style="${colour}"> <div class="day" id="${code + num}" style="${colour}">
<h2>${description || 'Unknown'}</h2> <h2>${description || 'Unknown'}</h2>
<h3>${staff || 'Unknown'}</h3> <h3>${staff || 'Unknown'}</h3>
@@ -1525,7 +1789,7 @@ function makeLessonDiv(lesson: any, num: number) {
// Add buttons for assessments and courses if applicable // Add buttons for assessments and courses if applicable
if (programmeID !== 0) { if (programmeID !== 0) {
lessonString += ` lessonString += /* html */`
<div class="day-button clickable" style="right: 5px;" onclick="location.href='${buildAssessmentURL(programmeID, metaID)}'">${assessmentsicon}</div> <div class="day-button clickable" style="right: 5px;" onclick="location.href='${buildAssessmentURL(programmeID, metaID)}'">${assessmentsicon}</div>
<div class="day-button clickable" style="right: 35px;" onclick="location.href='../#?page=/courses/${programmeID}:${metaID}'">${coursesicon}</div> <div class="day-button clickable" style="right: 35px;" onclick="location.href='../#?page=/courses/${programmeID}:${metaID}'">${coursesicon}</div>
` `
@@ -1537,7 +1801,7 @@ function makeLessonDiv(lesson: any, num: number) {
`<p onclick="location.href = '${buildAssessmentURL(programmeID, metaID, element.id)}';">${element.title}</p>` `<p onclick="location.href = '${buildAssessmentURL(programmeID, metaID, element.id)}';">${element.title}</p>`
).join('') ).join('')
lessonString += ` lessonString += /* html */`
<div class="tooltip assessmenttooltip"> <div class="tooltip assessmenttooltip">
<svg style="width:28px;height:28px;border-radius:0;" viewBox="0 0 24 24"> <svg style="width:28px;height:28px;border-radius:0;" viewBox="0 0 24 24">
<path fill="#ed3939" d="M16 2H4C2.9 2 2 2.9 2 4V20C2 21.11 2.9 22 4 22H16C17.11 22 18 21.11 18 20V4C18 2.9 17.11 2 16 2M16 20H4V4H6V12L8.5 9.75L11 12V4H16V20M20 15H22V17H20V15M22 7V13H20V7H22Z" /> <path fill="#ed3939" d="M16 2H4C2.9 2 2 2.9 2 4V20C2 21.11 2.9 22 4 22H16C17.11 22 18 21.11 18 20V4C18 2.9 17.11 2 16 2M16 20H4V4H6V12L8.5 9.75L11 12V4H16V20M20 15H22V17H20V15M22 7V13H20V7H22Z" />
@@ -2197,10 +2461,10 @@ export async function loadHomePage() {
const skeletonStructure = stringToHTML(/* html */` const skeletonStructure = stringToHTML(/* html */`
<div class="home-container" id="home-container"> <div class="home-container" id="home-container">
<div class="shortcut-container border"> <div class="border shortcut-container">
<div class="shortcuts border" id="shortcuts"></div> <div class="border shortcuts" id="shortcuts"></div>
</div> </div>
<div class="timetable-container border"> <div class="border timetable-container">
<div class="home-subtitle"> <div class="home-subtitle">
<h2 id="home-lesson-subtitle">Today's Lessons</h2> <h2 id="home-lesson-subtitle">Today's Lessons</h2>
<div class="timetable-arrows"> <div class="timetable-arrows">
@@ -2215,7 +2479,7 @@ export async function loadHomePage() {
<div class="day-container loading" id="day-container"> <div class="day-container loading" id="day-container">
</div> </div>
</div> </div>
<div class="upcoming-container border"> <div class="border upcoming-container">
<div class="upcoming-title"> <div class="upcoming-title">
<h2 class="home-subtitle">Upcoming Assessments</h2> <h2 class="home-subtitle">Upcoming Assessments</h2>
<div class="upcoming-filters" id="upcoming-filters"></div> <div class="upcoming-filters" id="upcoming-filters"></div>
@@ -2223,7 +2487,7 @@ export async function loadHomePage() {
<div class="upcoming-items loading" id="upcoming-items"> <div class="upcoming-items loading" id="upcoming-items">
</div> </div>
</div> </div>
<div class="notices-container border"> <div class="border notices-container">
<div style="display: flex; justify-content: space-between"> <div style="display: flex; justify-content: space-between">
<h2 class="home-subtitle">Notices</h2> <h2 class="home-subtitle">Notices</h2>
<input type="date" /> <input type="date" />
@@ -2255,7 +2519,12 @@ export async function loadHomePage() {
const cleanup = setupTimetableListeners() const cleanup = setupTimetableListeners()
// Initialize shortcuts immediately // Initialize shortcuts immediately
addShortcuts(settingsState.shortcuts) try {
addShortcuts(settingsState.shortcuts)
} catch(err: any) {
console.error('[BetterSEQTA+] Error adding shortcuts:',
err.message || err)
}
AddCustomShortcutsToPage() AddCustomShortcutsToPage()
// Get current date // Get current date
@@ -2268,7 +2537,6 @@ export async function loadHomePage() {
assessmentsPromise, assessmentsPromise,
classesPromise, classesPromise,
prefsPromise, prefsPromise,
noticesPromise
] = [ ] = [
// Timetable data // Timetable data
fetch(`${location.origin}/seqta/student/load/timetable?`, { fetch(`${location.origin}/seqta/student/load/timetable?`, {
@@ -2292,23 +2560,15 @@ export async function loadHomePage() {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ asArray: true, request: 'userPrefs' }) body: JSON.stringify({ asArray: true, request: 'userPrefs' })
}).then(res => res.json()),
// Notices data
fetch(`${location.origin}/seqta/student/load/notices?`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ date: TodayFormatted })
}).then(res => res.json()) }).then(res => res.json())
] ]
// Process all data in parallel // Process all data in parallel
const [timetableData, assessments, classes, prefs, notices] = await Promise.all([ const [timetableData, assessments, classes, prefs] = await Promise.all([
timetablePromise, timetablePromise,
assessmentsPromise, assessmentsPromise,
classesPromise, classesPromise,
prefsPromise, prefsPromise
noticesPromise
]) ])
// Process timetable data // Process timetable data
@@ -2577,7 +2837,7 @@ export async function SendNewsPage() {
(titlediv! as HTMLElement).innerText = 'News' (titlediv! as HTMLElement).innerText = 'News'
AppendLoadingSymbol('newsloading', '#news-container') AppendLoadingSymbol('newsloading', '#news-container')
const response = await browser.runtime.sendMessage({ type: 'sendNews' }) const response = await browser.runtime.sendMessage({ type: 'sendNews', source: settingsState.newsSource })
const newscontainer = document.querySelector('#news-container') const newscontainer = document.querySelector('#news-container')
document.getElementById('newsloading')?.remove() document.getElementById('newsloading')?.remove()
@@ -2594,7 +2854,7 @@ export async function SendNewsPage() {
const articleimage = document.createElement('div') const articleimage = document.createElement('div')
articleimage.classList.add('articleimage') articleimage.classList.add('articleimage')
if (article.urlToImage == 'null') { if (article.urlToImage == 'null' || article.urlToImage == null) {
articleimage.style.cssText = ` articleimage.style.cssText = `
background-image: url(${browser.runtime.getURL(LogoLightOutline)}); background-image: url(${browser.runtime.getURL(LogoLightOutline)});
width: 20%; width: 20%;
@@ -2613,6 +2873,8 @@ export async function SendNewsPage() {
title.target = '_blank' title.target = '_blank'
const description = document.createElement('p') const description = document.createElement('p')
article.description = article.description.length > 400 ? article.description.substring(0, 400) + '...' : article.description
description.innerHTML = article.description description.innerHTML = article.description
articletext.append(title, description) articletext.append(title, description)
@@ -2636,9 +2898,8 @@ export async function SendNewsPage() {
async function CheckForMenuList() { async function CheckForMenuList() {
try { try {
if (document.getElementById('menu')?.firstChild) { await waitForElm('#menu > ul');
ObserveMenuItemPosition() ObserveMenuItemPosition();
}
} catch (error) { } catch (error) {
return; return;
} }
@@ -2742,6 +3003,50 @@ async function handleAssessments(node: Element): Promise<void> {
const assessmentsWrapper = await waitForElm('#main > .assessmentsWrapper .assessments .AssessmentItem__AssessmentItem___2EZ95', true, 50); const assessmentsWrapper = await waitForElm('#main > .assessmentsWrapper .assessments .AssessmentItem__AssessmentItem___2EZ95', true, 50);
if (!assessmentsWrapper) return; if (!assessmentsWrapper) return;
// Grade conversion map for letter grades
const letterGradeMap: Record<string, number> = {
'A+': 100,
'A': 95,
'A-': 90,
'B+': 85,
'B': 80,
'B-': 75,
'C+': 70,
'C': 65,
'C-': 60,
'D+': 55,
'D': 50,
'D-': 45,
'E+': 40,
'E': 35,
'E-': 30,
'F': 0
};
// Function to parse grade text into a number
function parseGrade(gradeText: string): number {
// Remove any whitespace
const trimmedGrade = gradeText.trim().toUpperCase();
// Check if it is a non-percent grade
if (trimmedGrade.includes('/')) {
const grade = trimmedGrade.split("/");
var a = grade[1] as unknown as number
var b = grade[0] as unknown as number
return ((b/a) * 100);
}
// Check if it's a percentage
if (trimmedGrade.includes('%')) {
return parseFloat(trimmedGrade.replace('%', '')) || 0;
}
// Check if it's a letter grade
if (letterGradeMap.hasOwnProperty(trimmedGrade)) {
return letterGradeMap[trimmedGrade];
}
return 0;
}
// Function to calculate average of grades // Function to calculate average of grades
function calculateAverageGrade(): number { function calculateAverageGrade(): number {
const gradeElements = document.querySelectorAll('.Thermoscore__text___1NdvB'); const gradeElements = document.querySelectorAll('.Thermoscore__text___1NdvB');
@@ -2749,8 +3054,9 @@ async function handleAssessments(node: Element): Promise<void> {
let count = 0; let count = 0;
gradeElements.forEach(element => { gradeElements.forEach(element => {
const grade = parseFloat(element.textContent?.replace('%', '') || '0'); const gradeText = element.textContent || '';
if (!isNaN(grade)) { const grade = parseGrade(gradeText);
if (grade > 0) {
total += grade; total += grade;
count++; count++;
} }
@@ -2761,15 +3067,49 @@ async function handleAssessments(node: Element): Promise<void> {
// Function to add the average assessment item // Function to add the average assessment item
function addAverageAssessment() { function addAverageAssessment() {
const average = calculateAverageGrade(); const numaverage = calculateAverageGrade();
if (average === 0) return; if (numaverage === 0) return;
// Remove existing average section if it exists // Remove existing average section if it exists
const existingAverage = document.querySelector('.AssessmentItem__AssessmentItem___2EZ95:first-child'); const existingAverage = document.querySelector('.AssessmentItem__AssessmentItem___2EZ95:first-child');
if (existingAverage?.querySelector('.AssessmentItem__title___2bELn')?.textContent === 'Subject Average') { if (existingAverage?.querySelector('.AssessmentItem__title___2bELn')?.textContent === 'Subject Average') {
existingAverage.remove(); existingAverage.remove();
} }
const preaverage = numaverage.toFixed(0) as unknown as number
const prepaverage = Math.ceil(preaverage / 5) * 5;
const NumberGradeMap: Record<number, string> = {
100: "A+",
95: "A",
90: "A-",
85: "B+",
80: "B",
75: "B-",
70: "C+",
65: "C",
60: "C-",
55: "D+",
50: "D",
45: "D-",
40: "E+",
35: "E",
30: "E-",
0: "F"
};
var letteraverage = "N/A"
const check = Object.prototype.hasOwnProperty.call(NumberGradeMap, prepaverage);
if (check) {
console.debug("[BetterSEQTA+ Debugger] Match found")
letteraverage = NumberGradeMap[prepaverage];
} else {
console.debug("[BetterSEQTA+ Debugger] No match found")
letteraverage = "N/A"
}
var average = "N/A"
if (settingsState.lettergrade) {
average = letteraverage
} else {
average = `${numaverage.toFixed(2)}%`
}
const averageElement = stringToHTML(/* html */` const averageElement = stringToHTML(/* html */`
<div class="AssessmentItem__AssessmentItem___2EZ95"> <div class="AssessmentItem__AssessmentItem___2EZ95">
<div class="AssessmentItem__metaContainer___dMKma"> <div class="AssessmentItem__metaContainer___dMKma">
@@ -2780,8 +3120,8 @@ async function handleAssessments(node: Element): Promise<void> {
</div> </div>
</div> </div>
<div class="Thermoscore__Thermoscore___2tWMi"> <div class="Thermoscore__Thermoscore___2tWMi">
<div class="Thermoscore__fill___35WjF" style="width: ${average.toFixed(2)}%;"> <div class="Thermoscore__fill___35WjF" style="width: ${numaverage.toFixed(2)}%">
<div class="Thermoscore__text___1NdvB" title="${average.toFixed(2)}%">${average.toFixed(2)}%</div> <div class="Thermoscore__text___1NdvB" title="${average};">${average}</div>
</div> </div>
</div> </div>
</div> </div>
@@ -2796,4 +3136,4 @@ async function handleAssessments(node: Element): Promise<void> {
// Add the average assessment item // Add the average assessment item
addAverageAssessment(); addAverageAssessment();
} }
+39 -113
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,70 +15,49 @@ 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();
break; break;
case 'extensionPages':
browser.tabs.query({}).then(function (tabs) {
for (let tab of tabs) {
if (tab.url?.includes('chrome-extension://')) {
browser.tabs.sendMessage(tab.id!, request);
}
}
});
break;
case 'currentTab':
browser.tabs.query({ active: true, currentWindow: true }).then(function (tabs) {
browser.tabs.sendMessage(tabs[0].id!, request).then(function (response) {
sendResponse(response);
});
});
return true;
case 'githubTab':
browser.tabs.create({ url: 'github.com/BetterSEQTA/BetterSEQTA-Plus' });
break;
case 'setDefaultStorage': case 'extensionPages':
SetStorageValue(DefaultValues); browser.tabs.query({}).then(function (tabs) {
break; for (let tab of tabs) {
if (tab.url?.includes('chrome-extension://')) {
browser.tabs.sendMessage(tab.id!, request);
}
}
});
break;
case 'currentTab':
browser.tabs.query({ active: true, currentWindow: true }).then(function (tabs) {
browser.tabs.sendMessage(tabs[0].id!, request).then(function (response) {
sendResponse(response);
});
});
return true; // Keep message channel open for async response
case 'sendNews': case 'githubTab':
const date = new Date(); browser.tabs.create({ url: 'github.com/BetterSEQTA/BetterSEQTA-Plus' });
break;
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`;
GetNews(sendResponse, url);
return true;
default: case 'setDefaultStorage':
console.log('Unknown request type'); SetStorageValue(DefaultValues);
break;
case 'sendNews':
fetchNews(request.source ?? 'australia', sendResponse);
return true;
default:
console.log('Unknown request type');
} }
}); });
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,
@@ -220,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 } });
}
+151 -41
View File
@@ -1,5 +1,4 @@
@use "sass:meta"; @use "sass:meta";
@charset "UTF-8";
@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");
@include meta.load-css("injected/sidebar-animation.scss"); @include meta.load-css("injected/sidebar-animation.scss");
@@ -12,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,
@@ -121,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 {
@@ -203,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,
@@ -223,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;
@@ -244,8 +260,23 @@ html {
color: var(--text-primary); color: var(--text-primary);
} }
.ais-btnSearch .material-icons { .ais-btnSearch {
font-size: 18px !important; 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;
content: 'Search' !important;
}
}
} }
} }
@@ -459,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;
} }
@@ -485,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;
@@ -540,29 +590,42 @@ 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 {
top: -23px; &.below::before {
background-color: inherit; top: -23px;
clip-path: polygon(50% 40%, 0 100%, 100% 100%); background-color: inherit;
border-bottom-color: transparent !important; clip-path: polygon(50% 40%, 0 100%, 100% 100%);
} border-bottom-color: transparent !important;
#main > .timetablepage > .quickbar.above::after { }
content: "";
position: absolute; &.above[data-yiq="light"]::after {
bottom: -23px; background-color: rgba(0, 0, 0, 0.2);
z-index: 2; }
left: 50%;
margin: 0 0 0 -12px; &.above[data-yiq="dark"]::after {
background-color: rgba(255, 255, 255, 0.2); background-color: rgba(255, 255, 255, 0.2);
clip-path: polygon(50% 40%, 0 0, 100% 0); }
border: 12px solid rgba(255, 255, 255, 0);
border-top-color: transparent; &.above::after {
} content: "";
#main > .timetablepage > .quickbar.above::before { position: absolute;
border-bottom-color: transparent !important; bottom: -24px;
bottom: -23px !important; z-index: 0;
background-color: inherit; left: 50%;
clip-path: polygon(50% 40%, 0 0, 100% 0); margin: 0 0 0 -12px;
clip-path: polygon(50% 40%, 0 0, 100% 0);
border: 12px solid rgba(255, 255, 255, 0);
border-top-color: transparent;
}
&.above::before {
border-bottom-color: transparent !important;
border-top-color: transparent !important;
bottom: -24px !important;
z-index: -1 !important;
background-color: inherit;
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 {
@@ -625,9 +688,17 @@ td.colourBar {
#container #content .uiButton { #container #content .uiButton {
border-radius: 16px; border-radius: 16px;
} }
.dark {
#toolbar button.toggled,
#toolbar button.depressed {
background: #333333;
color: white;
}
}
#toolbar button.toggled, #toolbar button.toggled,
#toolbar button.depressed { #toolbar button.depressed {
background: var(--better-main); background: #f3f3f3;
color: black;
} }
ul.buttonChecklist { ul.buttonChecklist {
border-radius: 16px; border-radius: 16px;
@@ -649,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%;
} }
@@ -1757,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;
@@ -1967,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;
} }
@@ -1983,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 {
background: var(--auto-background); padding-left: 8px;
color: var(--text-primary) !important;
&:focus {
background: var(--auto-background);
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);
@@ -2649,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;
} }
@@ -2973,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 {
+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,
+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) {
@@ -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>
@@ -54,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();
}} }}
@@ -79,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"/>
@@ -93,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"/>
@@ -112,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>
+37 -4
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)",
@@ -107,6 +108,16 @@
onChange: (isOn: boolean) => settingsState.assessmentsAverage = isOn 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.",
@@ -146,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,
@@ -170,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>
@@ -183,4 +216,4 @@
</div> </div>
</div> </div>
{/if} {/if}
</div> </div>
+48 -17
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,13 +200,16 @@
<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'}
{#key themeLoaded} {#if !codeEditorFullscreen}
<CodeEditor {...(item.props as CodeEditorProps)} /> {#key themeLoaded}
{/key} <!-- Only render inline if not fullscreen -->
<CodeEditor className="h-[400px]" {...(item.props as CodeEditorProps)} />
{/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>
<input <input
@@ -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],
}, },
-2
View File
@@ -1,8 +1,6 @@
{ {
"manifest_version": 3, "manifest_version": 3,
"name": "BetterSEQTA+", "name": "BetterSEQTA+",
"version": "3.4.2",
"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",
+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

+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`
+16 -3
View File
@@ -22,8 +22,20 @@ type ThemeContent = {
}; };
function stripBase64Prefix(base64String: string): string { function stripBase64Prefix(base64String: string): string {
const prefixRegex = /^data:image\/\w+;base64,/; if (!base64String) return '';
return base64String.replace(prefixRegex, '');
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 }) => {
@@ -37,11 +49,12 @@ export const StoreDownloadTheme = async (theme: { themeContent: Theme }) => {
export const InstallTheme = async (themeData: ThemeContent) => { export const InstallTheme = async (themeData: ThemeContent) => {
const strippedCoverImage = stripBase64Prefix(themeData.coverImage); const strippedCoverImage = stripBase64Prefix(themeData.coverImage);
const coverImageBlob = base64ToBlob(strippedCoverImage); 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;
+1 -2
View File
@@ -136,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;
}
};
+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;
} }
+2
View File
@@ -39,6 +39,8 @@ export interface SettingsState {
devMode?: boolean; devMode?: boolean;
originalDarkMode?: boolean; originalDarkMode?: boolean;
assessmentsAverage?: boolean; assessmentsAverage?: boolean;
lettergrade: boolean;
newsSource?: string;
} }
interface ToggleItem { interface ToggleItem {
-402
View File
@@ -1,402 +0,0 @@
https://sethburkart123.github.io/sf-pro-https://sethburkart123.github.io/sf-pro-fonts/fonts/
/* -------------------------------------------------------------------------
* SF Pro Display
* ------------------------------------------------------------------------- */
@font-face {
font-family: 'SF Pro Display';
font-style: normal;
font-weight: 100;
src: url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-ultralight.woff2') format('woff2'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-ultralight.woff') format('woff'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-ultralight.ttf') format('truetype');
}
@font-face {
font-family: 'SF Pro Display';
font-style: normal;
font-weight: 200;
src: url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-thin.woff2') format('woff2'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-thin.woff') format('woff'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-thin.ttf') format('truetype');
}
@font-face {
font-family: 'SF Pro Display';
font-style: normal;
font-weight: 300;
src: url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-light.woff2') format('woff2'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-light.woff') format('woff'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-light.ttf') format('truetype');
}
@font-face {
font-family: 'SF Pro Display';
font-style: normal;
font-weight: 400;
src: url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-regular.woff2') format('woff2'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-regular.woff') format('woff'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-regular.ttf') format('truetype');
}
@font-face {
font-family: 'SF Pro Display';
font-style: normal;
font-weight: 500;
src: url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-medium.woff2') format('woff2'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-medium.woff') format('woff'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-medium.ttf') format('truetype');
}
@font-face {
font-family: 'SF Pro Display';
font-style: normal;
font-weight: 600;
src: url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-semibold.woff2') format('woff2'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-semibold.woff') format('woff'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-semibold.ttf') format('truetype');
}
@font-face {
font-family: 'SF Pro Display';
font-style: normal;
font-weight: 700;
src: url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-bold.woff2') format('woff2'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-bold.woff') format('woff'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-bold.ttf') format('truetype');
}
@font-face {
font-family: 'SF Pro Display';
font-style: normal;
font-weight: 800;
src: url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-heavy.woff2') format('woff2'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-heavy.woff') format('woff'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-heavy.ttf') format('truetype');
}
@font-face {
font-family: 'SF Pro Display';
font-style: normal;
font-weight: 900;
src: url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-black.woff2') format('woff2'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-black.woff') format('woff'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-black.ttf') format('truetype');
}
/* -------------------------------------------------------------------------
* SF Pro Display Italic
* ------------------------------------------------------------------------- */
@font-face {
font-family: 'SF Pro Display';
font-style: italic;
font-weight: 100;
src: url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-ultralightitalic.woff2') format('woff2'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-ultralightitalic.woff') format('woff'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-ultralightitalic.ttf') format('truetype');
}
@font-face {
font-family: 'SF Pro Display';
font-style: italic;
font-weight: 200;
src: url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-thinitalic.woff2') format('woff2'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-thinitalic.woff') format('woff'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-thinitalic.ttf') format('truetype');
}
@font-face {
font-family: 'SF Pro Display';
font-style: italic;
font-weight: 300;
src: url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-lightitalic.woff2') format('woff2'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-lightitalic.woff') format('woff'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-lightitalic.ttf') format('truetype');
}
@font-face {
font-family: 'SF Pro Display';
font-style: italic;
font-weight: 400;
src: url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-regularitalic.woff2') format('woff2'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-regularitalic.woff') format('woff'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-regularitalic.ttf') format('truetype');
}
@font-face {
font-family: 'SF Pro Display';
font-style: italic;
font-weight: 500;
src: url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-mediumitalic.woff2') format('woff2'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-mediumitalic.woff') format('woff'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-mediumitalic.ttf') format('truetype');
}
@font-face {
font-family: 'SF Pro Display';
font-style: italic;
font-weight: 600;
src: url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-semibolditalic.woff2') format('woff2'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-semibolditalic.woff') format('woff'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-semibolditalic.ttf') format('truetype');
}
@font-face {
font-family: 'SF Pro Display';
font-style: italic;
font-weight: 700;
src: url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-bolditalic.woff2') format('woff2'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-bolditalic.woff') format('woff'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-bolditalic.ttf') format('truetype');
}
@font-face {
font-family: 'SF Pro Display';
font-style: italic;
font-weight: 800;
src: url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-heavyitalic.woff2') format('woff2'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-heavyitalic.woff') format('woff'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-heavyitalic.ttf') format('truetype');
}
@font-face {
font-family: 'SF Pro Display';
font-style: italic;
font-weight: 900;
src: url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-blackitalic.woff2') format('woff2'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-blackitalic.woff') format('woff'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-display-blackitalic.ttf') format('truetype');
}
/* -------------------------------------------------------------------------
* SF Pro Text
* ------------------------------------------------------------------------- */
@font-face {
font-family: 'SF Pro Text';
font-style: regular;
font-weight: 300;
src: url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-text-light.woff2') format('woff2'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-text-light.woff') format('woff'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-text-light.ttf') format('truetype');
}
@font-face {
font-family: 'SF Pro Text';
font-style: regular;
font-weight: 400;
src: url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-text-regular.woff2') format('woff2'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-text-regular.woff') format('woff'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-text-regular.ttf') format('truetype');
}
@font-face {
font-family: 'SF Pro Text';
font-style: regular;
font-weight: 500;
src: url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-text-medium.woff2') format('woff2'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-text-medium.woff') format('woff'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-text-medium.ttf') format('truetype');
}
@font-face {
font-family: 'SF Pro Text';
font-style: regular;
font-weight: 600;
src: url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-text-semibold.woff2') format('woff2'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-text-semibold.woff') format('woff'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-text-semibold.ttf') format('truetype');
}
@font-face {
font-family: 'SF Pro Text';
font-style: regular;
font-weight: 700;
src: url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-text-bold.woff2') format('woff2'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-text-bold.woff') format('woff'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-text-bold.ttf') format('truetype');
}
@font-face {
font-family: 'SF Pro Text';
font-style: regular;
font-weight: 800;
src: url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-text-heavy.woff2') format('woff2'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-text-heavy.woff') format('woff'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-text-heavy.ttf') format('truetype');
}
/* -------------------------------------------------------------------------
* SF Pro Text Italic
* ------------------------------------------------------------------------- */
@font-face {
font-family: 'SF Pro Text';
font-style: italic;
font-weight: 300;
src: url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-text-lightitalic.woff2') format('woff2'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-text-lightitalic.woff') format('woff'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-text-lightitalic.ttf') format('truetype');
}
@font-face {
font-family: 'SF Pro Text';
font-style: italic;
font-weight: 400;
src: url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-text-regularitalic.woff2') format('woff2'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-text-regularitalic.woff') format('woff'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-text-regularitalic.ttf') format('truetype');
}
@font-face {
font-family: 'SF Pro Text';
font-style: italic;
font-weight: 500;
src: url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-text-mediumitalic.woff2') format('woff2'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-text-mediumitalic.woff') format('woff'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-text-mediumitalic.ttf') format('truetype');
}
@font-face {
font-family: 'SF Pro Text';
font-style: italic;
font-weight: 600;
src: url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-text-semibolditalic.woff2') format('woff2'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-text-semibolditalic.woff') format('woff'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-text-semibolditalic.ttf') format('truetype');
}
@font-face {
font-family: 'SF Pro Text';
font-style: italic;
font-weight: 700;
src: url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-text-bolditalic.woff2') format('woff2'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-text-bolditalic.woff') format('woff'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-text-bolditalic.ttf') format('truetype');
}
@font-face {
font-family: 'SF Pro Text';
font-style: italic;
font-weight: 800;
src: url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-text-heavyitalic.woff2') format('woff2'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-text-heavyitalic.woff') format('woff'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sf-pro-text-heavyitalic.ttf') format('truetype');
}
/* -------------------------------------------------------------------------
* SF Mono
* ------------------------------------------------------------------------- */
@font-face {
font-family: 'SF Mono';
font-style: regular;
font-weight: 300;
src: url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sfmono-light.woff2') format('woff2'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sfmono-light.woff') format('woff');
}
@font-face {
font-family: 'SF Mono';
font-style: regular;
font-weight: 400;
src: url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sfmono-regular.woff2') format('woff2'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sfmono-regular.woff') format('woff');
}
@font-face {
font-family: 'SF Mono';
font-style: regular;
font-weight: 500;
src: url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sfmono-medium.woff2') format('woff2'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sfmono-medium.woff') format('woff');
}
@font-face {
font-family: 'SF Mono';
font-style: regular;
font-weight: 600;
src: url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sfmono-semibold.woff2') format('woff2'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sfmono-semibold.woff') format('woff');
}
@font-face {
font-family: 'SF Mono';
font-style: regular;
font-weight: 700;
src: url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sfmono-bold.woff2') format('woff2'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sfmono-bold.woff') format('woff');
}
@font-face {
font-family: 'SF Mono';
font-style: regular;
font-weight: 800;
src: url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sfmono-heavy.woff2') format('woff2'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sfmono-heavy.woff') format('woff');
}
/* -------------------------------------------------------------------------
* SF Pro Text Italic
* ------------------------------------------------------------------------- */
@font-face {
font-family: 'SF Mono';
font-style: italic;
font-weight: 300;
src: url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sfmono-lightitalic.woff2') format('woff2'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sfmono-lightitalic.woff') format('woff');
}
@font-face {
font-family: 'SF Mono';
font-style: italic;
font-weight: 400;
src: url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sfmono-regularitalic.woff2') format('woff2'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sfmono-regularitalic.woff') format('woff');
}
@font-face {
font-family: 'SF Mono';
font-style: italic;
font-weight: 500;
src: url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sfmono-mediumitalic.woff2') format('woff2'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sfmono-mediumitalic.woff') format('woff');
}
@font-face {
font-family: 'SF Mono';
font-style: italic;
font-weight: 600;
src: url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sfmono-semibolditalic.woff2') format('woff2'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sfmono-semibolditalic.woff') format('woff');
}
@font-face {
font-family: 'SF Mono';
font-style: italic;
font-weight: 700;
src: url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sfmono-bolditalic.woff2') format('woff2'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sfmono-bolditalic.woff') format('woff');
}
@font-face {
font-family: 'SF Mono';
font-style: italic;
font-weight: 800;
src: url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sfmono-heavyitalic.woff2') format('woff2'),
url('https://sethburkart123.github.io/sf-pro-fonts/fonts/sfmono-heavyitalic.woff') format('woff');
}
+10 -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: {
@@ -64,6 +66,9 @@ export default defineConfig({
optimizeDeps: { optimizeDeps: {
include: ['@babel/runtime/helpers/extends', '@babel/runtime/helpers/interopRequireDefault'], 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,
@@ -71,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'),
} }
} }
} }
}); }));