mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-06-06 03:34:40 +00:00
format: run prettify
This commit is contained in:
+94
-111
@@ -2,87 +2,83 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
forbidden: [
|
forbidden: [
|
||||||
{
|
{
|
||||||
name: 'no-circular',
|
name: "no-circular",
|
||||||
severity: 'warn',
|
severity: "warn",
|
||||||
comment:
|
comment:
|
||||||
'This dependency is part of a circular relationship. You might want to revise ' +
|
"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) ',
|
"your solution (i.e. use dependency inversion, make sure the modules have a single responsibility) ",
|
||||||
from: {},
|
from: {},
|
||||||
to: {
|
to: {
|
||||||
circular: true
|
circular: true,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'no-orphans',
|
name: "no-orphans",
|
||||||
comment:
|
comment:
|
||||||
"This is an orphan module - it's likely not used (anymore?). Either use it or " +
|
"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), " +
|
"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 " +
|
"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 " +
|
"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.",
|
"files (.d.ts), tsconfig.json and some of the babel and webpack configs.",
|
||||||
severity: 'warn',
|
severity: "warn",
|
||||||
from: {
|
from: {
|
||||||
orphan: true,
|
orphan: true,
|
||||||
pathNot: [
|
pathNot: [
|
||||||
'(^|/)[.][^/]+[.](?:js|cjs|mjs|ts|cts|mts|json)$', // dot files
|
"(^|/)[.][^/]+[.](?:js|cjs|mjs|ts|cts|mts|json)$", // dot files
|
||||||
'[.]d[.]ts$', // TypeScript declaration files
|
"[.]d[.]ts$", // TypeScript declaration files
|
||||||
'(^|/)tsconfig[.]json$', // TypeScript config
|
"(^|/)tsconfig[.]json$", // TypeScript config
|
||||||
'(^|/)(?:babel|webpack)[.]config[.](?:js|cjs|mjs|ts|cts|mts|json)$' // other configs
|
"(^|/)(?:babel|webpack)[.]config[.](?:js|cjs|mjs|ts|cts|mts|json)$", // other configs
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
to: {},
|
to: {},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'no-deprecated-core',
|
name: "no-deprecated-core",
|
||||||
comment:
|
comment:
|
||||||
'A module depends on a node core module that has been deprecated. Find an alternative - these are ' +
|
"A module depends on a node core module that has been deprecated. Find an alternative - these are " +
|
||||||
"bound to exist - node doesn't deprecate lightly.",
|
"bound to exist - node doesn't deprecate lightly.",
|
||||||
severity: 'warn',
|
severity: "warn",
|
||||||
from: {},
|
from: {},
|
||||||
to: {
|
to: {
|
||||||
dependencyTypes: [
|
dependencyTypes: ["core"],
|
||||||
'core'
|
|
||||||
],
|
|
||||||
path: [
|
path: [
|
||||||
'^v8/tools/codemap$',
|
"^v8/tools/codemap$",
|
||||||
'^v8/tools/consarray$',
|
"^v8/tools/consarray$",
|
||||||
'^v8/tools/csvparser$',
|
"^v8/tools/csvparser$",
|
||||||
'^v8/tools/logreader$',
|
"^v8/tools/logreader$",
|
||||||
'^v8/tools/profile_view$',
|
"^v8/tools/profile_view$",
|
||||||
'^v8/tools/profile$',
|
"^v8/tools/profile$",
|
||||||
'^v8/tools/SourceMap$',
|
"^v8/tools/SourceMap$",
|
||||||
'^v8/tools/splaytree$',
|
"^v8/tools/splaytree$",
|
||||||
'^v8/tools/tickprocessor-driver$',
|
"^v8/tools/tickprocessor-driver$",
|
||||||
'^v8/tools/tickprocessor$',
|
"^v8/tools/tickprocessor$",
|
||||||
'^node-inspect/lib/_inspect$',
|
"^node-inspect/lib/_inspect$",
|
||||||
'^node-inspect/lib/internal/inspect_client$',
|
"^node-inspect/lib/internal/inspect_client$",
|
||||||
'^node-inspect/lib/internal/inspect_repl$',
|
"^node-inspect/lib/internal/inspect_repl$",
|
||||||
'^async_hooks$',
|
"^async_hooks$",
|
||||||
'^punycode$',
|
"^punycode$",
|
||||||
'^domain$',
|
"^domain$",
|
||||||
'^constants$',
|
"^constants$",
|
||||||
'^sys$',
|
"^sys$",
|
||||||
'^_linklist$',
|
"^_linklist$",
|
||||||
'^_stream_wrap$'
|
"^_stream_wrap$",
|
||||||
],
|
],
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'not-to-deprecated',
|
name: "not-to-deprecated",
|
||||||
comment:
|
comment:
|
||||||
'This module uses a (version of an) npm module that has been deprecated. Either upgrade to a later ' +
|
"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.',
|
"version of that module, or find an alternative. Deprecated modules are a security risk.",
|
||||||
severity: 'warn',
|
severity: "warn",
|
||||||
from: {},
|
from: {},
|
||||||
to: {
|
to: {
|
||||||
dependencyTypes: [
|
dependencyTypes: ["deprecated"],
|
||||||
'deprecated'
|
},
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'no-non-package-json',
|
name: "no-non-package-json",
|
||||||
severity: 'error',
|
severity: "error",
|
||||||
comment:
|
comment:
|
||||||
"This module depends on an npm package that isn't in the 'dependencies' section of your package.json. " +
|
"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 " +
|
"That's problematic as the package either (1) won't be available on live (2 - worse) will be " +
|
||||||
@@ -90,84 +86,75 @@ module.exports = {
|
|||||||
"in your package.json.",
|
"in your package.json.",
|
||||||
from: {},
|
from: {},
|
||||||
to: {
|
to: {
|
||||||
dependencyTypes: [
|
dependencyTypes: ["npm-no-pkg", "npm-unknown"],
|
||||||
'npm-no-pkg',
|
},
|
||||||
'npm-unknown'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'not-to-unresolvable',
|
name: "not-to-unresolvable",
|
||||||
comment:
|
comment:
|
||||||
"This module depends on a module that cannot be found ('resolved to disk'). If it's an npm " +
|
"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.',
|
"module: add it to your package.json. In all other cases you likely already know what to do.",
|
||||||
severity: 'error',
|
severity: "error",
|
||||||
from: {},
|
from: {},
|
||||||
to: {
|
to: {
|
||||||
couldNotResolve: true
|
couldNotResolve: true,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'no-duplicate-dep-types',
|
name: "no-duplicate-dep-types",
|
||||||
comment:
|
comment:
|
||||||
"Likely this module depends on an external ('npm') package that occurs more than once " +
|
"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 " +
|
"in your package.json i.e. bot as a devDependencies and in dependencies. This will cause " +
|
||||||
"maintenance problems later on.",
|
"maintenance problems later on.",
|
||||||
severity: 'warn',
|
severity: "warn",
|
||||||
from: {},
|
from: {},
|
||||||
to: {
|
to: {
|
||||||
moreThanOneDependencyType: true,
|
moreThanOneDependencyType: true,
|
||||||
// as it's pretty common to have a type import be a type only import
|
// as it's pretty common to have a type import be a type only import
|
||||||
// _and_ (e.g.) a devDependency - don't consider type-only dependency
|
// _and_ (e.g.) a devDependency - don't consider type-only dependency
|
||||||
// types for this rule
|
// types for this rule
|
||||||
dependencyTypesNot: ["type-only"]
|
dependencyTypesNot: ["type-only"],
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
/* rules you might want to tweak for your specific situation: */
|
/* rules you might want to tweak for your specific situation: */
|
||||||
|
|
||||||
{
|
{
|
||||||
name: 'not-to-spec',
|
name: "not-to-spec",
|
||||||
comment:
|
comment:
|
||||||
'This module depends on a spec (test) file. The sole responsibility of a spec file is to test code. ' +
|
"This module depends on a spec (test) file. The sole responsibility of a spec file is to test code. " +
|
||||||
"If there's something in a spec that's of use to other modules, it doesn't have that single " +
|
"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.',
|
"responsibility anymore. Factor it out into (e.g.) a separate utility/ helper or a mock.",
|
||||||
severity: 'error',
|
severity: "error",
|
||||||
from: {},
|
from: {},
|
||||||
to: {
|
to: {
|
||||||
path: '[.](?:spec|test)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$'
|
path: "[.](?:spec|test)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$",
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'not-to-dev-dep',
|
name: "not-to-dev-dep",
|
||||||
severity: 'error',
|
severity: "error",
|
||||||
comment:
|
comment:
|
||||||
"This module depends on an npm package from the 'devDependencies' section of your " +
|
"This module depends on an npm package from the 'devDependencies' section of your " +
|
||||||
'package.json. It looks like something that ships to production, though. To prevent problems ' +
|
"package.json. It looks like something that ships to production, though. To prevent problems " +
|
||||||
"with npm packages that aren't there on production declare it (only!) in the 'dependencies'" +
|
"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 ' +
|
"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.pathNot re of the not-to-dev-dep rule in the dependency-cruiser configuration",
|
||||||
from: {
|
from: {
|
||||||
path: '^(src)',
|
path: "^(src)",
|
||||||
pathNot: '[.](?:spec|test)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$'
|
pathNot: "[.](?:spec|test)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$",
|
||||||
},
|
},
|
||||||
to: {
|
to: {
|
||||||
dependencyTypes: [
|
dependencyTypes: ["npm-dev"],
|
||||||
'npm-dev',
|
|
||||||
],
|
|
||||||
// type only dependencies are not a problem as they don't end up in the
|
// type only dependencies are not a problem as they don't end up in the
|
||||||
// production code or are ignored by the runtime.
|
// production code or are ignored by the runtime.
|
||||||
dependencyTypesNot: [
|
dependencyTypesNot: ["type-only"],
|
||||||
'type-only'
|
pathNot: ["node_modules/@types/"],
|
||||||
],
|
},
|
||||||
pathNot: [
|
|
||||||
'node_modules/@types/'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'optional-deps-used',
|
name: "optional-deps-used",
|
||||||
severity: 'info',
|
severity: "info",
|
||||||
comment:
|
comment:
|
||||||
"This module depends on an npm package that is declared as an optional dependency " +
|
"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. " +
|
"in your package.json. As this makes sense in limited situations only, it's flagged here. " +
|
||||||
@@ -175,33 +162,28 @@ module.exports = {
|
|||||||
"dependency-cruiser configuration.",
|
"dependency-cruiser configuration.",
|
||||||
from: {},
|
from: {},
|
||||||
to: {
|
to: {
|
||||||
dependencyTypes: [
|
dependencyTypes: ["npm-optional"],
|
||||||
'npm-optional'
|
},
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'peer-deps-used',
|
name: "peer-deps-used",
|
||||||
comment:
|
comment:
|
||||||
"This module depends on an npm package that is declared as a peer dependency " +
|
"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 " +
|
"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 " +
|
"other cases - maybe not so much. If the use of a peer dependency is intentional " +
|
||||||
"add an exception to your dependency-cruiser configuration.",
|
"add an exception to your dependency-cruiser configuration.",
|
||||||
severity: 'warn',
|
severity: "warn",
|
||||||
from: {},
|
from: {},
|
||||||
to: {
|
to: {
|
||||||
dependencyTypes: [
|
dependencyTypes: ["npm-peer"],
|
||||||
'npm-peer'
|
},
|
||||||
]
|
},
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
options: {
|
options: {
|
||||||
|
|
||||||
/* Which modules not to follow further when encountered */
|
/* Which modules not to follow further when encountered */
|
||||||
doNotFollow: {
|
doNotFollow: {
|
||||||
/* path: an array of regular expressions in strings to match against */
|
/* path: an array of regular expressions in strings to match against */
|
||||||
path: ['node_modules']
|
path: ["node_modules"],
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Which modules to exclude */
|
/* Which modules to exclude */
|
||||||
@@ -274,7 +256,7 @@ module.exports = {
|
|||||||
defaults to './tsconfig.json'.
|
defaults to './tsconfig.json'.
|
||||||
*/
|
*/
|
||||||
tsConfig: {
|
tsConfig: {
|
||||||
fileName: 'tsconfig.json'
|
fileName: "tsconfig.json",
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Webpack configuration to use to get resolve options from.
|
/* Webpack configuration to use to get resolve options from.
|
||||||
@@ -364,8 +346,8 @@ module.exports = {
|
|||||||
"bun:wrap",
|
"bun:wrap",
|
||||||
"detect-libc",
|
"detect-libc",
|
||||||
"undici",
|
"undici",
|
||||||
"ws"
|
"ws",
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
reporterOptions: {
|
reporterOptions: {
|
||||||
@@ -375,7 +357,7 @@ module.exports = {
|
|||||||
collapses everything in node_modules to one folder deep so you see
|
collapses everything in node_modules to one folder deep so you see
|
||||||
the external modules, but their innards.
|
the external modules, but their innards.
|
||||||
*/
|
*/
|
||||||
collapsePattern: 'node_modules/(?:@[^/]+/[^/]+|[^/]+)',
|
collapsePattern: "node_modules/(?:@[^/]+/[^/]+|[^/]+)",
|
||||||
|
|
||||||
/* Options to tweak the appearance of your graph.See
|
/* Options to tweak the appearance of your graph.See
|
||||||
https://github.com/sverweij/dependency-cruiser/blob/main/doc/options-reference.md#reporteroptions
|
https://github.com/sverweij/dependency-cruiser/blob/main/doc/options-reference.md#reporteroptions
|
||||||
@@ -397,7 +379,8 @@ module.exports = {
|
|||||||
dependency graph reporter (`archi`) you probably want to tweak
|
dependency graph reporter (`archi`) you probably want to tweak
|
||||||
this collapsePattern to your situation.
|
this collapsePattern to your situation.
|
||||||
*/
|
*/
|
||||||
collapsePattern: '^(?:packages|src|lib(s?)|app(s?)|bin|test(s?)|spec(s?))/[^/]+|node_modules/(?:@[^/]+/[^/]+|[^/]+)',
|
collapsePattern:
|
||||||
|
"^(?:packages|src|lib(s?)|app(s?)|bin|test(s?)|spec(s?))/[^/]+|node_modules/(?:@[^/]+/[^/]+|[^/]+)",
|
||||||
|
|
||||||
/* Options to tweak the appearance of your graph. If you don't specify a
|
/* 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
|
theme for 'archi' dependency-cruiser will use the one specified in the
|
||||||
@@ -405,10 +388,10 @@ module.exports = {
|
|||||||
*/
|
*/
|
||||||
// theme: { },
|
// theme: { },
|
||||||
},
|
},
|
||||||
"text": {
|
text: {
|
||||||
"highlightFocused": true
|
highlightFocused: true,
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
// generated: dependency-cruiser@16.10.0 on 2025-02-16T22:32:01.621Z
|
// generated: dependency-cruiser@16.10.0 on 2025-02-16T22:32:01.621Z
|
||||||
|
|||||||
+9
-6
@@ -12,12 +12,15 @@
|
|||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
// allow importing ts extensions
|
// allow importing ts extensions
|
||||||
"sort-imports": ["error", {
|
"sort-imports": [
|
||||||
"ignoreCase": true,
|
"error",
|
||||||
"ignoreDeclarationSort": true,
|
{
|
||||||
"ignoreMemberSort": false,
|
"ignoreCase": true,
|
||||||
"memberSyntaxSortOrder": ["none", "all", "multiple", "single"]
|
"ignoreDeclarationSort": true,
|
||||||
}],
|
"ignoreMemberSort": false,
|
||||||
|
"memberSyntaxSortOrder": ["none", "all", "multiple", "single"]
|
||||||
|
}
|
||||||
|
],
|
||||||
"import/extensions": [
|
"import/extensions": [
|
||||||
"error",
|
"error",
|
||||||
"ignorePackages",
|
"ignorePackages",
|
||||||
|
|||||||
@@ -3,54 +3,54 @@ description: Report an issue with the modpack in its unmodified state. For other
|
|||||||
labels: bug
|
labels: bug
|
||||||
title: "[BUG]"
|
title: "[BUG]"
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
Before reporting an issue, [please search](https://github.com/BetterSEQTA/BetterSEQTA-Plus/issues) to make sure it has not already been reported (make sure to search closed issues as well!).
|
Before reporting an issue, [please search](https://github.com/BetterSEQTA/BetterSEQTA-Plus/issues) to make sure it has not already been reported (make sure to search closed issues as well!).
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Describe the bug
|
label: Describe the bug
|
||||||
description: Describe your issue. For general issues and questions you'll get a faster answer [from our Discord.](https://discord.gg/YzmbnCDkat)
|
description: Describe your issue. For general issues and questions you'll get a faster answer [from our Discord.](https://discord.gg/YzmbnCDkat)
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
- type: input
|
- type: input
|
||||||
attributes:
|
attributes:
|
||||||
label: Extension version
|
label: Extension version
|
||||||
description: What version of the extension are you using?
|
description: What version of the extension are you using?
|
||||||
placeholder: Find it by opening the config menu and clicking the about icon in the top right.
|
placeholder: Find it by opening the config menu and clicking the about icon in the top right.
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
attributes:
|
attributes:
|
||||||
label: Browser
|
label: Browser
|
||||||
description: Which Browser are you using?
|
description: Which Browser are you using?
|
||||||
options:
|
options:
|
||||||
- Chrome
|
- Chrome
|
||||||
- Firefox
|
- Firefox
|
||||||
- Brave
|
- Brave
|
||||||
- Safari
|
- Safari
|
||||||
- DuckDuckGO
|
- DuckDuckGO
|
||||||
- Microsoft Edge
|
- Microsoft Edge
|
||||||
- Other Chromium-Based Browser
|
- Other Chromium-Based Browser
|
||||||
- Other Non-Chromium-Based Browser
|
- Other Non-Chromium-Based Browser
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
attributes:
|
attributes:
|
||||||
label: Confirm
|
label: Confirm
|
||||||
options:
|
options:
|
||||||
- label: This bug report is about an issue with the extension itself. I have not modified the extension nor added any unsupported plugins. If this is not the case, I know that I should post the issue to the extension's Discord support channel instead.
|
- label: This bug report is about an issue with the extension itself. I have not modified the extension nor added any unsupported plugins. If this is not the case, I know that I should post the issue to the extension's Discord support channel instead.
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Additional context
|
label: Additional context
|
||||||
description: Screenshots, video or any other information. Include photos of the console if possible
|
description: Screenshots, video or any other information. Include photos of the console if possible
|
||||||
placeholder: |
|
placeholder: |
|
||||||
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
|
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
|
|||||||
@@ -3,52 +3,49 @@ description: Suggest a new Feature to be added or replaced in BetterSeqtaPLUS
|
|||||||
labels: enhancement
|
labels: enhancement
|
||||||
title: "[FR]"
|
title: "[FR]"
|
||||||
body:
|
body:
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: Confirm
|
||||||
|
options:
|
||||||
|
- label: "Is this feature request related to a Bug report?"
|
||||||
|
required: false
|
||||||
|
|
||||||
- type: checkboxes
|
- type: input
|
||||||
attributes:
|
attributes:
|
||||||
label: Confirm
|
|
||||||
options:
|
|
||||||
- label: "Is this feature request related to a Bug report?"
|
|
||||||
required: false
|
|
||||||
|
|
||||||
- type: input
|
|
||||||
attributes:
|
|
||||||
label: Bug report link
|
label: Bug report link
|
||||||
description: "If this feature request is related to a bug report, please insert the link to the bug report here"
|
description: "If this feature request is related to a bug report, please insert the link to the bug report here"
|
||||||
placeholder: "https://github.com/BetterSEQTA/BetterSEQTA-Plus/issues/..."
|
placeholder: "https://github.com/BetterSEQTA/BetterSEQTA-Plus/issues/..."
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
|
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
## Feature details
|
## Feature details
|
||||||
Before you request a feature, [please search](https://github.com/BetterSEQTA/BetterSEQTA-Plus/issues) if it has already been requested. (Make sure to check closed issues as well!)
|
Before you request a feature, [please search](https://github.com/BetterSEQTA/BetterSEQTA-Plus/issues) if it has already been requested. (Make sure to check closed issues as well!)
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
attributes:
|
||||||
|
label: Feature type
|
||||||
|
multiple: false
|
||||||
|
options:
|
||||||
|
- Graphical
|
||||||
|
- Functional
|
||||||
|
- Not Sure
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
- type: dropdown
|
- type: input
|
||||||
attributes:
|
attributes:
|
||||||
label: Feature type
|
label: Feature Details
|
||||||
multiple: false
|
description: Please write, with as much detail as possible, what you would like to see from this feature.
|
||||||
options:
|
placeholder: it would be cool if
|
||||||
- Graphical
|
validations:
|
||||||
- Functional
|
required: false
|
||||||
- Not Sure
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
- type: input
|
attributes:
|
||||||
attributes:
|
label: Additional details
|
||||||
label: Feature Details
|
description: Anything else that would help describe your vision (reference images, descriptions, etc)
|
||||||
description: Please write, with as much detail as possible, what you would like to see from this feature.
|
validations:
|
||||||
placeholder: it would be cool if
|
required: false
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Additional details
|
|
||||||
description: Anything else that would help describe your vision (reference images, descriptions, etc)
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
|
|||||||
+19
-19
@@ -2,9 +2,9 @@ name: NodeJS Build
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ "main" ]
|
branches: ["main"]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ "main" ]
|
branches: ["main"]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -15,24 +15,24 @@ jobs:
|
|||||||
node-version: [20.x]
|
node-version: [20.x]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
npm install --legacy-peer-deps
|
npm install --legacy-peer-deps
|
||||||
npm run build
|
npm run build
|
||||||
|
|
||||||
- name: Zip dist folder
|
- name: Zip dist folder
|
||||||
run: |
|
run: |
|
||||||
zip -r dist.zip dist
|
zip -r dist.zip dist
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: dist-zip
|
name: dist-zip
|
||||||
path: dist.zip
|
path: dist.zip
|
||||||
|
|||||||
+11
-11
@@ -17,23 +17,23 @@ diverse, inclusive, and healthy community.
|
|||||||
Examples of behavior that contributes to a positive environment for our
|
Examples of behavior that contributes to a positive environment for our
|
||||||
community include:
|
community include:
|
||||||
|
|
||||||
* Demonstrating empathy and kindness toward other people
|
- Demonstrating empathy and kindness toward other people
|
||||||
* Being respectful of differing opinions, viewpoints, and experiences
|
- Being respectful of differing opinions, viewpoints, and experiences
|
||||||
* Giving and gracefully accepting constructive feedback
|
- Giving and gracefully accepting constructive feedback
|
||||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
- Accepting responsibility and apologizing to those affected by our mistakes,
|
||||||
and learning from the experience
|
and learning from the experience
|
||||||
* Focusing on what is best not just for us as individuals, but for the
|
- Focusing on what is best not just for us as individuals, but for the
|
||||||
overall community
|
overall community
|
||||||
|
|
||||||
Examples of unacceptable behavior include:
|
Examples of unacceptable behavior include:
|
||||||
|
|
||||||
* The use of sexualized language or imagery, and sexual attention or
|
- The use of sexualized language or imagery, and sexual attention or
|
||||||
advances of any kind
|
advances of any kind
|
||||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
- Trolling, insulting or derogatory comments, and personal or political attacks
|
||||||
* Public or private harassment
|
- Public or private harassment
|
||||||
* Publishing others' private information, such as a physical or email
|
- Publishing others' private information, such as a physical or email
|
||||||
address, without their explicit permission
|
address, without their explicit permission
|
||||||
* Other conduct which could reasonably be considered inappropriate in a
|
- Other conduct which could reasonably be considered inappropriate in a
|
||||||
professional setting
|
professional setting
|
||||||
|
|
||||||
## Enforcement Responsibilities
|
## Enforcement Responsibilities
|
||||||
@@ -106,7 +106,7 @@ Violating these terms may lead to a permanent ban.
|
|||||||
### 4. Permanent Ban
|
### 4. Permanent Ban
|
||||||
|
|
||||||
**Community Impact**: Demonstrating a pattern of violation of community
|
**Community Impact**: Demonstrating a pattern of violation of community
|
||||||
standards, including sustained inappropriate behavior, harassment of an
|
standards, including sustained inappropriate behavior, harassment of an
|
||||||
individual, or aggression toward or disparagement of classes of individuals.
|
individual, or aggression toward or disparagement of classes of individuals.
|
||||||
|
|
||||||
**Consequence**: A permanent ban from any sort of public interaction within
|
**Consequence**: A permanent ban from any sort of public interaction within
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
#
|
#
|
||||||
|
|
||||||
<a href="https://chromewebstore.google.com/detail/betterseqta+/afdgaoaclhkhemfkkkonemoapeinchel">
|
<a href="https://chromewebstore.google.com/detail/betterseqta+/afdgaoaclhkhemfkkkonemoapeinchel">
|
||||||
@@ -65,8 +64,6 @@ Don't worry- if you get stuck feel free to ask around in the [discord](https://d
|
|||||||
git clone https://github.com/BetterSEQTA/BetterSEQTA-Plus
|
git clone https://github.com/BetterSEQTA/BetterSEQTA-Plus
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
1. Install dependencies
|
1. Install dependencies
|
||||||
|
|
||||||
You may install the dependencies like below:
|
You may install the dependencies like below:
|
||||||
@@ -80,15 +77,15 @@ But it is recommended to do it like this:
|
|||||||
```
|
```
|
||||||
npm install --legacy-peer-deps # Only NPM supported
|
npm install --legacy-peer-deps # Only NPM supported
|
||||||
```
|
```
|
||||||
|
|
||||||
### Running Development
|
### Running Development
|
||||||
|
|
||||||
2. Run the dev script (it updates as you save files)
|
2. Run the dev script (it updates as you save files)
|
||||||
|
|
||||||
```
|
```
|
||||||
npm run dev # or use your perferred package manager
|
npm run dev # or use your perferred package manager
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Building for production
|
### Building for production
|
||||||
|
|
||||||
2. Run the build script
|
2. Run the build script
|
||||||
@@ -102,6 +99,7 @@ npm run build # or use your perferred package manager
|
|||||||
```
|
```
|
||||||
npm run zip # This REQUIRES 7-Zip to be installed in order to work. You can also use your perferred package manager
|
npm run zip # This REQUIRES 7-Zip to be installed in order to work. You can also use your perferred package manager
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Load the extension into chrome
|
3. Load the extension into chrome
|
||||||
|
|
||||||
- Go to `chrome://extensions`
|
- Go to `chrome://extensions`
|
||||||
@@ -130,6 +128,7 @@ The folder structure is as follows:
|
|||||||
</a>
|
</a>
|
||||||
|
|
||||||
Want to contribute? [Click Here!](https://github.com/BetterSEQTA/BetterSEQTA-Plus/blob/main/CONTRIBUTING.md)
|
Want to contribute? [Click Here!](https://github.com/BetterSEQTA/BetterSEQTA-Plus/blob/main/CONTRIBUTING.md)
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
This extension was initially developed by [Nulkem](https://github.com/Nulkem/betterseqta), was ported to manifest V3 by [MEGA-Dawg68](https://github.com/MEGA-Dawg68) and is currently under active development by [SethBurkart123](https://github.com/SethBurkart123) and [Crazypersonalph](https://github.com/Crazypersonalph)
|
This extension was initially developed by [Nulkem](https://github.com/Nulkem/betterseqta), was ported to manifest V3 by [MEGA-Dawg68](https://github.com/MEGA-Dawg68) and is currently under active development by [SethBurkart123](https://github.com/SethBurkart123) and [Crazypersonalph](https://github.com/Crazypersonalph)
|
||||||
|
|||||||
+5
-4
@@ -4,12 +4,13 @@
|
|||||||
|
|
||||||
Below here is the supported versions of BetterSEQTA+. Anything older than this is not supported and contains bugs.
|
Below here is the supported versions of BetterSEQTA+. Anything older than this is not supported and contains bugs.
|
||||||
|
|
||||||
| Version | Supported |
|
| Version | Supported |
|
||||||
| ------- | ------------------ |
|
| ------- | --------- |
|
||||||
| 3.4.3 | ✅ |
|
| 3.4.3 | ✅ |
|
||||||
| < 3.4.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. open the [advisories tab](https://github.com/BetterSEQTA/BetterSEQTA-Plus/security/advisories) on the left and click the green "report a vulnerability" button or use [this quick-link](https://github.com/BetterSEQTA/BetterSEQTA-Plus/security/advisories/new) to create a new report
|
If you find vulnerabilities, REPORT IT IMMEDIATELY. open the [advisories tab](https://github.com/BetterSEQTA/BetterSEQTA-Plus/security/advisories) on the left and click the green "report a vulnerability" button or use [this quick-link](https://github.com/BetterSEQTA/BetterSEQTA-Plus/security/advisories/new) to create a new report
|
||||||
|
|||||||
@@ -7,11 +7,13 @@ Welcome to the BetterSEQTA+ documentation! This documentation will help you unde
|
|||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
### Getting Started
|
### Getting Started
|
||||||
|
|
||||||
- [Project Overview](./README.md) - This file
|
- [Project Overview](./README.md) - This file
|
||||||
- [Installation Guide](./installation.md) - How to install and set up BetterSEQTA+
|
- [Installation Guide](./installation.md) - How to install and set up BetterSEQTA+
|
||||||
- [Contributing Guide](../CONTRIBUTING.md) - How to contribute to BetterSEQTA+
|
- [Contributing Guide](../CONTRIBUTING.md) - How to contribute to BetterSEQTA+
|
||||||
|
|
||||||
### Plugin System
|
### Plugin System
|
||||||
|
|
||||||
- [Creating Your First Plugin](./plugins/README.md) - A comprehensive, beginner-friendly guide to creating plugins
|
- [Creating Your First Plugin](./plugins/README.md) - A comprehensive, beginner-friendly guide to creating plugins
|
||||||
- [Plugin API Reference](./plugins/api-reference.md) - Detailed technical documentation of the plugin APIs
|
- [Plugin API Reference](./plugins/api-reference.md) - Detailed technical documentation of the plugin APIs
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ Thank you for your interest in contributing to BetterSEQTA+! This document provi
|
|||||||
BetterSEQTA+ is committed to providing a welcoming and inclusive environment for all contributors. We expect all participants to adhere to our Code of Conduct, which promotes respectful and harassment-free interaction.
|
BetterSEQTA+ is committed to providing a welcoming and inclusive environment for all contributors. We expect all participants to adhere to our Code of Conduct, which promotes respectful and harassment-free interaction.
|
||||||
|
|
||||||
Key points:
|
Key points:
|
||||||
|
|
||||||
- Be respectful and inclusive
|
- Be respectful and inclusive
|
||||||
- Focus on what is best for the community
|
- Focus on what is best for the community
|
||||||
- Show empathy towards other community members
|
- Show empathy towards other community members
|
||||||
@@ -105,6 +106,7 @@ git checkout -b feature/my-new-feature
|
|||||||
2. **Write Clear Commit Messages**
|
2. **Write Clear Commit Messages**
|
||||||
|
|
||||||
Follow the conventional commits format:
|
Follow the conventional commits format:
|
||||||
|
|
||||||
```
|
```
|
||||||
feat: add new feature
|
feat: add new feature
|
||||||
fix: resolve bug with timetable
|
fix: resolve bug with timetable
|
||||||
@@ -118,6 +120,7 @@ git checkout -b feature/my-new-feature
|
|||||||
4. **Run Tests**
|
4. **Run Tests**
|
||||||
|
|
||||||
Make sure all tests pass before submitting your PR:
|
Make sure all tests pass before submitting your PR:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm test
|
npm test
|
||||||
```
|
```
|
||||||
@@ -157,6 +160,7 @@ We follow TypeScript best practices and have a consistent code style:
|
|||||||
5. **Use Linters**
|
5. **Use Linters**
|
||||||
|
|
||||||
We use ESLint and Prettier. Run them before submitting your PR:
|
We use ESLint and Prettier. Run them before submitting your PR:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run lint
|
npm run lint
|
||||||
npm run format
|
npm run format
|
||||||
@@ -173,6 +177,7 @@ If you find a bug, please report it by creating an issue on GitHub:
|
|||||||
2. **Use the Bug Report Template**
|
2. **Use the Bug Report Template**
|
||||||
|
|
||||||
Fill in all sections of the bug report template:
|
Fill in all sections of the bug report template:
|
||||||
|
|
||||||
- Description
|
- Description
|
||||||
- Steps to reproduce
|
- Steps to reproduce
|
||||||
- Expected behavior
|
- Expected behavior
|
||||||
@@ -195,6 +200,7 @@ We welcome feature suggestions! To suggest a new feature:
|
|||||||
2. **Use the Feature Request Template**
|
2. **Use the Feature Request Template**
|
||||||
|
|
||||||
Fill in all sections of the feature request template:
|
Fill in all sections of the feature request template:
|
||||||
|
|
||||||
- Description
|
- Description
|
||||||
- Use case
|
- Use case
|
||||||
- Potential implementation
|
- Potential implementation
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ bun install
|
|||||||
#### Extension not appearing in SEQTA
|
#### Extension not appearing in SEQTA
|
||||||
|
|
||||||
Make sure:
|
Make sure:
|
||||||
|
|
||||||
- You're visiting a SEQTA Learn page
|
- You're visiting a SEQTA Learn page
|
||||||
- The extension is enabled
|
- The extension is enabled
|
||||||
- You've refreshed the page after installing the extension
|
- You've refreshed the page after installing the extension
|
||||||
@@ -139,6 +140,7 @@ Make sure:
|
|||||||
#### Development build not updating
|
#### Development build not updating
|
||||||
|
|
||||||
Try:
|
Try:
|
||||||
|
|
||||||
1. Stopping the development server
|
1. Stopping the development server
|
||||||
2. Clearing your browser cache
|
2. Clearing your browser cache
|
||||||
3. Removing the extension from your browser
|
3. Removing the extension from your browser
|
||||||
|
|||||||
+41
-31
@@ -5,6 +5,7 @@ Hey there! 👋 So you want to create a plugin for BetterSEQTA+? That's awesome!
|
|||||||
## What is a Plugin?
|
## What is a Plugin?
|
||||||
|
|
||||||
In BetterSEQTA+, a plugin is like a mini-app that adds new features to SEQTA. Think of it as a piece of LEGO that you can snap onto SEQTA to make it do new things. For example, you could create a plugin that:
|
In BetterSEQTA+, a plugin is like a mini-app that adds new features to SEQTA. Think of it as a piece of LEGO that you can snap onto SEQTA to make it do new things. For example, you could create a plugin that:
|
||||||
|
|
||||||
- Changes how SEQTA looks
|
- Changes how SEQTA looks
|
||||||
- Adds new buttons or features
|
- Adds new buttons or features
|
||||||
- Shows extra information on your timetable
|
- Shows extra information on your timetable
|
||||||
@@ -16,14 +17,14 @@ In BetterSEQTA+, a plugin is like a mini-app that adds new features to SEQTA. Th
|
|||||||
Let's create a super simple plugin together. We'll make one that adds a friendly message to the SEQTA homepage. Here's what we'll need:
|
Let's create a super simple plugin together. We'll make one that adds a friendly message to the SEQTA homepage. Here's what we'll need:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import type { Plugin } from '@/plugins/core/types';
|
import type { Plugin } from "@/plugins/core/types";
|
||||||
|
|
||||||
const myFirstPlugin: Plugin = {
|
const myFirstPlugin: Plugin = {
|
||||||
// Every plugin needs these basic details
|
// Every plugin needs these basic details
|
||||||
id: 'my-first-plugin',
|
id: "my-first-plugin",
|
||||||
name: 'My First Plugin',
|
name: "My First Plugin",
|
||||||
description: 'Adds a friendly message to SEQTA',
|
description: "Adds a friendly message to SEQTA",
|
||||||
version: '1.0.0',
|
version: "1.0.0",
|
||||||
|
|
||||||
// This tells BetterSEQTA+ that users can turn our plugin on/off
|
// This tells BetterSEQTA+ that users can turn our plugin on/off
|
||||||
disableToggle: true,
|
disableToggle: true,
|
||||||
@@ -31,14 +32,14 @@ const myFirstPlugin: Plugin = {
|
|||||||
// This is where the magic happens!
|
// This is where the magic happens!
|
||||||
run: async (api) => {
|
run: async (api) => {
|
||||||
// Wait for the homepage to load
|
// Wait for the homepage to load
|
||||||
api.seqta.onMount('.home-page', (homePage) => {
|
api.seqta.onMount(".home-page", (homePage) => {
|
||||||
// Create our message
|
// Create our message
|
||||||
const message = document.createElement('div');
|
const message = document.createElement("div");
|
||||||
message.textContent = 'Hello from my first plugin! 🎉';
|
message.textContent = "Hello from my first plugin! 🎉";
|
||||||
message.style.padding = '20px';
|
message.style.padding = "20px";
|
||||||
message.style.backgroundColor = '#e9f5ff';
|
message.style.backgroundColor = "#e9f5ff";
|
||||||
message.style.borderRadius = '8px';
|
message.style.borderRadius = "8px";
|
||||||
message.style.margin = '20px';
|
message.style.margin = "20px";
|
||||||
|
|
||||||
// Add it to the page
|
// Add it to the page
|
||||||
homePage.prepend(message);
|
homePage.prepend(message);
|
||||||
@@ -46,10 +47,10 @@ const myFirstPlugin: Plugin = {
|
|||||||
|
|
||||||
// Return a cleanup function that removes our message when the plugin is disabled
|
// Return a cleanup function that removes our message when the plugin is disabled
|
||||||
return () => {
|
return () => {
|
||||||
const message = document.querySelector('.home-page > div');
|
const message = document.querySelector(".home-page > div");
|
||||||
message?.remove();
|
message?.remove();
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default myFirstPlugin;
|
export default myFirstPlugin;
|
||||||
@@ -79,13 +80,13 @@ This helps you interact with SEQTA's pages:
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// Wait for an element to appear on the page
|
// Wait for an element to appear on the page
|
||||||
api.seqta.onMount('.some-class', (element) => {
|
api.seqta.onMount(".some-class", (element) => {
|
||||||
// Do something with the element
|
// Do something with the element
|
||||||
});
|
});
|
||||||
|
|
||||||
// Know when the user changes pages
|
// Know when the user changes pages
|
||||||
api.seqta.onPageChange((page) => {
|
api.seqta.onPageChange((page) => {
|
||||||
console.log('User went to:', page);
|
console.log("User went to:", page);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get the current page
|
// Get the current page
|
||||||
@@ -97,8 +98,12 @@ const currentPage = api.seqta.getCurrentPage();
|
|||||||
Want to let users customize your plugin? Use settings!
|
Want to let users customize your plugin? Use settings!
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { BasePlugin } from '@/plugins/core/settings';
|
import { BasePlugin } from "@/plugins/core/settings";
|
||||||
import { booleanSetting, defineSettings, Setting } from '@/plugins/core/settingsHelpers';
|
import {
|
||||||
|
booleanSetting,
|
||||||
|
defineSettings,
|
||||||
|
Setting,
|
||||||
|
} from "@/plugins/core/settingsHelpers";
|
||||||
|
|
||||||
// Define your settings
|
// Define your settings
|
||||||
const settings = defineSettings({
|
const settings = defineSettings({
|
||||||
@@ -106,7 +111,7 @@ const settings = defineSettings({
|
|||||||
default: true,
|
default: true,
|
||||||
title: "Show Welcome Message",
|
title: "Show Welcome Message",
|
||||||
description: "Show a friendly message on the homepage",
|
description: "Show a friendly message on the homepage",
|
||||||
})
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create a class for your plugin
|
// Create a class for your plugin
|
||||||
@@ -129,14 +134,14 @@ const myPlugin: Plugin<typeof settings> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Listen for setting changes
|
// Listen for setting changes
|
||||||
api.settings.onChange('showMessage', (newValue) => {
|
api.settings.onChange("showMessage", (newValue) => {
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
// Show the message
|
// Show the message
|
||||||
} else {
|
} else {
|
||||||
// Hide the message
|
// Hide the message
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -146,14 +151,14 @@ Need to save some data? The storage API has got you covered:
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// Save some data
|
// Save some data
|
||||||
await api.storage.set('lastVisit', new Date().toISOString());
|
await api.storage.set("lastVisit", new Date().toISOString());
|
||||||
|
|
||||||
// Get it back later
|
// Get it back later
|
||||||
const lastVisit = await api.storage.get('lastVisit');
|
const lastVisit = await api.storage.get("lastVisit");
|
||||||
|
|
||||||
// Listen for changes
|
// Listen for changes
|
||||||
api.storage.onChange('lastVisit', (newValue) => {
|
api.storage.onChange("lastVisit", (newValue) => {
|
||||||
console.log('Last visit updated:', newValue);
|
console.log("Last visit updated:", newValue);
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -163,12 +168,12 @@ Want your plugin to be able to interface with other plugins? Then use events!
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// Listen for an event
|
// Listen for an event
|
||||||
api.events.on('myCustomEvent', (data) => {
|
api.events.on("myCustomEvent", (data) => {
|
||||||
console.log('Got event:', data);
|
console.log("Got event:", data);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Send an event
|
// Send an event
|
||||||
api.events.emit('myCustomEvent', { some: 'data' });
|
api.events.emit("myCustomEvent", { some: "data" });
|
||||||
```
|
```
|
||||||
|
|
||||||
## Adding Styles
|
## Adding Styles
|
||||||
@@ -199,7 +204,7 @@ const myPlugin: Plugin = {
|
|||||||
|
|
||||||
run: async (api) => {
|
run: async (api) => {
|
||||||
// Your plugin code here
|
// Your plugin code here
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -208,28 +213,31 @@ const myPlugin: Plugin = {
|
|||||||
Here are some tips to make your plugin awesome:
|
Here are some tips to make your plugin awesome:
|
||||||
|
|
||||||
1. **Always Clean Up**: When your plugin is disabled, clean up any changes you made:
|
1. **Always Clean Up**: When your plugin is disabled, clean up any changes you made:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
run: async (api) => {
|
run: async (api) => {
|
||||||
// Add stuff to the page
|
// Add stuff to the page
|
||||||
const element = document.createElement('div');
|
const element = document.createElement("div");
|
||||||
document.body.appendChild(element);
|
document.body.appendChild(element);
|
||||||
|
|
||||||
// Return a cleanup function
|
// Return a cleanup function
|
||||||
return () => {
|
return () => {
|
||||||
element.remove();
|
element.remove();
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Use TypeScript**: It helps catch errors before they happen and makes your code easier to understand.
|
2. **Use TypeScript**: It helps catch errors before they happen and makes your code easier to understand.
|
||||||
|
|
||||||
3. **Test Your Plugin**: Make sure it works in different situations:
|
3. **Test Your Plugin**: Make sure it works in different situations:
|
||||||
|
|
||||||
- When SEQTA is loading
|
- When SEQTA is loading
|
||||||
- When the user switches pages
|
- When the user switches pages
|
||||||
- When the plugin is enabled/disabled
|
- When the plugin is enabled/disabled
|
||||||
- When settings are changed
|
- When settings are changed
|
||||||
|
|
||||||
4. **Keep It Fast**: Don't slow down SEQTA:
|
4. **Keep It Fast**: Don't slow down SEQTA:
|
||||||
|
|
||||||
- Use `onMount` instead of intervals or timeouts
|
- Use `onMount` instead of intervals or timeouts
|
||||||
- Clean up event listeners when they're not needed
|
- Clean up event listeners when they're not needed
|
||||||
- Don't do heavy calculations on the main thread
|
- Don't do heavy calculations on the main thread
|
||||||
@@ -242,6 +250,7 @@ Here are some tips to make your plugin awesome:
|
|||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
Want to see more examples? Check out our built-in plugins:
|
Want to see more examples? Check out our built-in plugins:
|
||||||
|
|
||||||
- [themes](../../src/plugins/built-in/themes/index.ts): Shows how to change SEQTA's appearance
|
- [themes](../../src/plugins/built-in/themes/index.ts): Shows how to change SEQTA's appearance
|
||||||
- [notificationCollector](../../src/plugins/built-in/notificationCollector/index.ts): Shows how to work with SEQTA's notifications
|
- [notificationCollector](../../src/plugins/built-in/notificationCollector/index.ts): Shows how to work with SEQTA's notifications
|
||||||
- [timetable](../../src/plugins/built-in/timetable/index.ts): Shows how to modify SEQTA's timetable view
|
- [timetable](../../src/plugins/built-in/timetable/index.ts): Shows how to modify SEQTA's timetable view
|
||||||
@@ -250,6 +259,7 @@ Want to see more examples? Check out our built-in plugins:
|
|||||||
## Need Help?
|
## Need Help?
|
||||||
|
|
||||||
Got stuck? No worries! Here's where you can get help:
|
Got stuck? No worries! Here's where you can get help:
|
||||||
|
|
||||||
- Join our [Discord server](https://discord.gg/YzmbnCDkat)
|
- Join our [Discord server](https://discord.gg/YzmbnCDkat)
|
||||||
- Check out the built-in plugins in the `src/plugins/built-in` folder
|
- Check out the built-in plugins in the `src/plugins/built-in` folder
|
||||||
- Open an issue on our [GitHub page](https://github.com/betterseqta/betterseqta-plus/issues)
|
- Open an issue on our [GitHub page](https://github.com/betterseqta/betterseqta-plus/issues)
|
||||||
|
|||||||
@@ -7,9 +7,13 @@ This document provides detailed technical information about BetterSEQTA+'s plugi
|
|||||||
Here's how a plugin is structured:
|
Here's how a plugin is structured:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import type { Plugin } from '@/plugins/core/types';
|
import type { Plugin } from "@/plugins/core/types";
|
||||||
import { BasePlugin } from '@/plugins/core/settings';
|
import { BasePlugin } from "@/plugins/core/settings";
|
||||||
import { booleanSetting, defineSettings, Setting } from '@/plugins/core/settingsHelpers';
|
import {
|
||||||
|
booleanSetting,
|
||||||
|
defineSettings,
|
||||||
|
Setting,
|
||||||
|
} from "@/plugins/core/settingsHelpers";
|
||||||
|
|
||||||
// First, define your settings
|
// First, define your settings
|
||||||
const settings = defineSettings({
|
const settings = defineSettings({
|
||||||
@@ -17,7 +21,7 @@ const settings = defineSettings({
|
|||||||
default: true,
|
default: true,
|
||||||
title: "Enable Feature",
|
title: "Enable Feature",
|
||||||
description: "Turn this feature on or off",
|
description: "Turn this feature on or off",
|
||||||
})
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create a class to handle your settings
|
// Create a class to handle your settings
|
||||||
@@ -31,28 +35,28 @@ const settingsInstance = new MyPluginClass();
|
|||||||
|
|
||||||
// Create your plugin
|
// Create your plugin
|
||||||
const myPlugin: Plugin<typeof settings> = {
|
const myPlugin: Plugin<typeof settings> = {
|
||||||
id: 'my-plugin',
|
id: "my-plugin",
|
||||||
name: 'My Plugin',
|
name: "My Plugin",
|
||||||
description: 'A cool plugin that does things',
|
description: "A cool plugin that does things",
|
||||||
version: '1.0.0',
|
version: "1.0.0",
|
||||||
settings: settingsInstance.settings,
|
settings: settingsInstance.settings,
|
||||||
disableToggle: true,
|
disableToggle: true,
|
||||||
|
|
||||||
run: async (api) => {
|
run: async (api) => {
|
||||||
console.log('Plugin is running!');
|
console.log("Plugin is running!");
|
||||||
|
|
||||||
// Do stuff when settings change
|
// Do stuff when settings change
|
||||||
api.settings.onChange('enabled', (enabled) => {
|
api.settings.onChange("enabled", (enabled) => {
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
console.log('Feature enabled!');
|
console.log("Feature enabled!");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Return a cleanup function
|
// Return a cleanup function
|
||||||
return () => {
|
return () => {
|
||||||
console.log('Plugin cleanup');
|
console.log("Plugin cleanup");
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default myPlugin;
|
export default myPlugin;
|
||||||
@@ -63,27 +67,30 @@ export default myPlugin;
|
|||||||
The SEQTA API helps you interact with SEQTA's pages:
|
The SEQTA API helps you interact with SEQTA's pages:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import type { Plugin } from '@/plugins/core/types';
|
import type { Plugin } from "@/plugins/core/types";
|
||||||
|
|
||||||
const seqtaPlugin: Plugin<typeof settings> = {
|
const seqtaPlugin: Plugin<typeof settings> = {
|
||||||
id: 'seqta-example',
|
id: "seqta-example",
|
||||||
name: 'SEQTA Example',
|
name: "SEQTA Example",
|
||||||
description: 'Shows how to use the SEQTA API',
|
description: "Shows how to use the SEQTA API",
|
||||||
version: '1.0.0',
|
version: "1.0.0",
|
||||||
settings: {},
|
settings: {},
|
||||||
disableToggle: true,
|
disableToggle: true,
|
||||||
|
|
||||||
run: async (api) => {
|
run: async (api) => {
|
||||||
// Wait for elements to appear
|
// Wait for elements to appear
|
||||||
const { unregister: timetableUnregister } = api.seqta.onMount('.timetable', (timetable) => {
|
const { unregister: timetableUnregister } = api.seqta.onMount(
|
||||||
const button = document.createElement('button');
|
".timetable",
|
||||||
button.textContent = 'Export';
|
(timetable) => {
|
||||||
timetable.appendChild(button);
|
const button = document.createElement("button");
|
||||||
});
|
button.textContent = "Export";
|
||||||
|
timetable.appendChild(button);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// Track page changes
|
// Track page changes
|
||||||
const { unregister: pageUnregister } = api.seqta.onPageChange((page) => {
|
const { unregister: pageUnregister } = api.seqta.onPageChange((page) => {
|
||||||
console.log('User went to:', page);
|
console.log("User went to:", page);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Clean up when disabled
|
// Clean up when disabled
|
||||||
@@ -91,7 +98,7 @@ const seqtaPlugin: Plugin<typeof settings> = {
|
|||||||
timetableUnregister();
|
timetableUnregister();
|
||||||
pageUnregister();
|
pageUnregister();
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default seqtaPlugin;
|
export default seqtaPlugin;
|
||||||
@@ -102,22 +109,29 @@ export default seqtaPlugin;
|
|||||||
Here's how to add settings to your plugin:
|
Here's how to add settings to your plugin:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import type { Plugin } from '@/plugins/core/types';
|
import type { Plugin } from "@/plugins/core/types";
|
||||||
import { BasePlugin } from '@/plugins/core/settings';
|
import { BasePlugin } from "@/plugins/core/settings";
|
||||||
import { booleanSetting, stringSetting, numberSetting, selectSetting, defineSettings, Setting } from '@/plugins/core/settingsHelpers';
|
import {
|
||||||
|
booleanSetting,
|
||||||
|
stringSetting,
|
||||||
|
numberSetting,
|
||||||
|
selectSetting,
|
||||||
|
defineSettings,
|
||||||
|
Setting,
|
||||||
|
} from "@/plugins/core/settingsHelpers";
|
||||||
|
|
||||||
// Define your settings
|
// Define your settings
|
||||||
const settings = defineSettings({
|
const settings = defineSettings({
|
||||||
darkMode: booleanSetting({
|
darkMode: booleanSetting({
|
||||||
default: false,
|
default: false,
|
||||||
title: "Dark Mode",
|
title: "Dark Mode",
|
||||||
description: "Enable dark mode"
|
description: "Enable dark mode",
|
||||||
}),
|
}),
|
||||||
userName: stringSetting({
|
userName: stringSetting({
|
||||||
default: "",
|
default: "",
|
||||||
title: "User Name",
|
title: "User Name",
|
||||||
description: "Your display name",
|
description: "Your display name",
|
||||||
placeholder: "Enter your name..."
|
placeholder: "Enter your name...",
|
||||||
}),
|
}),
|
||||||
theme: selectSetting({
|
theme: selectSetting({
|
||||||
default: "light",
|
default: "light",
|
||||||
@@ -125,9 +139,9 @@ const settings = defineSettings({
|
|||||||
description: "Choose your theme",
|
description: "Choose your theme",
|
||||||
options: [
|
options: [
|
||||||
{ value: "light", label: "Light" },
|
{ value: "light", label: "Light" },
|
||||||
{ value: "dark", label: "Dark" }
|
{ value: "dark", label: "Dark" },
|
||||||
]
|
],
|
||||||
})
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create your settings class
|
// Create your settings class
|
||||||
@@ -144,29 +158,29 @@ class ThemePluginClass extends BasePlugin<typeof settings> {
|
|||||||
|
|
||||||
// Create the plugin
|
// Create the plugin
|
||||||
const themePlugin: Plugin<typeof settings> = {
|
const themePlugin: Plugin<typeof settings> = {
|
||||||
id: 'theme-example',
|
id: "theme-example",
|
||||||
name: 'Theme Example',
|
name: "Theme Example",
|
||||||
description: 'Shows how to use settings',
|
description: "Shows how to use settings",
|
||||||
version: '1.0.0',
|
version: "1.0.0",
|
||||||
settings: new ThemePluginClass().settings,
|
settings: new ThemePluginClass().settings,
|
||||||
disableToggle: true,
|
disableToggle: true,
|
||||||
|
|
||||||
run: async (api) => {
|
run: async (api) => {
|
||||||
// Apply initial settings
|
// Apply initial settings
|
||||||
if (api.settings.darkMode) {
|
if (api.settings.darkMode) {
|
||||||
document.body.classList.add('dark');
|
document.body.classList.add("dark");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listen for changes
|
// Listen for changes
|
||||||
const { unregister } = api.settings.onChange('darkMode', (enabled) => {
|
const { unregister } = api.settings.onChange("darkMode", (enabled) => {
|
||||||
document.body.classList.toggle('dark', enabled);
|
document.body.classList.toggle("dark", enabled);
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unregister();
|
unregister();
|
||||||
document.body.classList.remove('dark');
|
document.body.classList.remove("dark");
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default themePlugin;
|
export default themePlugin;
|
||||||
@@ -177,13 +191,13 @@ export default themePlugin;
|
|||||||
Here's how to use storage in your plugin:
|
Here's how to use storage in your plugin:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import type { Plugin } from '@/plugins/core/types';
|
import type { Plugin } from "@/plugins/core/types";
|
||||||
|
|
||||||
const storagePlugin: Plugin<typeof settings> = {
|
const storagePlugin: Plugin<typeof settings> = {
|
||||||
id: 'storage-example',
|
id: "storage-example",
|
||||||
name: 'Storage Example',
|
name: "Storage Example",
|
||||||
description: 'Shows how to use storage',
|
description: "Shows how to use storage",
|
||||||
version: '1.0.0',
|
version: "1.0.0",
|
||||||
settings: {},
|
settings: {},
|
||||||
disableToggle: true,
|
disableToggle: true,
|
||||||
|
|
||||||
@@ -192,21 +206,21 @@ const storagePlugin: Plugin<typeof settings> = {
|
|||||||
await api.storage.loaded;
|
await api.storage.loaded;
|
||||||
|
|
||||||
// Save some data
|
// Save some data
|
||||||
await api.storage.set('lastVisit', new Date().toISOString());
|
await api.storage.set("lastVisit", new Date().toISOString());
|
||||||
|
|
||||||
// Get saved data
|
// Get saved data
|
||||||
const lastVisit = await api.storage.get('lastVisit');
|
const lastVisit = await api.storage.get("lastVisit");
|
||||||
console.log('Last visit:', lastVisit);
|
console.log("Last visit:", lastVisit);
|
||||||
|
|
||||||
// Listen for changes
|
// Listen for changes
|
||||||
const { unregister } = api.storage.onChange('lastVisit', (newValue) => {
|
const { unregister } = api.storage.onChange("lastVisit", (newValue) => {
|
||||||
console.log('Last visit updated:', newValue);
|
console.log("Last visit updated:", newValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unregister();
|
unregister();
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default storagePlugin;
|
export default storagePlugin;
|
||||||
@@ -217,33 +231,39 @@ export default storagePlugin;
|
|||||||
Here's how to use events in your plugin:
|
Here's how to use events in your plugin:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import type { Plugin } from '@/plugins/core/types';
|
import type { Plugin } from "@/plugins/core/types";
|
||||||
|
|
||||||
const eventsPlugin: Plugin<typeof settings> = {
|
const eventsPlugin: Plugin<typeof settings> = {
|
||||||
id: 'events-example',
|
id: "events-example",
|
||||||
name: 'Events Example',
|
name: "Events Example",
|
||||||
description: 'Shows how to use events',
|
description: "Shows how to use events",
|
||||||
version: '1.0.0',
|
version: "1.0.0",
|
||||||
settings: {},
|
settings: {},
|
||||||
disableToggle: true,
|
disableToggle: true,
|
||||||
|
|
||||||
run: async (api) => {
|
run: async (api) => {
|
||||||
// Listen for theme changes
|
// Listen for theme changes
|
||||||
const { unregister: themeListener } = api.events.on('theme.changed', (theme) => {
|
const { unregister: themeListener } = api.events.on(
|
||||||
console.log('Theme changed to:', theme);
|
"theme.changed",
|
||||||
});
|
(theme) => {
|
||||||
|
console.log("Theme changed to:", theme);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// Listen for notifications
|
// Listen for notifications
|
||||||
const { unregister: notifyListener } = api.events.on('notification.new', (notification) => {
|
const { unregister: notifyListener } = api.events.on(
|
||||||
console.log('New notification:', notification);
|
"notification.new",
|
||||||
});
|
(notification) => {
|
||||||
|
console.log("New notification:", notification);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// Clean up listeners
|
// Clean up listeners
|
||||||
return () => {
|
return () => {
|
||||||
themeListener();
|
themeListener();
|
||||||
notifyListener();
|
notifyListener();
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default eventsPlugin;
|
export default eventsPlugin;
|
||||||
@@ -254,20 +274,20 @@ export default eventsPlugin;
|
|||||||
Here's how to write efficient plugins:
|
Here's how to write efficient plugins:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import type { Plugin } from '@/plugins/core/types';
|
import type { Plugin } from "@/plugins/core/types";
|
||||||
|
|
||||||
const efficientPlugin: Plugin<typeof settings> = {
|
const efficientPlugin: Plugin<typeof settings> = {
|
||||||
id: 'efficient-example',
|
id: "efficient-example",
|
||||||
name: 'Efficient Example',
|
name: "Efficient Example",
|
||||||
description: 'Shows performance best practices',
|
description: "Shows performance best practices",
|
||||||
version: '1.0.0',
|
version: "1.0.0",
|
||||||
settings: {},
|
settings: {},
|
||||||
disableToggle: true,
|
disableToggle: true,
|
||||||
|
|
||||||
run: async (api) => {
|
run: async (api) => {
|
||||||
// ✅ Good: Use onMount
|
// ✅ Good: Use onMount
|
||||||
const { unregister } = api.seqta.onMount('.timetable', (el) => {
|
const { unregister } = api.seqta.onMount(".timetable", (el) => {
|
||||||
el.classList.add('enhanced');
|
el.classList.add("enhanced");
|
||||||
});
|
});
|
||||||
|
|
||||||
// ❌ Bad: Don't use intervals
|
// ❌ Bad: Don't use intervals
|
||||||
@@ -277,7 +297,7 @@ const efficientPlugin: Plugin<typeof settings> = {
|
|||||||
// }, 100);
|
// }, 100);
|
||||||
|
|
||||||
// ✅ Good: Cache DOM elements
|
// ✅ Good: Cache DOM elements
|
||||||
const header = document.querySelector('.header');
|
const header = document.querySelector(".header");
|
||||||
if (header) {
|
if (header) {
|
||||||
// Reuse header instead of querying again
|
// Reuse header instead of querying again
|
||||||
}
|
}
|
||||||
@@ -285,7 +305,7 @@ const efficientPlugin: Plugin<typeof settings> = {
|
|||||||
// ✅ Good: Batch DOM updates
|
// ✅ Good: Batch DOM updates
|
||||||
const fragment = document.createDocumentFragment();
|
const fragment = document.createDocumentFragment();
|
||||||
for (let i = 0; i < 10; i++) {
|
for (let i = 0; i < 10; i++) {
|
||||||
const div = document.createElement('div');
|
const div = document.createElement("div");
|
||||||
fragment.appendChild(div);
|
fragment.appendChild(div);
|
||||||
}
|
}
|
||||||
document.body.appendChild(fragment);
|
document.body.appendChild(fragment);
|
||||||
@@ -294,13 +314,14 @@ const efficientPlugin: Plugin<typeof settings> = {
|
|||||||
unregister();
|
unregister();
|
||||||
// clearInterval(interval); // If you used the bad approach
|
// clearInterval(interval); // If you used the bad approach
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default efficientPlugin;
|
export default efficientPlugin;
|
||||||
```
|
```
|
||||||
|
|
||||||
Each plugin should be in its own file and exported as the default export. The plugin should:
|
Each plugin should be in its own file and exported as the default export. The plugin should:
|
||||||
|
|
||||||
1. Import necessary types and helpers
|
1. Import necessary types and helpers
|
||||||
2. Define settings if needed
|
2. Define settings if needed
|
||||||
3. Create a settings class if using settings
|
3. Create a settings class if using settings
|
||||||
@@ -308,6 +329,7 @@ Each plugin should be in its own file and exported as the default export. The pl
|
|||||||
5. Export the plugin as default
|
5. Export the plugin as default
|
||||||
|
|
||||||
Remember to always:
|
Remember to always:
|
||||||
|
|
||||||
- Use proper TypeScript types
|
- Use proper TypeScript types
|
||||||
- Clean up when your plugin is disabled
|
- Clean up when your plugin is disabled
|
||||||
- Handle errors gracefully
|
- Handle errors gracefully
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@ export const base64Loader = {
|
|||||||
const [filePath, query] = id.split("?");
|
const [filePath, query] = id.split("?");
|
||||||
if (query !== "base64") return null;
|
if (query !== "base64") return null;
|
||||||
|
|
||||||
const data = fs.readFileSync(filePath, { encoding: 'base64' });
|
const data = fs.readFileSync(filePath, { encoding: "base64" });
|
||||||
const mimeType = mime.lookup(filePath);
|
const mimeType = mime.lookup(filePath);
|
||||||
const dataURL = `data:${mimeType};base64,${data}`;
|
const dataURL = `data:${mimeType};base64,${data}`;
|
||||||
|
|
||||||
|
|||||||
+10
-10
@@ -1,25 +1,25 @@
|
|||||||
// ref: https://stackoverflow.com/a/76920975
|
// ref: https://stackoverflow.com/a/76920975
|
||||||
import type { Plugin } from 'vite';
|
import type { Plugin } from "vite";
|
||||||
|
|
||||||
export default function ClosePlugin(): Plugin {
|
export default function ClosePlugin(): Plugin {
|
||||||
return {
|
return {
|
||||||
name: 'ClosePlugin', // required, will show up in warnings and errors
|
name: "ClosePlugin", // required, will show up in warnings and errors
|
||||||
|
|
||||||
// use this to catch errors when building
|
// use this to catch errors when building
|
||||||
buildEnd(error) {
|
buildEnd(error) {
|
||||||
if(error) {
|
if (error) {
|
||||||
console.error('Error bundling')
|
console.error("Error bundling");
|
||||||
console.error(error)
|
console.error(error);
|
||||||
process.exit(1)
|
process.exit(1);
|
||||||
} else {
|
} else {
|
||||||
console.log('Build ended')
|
console.log("Build ended");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// use this to catch the end of a build without errors
|
// use this to catch the end of a build without errors
|
||||||
closeBundle() {
|
closeBundle() {
|
||||||
console.log('Bundle closed')
|
console.log("Bundle closed");
|
||||||
process.exit(0)
|
process.exit(0);
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { Browser, BuildTarget, Manifest } from './types'
|
import type { Browser, BuildTarget, Manifest } from "./types";
|
||||||
import type { AnyCase } from './utils'
|
import type { AnyCase } from "./utils";
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
@@ -15,7 +15,7 @@ export function createManifest(
|
|||||||
return {
|
return {
|
||||||
manifest,
|
manifest,
|
||||||
browser,
|
browser,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -29,5 +29,5 @@ export function createManifest(
|
|||||||
* @return {*} {@link Manifest}
|
* @return {*} {@link Manifest}
|
||||||
*/
|
*/
|
||||||
export function createManifestBase(manifest: Manifest): Manifest {
|
export function createManifestBase(manifest: Manifest): Manifest {
|
||||||
return manifest
|
return manifest;
|
||||||
}
|
}
|
||||||
+18
-18
@@ -1,26 +1,26 @@
|
|||||||
// vite-plugin-inline-worker-dev.ts
|
// vite-plugin-inline-worker-dev.ts
|
||||||
import { Plugin } from 'vite'
|
import { Plugin } from "vite";
|
||||||
import fs from 'fs/promises'
|
import fs from "fs/promises";
|
||||||
import { build, transform } from 'esbuild'
|
import { build, transform } from "esbuild";
|
||||||
|
|
||||||
export default function InlineWorkerDevPlugin(): Plugin {
|
export default function InlineWorkerDevPlugin(): Plugin {
|
||||||
return {
|
return {
|
||||||
name: 'vite:inline-worker-dev',
|
name: "vite:inline-worker-dev",
|
||||||
async load(id) {
|
async load(id) {
|
||||||
if (id.includes('?inlineWorker')) {
|
if (id.includes("?inlineWorker")) {
|
||||||
const [cleanPath] = id.split('?')
|
const [cleanPath] = id.split("?");
|
||||||
console.log('cleanPath', cleanPath)
|
console.log("cleanPath", cleanPath);
|
||||||
const code = await fs.readFile(cleanPath, 'utf-8')
|
const code = await fs.readFile(cleanPath, "utf-8");
|
||||||
const result = await build({
|
const result = await build({
|
||||||
entryPoints: [cleanPath],
|
entryPoints: [cleanPath],
|
||||||
bundle: true,
|
bundle: true,
|
||||||
write: false,
|
write: false,
|
||||||
platform: 'browser',
|
platform: "browser",
|
||||||
format: 'iife',
|
format: "iife",
|
||||||
target: 'esnext',
|
target: "esnext",
|
||||||
})
|
});
|
||||||
|
|
||||||
const workerCode = result.outputFiles[0].text
|
const workerCode = result.outputFiles[0].text;
|
||||||
|
|
||||||
const workerBlobCode = `
|
const workerBlobCode = `
|
||||||
const code = ${JSON.stringify(workerCode)};
|
const code = ${JSON.stringify(workerCode)};
|
||||||
@@ -28,10 +28,10 @@ export default function InlineWorkerDevPlugin(): Plugin {
|
|||||||
const blob = new Blob([code], { type: 'application/javascript' });
|
const blob = new Blob([code], { type: 'application/javascript' });
|
||||||
return new Worker(URL.createObjectURL(blob), { type: 'module' });
|
return new Worker(URL.createObjectURL(blob), { type: 'module' });
|
||||||
}
|
}
|
||||||
`
|
`;
|
||||||
return workerBlobCode
|
return workerBlobCode;
|
||||||
}
|
}
|
||||||
return null
|
return null;
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
+69
-46
@@ -1,49 +1,63 @@
|
|||||||
const glob = require('glob');
|
const glob = require("glob");
|
||||||
const semver = require('semver');
|
const semver = require("semver");
|
||||||
const { execSync } = require('child_process');
|
const { execSync } = require("child_process");
|
||||||
const path = require('path');
|
const path = require("path");
|
||||||
|
|
||||||
function getLatestVersion(files) {
|
function getLatestVersion(files) {
|
||||||
console.log('Files passed to getLatestVersion:', files);
|
console.log("Files passed to getLatestVersion:", files);
|
||||||
|
|
||||||
const versions = files.map(file => {
|
const versions = files
|
||||||
const match = file.match(/@([\d\.]+)-/);
|
.map((file) => {
|
||||||
console.log('Matching file:', file, 'Version found:', match ? match[1] : 'None');
|
const match = file.match(/@([\d\.]+)-/);
|
||||||
|
console.log(
|
||||||
|
"Matching file:",
|
||||||
|
file,
|
||||||
|
"Version found:",
|
||||||
|
match ? match[1] : "None",
|
||||||
|
);
|
||||||
|
|
||||||
if (!match) return null;
|
if (!match) return null;
|
||||||
|
|
||||||
const fullVersion = match[1]; // Original version (e.g., 3.4.5.1)
|
const fullVersion = match[1]; // Original version (e.g., 3.4.5.1)
|
||||||
const semverVersion = fullVersion.split('.').slice(0, 3).join('.'); // Trim to 3.4.5
|
const semverVersion = fullVersion.split(".").slice(0, 3).join("."); // Trim to 3.4.5
|
||||||
|
|
||||||
return { fullVersion, semverVersion };
|
return { fullVersion, semverVersion };
|
||||||
}).filter(Boolean);
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
console.log('Extracted versions:', versions.map(v => v.semverVersion));
|
console.log(
|
||||||
|
"Extracted versions:",
|
||||||
|
versions.map((v) => v.semverVersion),
|
||||||
|
);
|
||||||
|
|
||||||
// Find latest version using the trimmed semver format
|
// Find latest version using the trimmed semver format
|
||||||
const latestSemver = semver.maxSatisfying(versions.map(v => v.semverVersion), '*');
|
const latestSemver = semver.maxSatisfying(
|
||||||
console.log('Latest SemVer-compatible version:', latestSemver);
|
versions.map((v) => v.semverVersion),
|
||||||
|
"*",
|
||||||
|
);
|
||||||
|
console.log("Latest SemVer-compatible version:", latestSemver);
|
||||||
|
|
||||||
// Get the full version that matches the latest SemVer version
|
// Get the full version that matches the latest SemVer version
|
||||||
const latestVersion = versions.find(v => v.semverVersion === latestSemver)?.fullVersion || null;
|
const latestVersion =
|
||||||
|
versions.find((v) => v.semverVersion === latestSemver)?.fullVersion || null;
|
||||||
|
|
||||||
console.log('Final selected latest version:', latestVersion);
|
console.log("Final selected latest version:", latestVersion);
|
||||||
return latestVersion;
|
return latestVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLatestFiles(browser) {
|
function getLatestFiles(browser) {
|
||||||
const pattern = `dist/betterseqtaplus@*-*${browser}.zip`;
|
const pattern = `dist/betterseqtaplus@*-*${browser}.zip`;
|
||||||
console.log('Glob pattern:', pattern);
|
console.log("Glob pattern:", pattern);
|
||||||
|
|
||||||
const files = glob.sync(pattern);
|
const files = glob.sync(pattern);
|
||||||
console.log('Files found for browser', browser, ':', files);
|
console.log("Files found for browser", browser, ":", files);
|
||||||
|
|
||||||
const latestVersion = getLatestVersion(files);
|
const latestVersion = getLatestVersion(files);
|
||||||
|
|
||||||
// Find the exact file by matching the original full version
|
// Find the exact file by matching the original full version
|
||||||
const latestFile = files.find(file => file.includes(`@${latestVersion}-`));
|
const latestFile = files.find((file) => file.includes(`@${latestVersion}-`));
|
||||||
|
|
||||||
console.log('Latest file for browser', browser, ':', latestFile);
|
console.log("Latest file for browser", browser, ":", latestFile);
|
||||||
return latestFile;
|
return latestFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,44 +65,53 @@ function zipSources() {
|
|||||||
const zipFileName = `dist/betterseqtaplus@latest-sources.zip`;
|
const zipFileName = `dist/betterseqtaplus@latest-sources.zip`;
|
||||||
|
|
||||||
const excludePatterns = [
|
const excludePatterns = [
|
||||||
'node_modules',
|
"node_modules",
|
||||||
'dist',
|
"dist",
|
||||||
'.env*',
|
".env*",
|
||||||
'.git',
|
".git",
|
||||||
'.github',
|
".github",
|
||||||
'.vscode',
|
".vscode",
|
||||||
'LICENSE',
|
"LICENSE",
|
||||||
'package.json'
|
"package.json",
|
||||||
].map(pattern => `-x!${pattern}`).join(' ');
|
]
|
||||||
|
.map((pattern) => `-x!${pattern}`)
|
||||||
|
.join(" ");
|
||||||
|
|
||||||
const zipCommand = `7z a ${zipFileName} . ${excludePatterns}`;
|
const zipCommand = `7z a ${zipFileName} . ${excludePatterns}`;
|
||||||
|
|
||||||
console.log('Zipping project sources with command:', zipCommand);
|
console.log("Zipping project sources with command:", zipCommand);
|
||||||
execSync(zipCommand, { stdio: 'inherit' });
|
execSync(zipCommand, { stdio: "inherit" });
|
||||||
|
|
||||||
return zipFileName;
|
return zipFileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
function runPublishCommand(browsers) {
|
function runPublishCommand(browsers) {
|
||||||
const chromeZip = browsers.includes('chrome') ? getLatestFiles('chrome') : null;
|
const chromeZip = browsers.includes("chrome")
|
||||||
const firefoxZip = browsers.includes('firefox') ? getLatestFiles('firefox') : null;
|
? getLatestFiles("chrome")
|
||||||
const firefoxSourcesZip = browsers.includes('firefox') ? zipSources() : null;
|
: null;
|
||||||
|
const firefoxZip = browsers.includes("firefox")
|
||||||
|
? getLatestFiles("firefox")
|
||||||
|
: null;
|
||||||
|
const firefoxSourcesZip = browsers.includes("firefox") ? zipSources() : null;
|
||||||
|
|
||||||
console.log('Chrome zip:', chromeZip);
|
console.log("Chrome zip:", chromeZip);
|
||||||
console.log('Firefox zip:', firefoxZip);
|
console.log("Firefox zip:", firefoxZip);
|
||||||
console.log('Firefox sources zip:', firefoxSourcesZip);
|
console.log("Firefox sources zip:", firefoxSourcesZip);
|
||||||
|
|
||||||
if (browsers.length === 0) {
|
if (browsers.length === 0) {
|
||||||
console.log('No browsers specified. Exiting.');
|
console.log("No browsers specified. Exiting.");
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((browsers.includes('chrome') && !chromeZip) || (browsers.includes('firefox') && (!firefoxZip || !firefoxSourcesZip))) {
|
if (
|
||||||
console.error('Could not find required zip files for specified browsers.');
|
(browsers.includes("chrome") && !chromeZip) ||
|
||||||
|
(browsers.includes("firefox") && (!firefoxZip || !firefoxSourcesZip))
|
||||||
|
) {
|
||||||
|
console.error("Could not find required zip files for specified browsers.");
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
let command = 'publish-extension';
|
let command = "publish-extension";
|
||||||
if (chromeZip) {
|
if (chromeZip) {
|
||||||
command += ` --chrome-zip ${chromeZip}`;
|
command += ` --chrome-zip ${chromeZip}`;
|
||||||
}
|
}
|
||||||
@@ -96,13 +119,13 @@ function runPublishCommand(browsers) {
|
|||||||
command += ` --firefox-zip ${firefoxZip} --firefox-sources-zip ${firefoxSourcesZip}`;
|
command += ` --firefox-zip ${firefoxZip} --firefox-sources-zip ${firefoxSourcesZip}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Running command:', command);
|
console.log("Running command:", command);
|
||||||
execSync(command, { stdio: 'inherit' });
|
execSync(command, { stdio: "inherit" });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse command-line arguments
|
// Parse command-line arguments
|
||||||
const args = process.argv.slice(2);
|
const args = process.argv.slice(2);
|
||||||
const browserIndex = args.indexOf('--b');
|
const browserIndex = args.indexOf("--b");
|
||||||
const browsers = browserIndex !== -1 ? args.slice(browserIndex + 1) : [];
|
const browsers = browserIndex !== -1 ? args.slice(browserIndex + 1) : [];
|
||||||
|
|
||||||
runPublishCommand(browsers);
|
runPublishCommand(browsers);
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
import fs from 'fs';
|
import fs from "fs";
|
||||||
|
|
||||||
export default function touchGlobalCSSPlugin() {
|
export default function touchGlobalCSSPlugin() {
|
||||||
return {
|
return {
|
||||||
name: 'touch-global-css',
|
name: "touch-global-css",
|
||||||
handleHotUpdate({ modules }) {
|
handleHotUpdate({ modules }) {
|
||||||
// log all of the staticImportedUrls
|
// log all of the staticImportedUrls
|
||||||
const importers = modules[0]._clientModule.importers
|
const importers = modules[0]._clientModule.importers;
|
||||||
importers.forEach((importer) => {
|
importers.forEach((importer) => {
|
||||||
if (importer.file.includes('.css')) {
|
if (importer.file.includes(".css")) {
|
||||||
console.log("touching", importer.file)
|
console.log("touching", importer.file);
|
||||||
fs.utimesSync(importer.file, new Date(), new Date())
|
fs.utimesSync(importer.file, new Date(), new Date());
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
+67
-67
@@ -1,104 +1,104 @@
|
|||||||
import type { ManifestV3Export } from '@crxjs/vite-plugin'
|
import type { ManifestV3Export } from "@crxjs/vite-plugin";
|
||||||
import { type AnyCase, createEnum } from './utils'
|
import { type AnyCase, createEnum } from "./utils";
|
||||||
|
|
||||||
export const FrameworkEnum = {
|
export const FrameworkEnum = {
|
||||||
React: 'React',
|
React: "React",
|
||||||
Vanilla: 'Vanilla',
|
Vanilla: "Vanilla",
|
||||||
Preact: 'Preact',
|
Preact: "Preact",
|
||||||
Lit: 'Lit',
|
Lit: "Lit",
|
||||||
Svelte: 'Svelte',
|
Svelte: "Svelte",
|
||||||
Vue: 'Vue',
|
Vue: "Vue",
|
||||||
} as const
|
} as const;
|
||||||
|
|
||||||
export const BrowserEnum = {
|
export const BrowserEnum = {
|
||||||
Chrome: 'Chrome',
|
Chrome: "Chrome",
|
||||||
Brave: 'Brave',
|
Brave: "Brave",
|
||||||
Opera: 'Opera',
|
Opera: "Opera",
|
||||||
Edge: 'Edge',
|
Edge: "Edge",
|
||||||
Firefox: 'Firefox',
|
Firefox: "Firefox",
|
||||||
Safari: 'Safari',
|
Safari: "Safari",
|
||||||
} as const
|
} as const;
|
||||||
|
|
||||||
const LanguageEnum = {
|
const LanguageEnum = {
|
||||||
TypeScript: 'TypeScript',
|
TypeScript: "TypeScript",
|
||||||
JavaScript: 'JavaScript',
|
JavaScript: "JavaScript",
|
||||||
} as const
|
} as const;
|
||||||
|
|
||||||
export const StyleEnum = {
|
export const StyleEnum = {
|
||||||
Tailwind: 'Tailwind',
|
Tailwind: "Tailwind",
|
||||||
} as const
|
} as const;
|
||||||
|
|
||||||
export const PackageManagerEnum = {
|
export const PackageManagerEnum = {
|
||||||
Bun: 'Bun',
|
Bun: "Bun",
|
||||||
PnPm: 'PnPm',
|
PnPm: "PnPm",
|
||||||
Npm: 'Npm',
|
Npm: "Npm",
|
||||||
Yarn: 'Yarn',
|
Yarn: "Yarn",
|
||||||
} as const
|
} as const;
|
||||||
|
|
||||||
// see: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/firefox-webext-browser/index.d.ts
|
// see: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/firefox-webext-browser/index.d.ts
|
||||||
export type BrowserSpecificSettings = {
|
export type BrowserSpecificSettings = {
|
||||||
browser_specific_settings?: {
|
browser_specific_settings?: {
|
||||||
gecko?: {
|
gecko?: {
|
||||||
id: string
|
id: string;
|
||||||
strict_min_version?: string
|
strict_min_version?: string;
|
||||||
strict_max_version?: string
|
strict_max_version?: string;
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
export type Manifest = ManifestV3Export
|
export type Manifest = ManifestV3Export;
|
||||||
export type ManifestIcons = chrome.runtime.ManifestIcons
|
export type ManifestIcons = chrome.runtime.ManifestIcons;
|
||||||
export type ManifestBackground = chrome.runtime.ManifestV3['background']
|
export type ManifestBackground = chrome.runtime.ManifestV3["background"];
|
||||||
export type ManifestContentScripts =
|
export type ManifestContentScripts =
|
||||||
chrome.runtime.ManifestV3['content_scripts']
|
chrome.runtime.ManifestV3["content_scripts"];
|
||||||
export type ManifestWebAccessibleResources =
|
export type ManifestWebAccessibleResources =
|
||||||
chrome.runtime.ManifestV3['web_accessible_resources']
|
chrome.runtime.ManifestV3["web_accessible_resources"];
|
||||||
export type ManifestCommands = chrome.runtime.ManifestV3['commands']
|
export type ManifestCommands = chrome.runtime.ManifestV3["commands"];
|
||||||
export type ManifestAction = chrome.runtime.ManifestV3['action']
|
export type ManifestAction = chrome.runtime.ManifestV3["action"];
|
||||||
export type ManifestPermissions = chrome.runtime.ManifestV3['permissions']
|
export type ManifestPermissions = chrome.runtime.ManifestV3["permissions"];
|
||||||
export type ManifestOptionsUI = chrome.runtime.ManifestV3['options_ui']
|
export type ManifestOptionsUI = chrome.runtime.ManifestV3["options_ui"];
|
||||||
export type ManifestURLOverrides =
|
export type ManifestURLOverrides =
|
||||||
chrome.runtime.ManifestV3['chrome_url_overrides']
|
chrome.runtime.ManifestV3["chrome_url_overrides"];
|
||||||
|
|
||||||
export type BrowserName<T extends string> = Capitalize<T> | Lowercase<T>
|
export type BrowserName<T extends string> = Capitalize<T> | Lowercase<T>;
|
||||||
export type BrowserEnumType<T extends string> = {
|
export type BrowserEnumType<T extends string> = {
|
||||||
[browser in BrowserName<T>]: BrowserName<T>
|
[browser in BrowserName<T>]: BrowserName<T>;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type BuildMode = AnyCase<Browser>
|
export type BuildMode = AnyCase<Browser>;
|
||||||
export type BuildTarget = {
|
export type BuildTarget = {
|
||||||
manifest: Manifest
|
manifest: Manifest;
|
||||||
browser: AnyCase<Browser>
|
browser: AnyCase<Browser>;
|
||||||
}
|
};
|
||||||
export type BuildConfig = {
|
export type BuildConfig = {
|
||||||
command?: 'build' | 'serve'
|
command?: "build" | "serve";
|
||||||
mode?: AnyCase<Browser> | string | undefined
|
mode?: AnyCase<Browser> | string | undefined;
|
||||||
}
|
};
|
||||||
|
|
||||||
export interface Repository {
|
export interface Repository {
|
||||||
type: string
|
type: string;
|
||||||
url?: string
|
url?: string;
|
||||||
bugs?: Bugs
|
bugs?: Bugs;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Bugs {
|
export interface Bugs {
|
||||||
url?: string
|
url?: string;
|
||||||
email?: string
|
email?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Browser = (typeof BrowserEnum)[keyof typeof BrowserEnum]
|
export type Browser = (typeof BrowserEnum)[keyof typeof BrowserEnum];
|
||||||
export const Browser: AnyCase<Browser> = createEnum(BrowserEnum)
|
export const Browser: AnyCase<Browser> = createEnum(BrowserEnum);
|
||||||
|
|
||||||
export type PackageManager =
|
export type PackageManager =
|
||||||
(typeof PackageManagerEnum)[keyof typeof PackageManagerEnum]
|
(typeof PackageManagerEnum)[keyof typeof PackageManagerEnum];
|
||||||
export const PackageManager: AnyCase<PackageManager> =
|
export const PackageManager: AnyCase<PackageManager> =
|
||||||
createEnum(PackageManagerEnum)
|
createEnum(PackageManagerEnum);
|
||||||
|
|
||||||
export type Framework = (typeof FrameworkEnum)[keyof typeof FrameworkEnum]
|
export type Framework = (typeof FrameworkEnum)[keyof typeof FrameworkEnum];
|
||||||
export const Framework: AnyCase<Framework> = createEnum(FrameworkEnum)
|
export const Framework: AnyCase<Framework> = createEnum(FrameworkEnum);
|
||||||
|
|
||||||
export type Style = (typeof StyleEnum)[keyof typeof StyleEnum]
|
export type Style = (typeof StyleEnum)[keyof typeof StyleEnum];
|
||||||
export const Style: AnyCase<Style> = createEnum(StyleEnum)
|
export const Style: AnyCase<Style> = createEnum(StyleEnum);
|
||||||
|
|
||||||
export type Language = (typeof LanguageEnum)[keyof typeof LanguageEnum]
|
export type Language = (typeof LanguageEnum)[keyof typeof LanguageEnum];
|
||||||
export const Language: AnyCase<Language> = createEnum(LanguageEnum)
|
export const Language: AnyCase<Language> = createEnum(LanguageEnum);
|
||||||
|
|||||||
+6
-6
@@ -1,21 +1,21 @@
|
|||||||
export type ObjectValues<T> = T[keyof T]
|
export type ObjectValues<T> = T[keyof T];
|
||||||
|
|
||||||
export function createEnum<T extends Record<string, string>>(enumObj: T) {
|
export function createEnum<T extends Record<string, string>>(enumObj: T) {
|
||||||
return Object.values(enumObj) as unknown as ObjectValues<T>
|
return Object.values(enumObj) as unknown as ObjectValues<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AnyCase<T extends string> =
|
export type AnyCase<T extends string> =
|
||||||
| Uppercase<T>
|
| Uppercase<T>
|
||||||
| Lowercase<T>
|
| Lowercase<T>
|
||||||
| Capitalize<T>
|
| Capitalize<T>
|
||||||
| Uncapitalize<T>
|
| Uncapitalize<T>;
|
||||||
|
|
||||||
export type AnyCaseLanguage<T extends string, K extends string> =
|
export type AnyCaseLanguage<T extends string, K extends string> =
|
||||||
| Uppercase<T | K>
|
| Uppercase<T | K>
|
||||||
| Lowercase<T | K>
|
| Lowercase<T | K>
|
||||||
| Capitalize<T | K>
|
| Capitalize<T | K>
|
||||||
| Uncapitalize<T | K>
|
| Uncapitalize<T | K>;
|
||||||
|
|
||||||
export type OptionalKeys<T> = {
|
export type OptionalKeys<T> = {
|
||||||
[K in keyof T as undefined extends T[K] ? K : never]: T[K]
|
[K in keyof T as undefined extends T[K] ? K : never]: T[K];
|
||||||
}
|
};
|
||||||
|
|||||||
+29
-27
@@ -1,55 +1,57 @@
|
|||||||
import {
|
import {
|
||||||
initializeSettingsState,
|
initializeSettingsState,
|
||||||
settingsState,
|
settingsState,
|
||||||
} from "@/seqta/utils/listeners/SettingsState"
|
} from "@/seqta/utils/listeners/SettingsState";
|
||||||
import documentLoadCSS from "@/css/documentload.scss?inline"
|
import documentLoadCSS from "@/css/documentload.scss?inline";
|
||||||
import icon48 from "@/resources/icons/icon-48.png?base64"
|
import icon48 from "@/resources/icons/icon-48.png?base64";
|
||||||
import browser from "webextension-polyfill"
|
import browser from "webextension-polyfill";
|
||||||
|
|
||||||
import * as plugins from "@/plugins"
|
import * as plugins from "@/plugins";
|
||||||
import { main } from "@/seqta/main"
|
import { main } from "@/seqta/main";
|
||||||
|
|
||||||
|
export let MenuOptionsOpen = false;
|
||||||
|
|
||||||
export let MenuOptionsOpen = false
|
var IsSEQTAPage = false;
|
||||||
|
let hasSEQTAText = false;
|
||||||
var IsSEQTAPage = false
|
|
||||||
let hasSEQTAText = false
|
|
||||||
|
|
||||||
// This check is placed outside of the document load event due to issues with EP (https://github.com/BetterSEQTA/BetterSEQTA-Plus/issues/84)
|
// This check is placed outside of the document load event due to issues with EP (https://github.com/BetterSEQTA/BetterSEQTA-Plus/issues/84)
|
||||||
if (document.childNodes[1]) {
|
if (document.childNodes[1]) {
|
||||||
hasSEQTAText =
|
hasSEQTAText =
|
||||||
document.childNodes[1].textContent?.includes(
|
document.childNodes[1].textContent?.includes(
|
||||||
"Copyright (c) SEQTA Software",
|
"Copyright (c) SEQTA Software",
|
||||||
) ?? false
|
) ?? false;
|
||||||
init()
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
const hasSEQTATitle = document.title.includes("SEQTA Learn")
|
const hasSEQTATitle = document.title.includes("SEQTA Learn");
|
||||||
|
|
||||||
if (hasSEQTAText && hasSEQTATitle && !IsSEQTAPage) { // Verify we are on a SEQTA page
|
if (hasSEQTAText && hasSEQTATitle && !IsSEQTAPage) {
|
||||||
IsSEQTAPage = true
|
// Verify we are on a SEQTA page
|
||||||
console.info("[BetterSEQTA+] Verified SEQTA Page")
|
IsSEQTAPage = true;
|
||||||
|
console.info("[BetterSEQTA+] Verified SEQTA Page");
|
||||||
|
|
||||||
const documentLoadStyle = document.createElement("style")
|
const documentLoadStyle = document.createElement("style");
|
||||||
documentLoadStyle.textContent = documentLoadCSS
|
documentLoadStyle.textContent = documentLoadCSS;
|
||||||
document.head.appendChild(documentLoadStyle)
|
document.head.appendChild(documentLoadStyle);
|
||||||
|
|
||||||
const icon = document.querySelector('link[rel*="icon"]')! as HTMLLinkElement
|
const icon = document.querySelector(
|
||||||
icon.href = icon48 // Change the icon
|
'link[rel*="icon"]',
|
||||||
|
)! as HTMLLinkElement;
|
||||||
|
icon.href = icon48; // Change the icon
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await initializeSettingsState()
|
await initializeSettingsState();
|
||||||
|
|
||||||
if (typeof settingsState.onoff === "undefined") {
|
if (typeof settingsState.onoff === "undefined") {
|
||||||
await browser.runtime.sendMessage({ type: "setDefaultStorage" })
|
await browser.runtime.sendMessage({ type: "setDefaultStorage" });
|
||||||
}
|
}
|
||||||
|
|
||||||
await main()
|
await main();
|
||||||
|
|
||||||
if (settingsState.onoff) {
|
if (settingsState.onoff) {
|
||||||
// Initialize legacy plugins
|
// Initialize legacy plugins
|
||||||
plugins.Monofile()
|
plugins.Monofile();
|
||||||
|
|
||||||
// Initialize new plugin system
|
// Initialize new plugin system
|
||||||
await plugins.initializePlugins();
|
await plugins.initializePlugins();
|
||||||
@@ -57,9 +59,9 @@ async function init() {
|
|||||||
|
|
||||||
console.info(
|
console.info(
|
||||||
"[BetterSEQTA+] Successfully initialised BetterSEQTA+, starting to load assets.",
|
"[BetterSEQTA+] Successfully initialised BetterSEQTA+, starting to load assets.",
|
||||||
)
|
);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(error)
|
console.error(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+98
-77
@@ -1,63 +1,68 @@
|
|||||||
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';
|
import { fetchNews } from "./background/news";
|
||||||
|
|
||||||
function reloadSeqtaPages() {
|
function reloadSeqtaPages() {
|
||||||
const result = browser.tabs.query({})
|
const result = browser.tabs.query({});
|
||||||
function open (tabs: any) {
|
function open(tabs: any) {
|
||||||
for (let tab of tabs) {
|
for (let tab of tabs) {
|
||||||
if (tab.title.includes('SEQTA Learn')) {
|
if (tab.title.includes("SEQTA Learn")) {
|
||||||
browser.tabs.reload(tab.id);
|
browser.tabs.reload(tab.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result.then(open, console.error)
|
result.then(open, console.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
browser.runtime.onMessage.addListener((request: any, _: any, sendResponse: (response?: any) => void) => {
|
browser.runtime.onMessage.addListener(
|
||||||
|
(request: any, _: any, sendResponse: (response?: any) => void) => {
|
||||||
|
switch (request.type) {
|
||||||
|
case "reloadTabs":
|
||||||
|
reloadSeqtaPages();
|
||||||
|
break;
|
||||||
|
|
||||||
switch (request.type) {
|
case "extensionPages":
|
||||||
case 'reloadTabs':
|
browser.tabs.query({}).then(function (tabs) {
|
||||||
reloadSeqtaPages();
|
for (let tab of tabs) {
|
||||||
break;
|
if (tab.url?.includes("chrome-extension://")) {
|
||||||
|
browser.tabs.sendMessage(tab.id!, request);
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
});
|
break;
|
||||||
return true;
|
|
||||||
|
|
||||||
case 'githubTab':
|
case "currentTab":
|
||||||
browser.tabs.create({ url: 'github.com/BetterSEQTA/BetterSEQTA-Plus' });
|
browser.tabs
|
||||||
break;
|
.query({ active: true, currentWindow: true })
|
||||||
|
.then(function (tabs) {
|
||||||
|
browser.tabs
|
||||||
|
.sendMessage(tabs[0].id!, request)
|
||||||
|
.then(function (response) {
|
||||||
|
sendResponse(response);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
|
||||||
case 'setDefaultStorage':
|
case "githubTab":
|
||||||
SetStorageValue(DefaultValues);
|
browser.tabs.create({ url: "github.com/BetterSEQTA/BetterSEQTA-Plus" });
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'sendNews':
|
case "setDefaultStorage":
|
||||||
fetchNews(request.source ?? 'australia', sendResponse);
|
SetStorageValue(DefaultValues);
|
||||||
return true;
|
break;
|
||||||
|
|
||||||
default:
|
case "sendNews":
|
||||||
console.log('Unknown request type');
|
fetchNews(request.source ?? "australia", sendResponse);
|
||||||
}
|
return true;
|
||||||
|
|
||||||
return false;
|
default:
|
||||||
});
|
console.log("Unknown request type");
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const DefaultValues: SettingsState = {
|
const DefaultValues: SettingsState = {
|
||||||
onoff: true,
|
onoff: true,
|
||||||
@@ -86,66 +91,67 @@ const DefaultValues: SettingsState = {
|
|||||||
},
|
},
|
||||||
menuorder: [],
|
menuorder: [],
|
||||||
subjectfilters: {},
|
subjectfilters: {},
|
||||||
selectedTheme: '',
|
selectedTheme: "",
|
||||||
selectedColor: 'linear-gradient(40deg, rgba(201,61,0,1) 0%, RGBA(170, 5, 58, 1) 100%)',
|
selectedColor:
|
||||||
originalSelectedColor: '',
|
"linear-gradient(40deg, rgba(201,61,0,1) 0%, RGBA(170, 5, 58, 1) 100%)",
|
||||||
|
originalSelectedColor: "",
|
||||||
DarkMode: true,
|
DarkMode: true,
|
||||||
animations: true,
|
animations: true,
|
||||||
assessmentsAverage: true,
|
assessmentsAverage: true,
|
||||||
defaultPage: 'home',
|
defaultPage: "home",
|
||||||
shortcuts: [
|
shortcuts: [
|
||||||
{
|
{
|
||||||
name: 'YouTube',
|
name: "YouTube",
|
||||||
enabled: false,
|
enabled: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Outlook',
|
name: "Outlook",
|
||||||
enabled: true,
|
enabled: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Office',
|
name: "Office",
|
||||||
enabled: true,
|
enabled: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Spotify',
|
name: "Spotify",
|
||||||
enabled: false,
|
enabled: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Google',
|
name: "Google",
|
||||||
enabled: true,
|
enabled: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'DuckDuckGo',
|
name: "DuckDuckGo",
|
||||||
enabled: false,
|
enabled: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Cool Math Games',
|
name: "Cool Math Games",
|
||||||
enabled: false,
|
enabled: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'SACE',
|
name: "SACE",
|
||||||
enabled: false,
|
enabled: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Google Scholar',
|
name: "Google Scholar",
|
||||||
enabled: false,
|
enabled: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Gmail',
|
name: "Gmail",
|
||||||
enabled: false,
|
enabled: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Netflix',
|
name: "Netflix",
|
||||||
enabled: false,
|
enabled: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Education Perfect',
|
name: "Education Perfect",
|
||||||
enabled: false,
|
enabled: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
customshortcuts: [],
|
customshortcuts: [],
|
||||||
lettergrade: false,
|
lettergrade: false,
|
||||||
newsSource: 'australia',
|
newsSource: "australia",
|
||||||
};
|
};
|
||||||
|
|
||||||
function SetStorageValue(object: any) {
|
function SetStorageValue(object: any) {
|
||||||
@@ -158,7 +164,8 @@ function convertBksliderToSpeed(bksliderinput: number): number {
|
|||||||
const minBase = 50;
|
const minBase = 50;
|
||||||
const maxBase = 150;
|
const maxBase = 150;
|
||||||
|
|
||||||
const scaledValue = 2 + ((maxBase - bksliderinput) / (maxBase - minBase)) ** 4;
|
const scaledValue =
|
||||||
|
2 + ((maxBase - bksliderinput) / (maxBase - minBase)) ** 4;
|
||||||
const baseSpeed = 3;
|
const baseSpeed = 3;
|
||||||
|
|
||||||
const speed = baseSpeed / scaledValue;
|
const speed = baseSpeed / scaledValue;
|
||||||
@@ -166,50 +173,64 @@ function convertBksliderToSpeed(bksliderinput: number): number {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function migrateLegacySettings() {
|
async function migrateLegacySettings() {
|
||||||
const storage = await browser.storage.local.get(null) as unknown as SettingsState;
|
const storage = (await browser.storage.local.get(
|
||||||
|
null,
|
||||||
|
)) as unknown as SettingsState;
|
||||||
|
|
||||||
// Animated Background Migration
|
// Animated Background Migration
|
||||||
if ('animatedbk' in storage || 'bksliderinput' in storage) {
|
if ("animatedbk" in storage || "bksliderinput" in storage) {
|
||||||
const animatedSettings = {
|
const animatedSettings = {
|
||||||
enabled: storage.animatedbk ?? true,
|
enabled: storage.animatedbk ?? true,
|
||||||
speed: storage.bksliderinput ? convertBksliderToSpeed(parseFloat(storage.bksliderinput)) : 1
|
speed: storage.bksliderinput
|
||||||
|
? convertBksliderToSpeed(parseFloat(storage.bksliderinput))
|
||||||
|
: 1,
|
||||||
};
|
};
|
||||||
await browser.storage.local.set({ 'plugin.animated-background.settings': animatedSettings });
|
await browser.storage.local.set({
|
||||||
|
"plugin.animated-background.settings": animatedSettings,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assessments Average Migration
|
// Assessments Average Migration
|
||||||
if ('assessmentsAverage' in storage || 'lettergrade' in storage) {
|
if ("assessmentsAverage" in storage || "lettergrade" in storage) {
|
||||||
const assessmentsSettings = {
|
const assessmentsSettings = {
|
||||||
enabled: storage.assessmentsAverage ?? true,
|
enabled: storage.assessmentsAverage ?? true,
|
||||||
lettergrade: storage.lettergrade ?? false
|
lettergrade: storage.lettergrade ?? false,
|
||||||
};
|
};
|
||||||
await browser.storage.local.set({ 'plugin.assessments-average.settings': assessmentsSettings });
|
await browser.storage.local.set({
|
||||||
|
"plugin.assessments-average.settings": assessmentsSettings,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('selectedTheme' in storage) {
|
if ("selectedTheme" in storage) {
|
||||||
const themesSettings = { enabled: true };
|
const themesSettings = { enabled: true };
|
||||||
await browser.storage.local.set({ 'plugin.themes.settings': themesSettings });
|
await browser.storage.local.set({
|
||||||
|
"plugin.themes.settings": themesSettings,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (storage.notificationCollector !== false) {
|
if (storage.notificationCollector !== false) {
|
||||||
await browser.storage.local.set({ 'plugin.notificationCollector.settings': { enabled: true } });
|
await browser.storage.local.set({
|
||||||
|
"plugin.notificationCollector.settings": { enabled: true },
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
await browser.storage.local.set({ 'plugin.notificationCollector.settings': { enabled: false } });
|
await browser.storage.local.set({
|
||||||
|
"plugin.notificationCollector.settings": { enabled: false },
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const keysToRemove = [
|
const keysToRemove = [
|
||||||
'animatedbk',
|
"animatedbk",
|
||||||
'bksliderinput',
|
"bksliderinput",
|
||||||
'assessmentsAverage',
|
"assessmentsAverage",
|
||||||
'lettergrade'
|
"lettergrade",
|
||||||
];
|
];
|
||||||
await browser.storage.local.remove(keysToRemove);
|
await browser.storage.local.remove(keysToRemove);
|
||||||
}
|
}
|
||||||
|
|
||||||
browser.runtime.onInstalled.addListener(function (event) {
|
browser.runtime.onInstalled.addListener(function (event) {
|
||||||
browser.storage.local.remove(['justupdated']);
|
browser.storage.local.remove(["justupdated"]);
|
||||||
browser.storage.local.remove(['data']);
|
browser.storage.local.remove(["data"]);
|
||||||
|
|
||||||
if ( event.reason == 'install' || event.reason == 'update' ) {
|
if (event.reason == "install" || event.reason == "update") {
|
||||||
browser.storage.local.set({ justupdated: true });
|
browser.storage.local.set({ justupdated: true });
|
||||||
migrateLegacySettings();
|
migrateLegacySettings();
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-14
@@ -1,11 +1,11 @@
|
|||||||
import Parser from 'rss-parser';
|
import Parser from "rss-parser";
|
||||||
|
|
||||||
const fetchAustraliaNews = async (url: string, sendResponse: any) => {
|
const fetchAustraliaNews = async (url: string, sendResponse: any) => {
|
||||||
fetch(url)
|
fetch(url)
|
||||||
.then((result) => result.json())
|
.then((result) => result.json())
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response.code == 'rateLimited') {
|
if (response.code == "rateLimited") {
|
||||||
fetchAustraliaNews(url += '%00', sendResponse);
|
fetchAustraliaNews((url += "%00"), sendResponse);
|
||||||
} else {
|
} else {
|
||||||
sendResponse({ news: response });
|
sendResponse({ news: response });
|
||||||
}
|
}
|
||||||
@@ -31,13 +31,13 @@ const rssFeedsByCountry: Record<string, string[]> = {
|
|||||||
"https://critica.com.pa/rss.xml",
|
"https://critica.com.pa/rss.xml",
|
||||||
"https://www.panamaamerica.com.pa/rss.xml",
|
"https://www.panamaamerica.com.pa/rss.xml",
|
||||||
"https://noticiassin.com/feed/",
|
"https://noticiassin.com/feed/",
|
||||||
"https://elcapitalfinanciero.com/feed/"
|
"https://elcapitalfinanciero.com/feed/",
|
||||||
],
|
],
|
||||||
canada: [
|
canada: [
|
||||||
"https://www.cbc.ca/cmlink/rss-topstories",
|
"https://www.cbc.ca/cmlink/rss-topstories",
|
||||||
"https://calgaryherald.com/feed",
|
"https://calgaryherald.com/feed",
|
||||||
"https://ottawacitizen.com/feed",
|
"https://ottawacitizen.com/feed",
|
||||||
"https://www.montrealgazette.com/feed"
|
"https://www.montrealgazette.com/feed",
|
||||||
],
|
],
|
||||||
singapore: [
|
singapore: [
|
||||||
"https://www.straitstimes.com/news/singapore/rss.xml",
|
"https://www.straitstimes.com/news/singapore/rss.xml",
|
||||||
@@ -49,12 +49,9 @@ const rssFeedsByCountry: Record<string, string[]> = {
|
|||||||
],
|
],
|
||||||
japan: [
|
japan: [
|
||||||
"https://www3.nhk.or.jp/nhkworld/en/news/feeds/",
|
"https://www3.nhk.or.jp/nhkworld/en/news/feeds/",
|
||||||
"https://news.livedoor.com/topics/rss/int.xml"
|
"https://news.livedoor.com/topics/rss/int.xml",
|
||||||
],
|
|
||||||
netherlands: [
|
|
||||||
"https://www.dutchnews.nl/feed/",
|
|
||||||
"https://www.nrc.nl/rss/"
|
|
||||||
],
|
],
|
||||||
|
netherlands: ["https://www.dutchnews.nl/feed/", "https://www.nrc.nl/rss/"],
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function fetchNews(source: string, sendResponse: any) {
|
export async function fetchNews(source: string, sendResponse: any) {
|
||||||
@@ -63,9 +60,9 @@ export async function fetchNews(source: string, sendResponse: any) {
|
|||||||
|
|
||||||
const from =
|
const from =
|
||||||
date.getFullYear() +
|
date.getFullYear() +
|
||||||
'-' +
|
"-" +
|
||||||
(date.getMonth() + 1) +
|
(date.getMonth() + 1) +
|
||||||
'-' +
|
"-" +
|
||||||
(date.getDate() - 5);
|
(date.getDate() - 5);
|
||||||
|
|
||||||
const url = `https://newsapi.org/v2/everything?domains=abc.net.au&from=${from}&apiKey=17c0da766ba347c89d094449504e3080`;
|
const url = `https://newsapi.org/v2/everything?domains=abc.net.au&from=${from}&apiKey=17c0da766ba347c89d094449504e3080`;
|
||||||
@@ -76,7 +73,7 @@ export async function fetchNews(source: string, sendResponse: any) {
|
|||||||
|
|
||||||
const parser = new Parser();
|
const parser = new Parser();
|
||||||
let feeds: string[];
|
let feeds: string[];
|
||||||
console.log('fetchNews', source)
|
console.log("fetchNews", source);
|
||||||
|
|
||||||
if (rssFeedsByCountry[source.toLowerCase()]) {
|
if (rssFeedsByCountry[source.toLowerCase()]) {
|
||||||
// If the source is a country, fetch from predefined feeds
|
// If the source is a country, fetch from predefined feeds
|
||||||
@@ -85,7 +82,9 @@ export async function fetchNews(source: string, sendResponse: any) {
|
|||||||
// If the source is a URL, use it directly
|
// If the source is a URL, use it directly
|
||||||
feeds = [source];
|
feeds = [source];
|
||||||
} else {
|
} else {
|
||||||
throw new Error("Invalid source. Provide a country code or a valid RSS feed URL.");
|
throw new Error(
|
||||||
|
"Invalid source. Provide a country code or a valid RSS feed URL.",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const articlesPromises = feeds.map(async (feedUrl) => {
|
const articlesPromises = feeds.map(async (feedUrl) => {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
* along with EvenBetterSEQTA. If not, see <https://www.gnu.org/licenses/>.
|
* along with EvenBetterSEQTA. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@use 'injected/popup.scss';
|
@use "injected/popup.scss";
|
||||||
|
|
||||||
html {
|
html {
|
||||||
background: #161616 !important;
|
background: #161616 !important;
|
||||||
@@ -77,7 +77,9 @@ html {
|
|||||||
transform-origin: top;
|
transform-origin: top;
|
||||||
transition: transform 0.2s;
|
transition: transform 0.2s;
|
||||||
}
|
}
|
||||||
body:has(.outside-container:not(.hide)) #AddedSettings.tooltip:hover > .tooltiptext {
|
body:has(.outside-container:not(.hide))
|
||||||
|
#AddedSettings.tooltip:hover
|
||||||
|
> .tooltiptext {
|
||||||
transform: scale(0);
|
transform: scale(0);
|
||||||
}
|
}
|
||||||
.assessmenttooltip svg {
|
.assessmenttooltip svg {
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
import './documentload.scss';
|
import "./documentload.scss";
|
||||||
|
|||||||
+4
-2
@@ -15,7 +15,7 @@
|
|||||||
* along with EvenBetterSEQTA. If not, see <https://www.gnu.org/licenses/>.
|
* along with EvenBetterSEQTA. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,7 +25,9 @@
|
|||||||
span,
|
span,
|
||||||
body {
|
body {
|
||||||
color: white !important;
|
color: white !important;
|
||||||
text-shadow: 1px 1px 2px #161616, 0 0 1em #161616;
|
text-shadow:
|
||||||
|
1px 1px 2px #161616,
|
||||||
|
0 0 1em #161616;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
|||||||
+24
-9
@@ -758,7 +758,7 @@ ol > [data-label] {
|
|||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
[class*="Message__Message___"] > .uiFrameWrapper .iframeWrapper {
|
[class*="Message__Message___"] > .uiFrameWrapper .iframeWrapper {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
[class*="Viewer__newMessage___"] {
|
[class*="Viewer__newMessage___"] {
|
||||||
@@ -1375,10 +1375,13 @@ div > ol:has(.uiFileHandlerWrapper) {
|
|||||||
margin: 20px auto 0px;
|
margin: 20px auto 0px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.dark [class*="notifications__detailsBody___"] > [class*="notifications__subtitle___"] {
|
.dark
|
||||||
|
[class*="notifications__detailsBody___"]
|
||||||
|
> [class*="notifications__subtitle___"] {
|
||||||
color: #c1bcbc;
|
color: #c1bcbc;
|
||||||
}
|
}
|
||||||
[class*="notifications__detailsBody___"] > [class*="notifications__subtitle___"] {
|
[class*="notifications__detailsBody___"]
|
||||||
|
> [class*="notifications__subtitle___"] {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
[class*="notifications__notifications___"] > button {
|
[class*="notifications__notifications___"] > button {
|
||||||
@@ -1394,7 +1397,9 @@ div > ol:has(.uiFileHandlerWrapper) {
|
|||||||
height: 25px;
|
height: 25px;
|
||||||
width: 24px;
|
width: 24px;
|
||||||
}
|
}
|
||||||
[class*="notifications__notifications___"] > button > [class*="notifications__bubble___"] {
|
[class*="notifications__notifications___"]
|
||||||
|
> button
|
||||||
|
> [class*="notifications__bubble___"] {
|
||||||
background: var(--better-alert-highlight);
|
background: var(--better-alert-highlight);
|
||||||
width: 25px;
|
width: 25px;
|
||||||
height: 25px;
|
height: 25px;
|
||||||
@@ -1710,7 +1715,9 @@ ul {
|
|||||||
> [class*="SelectedAssessment__meta___"] {
|
> [class*="SelectedAssessment__meta___"] {
|
||||||
border-bottom: 1px solid var(--better-main);
|
border-bottom: 1px solid var(--better-main);
|
||||||
}
|
}
|
||||||
[class*="TabSet__TabSet___"] > ol[class*="TabSet__tabs___"] > li[class*="TabSet__selected___"] {
|
[class*="TabSet__TabSet___"]
|
||||||
|
> ol[class*="TabSet__tabs___"]
|
||||||
|
> li[class*="TabSet__selected___"] {
|
||||||
border-bottom-color: var(--better-main);
|
border-bottom-color: var(--better-main);
|
||||||
}
|
}
|
||||||
[class*="TabSet__TabSet___"] > ol[class*="TabSet__tabs___"] {
|
[class*="TabSet__TabSet___"] > ol[class*="TabSet__tabs___"] {
|
||||||
@@ -2181,7 +2188,9 @@ body {
|
|||||||
border-radius: 1600px;
|
border-radius: 1600px;
|
||||||
}
|
}
|
||||||
|
|
||||||
[class*="MessageList__MessageList___"] > ol > li[class*="MessageList__selected___"]
|
[class*="MessageList__MessageList___"]
|
||||||
|
> ol
|
||||||
|
> li[class*="MessageList__selected___"]
|
||||||
[class*="MessageList__unread___"] {
|
[class*="MessageList__unread___"] {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
@@ -2190,7 +2199,9 @@ body {
|
|||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
[class*="MessageList__MessageList___"] > ol > li[class*="MessageList__unread___"]::before,
|
[class*="MessageList__MessageList___"]
|
||||||
|
> ol
|
||||||
|
> li[class*="MessageList__unread___"]::before,
|
||||||
[class*="MessageList__MessageList___"] > ol > li::before {
|
[class*="MessageList__MessageList___"] > ol > li::before {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -2202,7 +2213,9 @@ body {
|
|||||||
transition: width 0.1s;
|
transition: width 0.1s;
|
||||||
}
|
}
|
||||||
|
|
||||||
[class*="MessageList__MessageList___"] > ol > li[class*="MessageList__unread___"]::before {
|
[class*="MessageList__MessageList___"]
|
||||||
|
> ol
|
||||||
|
> li[class*="MessageList__unread___"]::before {
|
||||||
width: 3px;
|
width: 3px;
|
||||||
}
|
}
|
||||||
.connectedNotificationsWrapper > div > button {
|
.connectedNotificationsWrapper > div > button {
|
||||||
@@ -2283,7 +2296,9 @@ body {
|
|||||||
background: var(--background-secondary);
|
background: var(--background-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
[class*="MessageList__MessageList___"] > ol > li[class*="MessageList__selected___"] {
|
[class*="MessageList__MessageList___"]
|
||||||
|
> ol
|
||||||
|
> li[class*="MessageList__selected___"] {
|
||||||
background: rgb(228 225 225);
|
background: rgb(228 225 225);
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,5 +36,7 @@
|
|||||||
transform-origin: 70% 0;
|
transform-origin: 70% 0;
|
||||||
will-change: opacity, transform;
|
will-change: opacity, transform;
|
||||||
transform: translateZ(0); // promotes GPU rendering
|
transform: translateZ(0); // promotes GPU rendering
|
||||||
transition: opacity 0.05s, transform 0.05s;
|
transition:
|
||||||
|
opacity 0.05s,
|
||||||
|
transform 0.05s;
|
||||||
}
|
}
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
padding-top: 2px;
|
padding-top: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sub:has(ul>li.hasChildren.active) > .nav > .back {
|
.sub:has(ul > li.hasChildren.active) > .nav > .back {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ html.transparencyEffects:not(.dark) {
|
|||||||
--background-secondary: rgba(229, 231, 235, 0.6);
|
--background-secondary: rgba(229, 231, 235, 0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
html.transparencyEffects {
|
html.transparencyEffects {
|
||||||
/* Background Fixes */
|
/* Background Fixes */
|
||||||
[class*="notifications__item___"],
|
[class*="notifications__item___"],
|
||||||
@@ -37,7 +36,8 @@ html.transparencyEffects {
|
|||||||
[class*="LabelList__selected___"],
|
[class*="LabelList__selected___"],
|
||||||
.buttonChecklist,
|
.buttonChecklist,
|
||||||
.pane,
|
.pane,
|
||||||
.legacy-root button, .legacy-root a,
|
.legacy-root button,
|
||||||
|
.legacy-root a,
|
||||||
[class*="MessageList__MessageList___"] {
|
[class*="MessageList__MessageList___"] {
|
||||||
backdrop-filter: blur(80px);
|
backdrop-filter: blur(80px);
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+7
-7
@@ -1,11 +1,11 @@
|
|||||||
declare module '*.mp4';
|
declare module "*.mp4";
|
||||||
declare module '*.woff';
|
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 "*.svelte";
|
||||||
|
|
||||||
declare module '*?inlineWorker' {
|
declare module "*?inlineWorker" {
|
||||||
const value: () => Worker;
|
const value: () => Worker;
|
||||||
export default value;
|
export default value;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import ColorPicker from "react-best-gradient-color-picker"
|
import ColorPicker from "react-best-gradient-color-picker";
|
||||||
import { useEffect, useRef, useState } from "react"
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { settingsState } from "@/seqta/utils/listeners/SettingsState.ts"
|
import { settingsState } from "@/seqta/utils/listeners/SettingsState.ts";
|
||||||
|
|
||||||
const defaultPresets = [
|
const defaultPresets = [
|
||||||
"linear-gradient(30deg, rgba(229,209,218,1) 0%, RGBA(235,169,202,1) 46%, rgba(214,155,162,1) 100%)",
|
"linear-gradient(30deg, rgba(229,209,218,1) 0%, RGBA(235,169,202,1) 46%, rgba(214,155,162,1) 100%)",
|
||||||
@@ -22,12 +22,12 @@ const defaultPresets = [
|
|||||||
"rgba(30, 64, 175, 0.89)",
|
"rgba(30, 64, 175, 0.89)",
|
||||||
"rgba(134, 25, 143, 1)",
|
"rgba(134, 25, 143, 1)",
|
||||||
"rgba(14, 165, 233, 0.9)",
|
"rgba(14, 165, 233, 0.9)",
|
||||||
]
|
];
|
||||||
|
|
||||||
interface PickerProps {
|
interface PickerProps {
|
||||||
customOnChange?: (color: string) => void
|
customOnChange?: (color: string) => void;
|
||||||
customState?: string
|
customState?: string;
|
||||||
savePresets?: boolean
|
savePresets?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Picker({
|
export default function Picker({
|
||||||
@@ -35,32 +35,44 @@ export default function Picker({
|
|||||||
customState,
|
customState,
|
||||||
savePresets = true,
|
savePresets = true,
|
||||||
}: PickerProps) {
|
}: PickerProps) {
|
||||||
const [customThemeColor, setCustomThemeColor] = useState<string | null>()
|
const [customThemeColor, setCustomThemeColor] = useState<string | null>();
|
||||||
const [presets, setPresets] = useState<string[]>()
|
const [presets, setPresets] = useState<string[]>();
|
||||||
|
|
||||||
const latestValuesRef = useRef({ customThemeColor, customOnChange, savePresets, presets });
|
const latestValuesRef = useRef({
|
||||||
|
customThemeColor,
|
||||||
|
customOnChange,
|
||||||
|
savePresets,
|
||||||
|
presets,
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (customState !== undefined && customState !== null) {
|
if (customState !== undefined && customState !== null) {
|
||||||
setCustomThemeColor(customState)
|
setCustomThemeColor(customState);
|
||||||
} else {
|
} else {
|
||||||
setCustomThemeColor(settingsState.selectedColor ?? null)
|
setCustomThemeColor(settingsState.selectedColor ?? null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (presets === undefined) {
|
if (presets === undefined) {
|
||||||
const savedPresets = localStorage.getItem("colorPickerPresets")
|
const savedPresets = localStorage.getItem("colorPickerPresets");
|
||||||
setPresets(savedPresets ? JSON.parse(savedPresets) : defaultPresets)
|
setPresets(savedPresets ? JSON.parse(savedPresets) : defaultPresets);
|
||||||
}
|
}
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
latestValuesRef.current = { customThemeColor, customOnChange, savePresets, presets };
|
latestValuesRef.current = {
|
||||||
|
customThemeColor,
|
||||||
|
customOnChange,
|
||||||
|
savePresets,
|
||||||
|
presets,
|
||||||
|
};
|
||||||
}, [customThemeColor, customOnChange, savePresets, presets]);
|
}, [customThemeColor, customOnChange, savePresets, presets]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
const { customThemeColor, customOnChange, savePresets, presets } = latestValuesRef.current;
|
const { customThemeColor, customOnChange, savePresets, presets } =
|
||||||
if (!(customThemeColor && !customOnChange && savePresets && presets)) return;
|
latestValuesRef.current;
|
||||||
|
if (!(customThemeColor && !customOnChange && savePresets && presets))
|
||||||
|
return;
|
||||||
|
|
||||||
// Only proceed if presets are different (avoid unnecessary updates)
|
// Only proceed if presets are different (avoid unnecessary updates)
|
||||||
const existingIndex = presets.indexOf(customThemeColor);
|
const existingIndex = presets.indexOf(customThemeColor);
|
||||||
@@ -79,15 +91,18 @@ export default function Picker({
|
|||||||
updatedPresets = [customThemeColor, ...presets].slice(0, 18);
|
updatedPresets = [customThemeColor, ...presets].slice(0, 18);
|
||||||
}
|
}
|
||||||
|
|
||||||
localStorage.setItem("colorPickerPresets", JSON.stringify(updatedPresets));
|
localStorage.setItem(
|
||||||
}
|
"colorPickerPresets",
|
||||||
}, [])
|
JSON.stringify(updatedPresets),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (customThemeColor && !customOnChange) {
|
if (customThemeColor && !customOnChange) {
|
||||||
settingsState.selectedColor = customThemeColor
|
settingsState.selectedColor = customThemeColor;
|
||||||
}
|
}
|
||||||
}, [customThemeColor, customOnChange])
|
}, [customThemeColor, customOnChange]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
@@ -97,12 +112,12 @@ export default function Picker({
|
|||||||
value={customThemeColor ?? ""}
|
value={customThemeColor ?? ""}
|
||||||
onChange={(color: string) => {
|
onChange={(color: string) => {
|
||||||
if (customOnChange) {
|
if (customOnChange) {
|
||||||
customOnChange(color)
|
customOnChange(color);
|
||||||
setCustomThemeColor(color)
|
setCustomThemeColor(color);
|
||||||
} else {
|
} else {
|
||||||
setCustomThemeColor(color)
|
setCustomThemeColor(color);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
.dark .switch[data-ison="true"],
|
.dark .switch[data-ison="true"],
|
||||||
.switch[data-ison="true"] {
|
.switch[data-ison="true"] {
|
||||||
background-color: #30D259;
|
background-color: #30d259;
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { type DBSchema, type IDBPDatabase, openDB } from 'idb';
|
import { type DBSchema, type IDBPDatabase, openDB } from "idb";
|
||||||
|
|
||||||
interface BackgroundDB extends DBSchema {
|
interface BackgroundDB extends DBSchema {
|
||||||
backgrounds: {
|
backgrounds: {
|
||||||
@@ -16,38 +16,46 @@ let db: IDBPDatabase<BackgroundDB> | null = null;
|
|||||||
export async function openDatabase(): Promise<IDBPDatabase<BackgroundDB>> {
|
export async function openDatabase(): Promise<IDBPDatabase<BackgroundDB>> {
|
||||||
if (db) return db;
|
if (db) return db;
|
||||||
|
|
||||||
db = await openDB<BackgroundDB>('BackgroundDB', 1, {
|
db = await openDB<BackgroundDB>("BackgroundDB", 1, {
|
||||||
upgrade(db: IDBPDatabase<BackgroundDB>) {
|
upgrade(db: IDBPDatabase<BackgroundDB>) {
|
||||||
db.createObjectStore('backgrounds', { keyPath: 'id' });
|
db.createObjectStore("backgrounds", { keyPath: "id" });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return db;
|
return db;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function readAllData(): Promise<Array<{ id: string; type: string; blob: Blob }>> {
|
export async function readAllData(): Promise<
|
||||||
|
Array<{ id: string; type: string; blob: Blob }>
|
||||||
|
> {
|
||||||
const db = await openDatabase();
|
const db = await openDatabase();
|
||||||
return db.getAll('backgrounds');
|
return db.getAll("backgrounds");
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function writeData(id: string, type: string, blob: Blob): Promise<void> {
|
export async function writeData(
|
||||||
|
id: string,
|
||||||
|
type: string,
|
||||||
|
blob: Blob,
|
||||||
|
): Promise<void> {
|
||||||
const db = await openDatabase();
|
const db = await openDatabase();
|
||||||
await db.put('backgrounds', { id, type, blob });
|
await db.put("backgrounds", { id, type, blob });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteData(id: string): Promise<void> {
|
export async function deleteData(id: string): Promise<void> {
|
||||||
const db = await openDatabase();
|
const db = await openDatabase();
|
||||||
await db.delete('backgrounds', id);
|
await db.delete("backgrounds", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function clearAllData(): Promise<void> {
|
export async function clearAllData(): Promise<void> {
|
||||||
const db = await openDatabase();
|
const db = await openDatabase();
|
||||||
await db.clear('backgrounds');
|
await db.clear("backgrounds");
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getDataById(id: string): Promise<{ id: string; type: string; blob: Blob } | undefined> {
|
export async function getDataById(
|
||||||
|
id: string,
|
||||||
|
): Promise<{ id: string; type: string; blob: Blob } | undefined> {
|
||||||
const db = await openDatabase();
|
const db = await openDatabase();
|
||||||
return db.get('backgrounds', id);
|
return db.get("backgrounds", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function closeDatabase(): void {
|
export function closeDatabase(): void {
|
||||||
@@ -59,15 +67,17 @@ export function closeDatabase(): void {
|
|||||||
|
|
||||||
// Helper function to check if IndexedDB is supported
|
// Helper function to check if IndexedDB is supported
|
||||||
export function isIndexedDBSupported(): boolean {
|
export function isIndexedDBSupported(): boolean {
|
||||||
return 'indexedDB' in window;
|
return "indexedDB" in window;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to check if there's enough storage space
|
// Helper function to check if there's enough storage space
|
||||||
export async function hasEnoughStorageSpace(requiredSpace: number): Promise<boolean> {
|
export async function hasEnoughStorageSpace(
|
||||||
if ('storage' in navigator && 'estimate' in navigator.storage) {
|
requiredSpace: number,
|
||||||
|
): Promise<boolean> {
|
||||||
|
if ("storage" in navigator && "estimate" in navigator.storage) {
|
||||||
const { quota, usage } = await navigator.storage.estimate();
|
const { quota, usage } = await navigator.storage.estimate();
|
||||||
if (quota !== undefined && usage !== undefined) {
|
if (quota !== undefined && usage !== undefined) {
|
||||||
return (quota - usage) > requiredSpace;
|
return quota - usage > requiredSpace;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If we can't determine, assume there's enough space
|
// If we can't determine, assume there's enough space
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class BackgroundUpdates {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public triggerUpdate(): void {
|
public triggerUpdate(): void {
|
||||||
this.listeners.forEach(callback => callback());
|
this.listeners.forEach((callback) => callback());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ type SettingsPopupCallback = () => void;
|
|||||||
* settingsPopup.addListener(() => {
|
* settingsPopup.addListener(() => {
|
||||||
* console.log('Settings popup closed');
|
* console.log('Settings popup closed');
|
||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
class SettingsPopup {
|
class SettingsPopup {
|
||||||
private static instance: SettingsPopup;
|
private static instance: SettingsPopup;
|
||||||
private listeners: Set<SettingsPopupCallback> = new Set();
|
private listeners: Set<SettingsPopupCallback> = new Set();
|
||||||
@@ -30,7 +30,7 @@ class SettingsPopup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public triggerClose(): void {
|
public triggerClose(): void {
|
||||||
this.listeners.forEach(callback => callback());
|
this.listeners.forEach((callback) => callback());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class ThemeUpdates {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public triggerUpdate(): void {
|
public triggerUpdate(): void {
|
||||||
this.listeners.forEach(callback => callback());
|
this.listeners.forEach((callback) => callback());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@import './components/ColourPicker.css';
|
@import "./components/ColourPicker.css";
|
||||||
|
|
||||||
@tailwind base;
|
@tailwind base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<title>BetterSEQTA+ Settings</title>
|
<title>BetterSEQTA+ Settings</title>
|
||||||
</head>
|
</head>
|
||||||
<body class="h-[600px]">
|
<body class="h-[600px]">
|
||||||
<div id="app" style="height: 100%;"></div>
|
<div id="app" style="height: 100%"></div>
|
||||||
<script type="module" src="./index.ts"></script>
|
<script type="module" src="./index.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
+15
-15
@@ -1,29 +1,29 @@
|
|||||||
import "./index.css"
|
import "./index.css";
|
||||||
import Settings from "./pages/settings.svelte"
|
import Settings from "./pages/settings.svelte";
|
||||||
import IconFamily from '@/resources/fonts/IconFamily.woff'
|
import IconFamily from "@/resources/fonts/IconFamily.woff";
|
||||||
import browser from "webextension-polyfill"
|
import browser from "webextension-polyfill";
|
||||||
import renderSvelte from "./main"
|
import renderSvelte from "./main";
|
||||||
|
|
||||||
function InjectCustomIcons() {
|
function InjectCustomIcons() {
|
||||||
console.info('[BetterSEQTA+] Injecting Icons')
|
console.info("[BetterSEQTA+] Injecting Icons");
|
||||||
|
|
||||||
const style = document.createElement('style')
|
const style = document.createElement("style");
|
||||||
style.setAttribute('type', 'text/css')
|
style.setAttribute("type", "text/css");
|
||||||
style.innerHTML = `
|
style.innerHTML = `
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'IconFamily';
|
font-family: 'IconFamily';
|
||||||
src: url('${browser.runtime.getURL(IconFamily)}') format('woff');
|
src: url('${browser.runtime.getURL(IconFamily)}') format('woff');
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}`
|
}`;
|
||||||
document.head.appendChild(style)
|
document.head.appendChild(style);
|
||||||
}
|
}
|
||||||
|
|
||||||
const mountPoint = document.getElementById('app')
|
const mountPoint = document.getElementById("app");
|
||||||
if (!mountPoint) {
|
if (!mountPoint) {
|
||||||
console.error('Mount point #app not found')
|
console.error("Mount point #app not found");
|
||||||
throw new Error('Mount point #app not found')
|
throw new Error("Mount point #app not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
InjectCustomIcons()
|
InjectCustomIcons();
|
||||||
renderSvelte(Settings, mountPoint, { standalone: true })
|
renderSvelte(Settings, mountPoint, { standalone: true });
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
import './index.css';
|
import "./index.css";
|
||||||
|
|
||||||
declare module "*.png";
|
declare module "*.png";
|
||||||
declare module "*.svg";
|
declare module "*.svg";
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { mount } from "svelte"
|
import { mount } from "svelte";
|
||||||
import type { SvelteComponent } from "svelte"
|
import type { SvelteComponent } from "svelte";
|
||||||
import style from './index.css?inline'
|
import style from "./index.css?inline";
|
||||||
|
|
||||||
export default function renderSvelte(
|
export default function renderSvelte(
|
||||||
Component: SvelteComponent | any,
|
Component: SvelteComponent | any,
|
||||||
@@ -13,11 +13,11 @@ export default function renderSvelte(
|
|||||||
standalone: false,
|
standalone: false,
|
||||||
...props,
|
...props,
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
const styleElement = document.createElement('style')
|
const styleElement = document.createElement("style");
|
||||||
styleElement.textContent = style
|
styleElement.textContent = style;
|
||||||
mountPoint.appendChild(styleElement)
|
mountPoint.appendChild(styleElement);
|
||||||
|
|
||||||
return app
|
return app;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ export interface SettingsList {
|
|||||||
title: string;
|
title: string;
|
||||||
id: number;
|
id: number;
|
||||||
description: string;
|
description: string;
|
||||||
Component: any; /* TODO: Give this a type */
|
Component: any /* TODO: Give this a type */;
|
||||||
props?: any;
|
props?: any;
|
||||||
}
|
}
|
||||||
@@ -1,36 +1,36 @@
|
|||||||
import type { Subscriber, Unsubscriber } from "svelte/store";
|
import type { Subscriber, Unsubscriber } from "svelte/store";
|
||||||
|
|
||||||
export class Standalone {
|
export class Standalone {
|
||||||
private static instance: Standalone;
|
private static instance: Standalone;
|
||||||
private _standalone = $state(false);
|
private _standalone = $state(false);
|
||||||
private subscribers = new Set<Subscriber<boolean>>();
|
private subscribers = new Set<Subscriber<boolean>>();
|
||||||
|
|
||||||
private constructor() {}
|
private constructor() {}
|
||||||
|
|
||||||
public static getInstance(): Standalone {
|
public static getInstance(): Standalone {
|
||||||
if (!Standalone.instance) {
|
if (!Standalone.instance) {
|
||||||
Standalone.instance = new Standalone();
|
Standalone.instance = new Standalone();
|
||||||
}
|
}
|
||||||
return Standalone.instance;
|
return Standalone.instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public setStandalone(value: boolean) {
|
public setStandalone(value: boolean) {
|
||||||
this._standalone = value;
|
this._standalone = value;
|
||||||
this.subscribers.forEach(subscriber => subscriber(value));
|
this.subscribers.forEach((subscriber) => subscriber(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
public get standalone() {
|
public get standalone() {
|
||||||
return this._standalone;
|
return this._standalone;
|
||||||
}
|
}
|
||||||
|
|
||||||
public subscribe(run: Subscriber<boolean>): Unsubscriber {
|
public subscribe(run: Subscriber<boolean>): Unsubscriber {
|
||||||
this.subscribers.add(run);
|
this.subscribers.add(run);
|
||||||
run(this._standalone);
|
run(this._standalone);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
this.subscribers.delete(run);
|
this.subscribers.delete(run);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const standalone = Standalone.getInstance();
|
export const standalone = Standalone.getInstance();
|
||||||
@@ -1,23 +1,31 @@
|
|||||||
import type { LoadedCustomTheme } from '@/types/CustomThemes';
|
import type { LoadedCustomTheme } from "@/types/CustomThemes";
|
||||||
|
|
||||||
export function generateImageId(): string {
|
export function generateImageId(): string {
|
||||||
return Math.random().toString(36).substr(2, 9);
|
return Math.random().toString(36).substr(2, 9);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleImageUpload(event: Event, theme: LoadedCustomTheme): Promise<LoadedCustomTheme> | LoadedCustomTheme {
|
export function handleImageUpload(
|
||||||
|
event: Event,
|
||||||
|
theme: LoadedCustomTheme,
|
||||||
|
): Promise<LoadedCustomTheme> | LoadedCustomTheme {
|
||||||
const input = event.target as HTMLInputElement;
|
const input = event.target as HTMLInputElement;
|
||||||
const file = input.files?.[0];
|
const file = input.files?.[0];
|
||||||
input.value = '';
|
input.value = "";
|
||||||
if (file) {
|
if (file) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = async () => {
|
reader.onload = async () => {
|
||||||
const imageBlob = await fetch(reader.result as string).then(res => res.blob());
|
const imageBlob = await fetch(reader.result as string).then((res) =>
|
||||||
|
res.blob(),
|
||||||
|
);
|
||||||
const imageId = generateImageId();
|
const imageId = generateImageId();
|
||||||
const variableName = `custom-image-${theme.CustomImages.length}`;
|
const variableName = `custom-image-${theme.CustomImages.length}`;
|
||||||
resolve({
|
resolve({
|
||||||
...theme,
|
...theme,
|
||||||
CustomImages: [...theme.CustomImages, { id: imageId, blob: imageBlob, variableName, url: null }],
|
CustomImages: [
|
||||||
|
...theme.CustomImages,
|
||||||
|
{ id: imageId, blob: imageBlob, variableName, url: null },
|
||||||
|
],
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file);
|
||||||
@@ -26,31 +34,43 @@ export function handleImageUpload(event: Event, theme: LoadedCustomTheme): Promi
|
|||||||
return theme;
|
return theme;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleRemoveImage(imageId: string, theme: LoadedCustomTheme): LoadedCustomTheme {
|
export function handleRemoveImage(
|
||||||
|
imageId: string,
|
||||||
|
theme: LoadedCustomTheme,
|
||||||
|
): LoadedCustomTheme {
|
||||||
return {
|
return {
|
||||||
...theme,
|
...theme,
|
||||||
CustomImages: theme.CustomImages.filter((image) => image.id !== imageId),
|
CustomImages: theme.CustomImages.filter((image) => image.id !== imageId),
|
||||||
} as LoadedCustomTheme;
|
} as LoadedCustomTheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleImageVariableChange(imageId: string, variableName: string, theme: LoadedCustomTheme): LoadedCustomTheme {
|
export function handleImageVariableChange(
|
||||||
|
imageId: string,
|
||||||
|
variableName: string,
|
||||||
|
theme: LoadedCustomTheme,
|
||||||
|
): LoadedCustomTheme {
|
||||||
return {
|
return {
|
||||||
...theme,
|
...theme,
|
||||||
CustomImages: theme.CustomImages.map((image) =>
|
CustomImages: theme.CustomImages.map((image) =>
|
||||||
image.id === imageId ? { ...image, variableName } : image
|
image.id === imageId ? { ...image, variableName } : image,
|
||||||
),
|
),
|
||||||
} as LoadedCustomTheme;
|
} as LoadedCustomTheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleCoverImageUpload(event: Event, theme: LoadedCustomTheme): Promise<LoadedCustomTheme> {
|
export function handleCoverImageUpload(
|
||||||
|
event: Event,
|
||||||
|
theme: LoadedCustomTheme,
|
||||||
|
): Promise<LoadedCustomTheme> {
|
||||||
const input = event.target as HTMLInputElement;
|
const input = event.target as HTMLInputElement;
|
||||||
const file = input.files?.[0];
|
const file = input.files?.[0];
|
||||||
input.value = '';
|
input.value = "";
|
||||||
if (file) {
|
if (file) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = async () => {
|
reader.onload = async () => {
|
||||||
const imageBlob = await fetch(reader.result as string).then(res => res.blob());
|
const imageBlob = await fetch(reader.result as string).then((res) =>
|
||||||
|
res.blob(),
|
||||||
|
);
|
||||||
resolve({ ...theme, coverImage: imageBlob });
|
resolve({ ...theme, coverImage: imageBlob });
|
||||||
};
|
};
|
||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file);
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
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'
|
import pkg from "../../package.json";
|
||||||
|
|
||||||
export const brave = createManifest({
|
export const brave = createManifest(
|
||||||
|
{
|
||||||
...baseManifest,
|
...baseManifest,
|
||||||
version: pkg.version,
|
version: pkg.version,
|
||||||
description: pkg.description,
|
description: pkg.description,
|
||||||
}, 'brave')
|
},
|
||||||
|
"brave",
|
||||||
|
);
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
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'
|
import pkg from "../../package.json";
|
||||||
|
|
||||||
export const chrome = createManifest({
|
export const chrome = createManifest(
|
||||||
|
{
|
||||||
...baseManifest,
|
...baseManifest,
|
||||||
version: pkg.version,
|
version: pkg.version,
|
||||||
description: pkg.description,
|
description: pkg.description,
|
||||||
}, 'chrome')
|
},
|
||||||
|
"chrome",
|
||||||
|
);
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
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'
|
import pkg from "../../package.json";
|
||||||
|
|
||||||
export const edge = createManifest({
|
export const edge = createManifest(
|
||||||
|
{
|
||||||
...baseManifest,
|
...baseManifest,
|
||||||
version: pkg.version,
|
version: pkg.version,
|
||||||
description: pkg.description,
|
description: pkg.description,
|
||||||
}, 'edge')
|
},
|
||||||
|
"edge",
|
||||||
|
);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
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'
|
import pkg from "../../package.json";
|
||||||
|
|
||||||
const updatedFirefoxManifest = {
|
const updatedFirefoxManifest = {
|
||||||
...baseManifest,
|
...baseManifest,
|
||||||
@@ -10,13 +10,13 @@ const updatedFirefoxManifest = {
|
|||||||
scripts: [baseManifest.background.service_worker],
|
scripts: [baseManifest.background.service_worker],
|
||||||
},
|
},
|
||||||
action: {
|
action: {
|
||||||
"default_popup": "interface/index.html#settings",
|
default_popup: "interface/index.html#settings",
|
||||||
},
|
},
|
||||||
browser_specific_settings: {
|
browser_specific_settings: {
|
||||||
gecko: {
|
gecko: {
|
||||||
id: pkg.author.email,
|
id: pkg.author.email,
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
export const firefox = createManifest(updatedFirefoxManifest, 'firefox')
|
export const firefox = createManifest(updatedFirefoxManifest, "firefox");
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
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'
|
import pkg from "../../package.json";
|
||||||
|
|
||||||
export const opera = createManifest({
|
export const opera = createManifest(
|
||||||
|
{
|
||||||
...baseManifest,
|
...baseManifest,
|
||||||
version: pkg.version,
|
version: pkg.version,
|
||||||
description: pkg.description,
|
description: pkg.description,
|
||||||
}, 'opera')
|
},
|
||||||
|
"opera",
|
||||||
|
);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
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'
|
import pkg from "../../package.json";
|
||||||
|
|
||||||
const updatedSafariManifest = {
|
const updatedSafariManifest = {
|
||||||
...baseManifest,
|
...baseManifest,
|
||||||
@@ -8,12 +8,12 @@ const updatedSafariManifest = {
|
|||||||
description: pkg.description,
|
description: pkg.description,
|
||||||
browser_specific_settings: {
|
browser_specific_settings: {
|
||||||
safari: {
|
safari: {
|
||||||
strict_min_version: '15.4',
|
strict_min_version: "15.4",
|
||||||
strict_max_version: '*',
|
strict_max_version: "*",
|
||||||
},
|
},
|
||||||
// ^^^ https://developer.apple.com/documentation/safariservices/safari_web_extensions/optimizing_your_web_extension_for_safari#3743239
|
// ^^^ https://developer.apple.com/documentation/safariservices/safari_web_extensions/optimizing_your_web_extension_for_safari#3743239
|
||||||
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/browser_specific_settings#safari_properties
|
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/browser_specific_settings#safari_properties
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
export const safari = createManifest(updatedSafariManifest, 'safari')
|
export const safari = createManifest(updatedSafariManifest, "safari");
|
||||||
|
|||||||
+42
-38
@@ -3,8 +3,8 @@ class ReactFiber {
|
|||||||
this.selector = selector;
|
this.selector = selector;
|
||||||
this.debug = options.debug || false;
|
this.debug = options.debug || false;
|
||||||
this.nodes = [...document.querySelectorAll(selector)]; // Support multiple elements
|
this.nodes = [...document.querySelectorAll(selector)]; // Support multiple elements
|
||||||
this.fibers = this.nodes.map(node => this.getFiberNode(node));
|
this.fibers = this.nodes.map((node) => this.getFiberNode(node));
|
||||||
this.components = this.fibers.map(fiber => this.getOwnerComponent(fiber));
|
this.components = this.fibers.map((fiber) => this.getOwnerComponent(fiber));
|
||||||
|
|
||||||
if (this.debug) {
|
if (this.debug) {
|
||||||
console.log("Selected Nodes:", this.nodes);
|
console.log("Selected Nodes:", this.nodes);
|
||||||
@@ -19,8 +19,10 @@ class ReactFiber {
|
|||||||
|
|
||||||
getFiberNode(node) {
|
getFiberNode(node) {
|
||||||
if (!node) return null;
|
if (!node) return null;
|
||||||
const fiberKey = Object.getOwnPropertyNames(node).find(name =>
|
const fiberKey = Object.getOwnPropertyNames(node).find(
|
||||||
name.startsWith('__reactFiber') || name.startsWith('__reactInternalInstance')
|
(name) =>
|
||||||
|
name.startsWith("__reactFiber") ||
|
||||||
|
name.startsWith("__reactInternalInstance"),
|
||||||
);
|
);
|
||||||
return fiberKey ? node[fiberKey] : null;
|
return fiberKey ? node[fiberKey] : null;
|
||||||
}
|
}
|
||||||
@@ -28,7 +30,10 @@ class ReactFiber {
|
|||||||
getOwnerComponent(fiberNode) {
|
getOwnerComponent(fiberNode) {
|
||||||
let current = fiberNode;
|
let current = fiberNode;
|
||||||
while (current) {
|
while (current) {
|
||||||
if (current.stateNode && (current.stateNode.setState || current.stateNode.forceUpdate)) {
|
if (
|
||||||
|
current.stateNode &&
|
||||||
|
(current.stateNode.setState || current.stateNode.forceUpdate)
|
||||||
|
) {
|
||||||
return current.stateNode;
|
return current.stateNode;
|
||||||
}
|
}
|
||||||
current = current.return;
|
current = current.return;
|
||||||
@@ -42,7 +47,7 @@ class ReactFiber {
|
|||||||
|
|
||||||
if (key === undefined) {
|
if (key === undefined) {
|
||||||
return state;
|
return state;
|
||||||
} else if (typeof key === 'string') {
|
} else if (typeof key === "string") {
|
||||||
return state?.[key];
|
return state?.[key];
|
||||||
} else if (Array.isArray(key)) {
|
} else if (Array.isArray(key)) {
|
||||||
const filteredState = {};
|
const filteredState = {};
|
||||||
@@ -57,23 +62,25 @@ class ReactFiber {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setState(update) {
|
setState(update) {
|
||||||
this.components.forEach(component => {
|
this.components.forEach((component) => {
|
||||||
if (component?.setState) {
|
if (component?.setState) {
|
||||||
if (typeof update === 'function') {
|
if (typeof update === "function") {
|
||||||
// Functional update
|
// Functional update
|
||||||
component.setState(prevState => {
|
component.setState((prevState) => {
|
||||||
const newState = update(prevState);
|
const newState = update(prevState);
|
||||||
if (this.debug) console.log("✅ Updated State (Functional):", newState);
|
if (this.debug)
|
||||||
|
console.log("✅ Updated State (Functional):", newState);
|
||||||
return newState;
|
return newState;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Object update (merge with existing state)
|
// Object update (merge with existing state)
|
||||||
component.setState(prevState => {
|
component.setState((prevState) => {
|
||||||
const newState = {
|
const newState = {
|
||||||
...prevState,
|
...prevState,
|
||||||
...update
|
...update,
|
||||||
};
|
};
|
||||||
if (this.debug) console.log("✅ Updated State (Object Merge):", newState);
|
if (this.debug)
|
||||||
|
console.log("✅ Updated State (Object Merge):", newState);
|
||||||
return newState;
|
return newState;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -93,7 +100,7 @@ class ReactFiber {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setProp(propName) {
|
setProp(propName) {
|
||||||
this.fibers.forEach(fiber => {
|
this.fibers.forEach((fiber) => {
|
||||||
if (fiber?.memoizedProps) {
|
if (fiber?.memoizedProps) {
|
||||||
fiber.memoizedProps[propName] = value;
|
fiber.memoizedProps[propName] = value;
|
||||||
}
|
}
|
||||||
@@ -102,7 +109,7 @@ class ReactFiber {
|
|||||||
}
|
}
|
||||||
|
|
||||||
forceUpdate() {
|
forceUpdate() {
|
||||||
this.components.forEach(component => {
|
this.components.forEach((component) => {
|
||||||
if (component?.forceUpdate) {
|
if (component?.forceUpdate) {
|
||||||
component.forceUpdate();
|
component.forceUpdate();
|
||||||
if (this.debug) console.log("🔄 Forced React Re-render");
|
if (this.debug) console.log("🔄 Forced React Re-render");
|
||||||
@@ -113,12 +120,12 @@ class ReactFiber {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function makeSerializable(obj) {
|
function makeSerializable(obj) {
|
||||||
if (typeof obj !== 'object' || obj === null) {
|
if (typeof obj !== "object" || obj === null) {
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(obj)) {
|
if (Array.isArray(obj)) {
|
||||||
return obj.map(item => makeSerializable(item));
|
return obj.map((item) => makeSerializable(item));
|
||||||
}
|
}
|
||||||
|
|
||||||
const serializableObj = {};
|
const serializableObj = {};
|
||||||
@@ -126,17 +133,17 @@ function makeSerializable(obj) {
|
|||||||
if (Object.hasOwn(obj, key)) {
|
if (Object.hasOwn(obj, key)) {
|
||||||
let value = obj[key];
|
let value = obj[key];
|
||||||
|
|
||||||
if (typeof value === 'function') {
|
if (typeof value === "function") {
|
||||||
value = '[Function]';
|
value = "[Function]";
|
||||||
} else if (value instanceof HTMLElement) {
|
} else if (value instanceof HTMLElement) {
|
||||||
value = {
|
value = {
|
||||||
type: 'HTMLElement',
|
type: "HTMLElement",
|
||||||
id: value.id,
|
id: value.id,
|
||||||
tagName: value.tagName
|
tagName: value.tagName,
|
||||||
}; // Replace DOM node with ID/tag info
|
}; // Replace DOM node with ID/tag info
|
||||||
} else if (typeof value === 'symbol') {
|
} else if (typeof value === "symbol") {
|
||||||
value = value.toString();
|
value = value.toString();
|
||||||
} else if (typeof value === 'object' && value !== null) {
|
} else if (typeof value === "object" && value !== null) {
|
||||||
value = makeSerializable(value);
|
value = makeSerializable(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,17 +153,11 @@ function makeSerializable(obj) {
|
|||||||
return serializableObj;
|
return serializableObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('message', (event) => {
|
window.addEventListener("message", (event) => {
|
||||||
if (event.data.type === "reactFiberRequest") {
|
if (event.data.type === "reactFiberRequest") {
|
||||||
const {
|
const { selector, action, payload, debug, messageId } = event.data;
|
||||||
selector,
|
|
||||||
action,
|
|
||||||
payload,
|
|
||||||
debug,
|
|
||||||
messageId
|
|
||||||
} = event.data;
|
|
||||||
const fiberInstance = ReactFiber.find(selector, {
|
const fiberInstance = ReactFiber.find(selector, {
|
||||||
debug
|
debug,
|
||||||
});
|
});
|
||||||
|
|
||||||
let response;
|
let response;
|
||||||
@@ -191,14 +192,17 @@ window.addEventListener('message', (event) => {
|
|||||||
response = null;
|
response = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response !== null && typeof response === 'object') {
|
if (response !== null && typeof response === "object") {
|
||||||
response = makeSerializable(response);
|
response = makeSerializable(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
window.postMessage({
|
window.postMessage(
|
||||||
type: "reactFiberResponse",
|
{
|
||||||
response,
|
type: "reactFiberResponse",
|
||||||
messageId,
|
response,
|
||||||
}, "*");
|
messageId,
|
||||||
|
},
|
||||||
|
"*",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1,7 +1,11 @@
|
|||||||
import { BasePlugin } from '../../core/settings';
|
import { BasePlugin } from "../../core/settings";
|
||||||
import { type Plugin } from '@/plugins/core/types';
|
import { type Plugin } from "@/plugins/core/types";
|
||||||
import { defineSettings, numberSetting, Setting } from '@/plugins/core/settingsHelpers';
|
import {
|
||||||
import styles from './styles.css?inline';
|
defineSettings,
|
||||||
|
numberSetting,
|
||||||
|
Setting,
|
||||||
|
} from "@/plugins/core/settingsHelpers";
|
||||||
|
import styles from "./styles.css?inline";
|
||||||
|
|
||||||
const settings = defineSettings({
|
const settings = defineSettings({
|
||||||
speed: numberSetting({
|
speed: numberSetting({
|
||||||
@@ -10,8 +14,8 @@ const settings = defineSettings({
|
|||||||
description: "Controls how fast the background moves",
|
description: "Controls how fast the background moves",
|
||||||
min: 0.1,
|
min: 0.1,
|
||||||
max: 2,
|
max: 2,
|
||||||
step: 0.05
|
step: 0.05,
|
||||||
})
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
class AnimatedBackgroundPluginClass extends BasePlugin<typeof settings> {
|
class AnimatedBackgroundPluginClass extends BasePlugin<typeof settings> {
|
||||||
@@ -22,10 +26,10 @@ class AnimatedBackgroundPluginClass extends BasePlugin<typeof settings> {
|
|||||||
const instance = new AnimatedBackgroundPluginClass();
|
const instance = new AnimatedBackgroundPluginClass();
|
||||||
|
|
||||||
const animatedBackgroundPlugin: Plugin<typeof settings> = {
|
const animatedBackgroundPlugin: Plugin<typeof settings> = {
|
||||||
id: 'animated-background',
|
id: "animated-background",
|
||||||
name: 'Animated Background',
|
name: "Animated Background",
|
||||||
description: 'Adds an animated background to BetterSEQTA+',
|
description: "Adds an animated background to BetterSEQTA+",
|
||||||
version: '1.0.0',
|
version: "1.0.0",
|
||||||
disableToggle: true,
|
disableToggle: true,
|
||||||
styles: styles,
|
styles: styles,
|
||||||
settings: instance.settings,
|
settings: instance.settings,
|
||||||
@@ -42,12 +46,12 @@ const animatedBackgroundPlugin: Plugin<typeof settings> = {
|
|||||||
const backgrounds = [
|
const backgrounds = [
|
||||||
{ classes: ["bg"] },
|
{ classes: ["bg"] },
|
||||||
{ classes: ["bg", "bg2"] },
|
{ classes: ["bg", "bg2"] },
|
||||||
{ classes: ["bg", "bg3"] }
|
{ classes: ["bg", "bg3"] },
|
||||||
];
|
];
|
||||||
|
|
||||||
backgrounds.forEach(({ classes }) => {
|
backgrounds.forEach(({ classes }) => {
|
||||||
const bk = document.createElement("div");
|
const bk = document.createElement("div");
|
||||||
classes.forEach(cls => bk.classList.add(cls));
|
classes.forEach((cls) => bk.classList.add(cls));
|
||||||
container.insertBefore(bk, menu);
|
container.insertBefore(bk, menu);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -55,20 +59,23 @@ const animatedBackgroundPlugin: Plugin<typeof settings> = {
|
|||||||
updateAnimationSpeed(api.settings.speed);
|
updateAnimationSpeed(api.settings.speed);
|
||||||
|
|
||||||
// Listen for speed changes
|
// Listen for speed changes
|
||||||
const speedUnregister = api.settings.onChange('speed', updateAnimationSpeed);
|
const speedUnregister = api.settings.onChange(
|
||||||
|
"speed",
|
||||||
|
updateAnimationSpeed,
|
||||||
|
);
|
||||||
|
|
||||||
// Return cleanup function
|
// Return cleanup function
|
||||||
return () => {
|
return () => {
|
||||||
speedUnregister.unregister();
|
speedUnregister.unregister();
|
||||||
// Remove background elements
|
// Remove background elements
|
||||||
const backgrounds = document.getElementsByClassName('bg');
|
const backgrounds = document.getElementsByClassName("bg");
|
||||||
Array.from(backgrounds).forEach(element => element.remove());
|
Array.from(backgrounds).forEach((element) => element.remove());
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function updateAnimationSpeed(speed: number) {
|
function updateAnimationSpeed(speed: number) {
|
||||||
const bgElements = document.getElementsByClassName('bg');
|
const bgElements = document.getElementsByClassName("bg");
|
||||||
Array.from(bgElements).forEach((element, index) => {
|
Array.from(bgElements).forEach((element, index) => {
|
||||||
const baseSpeed = index === 0 ? 3 : index === 1 ? 4 : 5;
|
const baseSpeed = index === 0 ? 3 : index === 1 ? 4 : 5;
|
||||||
(element as HTMLElement).style.animationDuration = `${baseSpeed / speed}s`;
|
(element as HTMLElement).style.animationDuration = `${baseSpeed / speed}s`;
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ export function CreateBackground() {
|
|||||||
const backgrounds = [
|
const backgrounds = [
|
||||||
{ classes: ["bg"] },
|
{ classes: ["bg"] },
|
||||||
{ classes: ["bg", "bg2"] },
|
{ classes: ["bg", "bg2"] },
|
||||||
{ classes: ["bg", "bg3"] }
|
{ classes: ["bg", "bg3"] },
|
||||||
];
|
];
|
||||||
|
|
||||||
backgrounds.forEach(({ classes }) => {
|
backgrounds.forEach(({ classes }) => {
|
||||||
const bk = document.createElement("div");
|
const bk = document.createElement("div");
|
||||||
classes.forEach(cls => bk.classList.add(cls));
|
classes.forEach((cls) => bk.classList.add(cls));
|
||||||
container.insertBefore(bk, menu);
|
container.insertBefore(bk, menu);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -2,5 +2,5 @@ export function RemoveBackground() {
|
|||||||
const backgrounds = document.getElementsByClassName("bg");
|
const backgrounds = document.getElementsByClassName("bg");
|
||||||
|
|
||||||
// Convert HTMLCollection to Array and remove each element
|
// Convert HTMLCollection to Array and remove each element
|
||||||
Array.from(backgrounds).forEach(element => element.remove());
|
Array.from(backgrounds).forEach((element) => element.remove());
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
import { BasePlugin } from "@/plugins/core/settings";
|
import { BasePlugin } from "@/plugins/core/settings";
|
||||||
import { booleanSetting, defineSettings, Setting } from "@/plugins/core/settingsHelpers";
|
import {
|
||||||
|
booleanSetting,
|
||||||
|
defineSettings,
|
||||||
|
Setting,
|
||||||
|
} from "@/plugins/core/settingsHelpers";
|
||||||
import { type Plugin } from "@/plugins/core/types";
|
import { type Plugin } from "@/plugins/core/types";
|
||||||
import stringToHTML from "@/seqta/utils/stringToHTML";
|
import stringToHTML from "@/seqta/utils/stringToHTML";
|
||||||
import { waitForElm } from "@/seqta/utils/waitForElm";
|
import { waitForElm } from "@/seqta/utils/waitForElm";
|
||||||
@@ -8,7 +12,7 @@ const settings = defineSettings({
|
|||||||
lettergrade: booleanSetting({
|
lettergrade: booleanSetting({
|
||||||
default: false,
|
default: false,
|
||||||
title: "Letter Grades",
|
title: "Letter Grades",
|
||||||
description: "Display the average as a letter instead of a percentage"
|
description: "Display the average as a letter instead of a percentage",
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -34,62 +38,105 @@ const assessmentsAveragePlugin: Plugin<typeof settings> = {
|
|||||||
"#main > .assessmentsWrapper .assessments [class*='AssessmentItem__AssessmentItem___']",
|
"#main > .assessmentsWrapper .assessments [class*='AssessmentItem__AssessmentItem___']",
|
||||||
true,
|
true,
|
||||||
10,
|
10,
|
||||||
1000
|
1000,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Helper function to find actual class names by their base pattern
|
// Helper function to find actual class names by their base pattern
|
||||||
const getClassByPattern = (element: Element | Document, basePattern: string): string => {
|
const getClassByPattern = (
|
||||||
|
element: Element | Document,
|
||||||
|
basePattern: string,
|
||||||
|
): string => {
|
||||||
// Find all classes on the element
|
// Find all classes on the element
|
||||||
const classes = Array.from(element.querySelectorAll('*'))
|
const classes = Array.from(element.querySelectorAll("*"))
|
||||||
.flatMap(el => Array.from(el.classList))
|
.flatMap((el) => Array.from(el.classList))
|
||||||
.filter(className => className.startsWith(basePattern));
|
.filter((className) => className.startsWith(basePattern));
|
||||||
|
|
||||||
return classes.length ? classes[0] : '';
|
return classes.length ? classes[0] : "";
|
||||||
};
|
};
|
||||||
|
|
||||||
// Find actual class names from the DOM
|
// Find actual class names from the DOM
|
||||||
const sampleAssessmentItem = document.querySelector("[class*='AssessmentItem__AssessmentItem___']");
|
const sampleAssessmentItem = document.querySelector(
|
||||||
|
"[class*='AssessmentItem__AssessmentItem___']",
|
||||||
|
);
|
||||||
if (!sampleAssessmentItem) return;
|
if (!sampleAssessmentItem) return;
|
||||||
|
|
||||||
// Extract all necessary class patterns from a sample assessment item
|
// Extract all necessary class patterns from a sample assessment item
|
||||||
const assessmentItemClass = Array.from(sampleAssessmentItem.classList)
|
const assessmentItemClass =
|
||||||
.find(c => c.startsWith('AssessmentItem__AssessmentItem___')) || '';
|
Array.from(sampleAssessmentItem.classList).find((c) =>
|
||||||
|
c.startsWith("AssessmentItem__AssessmentItem___"),
|
||||||
|
) || "";
|
||||||
|
|
||||||
const metaContainerClass = getClassByPattern(sampleAssessmentItem, 'AssessmentItem__metaContainer___');
|
const metaContainerClass = getClassByPattern(
|
||||||
const metaClass = getClassByPattern(sampleAssessmentItem, 'AssessmentItem__meta___');
|
sampleAssessmentItem,
|
||||||
const simpleResultClass = getClassByPattern(sampleAssessmentItem, 'AssessmentItem__simpleResult___');
|
"AssessmentItem__metaContainer___",
|
||||||
const titleClass = getClassByPattern(sampleAssessmentItem, 'AssessmentItem__title___');
|
);
|
||||||
|
const metaClass = getClassByPattern(
|
||||||
|
sampleAssessmentItem,
|
||||||
|
"AssessmentItem__meta___",
|
||||||
|
);
|
||||||
|
const simpleResultClass = getClassByPattern(
|
||||||
|
sampleAssessmentItem,
|
||||||
|
"AssessmentItem__simpleResult___",
|
||||||
|
);
|
||||||
|
const titleClass = getClassByPattern(
|
||||||
|
sampleAssessmentItem,
|
||||||
|
"AssessmentItem__title___",
|
||||||
|
);
|
||||||
|
|
||||||
// Get Thermoscore classes
|
// Get Thermoscore classes
|
||||||
const thermoscoreElement = document.querySelector("[class*='Thermoscore__Thermoscore___']");
|
const thermoscoreElement = document.querySelector(
|
||||||
|
"[class*='Thermoscore__Thermoscore___']",
|
||||||
|
);
|
||||||
if (!thermoscoreElement) return;
|
if (!thermoscoreElement) return;
|
||||||
|
|
||||||
const thermoscoreClass = Array.from(thermoscoreElement.classList)
|
const thermoscoreClass =
|
||||||
.find(c => c.startsWith('Thermoscore__Thermoscore___')) || '';
|
Array.from(thermoscoreElement.classList).find((c) =>
|
||||||
const fillClass = getClassByPattern(thermoscoreElement, 'Thermoscore__fill___');
|
c.startsWith("Thermoscore__Thermoscore___"),
|
||||||
const textClass = getClassByPattern(thermoscoreElement, 'Thermoscore__text___');
|
) || "";
|
||||||
|
const fillClass = getClassByPattern(
|
||||||
|
thermoscoreElement,
|
||||||
|
"Thermoscore__fill___",
|
||||||
|
);
|
||||||
|
const textClass = getClassByPattern(
|
||||||
|
thermoscoreElement,
|
||||||
|
"Thermoscore__text___",
|
||||||
|
);
|
||||||
|
|
||||||
// Find assessment list
|
// Find assessment list
|
||||||
const assessmentsList = document.querySelector("#main > .assessmentsWrapper .assessments [class*='AssessmentList__items___']");
|
const assessmentsList = document.querySelector(
|
||||||
|
"#main > .assessmentsWrapper .assessments [class*='AssessmentList__items___']",
|
||||||
|
);
|
||||||
if (!assessmentsList) return;
|
if (!assessmentsList) return;
|
||||||
|
|
||||||
const gradeElements = document.querySelectorAll("[class*='Thermoscore__text___']");
|
const gradeElements = document.querySelectorAll(
|
||||||
|
"[class*='Thermoscore__text___']",
|
||||||
|
);
|
||||||
if (!gradeElements.length) return;
|
if (!gradeElements.length) return;
|
||||||
|
|
||||||
// Parse and average grades
|
// Parse and average grades
|
||||||
const letterToNumber: Record<string, number> = {
|
const letterToNumber: Record<string, number> = {
|
||||||
"A+": 100, A: 95, "A-": 90,
|
"A+": 100,
|
||||||
"B+": 85, B: 80, "B-": 75,
|
A: 95,
|
||||||
"C+": 70, C: 65, "C-": 60,
|
"A-": 90,
|
||||||
"D+": 55, D: 50, "D-": 45,
|
"B+": 85,
|
||||||
"E+": 40, E: 35, "E-": 30,
|
B: 80,
|
||||||
|
"B-": 75,
|
||||||
|
"C+": 70,
|
||||||
|
C: 65,
|
||||||
|
"C-": 60,
|
||||||
|
"D+": 55,
|
||||||
|
D: 50,
|
||||||
|
"D-": 45,
|
||||||
|
"E+": 40,
|
||||||
|
E: 35,
|
||||||
|
"E-": 30,
|
||||||
F: 0,
|
F: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
function parseGrade(text: string): number {
|
function parseGrade(text: string): number {
|
||||||
const str = text.trim().toUpperCase();
|
const str = text.trim().toUpperCase();
|
||||||
if (str.includes("/")) {
|
if (str.includes("/")) {
|
||||||
const [raw, max] = str.split("/").map(n => parseFloat(n));
|
const [raw, max] = str.split("/").map((n) => parseFloat(n));
|
||||||
return (raw / max) * 100;
|
return (raw / max) * 100;
|
||||||
}
|
}
|
||||||
if (str.includes("%")) {
|
if (str.includes("%")) {
|
||||||
@@ -112,16 +159,23 @@ const assessmentsAveragePlugin: Plugin<typeof settings> = {
|
|||||||
|
|
||||||
const avg = total / count;
|
const avg = total / count;
|
||||||
const rounded = Math.ceil(avg / 5) * 5;
|
const rounded = Math.ceil(avg / 5) * 5;
|
||||||
const numberToLetter = Object.entries(letterToNumber).reduce((acc, [k, v]) => {
|
const numberToLetter = Object.entries(letterToNumber).reduce(
|
||||||
acc[v] = k;
|
(acc, [k, v]) => {
|
||||||
return acc;
|
acc[v] = k;
|
||||||
}, {} as Record<number, string>);
|
return acc;
|
||||||
|
},
|
||||||
|
{} as Record<number, string>,
|
||||||
|
);
|
||||||
|
|
||||||
const letterAvg = numberToLetter[rounded] ?? "N/A";
|
const letterAvg = numberToLetter[rounded] ?? "N/A";
|
||||||
const display = api.settings.lettergrade ? letterAvg : `${avg.toFixed(2)}%`;
|
const display = api.settings.lettergrade
|
||||||
|
? letterAvg
|
||||||
|
: `${avg.toFixed(2)}%`;
|
||||||
|
|
||||||
// Prevent duplicate
|
// Prevent duplicate
|
||||||
const existing = assessmentsList.querySelector(`[class*='AssessmentItem__title___']`);
|
const existing = assessmentsList.querySelector(
|
||||||
|
`[class*='AssessmentItem__title___']`,
|
||||||
|
);
|
||||||
if (existing?.textContent === "Subject Average") return;
|
if (existing?.textContent === "Subject Average") return;
|
||||||
|
|
||||||
// Use the dynamic class names in the HTML template
|
// Use the dynamic class names in the HTML template
|
||||||
@@ -144,7 +198,7 @@ const assessmentsAveragePlugin: Plugin<typeof settings> = {
|
|||||||
|
|
||||||
assessmentsList.insertBefore(averageElement!, assessmentsList.firstChild);
|
assessmentsList.insertBefore(averageElement!, assessmentsList.firstChild);
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default assessmentsAveragePlugin;
|
export default assessmentsAveragePlugin;
|
||||||
@@ -11,7 +11,7 @@ import { waitForElm } from "@/seqta/utils/waitForElm";
|
|||||||
import { runIndexing } from "../indexing/indexer";
|
import { runIndexing } from "../indexing/indexer";
|
||||||
import { initVectorSearch } from "../search/vector/vectorSearch";
|
import { initVectorSearch } from "../search/vector/vectorSearch";
|
||||||
import { cleanupSearchBar, mountSearchBar } from "./mountSearchBar";
|
import { cleanupSearchBar, mountSearchBar } from "./mountSearchBar";
|
||||||
import { IndexedDbManager } from 'embeddia';
|
import { IndexedDbManager } from "embeddia";
|
||||||
|
|
||||||
const settings = defineSettings({
|
const settings = defineSettings({
|
||||||
searchHotkey: stringSetting({
|
searchHotkey: stringSetting({
|
||||||
@@ -65,11 +65,10 @@ const globalSearchPlugin: Plugin<typeof settings> = {
|
|||||||
run: async (api) => {
|
run: async (api) => {
|
||||||
const appRef = { current: null };
|
const appRef = { current: null };
|
||||||
|
|
||||||
await IndexedDbManager.create(
|
await IndexedDbManager.create("embeddiaDB", "embeddiaObjectStore", {
|
||||||
'embeddiaDB',
|
primaryKey: "id",
|
||||||
'embeddiaObjectStore',
|
autoIncrement: false,
|
||||||
{ primaryKey: 'id', autoIncrement: false }
|
});
|
||||||
);
|
|
||||||
|
|
||||||
initVectorSearch();
|
initVectorSearch();
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { VectorWorkerManager } from "../indexing/worker/vectorWorkerManager";
|
|||||||
export function mountSearchBar(
|
export function mountSearchBar(
|
||||||
titleElement: Element,
|
titleElement: Element,
|
||||||
api: any,
|
api: any,
|
||||||
appRef: { current: any }
|
appRef: { current: any },
|
||||||
) {
|
) {
|
||||||
if (titleElement.querySelector(".search-trigger")) {
|
if (titleElement.querySelector(".search-trigger")) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -15,7 +15,10 @@ async function loadProgress<T = any>(jobId: string): Promise<T | undefined> {
|
|||||||
return rec?.progress as T | undefined;
|
return rec?.progress as T | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveProgress<T = any>(jobId: string, progress: T): Promise<void> {
|
async function saveProgress<T = any>(
|
||||||
|
jobId: string,
|
||||||
|
progress: T,
|
||||||
|
): Promise<void> {
|
||||||
await put(META_STORE, { jobId, progress }, `progress:${jobId}`);
|
await put(META_STORE, { jobId, progress }, `progress:${jobId}`);
|
||||||
}
|
}
|
||||||
/* ───────────────────────────────────────────── */
|
/* ───────────────────────────────────────────── */
|
||||||
@@ -67,7 +70,13 @@ function stopHeartbeat() {
|
|||||||
localStorage.removeItem(LOCK_KEY);
|
localStorage.removeItem(LOCK_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
function dispatchProgress(completed: number, total: number, indexing: boolean, status?: string, detail?: string) {
|
function dispatchProgress(
|
||||||
|
completed: number,
|
||||||
|
total: number,
|
||||||
|
indexing: boolean,
|
||||||
|
status?: string,
|
||||||
|
detail?: string,
|
||||||
|
) {
|
||||||
const event = new CustomEvent("indexing-progress", {
|
const event = new CustomEvent("indexing-progress", {
|
||||||
detail: { completed, total, indexing, status, detail },
|
detail: { completed, total, indexing, status, detail },
|
||||||
});
|
});
|
||||||
@@ -79,31 +88,41 @@ export async function loadAllStoredItems(): Promise<HydratedIndexItem[]> {
|
|||||||
const jobIds = Object.keys(jobs);
|
const jobIds = Object.keys(jobs);
|
||||||
|
|
||||||
for (const jobId of jobIds) {
|
for (const jobId of jobIds) {
|
||||||
try {
|
try {
|
||||||
const items = await getAll(jobId) as IndexItem[];
|
const items = (await getAll(jobId)) as IndexItem[];
|
||||||
const job = jobs[jobId];
|
const job = jobs[jobId];
|
||||||
const renderComponent = renderComponentMap[job.renderComponentId];
|
const renderComponent = renderComponentMap[job.renderComponentId];
|
||||||
|
|
||||||
if (!renderComponent) {
|
if (!renderComponent) {
|
||||||
console.warn(`Render component not found for job ${jobId} (ID: ${job.renderComponentId})`);
|
console.warn(
|
||||||
}
|
`Render component not found for job ${jobId} (ID: ${job.renderComponentId})`,
|
||||||
|
);
|
||||||
for (const item of items) {
|
|
||||||
// Ensure item has all required fields before pushing
|
|
||||||
if (item && item.id && item.text && item.category && item.actionId && job.renderComponentId) {
|
|
||||||
all.push({
|
|
||||||
...item,
|
|
||||||
renderComponent: renderComponent || undefined, // Assign undefined if not found
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.warn(`Skipping invalid item from job ${jobId}:`, item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Error loading items for job ${jobId}:`, error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const item of items) {
|
||||||
|
if (
|
||||||
|
item &&
|
||||||
|
item.id &&
|
||||||
|
item.text &&
|
||||||
|
item.category &&
|
||||||
|
item.actionId &&
|
||||||
|
job.renderComponentId
|
||||||
|
) {
|
||||||
|
all.push({
|
||||||
|
...item,
|
||||||
|
renderComponent: renderComponent || undefined,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.warn(`Skipping invalid item from job ${jobId}:`, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error loading items for job ${jobId}:`, error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
console.debug(`[Indexer] Loaded ${all.length} items from non-vector storage.`);
|
console.debug(
|
||||||
|
`[Indexer] Loaded ${all.length} items from non-vector storage.`,
|
||||||
|
);
|
||||||
return all;
|
return all;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,7 +148,12 @@ export async function runIndexing(): Promise<void> {
|
|||||||
|
|
||||||
// --- Step 1: Run Fetching/Storing Jobs (Main Thread) ---
|
// --- Step 1: Run Fetching/Storing Jobs (Main Thread) ---
|
||||||
for (const jobId of jobIds) {
|
for (const jobId of jobIds) {
|
||||||
dispatchProgress(completedJobs, totalSteps, true, `Running job: ${jobs[jobId].label}`);
|
dispatchProgress(
|
||||||
|
completedJobs,
|
||||||
|
totalSteps,
|
||||||
|
true,
|
||||||
|
`Running job: ${jobs[jobId].label}`,
|
||||||
|
);
|
||||||
const job = jobs[jobId];
|
const job = jobs[jobId];
|
||||||
const lastRun = await getLastRunMeta(jobId);
|
const lastRun = await getLastRunMeta(jobId);
|
||||||
|
|
||||||
@@ -139,26 +163,37 @@ export async function runIndexing(): Promise<void> {
|
|||||||
"color: gray",
|
"color: gray",
|
||||||
);
|
);
|
||||||
completedJobs++;
|
completedJobs++;
|
||||||
dispatchProgress(completedJobs, totalSteps, true, `Skipped job: ${job.label}`);
|
dispatchProgress(
|
||||||
|
completedJobs,
|
||||||
|
totalSteps,
|
||||||
|
true,
|
||||||
|
`Skipped job: ${job.label}`,
|
||||||
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getStoredItems = async (storeId?: string) => await getAll(storeId ?? jobId);
|
const getStoredItems = async (storeId?: string) =>
|
||||||
|
await getAll(storeId ?? jobId);
|
||||||
const setStoredItems = async (items: IndexItem[], storeId?: string) => {
|
const setStoredItems = async (items: IndexItem[], storeId?: string) => {
|
||||||
const targetStore = storeId ?? jobId;
|
const targetStore = storeId ?? jobId;
|
||||||
await clear(targetStore);
|
await clear(targetStore);
|
||||||
const validItems = items.filter(i => i && i.id);
|
const validItems = items.filter((i) => i && i.id);
|
||||||
if (validItems.length !== items.length) {
|
if (validItems.length !== items.length) {
|
||||||
console.warn(`[Indexer Job ${jobId} -> Store ${targetStore}] Filtered out ${items.length - validItems.length} invalid items before storing.`);
|
console.warn(
|
||||||
|
`[Indexer Job ${jobId} -> Store ${targetStore}] Filtered out ${items.length - validItems.length} invalid items before storing.`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
await Promise.all(validItems.map((i) => put(targetStore, i, i.id)));
|
await Promise.all(validItems.map((i) => put(targetStore, i, i.id)));
|
||||||
};
|
};
|
||||||
const addItem = async (item: IndexItem, storeId?: string) => {
|
const addItem = async (item: IndexItem, storeId?: string) => {
|
||||||
const targetStore = storeId ?? jobId;
|
const targetStore = storeId ?? jobId;
|
||||||
if (item && item.id) {
|
if (item && item.id) {
|
||||||
await put(targetStore, item, item.id);
|
await put(targetStore, item, item.id);
|
||||||
} else {
|
} else {
|
||||||
console.warn(`[Indexer Job ${jobId} -> Store ${targetStore}] Attempted to add invalid item:`, item);
|
console.warn(
|
||||||
|
`[Indexer Job ${jobId} -> Store ${targetStore}] Attempted to add invalid item:`,
|
||||||
|
item,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const removeItem = async (id: string, storeId?: string) => {
|
const removeItem = async (id: string, storeId?: string) => {
|
||||||
@@ -193,18 +228,30 @@ export async function runIndexing(): Promise<void> {
|
|||||||
// Hydrate items for vector processing
|
// Hydrate items for vector processing
|
||||||
const renderComponent = renderComponentMap[job.renderComponentId];
|
const renderComponent = renderComponentMap[job.renderComponentId];
|
||||||
if (!renderComponent) {
|
if (!renderComponent) {
|
||||||
console.warn(`Render component not found for job ${jobId} (ID: ${job.renderComponentId}) during hydration`);
|
console.warn(
|
||||||
|
`Render component not found for job ${jobId} (ID: ${job.renderComponentId}) during hydration`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
const hydratedItems = merged
|
const hydratedItems = merged
|
||||||
.filter(item => item && item.id && item.text && item.category && item.actionId && job.renderComponentId) // Filter invalid before hydrating
|
.filter(
|
||||||
.map((item) => ({
|
(item) =>
|
||||||
...item,
|
item &&
|
||||||
renderComponent: renderComponent || undefined, // Assign undefined if not found
|
item.id &&
|
||||||
}));
|
item.text &&
|
||||||
|
item.category &&
|
||||||
|
item.actionId &&
|
||||||
|
job.renderComponentId,
|
||||||
|
) // Filter invalid before hydrating
|
||||||
|
.map((item) => ({
|
||||||
|
...item,
|
||||||
|
renderComponent: renderComponent || undefined, // Assign undefined if not found
|
||||||
|
}));
|
||||||
|
|
||||||
if (hydratedItems.length !== merged.length) {
|
if (hydratedItems.length !== merged.length) {
|
||||||
console.warn(`[Indexer Job ${jobId}] Filtered out ${merged.length - hydratedItems.length} invalid items during hydration.`);
|
console.warn(
|
||||||
}
|
`[Indexer Job ${jobId}] Filtered out ${merged.length - hydratedItems.length} invalid items during hydration.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
allItemsFromJobs.push(...hydratedItems);
|
allItemsFromJobs.push(...hydratedItems);
|
||||||
|
|
||||||
@@ -218,7 +265,12 @@ export async function runIndexing(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
completedJobs++;
|
completedJobs++;
|
||||||
dispatchProgress(completedJobs, totalSteps, true, `Finished job: ${job.label}`);
|
dispatchProgress(
|
||||||
|
completedJobs,
|
||||||
|
totalSteps,
|
||||||
|
true,
|
||||||
|
`Finished job: ${job.label}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Step 2: Delegate Vectorization to Worker (Off Main Thread) ---
|
// --- Step 2: Delegate Vectorization to Worker (Off Main Thread) ---
|
||||||
@@ -233,54 +285,113 @@ export async function runIndexing(): Promise<void> {
|
|||||||
const workerManager = VectorWorkerManager.getInstance();
|
const workerManager = VectorWorkerManager.getInstance();
|
||||||
// Pass a progress callback to the worker manager
|
// Pass a progress callback to the worker manager
|
||||||
await workerManager.processItems(allItemsFromJobs, (progress) => {
|
await workerManager.processItems(allItemsFromJobs, (progress) => {
|
||||||
// Update overall progress based on worker feedback
|
// Update overall progress based on worker feedback
|
||||||
let detailMessage = progress.message || '';
|
let detailMessage = progress.message || "";
|
||||||
if (progress.status === 'processing' && progress.total && progress.processed !== undefined) {
|
if (
|
||||||
detailMessage = `Vectorizing: ${progress.processed} / ${progress.total}`;
|
progress.status === "processing" &&
|
||||||
// You could potentially update the 'completed' count more granularly here
|
progress.total &&
|
||||||
// For simplicity, we'll just update the detail message
|
progress.processed !== undefined
|
||||||
} else if (progress.status === 'complete') {
|
) {
|
||||||
detailMessage = "Vectorization complete";
|
detailMessage = `Vectorizing: ${progress.processed} / ${progress.total}`;
|
||||||
// Mark the vectorization step as complete
|
// You could potentially update the 'completed' count more granularly here
|
||||||
dispatchProgress(totalSteps, totalSteps, true, "Vectorization finished");
|
// For simplicity, we'll just update the detail message
|
||||||
} else if (progress.status === 'error') {
|
} else if (progress.status === "complete") {
|
||||||
detailMessage = `Vectorization error: ${progress.message}`;
|
detailMessage = "Vectorization complete";
|
||||||
dispatchProgress(completedJobs, totalSteps, true, "Vectorization failed", detailMessage); // Show error
|
// Mark the vectorization step as complete
|
||||||
} else if (progress.status === 'started') {
|
dispatchProgress(
|
||||||
detailMessage = `Vectorization started for ${progress.total} items`;
|
totalSteps,
|
||||||
} else if (progress.status === 'cancelled') {
|
totalSteps,
|
||||||
detailMessage = `Vectorization cancelled: ${progress.message}`;
|
true,
|
||||||
dispatchProgress(completedJobs, totalSteps, true, "Vectorization cancelled", detailMessage);
|
"Vectorization finished",
|
||||||
}
|
);
|
||||||
|
} else if (progress.status === "error") {
|
||||||
|
detailMessage = `Vectorization error: ${progress.message}`;
|
||||||
|
dispatchProgress(
|
||||||
|
completedJobs,
|
||||||
|
totalSteps,
|
||||||
|
true,
|
||||||
|
"Vectorization failed",
|
||||||
|
detailMessage,
|
||||||
|
); // Show error
|
||||||
|
} else if (progress.status === "started") {
|
||||||
|
detailMessage = `Vectorization started for ${progress.total} items`;
|
||||||
|
} else if (progress.status === "cancelled") {
|
||||||
|
detailMessage = `Vectorization cancelled: ${progress.message}`;
|
||||||
|
dispatchProgress(
|
||||||
|
completedJobs,
|
||||||
|
totalSteps,
|
||||||
|
true,
|
||||||
|
"Vectorization cancelled",
|
||||||
|
detailMessage,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Update the status detail
|
// Update the status detail
|
||||||
dispatchProgress(completedJobs, totalSteps, true, "Vectorization in progress", detailMessage);
|
dispatchProgress(
|
||||||
|
completedJobs,
|
||||||
|
totalSteps,
|
||||||
|
true,
|
||||||
|
"Vectorization in progress",
|
||||||
|
detailMessage,
|
||||||
|
);
|
||||||
|
|
||||||
// When worker signals completion of *its* task, mark the final step complete
|
// When worker signals completion of *its* task, mark the final step complete
|
||||||
if (progress.status === 'complete') {
|
if (progress.status === "complete") {
|
||||||
completedJobs++; // Increment completion count *after* vectorization finishes
|
completedJobs++; // Increment completion count *after* vectorization finishes
|
||||||
dispatchProgress(completedJobs, totalSteps, false, "Indexing finished"); // Set indexing to false
|
dispatchProgress(
|
||||||
} else if (progress.status === 'error' || progress.status === 'cancelled') {
|
completedJobs,
|
||||||
// Don't increment completed count on failure/cancel, just stop indexing indicator
|
totalSteps,
|
||||||
dispatchProgress(completedJobs, totalSteps, false, "Indexing stopped due to error/cancel");
|
false,
|
||||||
}
|
"Indexing finished",
|
||||||
|
); // Set indexing to false
|
||||||
|
} else if (
|
||||||
|
progress.status === "error" ||
|
||||||
|
progress.status === "cancelled"
|
||||||
|
) {
|
||||||
|
// Don't increment completed count on failure/cancel, just stop indexing indicator
|
||||||
|
dispatchProgress(
|
||||||
|
completedJobs,
|
||||||
|
totalSteps,
|
||||||
|
false,
|
||||||
|
"Indexing stopped due to error/cancel",
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
console.debug("%c[Indexer] Vectorization task sent to worker.", "color: green");
|
console.debug(
|
||||||
|
"%c[Indexer] Vectorization task sent to worker.",
|
||||||
|
"color: green",
|
||||||
|
);
|
||||||
// Note: runIndexing might return *before* vectorization is complete now.
|
// Note: runIndexing might return *before* vectorization is complete now.
|
||||||
// The progress updates will signal the true end state.
|
// The progress updates will signal the true end state.
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`%c[Indexer] ❌ Failed to send items to vector worker:`, "color: red", error);
|
console.error(
|
||||||
dispatchProgress(completedJobs, totalSteps, false, "Vectorization failed", String(error)); // Stop indexing indicator
|
`%c[Indexer] ❌ Failed to send items to vector worker:`,
|
||||||
|
"color: red",
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
dispatchProgress(
|
||||||
|
completedJobs,
|
||||||
|
totalSteps,
|
||||||
|
false,
|
||||||
|
"Vectorization failed",
|
||||||
|
String(error),
|
||||||
|
); // Stop indexing indicator
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
console.debug("%c[Indexer] No items to send for vectorization.", "color: gray");
|
console.debug(
|
||||||
|
"%c[Indexer] No items to send for vectorization.",
|
||||||
|
"color: gray",
|
||||||
|
);
|
||||||
// If no vectorization needed, indexing is done here.
|
// If no vectorization needed, indexing is done here.
|
||||||
completedJobs++; // Count the "skipped" vectorization step
|
completedJobs++; // Count the "skipped" vectorization step
|
||||||
dispatchProgress(completedJobs, totalSteps, false, "Indexing finished (no vectorization needed)");
|
dispatchProgress(
|
||||||
|
completedJobs,
|
||||||
|
totalSteps,
|
||||||
|
false,
|
||||||
|
"Indexing finished (no vectorization needed)",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Stop heartbeat ONLY when all jobs *and* the vectorization dispatch are done.
|
// Stop heartbeat ONLY when all jobs *and* the vectorization dispatch are done.
|
||||||
// The actual *completion* of vectorization is now asynchronous.
|
// The actual *completion* of vectorization is now asynchronous.
|
||||||
stopHeartbeat();
|
stopHeartbeat();
|
||||||
@@ -292,10 +403,10 @@ function mergeItems(existing: IndexItem[], incoming: IndexItem[]): IndexItem[] {
|
|||||||
const map = new Map<string, IndexItem>();
|
const map = new Map<string, IndexItem>();
|
||||||
// Prioritize incoming items if IDs clash
|
// Prioritize incoming items if IDs clash
|
||||||
for (const item of existing) {
|
for (const item of existing) {
|
||||||
if (item && item.id) map.set(item.id, item);
|
if (item && item.id) map.set(item.id, item);
|
||||||
}
|
}
|
||||||
for (const item of incoming) {
|
for (const item of incoming) {
|
||||||
if (item && item.id) map.set(item.id, item);
|
if (item && item.id) map.set(item.id, item);
|
||||||
}
|
}
|
||||||
return Array.from(map.values());
|
return Array.from(map.values());
|
||||||
}
|
}
|
||||||
@@ -49,21 +49,27 @@ const fetchNotifications = async () => {
|
|||||||
const fetchAssessmentName = async (
|
const fetchAssessmentName = async (
|
||||||
assessmentId: number,
|
assessmentId: number,
|
||||||
metaclassId: number,
|
metaclassId: number,
|
||||||
programmeId: number
|
programmeId: number,
|
||||||
): Promise<string> => {
|
): Promise<string> => {
|
||||||
const searchAssessment = (data: any): string | null => {
|
const searchAssessment = (data: any): string | null => {
|
||||||
// Search syllabus
|
// Search syllabus
|
||||||
for (const item of data.syllabus || []) {
|
for (const item of data.syllabus || []) {
|
||||||
const found = (item.assessments || []).find((a: any) => a.id === assessmentId);
|
const found = (item.assessments || []).find(
|
||||||
|
(a: any) => a.id === assessmentId,
|
||||||
|
);
|
||||||
if (found) return found.title;
|
if (found) return found.title;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search pending
|
// Search pending
|
||||||
const foundPending = (data.pending || []).find((a: any) => a.id === assessmentId);
|
const foundPending = (data.pending || []).find(
|
||||||
|
(a: any) => a.id === assessmentId,
|
||||||
|
);
|
||||||
if (foundPending) return foundPending.title;
|
if (foundPending) return foundPending.title;
|
||||||
|
|
||||||
// Search tasks
|
// Search tasks
|
||||||
const foundTask = (data.tasks || []).find((a: any) => a.id === assessmentId);
|
const foundTask = (data.tasks || []).find(
|
||||||
|
(a: any) => a.id === assessmentId,
|
||||||
|
);
|
||||||
if (foundTask) return foundTask.title;
|
if (foundTask) return foundTask.title;
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -88,11 +94,17 @@ const fetchAssessmentName = async (
|
|||||||
if (title) return title;
|
if (title) return title;
|
||||||
|
|
||||||
// Try from /upcoming if not found in /past
|
// Try from /upcoming if not found in /past
|
||||||
const upcomingPayload = await fetchAssessments("/seqta/student/assessment/list/upcoming");
|
const upcomingPayload = await fetchAssessments(
|
||||||
const foundUpcoming = (upcomingPayload || []).find((a: any) => a.id === assessmentId);
|
"/seqta/student/assessment/list/upcoming",
|
||||||
|
);
|
||||||
|
const foundUpcoming = (upcomingPayload || []).find(
|
||||||
|
(a: any) => a.id === assessmentId,
|
||||||
|
);
|
||||||
if (foundUpcoming) return foundUpcoming.title;
|
if (foundUpcoming) return foundUpcoming.title;
|
||||||
|
|
||||||
throw new Error(`Assessment with ID ${assessmentId} not found in past or upcoming.`);
|
throw new Error(
|
||||||
|
`Assessment with ID ${assessmentId} not found in past or upcoming.`,
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/* ------------- Job ------------- */
|
/* ------------- Job ------------- */
|
||||||
@@ -103,8 +115,9 @@ export const assessmentsJob: Job = {
|
|||||||
frequency: { type: "expiry", afterMs: 15 * 60 * 1000 },
|
frequency: { type: "expiry", afterMs: 15 * 60 * 1000 },
|
||||||
|
|
||||||
run: async (ctx) => {
|
run: async (ctx) => {
|
||||||
const progress =
|
const progress = (await ctx.getProgress<AssessmentsProgress>()) ?? {
|
||||||
(await ctx.getProgress<AssessmentsProgress>()) ?? { lastTs: 0 };
|
lastTs: 0,
|
||||||
|
};
|
||||||
|
|
||||||
let notifications: Notification[];
|
let notifications: Notification[];
|
||||||
try {
|
try {
|
||||||
@@ -116,8 +129,12 @@ export const assessmentsJob: Job = {
|
|||||||
|
|
||||||
const notificationIsIndexed = async (id: string): Promise<boolean> => {
|
const notificationIsIndexed = async (id: string): Promise<boolean> => {
|
||||||
const [inAssessments, inMessages] = await Promise.all([
|
const [inAssessments, inMessages] = await Promise.all([
|
||||||
ctx.getStoredItems("assessments").then((items) => items.some((i) => i.id === id)),
|
ctx
|
||||||
ctx.getStoredItems("messages").then((items) => items.some((i) => i.id === id)),
|
.getStoredItems("assessments")
|
||||||
|
.then((items) => items.some((i) => i.id === id)),
|
||||||
|
ctx
|
||||||
|
.getStoredItems("messages")
|
||||||
|
.then((items) => items.some((i) => i.id === id)),
|
||||||
]);
|
]);
|
||||||
return inAssessments || inMessages;
|
return inAssessments || inMessages;
|
||||||
};
|
};
|
||||||
@@ -131,7 +148,11 @@ export const assessmentsJob: Job = {
|
|||||||
if (notif.type === "coneqtassessments") {
|
if (notif.type === "coneqtassessments") {
|
||||||
const a = notif.coneqtAssessments;
|
const a = notif.coneqtAssessments;
|
||||||
|
|
||||||
const content = await fetchAssessmentName(a.assessmentID, a.metaclassID, a.programmeID);
|
const content = await fetchAssessmentName(
|
||||||
|
a.assessmentID,
|
||||||
|
a.metaclassID,
|
||||||
|
a.programmeID,
|
||||||
|
);
|
||||||
items.push({
|
items.push({
|
||||||
id,
|
id,
|
||||||
text: a.title,
|
text: a.title,
|
||||||
@@ -168,7 +189,7 @@ export const assessmentsJob: Job = {
|
|||||||
actionId: "message",
|
actionId: "message",
|
||||||
renderComponentId: "message",
|
renderComponentId: "message",
|
||||||
},
|
},
|
||||||
"messages"
|
"messages",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,12 +49,12 @@ export const messagesJob: Job = {
|
|||||||
|
|
||||||
run: async (ctx) => {
|
run: async (ctx) => {
|
||||||
const limit = 100;
|
const limit = 100;
|
||||||
const progress =
|
const progress = (await ctx.getProgress<MessagesProgress>()) ?? {
|
||||||
(await ctx.getProgress<MessagesProgress>()) ?? { offset: 0, done: false };
|
offset: 0,
|
||||||
|
done: false,
|
||||||
|
};
|
||||||
|
|
||||||
const existingIds = new Set(
|
const existingIds = new Set((await ctx.getStoredItems()).map((i) => i.id));
|
||||||
(await ctx.getStoredItems()).map((i) => i.id),
|
|
||||||
);
|
|
||||||
|
|
||||||
let consecutiveExisting = 0;
|
let consecutiveExisting = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
export function htmlToPlainText(rawHtml: string): string {
|
export function htmlToPlainText(rawHtml: string): string {
|
||||||
const parser = new DOMParser();
|
const parser = new DOMParser();
|
||||||
const doc = parser.parseFromString(rawHtml, 'text/html');
|
const doc = parser.parseFromString(rawHtml, "text/html");
|
||||||
const { body } = doc;
|
const { body } = doc;
|
||||||
|
|
||||||
body.querySelectorAll('script,style,template,noscript,meta,link').forEach(el => el.remove());
|
body
|
||||||
|
.querySelectorAll("script,style,template,noscript,meta,link")
|
||||||
|
.forEach((el) => el.remove());
|
||||||
|
|
||||||
body.querySelectorAll('.forward').forEach(el => {
|
body.querySelectorAll(".forward").forEach((el) => {
|
||||||
let n: ChildNode | null = el;
|
let n: ChildNode | null = el;
|
||||||
while (n) {
|
while (n) {
|
||||||
const next = n.nextSibling as ChildNode | null;
|
const next = n.nextSibling as ChildNode | null;
|
||||||
@@ -14,18 +16,18 @@ export function htmlToPlainText(rawHtml: string): string {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let text = body.innerText || '';
|
let text = body.innerText || "";
|
||||||
|
|
||||||
text = text
|
text = text
|
||||||
.replace(/\u00A0/g, ' ')
|
.replace(/\u00A0/g, " ")
|
||||||
.replace(/[ \t]{2,}/g, ' ')
|
.replace(/[ \t]{2,}/g, " ")
|
||||||
.replace(/\r\n|\r/g, '\n')
|
.replace(/\r\n|\r/g, "\n")
|
||||||
.replace(/\n{3,}/g, '\n\n')
|
.replace(/\n{3,}/g, "\n\n")
|
||||||
.replace(/^[.\w#][^{]{0,100}\{[^}]*\}$/gm, '')
|
.replace(/^[.\w#][^{]{0,100}\{[^}]*\}$/gm, "")
|
||||||
.split('\n')
|
.split("\n")
|
||||||
.map(line => line.trimEnd())
|
.map((line) => line.trimEnd())
|
||||||
.filter(line => line.trim().length > 0 || line === '')
|
.filter((line) => line.trim().length > 0 || line === "")
|
||||||
.join('\n')
|
.join("\n")
|
||||||
.trim();
|
.trim();
|
||||||
|
|
||||||
return text;
|
return text;
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
import {
|
import { EmbeddingIndex, getEmbedding, initializeModel } from "embeddia";
|
||||||
EmbeddingIndex,
|
|
||||||
getEmbedding,
|
|
||||||
initializeModel,
|
|
||||||
} from "embeddia";
|
|
||||||
import type { HydratedIndexItem } from "../types";
|
import type { HydratedIndexItem } from "../types";
|
||||||
|
|
||||||
let vectorIndex: EmbeddingIndex | null = null;
|
let vectorIndex: EmbeddingIndex | null = null;
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { refreshVectorCache } from '../../search/vector/vectorSearch';
|
import { refreshVectorCache } from "../../search/vector/vectorSearch";
|
||||||
import type { HydratedIndexItem } from '../types';
|
import type { HydratedIndexItem } from "../types";
|
||||||
import vectorWorker from './vectorWorker.ts?inlineWorker';
|
import vectorWorker from "./vectorWorker.ts?inlineWorker";
|
||||||
import type { SearchResult } from 'embeddia';
|
import type { SearchResult } from "embeddia";
|
||||||
|
|
||||||
export type ProgressCallback = (data: {
|
export type ProgressCallback = (data: {
|
||||||
status: 'started' | 'processing' | 'complete' | 'error' | 'cancelled';
|
status: "started" | "processing" | "complete" | "error" | "cancelled";
|
||||||
total?: number;
|
total?: number;
|
||||||
processed?: number;
|
processed?: number;
|
||||||
message?: string;
|
message?: string;
|
||||||
@@ -16,10 +16,21 @@ export class VectorWorkerManager {
|
|||||||
private isInitialized = false;
|
private isInitialized = false;
|
||||||
private readyPromise: Promise<void> | null = null; // To await initialization
|
private readyPromise: Promise<void> | null = null; // To await initialization
|
||||||
private progressCallback: ProgressCallback | null = null;
|
private progressCallback: ProgressCallback | null = null;
|
||||||
private searchPromises = new Map<string, { resolve: (value: SearchResult[]) => void, reject: (reason?: any) => void, timer: NodeJS.Timeout }>();
|
private searchPromises = new Map<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
resolve: (value: SearchResult[]) => void;
|
||||||
|
reject: (reason?: any) => void;
|
||||||
|
timer: NodeJS.Timeout;
|
||||||
|
}
|
||||||
|
>();
|
||||||
private debounceTimer: NodeJS.Timeout | null = null;
|
private debounceTimer: NodeJS.Timeout | null = null;
|
||||||
private lastSearchParams: { query: string; topK: number; resolve: (results: SearchResult[]) => void, reject: (reason?: any) => void } | null = null;
|
private lastSearchParams: {
|
||||||
|
query: string;
|
||||||
|
topK: number;
|
||||||
|
resolve: (results: SearchResult[]) => void;
|
||||||
|
reject: (reason?: any) => void;
|
||||||
|
} | null = null;
|
||||||
|
|
||||||
private constructor() {
|
private constructor() {
|
||||||
// Start initialization immediately, but allow awaiting it
|
// Start initialization immediately, but allow awaiting it
|
||||||
@@ -39,101 +50,115 @@ export class VectorWorkerManager {
|
|||||||
if (this.readyPromise) return this.readyPromise;
|
if (this.readyPromise) return this.readyPromise;
|
||||||
|
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
// Create the worker
|
// Create the worker
|
||||||
this.worker = vectorWorker();
|
this.worker = vectorWorker();
|
||||||
|
|
||||||
console.log('Worker initialized', this.worker);
|
console.log("Worker initialized", this.worker);
|
||||||
|
|
||||||
const timeout = setTimeout(() => {
|
const timeout = setTimeout(() => {
|
||||||
console.error('Vector worker initialization timed out');
|
console.error("Vector worker initialization timed out");
|
||||||
this.worker?.terminate(); // Clean up worker if it exists
|
this.worker?.terminate(); // Clean up worker if it exists
|
||||||
this.worker = null;
|
this.worker = null;
|
||||||
this.isInitialized = false; // Ensure state reflects failure
|
this.isInitialized = false; // Ensure state reflects failure
|
||||||
this.readyPromise = null; // Allow retrying init later
|
this.readyPromise = null; // Allow retrying init later
|
||||||
reject(new Error('Worker initialization timed out'));
|
reject(new Error("Worker initialization timed out"));
|
||||||
}, 10000); // Increased timeout
|
}, 10000); // Increased timeout
|
||||||
|
|
||||||
// Set up message handling
|
// Set up message handling
|
||||||
this.worker!.addEventListener('message', (e) => {
|
this.worker!.addEventListener("message", (e) => {
|
||||||
const { type, data } = e.data;
|
const { type, data } = e.data;
|
||||||
console.debug("Message from vector worker:", type, data);
|
console.debug("Message from vector worker:", type, data);
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'ready':
|
case "ready":
|
||||||
this.isInitialized = true;
|
this.isInitialized = true;
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
console.debug('Vector worker initialized and ready.');
|
console.debug("Vector worker initialized and ready.");
|
||||||
resolve(); // Resolve the init promise
|
resolve(); // Resolve the init promise
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'progress':
|
case "progress":
|
||||||
if (this.progressCallback) {
|
if (this.progressCallback) {
|
||||||
this.progressCallback(data);
|
this.progressCallback(data);
|
||||||
|
|
||||||
if (data.status === 'complete') {
|
if (data.status === "complete") {
|
||||||
refreshVectorCache();
|
refreshVectorCache();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case 'searchResults':
|
case "searchResults":
|
||||||
const searchInfo = this.searchPromises.get(data.messageId);
|
const searchInfo = this.searchPromises.get(data.messageId);
|
||||||
if (searchInfo) {
|
if (searchInfo) {
|
||||||
clearTimeout(searchInfo.timer); // Clear timeout on success
|
clearTimeout(searchInfo.timer); // Clear timeout on success
|
||||||
searchInfo.resolve(data.results);
|
searchInfo.resolve(data.results);
|
||||||
this.searchPromises.delete(data.messageId);
|
this.searchPromises.delete(data.messageId);
|
||||||
} else {
|
} else {
|
||||||
console.warn('Received search results for unknown messageId:', data.messageId);
|
console.warn(
|
||||||
}
|
"Received search results for unknown messageId:",
|
||||||
break;
|
data.messageId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case 'searchError':
|
case "searchError":
|
||||||
const errorInfo = this.searchPromises.get(data.messageId);
|
const errorInfo = this.searchPromises.get(data.messageId);
|
||||||
if (errorInfo) {
|
if (errorInfo) {
|
||||||
clearTimeout(errorInfo.timer); // Clear timeout on error
|
clearTimeout(errorInfo.timer); // Clear timeout on error
|
||||||
errorInfo.reject(new Error(data.error));
|
errorInfo.reject(new Error(data.error));
|
||||||
this.searchPromises.delete(data.messageId);
|
this.searchPromises.delete(data.messageId);
|
||||||
} else {
|
} else {
|
||||||
console.warn('Received search error for unknown messageId:', data.messageId);
|
console.warn(
|
||||||
}
|
"Received search error for unknown messageId:",
|
||||||
break;
|
data.messageId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case 'searchCancelled':
|
case "searchCancelled":
|
||||||
const cancelledInfo = this.searchPromises.get(data.messageId);
|
const cancelledInfo = this.searchPromises.get(data.messageId);
|
||||||
if (cancelledInfo) {
|
if (cancelledInfo) {
|
||||||
clearTimeout(cancelledInfo.timer); // Clear timeout on cancel
|
clearTimeout(cancelledInfo.timer); // Clear timeout on cancel
|
||||||
// Reject with a specific cancellation error or resolve with empty? Let's reject.
|
// Reject with a specific cancellation error or resolve with empty? Let's reject.
|
||||||
cancelledInfo.reject(new Error('Search cancelled by worker'));
|
cancelledInfo.reject(new Error("Search cancelled by worker"));
|
||||||
this.searchPromises.delete(data.messageId);
|
this.searchPromises.delete(data.messageId);
|
||||||
} else {
|
} else {
|
||||||
console.debug('Received cancellation for unknown messageId:', data.messageId);
|
console.debug(
|
||||||
}
|
"Received cancellation for unknown messageId:",
|
||||||
break;
|
data.messageId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
console.warn('Unknown message from worker:', type, data);
|
console.warn("Unknown message from worker:", type, data);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Initialize the worker
|
// Initialize the worker
|
||||||
this.worker!.postMessage({ type: 'init' });
|
this.worker!.postMessage({ type: "init" });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensures worker is ready before proceeding
|
// Ensures worker is ready before proceeding
|
||||||
private async ensureReady() {
|
private async ensureReady() {
|
||||||
if (!this.readyPromise) {
|
if (!this.readyPromise) {
|
||||||
// If init wasn't called or failed, try again
|
// If init wasn't called or failed, try again
|
||||||
console.warn("Worker not initialized, attempting init...");
|
console.warn("Worker not initialized, attempting init...");
|
||||||
this.readyPromise = this.initWorker();
|
this.readyPromise = this.initWorker();
|
||||||
}
|
}
|
||||||
await this.readyPromise;
|
await this.readyPromise;
|
||||||
if (!this.isInitialized || !this.worker) {
|
if (!this.isInitialized || !this.worker) {
|
||||||
throw new Error("Vector Worker is not available after initialization attempt.");
|
throw new Error(
|
||||||
|
"Vector Worker is not available after initialization attempt.",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async processItems(items: HydratedIndexItem[], onProgress?: ProgressCallback) {
|
async processItems(
|
||||||
|
items: HydratedIndexItem[],
|
||||||
|
onProgress?: ProgressCallback,
|
||||||
|
) {
|
||||||
await this.ensureReady(); // Wait for worker to be ready
|
await this.ensureReady(); // Wait for worker to be ready
|
||||||
|
|
||||||
this.progressCallback = onProgress || null;
|
this.progressCallback = onProgress || null;
|
||||||
@@ -146,13 +171,16 @@ export class VectorWorkerManager {
|
|||||||
const serialisableItems = items.map(({ renderComponent, ...rest }) => rest);
|
const serialisableItems = items.map(({ renderComponent, ...rest }) => rest);
|
||||||
|
|
||||||
this.worker!.postMessage({
|
this.worker!.postMessage({
|
||||||
type: 'process',
|
type: "process",
|
||||||
data: { items: serialisableItems }
|
data: { items: serialisableItems },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Public search method
|
// Public search method
|
||||||
public async search(query: string, topK: number = 10): Promise<SearchResult[]> {
|
public async search(
|
||||||
|
query: string,
|
||||||
|
topK: number = 10,
|
||||||
|
): Promise<SearchResult[]> {
|
||||||
await this.ensureReady();
|
await this.ensureReady();
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@@ -167,54 +195,62 @@ export class VectorWorkerManager {
|
|||||||
// Set a timeout for the search operation itself
|
// Set a timeout for the search operation itself
|
||||||
const searchTimeout = 10000; // e.g., 10 seconds
|
const searchTimeout = 10000; // e.g., 10 seconds
|
||||||
const searchTimer = setTimeout(() => {
|
const searchTimer = setTimeout(() => {
|
||||||
if (this.searchPromises.has(messageId)) {
|
if (this.searchPromises.has(messageId)) {
|
||||||
console.error(`Search timed out for messageId: ${messageId}`);
|
console.error(`Search timed out for messageId: ${messageId}`);
|
||||||
currentParams.reject(new Error(`Search timed out after ${searchTimeout}ms`));
|
currentParams.reject(
|
||||||
this.searchPromises.delete(messageId);
|
new Error(`Search timed out after ${searchTimeout}ms`),
|
||||||
}
|
);
|
||||||
|
this.searchPromises.delete(messageId);
|
||||||
|
}
|
||||||
}, searchTimeout);
|
}, searchTimeout);
|
||||||
|
|
||||||
|
this.searchPromises.set(messageId, {
|
||||||
|
resolve: currentParams.resolve,
|
||||||
|
reject: currentParams.reject,
|
||||||
|
timer: searchTimer,
|
||||||
|
});
|
||||||
|
|
||||||
this.searchPromises.set(messageId, { resolve: currentParams.resolve, reject: currentParams.reject, timer: searchTimer });
|
console.debug(
|
||||||
|
`Sending search request (ID: ${messageId}) to worker: "${currentParams.query}"`,
|
||||||
console.debug(`Sending search request (ID: ${messageId}) to worker: "${currentParams.query}"`);
|
);
|
||||||
console.log(this.worker);
|
console.log(this.worker);
|
||||||
this.worker.postMessage({
|
this.worker.postMessage({
|
||||||
type: "search",
|
type: "search",
|
||||||
data: { query: currentParams.query, topK: currentParams.topK },
|
data: { query: currentParams.query, topK: currentParams.topK },
|
||||||
messageId
|
messageId,
|
||||||
});
|
});
|
||||||
} else if (this.lastSearchParams) {
|
} else if (this.lastSearchParams) {
|
||||||
// This case might happen if ensureReady failed but didn't throw
|
// This case might happen if ensureReady failed but didn't throw
|
||||||
console.error("Worker unavailable when trying to send search request.");
|
console.error("Worker unavailable when trying to send search request.");
|
||||||
this.lastSearchParams.reject(new Error("Worker unavailable for search"));
|
this.lastSearchParams.reject(
|
||||||
this.lastSearchParams = null;
|
new Error("Worker unavailable for search"),
|
||||||
this.debounceTimer = null;
|
);
|
||||||
|
this.lastSearchParams = null;
|
||||||
|
this.debounceTimer = null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method to cancel all pending/debounced searches
|
// Method to cancel all pending/debounced searches
|
||||||
private cancelAllSearches(reason: string = "Cancelled") {
|
private cancelAllSearches(reason: string = "Cancelled") {
|
||||||
if (this.debounceTimer) {
|
if (this.debounceTimer) {
|
||||||
clearTimeout(this.debounceTimer);
|
clearTimeout(this.debounceTimer);
|
||||||
this.debounceTimer = null;
|
this.debounceTimer = null;
|
||||||
if (this.lastSearchParams) {
|
if (this.lastSearchParams) {
|
||||||
this.lastSearchParams.reject(new Error(`Search cancelled: ${reason}`));
|
this.lastSearchParams.reject(new Error(`Search cancelled: ${reason}`));
|
||||||
this.lastSearchParams = null;
|
this.lastSearchParams = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// We might also want to tell the worker to cancel its *current* search
|
// We might also want to tell the worker to cancel its *current* search
|
||||||
// if it supports it, but this requires worker modification.
|
// if it supports it, but this requires worker modification.
|
||||||
// For now, just reject pending promises in the manager.
|
// For now, just reject pending promises in the manager.
|
||||||
for (const [messageId, promiseInfo] of this.searchPromises.entries()) {
|
for (const [messageId, promiseInfo] of this.searchPromises.entries()) {
|
||||||
clearTimeout(promiseInfo.timer);
|
clearTimeout(promiseInfo.timer);
|
||||||
promiseInfo.reject(new Error(`Search cancelled: ${reason}`));
|
promiseInfo.reject(new Error(`Search cancelled: ${reason}`));
|
||||||
this.searchPromises.delete(messageId);
|
this.searchPromises.delete(messageId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
terminate() {
|
terminate() {
|
||||||
console.debug("Terminating Vector Worker Manager...");
|
console.debug("Terminating Vector Worker Manager...");
|
||||||
this.cancelAllSearches("Worker terminated"); // Cancel pending searches
|
this.cancelAllSearches("Worker terminated"); // Cancel pending searches
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { EmbeddingIndex, getEmbedding, initializeModel } from 'embeddia';
|
import { EmbeddingIndex, getEmbedding, initializeModel } from "embeddia";
|
||||||
import type { HydratedIndexItem } from '../../indexing/types';
|
import type { HydratedIndexItem } from "../../indexing/types";
|
||||||
import type { SearchResult } from 'embeddia';
|
import type { SearchResult } from "embeddia";
|
||||||
|
|
||||||
let vectorIndex: EmbeddingIndex | null = null;
|
let vectorIndex: EmbeddingIndex | null = null;
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ export async function initVectorSearch() {
|
|||||||
vectorIndex = new EmbeddingIndex([]);
|
vectorIndex = new EmbeddingIndex([]);
|
||||||
vectorIndex.preloadIndexedDB();
|
vectorIndex.preloadIndexedDB();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error initializing vector search', e);
|
console.error("Error initializing vector search", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,15 +18,18 @@ export interface VectorSearchResult extends SearchResult {
|
|||||||
object: HydratedIndexItem & { embedding: number[] };
|
object: HydratedIndexItem & { embedding: number[] };
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function searchVectors(query: string, topK: number = 10): Promise<VectorSearchResult[]> {
|
export async function searchVectors(
|
||||||
|
query: string,
|
||||||
|
topK: number = 10,
|
||||||
|
): Promise<VectorSearchResult[]> {
|
||||||
if (!vectorIndex) await initVectorSearch();
|
if (!vectorIndex) await initVectorSearch();
|
||||||
|
|
||||||
const queryEmbedding = await getEmbedding(query.slice(0, 100));
|
const queryEmbedding = await getEmbedding(query.slice(0, 100));
|
||||||
|
|
||||||
const results = await vectorIndex!.search(queryEmbedding, {
|
const results = await vectorIndex!.search(queryEmbedding, {
|
||||||
topK,
|
topK,
|
||||||
useStorage: 'indexedDB',
|
useStorage: "indexedDB",
|
||||||
dedupeEntries: true
|
dedupeEntries: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
return results as VectorSearchResult[];
|
return results as VectorSearchResult[];
|
||||||
|
|||||||
@@ -4,4 +4,3 @@ import type { HydratedIndexItem } from "../../indexing/types";
|
|||||||
export interface VectorSearchResult extends SearchResult {
|
export interface VectorSearchResult extends SearchResult {
|
||||||
object: HydratedIndexItem & { embedding: number[] };
|
object: HydratedIndexItem & { embedding: number[] };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Plugin } from '../../core/types';
|
import type { Plugin } from "../../core/types";
|
||||||
|
|
||||||
interface NotificationCollectorStorage {
|
interface NotificationCollectorStorage {
|
||||||
lastNotificationCount: number;
|
lastNotificationCount: number;
|
||||||
@@ -6,10 +6,10 @@ interface NotificationCollectorStorage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const notificationCollectorPlugin: Plugin<{}, NotificationCollectorStorage> = {
|
const notificationCollectorPlugin: Plugin<{}, NotificationCollectorStorage> = {
|
||||||
id: 'notificationCollector',
|
id: "notificationCollector",
|
||||||
name: 'Notification Collector',
|
name: "Notification Collector",
|
||||||
description: 'Collects and displays SEQTA notifications',
|
description: "Collects and displays SEQTA notifications",
|
||||||
version: '1.0.0',
|
version: "1.0.0",
|
||||||
settings: {},
|
settings: {},
|
||||||
disableToggle: true,
|
disableToggle: true,
|
||||||
|
|
||||||
@@ -23,22 +23,27 @@ const notificationCollectorPlugin: Plugin<{}, NotificationCollectorStorage> = {
|
|||||||
|
|
||||||
const checkNotifications = async () => {
|
const checkNotifications = async () => {
|
||||||
try {
|
try {
|
||||||
const alertDiv = document.querySelector("[class*='notifications__bubble___']") as HTMLElement;
|
const alertDiv = document.querySelector(
|
||||||
|
"[class*='notifications__bubble___']",
|
||||||
|
) as HTMLElement;
|
||||||
|
|
||||||
if (api.storage.lastNotificationCount !== 0) {
|
if (api.storage.lastNotificationCount !== 0) {
|
||||||
alertDiv.textContent = api.storage.lastNotificationCount.toString();
|
alertDiv.textContent = api.storage.lastNotificationCount.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(`${location.origin}/seqta/student/heartbeat?`, {
|
const response = await fetch(
|
||||||
method: 'POST',
|
`${location.origin}/seqta/student/heartbeat?`,
|
||||||
headers: {
|
{
|
||||||
'Content-Type': 'application/json; charset=utf-8'
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json; charset=utf-8",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
timestamp: "1970-01-01 00:00:00.0",
|
||||||
|
hash: "#?page=/home",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
);
|
||||||
timestamp: "1970-01-01 00:00:00.0",
|
|
||||||
hash: "#?page=/home",
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
@@ -67,7 +72,9 @@ const notificationCollectorPlugin: Plugin<{}, NotificationCollectorStorage> = {
|
|||||||
if (pollInterval) {
|
if (pollInterval) {
|
||||||
window.clearInterval(pollInterval);
|
window.clearInterval(pollInterval);
|
||||||
pollInterval = null;
|
pollInterval = null;
|
||||||
const alertDiv = document.querySelector("[class*='notifications__bubble___']") as HTMLElement;
|
const alertDiv = document.querySelector(
|
||||||
|
"[class*='notifications__bubble___']",
|
||||||
|
) as HTMLElement;
|
||||||
if (alertDiv) {
|
if (alertDiv) {
|
||||||
if (api.storage.lastNotificationCount > 9) {
|
if (api.storage.lastNotificationCount > 9) {
|
||||||
alertDiv.textContent = "9+";
|
alertDiv.textContent = "9+";
|
||||||
@@ -85,7 +92,7 @@ const notificationCollectorPlugin: Plugin<{}, NotificationCollectorStorage> = {
|
|||||||
return () => {
|
return () => {
|
||||||
stopPolling();
|
stopPolling();
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default notificationCollectorPlugin;
|
export default notificationCollectorPlugin;
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
import type { Plugin } from '@/plugins/core/types';
|
import type { Plugin } from "@/plugins/core/types";
|
||||||
import { BasePlugin } from '@/plugins/core/settings';
|
import { BasePlugin } from "@/plugins/core/settings";
|
||||||
import { booleanSetting, defineSettings, Setting } from '@/plugins/core/settingsHelpers';
|
import {
|
||||||
|
booleanSetting,
|
||||||
|
defineSettings,
|
||||||
|
Setting,
|
||||||
|
} from "@/plugins/core/settingsHelpers";
|
||||||
|
|
||||||
// Step 1: Define settings with proper typing
|
// Step 1: Define settings with proper typing
|
||||||
const settings = defineSettings({
|
const settings = defineSettings({
|
||||||
@@ -8,7 +12,7 @@ const settings = defineSettings({
|
|||||||
default: true,
|
default: true,
|
||||||
title: "Test Plugin",
|
title: "Test Plugin",
|
||||||
description: "Some random setting",
|
description: "Some random setting",
|
||||||
})
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Step 2: Create the plugin class with @Setting decorators
|
// Step 2: Create the plugin class with @Setting decorators
|
||||||
@@ -21,32 +25,32 @@ class TestPluginClass extends BasePlugin<typeof settings> {
|
|||||||
const settingsInstance = new TestPluginClass();
|
const settingsInstance = new TestPluginClass();
|
||||||
|
|
||||||
const testPlugin: Plugin<typeof settings> = {
|
const testPlugin: Plugin<typeof settings> = {
|
||||||
id: 'test',
|
id: "test",
|
||||||
name: 'Test Plugin',
|
name: "Test Plugin",
|
||||||
description: 'A test plugin for BetterSEQTA+',
|
description: "A test plugin for BetterSEQTA+",
|
||||||
version: '1.0.0',
|
version: "1.0.0",
|
||||||
settings: settingsInstance.settings,
|
settings: settingsInstance.settings,
|
||||||
disableToggle: true,
|
disableToggle: true,
|
||||||
|
|
||||||
run: async (api) => {
|
run: async (api) => {
|
||||||
console.log('Test plugin running');
|
console.log("Test plugin running");
|
||||||
|
|
||||||
api.events.on('ping', (data) => {
|
api.events.on("ping", (data) => {
|
||||||
console.log('Ping received! Page changed to: ', data);
|
console.log("Ping received! Page changed to: ", data);
|
||||||
});
|
});
|
||||||
|
|
||||||
const { unregister } = api.seqta.onPageChange((page) => {
|
const { unregister } = api.seqta.onPageChange((page) => {
|
||||||
//console.log('Page changed to', page);
|
//console.log('Page changed to', page);
|
||||||
api.events.emit('ping', page);
|
api.events.emit("ping", page);
|
||||||
|
|
||||||
console.log('Current setting value:', api.settings.someSetting);
|
console.log("Current setting value:", api.settings.someSetting);
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
console.log('Test plugin stopped');
|
console.log("Test plugin stopped");
|
||||||
unregister();
|
unregister();
|
||||||
}
|
};
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default testPlugin;
|
export default testPlugin;
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import renderSvelte from "@/interface/main"
|
import renderSvelte from "@/interface/main";
|
||||||
import themeCreator from "@/interface/pages/themeCreator.svelte"
|
import themeCreator from "@/interface/pages/themeCreator.svelte";
|
||||||
import { unmount } from "svelte"
|
import { unmount } from "svelte";
|
||||||
import { ThemeManager } from "@/plugins/built-in/themes/theme-manager"
|
import { ThemeManager } from "@/plugins/built-in/themes/theme-manager";
|
||||||
import { settingsState } from '@/seqta/utils/listeners/SettingsState'
|
import { settingsState } from "@/seqta/utils/listeners/SettingsState";
|
||||||
|
|
||||||
let themeCreatorSvelteApp: any = null
|
let themeCreatorSvelteApp: any = null;
|
||||||
const themeManager = ThemeManager.getInstance();
|
const themeManager = ThemeManager.getInstance();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -13,76 +13,79 @@ const themeManager = ThemeManager.getInstance();
|
|||||||
* @returns void
|
* @returns void
|
||||||
*/
|
*/
|
||||||
export function OpenThemeCreator(themeID: string = "") {
|
export function OpenThemeCreator(themeID: string = "") {
|
||||||
CloseThemeCreator()
|
CloseThemeCreator();
|
||||||
|
|
||||||
// Only store original color if we're not editing an existing theme
|
// Only store original color if we're not editing an existing theme
|
||||||
localStorage.setItem('themeCreatorOpen', 'true');
|
localStorage.setItem("themeCreatorOpen", "true");
|
||||||
if (!themeID) {
|
if (!themeID) {
|
||||||
localStorage.setItem('originalPreviewColor', settingsState.selectedColor);
|
localStorage.setItem("originalPreviewColor", settingsState.selectedColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
const width = "310px"
|
const width = "310px";
|
||||||
|
|
||||||
const themeCreatorDiv: HTMLDivElement = document.createElement("div")
|
const themeCreatorDiv: HTMLDivElement = document.createElement("div");
|
||||||
themeCreatorDiv.id = "themeCreator"
|
themeCreatorDiv.id = "themeCreator";
|
||||||
themeCreatorDiv.style.width = width
|
themeCreatorDiv.style.width = width;
|
||||||
|
|
||||||
const shadow = themeCreatorDiv.attachShadow({ mode: "open" })
|
const shadow = themeCreatorDiv.attachShadow({ mode: "open" });
|
||||||
themeCreatorSvelteApp = renderSvelte(themeCreator, shadow, {
|
themeCreatorSvelteApp = renderSvelte(themeCreator, shadow, {
|
||||||
themeID: themeID,
|
themeID: themeID,
|
||||||
})
|
});
|
||||||
|
|
||||||
const mainContent = document.querySelector("#container") as HTMLDivElement
|
const mainContent = document.querySelector("#container") as HTMLDivElement;
|
||||||
if (mainContent) mainContent.style.width = `calc(100% - ${width})`
|
if (mainContent) mainContent.style.width = `calc(100% - ${width})`;
|
||||||
|
|
||||||
// close button
|
// close button
|
||||||
const closeButton = document.createElement("button")
|
const closeButton = document.createElement("button");
|
||||||
closeButton.classList.add("themeCloseButton")
|
closeButton.classList.add("themeCloseButton");
|
||||||
closeButton.textContent = "×"
|
closeButton.textContent = "×";
|
||||||
closeButton.addEventListener("click", () => {
|
closeButton.addEventListener("click", () => {
|
||||||
CloseThemeCreator()
|
CloseThemeCreator();
|
||||||
themeManager.clearPreview()
|
themeManager.clearPreview();
|
||||||
})
|
});
|
||||||
|
|
||||||
document.body.appendChild(closeButton)
|
document.body.appendChild(closeButton);
|
||||||
|
|
||||||
const resizeBar = document.createElement("div")
|
const resizeBar = document.createElement("div");
|
||||||
resizeBar.classList.add("resizeBar")
|
resizeBar.classList.add("resizeBar");
|
||||||
resizeBar.style.right = "307.5px"
|
resizeBar.style.right = "307.5px";
|
||||||
|
|
||||||
let isDragging = false
|
let isDragging = false;
|
||||||
|
|
||||||
const mouseDownHandler = (_: MouseEvent) => {
|
const mouseDownHandler = (_: MouseEvent) => {
|
||||||
isDragging = true
|
isDragging = true;
|
||||||
document.addEventListener("mousemove", mouseMoveHandler)
|
document.addEventListener("mousemove", mouseMoveHandler);
|
||||||
document.addEventListener("mouseup", mouseUpHandler)
|
document.addEventListener("mouseup", mouseUpHandler);
|
||||||
document.body.style.userSelect = "none"
|
document.body.style.userSelect = "none";
|
||||||
themeCreatorDiv.style.pointerEvents = "none"
|
themeCreatorDiv.style.pointerEvents = "none";
|
||||||
}
|
};
|
||||||
|
|
||||||
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.max(310, windowWidth - e.clientX)
|
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`;
|
||||||
}
|
};
|
||||||
|
|
||||||
const mouseUpHandler = () => {
|
const mouseUpHandler = () => {
|
||||||
isDragging = false
|
isDragging = false;
|
||||||
document.removeEventListener("mousemove", mouseMoveHandler)
|
document.removeEventListener("mousemove", mouseMoveHandler);
|
||||||
document.removeEventListener("mouseup", mouseUpHandler)
|
document.removeEventListener("mouseup", mouseUpHandler);
|
||||||
document.body.style.userSelect = ""
|
document.body.style.userSelect = "";
|
||||||
themeCreatorDiv.style.pointerEvents = "auto"
|
themeCreatorDiv.style.pointerEvents = "auto";
|
||||||
}
|
};
|
||||||
|
|
||||||
resizeBar.addEventListener("mousedown", mouseDownHandler)
|
resizeBar.addEventListener("mousedown", mouseDownHandler);
|
||||||
resizeBar.addEventListener("mouseover", () => (resizeBar.style.opacity = "1"))
|
resizeBar.addEventListener(
|
||||||
resizeBar.addEventListener("mouseout", () => (resizeBar.style.opacity = "0"))
|
"mouseover",
|
||||||
|
() => (resizeBar.style.opacity = "1"),
|
||||||
|
);
|
||||||
|
resizeBar.addEventListener("mouseout", () => (resizeBar.style.opacity = "0"));
|
||||||
|
|
||||||
document.body.appendChild(themeCreatorDiv)
|
document.body.appendChild(themeCreatorDiv);
|
||||||
document.body.appendChild(resizeBar)
|
document.body.appendChild(resizeBar);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -91,19 +94,19 @@ export function OpenThemeCreator(themeID: string = "") {
|
|||||||
*/
|
*/
|
||||||
export function CloseThemeCreator() {
|
export function CloseThemeCreator() {
|
||||||
// Remove the stored flag
|
// Remove the stored flag
|
||||||
localStorage.removeItem('themeCreatorOpen');
|
localStorage.removeItem("themeCreatorOpen");
|
||||||
|
|
||||||
const themeCreator = document.getElementById("themeCreator")
|
const themeCreator = document.getElementById("themeCreator");
|
||||||
const closeButton = document.querySelector(
|
const closeButton = document.querySelector(
|
||||||
".themeCloseButton",
|
".themeCloseButton",
|
||||||
) as HTMLButtonElement
|
) as HTMLButtonElement;
|
||||||
const resizeBar = document.querySelector(".resizeBar") as HTMLDivElement
|
const resizeBar = document.querySelector(".resizeBar") as HTMLDivElement;
|
||||||
|
|
||||||
if (themeCreatorSvelteApp) unmount(themeCreatorSvelteApp)
|
if (themeCreatorSvelteApp) unmount(themeCreatorSvelteApp);
|
||||||
if (themeCreator) themeCreator.remove()
|
if (themeCreator) themeCreator.remove();
|
||||||
if (closeButton) closeButton.remove()
|
if (closeButton) closeButton.remove();
|
||||||
if (resizeBar) resizeBar.remove()
|
if (resizeBar) resizeBar.remove();
|
||||||
|
|
||||||
const mainContent = document.querySelector("#container") as HTMLDivElement
|
const mainContent = document.querySelector("#container") as HTMLDivElement;
|
||||||
if (mainContent) mainContent.style.width = "100%"
|
if (mainContent) mainContent.style.width = "100%";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import type { Plugin } from '../../core/types';
|
import type { Plugin } from "../../core/types";
|
||||||
import { ThemeManager } from './theme-manager';
|
import { ThemeManager } from "./theme-manager";
|
||||||
|
|
||||||
const themesPlugin: Plugin = {
|
const themesPlugin: Plugin = {
|
||||||
id: 'themes',
|
id: "themes",
|
||||||
name: 'Themes',
|
name: "Themes",
|
||||||
description: 'Adds a theme selector to the settings page',
|
description: "Adds a theme selector to the settings page",
|
||||||
version: '1.0.0',
|
version: "1.0.0",
|
||||||
settings: {},
|
settings: {},
|
||||||
|
|
||||||
run: async (_) => {
|
run: async (_) => {
|
||||||
const themeManager = ThemeManager.getInstance();
|
const themeManager = ThemeManager.getInstance();
|
||||||
await themeManager.initialize();
|
await themeManager.initialize();
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default themesPlugin;
|
export default themesPlugin;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import localforage from 'localforage';
|
import localforage from "localforage";
|
||||||
import type { CustomTheme, LoadedCustomTheme } from '@/types/CustomThemes';
|
import type { CustomTheme, LoadedCustomTheme } from "@/types/CustomThemes";
|
||||||
import { settingsState } from '@/seqta/utils/listeners/SettingsState';
|
import { settingsState } from "@/seqta/utils/listeners/SettingsState";
|
||||||
import debounce from '@/seqta/utils/debounce';
|
import debounce from "@/seqta/utils/debounce";
|
||||||
|
|
||||||
type ThemeContent = {
|
type ThemeContent = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -13,7 +13,7 @@ type ThemeContent = {
|
|||||||
CustomCSS?: string;
|
CustomCSS?: string;
|
||||||
hideThemeName?: boolean;
|
hideThemeName?: boolean;
|
||||||
forceDark?: boolean;
|
forceDark?: boolean;
|
||||||
images: { id: string, variableName: string, data: string }[]; // data: base64
|
images: { id: string; variableName: string; data: string }[]; // data: base64
|
||||||
};
|
};
|
||||||
|
|
||||||
export class ThemeManager {
|
export class ThemeManager {
|
||||||
@@ -27,7 +27,7 @@ export class ThemeManager {
|
|||||||
private imageUrlCache: Map<string, string> = new Map();
|
private imageUrlCache: Map<string, string> = new Map();
|
||||||
|
|
||||||
private constructor() {
|
private constructor() {
|
||||||
console.debug('[ThemeManager] Initializing...');
|
console.debug("[ThemeManager] Initializing...");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static getInstance(): ThemeManager {
|
public static getInstance(): ThemeManager {
|
||||||
@@ -48,12 +48,12 @@ export class ThemeManager {
|
|||||||
* Get a theme by ID from storage
|
* Get a theme by ID from storage
|
||||||
*/
|
*/
|
||||||
public async getTheme(themeId: string): Promise<CustomTheme | null> {
|
public async getTheme(themeId: string): Promise<CustomTheme | null> {
|
||||||
console.debug('[ThemeManager] Getting theme:', themeId);
|
console.debug("[ThemeManager] Getting theme:", themeId);
|
||||||
try {
|
try {
|
||||||
const theme = await localforage.getItem(themeId) as CustomTheme;
|
const theme = (await localforage.getItem(themeId)) as CustomTheme;
|
||||||
return theme;
|
return theme;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[ThemeManager] Error getting theme:', error);
|
console.error("[ThemeManager] Error getting theme:", error);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -69,19 +69,19 @@ export class ThemeManager {
|
|||||||
* Disable the current theme without deleting it
|
* Disable the current theme without deleting it
|
||||||
*/
|
*/
|
||||||
public async disableTheme(): Promise<void> {
|
public async disableTheme(): Promise<void> {
|
||||||
console.debug('[ThemeManager] Disabling current theme');
|
console.debug("[ThemeManager] Disabling current theme");
|
||||||
try {
|
try {
|
||||||
if (!this.currentTheme) {
|
if (!this.currentTheme) {
|
||||||
console.debug('[ThemeManager] No theme to disable');
|
console.debug("[ThemeManager] No theme to disable");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.removeTheme(this.currentTheme);
|
await this.removeTheme(this.currentTheme);
|
||||||
this.currentTheme = null;
|
this.currentTheme = null;
|
||||||
settingsState.selectedTheme = '';
|
settingsState.selectedTheme = "";
|
||||||
console.debug('[ThemeManager] Theme disabled successfully');
|
console.debug("[ThemeManager] Theme disabled successfully");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[ThemeManager] Error disabling theme:', error);
|
console.error("[ThemeManager] Error disabling theme:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,23 +89,28 @@ export class ThemeManager {
|
|||||||
* Initialize the theme system and restore previous state
|
* Initialize the theme system and restore previous state
|
||||||
*/
|
*/
|
||||||
public async initialize(): Promise<void> {
|
public async initialize(): Promise<void> {
|
||||||
console.debug('[ThemeManager] Starting initialization');
|
console.debug("[ThemeManager] Starting initialization");
|
||||||
try {
|
try {
|
||||||
// Check if theme creator was open during reload
|
// Check if theme creator was open during reload
|
||||||
const themeCreatorOpen = localStorage.getItem('themeCreatorOpen');
|
const themeCreatorOpen = localStorage.getItem("themeCreatorOpen");
|
||||||
if (themeCreatorOpen === 'true') {
|
if (themeCreatorOpen === "true") {
|
||||||
console.debug('[ThemeManager] Theme creator was open, clearing preview state');
|
console.debug(
|
||||||
|
"[ThemeManager] Theme creator was open, clearing preview state",
|
||||||
|
);
|
||||||
this.clearPreview();
|
this.clearPreview();
|
||||||
// Clean up the flag
|
// Clean up the flag
|
||||||
localStorage.removeItem('themeCreatorOpen');
|
localStorage.removeItem("themeCreatorOpen");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settingsState.selectedTheme) {
|
if (settingsState.selectedTheme) {
|
||||||
console.debug('[ThemeManager] Found selected theme, restoring:', settingsState.selectedTheme);
|
console.debug(
|
||||||
|
"[ThemeManager] Found selected theme, restoring:",
|
||||||
|
settingsState.selectedTheme,
|
||||||
|
);
|
||||||
await this.setTheme(settingsState.selectedTheme);
|
await this.setTheme(settingsState.selectedTheme);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[ThemeManager] Error during initialization:', error);
|
console.error("[ThemeManager] Error during initialization:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,13 +118,13 @@ export class ThemeManager {
|
|||||||
* Clean up theme system resources
|
* Clean up theme system resources
|
||||||
*/
|
*/
|
||||||
public async cleanup(): Promise<void> {
|
public async cleanup(): Promise<void> {
|
||||||
console.debug('[ThemeManager] Cleaning up resources');
|
console.debug("[ThemeManager] Cleaning up resources");
|
||||||
try {
|
try {
|
||||||
if (this.currentTheme) {
|
if (this.currentTheme) {
|
||||||
await this.removeTheme(this.currentTheme, false);
|
await this.removeTheme(this.currentTheme, false);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[ThemeManager] Error during cleanup:', error);
|
console.error("[ThemeManager] Error during cleanup:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,24 +132,24 @@ export class ThemeManager {
|
|||||||
* Set and apply a theme by ID
|
* Set and apply a theme by ID
|
||||||
*/
|
*/
|
||||||
public async setTheme(themeId: string): Promise<void> {
|
public async setTheme(themeId: string): Promise<void> {
|
||||||
console.debug('[ThemeManager] Setting theme:', themeId);
|
console.debug("[ThemeManager] Setting theme:", themeId);
|
||||||
try {
|
try {
|
||||||
const theme = await localforage.getItem(themeId) as CustomTheme;
|
const theme = (await localforage.getItem(themeId)) as CustomTheme;
|
||||||
if (!theme) {
|
if (!theme) {
|
||||||
console.error('[ThemeManager] Theme not found:', themeId);
|
console.error("[ThemeManager] Theme not found:", themeId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store original settings before applying new theme
|
// Store original settings before applying new theme
|
||||||
if (!settingsState.selectedTheme) {
|
if (!settingsState.selectedTheme) {
|
||||||
console.debug('[ThemeManager] Storing original settings');
|
console.debug("[ThemeManager] Storing original settings");
|
||||||
settingsState.originalSelectedColor = settingsState.selectedColor;
|
settingsState.originalSelectedColor = settingsState.selectedColor;
|
||||||
settingsState.originalDarkMode = settingsState.DarkMode;
|
settingsState.originalDarkMode = settingsState.DarkMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove current theme if exists
|
// Remove current theme if exists
|
||||||
if (this.currentTheme) {
|
if (this.currentTheme) {
|
||||||
console.debug('[ThemeManager] Removing current theme');
|
console.debug("[ThemeManager] Removing current theme");
|
||||||
|
|
||||||
await this.removeTheme(this.currentTheme);
|
await this.removeTheme(this.currentTheme);
|
||||||
}
|
}
|
||||||
@@ -153,9 +158,8 @@ export class ThemeManager {
|
|||||||
await this.applyTheme(theme);
|
await this.applyTheme(theme);
|
||||||
this.currentTheme = theme;
|
this.currentTheme = theme;
|
||||||
settingsState.selectedTheme = themeId;
|
settingsState.selectedTheme = themeId;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[ThemeManager] Error setting theme:', error);
|
console.error("[ThemeManager] Error setting theme:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,65 +167,80 @@ export class ThemeManager {
|
|||||||
* Apply theme components (CSS, images, settings)
|
* Apply theme components (CSS, images, settings)
|
||||||
*/
|
*/
|
||||||
private async applyTheme(theme: CustomTheme): Promise<void> {
|
private async applyTheme(theme: CustomTheme): Promise<void> {
|
||||||
console.debug('[ThemeManager] Applying theme:', theme.name);
|
console.debug("[ThemeManager] Applying theme:", theme.name);
|
||||||
try {
|
try {
|
||||||
// Apply custom CSS
|
// Apply custom CSS
|
||||||
if (theme.CustomCSS) {
|
if (theme.CustomCSS) {
|
||||||
console.debug('[ThemeManager] Applying custom CSS');
|
console.debug("[ThemeManager] Applying custom CSS");
|
||||||
this.applyCustomCSS(theme.CustomCSS);
|
this.applyCustomCSS(theme.CustomCSS);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply custom images
|
// Apply custom images
|
||||||
if (theme.CustomImages) {
|
if (theme.CustomImages) {
|
||||||
console.debug('[ThemeManager] Applying custom images');
|
console.debug("[ThemeManager] Applying custom images");
|
||||||
theme.CustomImages.forEach((image) => {
|
theme.CustomImages.forEach((image) => {
|
||||||
const imageUrl = URL.createObjectURL(image.blob);
|
const imageUrl = URL.createObjectURL(image.blob);
|
||||||
document.documentElement.style.setProperty('--' + image.variableName, `url(${imageUrl})`);
|
document.documentElement.style.setProperty(
|
||||||
|
"--" + image.variableName,
|
||||||
|
`url(${imageUrl})`,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply theme settings
|
// Apply theme settings
|
||||||
if (theme.forceDark !== undefined) {
|
if (theme.forceDark !== undefined) {
|
||||||
console.debug('[ThemeManager] Setting dark mode:', theme.forceDark);
|
console.debug("[ThemeManager] Setting dark mode:", theme.forceDark);
|
||||||
settingsState.DarkMode = theme.forceDark;
|
settingsState.DarkMode = theme.forceDark;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the stored selected color if available, otherwise use the default
|
// Use the stored selected color if available, otherwise use the default
|
||||||
if (theme.selectedColor) {
|
if (theme.selectedColor) {
|
||||||
console.debug('[ThemeManager] Restoring saved color:', theme.selectedColor);
|
console.debug(
|
||||||
|
"[ThemeManager] Restoring saved color:",
|
||||||
|
theme.selectedColor,
|
||||||
|
);
|
||||||
settingsState.selectedColor = theme.selectedColor;
|
settingsState.selectedColor = theme.selectedColor;
|
||||||
} else if (theme.defaultColour) {
|
} else if (theme.defaultColour) {
|
||||||
console.debug('[ThemeManager] Using default color:', theme.defaultColour);
|
console.debug(
|
||||||
|
"[ThemeManager] Using default color:",
|
||||||
|
theme.defaultColour,
|
||||||
|
);
|
||||||
settingsState.selectedColor = theme.defaultColour;
|
settingsState.selectedColor = theme.defaultColour;
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[ThemeManager] Error applying theme:', error);
|
console.error("[ThemeManager] Error applying theme:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove theme and restore original settings
|
* Remove theme and restore original settings
|
||||||
*/
|
*/
|
||||||
private async removeTheme(theme: CustomTheme, clearSelectedTheme: boolean = true): Promise<void> {
|
private async removeTheme(
|
||||||
console.debug('[ThemeManager] Removing theme:', theme.name);
|
theme: CustomTheme,
|
||||||
|
clearSelectedTheme: boolean = true,
|
||||||
|
): Promise<void> {
|
||||||
|
console.debug("[ThemeManager] Removing theme:", theme.name);
|
||||||
try {
|
try {
|
||||||
// Remove custom CSS
|
// Remove custom CSS
|
||||||
if (this.styleElement) {
|
if (this.styleElement) {
|
||||||
console.debug('[ThemeManager] Removing custom CSS');
|
console.debug("[ThemeManager] Removing custom CSS");
|
||||||
this.styleElement.remove();
|
this.styleElement.remove();
|
||||||
this.styleElement = null;
|
this.styleElement = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove custom images
|
// Remove custom images
|
||||||
if (theme.CustomImages) {
|
if (theme.CustomImages) {
|
||||||
console.debug('[ThemeManager] Removing custom images');
|
console.debug("[ThemeManager] Removing custom images");
|
||||||
theme.CustomImages.forEach((image) => {
|
theme.CustomImages.forEach((image) => {
|
||||||
const value = document.documentElement.style.getPropertyValue('--' + image.variableName);
|
const value = document.documentElement.style.getPropertyValue(
|
||||||
|
"--" + image.variableName,
|
||||||
|
);
|
||||||
if (value) {
|
if (value) {
|
||||||
URL.revokeObjectURL(value.slice(4, -1)); // Remove url() wrapper
|
URL.revokeObjectURL(value.slice(4, -1)); // Remove url() wrapper
|
||||||
}
|
}
|
||||||
document.documentElement.style.removeProperty('--' + image.variableName);
|
document.documentElement.style.removeProperty(
|
||||||
|
"--" + image.variableName,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,29 +248,34 @@ export class ThemeManager {
|
|||||||
// Store the current color with the theme before removing it
|
// Store the current color with the theme before removing it
|
||||||
await localforage.setItem(this.currentTheme.id, {
|
await localforage.setItem(this.currentTheme.id, {
|
||||||
...this.currentTheme,
|
...this.currentTheme,
|
||||||
selectedColor: settingsState.selectedColor
|
selectedColor: settingsState.selectedColor,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore original settings
|
// Restore original settings
|
||||||
if (settingsState.originalSelectedColor) {
|
if (settingsState.originalSelectedColor) {
|
||||||
console.debug('[ThemeManager] Restoring original color:', settingsState.originalSelectedColor);
|
console.debug(
|
||||||
|
"[ThemeManager] Restoring original color:",
|
||||||
|
settingsState.originalSelectedColor,
|
||||||
|
);
|
||||||
settingsState.selectedColor = settingsState.originalSelectedColor;
|
settingsState.selectedColor = settingsState.originalSelectedColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settingsState.originalDarkMode !== undefined) {
|
if (settingsState.originalDarkMode !== undefined) {
|
||||||
console.debug('[ThemeManager] Restoring original dark mode:', settingsState.originalDarkMode);
|
console.debug(
|
||||||
|
"[ThemeManager] Restoring original dark mode:",
|
||||||
|
settingsState.originalDarkMode,
|
||||||
|
);
|
||||||
settingsState.DarkMode = settingsState.originalDarkMode;
|
settingsState.DarkMode = settingsState.originalDarkMode;
|
||||||
settingsState.originalDarkMode = undefined;
|
settingsState.originalDarkMode = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.currentTheme = null;
|
this.currentTheme = null;
|
||||||
if (clearSelectedTheme) {
|
if (clearSelectedTheme) {
|
||||||
settingsState.selectedTheme = '';
|
settingsState.selectedTheme = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[ThemeManager] Error removing theme:', error);
|
console.error("[ThemeManager] Error removing theme:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,16 +283,16 @@ export class ThemeManager {
|
|||||||
* Apply custom CSS to the document
|
* Apply custom CSS to the document
|
||||||
*/
|
*/
|
||||||
private applyCustomCSS(css: string): void {
|
private applyCustomCSS(css: string): void {
|
||||||
console.debug('[ThemeManager] Applying custom CSS');
|
console.debug("[ThemeManager] Applying custom CSS");
|
||||||
try {
|
try {
|
||||||
if (!this.styleElement) {
|
if (!this.styleElement) {
|
||||||
this.styleElement = document.createElement('style');
|
this.styleElement = document.createElement("style");
|
||||||
this.styleElement.id = 'custom-theme';
|
this.styleElement.id = "custom-theme";
|
||||||
document.head.appendChild(this.styleElement);
|
document.head.appendChild(this.styleElement);
|
||||||
}
|
}
|
||||||
this.styleElement.textContent = css;
|
this.styleElement.textContent = css;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[ThemeManager] Error applying custom CSS:', error);
|
console.error("[ThemeManager] Error applying custom CSS:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,22 +300,24 @@ export class ThemeManager {
|
|||||||
* Get list of available themes
|
* Get list of available themes
|
||||||
*/
|
*/
|
||||||
public async getAvailableThemes(): Promise<CustomTheme[]> {
|
public async getAvailableThemes(): Promise<CustomTheme[]> {
|
||||||
console.debug('[ThemeManager] Getting available themes');
|
console.debug("[ThemeManager] Getting available themes");
|
||||||
try {
|
try {
|
||||||
const themeIds = await localforage.getItem('customThemes') as string[] | null;
|
const themeIds = (await localforage.getItem("customThemes")) as
|
||||||
|
| string[]
|
||||||
|
| null;
|
||||||
if (!themeIds) {
|
if (!themeIds) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const themes = await Promise.all(
|
const themes = await Promise.all(
|
||||||
themeIds.map(async (id) => {
|
themeIds.map(async (id) => {
|
||||||
return await localforage.getItem(id) as CustomTheme;
|
return (await localforage.getItem(id)) as CustomTheme;
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
return themes.filter(theme => theme !== null);
|
return themes.filter((theme) => theme !== null);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[ThemeManager] Error getting available themes:', error);
|
console.error("[ThemeManager] Error getting available themes:", error);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -300,21 +326,23 @@ export class ThemeManager {
|
|||||||
* Save or update a theme
|
* Save or update a theme
|
||||||
*/
|
*/
|
||||||
public async saveTheme(theme: LoadedCustomTheme): Promise<void> {
|
public async saveTheme(theme: LoadedCustomTheme): Promise<void> {
|
||||||
console.debug('[ThemeManager] Saving theme:', theme.name);
|
console.debug("[ThemeManager] Saving theme:", theme.name);
|
||||||
try {
|
try {
|
||||||
await localforage.setItem(theme.id, theme);
|
await localforage.setItem(theme.id, theme);
|
||||||
const themeIds = await localforage.getItem('customThemes') as string[] | null;
|
const themeIds = (await localforage.getItem("customThemes")) as
|
||||||
|
| string[]
|
||||||
|
| null;
|
||||||
|
|
||||||
if (themeIds) {
|
if (themeIds) {
|
||||||
if (!themeIds.includes(theme.id)) {
|
if (!themeIds.includes(theme.id)) {
|
||||||
themeIds.push(theme.id);
|
themeIds.push(theme.id);
|
||||||
await localforage.setItem('customThemes', themeIds);
|
await localforage.setItem("customThemes", themeIds);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await localforage.setItem('customThemes', [theme.id]);
|
await localforage.setItem("customThemes", [theme.id]);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[ThemeManager] Error saving theme:', error);
|
console.error("[ThemeManager] Error saving theme:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -322,40 +350,49 @@ export class ThemeManager {
|
|||||||
* Delete a theme
|
* Delete a theme
|
||||||
*/
|
*/
|
||||||
public async deleteTheme(themeId: string): Promise<void> {
|
public async deleteTheme(themeId: string): Promise<void> {
|
||||||
console.debug('[ThemeManager] Deleting theme:', themeId);
|
console.debug("[ThemeManager] Deleting theme:", themeId);
|
||||||
try {
|
try {
|
||||||
const theme = await localforage.getItem(themeId) as CustomTheme;
|
const theme = (await localforage.getItem(themeId)) as CustomTheme;
|
||||||
if (theme) {
|
if (theme) {
|
||||||
if (this.currentTheme?.id === themeId) {
|
if (this.currentTheme?.id === themeId) {
|
||||||
await this.removeTheme(theme);
|
await this.removeTheme(theme);
|
||||||
}
|
}
|
||||||
await localforage.removeItem(themeId);
|
await localforage.removeItem(themeId);
|
||||||
|
|
||||||
const themeIds = await localforage.getItem('customThemes') as string[] | null;
|
const themeIds = (await localforage.getItem("customThemes")) as
|
||||||
|
| string[]
|
||||||
|
| null;
|
||||||
if (themeIds) {
|
if (themeIds) {
|
||||||
const updatedThemeIds = themeIds.filter(id => id !== themeId);
|
const updatedThemeIds = themeIds.filter((id) => id !== themeId);
|
||||||
await localforage.setItem('customThemes', updatedThemeIds);
|
await localforage.setItem("customThemes", updatedThemeIds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[ThemeManager] Error deleting theme:', error);
|
console.error("[ThemeManager] Error deleting theme:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Download and install a theme from the store
|
* Download and install a theme from the store
|
||||||
*/
|
*/
|
||||||
public async downloadTheme(themeContent: { id: string; name: string; description: string; coverImage: string; }): Promise<void> {
|
public async downloadTheme(themeContent: {
|
||||||
console.debug('[ThemeManager] Downloading theme:', themeContent.name);
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
coverImage: string;
|
||||||
|
}): Promise<void> {
|
||||||
|
console.debug("[ThemeManager] Downloading theme:", themeContent.name);
|
||||||
try {
|
try {
|
||||||
if (!themeContent.id) return;
|
if (!themeContent.id) return;
|
||||||
|
|
||||||
const response = await fetch(`https://raw.githubusercontent.com/BetterSEQTA/BetterSEQTA-Themes/main/store/themes/${themeContent.id}/theme.json`);
|
const response = await fetch(
|
||||||
const themeData = await response.json() as ThemeContent;
|
`https://raw.githubusercontent.com/BetterSEQTA/BetterSEQTA-Themes/main/store/themes/${themeContent.id}/theme.json`,
|
||||||
|
);
|
||||||
|
const themeData = (await response.json()) as ThemeContent;
|
||||||
|
|
||||||
await this.installTheme(themeData);
|
await this.installTheme(themeData);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[ThemeManager] Error downloading theme:', error);
|
console.error("[ThemeManager] Error downloading theme:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -363,62 +400,67 @@ export class ThemeManager {
|
|||||||
* Install a theme from theme data
|
* Install a theme from theme data
|
||||||
*/
|
*/
|
||||||
public async installTheme(themeData: ThemeContent): Promise<void> {
|
public async installTheme(themeData: ThemeContent): Promise<void> {
|
||||||
console.debug('[ThemeManager] Installing theme:', themeData.name);
|
console.debug("[ThemeManager] Installing theme:", themeData.name);
|
||||||
try {
|
try {
|
||||||
// Validate required fields
|
// Validate required fields
|
||||||
if (!themeData.id || !themeData.name) {
|
if (!themeData.id || !themeData.name) {
|
||||||
throw new Error('Theme is missing required fields (id or name)');
|
throw new Error("Theme is missing required fields (id or name)");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle cover image (optional)
|
// Handle cover image (optional)
|
||||||
let coverImageBlob = null;
|
let coverImageBlob = null;
|
||||||
if (themeData.coverImage) {
|
if (themeData.coverImage) {
|
||||||
try {
|
try {
|
||||||
const strippedCoverImage = this.stripBase64Prefix(themeData.coverImage);
|
const strippedCoverImage = this.stripBase64Prefix(
|
||||||
|
themeData.coverImage,
|
||||||
|
);
|
||||||
coverImageBlob = this.base64ToBlob(strippedCoverImage);
|
coverImageBlob = this.base64ToBlob(strippedCoverImage);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('[ThemeManager] Failed to process cover image:', e);
|
console.warn("[ThemeManager] Failed to process cover image:", e);
|
||||||
// Continue without cover image
|
// Continue without cover image
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle images (optional)
|
// Handle images (optional)
|
||||||
const images = themeData.images?.map((image) => {
|
const images =
|
||||||
try {
|
themeData.images
|
||||||
if (!image.id || !image.variableName || !image.data) {
|
?.map((image) => {
|
||||||
console.warn('[ThemeManager] Skipping invalid image:', image);
|
try {
|
||||||
return null;
|
if (!image.id || !image.variableName || !image.data) {
|
||||||
}
|
console.warn("[ThemeManager] Skipping invalid image:", image);
|
||||||
return {
|
return null;
|
||||||
...image,
|
}
|
||||||
blob: this.base64ToBlob(this.stripBase64Prefix(image.data))
|
return {
|
||||||
};
|
...image,
|
||||||
} catch (e) {
|
blob: this.base64ToBlob(this.stripBase64Prefix(image.data)),
|
||||||
console.warn('[ThemeManager] Failed to process image:', e);
|
};
|
||||||
return null;
|
} catch (e) {
|
||||||
}
|
console.warn("[ThemeManager] Failed to process image:", e);
|
||||||
}).filter(img => img !== null) ?? [];
|
return null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter((img) => img !== null) ?? [];
|
||||||
|
|
||||||
// Create theme with defaults for optional fields
|
// Create theme with defaults for optional fields
|
||||||
const theme: LoadedCustomTheme = {
|
const theme: LoadedCustomTheme = {
|
||||||
id: themeData.id,
|
id: themeData.id,
|
||||||
name: themeData.name,
|
name: themeData.name,
|
||||||
description: themeData.description || '',
|
description: themeData.description || "",
|
||||||
webURL: themeData.id,
|
webURL: themeData.id,
|
||||||
coverImage: coverImageBlob,
|
coverImage: coverImageBlob,
|
||||||
CustomImages: images,
|
CustomImages: images,
|
||||||
CustomCSS: themeData.CustomCSS || '',
|
CustomCSS: themeData.CustomCSS || "",
|
||||||
defaultColour: themeData.defaultColour || 'rgba(0, 123, 255, 1)',
|
defaultColour: themeData.defaultColour || "rgba(0, 123, 255, 1)",
|
||||||
CanChangeColour: themeData.CanChangeColour ?? true,
|
CanChangeColour: themeData.CanChangeColour ?? true,
|
||||||
allowBackgrounds: true,
|
allowBackgrounds: true,
|
||||||
isEditable: false,
|
isEditable: false,
|
||||||
hideThemeName: themeData.hideThemeName ?? false,
|
hideThemeName: themeData.hideThemeName ?? false,
|
||||||
forceDark: themeData.forceDark
|
forceDark: themeData.forceDark,
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.saveTheme(theme);
|
await this.saveTheme(theme);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[ThemeManager] Error installing theme:', error);
|
console.error("[ThemeManager] Error installing theme:", error);
|
||||||
throw error; // Re-throw to handle in UI
|
throw error; // Re-throw to handle in UI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -427,11 +469,11 @@ export class ThemeManager {
|
|||||||
* Share a theme by exporting it
|
* Share a theme by exporting it
|
||||||
*/
|
*/
|
||||||
public async shareTheme(themeId: string): Promise<void> {
|
public async shareTheme(themeId: string): Promise<void> {
|
||||||
console.debug('[ThemeManager] Sharing theme:', themeId);
|
console.debug("[ThemeManager] Sharing theme:", themeId);
|
||||||
try {
|
try {
|
||||||
const theme = await localforage.getItem(themeId) as LoadedCustomTheme;
|
const theme = (await localforage.getItem(themeId)) as LoadedCustomTheme;
|
||||||
if (!theme) {
|
if (!theme) {
|
||||||
console.error('[ThemeManager] Theme not found');
|
console.error("[ThemeManager] Theme not found");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -447,26 +489,30 @@ export class ThemeManager {
|
|||||||
} = theme;
|
} = theme;
|
||||||
|
|
||||||
// Convert images to base64
|
// Convert images to base64
|
||||||
const finalImages = await Promise.all(CustomImages.map(async (image) => ({
|
const finalImages = await Promise.all(
|
||||||
id: image.id,
|
CustomImages.map(async (image) => ({
|
||||||
variableName: image.variableName,
|
id: image.id,
|
||||||
data: await this.blobToBase64(image.blob)
|
variableName: image.variableName,
|
||||||
})));
|
data: await this.blobToBase64(image.blob),
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
// Convert cover image to base64
|
// Convert cover image to base64
|
||||||
const coverImageBase64 = coverImage ? await this.blobToBase64(coverImage) : null;
|
const coverImageBase64 = coverImage
|
||||||
|
? await this.blobToBase64(coverImage)
|
||||||
|
: null;
|
||||||
|
|
||||||
// Create shareable theme data with only necessary fields
|
// Create shareable theme data with only necessary fields
|
||||||
const shareableTheme = {
|
const shareableTheme = {
|
||||||
...themeBasics,
|
...themeBasics,
|
||||||
images: finalImages,
|
images: finalImages,
|
||||||
coverImage: coverImageBase64
|
coverImage: coverImageBase64,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Save theme file
|
// Save theme file
|
||||||
this.saveThemeFile(shareableTheme, theme.name || 'Unnamed_Theme');
|
this.saveThemeFile(shareableTheme, theme.name || "Unnamed_Theme");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[ThemeManager] Error sharing theme:', error);
|
console.error("[ThemeManager] Error sharing theme:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -474,7 +520,7 @@ export class ThemeManager {
|
|||||||
* Preview a theme without applying it
|
* Preview a theme without applying it
|
||||||
*/
|
*/
|
||||||
public async previewTheme(theme: LoadedCustomTheme): Promise<void> {
|
public async previewTheme(theme: LoadedCustomTheme): Promise<void> {
|
||||||
console.debug('[ThemeManager] Previewing theme:', theme.name);
|
console.debug("[ThemeManager] Previewing theme:", theme.name);
|
||||||
try {
|
try {
|
||||||
const { CustomCSS, CustomImages, defaultColour, forceDark } = theme;
|
const { CustomCSS, CustomImages, defaultColour, forceDark } = theme;
|
||||||
|
|
||||||
@@ -482,7 +528,10 @@ export class ThemeManager {
|
|||||||
if (!theme.webURL) {
|
if (!theme.webURL) {
|
||||||
if (this.originalPreviewColor === null) {
|
if (this.originalPreviewColor === null) {
|
||||||
this.originalPreviewColor = settingsState.selectedColor;
|
this.originalPreviewColor = settingsState.selectedColor;
|
||||||
localStorage.setItem('originalPreviewColor', settingsState.selectedColor);
|
localStorage.setItem(
|
||||||
|
"originalPreviewColor",
|
||||||
|
settingsState.selectedColor,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (this.originalPreviewTheme === null) {
|
if (this.originalPreviewTheme === null) {
|
||||||
this.originalPreviewTheme = settingsState.DarkMode;
|
this.originalPreviewTheme = settingsState.DarkMode;
|
||||||
@@ -495,10 +544,12 @@ export class ThemeManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply custom images
|
// Apply custom images
|
||||||
const newImageVariableNames = CustomImages.map(image => image.variableName);
|
const newImageVariableNames = CustomImages.map(
|
||||||
|
(image) => image.variableName,
|
||||||
|
);
|
||||||
|
|
||||||
// Remove old preview images
|
// Remove old preview images
|
||||||
this.previousImageVariableNames.forEach(variableName => {
|
this.previousImageVariableNames.forEach((variableName) => {
|
||||||
if (!newImageVariableNames.includes(variableName)) {
|
if (!newImageVariableNames.includes(variableName)) {
|
||||||
this.removeImageFromDocument(variableName);
|
this.removeImageFromDocument(variableName);
|
||||||
}
|
}
|
||||||
@@ -507,7 +558,10 @@ export class ThemeManager {
|
|||||||
// Apply new images
|
// Apply new images
|
||||||
CustomImages.forEach((image) => {
|
CustomImages.forEach((image) => {
|
||||||
const imageUrl = URL.createObjectURL(image.blob);
|
const imageUrl = URL.createObjectURL(image.blob);
|
||||||
document.documentElement.style.setProperty(`--${image.variableName}`, `url(${imageUrl})`);
|
document.documentElement.style.setProperty(
|
||||||
|
`--${image.variableName}`,
|
||||||
|
`url(${imageUrl})`,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update previousImageVariableNames
|
// Update previousImageVariableNames
|
||||||
@@ -522,7 +576,7 @@ export class ThemeManager {
|
|||||||
settingsState.selectedColor = defaultColour;
|
settingsState.selectedColor = defaultColour;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[ThemeManager] Error previewing theme:', error);
|
console.error("[ThemeManager] Error previewing theme:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -530,7 +584,7 @@ export class ThemeManager {
|
|||||||
* Update the preview of a theme in real-time (for theme creator)
|
* Update the preview of a theme in real-time (for theme creator)
|
||||||
*/
|
*/
|
||||||
public async updatePreview(theme: Partial<LoadedCustomTheme>): Promise<void> {
|
public async updatePreview(theme: Partial<LoadedCustomTheme>): Promise<void> {
|
||||||
console.debug('[ThemeManager] Updating theme preview');
|
console.debug("[ThemeManager] Updating theme preview");
|
||||||
try {
|
try {
|
||||||
// Only store original settings if this is a new theme (not editing)
|
// Only store original settings if this is a new theme (not editing)
|
||||||
// We can tell it's a new theme if it has no webURL (which is set when a theme is saved/loaded)
|
// We can tell it's a new theme if it has no webURL (which is set when a theme is saved/loaded)
|
||||||
@@ -550,10 +604,12 @@ export class ThemeManager {
|
|||||||
|
|
||||||
// Handle images if present
|
// Handle images if present
|
||||||
if (theme.CustomImages) {
|
if (theme.CustomImages) {
|
||||||
const newImageVariableNames = theme.CustomImages.map(image => image.variableName);
|
const newImageVariableNames = theme.CustomImages.map(
|
||||||
|
(image) => image.variableName,
|
||||||
|
);
|
||||||
|
|
||||||
// Remove old preview images that are no longer present
|
// Remove old preview images that are no longer present
|
||||||
this.previousImageVariableNames.forEach(variableName => {
|
this.previousImageVariableNames.forEach((variableName) => {
|
||||||
if (!newImageVariableNames.includes(variableName)) {
|
if (!newImageVariableNames.includes(variableName)) {
|
||||||
this.removeImageFromDocument(variableName);
|
this.removeImageFromDocument(variableName);
|
||||||
// Clean up cached URL
|
// Clean up cached URL
|
||||||
@@ -568,10 +624,16 @@ export class ThemeManager {
|
|||||||
// Only create new URL if one doesn't exist
|
// Only create new URL if one doesn't exist
|
||||||
const imageUrl = URL.createObjectURL(image.blob);
|
const imageUrl = URL.createObjectURL(image.blob);
|
||||||
this.imageUrlCache.set(image.variableName, imageUrl);
|
this.imageUrlCache.set(image.variableName, imageUrl);
|
||||||
document.documentElement.style.setProperty(`--${image.variableName}`, `url(${imageUrl})`);
|
document.documentElement.style.setProperty(
|
||||||
|
`--${image.variableName}`,
|
||||||
|
`url(${imageUrl})`,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// Reuse existing URL
|
// Reuse existing URL
|
||||||
document.documentElement.style.setProperty(`--${image.variableName}`, `url(${existingUrl})`);
|
document.documentElement.style.setProperty(
|
||||||
|
`--${image.variableName}`,
|
||||||
|
`url(${existingUrl})`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -588,7 +650,7 @@ export class ThemeManager {
|
|||||||
settingsState.selectedColor = theme.defaultColour;
|
settingsState.selectedColor = theme.defaultColour;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[ThemeManager] Error updating theme preview:', error);
|
console.error("[ThemeManager] Error updating theme preview:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -596,22 +658,25 @@ export class ThemeManager {
|
|||||||
* Update the preview of a theme (debounced)
|
* Update the preview of a theme (debounced)
|
||||||
* @param theme - The theme to update the preview of
|
* @param theme - The theme to update the preview of
|
||||||
*/
|
*/
|
||||||
public updatePreviewDebounced = debounce((theme: Partial<LoadedCustomTheme>): void => {
|
public updatePreviewDebounced = debounce(
|
||||||
this.updatePreview(theme);
|
(theme: Partial<LoadedCustomTheme>): void => {
|
||||||
}, 2);
|
this.updatePreview(theme);
|
||||||
|
},
|
||||||
|
2,
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear theme preview
|
* Clear theme preview
|
||||||
*/
|
*/
|
||||||
public clearPreview(): void {
|
public clearPreview(): void {
|
||||||
console.debug('[ThemeManager] Clearing theme preview');
|
console.debug("[ThemeManager] Clearing theme preview");
|
||||||
try {
|
try {
|
||||||
// Remove preview images and revoke URLs
|
// Remove preview images and revoke URLs
|
||||||
this.previousImageVariableNames.forEach(variableName => {
|
this.previousImageVariableNames.forEach((variableName) => {
|
||||||
this.removeImageFromDocument(variableName);
|
this.removeImageFromDocument(variableName);
|
||||||
});
|
});
|
||||||
// Clear all cached URLs
|
// Clear all cached URLs
|
||||||
this.imageUrlCache.forEach(url => URL.revokeObjectURL(url));
|
this.imageUrlCache.forEach((url) => URL.revokeObjectURL(url));
|
||||||
this.imageUrlCache.clear();
|
this.imageUrlCache.clear();
|
||||||
this.previousImageVariableNames = [];
|
this.previousImageVariableNames = [];
|
||||||
|
|
||||||
@@ -622,40 +687,51 @@ export class ThemeManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Restore original settings
|
// Restore original settings
|
||||||
const storedColor = localStorage.getItem('originalPreviewColor');
|
const storedColor = localStorage.getItem("originalPreviewColor");
|
||||||
|
|
||||||
if (storedColor) {
|
if (storedColor) {
|
||||||
settingsState.selectedColor = storedColor;
|
settingsState.selectedColor = storedColor;
|
||||||
localStorage.removeItem('originalPreviewColor');
|
localStorage.removeItem("originalPreviewColor");
|
||||||
} else if (this.originalPreviewColor !== null) {
|
} else if (this.originalPreviewColor !== null) {
|
||||||
console.debug('[ThemeManager] Restoring color from memory:', this.originalPreviewColor);
|
console.debug(
|
||||||
|
"[ThemeManager] Restoring color from memory:",
|
||||||
|
this.originalPreviewColor,
|
||||||
|
);
|
||||||
settingsState.selectedColor = this.originalPreviewColor;
|
settingsState.selectedColor = this.originalPreviewColor;
|
||||||
console.debug('[ThemeManager] Color after restore:', settingsState.selectedColor);
|
console.debug(
|
||||||
|
"[ThemeManager] Color after restore:",
|
||||||
|
settingsState.selectedColor,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
console.debug('[ThemeManager] No color to restore found');
|
console.debug("[ThemeManager] No color to restore found");
|
||||||
}
|
}
|
||||||
this.originalPreviewColor = null;
|
this.originalPreviewColor = null;
|
||||||
|
|
||||||
if (this.originalPreviewTheme !== null) {
|
if (this.originalPreviewTheme !== null) {
|
||||||
console.debug('[ThemeManager] Restoring dark mode:', this.originalPreviewTheme);
|
console.debug(
|
||||||
|
"[ThemeManager] Restoring dark mode:",
|
||||||
|
this.originalPreviewTheme,
|
||||||
|
);
|
||||||
settingsState.DarkMode = this.originalPreviewTheme;
|
settingsState.DarkMode = this.originalPreviewTheme;
|
||||||
this.originalPreviewTheme = null;
|
this.originalPreviewTheme = null;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[ThemeManager] Error clearing preview:', error);
|
console.error("[ThemeManager] Error clearing preview:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Utility methods
|
// Utility methods
|
||||||
private stripBase64Prefix(base64String: string): string {
|
private stripBase64Prefix(base64String: string): string {
|
||||||
if (!base64String) return '';
|
if (!base64String) return "";
|
||||||
|
|
||||||
const prefixRegex = /^data:[^;]+;base64,/;
|
const prefixRegex = /^data:[^;]+;base64,/;
|
||||||
try {
|
try {
|
||||||
return prefixRegex.test(base64String) ? base64String.replace(prefixRegex, '') : base64String;
|
return prefixRegex.test(base64String)
|
||||||
} catch(err) {
|
? base64String.replace(prefixRegex, "")
|
||||||
console.error('[ThemeManager] Error stripping base64 prefix:', err);
|
: base64String;
|
||||||
return '';
|
} catch (err) {
|
||||||
|
console.error("[ThemeManager] Error stripping base64 prefix:", err);
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -669,9 +745,9 @@ export class ThemeManager {
|
|||||||
ia[i] = byteString.charCodeAt(i);
|
ia[i] = byteString.charCodeAt(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Blob([ab], { type: 'image/png' });
|
return new Blob([ab], { type: "image/png" });
|
||||||
} catch(err) {
|
} catch (err) {
|
||||||
console.error('[ThemeManager] Error converting base64 to blob:', err);
|
console.error("[ThemeManager] Error converting base64 to blob:", err);
|
||||||
return new Blob();
|
return new Blob();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -681,7 +757,7 @@ export class ThemeManager {
|
|||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onloadend = () => {
|
reader.onloadend = () => {
|
||||||
const base64String = reader.result as string;
|
const base64String = reader.result as string;
|
||||||
const base64Data = base64String.split(',')[1];
|
const base64Data = base64String.split(",")[1];
|
||||||
resolve(base64Data);
|
resolve(base64Data);
|
||||||
};
|
};
|
||||||
reader.onerror = reject;
|
reader.onerror = reject;
|
||||||
@@ -692,23 +768,25 @@ export class ThemeManager {
|
|||||||
private saveThemeFile(data: object, fileName: string): void {
|
private saveThemeFile(data: object, fileName: string): void {
|
||||||
try {
|
try {
|
||||||
const fileData = JSON.stringify(data, null, 2);
|
const fileData = JSON.stringify(data, null, 2);
|
||||||
const blob = new Blob([fileData], { type: 'application/json' });
|
const blob = new Blob([fileData], { type: "application/json" });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const a = document.createElement('a');
|
const a = document.createElement("a");
|
||||||
a.href = url;
|
a.href = url;
|
||||||
a.download = `${fileName}.theme.json`;
|
a.download = `${fileName}.theme.json`;
|
||||||
document.body.appendChild(a);
|
document.body.appendChild(a);
|
||||||
a.click();
|
a.click();
|
||||||
document.body.removeChild(a);
|
document.body.removeChild(a);
|
||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
} catch(err) {
|
} catch (err) {
|
||||||
console.error('[ThemeManager] Error saving theme file:', err);
|
console.error("[ThemeManager] Error saving theme file:", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private removeImageFromDocument(variableName: string): void {
|
private removeImageFromDocument(variableName: string): void {
|
||||||
try {
|
try {
|
||||||
const value = document.documentElement.style.getPropertyValue('--' + variableName);
|
const value = document.documentElement.style.getPropertyValue(
|
||||||
|
"--" + variableName,
|
||||||
|
);
|
||||||
if (value) {
|
if (value) {
|
||||||
const url = this.imageUrlCache.get(variableName);
|
const url = this.imageUrlCache.get(variableName);
|
||||||
if (url) {
|
if (url) {
|
||||||
@@ -716,23 +794,23 @@ export class ThemeManager {
|
|||||||
this.imageUrlCache.delete(variableName);
|
this.imageUrlCache.delete(variableName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
document.documentElement.style.removeProperty('--' + variableName);
|
document.documentElement.style.removeProperty("--" + variableName);
|
||||||
} catch(err) {
|
} catch (err) {
|
||||||
console.error('[ThemeManager] Error removing image from document:', err);
|
console.error("[ThemeManager] Error removing image from document:", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private applyPreviewCSS(css: string): void {
|
private applyPreviewCSS(css: string): void {
|
||||||
console.debug('[ThemeManager] Applying preview CSS');
|
console.debug("[ThemeManager] Applying preview CSS");
|
||||||
try {
|
try {
|
||||||
if (!this.previewStyleElement) {
|
if (!this.previewStyleElement) {
|
||||||
this.previewStyleElement = document.createElement('style');
|
this.previewStyleElement = document.createElement("style");
|
||||||
this.previewStyleElement.id = 'custom-theme-preview';
|
this.previewStyleElement.id = "custom-theme-preview";
|
||||||
document.head.appendChild(this.previewStyleElement);
|
document.head.appendChild(this.previewStyleElement);
|
||||||
}
|
}
|
||||||
this.previewStyleElement.textContent = css;
|
this.previewStyleElement.textContent = css;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[ThemeManager] Error applying preview CSS:', error);
|
console.error("[ThemeManager] Error applying preview CSS:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,268 +1,279 @@
|
|||||||
import { settingsState } from '@/seqta/utils/listeners/SettingsState';
|
import { settingsState } from "@/seqta/utils/listeners/SettingsState";
|
||||||
import type { Plugin } from '../../core/types';
|
import type { Plugin } from "../../core/types";
|
||||||
import { convertTo12HourFormat } from '@/seqta/utils/convertTo12HourFormat';
|
import { convertTo12HourFormat } from "@/seqta/utils/convertTo12HourFormat";
|
||||||
import { waitForElm } from '@/seqta/utils/waitForElm';
|
import { waitForElm } from "@/seqta/utils/waitForElm";
|
||||||
|
|
||||||
const timetablePlugin: Plugin<{}, {}> = {
|
const timetablePlugin: Plugin<{}, {}> = {
|
||||||
id: 'timetable',
|
id: "timetable",
|
||||||
name: 'Timetable Enhancer',
|
name: "Timetable Enhancer",
|
||||||
description: 'Adds extra features to the timetable view',
|
description: "Adds extra features to the timetable view",
|
||||||
version: '1.0.0',
|
version: "1.0.0",
|
||||||
settings: {},
|
settings: {},
|
||||||
disableToggle: true,
|
disableToggle: true,
|
||||||
|
|
||||||
run: async (api) => {
|
run: async (api) => {
|
||||||
const { unregister } = api.seqta.onMount('.timetablepage', handleTimetable)
|
const { unregister } = api.seqta.onMount(".timetablepage", handleTimetable);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
// Call the unregister function to remove the mount listener
|
// Call the unregister function to remove the mount listener
|
||||||
unregister();
|
unregister();
|
||||||
|
|
||||||
const timetablePage = document.querySelector('.timetablepage')
|
const timetablePage = document.querySelector(".timetablepage");
|
||||||
if (timetablePage) {
|
if (timetablePage) {
|
||||||
const zoomControls = document.querySelector('.timetable-zoom-controls')
|
const zoomControls = document.querySelector(".timetable-zoom-controls");
|
||||||
if (zoomControls) zoomControls.remove()
|
if (zoomControls) zoomControls.remove();
|
||||||
|
|
||||||
const hideControls = document.querySelector('.timetable-hide-controls')
|
const hideControls = document.querySelector(".timetable-hide-controls");
|
||||||
if (hideControls) hideControls.remove()
|
if (hideControls) hideControls.remove();
|
||||||
|
|
||||||
resetTimetableStyles()
|
resetTimetableStyles();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Store event handlers globally for cleanup
|
// Store event handlers globally for cleanup
|
||||||
const zoomHandlers = new WeakMap<Element, { zoomIn: () => void; zoomOut: () => void }>()
|
const zoomHandlers = new WeakMap<
|
||||||
|
Element,
|
||||||
|
{ zoomIn: () => void; zoomOut: () => void }
|
||||||
|
>();
|
||||||
|
|
||||||
function resetTimetableStyles(): void {
|
function resetTimetableStyles(): void {
|
||||||
const firstDayColumn = document.querySelector(".dailycal .content .days td") as HTMLElement
|
const firstDayColumn = document.querySelector(
|
||||||
if (!firstDayColumn) return
|
".dailycal .content .days td",
|
||||||
|
) as HTMLElement;
|
||||||
|
if (!firstDayColumn) return;
|
||||||
|
|
||||||
const baseContainerHeight = parseInt(firstDayColumn.style.height) || firstDayColumn.offsetHeight
|
const baseContainerHeight =
|
||||||
|
parseInt(firstDayColumn.style.height) || firstDayColumn.offsetHeight;
|
||||||
|
|
||||||
const dayColumns = document.querySelectorAll(".dailycal .content .days td")
|
const dayColumns = document.querySelectorAll(".dailycal .content .days td");
|
||||||
dayColumns.forEach((td: Element) => {
|
dayColumns.forEach((td: Element) => {
|
||||||
(td as HTMLElement).style.height = `${baseContainerHeight}px`
|
(td as HTMLElement).style.height = `${baseContainerHeight}px`;
|
||||||
})
|
});
|
||||||
|
|
||||||
const timeColumn = document.querySelector(".times")
|
const timeColumn = document.querySelector(".times");
|
||||||
if (timeColumn) {
|
if (timeColumn) {
|
||||||
const times = timeColumn.querySelectorAll(".time")
|
const times = timeColumn.querySelectorAll(".time");
|
||||||
const timeHeight = baseContainerHeight / times.length
|
const timeHeight = baseContainerHeight / times.length;
|
||||||
times.forEach((time: Element) => {
|
times.forEach((time: Element) => {
|
||||||
(time as HTMLElement).style.height = `${timeHeight}px`
|
(time as HTMLElement).style.height = `${timeHeight}px`;
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const lessons = document.querySelectorAll(".dailycal .lesson")
|
const lessons = document.querySelectorAll(".dailycal .lesson");
|
||||||
lessons.forEach((lesson: Element) => {
|
lessons.forEach((lesson: Element) => {
|
||||||
const lessonEl = lesson as HTMLElement
|
const lessonEl = lesson as HTMLElement;
|
||||||
const originalHeight = lessonEl.getAttribute('data-original-height')
|
const originalHeight = lessonEl.getAttribute("data-original-height");
|
||||||
if (originalHeight) {
|
if (originalHeight) {
|
||||||
lessonEl.style.height = `${originalHeight}px`
|
lessonEl.style.height = `${originalHeight}px`;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
const entries = document.querySelectorAll(".entry")
|
const entries = document.querySelectorAll(".entry");
|
||||||
entries.forEach((entry: Element) => {
|
entries.forEach((entry: Element) => {
|
||||||
const entryEl = entry as HTMLElement
|
const entryEl = entry as HTMLElement;
|
||||||
entryEl.style.opacity = '1'
|
entryEl.style.opacity = "1";
|
||||||
})
|
});
|
||||||
|
|
||||||
const zoomControls = document.querySelector('.timetable-zoom-controls')
|
const zoomControls = document.querySelector(".timetable-zoom-controls");
|
||||||
if (zoomControls) {
|
if (zoomControls) {
|
||||||
const handlers = zoomHandlers.get(zoomControls)
|
const handlers = zoomHandlers.get(zoomControls);
|
||||||
if (handlers) {
|
if (handlers) {
|
||||||
const zoomIn = zoomControls.querySelector('.timetable-zoom:nth-child(2)')
|
const zoomIn = zoomControls.querySelector(".timetable-zoom:nth-child(2)");
|
||||||
const zoomOut = zoomControls.querySelector('.timetable-zoom:nth-child(1)')
|
const zoomOut = zoomControls.querySelector(
|
||||||
if (zoomIn) zoomIn.removeEventListener('click', handlers.zoomIn)
|
".timetable-zoom:nth-child(1)",
|
||||||
if (zoomOut) zoomOut.removeEventListener('click', handlers.zoomOut)
|
);
|
||||||
zoomHandlers.delete(zoomControls)
|
if (zoomIn) zoomIn.removeEventListener("click", handlers.zoomIn);
|
||||||
|
if (zoomOut) zoomOut.removeEventListener("click", handlers.zoomOut);
|
||||||
|
zoomHandlers.delete(zoomControls);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
// Store original heights when timetable loads
|
||||||
const lessons = document.querySelectorAll(".dailycal .lesson")
|
const lessons = document.querySelectorAll(".dailycal .lesson");
|
||||||
lessons.forEach((lesson: Element) => {
|
lessons.forEach((lesson: Element) => {
|
||||||
const lessonEl = lesson as HTMLElement
|
const lessonEl = lesson as HTMLElement;
|
||||||
lessonEl.setAttribute(
|
lessonEl.setAttribute(
|
||||||
"data-original-height",
|
"data-original-height",
|
||||||
lessonEl.offsetHeight.toString(),
|
lessonEl.offsetHeight.toString(),
|
||||||
)
|
);
|
||||||
})
|
});
|
||||||
|
|
||||||
// Existing time format code
|
// 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()
|
handleTimetableZoom();
|
||||||
handleTimetableAssessmentHide()
|
handleTimetableAssessmentHide();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleTimetableZoom(): void {
|
function handleTimetableZoom(): void {
|
||||||
console.log("Initializing timetable zoom controls")
|
console.log("Initializing timetable zoom controls");
|
||||||
|
|
||||||
// Lazy initialize state variables only when function is first called
|
// Lazy initialize state variables only when function is first called
|
||||||
let timetableZoomLevel = 1
|
let timetableZoomLevel = 1;
|
||||||
let baseContainerHeight: number | null = null
|
let baseContainerHeight: number | null = null;
|
||||||
const originalEntryPositions = new Map<
|
const originalEntryPositions = new Map<
|
||||||
Element,
|
Element,
|
||||||
{ topRatio: number; heightRatio: number }
|
{ topRatio: number; heightRatio: number }
|
||||||
>()
|
>();
|
||||||
|
|
||||||
// Create zoom controls
|
// Create zoom controls
|
||||||
const zoomControls = document.createElement("div")
|
const zoomControls = document.createElement("div");
|
||||||
zoomControls.className = "timetable-zoom-controls"
|
zoomControls.className = "timetable-zoom-controls";
|
||||||
|
|
||||||
const zoomIn = document.createElement("button")
|
const zoomIn = document.createElement("button");
|
||||||
zoomIn.className = "uiButton timetable-zoom iconFamily"
|
zoomIn.className = "uiButton timetable-zoom iconFamily";
|
||||||
zoomIn.innerHTML = "" // Unicode for zoom in icon (custom iconfamily)
|
zoomIn.innerHTML = ""; // Unicode for zoom in icon (custom iconfamily)
|
||||||
|
|
||||||
const zoomOut = document.createElement("button")
|
const zoomOut = document.createElement("button");
|
||||||
zoomOut.className = "uiButton timetable-zoom iconFamily"
|
zoomOut.className = "uiButton timetable-zoom iconFamily";
|
||||||
zoomOut.innerHTML = "" // Unicode for zoom out icon (custom iconfamily)
|
zoomOut.innerHTML = ""; // Unicode for zoom out icon (custom iconfamily)
|
||||||
|
|
||||||
zoomControls.appendChild(zoomOut)
|
zoomControls.appendChild(zoomOut);
|
||||||
zoomControls.appendChild(zoomIn)
|
zoomControls.appendChild(zoomIn);
|
||||||
|
|
||||||
const toolbar = document.getElementById("toolbar")
|
const toolbar = document.getElementById("toolbar");
|
||||||
toolbar?.appendChild(zoomControls)
|
toolbar?.appendChild(zoomControls);
|
||||||
|
|
||||||
// Store event listener references
|
// Store event listener references
|
||||||
const zoomInHandler = () => {
|
const zoomInHandler = () => {
|
||||||
if (timetableZoomLevel < 2) {
|
if (timetableZoomLevel < 2) {
|
||||||
timetableZoomLevel += 0.2
|
timetableZoomLevel += 0.2;
|
||||||
updateZoom()
|
updateZoom();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const zoomOutHandler = () => {
|
const zoomOutHandler = () => {
|
||||||
if (timetableZoomLevel > 0.6) {
|
if (timetableZoomLevel > 0.6) {
|
||||||
timetableZoomLevel -= 0.2
|
timetableZoomLevel -= 0.2;
|
||||||
updateZoom()
|
updateZoom();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
zoomIn.addEventListener("click", zoomInHandler)
|
zoomIn.addEventListener("click", zoomInHandler);
|
||||||
zoomOut.addEventListener("click", zoomOutHandler)
|
zoomOut.addEventListener("click", zoomOutHandler);
|
||||||
|
|
||||||
// Store references for cleanup
|
// Store references for cleanup
|
||||||
zoomHandlers.set(zoomControls, { zoomIn: zoomInHandler, zoomOut: zoomOutHandler })
|
zoomHandlers.set(zoomControls, {
|
||||||
|
zoomIn: zoomInHandler,
|
||||||
|
zoomOut: zoomOutHandler,
|
||||||
|
});
|
||||||
|
|
||||||
const initializePositions = () => {
|
const initializePositions = () => {
|
||||||
// Get the base container height from the first TD
|
// Get the base container height from the first TD
|
||||||
const firstDayColumn = document.querySelector(
|
const firstDayColumn = document.querySelector(
|
||||||
".dailycal .content .days td",
|
".dailycal .content .days td",
|
||||||
) as HTMLElement
|
) as HTMLElement;
|
||||||
if (!firstDayColumn) return false
|
if (!firstDayColumn) return false;
|
||||||
|
|
||||||
baseContainerHeight =
|
baseContainerHeight =
|
||||||
parseInt(firstDayColumn.style.height) || firstDayColumn.offsetHeight
|
parseInt(firstDayColumn.style.height) || firstDayColumn.offsetHeight;
|
||||||
|
|
||||||
// Store original ratios
|
// Store original ratios
|
||||||
const entries = document.querySelectorAll(".entriesWrapper .entry")
|
const entries = document.querySelectorAll(".entriesWrapper .entry");
|
||||||
entries.forEach((entry: Element) => {
|
entries.forEach((entry: Element) => {
|
||||||
const entryEl = entry as HTMLElement
|
const entryEl = entry as HTMLElement;
|
||||||
|
|
||||||
// Calculate ratios relative to detected base height
|
// Calculate ratios relative to detected base height
|
||||||
if (baseContainerHeight === null) return
|
if (baseContainerHeight === null) return;
|
||||||
const topRatio = parseInt(entryEl.style.top) / baseContainerHeight
|
const topRatio = parseInt(entryEl.style.top) / baseContainerHeight;
|
||||||
const heightRatio = parseInt(entryEl.style.height) / baseContainerHeight
|
const heightRatio = parseInt(entryEl.style.height) / baseContainerHeight;
|
||||||
|
|
||||||
originalEntryPositions.set(entry, { topRatio, heightRatio })
|
originalEntryPositions.set(entry, { topRatio, heightRatio });
|
||||||
})
|
});
|
||||||
|
|
||||||
return true
|
return true;
|
||||||
}
|
};
|
||||||
|
|
||||||
const updateZoom = () => {
|
const updateZoom = () => {
|
||||||
// Initialize positions if not already done
|
// Initialize positions if not already done
|
||||||
if (baseContainerHeight === null && !initializePositions()) {
|
if (baseContainerHeight === null && !initializePositions()) {
|
||||||
console.error("Failed to initialize positions")
|
console.error("Failed to initialize positions");
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.debug(`Updating zoom level to: ${timetableZoomLevel}`)
|
console.debug(`Updating zoom level to: ${timetableZoomLevel}`);
|
||||||
|
|
||||||
// Calculate new container height
|
// Calculate new container height
|
||||||
if (baseContainerHeight === null) return
|
if (baseContainerHeight === null) return;
|
||||||
const newContainerHeight = baseContainerHeight * timetableZoomLevel
|
const newContainerHeight = baseContainerHeight * timetableZoomLevel;
|
||||||
|
|
||||||
// Update all day columns (TDs)
|
// Update all day columns (TDs)
|
||||||
const dayColumns = document.querySelectorAll(".dailycal .content .days td")
|
const dayColumns = document.querySelectorAll(".dailycal .content .days td");
|
||||||
dayColumns.forEach((td: Element) => {
|
dayColumns.forEach((td: Element) => {
|
||||||
(td as HTMLElement).style.height = `${newContainerHeight}px`
|
(td as HTMLElement).style.height = `${newContainerHeight}px`;
|
||||||
})
|
});
|
||||||
|
|
||||||
// Update all entries using stored ratios
|
// Update all entries using stored ratios
|
||||||
const entries = document.querySelectorAll(".entriesWrapper .entry")
|
const entries = document.querySelectorAll(".entriesWrapper .entry");
|
||||||
entries.forEach((entry: Element) => {
|
entries.forEach((entry: Element) => {
|
||||||
const entryEl = entry as HTMLElement
|
const entryEl = entry as HTMLElement;
|
||||||
const originalRatios = originalEntryPositions.get(entry)
|
const originalRatios = originalEntryPositions.get(entry);
|
||||||
|
|
||||||
if (originalRatios) {
|
if (originalRatios) {
|
||||||
// Calculate new positions from original ratios
|
// Calculate new positions from original ratios
|
||||||
const newTop = originalRatios.topRatio * newContainerHeight
|
const newTop = originalRatios.topRatio * newContainerHeight;
|
||||||
const newHeight = originalRatios.heightRatio * newContainerHeight
|
const newHeight = originalRatios.heightRatio * newContainerHeight;
|
||||||
|
|
||||||
// Apply new values
|
// Apply new values
|
||||||
entryEl.style.top = `${Math.round(newTop)}px`
|
entryEl.style.top = `${Math.round(newTop)}px`;
|
||||||
entryEl.style.height = `${Math.round(newHeight)}px`
|
entryEl.style.height = `${Math.round(newHeight)}px`;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
// Update time column to match
|
// Update time column to match
|
||||||
const timeColumn = document.querySelector(".times")
|
const timeColumn = document.querySelector(".times");
|
||||||
if (timeColumn) {
|
if (timeColumn) {
|
||||||
const times = timeColumn.querySelectorAll(".time")
|
const times = timeColumn.querySelectorAll(".time");
|
||||||
const timeHeight = newContainerHeight / times.length
|
const timeHeight = newContainerHeight / times.length;
|
||||||
times.forEach((time: Element) => {
|
times.forEach((time: Element) => {
|
||||||
(time as HTMLElement).style.height = `${timeHeight}px`
|
(time as HTMLElement).style.height = `${timeHeight}px`;
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
entries[Math.round((entries.length - 1) / 2)].scrollIntoView({
|
entries[Math.round((entries.length - 1) / 2)].scrollIntoView({
|
||||||
behavior: "instant",
|
behavior: "instant",
|
||||||
block: "center",
|
block: "center",
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleTimetableAssessmentHide(): void {
|
function handleTimetableAssessmentHide(): void {
|
||||||
const hideControls = document.createElement("div")
|
const hideControls = document.createElement("div");
|
||||||
hideControls.className = "timetable-hide-controls"
|
hideControls.className = "timetable-hide-controls";
|
||||||
|
|
||||||
const hideOn = document.createElement("button")
|
const hideOn = document.createElement("button");
|
||||||
hideOn.className = "uiButton timetable-hide iconFamily"
|
hideOn.className = "uiButton timetable-hide iconFamily";
|
||||||
hideOn.innerHTML = "👁"
|
hideOn.innerHTML = "👁";
|
||||||
|
|
||||||
hideControls.appendChild(hideOn)
|
hideControls.appendChild(hideOn);
|
||||||
|
|
||||||
const toolbar = document.getElementById("toolbar")
|
const toolbar = document.getElementById("toolbar");
|
||||||
toolbar?.appendChild(hideControls)
|
toolbar?.appendChild(hideControls);
|
||||||
|
|
||||||
function hideElements(): void {
|
function hideElements(): void {
|
||||||
const entries = document.querySelectorAll(".entry")
|
const entries = document.querySelectorAll(".entry");
|
||||||
|
|
||||||
entries.forEach((entry: Element) => {
|
entries.forEach((entry: Element) => {
|
||||||
const entryEl = entry as HTMLElement
|
const entryEl = entry as HTMLElement;
|
||||||
if (!entryEl.classList.contains("assessment")) {
|
if (!entryEl.classList.contains("assessment")) {
|
||||||
entryEl.style.opacity = entryEl.style.opacity === "0.3" ? "1" : "0.3"
|
entryEl.style.opacity = entryEl.style.opacity === "0.3" ? "1" : "0.3";
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
hideOn.addEventListener("click", hideElements)
|
hideOn.addEventListener("click", hideElements);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default timetablePlugin;
|
export default timetablePlugin;
|
||||||
|
|||||||
@@ -1,7 +1,16 @@
|
|||||||
import type { EventsAPI, Plugin, PluginAPI, PluginSettings, SEQTAAPI, SettingsAPI, SettingValue, StorageAPI } from './types';
|
import type {
|
||||||
import { eventManager } from '@/seqta/utils/listeners/EventManager';
|
EventsAPI,
|
||||||
import ReactFiber from '@/seqta/utils/ReactFiber';
|
Plugin,
|
||||||
import browser from 'webextension-polyfill';
|
PluginAPI,
|
||||||
|
PluginSettings,
|
||||||
|
SEQTAAPI,
|
||||||
|
SettingsAPI,
|
||||||
|
SettingValue,
|
||||||
|
StorageAPI,
|
||||||
|
} from "./types";
|
||||||
|
import { eventManager } from "@/seqta/utils/listeners/EventManager";
|
||||||
|
import ReactFiber from "@/seqta/utils/ReactFiber";
|
||||||
|
import browser from "webextension-polyfill";
|
||||||
|
|
||||||
function createSEQTAAPI(): SEQTAAPI {
|
function createSEQTAAPI(): SEQTAAPI {
|
||||||
return {
|
return {
|
||||||
@@ -11,41 +20,46 @@ function createSEQTAAPI(): SEQTAAPI {
|
|||||||
{
|
{
|
||||||
customCheck: (element) => element.matches(selector),
|
customCheck: (element) => element.matches(selector),
|
||||||
},
|
},
|
||||||
callback
|
callback,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
getFiber: (selector) => {
|
getFiber: (selector) => {
|
||||||
return ReactFiber.find(selector);
|
return ReactFiber.find(selector);
|
||||||
},
|
},
|
||||||
getCurrentPage: () => {
|
getCurrentPage: () => {
|
||||||
const path = window.location.hash.split('?page=/')[1] || '';
|
const path = window.location.hash.split("?page=/")[1] || "";
|
||||||
return path.split('/')[0];
|
return path.split("/")[0];
|
||||||
},
|
},
|
||||||
onPageChange: (callback) => {
|
onPageChange: (callback) => {
|
||||||
const handler = () => {
|
const handler = () => {
|
||||||
const page = window.location.hash.split('?page=/')[1] || '';
|
const page = window.location.hash.split("?page=/")[1] || "";
|
||||||
callback(page.split('/')[0]);
|
callback(page.split("/")[0]);
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener('hashchange', handler);
|
window.addEventListener("hashchange", handler);
|
||||||
|
|
||||||
// Return an unregister function
|
// Return an unregister function
|
||||||
return {
|
return {
|
||||||
unregister: () => {
|
unregister: () => {
|
||||||
window.removeEventListener('hashchange', handler);
|
window.removeEventListener("hashchange", handler);
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function createSettingsAPI<T extends PluginSettings>(plugin: Plugin<T>): SettingsAPI<T> & { loaded: Promise<void> } {
|
function createSettingsAPI<T extends PluginSettings>(
|
||||||
|
plugin: Plugin<T>,
|
||||||
|
): SettingsAPI<T> & { loaded: Promise<void> } {
|
||||||
const storageKey = `plugin.${plugin.id}.settings`;
|
const storageKey = `plugin.${plugin.id}.settings`;
|
||||||
const listeners = new Map<keyof T, Set<(value: any) => void>>();
|
const listeners = new Map<keyof T, Set<(value: any) => void>>();
|
||||||
|
|
||||||
// Initialize with default values
|
// Initialize with default values
|
||||||
const settingsWithMeta: any = {
|
const settingsWithMeta: any = {
|
||||||
onChange: <K extends keyof T>(key: K, callback: (value: SettingValue<T[K]>) => void) => {
|
onChange: <K extends keyof T>(
|
||||||
|
key: K,
|
||||||
|
callback: (value: SettingValue<T[K]>) => void,
|
||||||
|
) => {
|
||||||
if (!listeners.has(key)) {
|
if (!listeners.has(key)) {
|
||||||
listeners.set(key, new Set());
|
listeners.set(key, new Set());
|
||||||
}
|
}
|
||||||
@@ -53,13 +67,16 @@ function createSettingsAPI<T extends PluginSettings>(plugin: Plugin<T>): Setting
|
|||||||
return {
|
return {
|
||||||
unregister: () => {
|
unregister: () => {
|
||||||
listeners.get(key)!.delete(callback);
|
listeners.get(key)!.delete(callback);
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
offChange: <K extends keyof T>(key: K, callback: (value: SettingValue<T[K]>) => void) => {
|
offChange: <K extends keyof T>(
|
||||||
|
key: K,
|
||||||
|
callback: (value: SettingValue<T[K]>) => void,
|
||||||
|
) => {
|
||||||
listeners.get(key)?.delete(callback);
|
listeners.get(key)?.delete(callback);
|
||||||
},
|
},
|
||||||
loaded: Promise.resolve() // will be replaced below
|
loaded: Promise.resolve(), // will be replaced below
|
||||||
};
|
};
|
||||||
|
|
||||||
// Fill with defaults first
|
// Fill with defaults first
|
||||||
@@ -71,33 +88,45 @@ function createSettingsAPI<T extends PluginSettings>(plugin: Plugin<T>): Setting
|
|||||||
const loaded = (async () => {
|
const loaded = (async () => {
|
||||||
try {
|
try {
|
||||||
const stored = await browser.storage.local.get(storageKey);
|
const stored = await browser.storage.local.get(storageKey);
|
||||||
const storedSettings = stored[storageKey] as Partial<Record<keyof T, any>>;
|
const storedSettings = stored[storageKey] as Partial<
|
||||||
|
Record<keyof T, any>
|
||||||
|
>;
|
||||||
if (storedSettings) {
|
if (storedSettings) {
|
||||||
for (const key in storedSettings) {
|
for (const key in storedSettings) {
|
||||||
if (key in settingsWithMeta) {
|
if (key in settingsWithMeta) {
|
||||||
settingsWithMeta[key] = storedSettings[key];
|
settingsWithMeta[key] = storedSettings[key];
|
||||||
listeners.get(key as keyof T)?.forEach(cb => cb(storedSettings[key]));
|
listeners
|
||||||
|
.get(key as keyof T)
|
||||||
|
?.forEach((cb) => cb(storedSettings[key]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[BetterSEQTA+] Error loading settings for plugin ${plugin.id}:`, error);
|
console.error(
|
||||||
|
`[BetterSEQTA+] Error loading settings for plugin ${plugin.id}:`,
|
||||||
|
error,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
settingsWithMeta.loaded = loaded;
|
settingsWithMeta.loaded = loaded;
|
||||||
|
|
||||||
// Listen for storage changes and update settingsWithMeta
|
// Listen for storage changes and update settingsWithMeta
|
||||||
const handleStorageChange = (changes: { [key: string]: browser.Storage.StorageChange }, area: string) => {
|
const handleStorageChange = (
|
||||||
if (area !== 'local' || !(storageKey in changes)) return;
|
changes: { [key: string]: browser.Storage.StorageChange },
|
||||||
|
area: string,
|
||||||
|
) => {
|
||||||
|
if (area !== "local" || !(storageKey in changes)) return;
|
||||||
|
|
||||||
const newValue = changes[storageKey].newValue as Partial<Record<keyof T, any>> | undefined;
|
const newValue = changes[storageKey].newValue as
|
||||||
|
| Partial<Record<keyof T, any>>
|
||||||
|
| undefined;
|
||||||
if (!newValue) return;
|
if (!newValue) return;
|
||||||
|
|
||||||
for (const key in newValue) {
|
for (const key in newValue) {
|
||||||
const typedKey = key as keyof T;
|
const typedKey = key as keyof T;
|
||||||
settingsWithMeta[typedKey] = newValue[typedKey];
|
settingsWithMeta[typedKey] = newValue[typedKey];
|
||||||
listeners.get(typedKey)?.forEach(cb => cb(newValue[typedKey]));
|
listeners.get(typedKey)?.forEach((cb) => cb(newValue[typedKey]));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -108,7 +137,8 @@ function createSettingsAPI<T extends PluginSettings>(plugin: Plugin<T>): Setting
|
|||||||
return target[prop];
|
return target[prop];
|
||||||
},
|
},
|
||||||
set(target, prop, value) {
|
set(target, prop, value) {
|
||||||
if (['onChange', 'offChange', 'loaded'].includes(prop as string)) return false;
|
if (["onChange", "offChange", "loaded"].includes(prop as string))
|
||||||
|
return false;
|
||||||
|
|
||||||
target[prop] = value;
|
target[prop] = value;
|
||||||
|
|
||||||
@@ -120,19 +150,23 @@ function createSettingsAPI<T extends PluginSettings>(plugin: Plugin<T>): Setting
|
|||||||
|
|
||||||
browser.storage.local.set({ [storageKey]: dataToStore });
|
browser.storage.local.set({ [storageKey]: dataToStore });
|
||||||
|
|
||||||
listeners.get(prop as keyof T)?.forEach(cb => cb(value));
|
listeners.get(prop as keyof T)?.forEach((cb) => cb(value));
|
||||||
return true;
|
return true;
|
||||||
}
|
},
|
||||||
}) as SettingsAPI<T> & { loaded: Promise<void> };
|
}) as SettingsAPI<T> & { loaded: Promise<void> };
|
||||||
|
|
||||||
return proxy;
|
return proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createStorageAPI<T = any>(pluginId: string): StorageAPI<T> & { [K in keyof T]: T[K] } {
|
function createStorageAPI<T = any>(
|
||||||
|
pluginId: string,
|
||||||
|
): StorageAPI<T> & { [K in keyof T]: T[K] } {
|
||||||
const prefix = `plugin.${pluginId}.storage.`;
|
const prefix = `plugin.${pluginId}.storage.`;
|
||||||
const cache: Record<string, any> = {};
|
const cache: Record<string, any> = {};
|
||||||
const listeners = new Map<string, Set<(value: any) => void>>();
|
const listeners = new Map<string, Set<(value: any) => void>>();
|
||||||
const storageListeners = new Set<(changes: { [key: string]: any }, area: string) => void>();
|
const storageListeners = new Set<
|
||||||
|
(changes: { [key: string]: any }, area: string) => void
|
||||||
|
>();
|
||||||
|
|
||||||
// Load all existing storage values for this plugin
|
// Load all existing storage values for this plugin
|
||||||
const loadStoragePromise = (async () => {
|
const loadStoragePromise = (async () => {
|
||||||
@@ -147,20 +181,28 @@ function createStorageAPI<T = any>(pluginId: string): StorageAPI<T> & { [K in ke
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[BetterSEQTA+] Error loading storage for plugin ${pluginId}:`, error);
|
console.error(
|
||||||
|
`[BetterSEQTA+] Error loading storage for plugin ${pluginId}:`,
|
||||||
|
error,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// Listen for storage changes
|
// Listen for storage changes
|
||||||
const handleStorageChange = (changes: { [key: string]: any }, area: string) => {
|
const handleStorageChange = (
|
||||||
if (area === 'local') {
|
changes: { [key: string]: any },
|
||||||
|
area: string,
|
||||||
|
) => {
|
||||||
|
if (area === "local") {
|
||||||
Object.entries(changes).forEach(([key, change]) => {
|
Object.entries(changes).forEach(([key, change]) => {
|
||||||
if (key.startsWith(prefix)) {
|
if (key.startsWith(prefix)) {
|
||||||
const shortKey = key.slice(prefix.length);
|
const shortKey = key.slice(prefix.length);
|
||||||
cache[shortKey] = change.newValue;
|
cache[shortKey] = change.newValue;
|
||||||
|
|
||||||
// Notify listeners
|
// Notify listeners
|
||||||
listeners.get(shortKey)?.forEach(callback => callback(change.newValue));
|
listeners
|
||||||
|
.get(shortKey)
|
||||||
|
?.forEach((callback) => callback(change.newValue));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -171,7 +213,7 @@ function createStorageAPI<T = any>(pluginId: string): StorageAPI<T> & { [K in ke
|
|||||||
// Create the proxy for direct property access
|
// Create the proxy for direct property access
|
||||||
return new Proxy(cache, {
|
return new Proxy(cache, {
|
||||||
get(target, prop: string) {
|
get(target, prop: string) {
|
||||||
if (prop === 'onChange') {
|
if (prop === "onChange") {
|
||||||
return (key: keyof T, callback: (value: T[keyof T]) => void) => {
|
return (key: keyof T, callback: (value: T[keyof T]) => void) => {
|
||||||
if (!listeners.has(key as string)) {
|
if (!listeners.has(key as string)) {
|
||||||
listeners.set(key as string, new Set());
|
listeners.set(key as string, new Set());
|
||||||
@@ -180,16 +222,16 @@ function createStorageAPI<T = any>(pluginId: string): StorageAPI<T> & { [K in ke
|
|||||||
return {
|
return {
|
||||||
unregister: () => {
|
unregister: () => {
|
||||||
listeners.get(key as string)?.delete(callback);
|
listeners.get(key as string)?.delete(callback);
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (prop === 'offChange') {
|
if (prop === "offChange") {
|
||||||
return (key: keyof T, callback: (value: T[keyof T]) => void) => {
|
return (key: keyof T, callback: (value: T[keyof T]) => void) => {
|
||||||
listeners.get(key as string)?.delete(callback);
|
listeners.get(key as string)?.delete(callback);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (prop === 'loaded') {
|
if (prop === "loaded") {
|
||||||
return loadStoragePromise;
|
return loadStoragePromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,7 +239,7 @@ function createStorageAPI<T = any>(pluginId: string): StorageAPI<T> & { [K in ke
|
|||||||
return target[prop];
|
return target[prop];
|
||||||
},
|
},
|
||||||
set(target, prop: string, value: any) {
|
set(target, prop: string, value: any) {
|
||||||
if (['onChange', 'offChange', 'loaded'].includes(prop)) {
|
if (["onChange", "offChange", "loaded"].includes(prop)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,16 +248,19 @@ function createStorageAPI<T = any>(pluginId: string): StorageAPI<T> & { [K in ke
|
|||||||
browser.storage.local.set({ [prefix + prop]: value });
|
browser.storage.local.set({ [prefix + prop]: value });
|
||||||
|
|
||||||
// Notify listeners
|
// Notify listeners
|
||||||
listeners.get(prop)?.forEach(callback => callback(value));
|
listeners.get(prop)?.forEach((callback) => callback(value));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
},
|
||||||
}) as StorageAPI<T> & { [K in keyof T]: T[K] };
|
}) as StorageAPI<T> & { [K in keyof T]: T[K] };
|
||||||
}
|
}
|
||||||
|
|
||||||
function createEventsAPI(pluginId: string): EventsAPI {
|
function createEventsAPI(pluginId: string): EventsAPI {
|
||||||
const prefix = `plugin.${pluginId}.`;
|
const prefix = `plugin.${pluginId}.`;
|
||||||
const eventListeners = new Map<string, Set<{ callback: (...args: any[]) => void, listener: EventListener }>>();
|
const eventListeners = new Map<
|
||||||
|
string,
|
||||||
|
Set<{ callback: (...args: any[]) => void; listener: EventListener }>
|
||||||
|
>();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
on: (event, callback) => {
|
on: (event, callback) => {
|
||||||
@@ -235,20 +280,22 @@ function createEventsAPI(pluginId: string): EventsAPI {
|
|||||||
unregister: () => {
|
unregister: () => {
|
||||||
document.removeEventListener(fullEventName, listener);
|
document.removeEventListener(fullEventName, listener);
|
||||||
eventListeners.get(event)?.delete({ callback, listener });
|
eventListeners.get(event)?.delete({ callback, listener });
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
emit: (event, ...args) => {
|
emit: (event, ...args) => {
|
||||||
document.dispatchEvent(
|
document.dispatchEvent(
|
||||||
new CustomEvent(prefix + event, {
|
new CustomEvent(prefix + event, {
|
||||||
detail: args.length > 0 ? args : null
|
detail: args.length > 0 ? args : null,
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createPluginAPI<T extends PluginSettings, S = any>(plugin: Plugin<T, S>): PluginAPI<T, S> {
|
export function createPluginAPI<T extends PluginSettings, S = any>(
|
||||||
|
plugin: Plugin<T, S>,
|
||||||
|
): PluginAPI<T, S> {
|
||||||
return {
|
return {
|
||||||
seqta: createSEQTAAPI(),
|
seqta: createSEQTAAPI(),
|
||||||
settings: createSettingsAPI(plugin),
|
settings: createSettingsAPI(plugin),
|
||||||
|
|||||||
+96
-62
@@ -1,6 +1,13 @@
|
|||||||
import type { BooleanSetting, NumberSetting, Plugin, PluginSettings, SelectSetting, StringSetting } from './types';
|
import type {
|
||||||
import { createPluginAPI } from './createAPI';
|
BooleanSetting,
|
||||||
import browser from 'webextension-polyfill';
|
NumberSetting,
|
||||||
|
Plugin,
|
||||||
|
PluginSettings,
|
||||||
|
SelectSetting,
|
||||||
|
StringSetting,
|
||||||
|
} from "./types";
|
||||||
|
import { createPluginAPI } from "./createAPI";
|
||||||
|
import browser from "webextension-polyfill";
|
||||||
|
|
||||||
interface PluginSettingsStorage {
|
interface PluginSettingsStorage {
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
@@ -49,7 +56,7 @@ export class PluginManager {
|
|||||||
|
|
||||||
private async processBackloggedEvents(pluginId: string) {
|
private async processBackloggedEvents(pluginId: string) {
|
||||||
for (const [key, argsList] of this.eventBacklog.entries()) {
|
for (const [key, argsList] of this.eventBacklog.entries()) {
|
||||||
const [eventPluginId, event] = key.split(':');
|
const [eventPluginId, event] = key.split(":");
|
||||||
if (eventPluginId === pluginId) {
|
if (eventPluginId === pluginId) {
|
||||||
for (const args of argsList) {
|
for (const args of argsList) {
|
||||||
this.dispatchPluginEvent(pluginId, event, args);
|
this.dispatchPluginEvent(pluginId, event, args);
|
||||||
@@ -59,7 +66,9 @@ export class PluginManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public registerPlugin<T extends PluginSettings, S>(plugin: Plugin<T, S>): void {
|
public registerPlugin<T extends PluginSettings, S>(
|
||||||
|
plugin: Plugin<T, S>,
|
||||||
|
): void {
|
||||||
if (this.plugins.has(plugin.id)) {
|
if (this.plugins.has(plugin.id)) {
|
||||||
throw new Error(`Plugin with id "${plugin.id}" is already registered`);
|
throw new Error(`Plugin with id "${plugin.id}" is already registered`);
|
||||||
}
|
}
|
||||||
@@ -82,31 +91,35 @@ export class PluginManager {
|
|||||||
|
|
||||||
// Check if plugin is enabled before starting
|
// Check if plugin is enabled before starting
|
||||||
if (plugin.disableToggle) {
|
if (plugin.disableToggle) {
|
||||||
const settings = await browser.storage.local.get(`plugin.${pluginId}.settings`);
|
const settings = await browser.storage.local.get(
|
||||||
const pluginSettings = settings[`plugin.${pluginId}.settings`] as PluginSettingsStorage | undefined;
|
`plugin.${pluginId}.settings`,
|
||||||
const enabled = pluginSettings?.enabled ?? plugin.defaultEnabled ?? true;
|
);
|
||||||
|
const pluginSettings = settings[`plugin.${pluginId}.settings`] as
|
||||||
|
| PluginSettingsStorage
|
||||||
|
| undefined;
|
||||||
|
const enabled =
|
||||||
|
pluginSettings?.enabled ?? plugin.defaultEnabled ?? true;
|
||||||
if (!enabled) {
|
if (!enabled) {
|
||||||
console.info(`Plugin "${pluginId}" is disabled, skipping initialization`);
|
console.info(
|
||||||
|
`Plugin "${pluginId}" is disabled, skipping initialization`,
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inject plugin styles if provided
|
// Inject plugin styles if provided
|
||||||
if (plugin.styles) {
|
if (plugin.styles) {
|
||||||
const styleElement = document.createElement('style');
|
const styleElement = document.createElement("style");
|
||||||
styleElement.textContent = plugin.styles;
|
styleElement.textContent = plugin.styles;
|
||||||
document.head.appendChild(styleElement);
|
document.head.appendChild(styleElement);
|
||||||
this.styleElements.set(pluginId, styleElement);
|
this.styleElements.set(pluginId, styleElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for both settings and storage to be loaded before starting the plugin
|
// Wait for both settings and storage to be loaded before starting the plugin
|
||||||
await Promise.all([
|
await Promise.all([(api.settings as any).loaded, api.storage.loaded]);
|
||||||
(api.settings as any).loaded,
|
|
||||||
api.storage.loaded
|
|
||||||
]);
|
|
||||||
|
|
||||||
const result = await plugin.run(api);
|
const result = await plugin.run(api);
|
||||||
if (typeof result === 'function') {
|
if (typeof result === "function") {
|
||||||
this.cleanupFunctions.set(plugin.id, result);
|
this.cleanupFunctions.set(plugin.id, result);
|
||||||
}
|
}
|
||||||
this.runningPlugins.set(pluginId, true);
|
this.runningPlugins.set(pluginId, true);
|
||||||
@@ -115,17 +128,20 @@ export class PluginManager {
|
|||||||
// Process any backlogged events
|
// Process any backlogged events
|
||||||
await this.processBackloggedEvents(pluginId);
|
await this.processBackloggedEvents(pluginId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[BetterSEQTA+] Failed to start plugin ${pluginId}:`, error);
|
console.error(
|
||||||
|
`[BetterSEQTA+] Failed to start plugin ${pluginId}:`,
|
||||||
|
error,
|
||||||
|
);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async startAllPlugins(): Promise<void> {
|
public async startAllPlugins(): Promise<void> {
|
||||||
const startPromises = Array.from(this.plugins.keys()).map(id =>
|
const startPromises = Array.from(this.plugins.keys()).map((id) =>
|
||||||
this.startPlugin(id).catch(error => {
|
this.startPlugin(id).catch((error) => {
|
||||||
console.error(`Failed to start plugin "${id}":`, error);
|
console.error(`Failed to start plugin "${id}":`, error);
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
await Promise.allSettled(startPromises);
|
await Promise.allSettled(startPromises);
|
||||||
@@ -146,11 +162,11 @@ export class PluginManager {
|
|||||||
}
|
}
|
||||||
this.runningPlugins.set(pluginId, false);
|
this.runningPlugins.set(pluginId, false);
|
||||||
console.info(`Plugin "${pluginId}" stopped`);
|
console.info(`Plugin "${pluginId}" stopped`);
|
||||||
this.emit('plugin.stopped', pluginId);
|
this.emit("plugin.stopped", pluginId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public stopAllPlugins(): void {
|
public stopAllPlugins(): void {
|
||||||
Array.from(this.plugins.keys()).forEach(id => this.stopPlugin(id));
|
Array.from(this.plugins.keys()).forEach((id) => this.stopPlugin(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
public getPlugin(pluginId: string): Plugin | undefined {
|
public getPlugin(pluginId: string): Plugin | undefined {
|
||||||
@@ -166,40 +182,49 @@ export class PluginManager {
|
|||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
settings: {
|
settings: {
|
||||||
[key: string]: (Omit<BooleanSetting, 'type'> & { type: 'boolean', id: string }) |
|
[key: string]:
|
||||||
(Omit<StringSetting, 'type'> & { type: 'string', id: string }) |
|
| (Omit<BooleanSetting, "type"> & { type: "boolean"; id: string })
|
||||||
(Omit<NumberSetting, 'type'> & { type: 'number', id: string }) |
|
| (Omit<StringSetting, "type"> & { type: "string"; id: string })
|
||||||
(Omit<SelectSetting<string>, 'type'> & { type: 'select', id: string, options: Array<{ value: string, label: string }> });
|
| (Omit<NumberSetting, "type"> & { type: "number"; id: string })
|
||||||
}
|
| (Omit<SelectSetting<string>, "type"> & {
|
||||||
|
type: "select";
|
||||||
|
id: string;
|
||||||
|
options: Array<{ value: string; label: string }>;
|
||||||
|
});
|
||||||
|
};
|
||||||
}> {
|
}> {
|
||||||
return Array.from(this.plugins.entries()).map(([id, plugin]) => {
|
return Array.from(this.plugins.entries()).map(([id, plugin]) => {
|
||||||
const settingsEntries = Object.entries(plugin.settings).map(([key, setting]) => {
|
const settingsEntries = Object.entries(plugin.settings).map(
|
||||||
const settingObj = setting as any;
|
([key, setting]) => {
|
||||||
// Create a copy of the setting object without any functions
|
const settingObj = setting as any;
|
||||||
const result: any = Object.fromEntries(
|
// Create a copy of the setting object without any functions
|
||||||
Object.entries(settingObj)
|
const result: any = Object.fromEntries(
|
||||||
.filter(([_, value]) => typeof value !== 'function')
|
Object.entries(settingObj).filter(
|
||||||
);
|
([_, value]) => typeof value !== "function",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
// Ensure required properties are present
|
// Ensure required properties are present
|
||||||
result.id = key;
|
result.id = key;
|
||||||
result.title = result.title || key;
|
result.title = result.title || key;
|
||||||
result.description = result.description || '';
|
result.description = result.description || "";
|
||||||
result.defaultEnabled = plugin.defaultEnabled ?? true;
|
result.defaultEnabled = plugin.defaultEnabled ?? true;
|
||||||
|
|
||||||
return [key, result];
|
return [key, result];
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
if (plugin.disableToggle) {
|
if (plugin.disableToggle) {
|
||||||
settingsEntries.push([
|
settingsEntries.push([
|
||||||
'enabled', {
|
"enabled",
|
||||||
id: 'enabled',
|
{
|
||||||
|
id: "enabled",
|
||||||
title: plugin.name,
|
title: plugin.name,
|
||||||
description: plugin.description,
|
description: plugin.description,
|
||||||
type: 'boolean',
|
type: "boolean",
|
||||||
default: plugin.defaultEnabled ?? true
|
default: plugin.defaultEnabled ?? true,
|
||||||
}
|
},
|
||||||
])
|
]);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
pluginId: id,
|
pluginId: id,
|
||||||
@@ -218,7 +243,7 @@ export class PluginManager {
|
|||||||
private emit(event: string, ...args: any[]): void {
|
private emit(event: string, ...args: any[]): void {
|
||||||
const listeners = this.listeners.get(event);
|
const listeners = this.listeners.get(event);
|
||||||
if (listeners) {
|
if (listeners) {
|
||||||
listeners.forEach(listener => listener(...args));
|
listeners.forEach((listener) => listener(...args));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,7 +262,10 @@ export class PluginManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add handler for plugin enable/disable state changes
|
// Add handler for plugin enable/disable state changes
|
||||||
private async handlePluginStateChange(pluginId: string, enabled: boolean): Promise<void> {
|
private async handlePluginStateChange(
|
||||||
|
pluginId: string,
|
||||||
|
enabled: boolean,
|
||||||
|
): Promise<void> {
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
await this.startPlugin(pluginId);
|
await this.startPlugin(pluginId);
|
||||||
} else {
|
} else {
|
||||||
@@ -247,24 +275,30 @@ export class PluginManager {
|
|||||||
|
|
||||||
// Add listener for plugin settings changes
|
// Add listener for plugin settings changes
|
||||||
private setupPluginStateListener(): void {
|
private setupPluginStateListener(): void {
|
||||||
browser.storage.onChanged.addListener((changes: { [key: string]: StorageChange }, area: string) => {
|
browser.storage.onChanged.addListener(
|
||||||
if (area !== 'local') return;
|
(changes: { [key: string]: StorageChange }, area: string) => {
|
||||||
|
if (area !== "local") return;
|
||||||
|
|
||||||
for (const [key, change] of Object.entries(changes)) {
|
for (const [key, change] of Object.entries(changes)) {
|
||||||
const match = key.match(/^plugin\.(.+)\.settings$/);
|
const match = key.match(/^plugin\.(.+)\.settings$/);
|
||||||
if (!match) continue;
|
if (!match) continue;
|
||||||
|
|
||||||
const pluginId = match[1];
|
const pluginId = match[1];
|
||||||
const plugin = this.plugins.get(pluginId);
|
const plugin = this.plugins.get(pluginId);
|
||||||
if (!plugin?.disableToggle) continue;
|
if (!plugin?.disableToggle) continue;
|
||||||
|
|
||||||
const enabled = (change.newValue as PluginSettingsStorage)?.enabled ?? true;
|
const enabled =
|
||||||
const wasEnabled = (change.oldValue as PluginSettingsStorage)?.enabled ?? plugin.defaultEnabled ?? true;
|
(change.newValue as PluginSettingsStorage)?.enabled ?? true;
|
||||||
|
const wasEnabled =
|
||||||
|
(change.oldValue as PluginSettingsStorage)?.enabled ??
|
||||||
|
plugin.defaultEnabled ??
|
||||||
|
true;
|
||||||
|
|
||||||
if (enabled !== wasEnabled) {
|
if (enabled !== wasEnabled) {
|
||||||
this.handlePluginStateChange(pluginId, enabled);
|
this.handlePluginStateChange(pluginId, enabled);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
import type { PluginSettings } from './types';
|
import type { PluginSettings } from "./types";
|
||||||
|
|
||||||
export function Setting(settingDef: any): PropertyDecorator {
|
export function Setting(settingDef: any): PropertyDecorator {
|
||||||
return (target, propertyKey) => {
|
return (target, propertyKey) => {
|
||||||
const proto = target.constructor.prototype;
|
const proto = target.constructor.prototype;
|
||||||
if (!proto.hasOwnProperty('settings')) {
|
if (!proto.hasOwnProperty("settings")) {
|
||||||
Object.defineProperty(proto, 'settings', {
|
Object.defineProperty(proto, "settings", {
|
||||||
value: {},
|
value: {},
|
||||||
writable: true,
|
writable: true,
|
||||||
configurable: true,
|
configurable: true,
|
||||||
enumerable: true
|
enumerable: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ export abstract class BasePlugin<T extends PluginSettings = PluginSettings> {
|
|||||||
// Copy settings from the prototype to the instance
|
// Copy settings from the prototype to the instance
|
||||||
// This ensures that each instance has its own settings object
|
// This ensures that each instance has its own settings object
|
||||||
// IMPORTANT: Ensure the prototype actually HAS settings before copying
|
// IMPORTANT: Ensure the prototype actually HAS settings before copying
|
||||||
if (this.constructor.prototype.hasOwnProperty('settings')) {
|
if (this.constructor.prototype.hasOwnProperty("settings")) {
|
||||||
// Deep clone might be safer if settings objects become complex,
|
// Deep clone might be safer if settings objects become complex,
|
||||||
// but a shallow clone is usually fine for this structure.
|
// but a shallow clone is usually fine for this structure.
|
||||||
this.settings = { ...this.constructor.prototype.settings } as T;
|
this.settings = { ...this.constructor.prototype.settings } as T;
|
||||||
|
|||||||
@@ -1,30 +1,43 @@
|
|||||||
import type { BooleanSetting, NumberSetting, SelectSetting, StringSetting } from './types';
|
import type {
|
||||||
|
BooleanSetting,
|
||||||
|
NumberSetting,
|
||||||
|
SelectSetting,
|
||||||
|
StringSetting,
|
||||||
|
} from "./types";
|
||||||
|
|
||||||
export function numberSetting(options: Omit<NumberSetting, 'type'>): NumberSetting {
|
export function numberSetting(
|
||||||
|
options: Omit<NumberSetting, "type">,
|
||||||
|
): NumberSetting {
|
||||||
return {
|
return {
|
||||||
type: 'number',
|
type: "number",
|
||||||
...options
|
...options,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function booleanSetting(options: Omit<BooleanSetting, 'type'>): BooleanSetting {
|
export function booleanSetting(
|
||||||
|
options: Omit<BooleanSetting, "type">,
|
||||||
|
): BooleanSetting {
|
||||||
return {
|
return {
|
||||||
type: 'boolean',
|
type: "boolean",
|
||||||
...options
|
...options,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function stringSetting(options: Omit<StringSetting, 'type'>): StringSetting {
|
export function stringSetting(
|
||||||
|
options: Omit<StringSetting, "type">,
|
||||||
|
): StringSetting {
|
||||||
return {
|
return {
|
||||||
type: 'string',
|
type: "string",
|
||||||
...options
|
...options,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function selectSetting<T extends string>(options: Omit<SelectSetting<T>, 'type'>): SelectSetting<T> {
|
export function selectSetting<T extends string>(
|
||||||
|
options: Omit<SelectSetting<T>, "type">,
|
||||||
|
): SelectSetting<T> {
|
||||||
return {
|
return {
|
||||||
type: 'select',
|
type: "select",
|
||||||
...options
|
...options,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,16 +48,15 @@ export function defineSettings<T extends Record<string, any>>(settings: T): T {
|
|||||||
export function Setting(settingDef: any): PropertyDecorator {
|
export function Setting(settingDef: any): PropertyDecorator {
|
||||||
return (target, propertyKey) => {
|
return (target, propertyKey) => {
|
||||||
const proto = target.constructor.prototype;
|
const proto = target.constructor.prototype;
|
||||||
if (!proto.hasOwnProperty('settings')) {
|
if (!proto.hasOwnProperty("settings")) {
|
||||||
Object.defineProperty(proto, 'settings', {
|
Object.defineProperty(proto, "settings", {
|
||||||
value: {},
|
value: {},
|
||||||
writable: true,
|
writable: true,
|
||||||
configurable: true,
|
configurable: true,
|
||||||
enumerable: true
|
enumerable: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
proto.settings[propertyKey] = settingDef;
|
proto.settings[propertyKey] = settingDef;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
+51
-24
@@ -1,14 +1,14 @@
|
|||||||
import ReactFiber from '@/seqta/utils/ReactFiber';
|
import ReactFiber from "@/seqta/utils/ReactFiber";
|
||||||
|
|
||||||
export interface BooleanSetting {
|
export interface BooleanSetting {
|
||||||
type: 'boolean';
|
type: "boolean";
|
||||||
default: boolean;
|
default: boolean;
|
||||||
title: string;
|
title: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StringSetting {
|
export interface StringSetting {
|
||||||
type: 'string';
|
type: "string";
|
||||||
default: string;
|
default: string;
|
||||||
title: string;
|
title: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
@@ -17,7 +17,7 @@ export interface StringSetting {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface NumberSetting {
|
export interface NumberSetting {
|
||||||
type: 'number';
|
type: "number";
|
||||||
default: number;
|
default: number;
|
||||||
title: string;
|
title: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
@@ -27,46 +27,68 @@ export interface NumberSetting {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface SelectSetting<T extends string> {
|
export interface SelectSetting<T extends string> {
|
||||||
type: 'select';
|
type: "select";
|
||||||
options: readonly T[];
|
options: readonly T[];
|
||||||
default: T;
|
default: T;
|
||||||
title: string;
|
title: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PluginSetting = BooleanSetting | StringSetting | NumberSetting | SelectSetting<string>;
|
export type PluginSetting =
|
||||||
|
| BooleanSetting
|
||||||
|
| StringSetting
|
||||||
|
| NumberSetting
|
||||||
|
| SelectSetting<string>;
|
||||||
|
|
||||||
export type PluginSettings = {
|
export type PluginSettings = {
|
||||||
[key: string]: PluginSetting;
|
[key: string]: PluginSetting;
|
||||||
}
|
};
|
||||||
|
|
||||||
// Helper type to extract the actual value type from a setting
|
// Helper type to extract the actual value type from a setting
|
||||||
export type SettingValue<T extends PluginSetting> = T extends BooleanSetting ? boolean :
|
export type SettingValue<T extends PluginSetting> = T extends BooleanSetting
|
||||||
T extends StringSetting ? string :
|
? boolean
|
||||||
T extends NumberSetting ? number :
|
: T extends StringSetting
|
||||||
T extends SelectSetting<infer O> ? O :
|
? string
|
||||||
never;
|
: T extends NumberSetting
|
||||||
|
? number
|
||||||
|
: T extends SelectSetting<infer O>
|
||||||
|
? O
|
||||||
|
: never;
|
||||||
|
|
||||||
export type SettingsAPI<T extends PluginSettings> = {
|
export type SettingsAPI<T extends PluginSettings> = {
|
||||||
[K in keyof T]: SettingValue<T[K]>;
|
[K in keyof T]: SettingValue<T[K]>;
|
||||||
} & {
|
} & {
|
||||||
onChange: <K extends keyof T>(key: K, callback: (value: SettingValue<T[K]>) => void) => { unregister: () => void };
|
onChange: <K extends keyof T>(
|
||||||
offChange: <K extends keyof T>(key: K, callback: (value: SettingValue<T[K]>) => void) => void;
|
key: K,
|
||||||
|
callback: (value: SettingValue<T[K]>) => void,
|
||||||
|
) => { unregister: () => void };
|
||||||
|
offChange: <K extends keyof T>(
|
||||||
|
key: K,
|
||||||
|
callback: (value: SettingValue<T[K]>) => void,
|
||||||
|
) => void;
|
||||||
loaded: Promise<void>;
|
loaded: Promise<void>;
|
||||||
}
|
};
|
||||||
|
|
||||||
export interface SEQTAAPI {
|
export interface SEQTAAPI {
|
||||||
onMount: (selector: string, callback: (element: Element) => void) => { unregister: () => void };
|
onMount: (
|
||||||
|
selector: string,
|
||||||
|
callback: (element: Element) => void,
|
||||||
|
) => { unregister: () => void };
|
||||||
getFiber: (selector: string) => ReactFiber;
|
getFiber: (selector: string) => ReactFiber;
|
||||||
getCurrentPage: () => string;
|
getCurrentPage: () => string;
|
||||||
onPageChange: (callback: (page: string) => void) => { unregister: () => void };
|
onPageChange: (callback: (page: string) => void) => {
|
||||||
|
unregister: () => void;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StorageAPI<T = any> {
|
export interface StorageAPI<T = any> {
|
||||||
/**
|
/**
|
||||||
* Register a callback to be called when a storage value changes
|
* Register a callback to be called when a storage value changes
|
||||||
*/
|
*/
|
||||||
onChange: <K extends keyof T>(key: K, callback: (value: T[K]) => void) => { unregister: () => void };
|
onChange: <K extends keyof T>(
|
||||||
|
key: K,
|
||||||
|
callback: (value: T[K]) => void,
|
||||||
|
) => { unregister: () => void };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Promise that resolves when storage values are loaded
|
* Promise that resolves when storage values are loaded
|
||||||
@@ -76,10 +98,13 @@ export interface StorageAPI<T = any> {
|
|||||||
|
|
||||||
export type TypedStorageAPI<T> = StorageAPI<T> & {
|
export type TypedStorageAPI<T> = StorageAPI<T> & {
|
||||||
[K in keyof T]: T[K];
|
[K in keyof T]: T[K];
|
||||||
}
|
};
|
||||||
|
|
||||||
export interface EventsAPI {
|
export interface EventsAPI {
|
||||||
on: (event: string, callback: (...args: any[]) => void) => { unregister: () => void };
|
on: (
|
||||||
|
event: string,
|
||||||
|
callback: (...args: any[]) => void,
|
||||||
|
) => { unregister: () => void };
|
||||||
emit: (event: string, ...args: any[]) => void;
|
emit: (event: string, ...args: any[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,8 +121,10 @@ export interface Plugin<T extends PluginSettings = PluginSettings, S = any> {
|
|||||||
description: string;
|
description: string;
|
||||||
version: string;
|
version: string;
|
||||||
settings: T;
|
settings: T;
|
||||||
styles?: string; // Optional CSS styles for the plugin
|
styles?: string; // Optional CSS styles for the plugin
|
||||||
disableToggle?: boolean; // Optional flag to show/hide the plugin's enable/disable toggle in settings
|
disableToggle?: boolean; // Optional flag to show/hide the plugin's enable/disable toggle in settings
|
||||||
defaultEnabled?: boolean; // Optional flag to set the plugin's default enabled state
|
defaultEnabled?: boolean; // Optional flag to set the plugin's default enabled state
|
||||||
run: (api: PluginAPI<T, S>) => void | Promise<void> | (() => void) | Promise<(() => void)>;
|
run: (
|
||||||
|
api: PluginAPI<T, S>,
|
||||||
|
) => void | Promise<void> | (() => void) | Promise<() => void>;
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
import { PluginManager } from './core/manager';
|
import { PluginManager } from "./core/manager";
|
||||||
|
|
||||||
// plugins
|
// plugins
|
||||||
import timetablePlugin from './built-in/timetable';
|
import timetablePlugin from "./built-in/timetable";
|
||||||
import notificationCollectorPlugin from './built-in/notificationCollector';
|
import notificationCollectorPlugin from "./built-in/notificationCollector";
|
||||||
import themesPlugin from './built-in/themes';
|
import themesPlugin from "./built-in/themes";
|
||||||
import animatedBackgroundPlugin from './built-in/animatedBackground';
|
import animatedBackgroundPlugin from "./built-in/animatedBackground";
|
||||||
import assessmentsAveragePlugin from './built-in/assessmentsAverage';
|
import assessmentsAveragePlugin from "./built-in/assessmentsAverage";
|
||||||
import globalSearchPlugin from './built-in/globalSearch/src/core';
|
import globalSearchPlugin from "./built-in/globalSearch/src/core";
|
||||||
//import testPlugin from './built-in/test';
|
//import testPlugin from './built-in/test';
|
||||||
|
|
||||||
// Initialize plugin manager
|
// Initialize plugin manager
|
||||||
@@ -21,7 +21,7 @@ pluginManager.registerPlugin(timetablePlugin);
|
|||||||
pluginManager.registerPlugin(globalSearchPlugin);
|
pluginManager.registerPlugin(globalSearchPlugin);
|
||||||
//pluginManager.registerPlugin(testPlugin);
|
//pluginManager.registerPlugin(testPlugin);
|
||||||
|
|
||||||
export { init as Monofile } from './monofile';
|
export { init as Monofile } from "./monofile";
|
||||||
|
|
||||||
export async function initializePlugins(): Promise<void> {
|
export async function initializePlugins(): Promise<void> {
|
||||||
await pluginManager.startAllPlugins();
|
await pluginManager.startAllPlugins();
|
||||||
|
|||||||
+242
-239
@@ -1,135 +1,134 @@
|
|||||||
// Third-party libraries
|
// Third-party libraries
|
||||||
import browser from "webextension-polyfill"
|
import browser from "webextension-polyfill";
|
||||||
import { animate, stagger } from "motion"
|
import { animate, stagger } from "motion";
|
||||||
|
|
||||||
// Internal utilities and functions
|
// Internal utilities and functions
|
||||||
import { ChangeMenuItemPositions, MenuOptionsOpen } from "@/seqta/utils/Openers/OpenMenuOptions"
|
|
||||||
import { GetThresholdOfColor } from "@/seqta/ui/colors/getThresholdColour"
|
|
||||||
import { waitForElm } from "@/seqta/utils/waitForElm"
|
|
||||||
import { delay } from "@/seqta/utils/delay"
|
|
||||||
import stringToHTML from "@/seqta/utils/stringToHTML"
|
|
||||||
import { MessageHandler } from "@/seqta/utils/listeners/MessageListener"
|
|
||||||
import {
|
import {
|
||||||
settingsState,
|
ChangeMenuItemPositions,
|
||||||
} from "@/seqta/utils/listeners/SettingsState"
|
MenuOptionsOpen,
|
||||||
import { StorageChangeHandler } from "@/seqta/utils/listeners/StorageChanges"
|
} from "@/seqta/utils/Openers/OpenMenuOptions";
|
||||||
import { eventManager } from "@/seqta/utils/listeners/EventManager"
|
import { GetThresholdOfColor } from "@/seqta/ui/colors/getThresholdColour";
|
||||||
|
import { waitForElm } from "@/seqta/utils/waitForElm";
|
||||||
|
import { delay } from "@/seqta/utils/delay";
|
||||||
|
import stringToHTML from "@/seqta/utils/stringToHTML";
|
||||||
|
import { MessageHandler } from "@/seqta/utils/listeners/MessageListener";
|
||||||
|
import { settingsState } from "@/seqta/utils/listeners/SettingsState";
|
||||||
|
import { StorageChangeHandler } from "@/seqta/utils/listeners/StorageChanges";
|
||||||
|
import { eventManager } from "@/seqta/utils/listeners/EventManager";
|
||||||
|
|
||||||
// UI and theme management
|
// UI and theme management
|
||||||
import RegisterClickListeners from "@/seqta/utils/listeners/ClickListeners"
|
import RegisterClickListeners from "@/seqta/utils/listeners/ClickListeners";
|
||||||
import { AddBetterSEQTAElements } from "@/seqta/ui/AddBetterSEQTAElements"
|
import { AddBetterSEQTAElements } from "@/seqta/ui/AddBetterSEQTAElements";
|
||||||
import { updateAllColors } from "@/seqta/ui/colors/Manager"
|
import { updateAllColors } from "@/seqta/ui/colors/Manager";
|
||||||
import loading from "@/seqta/ui/Loading"
|
import loading from "@/seqta/ui/Loading";
|
||||||
import { SendNewsPage } from "@/seqta/utils/SendNewsPage"
|
import { SendNewsPage } from "@/seqta/utils/SendNewsPage";
|
||||||
import { loadHomePage } from "@/seqta/utils/Loaders/LoadHomePage"
|
import { loadHomePage } from "@/seqta/utils/Loaders/LoadHomePage";
|
||||||
import { OpenWhatsNewPopup } from "@/seqta/utils/Whatsnew"
|
import { OpenWhatsNewPopup } from "@/seqta/utils/Whatsnew";
|
||||||
|
|
||||||
// JSON content
|
// JSON content
|
||||||
import MenuitemSVGKey from "@/seqta/content/MenuItemSVGKey.json"
|
import MenuitemSVGKey from "@/seqta/content/MenuItemSVGKey.json";
|
||||||
|
|
||||||
// Icons and fonts
|
// Icons and fonts
|
||||||
import IconFamily from "@/resources/fonts/IconFamily.woff"
|
import IconFamily from "@/resources/fonts/IconFamily.woff";
|
||||||
|
|
||||||
// Stylesheets
|
// Stylesheets
|
||||||
import iframeCSS from "@/css/iframe.scss?raw"
|
import iframeCSS from "@/css/iframe.scss?raw";
|
||||||
|
|
||||||
function SetDisplayNone(ElementName: string) {
|
function SetDisplayNone(ElementName: string) {
|
||||||
return `li[data-key=${ElementName}]{display:var(--menuHidden) !important; transition: 1s;}`
|
return `li[data-key=${ElementName}]{display:var(--menuHidden) !important; transition: 1s;}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function HideMenuItems(): Promise<void> {
|
async function HideMenuItems(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
let stylesheetInnerText: string = ""
|
let stylesheetInnerText: string = "";
|
||||||
for (const [menuItem, { toggle }] of Object.entries(
|
for (const [menuItem, { toggle }] of Object.entries(
|
||||||
settingsState.menuitems,
|
settingsState.menuitems,
|
||||||
)) {
|
)) {
|
||||||
if (!toggle) {
|
if (!toggle) {
|
||||||
stylesheetInnerText += SetDisplayNone(menuItem)
|
stylesheetInnerText += SetDisplayNone(menuItem);
|
||||||
console.info(`[BetterSEQTA+] Hiding ${menuItem} menu item`)
|
console.info(`[BetterSEQTA+] Hiding ${menuItem} menu item`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const menuItemStyle: HTMLStyleElement = document.createElement("style")
|
const menuItemStyle: HTMLStyleElement = document.createElement("style");
|
||||||
menuItemStyle.innerText = stylesheetInnerText
|
menuItemStyle.innerText = stylesheetInnerText;
|
||||||
document.head.appendChild(menuItemStyle)
|
document.head.appendChild(menuItemStyle);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[BetterSEQTA+] An error occurred:", error)
|
console.error("[BetterSEQTA+] An error occurred:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hideSideBar() {
|
export function hideSideBar() {
|
||||||
const sidebar = document.getElementById("menu") // The sidebar element to be closed
|
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 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 currentMenuWidth = window.getComputedStyle(sidebar!).width; // Get the styles of the different elements
|
||||||
const currentContentPosition = window.getComputedStyle(main!).position
|
const currentContentPosition = window.getComputedStyle(main!).position;
|
||||||
|
|
||||||
if (currentMenuWidth != "0") {
|
if (currentMenuWidth != "0") {
|
||||||
// Actually modify it to collapse the sidebar
|
// Actually modify it to collapse the sidebar
|
||||||
sidebar!.style.width = "0"
|
sidebar!.style.width = "0";
|
||||||
} else {
|
} else {
|
||||||
sidebar!.style.width = "100%"
|
sidebar!.style.width = "100%";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentContentPosition != "relative") {
|
if (currentContentPosition != "relative") {
|
||||||
main!.style.position = "relative"
|
main!.style.position = "relative";
|
||||||
} else {
|
} else {
|
||||||
main!.style.position = "absolute"
|
main!.style.position = "absolute";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export async function finishLoad() {
|
export async function finishLoad() {
|
||||||
try {
|
try {
|
||||||
document.querySelector(".legacy-root")?.classList.remove("hidden")
|
document.querySelector(".legacy-root")?.classList.remove("hidden");
|
||||||
|
|
||||||
const loadingbk = document.getElementById("loading")
|
const loadingbk = document.getElementById("loading");
|
||||||
loadingbk?.classList.add("closeLoading")
|
loadingbk?.classList.add("closeLoading");
|
||||||
await delay(501)
|
await delay(501);
|
||||||
loadingbk?.remove()
|
loadingbk?.remove();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error during loading cleanup:", err)
|
console.error("Error during loading cleanup:", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settingsState.justupdated && !document.getElementById("whatsnewbk")) {
|
if (settingsState.justupdated && !document.getElementById("whatsnewbk")) {
|
||||||
OpenWhatsNewPopup()
|
OpenWhatsNewPopup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function GetCSSElement(file: string) {
|
export function GetCSSElement(file: string) {
|
||||||
const cssFile = browser.runtime.getURL(file)
|
const cssFile = browser.runtime.getURL(file);
|
||||||
const fileref = document.createElement("link")
|
const fileref = document.createElement("link");
|
||||||
fileref.setAttribute("rel", "stylesheet")
|
fileref.setAttribute("rel", "stylesheet");
|
||||||
fileref.setAttribute("type", "text/css")
|
fileref.setAttribute("type", "text/css");
|
||||||
fileref.setAttribute("href", cssFile)
|
fileref.setAttribute("href", cssFile);
|
||||||
|
|
||||||
return fileref
|
return fileref;
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeThemeTagsFromNotices() {
|
function removeThemeTagsFromNotices() {
|
||||||
// Grabs an array of the notice iFrames
|
// Grabs an array of the notice iFrames
|
||||||
const userHTMLArray = document.getElementsByClassName("userHTML")
|
const userHTMLArray = document.getElementsByClassName("userHTML");
|
||||||
// Iterates through the array, applying the iFrame css
|
// Iterates through the array, applying the iFrame css
|
||||||
for (const item of userHTMLArray) {
|
for (const item of userHTMLArray) {
|
||||||
// Grabs the HTML of the body tag
|
// Grabs the HTML of the body tag
|
||||||
const item1 = item as HTMLIFrameElement
|
const item1 = item as HTMLIFrameElement;
|
||||||
const body = item1.contentWindow!.document.querySelectorAll("body")[0]
|
const body = item1.contentWindow!.document.querySelectorAll("body")[0];
|
||||||
if (body) {
|
if (body) {
|
||||||
// Replaces the theme tag with nothing
|
// Replaces the theme tag with nothing
|
||||||
const bodyText = body.innerHTML
|
const bodyText = body.innerHTML;
|
||||||
body.innerHTML = bodyText
|
body.innerHTML = bodyText
|
||||||
.replace(/\[\[[\w]+[:][\w]+[\]\]]+/g, "")
|
.replace(/\[\[[\w]+[:][\w]+[\]\]]+/g, "")
|
||||||
.replace(/ +/, " ")
|
.replace(/ +/, " ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateIframesWithDarkMode(): Promise<void> {
|
async function updateIframesWithDarkMode(): Promise<void> {
|
||||||
const cssLink = document.createElement("style")
|
const cssLink = document.createElement("style");
|
||||||
cssLink.classList.add("iframecss")
|
cssLink.classList.add("iframecss");
|
||||||
const cssContent = document.createTextNode(iframeCSS)
|
const cssContent = document.createTextNode(iframeCSS);
|
||||||
cssLink.appendChild(cssContent)
|
cssLink.appendChild(cssContent);
|
||||||
|
|
||||||
eventManager.register(
|
eventManager.register(
|
||||||
"iframeAdded",
|
"iframeAdded",
|
||||||
@@ -139,63 +138,63 @@ async function updateIframesWithDarkMode(): Promise<void> {
|
|||||||
!element.classList.contains("iframecss"),
|
!element.classList.contains("iframecss"),
|
||||||
},
|
},
|
||||||
(element) => {
|
(element) => {
|
||||||
const iframe = element as HTMLIFrameElement
|
const iframe = element as HTMLIFrameElement;
|
||||||
try {
|
try {
|
||||||
applyDarkModeToIframe(iframe, cssLink)
|
applyDarkModeToIframe(iframe, cssLink);
|
||||||
|
|
||||||
if (element.classList.contains("cke_wysiwyg_frame")) {
|
if (element.classList.contains("cke_wysiwyg_frame")) {
|
||||||
(async () => {
|
(async () => {
|
||||||
await delay(100)
|
await delay(100);
|
||||||
iframe.contentDocument?.body.setAttribute("spellcheck", "true")
|
iframe.contentDocument?.body.setAttribute("spellcheck", "true");
|
||||||
})()
|
})();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error applying dark mode:", error)
|
console.error("Error applying dark mode:", error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyDarkModeToIframe(
|
function applyDarkModeToIframe(
|
||||||
iframe: HTMLIFrameElement,
|
iframe: HTMLIFrameElement,
|
||||||
cssLink: HTMLStyleElement,
|
cssLink: HTMLStyleElement,
|
||||||
): void {
|
): void {
|
||||||
const iframeDocument = iframe.contentDocument
|
const iframeDocument = iframe.contentDocument;
|
||||||
if (!iframeDocument) return
|
if (!iframeDocument) return;
|
||||||
|
|
||||||
iframe.onload = () => {
|
iframe.onload = () => {
|
||||||
applyDarkModeToIframe(iframe, cssLink)
|
applyDarkModeToIframe(iframe, cssLink);
|
||||||
}
|
};
|
||||||
|
|
||||||
if (settingsState.DarkMode) {
|
if (settingsState.DarkMode) {
|
||||||
iframeDocument.documentElement.classList.add("dark")
|
iframeDocument.documentElement.classList.add("dark");
|
||||||
}
|
}
|
||||||
|
|
||||||
const head = iframeDocument.head
|
const head = iframeDocument.head;
|
||||||
if (head && !head.innerHTML.includes("iframecss")) {
|
if (head && !head.innerHTML.includes("iframecss")) {
|
||||||
head.innerHTML += cssLink.outerHTML
|
head.innerHTML += cssLink.outerHTML;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function SortMessagePageItems(messagesParentElement: any) {
|
function SortMessagePageItems(messagesParentElement: any) {
|
||||||
try {
|
try {
|
||||||
let filterbutton = document.createElement("div")
|
let filterbutton = document.createElement("div");
|
||||||
filterbutton.classList.add("messages-filterbutton")
|
filterbutton.classList.add("messages-filterbutton");
|
||||||
filterbutton.innerText = "Filter"
|
filterbutton.innerText = "Filter";
|
||||||
|
|
||||||
let header = document.querySelector(
|
let header = document.querySelector(
|
||||||
"[class*='MessageList__MessageList___']",
|
"[class*='MessageList__MessageList___']",
|
||||||
) as HTMLElement
|
) as HTMLElement;
|
||||||
header.append(filterbutton)
|
header.append(filterbutton);
|
||||||
messagesParentElement
|
messagesParentElement;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error sorting message page items:", error)
|
console.error("Error sorting message page items:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function LoadPageElements(): Promise<void> {
|
async function LoadPageElements(): Promise<void> {
|
||||||
await AddBetterSEQTAElements()
|
await AddBetterSEQTAElements();
|
||||||
const sublink: string | undefined = window.location.href.split("/")[4]
|
const sublink: string | undefined = window.location.href.split("/")[4];
|
||||||
|
|
||||||
eventManager.register(
|
eventManager.register(
|
||||||
"messagesAdded",
|
"messagesAdded",
|
||||||
@@ -204,7 +203,7 @@ async function LoadPageElements(): Promise<void> {
|
|||||||
className: "messages",
|
className: "messages",
|
||||||
},
|
},
|
||||||
handleMessages,
|
handleMessages,
|
||||||
)
|
);
|
||||||
|
|
||||||
eventManager.register(
|
eventManager.register(
|
||||||
"noticesAdded",
|
"noticesAdded",
|
||||||
@@ -213,7 +212,7 @@ async function LoadPageElements(): Promise<void> {
|
|||||||
className: "notices",
|
className: "notices",
|
||||||
},
|
},
|
||||||
CheckNoticeTextColour,
|
CheckNoticeTextColour,
|
||||||
)
|
);
|
||||||
|
|
||||||
eventManager.register(
|
eventManager.register(
|
||||||
"dashboardAdded",
|
"dashboardAdded",
|
||||||
@@ -222,7 +221,7 @@ async function LoadPageElements(): Promise<void> {
|
|||||||
className: "dashboard",
|
className: "dashboard",
|
||||||
},
|
},
|
||||||
handleDashboard,
|
handleDashboard,
|
||||||
)
|
);
|
||||||
|
|
||||||
eventManager.register(
|
eventManager.register(
|
||||||
"documentsAdded",
|
"documentsAdded",
|
||||||
@@ -231,7 +230,7 @@ async function LoadPageElements(): Promise<void> {
|
|||||||
className: "documents",
|
className: "documents",
|
||||||
},
|
},
|
||||||
handleDocuments,
|
handleDocuments,
|
||||||
)
|
);
|
||||||
|
|
||||||
eventManager.register(
|
eventManager.register(
|
||||||
"reportsAdded",
|
"reportsAdded",
|
||||||
@@ -240,7 +239,7 @@ async function LoadPageElements(): Promise<void> {
|
|||||||
className: "reports",
|
className: "reports",
|
||||||
},
|
},
|
||||||
handleReports,
|
handleReports,
|
||||||
)
|
);
|
||||||
|
|
||||||
/* eventManager.register(
|
/* eventManager.register(
|
||||||
"timetableAdded",
|
"timetableAdded",
|
||||||
@@ -258,21 +257,21 @@ async function LoadPageElements(): Promise<void> {
|
|||||||
className: "notice",
|
className: "notice",
|
||||||
},
|
},
|
||||||
handleNotices,
|
handleNotices,
|
||||||
)
|
);
|
||||||
|
|
||||||
RegisterClickListeners()
|
RegisterClickListeners();
|
||||||
|
|
||||||
await handleSublink(sublink)
|
await handleSublink(sublink);
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
node.style.opacity = "0"
|
node.style.opacity = "0";
|
||||||
|
|
||||||
// get index of node in relation to parent
|
// get index of node in relation to parent
|
||||||
const index = Array.from(node.parentElement!.children).indexOf(node)
|
const index = Array.from(node.parentElement!.children).indexOf(node);
|
||||||
|
|
||||||
animate(
|
animate(
|
||||||
node,
|
node,
|
||||||
@@ -283,71 +282,73 @@ async function handleNotices(node: Element): Promise<void> {
|
|||||||
stiffness: 250,
|
stiffness: 250,
|
||||||
damping: 20,
|
damping: 20,
|
||||||
},
|
},
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleSublink(sublink: string | undefined): Promise<void> {
|
async function handleSublink(sublink: string | undefined): Promise<void> {
|
||||||
switch (sublink) {
|
switch (sublink) {
|
||||||
case "news":
|
case "news":
|
||||||
await handleNewsPage()
|
await handleNewsPage();
|
||||||
break
|
break;
|
||||||
case undefined:
|
case undefined:
|
||||||
window.location.replace(`${location.origin}/#?page=/${settingsState.defaultPage}`)
|
window.location.replace(
|
||||||
if (settingsState.defaultPage === "home") loadHomePage()
|
`${location.origin}/#?page=/${settingsState.defaultPage}`,
|
||||||
|
);
|
||||||
|
if (settingsState.defaultPage === "home") loadHomePage();
|
||||||
if (settingsState.defaultPage === "documents")
|
if (settingsState.defaultPage === "documents")
|
||||||
handleDocuments(document.querySelector(".documents")!)
|
handleDocuments(document.querySelector(".documents")!);
|
||||||
if (settingsState.defaultPage === "reports")
|
if (settingsState.defaultPage === "reports")
|
||||||
handleReports(document.querySelector(".reports")!)
|
handleReports(document.querySelector(".reports")!);
|
||||||
if (settingsState.defaultPage === "messages")
|
if (settingsState.defaultPage === "messages")
|
||||||
handleMessages(document.querySelector(".messages")!)
|
handleMessages(document.querySelector(".messages")!);
|
||||||
|
|
||||||
finishLoad()
|
finishLoad();
|
||||||
break
|
break;
|
||||||
case "home":
|
case "home":
|
||||||
window.location.replace(`${location.origin}/#?page=/home`)
|
window.location.replace(`${location.origin}/#?page=/home`);
|
||||||
console.info("[BetterSEQTA+] Started Init")
|
console.info("[BetterSEQTA+] Started Init");
|
||||||
if (settingsState.onoff) loadHomePage()
|
if (settingsState.onoff) loadHomePage();
|
||||||
finishLoad()
|
finishLoad();
|
||||||
break
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
await handleDefault()
|
await handleDefault();
|
||||||
break
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleNewsPage(): Promise<void> {
|
async function handleNewsPage(): Promise<void> {
|
||||||
console.info("[BetterSEQTA+] Started Init")
|
console.info("[BetterSEQTA+] Started Init");
|
||||||
if (settingsState.onoff) {
|
if (settingsState.onoff) {
|
||||||
SendNewsPage()
|
SendNewsPage();
|
||||||
finishLoad()
|
finishLoad();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleDefault(): Promise<void> {
|
async function handleDefault(): Promise<void> {
|
||||||
finishLoad()
|
finishLoad();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleMessages(node: Element): Promise<void> {
|
async function handleMessages(node: Element): Promise<void> {
|
||||||
if (!(node instanceof HTMLElement)) return
|
if (!(node instanceof HTMLElement)) return;
|
||||||
|
|
||||||
const element = document.getElementById("title")!.firstChild as HTMLElement
|
const element = document.getElementById("title")!.firstChild as HTMLElement;
|
||||||
element.innerText = "Direct Messages"
|
element.innerText = "Direct Messages";
|
||||||
document.title = "Direct Messages ― SEQTA Learn"
|
document.title = "Direct Messages ― SEQTA Learn";
|
||||||
SortMessagePageItems(node)
|
SortMessagePageItems(node);
|
||||||
|
|
||||||
if (!settingsState.animations) return
|
if (!settingsState.animations) return;
|
||||||
|
|
||||||
// Hides messages on page load
|
// Hides messages on page load
|
||||||
const style = document.createElement("style")
|
const style = document.createElement("style");
|
||||||
style.classList.add("messageHider")
|
style.classList.add("messageHider");
|
||||||
style.innerHTML = "[data-message]{opacity: 0 !important;}"
|
style.innerHTML = "[data-message]{opacity: 0 !important;}";
|
||||||
document.head.append(style)
|
document.head.append(style);
|
||||||
|
|
||||||
await waitForElm("[data-message]", true, 10)
|
await waitForElm("[data-message]", true, 10);
|
||||||
const messages = Array.from(
|
const messages = Array.from(
|
||||||
document.querySelectorAll("[data-message]"),
|
document.querySelectorAll("[data-message]"),
|
||||||
).slice(0, 35)
|
).slice(0, 35);
|
||||||
animate(
|
animate(
|
||||||
messages,
|
messages,
|
||||||
{ opacity: [0, 1], y: [10, 0] },
|
{ opacity: [0, 1], y: [10, 0] },
|
||||||
@@ -356,21 +357,21 @@ async function handleMessages(node: Element): Promise<void> {
|
|||||||
duration: 0.5,
|
duration: 0.5,
|
||||||
ease: [0.22, 0.03, 0.26, 1],
|
ease: [0.22, 0.03, 0.26, 1],
|
||||||
},
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
document.head.querySelector("style.messageHider")?.remove()
|
document.head.querySelector("style.messageHider")?.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleDashboard(node: Element): Promise<void> {
|
async function handleDashboard(node: Element): Promise<void> {
|
||||||
if (!(node instanceof HTMLElement)) return
|
if (!(node instanceof HTMLElement)) return;
|
||||||
if (!settingsState.animations) return
|
if (!settingsState.animations) return;
|
||||||
|
|
||||||
const style = document.createElement("style")
|
const style = document.createElement("style");
|
||||||
style.classList.add("dashboardHider")
|
style.classList.add("dashboardHider");
|
||||||
style.innerHTML = ".dashboard{opacity: 0 !important;}"
|
style.innerHTML = ".dashboard{opacity: 0 !important;}";
|
||||||
document.head.append(style)
|
document.head.append(style);
|
||||||
|
|
||||||
await waitForElm(".dashlet", true, 10)
|
await waitForElm(".dashlet", true, 10);
|
||||||
animate(
|
animate(
|
||||||
".dashboard > *",
|
".dashboard > *",
|
||||||
{ opacity: [0, 1], y: [10, 0] },
|
{ opacity: [0, 1], y: [10, 0] },
|
||||||
@@ -379,16 +380,16 @@ async function handleDashboard(node: Element): Promise<void> {
|
|||||||
duration: 0.5,
|
duration: 0.5,
|
||||||
ease: [0.22, 0.03, 0.26, 1],
|
ease: [0.22, 0.03, 0.26, 1],
|
||||||
},
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
document.head.querySelector("style.dashboardHider")?.remove()
|
document.head.querySelector("style.dashboardHider")?.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleDocuments(node: Element): Promise<void> {
|
async function handleDocuments(node: Element): Promise<void> {
|
||||||
if (!(node instanceof HTMLElement)) return
|
if (!(node instanceof HTMLElement)) return;
|
||||||
if (!settingsState.animations) return
|
if (!settingsState.animations) return;
|
||||||
|
|
||||||
await waitForElm(".document", true, 10)
|
await waitForElm(".document", true, 10);
|
||||||
animate(
|
animate(
|
||||||
".documents tbody tr.document",
|
".documents tbody tr.document",
|
||||||
{ opacity: [0, 1], y: [10, 0] },
|
{ opacity: [0, 1], y: [10, 0] },
|
||||||
@@ -397,14 +398,14 @@ async function handleDocuments(node: Element): Promise<void> {
|
|||||||
duration: 0.5,
|
duration: 0.5,
|
||||||
ease: [0.22, 0.03, 0.26, 1],
|
ease: [0.22, 0.03, 0.26, 1],
|
||||||
},
|
},
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleReports(node: Element): Promise<void> {
|
async function handleReports(node: Element): Promise<void> {
|
||||||
if (!(node instanceof HTMLElement)) return
|
if (!(node instanceof HTMLElement)) return;
|
||||||
if (!settingsState.animations) return
|
if (!settingsState.animations) return;
|
||||||
|
|
||||||
await waitForElm(".report", true, 10)
|
await waitForElm(".report", true, 10);
|
||||||
animate(
|
animate(
|
||||||
".reports .item",
|
".reports .item",
|
||||||
{ opacity: [0, 1], y: [10, 0] },
|
{ opacity: [0, 1], y: [10, 0] },
|
||||||
@@ -413,7 +414,7 @@ async function handleReports(node: Element): Promise<void> {
|
|||||||
duration: 0.5,
|
duration: 0.5,
|
||||||
ease: [0.22, 0.03, 0.26, 1],
|
ease: [0.22, 0.03, 0.26, 1],
|
||||||
},
|
},
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CheckNoticeTextColour(notice: any) {
|
function CheckNoticeTextColour(notice: any) {
|
||||||
@@ -425,60 +426,60 @@ function CheckNoticeTextColour(notice: any) {
|
|||||||
parentElement: notice,
|
parentElement: notice,
|
||||||
},
|
},
|
||||||
(node) => {
|
(node) => {
|
||||||
var hex = (node as HTMLElement).style.cssText.split(" ")[1]
|
var hex = (node as HTMLElement).style.cssText.split(" ")[1];
|
||||||
if (hex) {
|
if (hex) {
|
||||||
const hex1 = hex.slice(0, -1)
|
const hex1 = hex.slice(0, -1);
|
||||||
var threshold = GetThresholdOfColor(hex1)
|
var threshold = GetThresholdOfColor(hex1);
|
||||||
if (settingsState.DarkMode && threshold < 100) {
|
if (settingsState.DarkMode && threshold < 100) {
|
||||||
(node as HTMLElement).style.cssText = "--color: undefined;"
|
(node as HTMLElement).style.cssText = "--color: undefined;";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function tryLoad() {
|
export function tryLoad() {
|
||||||
waitForElm(".login").then(() => {
|
waitForElm(".login").then(() => {
|
||||||
finishLoad()
|
finishLoad();
|
||||||
})
|
});
|
||||||
|
|
||||||
waitForElm(".day-container").then(() => {
|
waitForElm(".day-container").then(() => {
|
||||||
finishLoad()
|
finishLoad();
|
||||||
})
|
});
|
||||||
|
|
||||||
waitForElm("[data-key=welcome]").then((elm: any) => {
|
waitForElm("[data-key=welcome]").then((elm: any) => {
|
||||||
elm.classList.remove("active")
|
elm.classList.remove("active");
|
||||||
})
|
});
|
||||||
|
|
||||||
waitForElm(".code", true, 50).then((elm: any) => {
|
waitForElm(".code", true, 50).then((elm: any) => {
|
||||||
if (!elm.innerText.includes("BetterSEQTA")) LoadPageElements()
|
if (!elm.innerText.includes("BetterSEQTA")) LoadPageElements();
|
||||||
})
|
});
|
||||||
|
|
||||||
updateIframesWithDarkMode()
|
updateIframesWithDarkMode();
|
||||||
// Waits for page to call on load, run scripts
|
// Waits for page to call on load, run scripts
|
||||||
document.addEventListener(
|
document.addEventListener(
|
||||||
"load",
|
"load",
|
||||||
function () {
|
function () {
|
||||||
removeThemeTagsFromNotices()
|
removeThemeTagsFromNotices();
|
||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ReplaceMenuSVG(element: HTMLElement, svg: string) {
|
function ReplaceMenuSVG(element: HTMLElement, svg: string) {
|
||||||
let item = element.firstChild as HTMLElement
|
let item = element.firstChild as HTMLElement;
|
||||||
item!.firstChild!.remove()
|
item!.firstChild!.remove();
|
||||||
|
|
||||||
item.innerHTML = `<span>${item.innerHTML}</span>`
|
item.innerHTML = `<span>${item.innerHTML}</span>`;
|
||||||
|
|
||||||
let newsvg = stringToHTML(svg).firstChild
|
let newsvg = stringToHTML(svg).firstChild;
|
||||||
item.insertBefore(newsvg as Node, item.firstChild)
|
item.insertBefore(newsvg as Node, item.firstChild);
|
||||||
}
|
}
|
||||||
|
|
||||||
const processedSymbol = Symbol('processed')
|
const processedSymbol = Symbol("processed");
|
||||||
|
|
||||||
export async function ObserveMenuItemPosition() {
|
export async function ObserveMenuItemPosition() {
|
||||||
await waitForElm("#menu > ul > li")
|
await waitForElm("#menu > ul > li");
|
||||||
|
|
||||||
eventManager.register(
|
eventManager.register(
|
||||||
"menuList",
|
"menuList",
|
||||||
@@ -486,71 +487,73 @@ export async function ObserveMenuItemPosition() {
|
|||||||
parentElement: document.querySelector("#menu")!.firstChild as Element,
|
parentElement: document.querySelector("#menu")!.firstChild as Element,
|
||||||
},
|
},
|
||||||
(element: Element) => {
|
(element: Element) => {
|
||||||
const node = element as HTMLElement
|
const node = element as HTMLElement;
|
||||||
|
|
||||||
// Only process top-level menu items and skip everything else
|
// Only process top-level menu items and skip everything else
|
||||||
if (!node.classList.contains('item') ||
|
if (
|
||||||
node.nodeName !== 'LI' ||
|
!node.classList.contains("item") ||
|
||||||
node.parentElement?.parentElement?.id !== 'menu') {
|
node.nodeName !== "LI" ||
|
||||||
return
|
node.parentElement?.parentElement?.id !== "menu"
|
||||||
|
) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Early exit if already processed
|
// Early exit if already processed
|
||||||
if ((element as any)[processedSymbol]) {
|
if ((element as any)[processedSymbol]) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!node?.dataset?.checked && !MenuOptionsOpen) {
|
if (!node?.dataset?.checked && !MenuOptionsOpen) {
|
||||||
const key =
|
const key =
|
||||||
MenuitemSVGKey[node?.dataset?.key! as keyof typeof MenuitemSVGKey]
|
MenuitemSVGKey[node?.dataset?.key! as keyof typeof MenuitemSVGKey];
|
||||||
if (key) {
|
if (key) {
|
||||||
ReplaceMenuSVG(
|
ReplaceMenuSVG(
|
||||||
node,
|
node,
|
||||||
MenuitemSVGKey[node.dataset.key as keyof typeof MenuitemSVGKey],
|
MenuitemSVGKey[node.dataset.key as keyof typeof MenuitemSVGKey],
|
||||||
)
|
);
|
||||||
} else if (node?.firstChild?.nodeName === "LABEL") {
|
} else if (node?.firstChild?.nodeName === "LABEL") {
|
||||||
const label = node.firstChild as HTMLElement
|
const label = node.firstChild as HTMLElement;
|
||||||
let textNode = label.lastChild as HTMLElement
|
let textNode = label.lastChild as HTMLElement;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
textNode.nodeType === 3 &&
|
textNode.nodeType === 3 &&
|
||||||
textNode.parentNode &&
|
textNode.parentNode &&
|
||||||
textNode.parentNode.nodeName !== "SPAN"
|
textNode.parentNode.nodeName !== "SPAN"
|
||||||
) {
|
) {
|
||||||
const span = document.createElement("span")
|
const span = document.createElement("span");
|
||||||
span.textContent = textNode.nodeValue
|
span.textContent = textNode.nodeValue;
|
||||||
|
|
||||||
label.replaceChild(span, textNode)
|
label.replaceChild(span, textNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ChangeMenuItemPositions(settingsState.menuorder);
|
ChangeMenuItemPositions(settingsState.menuorder);
|
||||||
|
|
||||||
(element as any)[processedSymbol] = true
|
(element as any)[processedSymbol] = true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function showConflictPopup() {
|
export function showConflictPopup() {
|
||||||
if (document.getElementById("conflict-popup")) return
|
if (document.getElementById("conflict-popup")) return;
|
||||||
document.body.classList.remove("hidden")
|
document.body.classList.remove("hidden");
|
||||||
|
|
||||||
const background = document.createElement("div")
|
const background = document.createElement("div");
|
||||||
background.id = "conflict-popup"
|
background.id = "conflict-popup";
|
||||||
background.classList.add("whatsnewBackground")
|
background.classList.add("whatsnewBackground");
|
||||||
background.style.zIndex = "10000000"
|
background.style.zIndex = "10000000";
|
||||||
|
|
||||||
const container = document.createElement("div")
|
const container = document.createElement("div");
|
||||||
container.classList.add("whatsnewContainer")
|
container.classList.add("whatsnewContainer");
|
||||||
container.style.height = "auto"
|
container.style.height = "auto";
|
||||||
|
|
||||||
const headerHTML = /* html */ `
|
const headerHTML = /* html */ `
|
||||||
<div class="whatsnewHeader">
|
<div class="whatsnewHeader">
|
||||||
<h1>Extension Conflict Detected</h1>
|
<h1>Extension Conflict Detected</h1>
|
||||||
<p>Legacy BetterSEQTA Installed</p>
|
<p>Legacy BetterSEQTA Installed</p>
|
||||||
</div>
|
</div>
|
||||||
`
|
`;
|
||||||
const header = stringToHTML(headerHTML).firstChild
|
const header = stringToHTML(headerHTML).firstChild;
|
||||||
|
|
||||||
const textHTML = /* html */ `
|
const textHTML = /* html */ `
|
||||||
<div class="whatsnewTextContainer" style="overflow-y: auto; font-size: 1.3rem;">
|
<div class="whatsnewTextContainer" style="overflow-y: auto; font-size: 1.3rem;">
|
||||||
@@ -562,91 +565,91 @@ export function showConflictPopup() {
|
|||||||
Please remove the older BetterSEQTA extension to ensure that BetterSEQTA+ works correctly.
|
Please remove the older BetterSEQTA extension to ensure that BetterSEQTA+ works correctly.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
`
|
`;
|
||||||
const text = stringToHTML(textHTML).firstChild
|
const text = stringToHTML(textHTML).firstChild;
|
||||||
|
|
||||||
const exitButton = document.createElement("div")
|
const exitButton = document.createElement("div");
|
||||||
exitButton.id = "whatsnewclosebutton"
|
exitButton.id = "whatsnewclosebutton";
|
||||||
|
|
||||||
if (header) container.append(header)
|
if (header) container.append(header);
|
||||||
if (text) container.append(text)
|
if (text) container.append(text);
|
||||||
container.append(exitButton)
|
container.append(exitButton);
|
||||||
|
|
||||||
background.append(container)
|
background.append(container);
|
||||||
|
|
||||||
document.getElementById("container")?.append(background)
|
document.getElementById("container")?.append(background);
|
||||||
|
|
||||||
if (settingsState.animations) {
|
if (settingsState.animations) {
|
||||||
animate([background as HTMLElement], { opacity: [0, 1] })
|
animate([background as HTMLElement], { opacity: [0, 1] });
|
||||||
}
|
}
|
||||||
|
|
||||||
background.addEventListener("click", (event) => {
|
background.addEventListener("click", (event) => {
|
||||||
if (event.target === background) {
|
if (event.target === background) {
|
||||||
background.remove()
|
background.remove();
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
exitButton.addEventListener("click", () => {
|
exitButton.addEventListener("click", () => {
|
||||||
background.remove()
|
background.remove();
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function init() {
|
export function init() {
|
||||||
const handleDisabled = () => {
|
const handleDisabled = () => {
|
||||||
waitForElm(".code", true, 50).then(AppendElementsToDisabledPage)
|
waitForElm(".code", true, 50).then(AppendElementsToDisabledPage);
|
||||||
}
|
};
|
||||||
|
|
||||||
if (settingsState.onoff) {
|
if (settingsState.onoff) {
|
||||||
console.info("[BetterSEQTA+] Enabled")
|
console.info("[BetterSEQTA+] Enabled");
|
||||||
if (settingsState.DarkMode) document.documentElement.classList.add("dark");
|
if (settingsState.DarkMode) document.documentElement.classList.add("dark");
|
||||||
|
|
||||||
document.querySelector(".legacy-root")?.classList.add("hidden")
|
document.querySelector(".legacy-root")?.classList.add("hidden");
|
||||||
ObserveMenuItemPosition();
|
ObserveMenuItemPosition();
|
||||||
|
|
||||||
new StorageChangeHandler()
|
new StorageChangeHandler();
|
||||||
new MessageHandler()
|
new MessageHandler();
|
||||||
|
|
||||||
updateAllColors()
|
updateAllColors();
|
||||||
loading()
|
loading();
|
||||||
InjectCustomIcons()
|
InjectCustomIcons();
|
||||||
HideMenuItems()
|
HideMenuItems();
|
||||||
tryLoad()
|
tryLoad();
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const legacyElement = document.querySelector(
|
const legacyElement = document.querySelector(
|
||||||
".outside-container .bottom-container",
|
".outside-container .bottom-container",
|
||||||
)
|
);
|
||||||
if (legacyElement) {
|
if (legacyElement) {
|
||||||
console.log("Legacy extension detected")
|
console.log("Legacy extension detected");
|
||||||
showConflictPopup()
|
showConflictPopup();
|
||||||
}
|
}
|
||||||
}, 1000)
|
}, 1000);
|
||||||
} else {
|
} else {
|
||||||
handleDisabled()
|
handleDisabled();
|
||||||
window.addEventListener("load", handleDisabled)
|
window.addEventListener("load", handleDisabled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function InjectCustomIcons() {
|
function InjectCustomIcons() {
|
||||||
console.info("[BetterSEQTA+] Injecting Icons")
|
console.info("[BetterSEQTA+] Injecting Icons");
|
||||||
|
|
||||||
const style = document.createElement("style")
|
const style = document.createElement("style");
|
||||||
style.setAttribute("type", "text/css")
|
style.setAttribute("type", "text/css");
|
||||||
style.innerHTML = `
|
style.innerHTML = `
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'IconFamily';
|
font-family: 'IconFamily';
|
||||||
src: url('${browser.runtime.getURL(IconFamily)}') format('woff');
|
src: url('${browser.runtime.getURL(IconFamily)}') format('woff');
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}`
|
}`;
|
||||||
document.head.appendChild(style)
|
document.head.appendChild(style);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AppendElementsToDisabledPage() {
|
export function AppendElementsToDisabledPage() {
|
||||||
console.info("[BetterSEQTA+] Appending elements to disabled page")
|
console.info("[BetterSEQTA+] Appending elements to disabled page");
|
||||||
AddBetterSEQTAElements()
|
AddBetterSEQTAElements();
|
||||||
|
|
||||||
let settingsStyle = document.createElement("style")
|
let settingsStyle = document.createElement("style");
|
||||||
settingsStyle.innerHTML = /* css */ `
|
settingsStyle.innerHTML = /* css */ `
|
||||||
.addedButton {
|
.addedButton {
|
||||||
position: absolute !important;
|
position: absolute !important;
|
||||||
@@ -671,6 +674,6 @@ export function AppendElementsToDisabledPage() {
|
|||||||
box-shadow: 0px 0px 20px -2px rgba(0, 0, 0, 0.6);
|
box-shadow: 0px 0px 20px -2px rgba(0, 0, 0, 0.6);
|
||||||
transform-origin: 70% 0;
|
transform-origin: 70% 0;
|
||||||
}
|
}
|
||||||
`
|
`;
|
||||||
document.head.append(settingsStyle)
|
document.head.append(settingsStyle);
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,5 @@ module.exports = {
|
|||||||
plugins: {
|
plugins: {
|
||||||
tailwindcss: {},
|
tailwindcss: {},
|
||||||
autoprefixer: {},
|
autoprefixer: {},
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'IconFamily';
|
font-family: "IconFamily";
|
||||||
src: local('IconFamily') local('Icon Family') url('/fonts/IconFamily.woff') format('woff');
|
src: local("IconFamily") local("Icon Family") url("/fonts/IconFamily.woff")
|
||||||
|
format("woff");
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
+23499
-16443
File diff suppressed because it is too large
Load Diff
@@ -12,22 +12,25 @@ h1 {
|
|||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
small {
|
small {
|
||||||
font-size: .66666667em;
|
font-size: 0.66666667em;
|
||||||
}
|
}
|
||||||
a {
|
a {
|
||||||
color: #e74c3c;
|
color: #e74c3c;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
a:hover, a:focus {
|
a:hover,
|
||||||
|
a:focus {
|
||||||
box-shadow: 0 1px #e74c3c;
|
box-shadow: 0 1px #e74c3c;
|
||||||
}
|
}
|
||||||
.bshadow0, input {
|
.bshadow0,
|
||||||
|
input {
|
||||||
box-shadow: inset 0 -2px #e7e7e7;
|
box-shadow: inset 0 -2px #e7e7e7;
|
||||||
}
|
}
|
||||||
input:hover {
|
input:hover {
|
||||||
box-shadow: inset 0 -2px #ccc;
|
box-shadow: inset 0 -2px #ccc;
|
||||||
}
|
}
|
||||||
input, fieldset {
|
input,
|
||||||
|
fieldset {
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -38,7 +41,7 @@ input {
|
|||||||
color: inherit;
|
color: inherit;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
height: 1.5em;
|
height: 1.5em;
|
||||||
padding: .25em 0;
|
padding: 0.25em 0;
|
||||||
}
|
}
|
||||||
input:focus {
|
input:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
@@ -77,19 +80,22 @@ p {
|
|||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
.mvm {
|
.mvm {
|
||||||
margin-top: .75em;
|
margin-top: 0.75em;
|
||||||
margin-bottom: .75em;
|
margin-bottom: 0.75em;
|
||||||
}
|
}
|
||||||
.mtn {
|
.mtn {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
.mtl, .mal {
|
.mtl,
|
||||||
|
.mal {
|
||||||
margin-top: 1.5em;
|
margin-top: 1.5em;
|
||||||
}
|
}
|
||||||
.mbl, .mal {
|
.mbl,
|
||||||
|
.mal {
|
||||||
margin-bottom: 1.5em;
|
margin-bottom: 1.5em;
|
||||||
}
|
}
|
||||||
.mal, .mhl {
|
.mal,
|
||||||
|
.mhl {
|
||||||
margin-left: 1.5em;
|
margin-left: 1.5em;
|
||||||
margin-right: 1.5em;
|
margin-right: 1.5em;
|
||||||
}
|
}
|
||||||
@@ -98,16 +104,18 @@ p {
|
|||||||
margin-right: 1em;
|
margin-right: 1em;
|
||||||
}
|
}
|
||||||
.mls {
|
.mls {
|
||||||
margin-left: .25em;
|
margin-left: 0.25em;
|
||||||
}
|
}
|
||||||
.ptl {
|
.ptl {
|
||||||
padding-top: 1.5em;
|
padding-top: 1.5em;
|
||||||
}
|
}
|
||||||
.pbs, .pvs {
|
.pbs,
|
||||||
padding-bottom: .25em;
|
.pvs {
|
||||||
|
padding-bottom: 0.25em;
|
||||||
}
|
}
|
||||||
.pvs, .pts {
|
.pvs,
|
||||||
padding-top: .25em;
|
.pts {
|
||||||
|
padding-top: 0.25em;
|
||||||
}
|
}
|
||||||
.unit {
|
.unit {
|
||||||
float: left;
|
float: left;
|
||||||
@@ -121,7 +129,8 @@ p {
|
|||||||
.size1of1 {
|
.size1of1 {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
.clearfix:before, .clearfix:after {
|
.clearfix:before,
|
||||||
|
.clearfix:after {
|
||||||
content: " ";
|
content: " ";
|
||||||
display: table;
|
display: table;
|
||||||
}
|
}
|
||||||
@@ -134,7 +143,7 @@ p {
|
|||||||
.textbox0 {
|
.textbox0 {
|
||||||
width: 3em;
|
width: 3em;
|
||||||
background: #f1f1f1;
|
background: #f1f1f1;
|
||||||
padding: .25em .5em;
|
padding: 0.25em 0.5em;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
height: 1.5em;
|
height: 1.5em;
|
||||||
}
|
}
|
||||||
@@ -149,4 +158,3 @@ p {
|
|||||||
.fs1 {
|
.fs1 {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,30 +1,32 @@
|
|||||||
if (!('boxShadow' in document.body.style)) {
|
if (!("boxShadow" in document.body.style)) {
|
||||||
document.body.setAttribute('class', 'noBoxShadow');
|
document.body.setAttribute("class", "noBoxShadow");
|
||||||
}
|
}
|
||||||
|
|
||||||
document.body.addEventListener("click", function(e) {
|
document.body.addEventListener("click", function (e) {
|
||||||
var target = e.target;
|
var target = e.target;
|
||||||
if (target.tagName === "INPUT" &&
|
if (
|
||||||
target.getAttribute('class').indexOf('liga') === -1) {
|
target.tagName === "INPUT" &&
|
||||||
target.select();
|
target.getAttribute("class").indexOf("liga") === -1
|
||||||
}
|
) {
|
||||||
|
target.select();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
(function() {
|
(function () {
|
||||||
var fontSize = document.getElementById('fontSize'),
|
var fontSize = document.getElementById("fontSize"),
|
||||||
testDrive = document.getElementById('testDrive'),
|
testDrive = document.getElementById("testDrive"),
|
||||||
testText = document.getElementById('testText');
|
testText = document.getElementById("testText");
|
||||||
function updateTest() {
|
function updateTest() {
|
||||||
testDrive.innerHTML = testText.value || String.fromCharCode(160);
|
testDrive.innerHTML = testText.value || String.fromCharCode(160);
|
||||||
if (window.icomoonLiga) {
|
if (window.icomoonLiga) {
|
||||||
window.icomoonLiga(testDrive);
|
window.icomoonLiga(testDrive);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
function updateSize() {
|
}
|
||||||
testDrive.style.fontSize = fontSize.value + 'px';
|
function updateSize() {
|
||||||
}
|
testDrive.style.fontSize = fontSize.value + "px";
|
||||||
fontSize.addEventListener('change', updateSize, false);
|
}
|
||||||
testText.addEventListener('input', updateTest, false);
|
fontSize.addEventListener("change", updateSize, false);
|
||||||
testText.addEventListener('change', updateTest, false);
|
testText.addEventListener("input", updateTest, false);
|
||||||
updateSize();
|
testText.addEventListener("change", updateTest, false);
|
||||||
}());
|
updateSize();
|
||||||
|
})();
|
||||||
|
|||||||
@@ -1,21 +1,23 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'icomoon';
|
font-family: "icomoon";
|
||||||
src: url('fonts/icomoon.eot?biv4go');
|
src: url("fonts/icomoon.eot?biv4go");
|
||||||
src: url('fonts/icomoon.eot?biv4go#iefix') format('embedded-opentype'),
|
src:
|
||||||
url('IconFamily.woff') format('woff'),
|
url("fonts/icomoon.eot?biv4go#iefix") format("embedded-opentype"),
|
||||||
url('fonts/icomoon.svg?biv4go#icomoon') format('svg');
|
url("IconFamily.woff") format("woff"),
|
||||||
|
url("fonts/icomoon.svg?biv4go#icomoon") format("svg");
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: block;
|
font-display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
input.unitRight {
|
input.unitRight {
|
||||||
font-family: 'icomoon';
|
font-family: "icomoon";
|
||||||
}
|
}
|
||||||
|
|
||||||
[class^="icon-"], [class*=" icon-"] {
|
[class^="icon-"],
|
||||||
|
[class*=" icon-"] {
|
||||||
/* use !important to prevent issues with browser extensions that change fonts */
|
/* use !important to prevent issues with browser extensions that change fonts */
|
||||||
font-family: 'icomoon' !important;
|
font-family: "icomoon" !important;
|
||||||
speak: never;
|
speak: never;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export default /* html */`
|
export default /* html */ `
|
||||||
<svg style="width:24px;height:24px;border-radius:0;" viewBox="0 0 24 24">
|
<svg style="width:24px;height:24px;border-radius:0;" viewBox="0 0 24 24">
|
||||||
<path fill="currentColor" d="M6 20H13V22H6C4.89 22 4 21.11 4 20V4C4 2.9 4.89 2 6 2H18C19.11 2 20 2.9 20 4V12.54L18.5 11.72L18 12V4H13V12L10.5 9.75L8 12V4H6V20M24 17L18.5 14L13 17L18.5 20L24 17M15 19.09V21.09L18.5 23L22 21.09V19.09L18.5 21L15 19.09Z"></path>
|
<path fill="currentColor" d="M6 20H13V22H6C4.89 22 4 21.11 4 20V4C4 2.9 4.89 2 6 2H18C19.11 2 20 2.9 20 4V12.54L18.5 11.72L18 12V4H13V12L10.5 9.75L8 12V4H6V20M24 17L18.5 14L13 17L18.5 20L24 17M15 19.09V21.09L18.5 23L22 21.09V19.09L18.5 21L15 19.09Z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export default /* html */`
|
export default /* html */ `
|
||||||
<svg style="width:24px;height:24px;border-radius:0;" viewBox="0 0 24 24">
|
<svg style="width:24px;height:24px;border-radius:0;" viewBox="0 0 24 24">
|
||||||
<path fill="currentColor" d="M19 1L14 6V17L19 12.5V1M21 5V18.5C19.9 18.15 18.7 18 17.5 18C15.8 18 13.35 18.65 12 19.5V6C10.55 4.9 8.45 4.5 6.5 4.5C4.55 4.5 2.45 4.9 1 6V20.65C1 20.9 1.25 21.15 1.5 21.15C1.6 21.15 1.65 21.1 1.75 21.1C3.1 20.45 5.05 20 6.5 20C8.45 20 10.55 20.4 12 21.5C13.35 20.65 15.8 20 17.5 20C19.15 20 20.85 20.3 22.25 21.05C22.35 21.1 22.4 21.1 22.5 21.1C22.75 21.1 23 20.85 23 20.6V6C22.4 5.55 21.75 5.25 21 5M10 18.41C8.75 18.09 7.5 18 6.5 18C5.44 18 4.18 18.19 3 18.5V7.13C3.91 6.73 5.14 6.5 6.5 6.5C7.86 6.5 9.09 6.73 10 7.13V18.41Z"></path>
|
<path fill="currentColor" d="M19 1L14 6V17L19 12.5V1M21 5V18.5C19.9 18.15 18.7 18 17.5 18C15.8 18 13.35 18.65 12 19.5V6C10.55 4.9 8.45 4.5 6.5 4.5C4.55 4.5 2.45 4.9 1 6V20.65C1 20.9 1.25 21.15 1.5 21.15C1.6 21.15 1.65 21.1 1.75 21.1C3.1 20.45 5.05 20 6.5 20C8.45 20 10.55 20.4 12 21.5C13.35 20.65 15.8 20 17.5 20C19.15 20 20.85 20.3 22.25 21.05C22.35 21.1 22.4 21.1 22.5 21.1C22.75 21.1 23 20.85 23 20.6V6C22.4 5.55 21.75 5.25 21 5M10 18.41C8.75 18.09 7.5 18 6.5 18C5.44 18 4.18 18.19 3 18.5V7.13C3.91 6.73 5.14 6.5 6.5 6.5C7.86 6.5 9.09 6.73 10 7.13V18.41Z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
+16
-18
@@ -1,42 +1,40 @@
|
|||||||
// Third-party libraries
|
// Third-party libraries
|
||||||
import browser from "webextension-polyfill"
|
import browser from "webextension-polyfill";
|
||||||
|
|
||||||
// Internal utilities and functions
|
// Internal utilities and functions
|
||||||
import {
|
import { settingsState } from "@/seqta/utils/listeners/SettingsState";
|
||||||
settingsState,
|
|
||||||
} from "@/seqta/utils/listeners/SettingsState"
|
|
||||||
|
|
||||||
// UI and theme management
|
// UI and theme management
|
||||||
import pageState from "@/pageState.js?url"
|
import pageState from "@/pageState.js?url";
|
||||||
|
|
||||||
// Stylesheets
|
// Stylesheets
|
||||||
import injectedCSS from "@/css/injected.scss?inline"
|
import injectedCSS from "@/css/injected.scss?inline";
|
||||||
|
|
||||||
export async function main() {
|
export async function main() {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
if (settingsState.onoff) {
|
if (settingsState.onoff) {
|
||||||
injectPageState()
|
injectPageState();
|
||||||
|
|
||||||
// TEMP FIX for bug! -> this is a hack to get the injected.css file to have HMR in development mode as this import system is currently broken with crxjs
|
// TEMP FIX for bug! -> this is a hack to get the injected.css file to have HMR in development mode as this import system is currently broken with crxjs
|
||||||
if (import.meta.env.MODE === "development") {
|
if (import.meta.env.MODE === "development") {
|
||||||
import("../css/injected.scss")
|
import("../css/injected.scss");
|
||||||
} else {
|
} else {
|
||||||
const injectedStyle = document.createElement("style")
|
const injectedStyle = document.createElement("style");
|
||||||
injectedStyle.textContent = injectedCSS
|
injectedStyle.textContent = injectedCSS;
|
||||||
document.head.appendChild(injectedStyle)
|
document.head.appendChild(injectedStyle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resolve(true)
|
resolve(true);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(error)
|
console.error(error);
|
||||||
reject(error)
|
reject(error);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function injectPageState() {
|
function injectPageState() {
|
||||||
const mainScript = document.createElement("script")
|
const mainScript = document.createElement("script");
|
||||||
mainScript.src = browser.runtime.getURL(pageState)
|
mainScript.src = browser.runtime.getURL(pageState);
|
||||||
document.head.appendChild(mainScript)
|
document.head.appendChild(mainScript);
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,6 @@ import { loadHomePage } from "@/seqta/utils/Loaders/LoadHomePage";
|
|||||||
import { SendNewsPage } from "@/seqta/utils/SendNewsPage";
|
import { SendNewsPage } from "@/seqta/utils/SendNewsPage";
|
||||||
import { setupSettingsButton } from "@/seqta/utils/setupSettingsButton";
|
import { setupSettingsButton } from "@/seqta/utils/setupSettingsButton";
|
||||||
|
|
||||||
|
|
||||||
import { GetThresholdOfColor } from "@/seqta/ui/colors/getThresholdColour";
|
import { GetThresholdOfColor } from "@/seqta/ui/colors/getThresholdColour";
|
||||||
import { appendBackgroundToUI } from "./ImageBackgrounds";
|
import { appendBackgroundToUI } from "./ImageBackgrounds";
|
||||||
import stringToHTML from "@/seqta/utils/stringToHTML";
|
import stringToHTML from "@/seqta/utils/stringToHTML";
|
||||||
@@ -18,12 +17,12 @@ async function getUserInfo() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${location.origin}/seqta/student/login`, {
|
const response = await fetch(`${location.origin}/seqta/student/login`, {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json; charset=utf-8',
|
"Content-Type": "application/json; charset=utf-8",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
mode: 'normal',
|
mode: "normal",
|
||||||
query: null,
|
query: null,
|
||||||
redirect_url: location.origin,
|
redirect_url: location.origin,
|
||||||
}),
|
}),
|
||||||
@@ -33,7 +32,7 @@ async function getUserInfo() {
|
|||||||
cachedUserInfo = responseData.payload;
|
cachedUserInfo = responseData.payload;
|
||||||
return cachedUserInfo;
|
return cachedUserInfo;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching user info:', error);
|
console.error("Error fetching user info:", error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -41,11 +40,11 @@ async function getUserInfo() {
|
|||||||
export async function AddBetterSEQTAElements() {
|
export async function AddBetterSEQTAElements() {
|
||||||
if (settingsState.onoff) {
|
if (settingsState.onoff) {
|
||||||
if (settingsState.DarkMode) {
|
if (settingsState.DarkMode) {
|
||||||
document.documentElement.classList.add('dark');
|
document.documentElement.classList.add("dark");
|
||||||
}
|
}
|
||||||
|
|
||||||
const fragment = document.createDocumentFragment();
|
const fragment = document.createDocumentFragment();
|
||||||
const menu = document.getElementById('menu')!;
|
const menu = document.getElementById("menu")!;
|
||||||
const menuList = menu.firstChild as HTMLElement;
|
const menuList = menu.firstChild as HTMLElement;
|
||||||
|
|
||||||
createHomeButton(fragment, menuList);
|
createHomeButton(fragment, menuList);
|
||||||
@@ -57,10 +56,10 @@ export async function AddBetterSEQTAElements() {
|
|||||||
await Promise.all([
|
await Promise.all([
|
||||||
appendBackgroundToUI(),
|
appendBackgroundToUI(),
|
||||||
handleUserInfo(),
|
handleUserInfo(),
|
||||||
handleStudentData()
|
handleStudentData(),
|
||||||
]);
|
]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error initializing UI elements:', error);
|
console.error("Error initializing UI elements:", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
setupEventListeners();
|
setupEventListeners();
|
||||||
@@ -74,12 +73,14 @@ export async function AddBetterSEQTAElements() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createHomeButton(fragment: DocumentFragment, menuList: HTMLElement) {
|
function createHomeButton(fragment: DocumentFragment, menuList: HTMLElement) {
|
||||||
const container = document.getElementById('content')!;
|
const container = document.getElementById("content")!;
|
||||||
const div = document.createElement('div');
|
const div = document.createElement("div");
|
||||||
div.classList.add('titlebar');
|
div.classList.add("titlebar");
|
||||||
container.append(div);
|
container.append(div);
|
||||||
|
|
||||||
const NewButton = stringToHTML('<li class="item" data-key="home" id="homebutton" data-path="/home" data-betterseqta="true"><label><svg style="width:24px;height:24px" viewBox="0 0 24 24"><path fill="currentColor" d="M10,20V14H14V20H19V12H22L12,3L2,12H5V20H10Z" /></svg><span>Home</span></label></li>');
|
const NewButton = stringToHTML(
|
||||||
|
'<li class="item" data-key="home" id="homebutton" data-path="/home" data-betterseqta="true"><label><svg style="width:24px;height:24px" viewBox="0 0 24 24"><path fill="currentColor" d="M10,20V14H14V20H19V12H22L12,3L2,12H5V20H10Z" /></svg><span>Home</span></label></li>',
|
||||||
|
);
|
||||||
if (NewButton.firstChild) {
|
if (NewButton.firstChild) {
|
||||||
fragment.appendChild(NewButton.firstChild);
|
fragment.appendChild(NewButton.firstChild);
|
||||||
}
|
}
|
||||||
@@ -90,7 +91,7 @@ async function handleUserInfo() {
|
|||||||
const info = await getUserInfo();
|
const info = await getUserInfo();
|
||||||
updateUserInfo(info);
|
updateUserInfo(info);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching and processing student data:', error);
|
console.error("Error fetching and processing student data:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,9 +113,9 @@ function updateUserInfo(info: {
|
|||||||
userDesc: string | null;
|
userDesc: string | null;
|
||||||
userName: string | null;
|
userName: string | null;
|
||||||
}) {
|
}) {
|
||||||
const titlebar = document.getElementsByClassName('titlebar')[0];
|
const titlebar = document.getElementsByClassName("titlebar")[0];
|
||||||
|
|
||||||
const userInfo = stringToHTML(/* html */`
|
const userInfo = stringToHTML(/* html */ `
|
||||||
<div class="userInfosvgdiv tooltip">
|
<div class="userInfosvgdiv tooltip">
|
||||||
<svg class="userInfosvg" viewBox="0 0 24 24"><path fill="var(--text-primary)" d="M12,19.2C9.5,19.2 7.29,17.92 6,16C6.03,14 10,12.9 12,12.9C14,12.9 17.97,14 18,16C16.71,17.92 14.5,19.2 12,19.2M12,5A3,3 0 0,1 15,8A3,3 0 0,1 12,11A3,3 0 0,1 9,8A3,3 0 0,1 12,5M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,6.47 17.5,2 12,2Z"></path></svg>
|
<svg class="userInfosvg" viewBox="0 0 24 24"><path fill="var(--text-primary)" d="M12,19.2C9.5,19.2 7.29,17.92 6,16C6.03,14 10,12.9 12,12.9C14,12.9 17.97,14 18,16C16.71,17.92 14.5,19.2 12,19.2M12,5A3,3 0 0,1 15,8A3,3 0 0,1 12,11A3,3 0 0,1 9,8A3,3 0 0,1 12,5M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,6.47 17.5,2 12,2Z"></path></svg>
|
||||||
<div class="tooltiptext topmenutooltip" id="logouttooltip"></div>
|
<div class="tooltiptext topmenutooltip" id="logouttooltip"></div>
|
||||||
@@ -122,7 +123,7 @@ function updateUserInfo(info: {
|
|||||||
`).firstChild;
|
`).firstChild;
|
||||||
titlebar.append(userInfo!);
|
titlebar.append(userInfo!);
|
||||||
|
|
||||||
const userinfo = stringToHTML(/* html */`
|
const userinfo = stringToHTML(/* html */ `
|
||||||
<div class="userInfo">
|
<div class="userInfo">
|
||||||
<div class="userInfoText">
|
<div class="userInfoText">
|
||||||
<div style="display: flex; align-items: center;">
|
<div style="display: flex; align-items: center;">
|
||||||
@@ -135,26 +136,29 @@ function updateUserInfo(info: {
|
|||||||
`).firstChild;
|
`).firstChild;
|
||||||
titlebar.append(userinfo!);
|
titlebar.append(userinfo!);
|
||||||
|
|
||||||
var logoutbutton = document.getElementsByClassName('logout')[0];
|
var logoutbutton = document.getElementsByClassName("logout")[0];
|
||||||
var userInfosvgdiv = document.getElementById('logouttooltip')!;
|
var userInfosvgdiv = document.getElementById("logouttooltip")!;
|
||||||
userInfosvgdiv.appendChild(logoutbutton);
|
userInfosvgdiv.appendChild(logoutbutton);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleStudentData() {
|
async function handleStudentData() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${location.origin}/seqta/student/load/message/people`, {
|
const response = await fetch(
|
||||||
method: 'POST',
|
`${location.origin}/seqta/student/load/message/people`,
|
||||||
headers: {
|
{
|
||||||
'Content-Type': 'application/json; charset=utf-8',
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json; charset=utf-8",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ mode: "student" }),
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ mode: 'student' }),
|
);
|
||||||
});
|
|
||||||
|
|
||||||
const responseData = await response.json();
|
const responseData = await response.json();
|
||||||
let students = responseData.payload;
|
let students = responseData.payload;
|
||||||
await updateStudentInfo(students);
|
await updateStudentInfo(students);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching and processing student data:', error);
|
console.error("Error fetching and processing student data:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,12 +166,12 @@ async function updateStudentInfo(students: any) {
|
|||||||
const info = await getUserInfo();
|
const info = await getUserInfo();
|
||||||
var index = students.findIndex(function (person: any) {
|
var index = students.findIndex(function (person: any) {
|
||||||
return (
|
return (
|
||||||
person.firstname == info.userDesc.split(' ')[0] &&
|
person.firstname == info.userDesc.split(" ")[0] &&
|
||||||
person.surname == info.userDesc.split(' ')[1]
|
person.surname == info.userDesc.split(" ")[1]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
let houseelement1 = document.getElementsByClassName('userInfohouse')[0];
|
let houseelement1 = document.getElementsByClassName("userInfohouse")[0];
|
||||||
const houseelement = houseelement1 as HTMLElement;
|
const houseelement = houseelement1 as HTMLElement;
|
||||||
|
|
||||||
if (students[index]?.house) {
|
if (students[index]?.house) {
|
||||||
@@ -175,7 +179,8 @@ async function updateStudentInfo(students: any) {
|
|||||||
houseelement.style.background = students[index].house_colour;
|
houseelement.style.background = students[index].house_colour;
|
||||||
try {
|
try {
|
||||||
let colorresult = GetThresholdOfColor(students[index]?.house_colour);
|
let colorresult = GetThresholdOfColor(students[index]?.house_colour);
|
||||||
houseelement.style.color = colorresult && colorresult > 300 ? 'black' : 'white';
|
houseelement.style.color =
|
||||||
|
colorresult && colorresult > 300 ? "black" : "white";
|
||||||
houseelement.innerText = students[index].year + students[index].house;
|
houseelement.innerText = students[index].year + students[index].house;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
houseelement.innerText = students[index].house;
|
houseelement.innerText = students[index].house;
|
||||||
@@ -184,120 +189,133 @@ async function updateStudentInfo(students: any) {
|
|||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
houseelement.innerText = students[index].year;
|
houseelement.innerText = students[index].year;
|
||||||
} catch(err) {
|
} catch (err) {
|
||||||
houseelement.innerText = 'N/A';
|
houseelement.innerText = "N/A";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createNewsButton(fragment: DocumentFragment, menu: HTMLElement) {
|
function createNewsButton(fragment: DocumentFragment, menu: HTMLElement) {
|
||||||
const NewsButtonStr = '<li class="item" data-key="news" id="newsbutton" data-path="/news" data-betterseqta="true"><label><svg style="width:24px;height:24px" viewBox="0 0 24 24"><path fill="currentColor" d="M20 3H4C2.89 3 2 3.89 2 5V19C2 20.11 2.89 21 4 21H20C21.11 21 22 20.11 22 19V5C22 3.89 21.11 3 20 3M5 7H10V13H5V7M19 17H5V15H19V17M19 13H12V11H19V13M19 9H12V7H19V9Z" /></svg><span>News</span></label></li>';
|
const NewsButtonStr =
|
||||||
|
'<li class="item" data-key="news" id="newsbutton" data-path="/news" data-betterseqta="true"><label><svg style="width:24px;height:24px" viewBox="0 0 24 24"><path fill="currentColor" d="M20 3H4C2.89 3 2 3.89 2 5V19C2 20.11 2.89 21 4 21H20C21.11 21 22 20.11 22 19V5C22 3.89 21.11 3 20 3M5 7H10V13H5V7M19 17H5V15H19V17M19 13H12V11H19V13M19 9H12V7H19V9Z" /></svg><span>News</span></label></li>';
|
||||||
const NewsButton = stringToHTML(NewsButtonStr);
|
const NewsButton = stringToHTML(NewsButtonStr);
|
||||||
|
|
||||||
if (NewsButton.firstChild) {
|
if (NewsButton.firstChild) {
|
||||||
fragment.appendChild(NewsButton.firstChild);
|
fragment.appendChild(NewsButton.firstChild);
|
||||||
}
|
}
|
||||||
|
|
||||||
let iconCover = document.createElement('div');
|
let iconCover = document.createElement("div");
|
||||||
iconCover.classList.add('icon-cover');
|
iconCover.classList.add("icon-cover");
|
||||||
iconCover.id = 'icon-cover';
|
iconCover.id = "icon-cover";
|
||||||
menu.appendChild(iconCover);
|
menu.appendChild(iconCover);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupEventListeners() {
|
function setupEventListeners() {
|
||||||
const menuCover = document.querySelector('#icon-cover');
|
const menuCover = document.querySelector("#icon-cover");
|
||||||
const homebutton = document.getElementById('homebutton');
|
const homebutton = document.getElementById("homebutton");
|
||||||
const newsbutton = document.getElementById('newsbutton');
|
const newsbutton = document.getElementById("newsbutton");
|
||||||
|
|
||||||
homebutton?.addEventListener('click', function() {
|
homebutton?.addEventListener("click", function () {
|
||||||
if (!homebutton.classList.contains('draggable') && !homebutton.classList.contains('active')) {
|
if (
|
||||||
|
!homebutton.classList.contains("draggable") &&
|
||||||
|
!homebutton.classList.contains("active")
|
||||||
|
) {
|
||||||
loadHomePage();
|
loadHomePage();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
newsbutton?.addEventListener('click', function() {
|
newsbutton?.addEventListener("click", function () {
|
||||||
if (!newsbutton.classList.contains('draggable') && !newsbutton.classList.contains('active')) {
|
if (
|
||||||
|
!newsbutton.classList.contains("draggable") &&
|
||||||
|
!newsbutton.classList.contains("active")
|
||||||
|
) {
|
||||||
SendNewsPage();
|
SendNewsPage();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
menuCover?.addEventListener('click', function() {
|
menuCover?.addEventListener("click", function () {
|
||||||
location.href = '../#?page=/home';
|
location.href = "../#?page=/home";
|
||||||
loadHomePage();
|
loadHomePage();
|
||||||
(document.getElementById('menu')!.firstChild! as HTMLElement).classList.remove('noscroll');
|
(
|
||||||
|
document.getElementById("menu")!.firstChild! as HTMLElement
|
||||||
|
).classList.remove("noscroll");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createSettingsButton() {
|
async function createSettingsButton() {
|
||||||
let SettingsButton = stringToHTML( /* html */`
|
let SettingsButton = stringToHTML(/* html */ `
|
||||||
<button class="addedButton tooltip" id="AddedSettings">
|
<button class="addedButton tooltip" id="AddedSettings">
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24">
|
<svg width="24" height="24" viewBox="0 0 24 24">
|
||||||
<g><g><path d="M23.182,6.923c-.29,0-3.662,2.122-4.142,2.4l-2.8-1.555V4.511l4.257-2.456a.518.518,0,0,0,.233-.408.479.479,0,0,0-.233-.407,6.511,6.511,0,1,0-3.327,12.107,6.582,6.582,0,0,0,6.148-4.374,5.228,5.228,0,0,0,.333-1.542A.461.461,0,0,0,23.182,6.923Z"></path><path d="M9.73,10.418,7.376,12.883c-.01.01-.021.016-.03.025L1.158,19.1a2.682,2.682,0,1,0,3.793,3.793l4.583-4.582,0,0,4.1-4.005-.037-.037A9.094,9.094,0,0,1,9.73,10.418ZM3.053,21.888A.894.894,0,1,1,3.946,21,.893.893,0,0,1,3.053,21.888Z"></path></g></g>
|
<g><g><path d="M23.182,6.923c-.29,0-3.662,2.122-4.142,2.4l-2.8-1.555V4.511l4.257-2.456a.518.518,0,0,0,.233-.408.479.479,0,0,0-.233-.407,6.511,6.511,0,1,0-3.327,12.107,6.582,6.582,0,0,0,6.148-4.374,5.228,5.228,0,0,0,.333-1.542A.461.461,0,0,0,23.182,6.923Z"></path><path d="M9.73,10.418,7.376,12.883c-.01.01-.021.016-.03.025L1.158,19.1a2.682,2.682,0,1,0,3.793,3.793l4.583-4.582,0,0,4.1-4.005-.037-.037A9.094,9.094,0,0,1,9.73,10.418ZM3.053,21.888A.894.894,0,1,1,3.946,21,.893.893,0,0,1,3.053,21.888Z"></path></g></g>
|
||||||
</svg>
|
</svg>
|
||||||
${settingsState.onoff ? '<div class="tooltiptext topmenutooltip">BetterSEQTA+ Settings</div>' : ''}
|
${settingsState.onoff ? '<div class="tooltiptext topmenutooltip">BetterSEQTA+ Settings</div>' : ""}
|
||||||
</button>
|
</button>
|
||||||
`);
|
`);
|
||||||
let ContentDiv = document.getElementById('content');
|
let ContentDiv = document.getElementById("content");
|
||||||
ContentDiv!.append(SettingsButton.firstChild!);
|
ContentDiv!.append(SettingsButton.firstChild!);
|
||||||
}
|
}
|
||||||
|
|
||||||
function GetLightDarkModeString() {
|
function GetLightDarkModeString() {
|
||||||
if (settingsState.DarkMode) {
|
if (settingsState.DarkMode) {
|
||||||
return 'Switch to light theme'
|
return "Switch to light theme";
|
||||||
} else {
|
} else {
|
||||||
return 'Switch to dark theme'
|
return "Switch to dark theme";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function addDarkLightToggle() {
|
async function addDarkLightToggle() {
|
||||||
const tooltipString = GetLightDarkModeString();
|
const tooltipString = GetLightDarkModeString();
|
||||||
const svgContent = settingsState.DarkMode ?
|
const svgContent = settingsState.DarkMode
|
||||||
/* html */`<defs><clipPath id="__lottie_element_80"><rect width="24" height="24" x="0" y="0"></rect></clipPath></defs><g clip-path="url(#__lottie_element_80)"><g style="display: block;" transform="matrix(1,0,0,1,12,12)" opacity="1"><g opacity="1" transform="matrix(1,0,0,1,0,0)"><path fill-opacity="1" d=" M0,-4 C-2.2100000381469727,-4 -4,-2.2100000381469727 -4,0 C-4,2.2100000381469727 -2.2100000381469727,4 0,4 C2.2100000381469727,4 4,2.2100000381469727 4,0 C4,-2.2100000381469727 2.2100000381469727,-4 0,-4z"></path></g></g><g style="display: block;" transform="matrix(1,0,0,1,12,12)" opacity="1"><g opacity="1" transform="matrix(1,0,0,1,0,0)"><path fill-opacity="1" d=" M0,6 C-3.309999942779541,6 -6,3.309999942779541 -6,0 C-6,-3.309999942779541 -3.309999942779541,-6 0,-6 C3.309999942779541,-6 6,-3.309999942779541 6,0 C6,3.309999942779541 3.309999942779541,6 0,6z M8,-3.309999942779541 C8,-3.309999942779541 8,-8 8,-8 C8,-8 3.309999942779541,-8 3.309999942779541,-8 C3.309999942779541,-8 0,-11.3100004196167 0,-11.3100004196167 C0,-11.3100004196167 -3.309999942779541,-8 -3.309999942779541,-8 C-3.309999942779541,-8 -8,-8 -8,-8 C-8,-8 -8,-3.309999942779541 -8,-3.309999942779541 C-8,-3.309999942779541 -11.3100004196167,0 -11.3100004196167,0 C-11.3100004196167,0 -8,3.309999942779541 -8,3.309999942779541 C-8,3.309999942779541 -8,8 -8,8 C-8,8 -3.309999942779541,8 -3.309999942779541,8 C-3.309999942779541,8 0,11.3100004196167 0,11.3100004196167 C0,11.3100004196167 3.309999942779541,8 3.309999942779541,8 C3.309999942779541,8 8,8 8,8 C8,8 8,3.309999942779541 8,3.309999942779541 C8,3.309999942779541 11.3100004196167,0 11.3100004196167,0 C11.3100004196167,0 8,-3.309999942779541 8,-3.309999942779541z"></path></g></g></g>` :
|
? /* html */ `<defs><clipPath id="__lottie_element_80"><rect width="24" height="24" x="0" y="0"></rect></clipPath></defs><g clip-path="url(#__lottie_element_80)"><g style="display: block;" transform="matrix(1,0,0,1,12,12)" opacity="1"><g opacity="1" transform="matrix(1,0,0,1,0,0)"><path fill-opacity="1" d=" M0,-4 C-2.2100000381469727,-4 -4,-2.2100000381469727 -4,0 C-4,2.2100000381469727 -2.2100000381469727,4 0,4 C2.2100000381469727,4 4,2.2100000381469727 4,0 C4,-2.2100000381469727 2.2100000381469727,-4 0,-4z"></path></g></g><g style="display: block;" transform="matrix(1,0,0,1,12,12)" opacity="1"><g opacity="1" transform="matrix(1,0,0,1,0,0)"><path fill-opacity="1" d=" M0,6 C-3.309999942779541,6 -6,3.309999942779541 -6,0 C-6,-3.309999942779541 -3.309999942779541,-6 0,-6 C3.309999942779541,-6 6,-3.309999942779541 6,0 C6,3.309999942779541 3.309999942779541,6 0,6z M8,-3.309999942779541 C8,-3.309999942779541 8,-8 8,-8 C8,-8 3.309999942779541,-8 3.309999942779541,-8 C3.309999942779541,-8 0,-11.3100004196167 0,-11.3100004196167 C0,-11.3100004196167 -3.309999942779541,-8 -3.309999942779541,-8 C-3.309999942779541,-8 -8,-8 -8,-8 C-8,-8 -8,-3.309999942779541 -8,-3.309999942779541 C-8,-3.309999942779541 -11.3100004196167,0 -11.3100004196167,0 C-11.3100004196167,0 -8,3.309999942779541 -8,3.309999942779541 C-8,3.309999942779541 -8,8 -8,8 C-8,8 -3.309999942779541,8 -3.309999942779541,8 C-3.309999942779541,8 0,11.3100004196167 0,11.3100004196167 C0,11.3100004196167 3.309999942779541,8 3.309999942779541,8 C3.309999942779541,8 8,8 8,8 C8,8 8,3.309999942779541 8,3.309999942779541 C8,3.309999942779541 11.3100004196167,0 11.3100004196167,0 C11.3100004196167,0 8,-3.309999942779541 8,-3.309999942779541z"></path></g></g></g>`
|
||||||
/* html */`<defs><clipPath id="__lottie_element_263"><rect width="24" height="24" x="0" y="0"></rect></clipPath></defs><g clip-path="url(#__lottie_element_263)"><g style="display: block;" transform="matrix(1.5,0,0,1.5,7,12)" opacity="1"><g opacity="1" transform="matrix(1,0,0,1,0,0)"><path fill-opacity="1" d=" M0,-4 C-2.2100000381469727,-4 -1.2920000553131104,-2.2100000381469727 -1.2920000553131104,0 C-1.2920000553131104,2.2100000381469727 -2.2100000381469727,4 0,4 C2.2100000381469727,4 4,2.2100000381469727 4,0 C4,-2.2100000381469727 2.2100000381469727,-4 0,-4z"></path></g></g><g style="display: block;" transform="matrix(-1,0,0,-1,12,12)" opacity="1"><g opacity="1" transform="matrix(1,0,0,1,0,0)"><path fill-opacity="1" d=" M0,6 C-3.309999942779541,6 -6,3.309999942779541 -6,0 C-6,-3.309999942779541 -3.309999942779541,-6 0,-6 C3.309999942779541,-6 6,-3.309999942779541 6,0 C6,3.309999942779541 3.309999942779541,6 0,6z M8,-3.309999942779541 C8,-3.309999942779541 8,-8 8,-8 C8,-8 3.309999942779541,-8 3.309999942779541,-8 C3.309999942779541,-8 0,-11.3100004196167 0,-11.3100004196167 C0,-11.3100004196167 -3.309999942779541,-8 -3.309999942779541,-8 C-3.309999942779541,-8 -8,-8 -8,-8 C-8,-8 -8,-3.309999942779541 -8,-3.309999942779541 C-8,-3.309999942779541 -11.3100004196167,0 -11.3100004196167,0 C-11.3100004196167,0 -8,3.309999942779541 -8,3.309999942779541 C-8,3.309999942779541 -8,8 -8,8 C-8,8 -3.309999942779541,8 -3.309999942779541,8 C-3.309999942779541,8 0,11.3100004196167 0,11.3100004196167 C0,11.3100004196167 3.309999942779541,8 3.309999942779541,8 C3.309999942779541,8 8,8 8,8 C8,8 8,3.309999942779541 8,3.309999942779541 C8,3.309999942779541 11.3100004196167,0 11.3100004196167,0 C11.3100004196167,0 8,-3.309999942779541 8,-3.309999942779541z"></path></g></g></g>`;
|
: /* html */ `<defs><clipPath id="__lottie_element_263"><rect width="24" height="24" x="0" y="0"></rect></clipPath></defs><g clip-path="url(#__lottie_element_263)"><g style="display: block;" transform="matrix(1.5,0,0,1.5,7,12)" opacity="1"><g opacity="1" transform="matrix(1,0,0,1,0,0)"><path fill-opacity="1" d=" M0,-4 C-2.2100000381469727,-4 -1.2920000553131104,-2.2100000381469727 -1.2920000553131104,0 C-1.2920000553131104,2.2100000381469727 -2.2100000381469727,4 0,4 C2.2100000381469727,4 4,2.2100000381469727 4,0 C4,-2.2100000381469727 2.2100000381469727,-4 0,-4z"></path></g></g><g style="display: block;" transform="matrix(-1,0,0,-1,12,12)" opacity="1"><g opacity="1" transform="matrix(1,0,0,1,0,0)"><path fill-opacity="1" d=" M0,6 C-3.309999942779541,6 -6,3.309999942779541 -6,0 C-6,-3.309999942779541 -3.309999942779541,-6 0,-6 C3.309999942779541,-6 6,-3.309999942779541 6,0 C6,3.309999942779541 3.309999942779541,6 0,6z M8,-3.309999942779541 C8,-3.309999942779541 8,-8 8,-8 C8,-8 3.309999942779541,-8 3.309999942779541,-8 C3.309999942779541,-8 0,-11.3100004196167 0,-11.3100004196167 C0,-11.3100004196167 -3.309999942779541,-8 -3.309999942779541,-8 C-3.309999942779541,-8 -8,-8 -8,-8 C-8,-8 -8,-3.309999942779541 -8,-3.309999942779541 C-8,-3.309999942779541 -11.3100004196167,0 -11.3100004196167,0 C-11.3100004196167,0 -8,3.309999942779541 -8,3.309999942779541 C-8,3.309999942779541 -8,8 -8,8 C-8,8 -3.309999942779541,8 -3.309999942779541,8 C-3.309999942779541,8 0,11.3100004196167 0,11.3100004196167 C0,11.3100004196167 3.309999942779541,8 3.309999942779541,8 C3.309999942779541,8 8,8 8,8 C8,8 8,3.309999942779541 8,3.309999942779541 C8,3.309999942779541 11.3100004196167,0 11.3100004196167,0 C11.3100004196167,0 8,-3.309999942779541 8,-3.309999942779541z"></path></g></g></g>`;
|
||||||
|
|
||||||
const LightDarkModeButton = stringToHTML(/* html */`
|
const LightDarkModeButton = stringToHTML(/* html */ `
|
||||||
<button class="addedButton DarkLightButton tooltip" id="LightDarkModeButton">
|
<button class="addedButton DarkLightButton tooltip" id="LightDarkModeButton">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg">${svgContent}</svg>
|
<svg xmlns="http://www.w3.org/2000/svg">${svgContent}</svg>
|
||||||
<div class="tooltiptext topmenutooltip" id="darklighttooliptext">${tooltipString}</div>
|
<div class="tooltiptext topmenutooltip" id="darklighttooliptext">${tooltipString}</div>
|
||||||
</button>
|
</button>
|
||||||
`);
|
`);
|
||||||
|
|
||||||
let ContentDiv = document.getElementById('content');
|
let ContentDiv = document.getElementById("content");
|
||||||
ContentDiv!.append(LightDarkModeButton.firstChild!);
|
ContentDiv!.append(LightDarkModeButton.firstChild!);
|
||||||
|
|
||||||
updateAllColors();
|
updateAllColors();
|
||||||
|
|
||||||
document.getElementById('LightDarkModeButton')!.addEventListener('click', async () => {
|
document
|
||||||
const darklightText = document.getElementById('darklighttooliptext');
|
.getElementById("LightDarkModeButton")!
|
||||||
|
.addEventListener("click", async () => {
|
||||||
|
const darklightText = document.getElementById("darklighttooliptext");
|
||||||
|
|
||||||
if (settingsState.originalDarkMode != undefined && settingsState.selectedTheme) {
|
if (
|
||||||
darklightText!.innerText = 'Locked by current theme';
|
settingsState.originalDarkMode != undefined &&
|
||||||
|
settingsState.selectedTheme
|
||||||
|
) {
|
||||||
|
darklightText!.innerText = "Locked by current theme";
|
||||||
|
|
||||||
await delay(1000)
|
await delay(1000);
|
||||||
|
|
||||||
|
darklightText!.innerText = GetLightDarkModeString();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
settingsState.DarkMode = !settingsState.DarkMode;
|
||||||
|
|
||||||
|
updateAllColors();
|
||||||
|
|
||||||
darklightText!.innerText = GetLightDarkModeString();
|
darklightText!.innerText = GetLightDarkModeString();
|
||||||
|
});
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
settingsState.DarkMode = !settingsState.DarkMode;
|
|
||||||
|
|
||||||
updateAllColors();
|
|
||||||
|
|
||||||
darklightText!.innerText = GetLightDarkModeString();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function customizeMenuToggle() {
|
function customizeMenuToggle() {
|
||||||
const menuToggle = document.getElementById('menuToggle');
|
const menuToggle = document.getElementById("menuToggle");
|
||||||
if (menuToggle) {
|
if (menuToggle) {
|
||||||
menuToggle.innerHTML = '';
|
menuToggle.innerHTML = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
for (let i = 0; i < 3; i++) {
|
for (let i = 0; i < 3; i++) {
|
||||||
const line = document.createElement('div');
|
const line = document.createElement("div");
|
||||||
line.className = 'hamburger-line';
|
line.className = "hamburger-line";
|
||||||
menuToggle!.appendChild(line);
|
menuToggle!.appendChild(line);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,18 @@
|
|||||||
import { getDataById, isIndexedDBSupported } from '@/interface/hooks/BackgroundDataLoader';
|
import {
|
||||||
|
getDataById,
|
||||||
|
isIndexedDBSupported,
|
||||||
|
} from "@/interface/hooks/BackgroundDataLoader";
|
||||||
|
|
||||||
export async function appendBackgroundToUI() {
|
export async function appendBackgroundToUI() {
|
||||||
const parent = document.getElementById('container');
|
const parent = document.getElementById("container");
|
||||||
if (!parent) return;
|
if (!parent) return;
|
||||||
|
|
||||||
const backgroundContainer = document.createElement('div');
|
const backgroundContainer = document.createElement("div");
|
||||||
backgroundContainer.classList.add('imageBackground');
|
backgroundContainer.classList.add("imageBackground");
|
||||||
backgroundContainer.setAttribute('excludeDarkCheck', 'true');
|
backgroundContainer.setAttribute("excludeDarkCheck", "true");
|
||||||
|
|
||||||
const mediaContainer = document.createElement('div');
|
const mediaContainer = document.createElement("div");
|
||||||
mediaContainer.id = 'media-container';
|
mediaContainer.id = "media-container";
|
||||||
backgroundContainer.appendChild(mediaContainer);
|
backgroundContainer.appendChild(mediaContainer);
|
||||||
|
|
||||||
parent.appendChild(backgroundContainer);
|
parent.appendChild(backgroundContainer);
|
||||||
@@ -24,9 +27,9 @@ export async function loadBackground() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const selectedBackgroundId = localStorage.getItem('selectedBackground');
|
const selectedBackgroundId = localStorage.getItem("selectedBackground");
|
||||||
if (!selectedBackgroundId) {
|
if (!selectedBackgroundId) {
|
||||||
const backgroundContainer = document.querySelector('.imageBackground');
|
const backgroundContainer = document.querySelector(".imageBackground");
|
||||||
if (backgroundContainer) {
|
if (backgroundContainer) {
|
||||||
backgroundContainer.remove();
|
backgroundContainer.remove();
|
||||||
}
|
}
|
||||||
@@ -36,35 +39,36 @@ export async function loadBackground() {
|
|||||||
const background = await getDataById(selectedBackgroundId);
|
const background = await getDataById(selectedBackgroundId);
|
||||||
if (!background) return;
|
if (!background) return;
|
||||||
|
|
||||||
let backgroundContainer = document.querySelector('.imageBackground');
|
let backgroundContainer = document.querySelector(".imageBackground");
|
||||||
if (!backgroundContainer) {
|
if (!backgroundContainer) {
|
||||||
backgroundContainer = document.createElement('div');
|
backgroundContainer = document.createElement("div");
|
||||||
backgroundContainer.classList.add('imageBackground');
|
backgroundContainer.classList.add("imageBackground");
|
||||||
backgroundContainer.setAttribute('excludeDarkCheck', 'true');
|
backgroundContainer.setAttribute("excludeDarkCheck", "true");
|
||||||
const parent = document.getElementById('container');
|
const parent = document.getElementById("container");
|
||||||
if (parent) {
|
if (parent) {
|
||||||
parent.appendChild(backgroundContainer);
|
parent.appendChild(backgroundContainer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mediaContainer = document.getElementById('media-container');
|
let mediaContainer = document.getElementById("media-container");
|
||||||
if (!mediaContainer) {
|
if (!mediaContainer) {
|
||||||
mediaContainer = document.createElement('div');
|
mediaContainer = document.createElement("div");
|
||||||
mediaContainer.id = 'media-container';
|
mediaContainer.id = "media-container";
|
||||||
backgroundContainer.appendChild(mediaContainer);
|
backgroundContainer.appendChild(mediaContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
mediaContainer = document.getElementById('media-container');
|
mediaContainer = document.getElementById("media-container");
|
||||||
if (!mediaContainer) return;
|
if (!mediaContainer) return;
|
||||||
|
|
||||||
mediaContainer.innerHTML = '';
|
mediaContainer.innerHTML = "";
|
||||||
|
|
||||||
const mediaElement = background.type === 'video'
|
const mediaElement =
|
||||||
? document.createElement('video')
|
background.type === "video"
|
||||||
: document.createElement('img');
|
? document.createElement("video")
|
||||||
|
: document.createElement("img");
|
||||||
|
|
||||||
mediaElement.src = URL.createObjectURL(background.blob);
|
mediaElement.src = URL.createObjectURL(background.blob);
|
||||||
mediaElement.classList.add('background');
|
mediaElement.classList.add("background");
|
||||||
|
|
||||||
if (mediaElement instanceof HTMLVideoElement) {
|
if (mediaElement instanceof HTMLVideoElement) {
|
||||||
mediaElement.loop = true;
|
mediaElement.loop = true;
|
||||||
@@ -74,6 +78,6 @@ export async function loadBackground() {
|
|||||||
|
|
||||||
mediaContainer.appendChild(mediaElement);
|
mediaContainer.appendChild(mediaElement);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading background:', error);
|
console.error("Error loading background:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+9
-12
File diff suppressed because one or more lines are too long
@@ -6,19 +6,22 @@ import { debounce } from "lodash";
|
|||||||
export class SettingsResizer {
|
export class SettingsResizer {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.adjustPopupHeight();
|
this.adjustPopupHeight();
|
||||||
window.addEventListener('resize', debounce(this.adjustPopupHeight, 250) as EventListener);
|
window.addEventListener(
|
||||||
document.addEventListener('DOMContentLoaded', this.adjustPopupHeight);
|
"resize",
|
||||||
|
debounce(this.adjustPopupHeight, 250) as EventListener,
|
||||||
|
);
|
||||||
|
document.addEventListener("DOMContentLoaded", this.adjustPopupHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
private adjustPopupHeight() {
|
private adjustPopupHeight() {
|
||||||
const iframePopup = document.getElementById('ExtensionPopup');
|
const iframePopup = document.getElementById("ExtensionPopup");
|
||||||
if (!iframePopup) return;
|
if (!iframePopup) return;
|
||||||
|
|
||||||
const viewportHeight = window.innerHeight;
|
const viewportHeight = window.innerHeight;
|
||||||
const idealHeight = viewportHeight - 80 - 15; // -80px for the top of the popup
|
const idealHeight = viewportHeight - 80 - 15; // -80px for the top of the popup
|
||||||
|
|
||||||
if (idealHeight > 600) {
|
if (idealHeight > 600) {
|
||||||
iframePopup.style.height = '600px';
|
iframePopup.style.height = "600px";
|
||||||
} else {
|
} else {
|
||||||
iframePopup.style.height = `${idealHeight}px`;
|
iframePopup.style.height = `${idealHeight}px`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import Color from 'color';
|
import Color from "color";
|
||||||
|
|
||||||
function adjustLuminance(color: any, lum: any) {
|
function adjustLuminance(color: any, lum: any) {
|
||||||
let adjustedColor = Color(color.toLowerCase());
|
let adjustedColor = Color(color.toLowerCase());
|
||||||
@@ -8,20 +8,20 @@ function adjustLuminance(color: any, lum: any) {
|
|||||||
adjustedColor = Color.rgb(
|
adjustedColor = Color.rgb(
|
||||||
Math.min(Math.max(0, rgbObj.r + rgbObj.r * lum), 255),
|
Math.min(Math.max(0, rgbObj.r + rgbObj.r * lum), 255),
|
||||||
Math.min(Math.max(0, rgbObj.g + rgbObj.g * lum), 255),
|
Math.min(Math.max(0, rgbObj.g + rgbObj.g * lum), 255),
|
||||||
Math.min(Math.max(0, rgbObj.b + rgbObj.b * lum), 255)
|
Math.min(Math.max(0, rgbObj.b + rgbObj.b * lum), 255),
|
||||||
);
|
);
|
||||||
|
|
||||||
return adjustedColor.string();
|
return adjustedColor.string();
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ColorLuminance(color: any, lum = 0) {
|
export default function ColorLuminance(color: any, lum = 0) {
|
||||||
if (color == '' || color == null) {
|
if (color == "" || color == null) {
|
||||||
// light cyan blue
|
// light cyan blue
|
||||||
return '#00bfff';
|
return "#00bfff";
|
||||||
}
|
}
|
||||||
const colorRegex = /rgba?\(([^)]+)\)/gi; // Case-insensitive match for rgb() or rgba()
|
const colorRegex = /rgba?\(([^)]+)\)/gi; // Case-insensitive match for rgb() or rgba()
|
||||||
|
|
||||||
if (color.toLowerCase().includes('gradient')) {
|
if (color.toLowerCase().includes("gradient")) {
|
||||||
let gradient = color;
|
let gradient = color;
|
||||||
|
|
||||||
let uniqueColorSet = new Set();
|
let uniqueColorSet = new Set();
|
||||||
@@ -35,7 +35,10 @@ export default function ColorLuminance(color: any, lum = 0) {
|
|||||||
// Adjust luminance for each unique color stop
|
// Adjust luminance for each unique color stop
|
||||||
for (let colorStop of uniqueColorSet) {
|
for (let colorStop of uniqueColorSet) {
|
||||||
const adjustedColor = adjustLuminance(colorStop, lum);
|
const adjustedColor = adjustLuminance(colorStop, lum);
|
||||||
gradient = gradient.replace(new RegExp(colorStop as string, 'gi'), adjustedColor);
|
gradient = gradient.replace(
|
||||||
|
new RegExp(colorStop as string, "gi"),
|
||||||
|
adjustedColor,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return gradient;
|
return gradient;
|
||||||
|
|||||||
@@ -1,72 +1,81 @@
|
|||||||
import browser from 'webextension-polyfill'
|
import browser from "webextension-polyfill";
|
||||||
import { GetThresholdOfColor } from '@/seqta/ui/colors/getThresholdColour';
|
import { GetThresholdOfColor } from "@/seqta/ui/colors/getThresholdColour";
|
||||||
import { lightenAndPaleColor } from './lightenAndPaleColor';
|
import { lightenAndPaleColor } from "./lightenAndPaleColor";
|
||||||
import ColorLuminance from './ColorLuminance';
|
import ColorLuminance from "./ColorLuminance";
|
||||||
import { settingsState } from '@/seqta/utils/listeners/SettingsState';
|
import { settingsState } from "@/seqta/utils/listeners/SettingsState";
|
||||||
|
|
||||||
import darkLogo from '@/resources/icons/betterseqta-light-full.png';
|
import darkLogo from "@/resources/icons/betterseqta-light-full.png";
|
||||||
import lightLogo from '@/resources/icons/betterseqta-dark-full.png';
|
import lightLogo from "@/resources/icons/betterseqta-dark-full.png";
|
||||||
|
|
||||||
// Helper functions
|
// Helper functions
|
||||||
const setCSSVar = (varName: any, value: any) => document.documentElement.style.setProperty(varName, value);
|
const setCSSVar = (varName: any, value: any) =>
|
||||||
const applyProperties = (props: any) => Object.entries(props).forEach(([key, value]) => setCSSVar(key, value));
|
document.documentElement.style.setProperty(varName, value);
|
||||||
|
const applyProperties = (props: any) =>
|
||||||
|
Object.entries(props).forEach(([key, value]) => setCSSVar(key, value));
|
||||||
|
|
||||||
export function updateAllColors() {
|
export function updateAllColors() {
|
||||||
// Determine the color to use
|
// Determine the color to use
|
||||||
const selectedColor = settingsState.selectedColor !== '' ? settingsState.selectedColor : '#007bff';
|
const selectedColor =
|
||||||
|
settingsState.selectedColor !== ""
|
||||||
|
? settingsState.selectedColor
|
||||||
|
: "#007bff";
|
||||||
|
|
||||||
if (settingsState.transparencyEffects) {
|
if (settingsState.transparencyEffects) {
|
||||||
document.documentElement.classList.add('transparencyEffects');
|
document.documentElement.classList.add("transparencyEffects");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Common properties, always applied
|
// Common properties, always applied
|
||||||
const commonProps = {
|
const commonProps = {
|
||||||
'--better-sub': '#161616',
|
"--better-sub": "#161616",
|
||||||
'--better-alert-highlight': '#c61851',
|
"--better-alert-highlight": "#c61851",
|
||||||
'--better-main': settingsState.selectedColor
|
"--better-main": settingsState.selectedColor,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Mode-based properties, applied if storedSetting is provided
|
// Mode-based properties, applied if storedSetting is provided
|
||||||
let modeProps = {};
|
let modeProps = {};
|
||||||
modeProps = settingsState.DarkMode ? {
|
modeProps = settingsState.DarkMode
|
||||||
'--betterseqta-logo': `url(${browser.runtime.getURL(darkLogo)})`
|
? {
|
||||||
} : {
|
"--betterseqta-logo": `url(${browser.runtime.getURL(darkLogo)})`,
|
||||||
'--better-pale': lightenAndPaleColor(selectedColor),
|
}
|
||||||
'--betterseqta-logo': `url(${browser.runtime.getURL(lightLogo)})`
|
: {
|
||||||
};
|
"--better-pale": lightenAndPaleColor(selectedColor),
|
||||||
|
"--betterseqta-logo": `url(${browser.runtime.getURL(lightLogo)})`,
|
||||||
|
};
|
||||||
|
|
||||||
if (settingsState.DarkMode) {
|
if (settingsState.DarkMode) {
|
||||||
document.documentElement.style.removeProperty('--better-pale');
|
document.documentElement.style.removeProperty("--better-pale");
|
||||||
document.documentElement.classList.add('dark');
|
document.documentElement.classList.add("dark");
|
||||||
} else {
|
} else {
|
||||||
document.documentElement.classList.remove('dark');
|
document.documentElement.classList.remove("dark");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dynamic properties, always applied
|
// Dynamic properties, always applied
|
||||||
const rgbThreshold = GetThresholdOfColor(selectedColor);
|
const rgbThreshold = GetThresholdOfColor(selectedColor);
|
||||||
const isBright = rgbThreshold > 210;
|
const isBright = rgbThreshold > 210;
|
||||||
const dynamicProps = {
|
const dynamicProps = {
|
||||||
'--text-color': isBright ? 'black' : 'white',
|
"--text-color": isBright ? "black" : "white",
|
||||||
'--better-light': selectedColor === '#ffffff' ? '#b7b7b7' : ColorLuminance(selectedColor, 0.95)
|
"--better-light":
|
||||||
|
selectedColor === "#ffffff"
|
||||||
|
? "#b7b7b7"
|
||||||
|
: ColorLuminance(selectedColor, 0.95),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Apply all the properties
|
// Apply all the properties
|
||||||
applyProperties({ ...commonProps, ...modeProps, ...dynamicProps });
|
applyProperties({ ...commonProps, ...modeProps, ...dynamicProps });
|
||||||
|
|
||||||
let alliframes = document.getElementsByTagName('iframe');
|
let alliframes = document.getElementsByTagName("iframe");
|
||||||
|
|
||||||
for (let i = 0; i < alliframes.length; i++) {
|
for (let i = 0; i < alliframes.length; i++) {
|
||||||
const element = alliframes[i];
|
const element = alliframes[i];
|
||||||
|
|
||||||
if (element.getAttribute('excludeDarkCheck') == 'true') {
|
if (element.getAttribute("excludeDarkCheck") == "true") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settingsState.DarkMode) {
|
if (settingsState.DarkMode) {
|
||||||
element.contentDocument?.documentElement.classList.add('dark');
|
element.contentDocument?.documentElement.classList.add("dark");
|
||||||
} else {
|
} else {
|
||||||
element.contentDocument?.documentElement.classList.remove('dark');
|
element.contentDocument?.documentElement.classList.remove("dark");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,38 +1,38 @@
|
|||||||
import Color from "color"
|
import Color from "color";
|
||||||
export function GetThresholdOfColor(color: any) {
|
export function GetThresholdOfColor(color: any) {
|
||||||
if (!color) return 0
|
if (!color) return 0;
|
||||||
// Case-insensitive regular expression for matching RGBA colors
|
// Case-insensitive regular expression for matching RGBA colors
|
||||||
const rgbaRegex = /rgba?\(([^)]+)\)/gi
|
const rgbaRegex = /rgba?\(([^)]+)\)/gi;
|
||||||
|
|
||||||
// Check if the color string is a gradient (linear or radial)
|
// Check if the color string is a gradient (linear or radial)
|
||||||
if (color.includes("gradient")) {
|
if (color.includes("gradient")) {
|
||||||
let gradientThresholds = []
|
let gradientThresholds = [];
|
||||||
|
|
||||||
// Find and replace all instances of RGBA in the gradient
|
// Find and replace all instances of RGBA in the gradient
|
||||||
let match
|
let match;
|
||||||
while ((match = rgbaRegex.exec(color)) !== null) {
|
while ((match = rgbaRegex.exec(color)) !== null) {
|
||||||
// Extract the individual components (r, g, b, a)
|
// Extract the individual components (r, g, b, a)
|
||||||
const rgbaString = match[1]
|
const rgbaString = match[1];
|
||||||
const [r, g, b] = rgbaString.split(",").map((str) => str.trim())
|
const [r, g, b] = rgbaString.split(",").map((str) => str.trim());
|
||||||
|
|
||||||
// Compute the threshold using your existing algorithm
|
// Compute the threshold using your existing algorithm
|
||||||
const threshold = Math.sqrt(
|
const threshold = Math.sqrt(
|
||||||
parseInt(r) ** 2 + parseInt(g) ** 2 + parseInt(b) ** 2,
|
parseInt(r) ** 2 + parseInt(g) ** 2 + parseInt(b) ** 2,
|
||||||
)
|
);
|
||||||
|
|
||||||
// Store the computed threshold
|
// Store the computed threshold
|
||||||
gradientThresholds.push(threshold)
|
gradientThresholds.push(threshold);
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate the average threshold
|
|
||||||
const averageThreshold =
|
|
||||||
gradientThresholds.reduce((acc, val) => acc + val, 0) /
|
|
||||||
gradientThresholds.length
|
|
||||||
|
|
||||||
return averageThreshold
|
|
||||||
} else {
|
|
||||||
// Handle the color as a simple RGBA (or hex, or whatever the Color library supports)
|
|
||||||
const rgb = Color.rgb(color).object()
|
|
||||||
return Math.sqrt(rgb.r ** 2 + rgb.g ** 2 + rgb.b ** 2)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calculate the average threshold
|
||||||
|
const averageThreshold =
|
||||||
|
gradientThresholds.reduce((acc, val) => acc + val, 0) /
|
||||||
|
gradientThresholds.length;
|
||||||
|
|
||||||
|
return averageThreshold;
|
||||||
|
} else {
|
||||||
|
// Handle the color as a simple RGBA (or hex, or whatever the Color library supports)
|
||||||
|
const rgb = Color.rgb(color).object();
|
||||||
|
return Math.sqrt(rgb.r ** 2 + rgb.g ** 2 + rgb.b ** 2);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
import Color from 'color';
|
import Color from "color";
|
||||||
|
|
||||||
export function lightenAndPaleColor(inputColor: any, lightenFactor = 0.75, paleFactor = 0.55) {
|
export function lightenAndPaleColor(
|
||||||
|
inputColor: any,
|
||||||
|
lightenFactor = 0.75,
|
||||||
|
paleFactor = 0.55,
|
||||||
|
) {
|
||||||
if (!inputColor) return;
|
if (!inputColor) return;
|
||||||
|
|
||||||
if (inputColor.includes('gradient')) {
|
if (inputColor.includes("gradient")) {
|
||||||
const baseColor = findMatchingColor(inputColor);
|
const baseColor = findMatchingColor(inputColor);
|
||||||
|
|
||||||
return lightenAndPaleColor(baseColor, lightenFactor, paleFactor);
|
return lightenAndPaleColor(baseColor, lightenFactor, paleFactor);
|
||||||
@@ -29,27 +33,38 @@ export function lightenAndPaleColor(inputColor: any, lightenFactor = 0.75, paleF
|
|||||||
}
|
}
|
||||||
// Utility function to average an array of Color objects
|
// Utility function to average an array of Color objects
|
||||||
function averageColors(colors: any) {
|
function averageColors(colors: any) {
|
||||||
let avgR = 0, avgG = 0, avgB = 0;
|
let avgR = 0,
|
||||||
|
avgG = 0,
|
||||||
|
avgB = 0;
|
||||||
colors.forEach((color: any) => {
|
colors.forEach((color: any) => {
|
||||||
avgR += color.red();
|
avgR += color.red();
|
||||||
avgG += color.green();
|
avgG += color.green();
|
||||||
avgB += color.blue();
|
avgB += color.blue();
|
||||||
});
|
});
|
||||||
return Color.rgb(avgR / colors.length, avgG / colors.length, avgB / colors.length);
|
return Color.rgb(
|
||||||
|
avgR / colors.length,
|
||||||
|
avgG / colors.length,
|
||||||
|
avgB / colors.length,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
// Main function to find a matching color for a CSS gradient
|
// Main function to find a matching color for a CSS gradient
|
||||||
function findMatchingColor(cssGradient: any) {
|
function findMatchingColor(cssGradient: any) {
|
||||||
try {
|
try {
|
||||||
// Step 1: Parse the gradient to extract color stops (case-insensitive)
|
// Step 1: Parse the gradient to extract color stops (case-insensitive)
|
||||||
const regex = /#[0-9a-fA-F]{6}|rgb\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*\)|rgba\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*,\s*[\d.]+\s*\)/gi;
|
const regex =
|
||||||
|
/#[0-9a-fA-F]{6}|rgb\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*\)|rgba\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*,\s*[\d.]+\s*\)/gi;
|
||||||
const colorStops = cssGradient.match(regex);
|
const colorStops = cssGradient.match(regex);
|
||||||
|
|
||||||
if (!colorStops) {
|
if (!colorStops) {
|
||||||
throw new Error('No valid color stops found in the provided CSS gradient.');
|
throw new Error(
|
||||||
|
"No valid color stops found in the provided CSS gradient.",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalize and trim the color stops
|
// Normalize and trim the color stops
|
||||||
const normalizedColorStops = colorStops.map((color: any) => color.toLowerCase().replace(/\s+/g, ''));
|
const normalizedColorStops = colorStops.map((color: any) =>
|
||||||
|
color.toLowerCase().replace(/\s+/g, ""),
|
||||||
|
);
|
||||||
|
|
||||||
// Convert the color stops to Color objects
|
// Convert the color stops to Color objects
|
||||||
const colorObjects = normalizedColorStops.map((color: any) => Color(color));
|
const colorObjects = normalizedColorStops.map((color: any) => Color(color));
|
||||||
@@ -57,7 +72,6 @@ function findMatchingColor(cssGradient: any) {
|
|||||||
// Step 2: Average the color stops
|
// Step 2: Average the color stops
|
||||||
const baseColor = averageColors(colorObjects);
|
const baseColor = averageColors(colorObjects);
|
||||||
|
|
||||||
|
|
||||||
// Step 4: Return the matching color in HEX format
|
// Step 4: Return the matching color in HEX format
|
||||||
return baseColor.hex();
|
return baseColor.hex();
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
|
|||||||
@@ -20,191 +20,356 @@ function generateMockUserCode(): string {
|
|||||||
function getRandomDate(): Date {
|
function getRandomDate(): Date {
|
||||||
const start = new Date();
|
const start = new Date();
|
||||||
const end = new Date(start.getTime() + 60 * 24 * 60 * 60 * 1000); // 60 days from now
|
const end = new Date(start.getTime() + 60 * 24 * 60 * 60 * 1000); // 60 days from now
|
||||||
return new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime()));
|
return new Date(
|
||||||
|
start.getTime() + Math.random() * (end.getTime() - start.getTime()),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const contentConfig: ContentConfig = {
|
const contentConfig: ContentConfig = {
|
||||||
lessonTitle: {
|
lessonTitle: {
|
||||||
selector: '.day h2',
|
selector: ".day h2",
|
||||||
action: (element) => { element.textContent = getRandomElement(mockData.subjects); }
|
action: (element) => {
|
||||||
|
element.textContent = getRandomElement(mockData.subjects);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
teacher: {
|
teacher: {
|
||||||
selector: '.day h3:first-of-type',
|
selector: ".day h3:first-of-type",
|
||||||
action: (element) => { element.textContent = getRandomElement(mockData.teachers); }
|
action: (element) => {
|
||||||
|
element.textContent = getRandomElement(mockData.teachers);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
classroom: {
|
classroom: {
|
||||||
selector: '.day h3:last-of-type',
|
selector: ".day h3:last-of-type",
|
||||||
action: (element) => { element.textContent = getRandomElement(mockData.classrooms); }
|
action: (element) => {
|
||||||
|
element.textContent = getRandomElement(mockData.classrooms);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
userName: {
|
userName: {
|
||||||
selector: '.userInfoName, .name',
|
selector: ".userInfoName, .name",
|
||||||
action: (element) => { element.textContent = getRandomElement(mockData.names); }
|
action: (element) => {
|
||||||
|
element.textContent = getRandomElement(mockData.names);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
userCode: {
|
userCode: {
|
||||||
selector: '.userInfoText > .userInfoCode',
|
selector: ".userInfoText > .userInfoCode",
|
||||||
action: (element) => { element.textContent = generateMockUserCode(); }
|
action: (element) => {
|
||||||
|
element.textContent = generateMockUserCode();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
assessmentTitle: {
|
assessmentTitle: {
|
||||||
selector: '.upcoming-assessment .upcoming-assessment-title',
|
selector: ".upcoming-assessment .upcoming-assessment-title",
|
||||||
action: (element) => { element.textContent = getRandomElement(mockData.assessmentTitles); }
|
action: (element) => {
|
||||||
|
element.textContent = getRandomElement(mockData.assessmentTitles);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
assessmentSubject: {
|
assessmentSubject: {
|
||||||
selector: '.upcoming-assessment .upcoming-details h5',
|
selector: ".upcoming-assessment .upcoming-details h5",
|
||||||
action: (element) => { element.textContent = getRandomElement(mockData.subjects); }
|
action: (element) => {
|
||||||
|
element.textContent = getRandomElement(mockData.subjects);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
noticeTitle: {
|
noticeTitle: {
|
||||||
selector: '.notice h3',
|
selector: ".notice h3",
|
||||||
action: (element) => { element.textContent = getRandomElement(mockData.notices); }
|
action: (element) => {
|
||||||
|
element.textContent = getRandomElement(mockData.notices);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
noticeContent: {
|
noticeContent: {
|
||||||
selector: '.notice .contents',
|
selector: ".notice .contents",
|
||||||
action: (element) => { element.textContent = 'Content has been redacted for privacy.'; }
|
action: (element) => {
|
||||||
|
element.textContent = "Content has been redacted for privacy.";
|
||||||
|
},
|
||||||
},
|
},
|
||||||
upcomingCheckboxes: {
|
upcomingCheckboxes: {
|
||||||
selector: '.upcoming-checkbox-container',
|
selector: ".upcoming-checkbox-container",
|
||||||
action: (element) => { element.firstChild!.textContent = 'SUBJ'; }
|
action: (element) => {
|
||||||
|
element.firstChild!.textContent = "SUBJ";
|
||||||
|
},
|
||||||
},
|
},
|
||||||
dates: {
|
dates: {
|
||||||
selector: '.upcoming-date-title h5, input[type="date"]',
|
selector: '.upcoming-date-title h5, input[type="date"]',
|
||||||
action: (element) => {
|
action: (element) => {
|
||||||
const randomDate = getRandomDate();
|
const randomDate = getRandomDate();
|
||||||
if (element instanceof HTMLInputElement) {
|
if (element instanceof HTMLInputElement) {
|
||||||
element.value = randomDate.toISOString().split('T')[0];
|
element.value = randomDate.toISOString().split("T")[0];
|
||||||
} else {
|
} else {
|
||||||
element.textContent = randomDate.toLocaleDateString('en-US', { weekday: 'long', day: 'numeric', month: 'long' });
|
element.textContent = randomDate.toLocaleDateString("en-US", {
|
||||||
|
weekday: "long",
|
||||||
|
day: "numeric",
|
||||||
|
month: "long",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
messageSubject: {
|
messageSubject: {
|
||||||
selector: '[class*="MessageList__subject___"]',
|
selector: '[class*="MessageList__subject___"]',
|
||||||
action: (element) => { element.textContent = getRandomElement(mockData.messages.subjects); }
|
action: (element) => {
|
||||||
|
element.textContent = getRandomElement(mockData.messages.subjects);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
messageSender: {
|
messageSender: {
|
||||||
selector: '[class*="MessageList__value___"]',
|
selector: '[class*="MessageList__value___"]',
|
||||||
action: (element) => { element.textContent = getRandomElement(mockData.messages.sender); }
|
action: (element) => {
|
||||||
|
element.textContent = getRandomElement(mockData.messages.sender);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
messageRecipients: {
|
messageRecipients: {
|
||||||
selector: '[class*="MessageList__recipients___"] [class*="MessageList__value___"]',
|
selector:
|
||||||
action: (element) => { element.textContent = 'Recipient(s) Redacted'; }
|
'[class*="MessageList__recipients___"] [class*="MessageList__value___"]',
|
||||||
|
action: (element) => {
|
||||||
|
element.textContent = "Recipient(s) Redacted";
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
messageDate: {
|
messageDate: {
|
||||||
selector: '[class*="MessageList__date___"]',
|
selector: '[class*="MessageList__date___"]',
|
||||||
action: (element) => { element.textContent = getRandomDate().toLocaleDateString('en-US', { weekday: 'long', day: 'numeric', month: 'long' }); }
|
action: (element) => {
|
||||||
|
element.textContent = getRandomDate().toLocaleDateString("en-US", {
|
||||||
|
weekday: "long",
|
||||||
|
day: "numeric",
|
||||||
|
month: "long",
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
avatarImage: {
|
avatarImage: {
|
||||||
selector: '[class*="Avatar__Avatar___"]',
|
selector: '[class*="Avatar__Avatar___"]',
|
||||||
action: (element) => {
|
action: (element) => {
|
||||||
if (element instanceof HTMLElement) {
|
if (element instanceof HTMLElement) {
|
||||||
element.style.removeProperty('background-image');
|
element.style.removeProperty("background-image");
|
||||||
element.firstChild!.firstChild!.textContent = getRandomElement(mockData.names)[0];
|
element.firstChild!.firstChild!.textContent = getRandomElement(
|
||||||
|
mockData.names,
|
||||||
|
)[0];
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
notificationCount: {
|
notificationCount: {
|
||||||
selector: '[class*="notifications__bubble___"]',
|
selector: '[class*="notifications__bubble___"]',
|
||||||
action: (element) => { element.textContent = Math.floor(Math.random() * 100).toString(); }
|
action: (element) => {
|
||||||
|
element.textContent = Math.floor(Math.random() * 100).toString();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
schoolName: {
|
schoolName: {
|
||||||
selector: 'title',
|
selector: "title",
|
||||||
action: (element) => { element.textContent = 'School Portal'; }
|
action: (element) => {
|
||||||
|
element.textContent = "School Portal";
|
||||||
|
},
|
||||||
},
|
},
|
||||||
documentNames: {
|
documentNames: {
|
||||||
selector: '.document td.title',
|
selector: ".document td.title",
|
||||||
action: (element) => { element.textContent = 'Document Name Redacted'; }
|
action: (element) => {
|
||||||
|
element.textContent = "Document Name Redacted";
|
||||||
|
},
|
||||||
},
|
},
|
||||||
forumTopics: {
|
forumTopics: {
|
||||||
selector: '#menu .sub ul li label',
|
selector: "#menu .sub ul li label",
|
||||||
action: (element) => { element.textContent = 'Forum Topic Redacted'; }
|
action: (element) => {
|
||||||
|
element.textContent = "Forum Topic Redacted";
|
||||||
|
},
|
||||||
},
|
},
|
||||||
courseNames: {
|
courseNames: {
|
||||||
selector: '#menu .sub ul li[data-colour] label',
|
selector: "#menu .sub ul li[data-colour] label",
|
||||||
action: (element) => { element.textContent = 'Course Name Redacted'; }
|
action: (element) => {
|
||||||
|
element.textContent = "Course Name Redacted";
|
||||||
|
},
|
||||||
},
|
},
|
||||||
yearGroups: {
|
yearGroups: {
|
||||||
selector: '#menu .sub > ul > li > label',
|
selector: "#menu .sub > ul > li > label",
|
||||||
action: (element) => { element.textContent = 'Year Group Redacted'; }
|
action: (element) => {
|
||||||
|
element.textContent = "Year Group Redacted";
|
||||||
|
},
|
||||||
},
|
},
|
||||||
newsArticleTitle: {
|
newsArticleTitle: {
|
||||||
selector: '.ArticleText a',
|
selector: ".ArticleText a",
|
||||||
action: (element) => { element.textContent = 'News Article Title Redacted'; }
|
action: (element) => {
|
||||||
|
element.textContent = "News Article Title Redacted";
|
||||||
|
},
|
||||||
},
|
},
|
||||||
newsArticleContent: {
|
newsArticleContent: {
|
||||||
selector: '.ArticleText p',
|
selector: ".ArticleText p",
|
||||||
action: (element) => { element.textContent = 'News Article Content Redacted'; }
|
action: (element) => {
|
||||||
|
element.textContent = "News Article Content Redacted";
|
||||||
|
},
|
||||||
},
|
},
|
||||||
userHouse: {
|
userHouse: {
|
||||||
selector: '.userInfohouse',
|
selector: ".userInfohouse",
|
||||||
action: (element) => { element.textContent = 'House'; }
|
action: (element) => {
|
||||||
}
|
element.textContent = "House";
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockData = {
|
const mockData = {
|
||||||
subjects: [
|
subjects: [
|
||||||
"Mathematics", "English", "Science", "History", "Geography",
|
"Mathematics",
|
||||||
"Art", "Music", "Physical Education", "Chemistry", "Physics",
|
"English",
|
||||||
"Biology", "Economics", "Business Studies", "French", "Spanish",
|
"Science",
|
||||||
"Computer Science", "Literature", "Environmental Studies",
|
"History",
|
||||||
"Political Science", "Sociology"
|
"Geography",
|
||||||
|
"Art",
|
||||||
|
"Music",
|
||||||
|
"Physical Education",
|
||||||
|
"Chemistry",
|
||||||
|
"Physics",
|
||||||
|
"Biology",
|
||||||
|
"Economics",
|
||||||
|
"Business Studies",
|
||||||
|
"French",
|
||||||
|
"Spanish",
|
||||||
|
"Computer Science",
|
||||||
|
"Literature",
|
||||||
|
"Environmental Studies",
|
||||||
|
"Political Science",
|
||||||
|
"Sociology",
|
||||||
],
|
],
|
||||||
teachers: [
|
teachers: [
|
||||||
"Mr. Smith", "Mrs. Johnson", "Ms. Williams", "Dr. Brown",
|
"Mr. Smith",
|
||||||
"Mr. Davis", "Mrs. Miller", "Mr. Wilson", "Ms. Moore",
|
"Mrs. Johnson",
|
||||||
"Dr. Taylor", "Mrs. Anderson", "Mr. Garcia", "Mrs. Martinez",
|
"Ms. Williams",
|
||||||
"Ms. Thompson", "Dr. Lee", "Mr. Robinson", "Mrs. Hall",
|
"Dr. Brown",
|
||||||
"Ms. White", "Dr. Clark", "Mr. Lewis", "Mrs. King"
|
"Mr. Davis",
|
||||||
|
"Mrs. Miller",
|
||||||
|
"Mr. Wilson",
|
||||||
|
"Ms. Moore",
|
||||||
|
"Dr. Taylor",
|
||||||
|
"Mrs. Anderson",
|
||||||
|
"Mr. Garcia",
|
||||||
|
"Mrs. Martinez",
|
||||||
|
"Ms. Thompson",
|
||||||
|
"Dr. Lee",
|
||||||
|
"Mr. Robinson",
|
||||||
|
"Mrs. Hall",
|
||||||
|
"Ms. White",
|
||||||
|
"Dr. Clark",
|
||||||
|
"Mr. Lewis",
|
||||||
|
"Mrs. King",
|
||||||
],
|
],
|
||||||
classrooms: [
|
classrooms: [
|
||||||
"A101", "B205", "C304", "D102", "E201",
|
"A101",
|
||||||
"F103", "G204", "H301", "I202", "J105",
|
"B205",
|
||||||
"K107", "L206", "M303", "N104", "O209"
|
"C304",
|
||||||
|
"D102",
|
||||||
|
"E201",
|
||||||
|
"F103",
|
||||||
|
"G204",
|
||||||
|
"H301",
|
||||||
|
"I202",
|
||||||
|
"J105",
|
||||||
|
"K107",
|
||||||
|
"L206",
|
||||||
|
"M303",
|
||||||
|
"N104",
|
||||||
|
"O209",
|
||||||
],
|
],
|
||||||
names: [
|
names: [
|
||||||
"John Doe", "Jane Smith", "Michael Johnson", "Emily Brown",
|
"John Doe",
|
||||||
"David Lee", "Sarah Davis", "Robert Wilson", "Lisa Taylor",
|
"Jane Smith",
|
||||||
"William Moore", "Jennifer Anderson", "Thomas Garcia",
|
"Michael Johnson",
|
||||||
"Olivia Martinez", "Daniel Thompson", "Sophia Lee",
|
"Emily Brown",
|
||||||
"Matthew Robinson", "Ava Hall", "Jacob White",
|
"David Lee",
|
||||||
"Mia Clark", "James Lewis", "Lily King"
|
"Sarah Davis",
|
||||||
|
"Robert Wilson",
|
||||||
|
"Lisa Taylor",
|
||||||
|
"William Moore",
|
||||||
|
"Jennifer Anderson",
|
||||||
|
"Thomas Garcia",
|
||||||
|
"Olivia Martinez",
|
||||||
|
"Daniel Thompson",
|
||||||
|
"Sophia Lee",
|
||||||
|
"Matthew Robinson",
|
||||||
|
"Ava Hall",
|
||||||
|
"Jacob White",
|
||||||
|
"Mia Clark",
|
||||||
|
"James Lewis",
|
||||||
|
"Lily King",
|
||||||
],
|
],
|
||||||
assessmentTitles: [
|
assessmentTitles: [
|
||||||
"Mid-term Exam", "Final Project", "Research Paper",
|
"Mid-term Exam",
|
||||||
"Oral Presentation", "Lab Report", "Essay",
|
"Final Project",
|
||||||
"Group Assignment", "Portfolio Review", "Quiz",
|
"Research Paper",
|
||||||
"Practical Test", "Class Presentation",
|
"Oral Presentation",
|
||||||
"Online Assessment", "Case Study", "Field Report",
|
"Lab Report",
|
||||||
"Peer Review", "Coding Challenge", "Math Test",
|
"Essay",
|
||||||
"Literary Analysis", "Debate", "Design Project"
|
"Group Assignment",
|
||||||
|
"Portfolio Review",
|
||||||
|
"Quiz",
|
||||||
|
"Practical Test",
|
||||||
|
"Class Presentation",
|
||||||
|
"Online Assessment",
|
||||||
|
"Case Study",
|
||||||
|
"Field Report",
|
||||||
|
"Peer Review",
|
||||||
|
"Coding Challenge",
|
||||||
|
"Math Test",
|
||||||
|
"Literary Analysis",
|
||||||
|
"Debate",
|
||||||
|
"Design Project",
|
||||||
],
|
],
|
||||||
notices: [
|
notices: [
|
||||||
"School Assembly", "Excursion Reminder", "Fundraising Event",
|
"School Assembly",
|
||||||
"Parent-Teacher Meetings", "Sports Day", "Book Fair",
|
"Excursion Reminder",
|
||||||
"Career Day", "Music Concert", "Art Exhibition",
|
"Fundraising Event",
|
||||||
"Science Fair", "Holiday Celebration", "Community Service Day",
|
"Parent-Teacher Meetings",
|
||||||
"Graduation Ceremony", "Award Ceremony", "Workshop",
|
"Sports Day",
|
||||||
"Open House", "Seminar", "Club Meeting",
|
"Book Fair",
|
||||||
"Field Trip", "Cultural Festival"
|
"Career Day",
|
||||||
|
"Music Concert",
|
||||||
|
"Art Exhibition",
|
||||||
|
"Science Fair",
|
||||||
|
"Holiday Celebration",
|
||||||
|
"Community Service Day",
|
||||||
|
"Graduation Ceremony",
|
||||||
|
"Award Ceremony",
|
||||||
|
"Workshop",
|
||||||
|
"Open House",
|
||||||
|
"Seminar",
|
||||||
|
"Club Meeting",
|
||||||
|
"Field Trip",
|
||||||
|
"Cultural Festival",
|
||||||
],
|
],
|
||||||
messages: {
|
messages: {
|
||||||
subjects: [
|
subjects: [
|
||||||
"Mid-year Exams", "Science project due soon", "Mufti Day coming up!",
|
"Mid-year Exams",
|
||||||
"School Assembly", "Excursion Reminder", "Fundraising Event",
|
"Science project due soon",
|
||||||
"Parent-Teacher Meetings", "Sports Day", "Book Fair",
|
"Mufti Day coming up!",
|
||||||
"Career Day", "Music Concert", "Art Exhibition",
|
"School Assembly",
|
||||||
"Science Fair", "Holiday Celebration", "Community Service Day",
|
"Excursion Reminder",
|
||||||
"Graduation Ceremony", "Award Ceremony", "Workshop",
|
"Fundraising Event",
|
||||||
"Open House", "Seminar", "Club Meeting",
|
"Parent-Teacher Meetings",
|
||||||
"Field Trip", "Cultural Festival"
|
"Sports Day",
|
||||||
|
"Book Fair",
|
||||||
|
"Career Day",
|
||||||
|
"Music Concert",
|
||||||
|
"Art Exhibition",
|
||||||
|
"Science Fair",
|
||||||
|
"Holiday Celebration",
|
||||||
|
"Community Service Day",
|
||||||
|
"Graduation Ceremony",
|
||||||
|
"Award Ceremony",
|
||||||
|
"Workshop",
|
||||||
|
"Open House",
|
||||||
|
"Seminar",
|
||||||
|
"Club Meeting",
|
||||||
|
"Field Trip",
|
||||||
|
"Cultural Festival",
|
||||||
],
|
],
|
||||||
sender: [
|
sender: [
|
||||||
"Mr. Smith", "Mrs. Johnson", "Ms. Williams", "Dr. Brown",
|
"Mr. Smith",
|
||||||
"Mr. Davis", "Mrs. Miller", "Mr. Wilson", "Ms. Moore",
|
"Mrs. Johnson",
|
||||||
"Dr. Taylor", "Mrs. Anderson", "Mr. Garcia", "Mrs. Martinez",
|
"Ms. Williams",
|
||||||
]
|
"Dr. Brown",
|
||||||
}
|
"Mr. Davis",
|
||||||
|
"Mrs. Miller",
|
||||||
|
"Mr. Wilson",
|
||||||
|
"Ms. Moore",
|
||||||
|
"Dr. Taylor",
|
||||||
|
"Mrs. Anderson",
|
||||||
|
"Mr. Garcia",
|
||||||
|
"Mrs. Martinez",
|
||||||
|
],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function hideSensitiveContent() {
|
export default function hideSensitiveContent() {
|
||||||
|
|||||||
+16
-16
@@ -1,35 +1,35 @@
|
|||||||
import renderSvelte from '@/interface/main';
|
import renderSvelte from "@/interface/main";
|
||||||
import Store from '@/interface/pages/store.svelte'
|
import Store from "@/interface/pages/store.svelte";
|
||||||
|
|
||||||
import { unmount } from 'svelte'
|
import { unmount } from "svelte";
|
||||||
|
|
||||||
let remove: () => void
|
let remove: () => void;
|
||||||
|
|
||||||
export function OpenStorePage() {
|
export function OpenStorePage() {
|
||||||
remove = renderStore()
|
remove = renderStore();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function renderStore() {
|
export function renderStore() {
|
||||||
const container = document.querySelector('#container');
|
const container = document.querySelector("#container");
|
||||||
if (!container) {
|
if (!container) {
|
||||||
throw new Error('Container not found');
|
throw new Error("Container not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
const child = document.createElement('div');
|
const child = document.createElement("div");
|
||||||
child.id = 'store';
|
child.id = "store";
|
||||||
container!.appendChild(child);
|
container!.appendChild(child);
|
||||||
|
|
||||||
const shadow = child.attachShadow({ mode: 'open' });
|
const shadow = child.attachShadow({ mode: "open" });
|
||||||
const app = renderSvelte(Store, shadow);
|
const app = renderSvelte(Store, shadow);
|
||||||
|
|
||||||
return () => unmount(app)
|
return () => unmount(app);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function closeStore() {
|
export function closeStore() {
|
||||||
document.getElementById('store')!.classList.add('hide')
|
document.getElementById("store")!.classList.add("hide");
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
remove()
|
remove();
|
||||||
document.getElementById('store')!.remove()
|
document.getElementById("store")!.remove();
|
||||||
}, 500)
|
}, 500);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,40 @@
|
|||||||
import { changeSettingsClicked, closeExtensionPopup, SettingsClicked } from "../Closers/closeExtensionPopup"
|
import {
|
||||||
import renderSvelte from "@/interface/main"
|
changeSettingsClicked,
|
||||||
import { SettingsResizer } from "@/seqta/ui/SettingsResizer"
|
closeExtensionPopup,
|
||||||
import Settings from "@/interface/pages/settings.svelte"
|
SettingsClicked,
|
||||||
|
} from "../Closers/closeExtensionPopup";
|
||||||
|
import renderSvelte from "@/interface/main";
|
||||||
|
import { SettingsResizer } from "@/seqta/ui/SettingsResizer";
|
||||||
|
import Settings from "@/interface/pages/settings.svelte";
|
||||||
|
|
||||||
export function addExtensionSettings() {
|
export function addExtensionSettings() {
|
||||||
const extensionPopup = document.createElement("div")
|
const extensionPopup = document.createElement("div");
|
||||||
extensionPopup.classList.add("outside-container", "hide")
|
extensionPopup.classList.add("outside-container", "hide");
|
||||||
extensionPopup.id = "ExtensionPopup"
|
extensionPopup.id = "ExtensionPopup";
|
||||||
|
|
||||||
const extensionContainer = document.querySelector(
|
const extensionContainer = document.querySelector(
|
||||||
"#container",
|
"#container",
|
||||||
) as HTMLDivElement
|
) as HTMLDivElement;
|
||||||
if (extensionContainer) extensionContainer.appendChild(extensionPopup)
|
if (extensionContainer) extensionContainer.appendChild(extensionPopup);
|
||||||
|
|
||||||
// create shadow dom and render svelte app
|
// create shadow dom and render svelte app
|
||||||
try {
|
try {
|
||||||
const shadow = extensionPopup.attachShadow({ mode: "open" })
|
const shadow = extensionPopup.attachShadow({ mode: "open" });
|
||||||
requestIdleCallback(() => renderSvelte(Settings, shadow))
|
requestIdleCallback(() => renderSvelte(Settings, shadow));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err);
|
||||||
}
|
|
||||||
|
|
||||||
const container = document.getElementById("container")
|
|
||||||
|
|
||||||
new SettingsResizer()
|
|
||||||
|
|
||||||
container!.onclick = (event) => {
|
|
||||||
if (!SettingsClicked) return
|
|
||||||
|
|
||||||
if (!(event.target as HTMLElement).closest("#AddedSettings")) {
|
|
||||||
if (event.target == extensionPopup) return
|
|
||||||
changeSettingsClicked(closeExtensionPopup())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const container = document.getElementById("container");
|
||||||
|
|
||||||
|
new SettingsResizer();
|
||||||
|
|
||||||
|
container!.onclick = (event) => {
|
||||||
|
if (!SettingsClicked) return;
|
||||||
|
|
||||||
|
if (!(event.target as HTMLElement).closest("#AddedSettings")) {
|
||||||
|
if (event.target == extensionPopup) return;
|
||||||
|
changeSettingsClicked(closeExtensionPopup());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user