mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-06-06 11:44:40 +00:00
Compare commits
92 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d53dc9ff06 | |||
| 5b590512ee | |||
| 3ff8ef144a | |||
| d9abed1c5d | |||
| 82a789bbec | |||
| ce6538f850 | |||
| 979ae7149f | |||
| 6e71437fe8 | |||
| 940ecf8714 | |||
| e0cc2e0fdf | |||
| 5a19ef92e8 | |||
| 0a3781e9c2 | |||
| a2e39c9d84 | |||
| 520abbb5c3 | |||
| d0a11da15f | |||
| fd5802f9a3 | |||
| 380d829d19 | |||
| 702528fb0c | |||
| 2c077bc755 | |||
| fd86e57442 | |||
| 60ce18280e | |||
| 668dbfd78b | |||
| 810aa17f15 | |||
| b64558e50a | |||
| 9b969bd708 | |||
| 1945f7c592 | |||
| 3e26d9af3c | |||
| 3c8d7e246b | |||
| 2e56518330 | |||
| e67f3110e0 | |||
| a67f4d2e25 | |||
| d6025140fd | |||
| 88e9ddf29c | |||
| 11adc4f933 | |||
| 15691e8d94 | |||
| 754b8d0589 | |||
| 1d634d0da1 | |||
| 7136de90be | |||
| 466628479e | |||
| 9c08d0bac2 | |||
| 6c5320007f | |||
| 4734a443b4 | |||
| 7c38e1dc29 | |||
| f3f90ef2a8 | |||
| 9bcc94aa8a | |||
| ff2431f269 | |||
| b442194bc5 | |||
| b59c0eae25 | |||
| e895ce9f6b | |||
| 7192f41535 | |||
| f1b707ab25 | |||
| 7f47cb8183 | |||
| 7f5d138bc9 | |||
| cef0f29640 | |||
| 157343dda9 | |||
| 7705c0a3cd | |||
| 7def7b190c | |||
| c294fb7369 | |||
| 0dbbef0eb1 | |||
| c3c747d996 | |||
| cdc8062275 | |||
| 1857b5ff01 | |||
| 700e3ebb48 | |||
| 16b9610301 | |||
| 7d11e203a6 | |||
| 530f07e640 | |||
| 08586781ce | |||
| 3ca5a49769 | |||
| 886c79b3ee | |||
| 30aa39142d | |||
| 4188ef0d67 | |||
| ad9a013b00 | |||
| cd1f954cc7 | |||
| 6ef6c986dc | |||
| f2e28175a0 | |||
| 3ddcb204ef | |||
| 766f0e6d3f | |||
| f1fcba58ef | |||
| dba2d13bb3 | |||
| 30bf345b86 | |||
| 0e98f52058 | |||
| f89508deb2 | |||
| c7b69ad97b | |||
| 2ef8bb215a | |||
| 16273cf012 | |||
| 13d3ccd8e4 | |||
| 7ebc4db9db | |||
| ed9d662ba4 | |||
| 8647e0b272 | |||
| d93abec615 | |||
| 339b409937 | |||
| 860916a5b8 |
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a target="_blank" href="https://chrome.google.com/webstore/detail/betterseqta%20/afdgaoaclhkhemfkkkonemoapeinchel"><img src="https://user-images.githubusercontent.com/95666457/149519713-159d7ef7-2c21-4034-a616-f037ff46d9a4.png" alt="ChromeDownload" width="250"></a>
|
<a target="_blank" href="https://chrome.google.com/webstore/detail/betterseqta%20/afdgaoaclhkhemfkkkonemoapeinchel"><img src="https://user-images.githubusercontent.com/95666457/149519713-159d7ef7-2c21-4034-a616-f037ff46d9a4.png" alt="ChromeDownload" width="250"></a>
|
||||||
<a target="_blank" href="https://discord.gg/YzmbnCDkat"><img src="https://github.com/SethBurkart123/EvenBetterSEQTA/assets/108050083/23055730-b16e-44c0-9bef-221d8545af92" width="240" style="border-radius:10%;" /></a>
|
<a target="_blank" href="https://discord.gg/YzmbnCDkat"><img src="https://github.com/BetterSEQTA/BetterSEQTA-Plus/assets/108050083/23055730-b16e-44c0-9bef-221d8545af92" width="240" style="border-radius:10%;" /></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@@ -64,7 +64,7 @@ Don't worry- if you get stuck feel free to ask around in the [discord](https://d
|
|||||||
- **🐛 Found a bug?** Open an [issue](https://github.com/BetterSEQTA/BetterSEQTA-plus/issues) or fix it yourself!
|
- **🐛 Found a bug?** Open an [issue](https://github.com/BetterSEQTA/BetterSEQTA-plus/issues) or fix it yourself!
|
||||||
- **💬 Need help?** Join our [Discord community](https://discord.gg/YzmbnCDkat)
|
- **💬 Need help?** Join our [Discord community](https://discord.gg/YzmbnCDkat)
|
||||||
|
|
||||||
We have lots of [`good first issue`](https://github.com/BetterSEQTA/BetterSEQTA-plus/labels/good%20first%20issue) labels perfect for beginners!
|
We have lots of https://github.com/BetterSEQTA/BetterSEQTA-Plus/labels/good%20first%20issue labels perfect for beginners!
|
||||||
|
|
||||||
## Quick Development Setup
|
## Quick Development Setup
|
||||||
|
|
||||||
@@ -85,6 +85,8 @@ npm run dev
|
|||||||
2. Enable "Developer mode"
|
2. Enable "Developer mode"
|
||||||
3. Click "Load unpacked" → Select `dist` folder
|
3. Click "Load unpacked" → Select `dist` folder
|
||||||
4. Visit a SEQTA page to see it work! 🎉
|
4. Visit a SEQTA page to see it work! 🎉
|
||||||
|
> [!WARNING]
|
||||||
|
> Whenever you update the extension while not in dev mode, you will need to use the reload button on the extension page.
|
||||||
|
|
||||||
📚 **Need more details?** Check our [detailed setup guide](./docs/GETTING_STARTED_CONTRIBUTING.md#your-first-30-minutes)
|
📚 **Need more details?** Check our [detailed setup guide](./docs/GETTING_STARTED_CONTRIBUTING.md#your-first-30-minutes)
|
||||||
|
|
||||||
|
|||||||
@@ -230,6 +230,6 @@ Ready to contribute? Here's what to do next:
|
|||||||
Still confused about something? That's totally normal! Here are your options:
|
Still confused about something? That's totally normal! Here are your options:
|
||||||
- 💬 Ask in our [Discord server](https://discord.gg/YzmbnCDkat)
|
- 💬 Ask in our [Discord server](https://discord.gg/YzmbnCDkat)
|
||||||
- 🐛 Open an issue on GitHub
|
- 🐛 Open an issue on GitHub
|
||||||
- 📧 Email us at betterseqta@betterseqta.com
|
- 📧 Email us at betterseqta.plus@gmail.com
|
||||||
|
|
||||||
Remember: **Every expert was once a beginner!** We're here to help you learn and contribute. 🚀
|
Remember: **Every expert was once a beginner!** We're here to help you learn and contribute. 🚀
|
||||||
+12
-11
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "betterseqtaplus",
|
"name": "betterseqtaplus",
|
||||||
"version": "3.4.9",
|
"version": "3.4.13",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Enhance SEQTA Learn's usability and aesthetics! A fork of BetterSEQTA to continue development add add heaps more features!",
|
"description": "Enhance SEQTA Learn's usability and aesthetics! A fork of BetterSEQTA to continue development add add heaps more features!",
|
||||||
"browserslist": "> 0.5%, last 2 versions, not dead",
|
"browserslist": "> 0.5%, last 2 versions, not dead",
|
||||||
@@ -35,19 +35,19 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/plugin-transform-runtime": "^7.26.9",
|
"@babel/plugin-transform-runtime": "^7.26.9",
|
||||||
"@babel/runtime": "^7.26.9",
|
"@babel/runtime": "^7.26.9",
|
||||||
"@bedframe/cli": "^0.0.91",
|
"@bedframe/cli": "^0.0.95",
|
||||||
"@crxjs/vite-plugin": "2.0.0-beta.32",
|
"@crxjs/vite-plugin": "^2.2.0",
|
||||||
"@types/mime-types": "^2.1.4",
|
"@types/mime-types": "^3.0.1",
|
||||||
"@types/react": "^19.0.10",
|
"@types/react": "^19.0.10",
|
||||||
"@types/react-dom": "^19.0.4",
|
"@types/react-dom": "^19.0.4",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^10.0.0",
|
||||||
"dependency-cruiser": "^16.10.0",
|
"dependency-cruiser": "^17.0.1",
|
||||||
"eslint": "9.22.0",
|
"eslint": "^9.33.0",
|
||||||
"glob": "^11.0.1",
|
"glob": "^11.0.1",
|
||||||
"mime-types": "^2.1.35",
|
"mime-types": "^3.0.1",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.5.3",
|
||||||
"process": "^0.11.10",
|
"process": "^0.11.10",
|
||||||
"publish-browser-extension": "^3.0.0",
|
"publish-browser-extension": "^3.0.1",
|
||||||
"sass": "^1.85.1",
|
"sass": "^1.85.1",
|
||||||
"sass-loader": "^16.0.5",
|
"sass-loader": "^16.0.5",
|
||||||
"semver": "^7.7.1",
|
"semver": "^7.7.1",
|
||||||
@@ -55,6 +55,7 @@
|
|||||||
"url": "^0.11.4"
|
"url": "^0.11.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@bedframe/core": "^0.0.46",
|
||||||
"@codemirror/autocomplete": "^6.18.6",
|
"@codemirror/autocomplete": "^6.18.6",
|
||||||
"@codemirror/commands": "^6.8.0",
|
"@codemirror/commands": "^6.8.0",
|
||||||
"@codemirror/lang-css": "^6.3.1",
|
"@codemirror/lang-css": "^6.3.1",
|
||||||
@@ -65,10 +66,10 @@
|
|||||||
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||||
"@tailwindcss/forms": "^0.5.10",
|
"@tailwindcss/forms": "^0.5.10",
|
||||||
"@tsconfig/svelte": "^5.0.4",
|
"@tsconfig/svelte": "^5.0.4",
|
||||||
"@types/chrome": "^0.0.308",
|
"@types/chrome": "^0.1.4",
|
||||||
"@types/color": "^4.2.0",
|
"@types/color": "^4.2.0",
|
||||||
"@types/lodash": "^4.17.16",
|
"@types/lodash": "^4.17.16",
|
||||||
"@types/node": "^22.13.10",
|
"@types/node": "^24.3.0",
|
||||||
"@types/sortablejs": "^1.15.8",
|
"@types/sortablejs": "^1.15.8",
|
||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^10.0.0",
|
||||||
"@types/webextension-polyfill": "^0.12.3",
|
"@types/webextension-polyfill": "^0.12.3",
|
||||||
|
|||||||
+1
-1
@@ -108,7 +108,7 @@ function getDefaultValues(): SettingsState {
|
|||||||
originalSelectedColor: "",
|
originalSelectedColor: "",
|
||||||
DarkMode: true,
|
DarkMode: true,
|
||||||
animations: !isLowEndDevice,
|
animations: !isLowEndDevice,
|
||||||
assessmentsAverage: true,
|
assessmentsAverage: false,
|
||||||
defaultPage: "home",
|
defaultPage: "home",
|
||||||
shortcuts: [
|
shortcuts: [
|
||||||
{
|
{
|
||||||
|
|||||||
+113
-27
@@ -38,11 +38,27 @@ body,
|
|||||||
html {
|
html {
|
||||||
font-family: Rubik, sans-serif !important;
|
font-family: Rubik, sans-serif !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Ensure native select dropdowns are readable on Windows */
|
||||||
|
select option {
|
||||||
|
background-color: #ffffff !important;
|
||||||
|
color: #111827 !important;
|
||||||
|
}
|
||||||
|
.dark select option {
|
||||||
|
background-color: #1f2937 !important;
|
||||||
|
color: #ffffff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Consistent rounded corners for selects */
|
||||||
|
select {
|
||||||
|
border-radius: 8px !important;
|
||||||
|
}
|
||||||
#container {
|
#container {
|
||||||
transition: 200ms;
|
transition: 200ms;
|
||||||
background: var(--auto-background) !important;
|
background: var(--auto-background) !important;
|
||||||
}
|
}
|
||||||
* {
|
:root * {
|
||||||
|
font-family: Rubik, sans-serif !important;
|
||||||
--theme-fg-parts: white;
|
--theme-fg-parts: white;
|
||||||
}
|
}
|
||||||
.extension-editor {
|
.extension-editor {
|
||||||
@@ -143,6 +159,16 @@ html {
|
|||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
#main {
|
||||||
|
> .timetablepage {
|
||||||
|
> .quickbar {
|
||||||
|
.gutter {
|
||||||
|
border-bottom-left-radius: 15px;
|
||||||
|
border-bottom-right-radius: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.forums {
|
.forums {
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
}
|
}
|
||||||
@@ -379,6 +405,18 @@ ul.magicDelete > li.deleting {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Allow long course/assessment names in the sidebar to wrap and break safely */
|
||||||
|
#menu li > label,
|
||||||
|
#menu section > label {
|
||||||
|
white-space: normal;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
word-break: break-word;
|
||||||
|
text-transform: none;
|
||||||
|
font-size: 16px;
|
||||||
|
hyphens: auto;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
#menu {
|
#menu {
|
||||||
width: 270px;
|
width: 270px;
|
||||||
z-index: 19;
|
z-index: 19;
|
||||||
@@ -451,11 +489,6 @@ ul.magicDelete > li.deleting {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#menu li > label,
|
|
||||||
#menu section > label {
|
|
||||||
text-transform: none;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
#userActions {
|
#userActions {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -792,7 +825,7 @@ div > ol:has(.uiFileHandlerWrapper) {
|
|||||||
min-height: 128px !important;
|
min-height: 128px !important;
|
||||||
}
|
}
|
||||||
.student #menu > ul::before {
|
.student #menu > ul::before {
|
||||||
background-image: var(--betterseqta-logo);
|
background-image: var(--betterseqta-logo) !important;
|
||||||
position: -webkit-sticky;
|
position: -webkit-sticky;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
@@ -801,6 +834,18 @@ div > ol:has(.uiFileHandlerWrapper) {
|
|||||||
box-shadow: 0px 0px 4px 2px rgba(0, 0, 0, 0.2);
|
box-shadow: 0px 0px 4px 2px rgba(0, 0, 0, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html.transparencyEffects [class*="BasicPanel__BasicPanel___q92_U"] > ol > li {
|
||||||
|
border-bottom: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.transparencyEffects
|
||||||
|
[class*="BasicPanel__BasicPanel___q92_U"]
|
||||||
|
> ol
|
||||||
|
> li
|
||||||
|
+ li {
|
||||||
|
border-top: 1px solid var(--theme-offset-bg);
|
||||||
|
}
|
||||||
|
|
||||||
.assessmentsWrapper .message {
|
.assessmentsWrapper .message {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -999,8 +1044,8 @@ div > ol:has(.uiFileHandlerWrapper) {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
#title {
|
#title {
|
||||||
background: var(--background-primary);
|
background: var(--background-primary) !important;
|
||||||
color: var(--text-primary);
|
color: var(--text-primary) !important;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding-right: 56px !important;
|
padding-right: 56px !important;
|
||||||
@@ -1111,7 +1156,7 @@ div > ol:has(.uiFileHandlerWrapper) {
|
|||||||
height: 15em;
|
height: 15em;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-auto-flow: column;
|
grid-auto-flow: column;
|
||||||
grid-auto-columns: minmax(130px, 1fr);
|
grid-auto-columns: minmax(142px, 1fr);
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
|
|
||||||
@@ -1320,7 +1365,17 @@ div > ol:has(.uiFileHandlerWrapper) {
|
|||||||
font-size: 20px !important;
|
font-size: 20px !important;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
min-height: 46px;
|
min-height: 46px;
|
||||||
height: 36%;
|
/* Let the title expand naturally but clamp to 2 lines to avoid overlap */
|
||||||
|
height: auto;
|
||||||
|
line-height: 1.2;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
word-break: break-word;
|
||||||
|
hyphens: auto;
|
||||||
}
|
}
|
||||||
.day h3 {
|
.day h3 {
|
||||||
padding: 0px 5px;
|
padding: 0px 5px;
|
||||||
@@ -1654,6 +1709,8 @@ iframe.userHTML {
|
|||||||
|
|
||||||
.programmeNavigator {
|
.programmeNavigator {
|
||||||
box-shadow: 0 0 40px 0px rgba(0, 0, 0, 0.05);
|
box-shadow: 0 0 40px 0px rgba(0, 0, 0, 0.05);
|
||||||
|
overflow-y: scroll;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
.navigator {
|
.navigator {
|
||||||
padding: 6px !important;
|
padding: 6px !important;
|
||||||
@@ -1722,7 +1779,9 @@ iframe.userHTML {
|
|||||||
background: var(--auto-background);
|
background: var(--auto-background);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
scale: 0.95;
|
scale: 0.95;
|
||||||
transition: opacity 0.2s ease-out, scale 0.1s ease-out;
|
transition:
|
||||||
|
opacity 0.2s ease-out,
|
||||||
|
scale 0.1s ease-out;
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
@@ -1736,7 +1795,6 @@ iframe.userHTML {
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
scale: 1;
|
scale: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1760,7 +1818,9 @@ iframe.userHTML {
|
|||||||
.dark .programmeNavigator .navigator {
|
.dark .programmeNavigator .navigator {
|
||||||
.search {
|
.search {
|
||||||
background: var(--background-secondary) !important;
|
background: var(--background-secondary) !important;
|
||||||
box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.1), inset 0px 0px 15px 0px rgba(0, 0, 0, 0.1) !important;
|
box-shadow:
|
||||||
|
0px 0px 10px 0px rgba(0, 0, 0, 0.1),
|
||||||
|
inset 0px 0px 15px 0px rgba(0, 0, 0, 0.1) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.dark #main > .course > .content > h1 {
|
.dark #main > .course > .content > h1 {
|
||||||
@@ -1991,13 +2051,17 @@ div.liveEntry {
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.dailycalMarker {
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.uiFileHandler .uiButton {
|
.uiFileHandler .uiButton {
|
||||||
border-radius: 32px !important;
|
border-radius: 32px !important;
|
||||||
color: var(--text-primary) !important;
|
color: var(--text-primary) !important;
|
||||||
margin-top: 4px !important;
|
margin-top: 4px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.uiFile {
|
a.uiFile:not(.rows) {
|
||||||
display: flex !important;
|
display: flex !important;
|
||||||
height: auto !important;
|
height: auto !important;
|
||||||
width: 200px !important;
|
width: 200px !important;
|
||||||
@@ -2021,6 +2085,7 @@ a.uiFile {
|
|||||||
svg {
|
svg {
|
||||||
color: white;
|
color: white;
|
||||||
position: unset !important;
|
position: unset !important;
|
||||||
|
display: unset !important;
|
||||||
width: auto !important;
|
width: auto !important;
|
||||||
height: 42px !important;
|
height: 42px !important;
|
||||||
z-index: 1 !important;
|
z-index: 1 !important;
|
||||||
@@ -2142,7 +2207,6 @@ div.bar.flat {
|
|||||||
border-radius: 12px !important;
|
border-radius: 12px !important;
|
||||||
border: none !important;
|
border: none !important;
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
color: #fff !important;
|
|
||||||
padding: 16px !important;
|
padding: 16px !important;
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
height: 100% !important;
|
height: 100% !important;
|
||||||
@@ -2150,10 +2214,10 @@ div.bar.flat {
|
|||||||
display: flex !important;
|
display: flex !important;
|
||||||
align-items: flex-start !important;
|
align-items: flex-start !important;
|
||||||
overflow: hidden !important;
|
overflow: hidden !important;
|
||||||
|
color: var(--text-primary) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.cke_toolbox > .cke_toolbar > .cke_toolgroup > .cke_button {
|
.cke_toolbox > .cke_toolbar > .cke_toolgroup > .cke_button {
|
||||||
background: var(--background-secondary) !important;
|
background: var(--background-secondary) !important;
|
||||||
color: var(--text-primary) !important;
|
color: var(--text-primary) !important;
|
||||||
@@ -3295,6 +3359,22 @@ div.day-empty {
|
|||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
transform-origin: center center;
|
transform-origin: center center;
|
||||||
}
|
}
|
||||||
|
.whatsnewTextContainer.privacyStatement p {
|
||||||
|
margin-bottom: 1.5ex;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.whatsnewTextContainer.privacyStatement a {
|
||||||
|
background: rgba(184, 184, 184, 0.1);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
margin-left: 0.25rem;
|
||||||
|
padding: 2px 4px;
|
||||||
|
}
|
||||||
|
.dark .whatsnewTextContainer.privacyStatement a {
|
||||||
|
background: rgba(7, 7, 7, 0.1);
|
||||||
|
}
|
||||||
.whatsnewHeader {
|
.whatsnewHeader {
|
||||||
margin: 20px;
|
margin: 20px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -3329,6 +3409,7 @@ div.day-empty {
|
|||||||
.whatsnewImg {
|
.whatsnewImg {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
width: 90%;
|
width: 90%;
|
||||||
|
aspect-ratio: 16 / 10;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.3);
|
box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.3);
|
||||||
}
|
}
|
||||||
@@ -3355,7 +3436,6 @@ div.day-empty {
|
|||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #9a3412;
|
color: #9a3412;
|
||||||
background-color: #ffedd569;
|
|
||||||
border-radius: 9999px;
|
border-radius: 9999px;
|
||||||
border: 1px solid rgba(253, 186, 140, 0.3);
|
border: 1px solid rgba(253, 186, 140, 0.3);
|
||||||
background-color: #ffedd5;
|
background-color: #ffedd5;
|
||||||
@@ -3706,7 +3786,12 @@ div.day-empty {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.notice-unified-content {
|
.notice-unified-content {
|
||||||
h1, h2, h3, h4, h5, h6 {
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
font-weight: inherit !important;
|
font-weight: inherit !important;
|
||||||
@@ -3727,7 +3812,7 @@ div.day-empty {
|
|||||||
font-size: 20px !important; // Nice middle ground - not too big, not too small
|
font-size: 20px !important; // Nice middle ground - not too big, not too small
|
||||||
font-weight: 600 !important;
|
font-weight: 600 !important;
|
||||||
color: var(--text-primary) !important;
|
color: var(--text-primary) !important;
|
||||||
margin: 0 0 12px 0 !important;
|
margin: 0 0 12px !important;
|
||||||
line-height: 1.3 !important;
|
line-height: 1.3 !important;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
@@ -3798,7 +3883,8 @@ div.day-empty {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ul, ol {
|
ul,
|
||||||
|
ol {
|
||||||
margin: 12px 0;
|
margin: 12px 0;
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
}
|
}
|
||||||
@@ -3838,7 +3924,6 @@ button.notice-close-btn {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 18px;
|
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
transition: all 0.2s ease !important;
|
transition: all 0.2s ease !important;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
@@ -3896,13 +3981,13 @@ button.notice-close-btn {
|
|||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
margin: 16px 20px 20px 20px;
|
margin: 16px 20px 20px;
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notice-modal-body {
|
.notice-modal-body {
|
||||||
padding: 0 20px 20px 20px;
|
padding: 0 20px 20px;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
@@ -3945,7 +4030,8 @@ button.notice-close-btn {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ul, ol {
|
ul,
|
||||||
|
ol {
|
||||||
margin: 12px 0;
|
margin: 12px 0;
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
}
|
}
|
||||||
@@ -3982,11 +4068,11 @@ button.notice-close-btn {
|
|||||||
|
|
||||||
.notice-modal-title {
|
.notice-modal-title {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
margin: 12px 16px 16px 16px;
|
margin: 12px 16px 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notice-modal-body {
|
.notice-modal-body {
|
||||||
padding: 0 16px 16px 16px;
|
padding: 0 16px 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notice-card {
|
.notice-card {
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"last_updated": "2024-06-15T12:00:00Z",
|
||||||
|
"whatsnew_html": "<div class=\"whatsnewTextContainer\" style=\"overflow-y: auto; font-size: 1.3rem; line-height: 1.6;\"><p>It has come to our attention that several schools have expressed concerns about BetterSEQTA+. This is very disheartening, so we have decided to release a statement on the situation.</p><p>To view our privacy policy, please click the <strong>shield icon</strong> in the settings menu, or <a href=\"https://betterseqta.org/privacy\" target=\"_blank\" rel=\"noopener noreferrer\" id=\"privacy-link\" style=\"color: inherit; text-decoration: underline; cursor: pointer; white-space: nowrap;\">click here</a>.</p><p style=\"font-weight: bold; margin-top: 15px;\">We never collect any information from you, and aim to provide the best features possible.</p></div>"
|
||||||
|
}
|
||||||
@@ -2,6 +2,16 @@ div:has(> #rbgcp-wrapper) {
|
|||||||
background: transparent !important;
|
background: transparent !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#rbgcp-inputs-wrap {
|
||||||
|
padding-top: 4px !important;
|
||||||
|
margin-bottom: -8px;
|
||||||
|
|
||||||
|
#rbgcp-hex-input,
|
||||||
|
#rbgcp-input {
|
||||||
|
height: 28px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
#rbgcp-wrapper {
|
#rbgcp-wrapper {
|
||||||
div[style="padding-top: 11px; position: relative;"] div {
|
div[style="padding-top: 11px; position: relative;"] div {
|
||||||
|
|||||||
@@ -108,7 +108,6 @@ export default function Picker({
|
|||||||
<ColorPicker
|
<ColorPicker
|
||||||
disableDarkMode={true}
|
disableDarkMode={true}
|
||||||
presets={presets}
|
presets={presets}
|
||||||
hideInputs={customOnChange ? false : true}
|
|
||||||
value={customThemeColor ?? ""}
|
value={customThemeColor ?? ""}
|
||||||
onChange={(color: string) => {
|
onChange={(color: string) => {
|
||||||
if (customOnChange) {
|
if (customOnChange) {
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { fade } from 'svelte/transition';
|
||||||
|
import { animate } from 'motion';
|
||||||
|
|
||||||
|
let { onConfirm, onCancel, title, message } = $props<{
|
||||||
|
onConfirm: () => void;
|
||||||
|
onCancel: () => void;
|
||||||
|
title: string;
|
||||||
|
message: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
let modalElement: HTMLElement;
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (modalElement) {
|
||||||
|
animate(
|
||||||
|
modalElement,
|
||||||
|
{ scale: [0.9, 1], opacity: [0, 1] },
|
||||||
|
{
|
||||||
|
type: 'spring',
|
||||||
|
stiffness: 300,
|
||||||
|
damping: 25
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="flex fixed inset-0 z-[10000] justify-center items-center bg-black/50"
|
||||||
|
style="position: fixed; top: 0; left: 0; right: 0; bottom: 0;"
|
||||||
|
onclick={(e) => {
|
||||||
|
if (e.target === e.currentTarget) onCancel();
|
||||||
|
}}
|
||||||
|
onkeydown={(e) => {
|
||||||
|
if (e.key === 'Escape') onCancel();
|
||||||
|
}}
|
||||||
|
role="button"
|
||||||
|
tabindex="-1"
|
||||||
|
transition:fade={{ duration: 150 }}
|
||||||
|
>
|
||||||
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
|
<div
|
||||||
|
bind:this={modalElement}
|
||||||
|
class="p-4 mx-4 w-full max-w-md bg-white rounded-2xl shadow-2xl dark:bg-zinc-800"
|
||||||
|
onclick={(e) => e.stopPropagation()}
|
||||||
|
onkeydown={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<h2 class="mb-3 text-xl font-bold text-gray-900 dark:text-white">
|
||||||
|
{title}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div class="mb-6 text-lg text-gray-700 whitespace-pre-line dark:text-gray-300">
|
||||||
|
{message}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex gap-3 justify-end">
|
||||||
|
<button
|
||||||
|
onclick={onCancel}
|
||||||
|
class="px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 rounded-lg transition-colors hover:bg-gray-200 dark:bg-zinc-700 dark:text-gray-200 dark:hover:bg-zinc-600"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onclick={onConfirm}
|
||||||
|
class="px-4 py-2 text-sm font-medium text-white bg-green-600 rounded-lg shadow-inner transition-colors hover:bg-green-700 dark:bg-green-500 dark:hover:bg-green-600"
|
||||||
|
>
|
||||||
|
Enable
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
@@ -8,12 +8,12 @@
|
|||||||
let select: HTMLSelectElement;
|
let select: HTMLSelectElement;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="border dark:bg-[#38373D]/50 bg-[#DDDDDD]/50 border-[#DDDDDD]/30 dark:border-[#38373D]/30 shadow-2xl rounded-lg w-full overflow-clip">
|
<div class="border dark:bg-[#38373D]/50 bg-[#DDDDDD]/50 border-[#DDDDDD]/30 dark:border-[#38373D]/30 shadow-2xl rounded-xl w-full overflow-clip">
|
||||||
<select
|
<select
|
||||||
bind:this={select}
|
bind:this={select}
|
||||||
value={state}
|
value={state}
|
||||||
onchange={() => onChange(select.value)}
|
onchange={() => onChange(select.value)}
|
||||||
class="px-4 py-1 text-[0.75rem] dark:text-white w-full border-none bg-transparent focus:ring-0 focus:bg-white/20 dark:focus:bg-black/10"
|
class="px-4 py-2 pr-9 text-[0.875rem] font-medium text-black dark:text-white w-full border-none bg-white/80 dark:bg-zinc-800/70 hover:bg-white/90 dark:hover:bg-zinc-800/80 focus:bg-white/90 dark:focus:bg-zinc-800/80 focus:ring-0 rounded-md appearance-none transition-colors"
|
||||||
>
|
>
|
||||||
{#each options as option}
|
{#each options as option}
|
||||||
<option value={option.value}>
|
<option value={option.value}>
|
||||||
@@ -22,3 +22,19 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* Make native dropdown list readable on Windows */
|
||||||
|
select option {
|
||||||
|
background-color: #ffffff;
|
||||||
|
color: #111827; /* zinc-900 */
|
||||||
|
}
|
||||||
|
:global(.dark) select option {
|
||||||
|
background-color: #1f2937; /* zinc-800 */
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.dark) div::after {
|
||||||
|
color: rgba(255, 255, 255, 0.6);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -11,13 +11,16 @@
|
|||||||
|
|
||||||
import { closeExtensionPopup } from "@/seqta/utils/Closers/closeExtensionPopup";
|
import { closeExtensionPopup } from "@/seqta/utils/Closers/closeExtensionPopup";
|
||||||
import { OpenAboutPage } from "@/seqta/utils/Openers/OpenAboutPage";
|
import { OpenAboutPage } from "@/seqta/utils/Openers/OpenAboutPage";
|
||||||
import { OpenWhatsNewPopup } from "@/seqta/utils/Whatsnew";
|
import { OpenWhatsNewPopup } from "@/seqta/utils/Openers/OpenWhatsNewPopup";
|
||||||
import { OpenMinecraftServerPopup } from "@/seqta/utils/AboutMinecraftServer";
|
//import { OpenMinecraftServerPopup } from "@/seqta/utils/Openers/OpenMinecraftServerPopup";
|
||||||
|
|
||||||
import ColourPicker from "../components/ColourPicker.svelte";
|
import ColourPicker from "../components/ColourPicker.svelte";
|
||||||
|
import DisclaimerModal from "../components/DisclaimerModal.svelte";
|
||||||
import { settingsPopup } from "../hooks/SettingsPopup";
|
import { settingsPopup } from "../hooks/SettingsPopup";
|
||||||
|
|
||||||
let devModeSequence = "";
|
let devModeSequence = "";
|
||||||
|
let showDisclaimerModal = $state(false);
|
||||||
|
let disclaimerCallbacks = $state<{ onConfirm: () => void, onCancel: () => void } | null>(null);
|
||||||
|
|
||||||
const handleDevModeToggle = () => {
|
const handleDevModeToggle = () => {
|
||||||
const handleKeyDown = (event: KeyboardEvent) => {
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
@@ -50,14 +53,24 @@
|
|||||||
closeExtensionPopup();
|
closeExtensionPopup();
|
||||||
};
|
};
|
||||||
|
|
||||||
const openMinecraftServer = () => {
|
/* const openMinecraftServer = () => {
|
||||||
OpenMinecraftServerPopup();
|
OpenMinecraftServerPopup();
|
||||||
closeExtensionPopup();
|
closeExtensionPopup();
|
||||||
|
}; */
|
||||||
|
|
||||||
|
const openPrivacyStatement = () => {
|
||||||
|
window.open("https://betterseqta.org/privacy", "_blank");
|
||||||
|
closeExtensionPopup();
|
||||||
};
|
};
|
||||||
|
|
||||||
let { standalone } = $props<{ standalone?: boolean }>();
|
let { standalone } = $props<{ standalone?: boolean }>();
|
||||||
let showColourPicker = $state<boolean>(false);
|
let showColourPicker = $state<boolean>(false);
|
||||||
|
|
||||||
|
const showDisclaimer = (onConfirm: () => void, onCancel: () => void) => {
|
||||||
|
disclaimerCallbacks = { onConfirm, onCancel };
|
||||||
|
showDisclaimerModal = true;
|
||||||
|
};
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
settingsPopup.addListener(() => {
|
settingsPopup.addListener(() => {
|
||||||
showColourPicker = false;
|
showColourPicker = false;
|
||||||
@@ -101,23 +114,32 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{#if !standalone}
|
{#if !standalone}
|
||||||
|
<div class="flex absolute top-1 right-1 gap-1 items-center">
|
||||||
<button
|
<button
|
||||||
onclick={openAbout}
|
onclick={openAbout}
|
||||||
class="absolute top-1 right-[62px] w-8 h-8 text-lg rounded-xl font-IconFamily bg-zinc-100 dark:bg-zinc-700"
|
class="flex justify-center items-center w-8 h-8 text-lg rounded-xl font-IconFamily bg-zinc-100 dark:bg-zinc-700"
|
||||||
>
|
>
|
||||||
{"\ueb73"}
|
{"\ueb73"}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onclick={openChangelog}
|
onclick={openChangelog}
|
||||||
class="absolute top-1 right-10 w-8 h-8 text-lg rounded-xl font-IconFamily bg-zinc-100 dark:bg-zinc-700"
|
class="flex justify-center items-center w-8 h-8 text-lg rounded-xl font-IconFamily bg-zinc-100 dark:bg-zinc-700"
|
||||||
>
|
>
|
||||||
{"\ue929"}
|
{"\ue929"}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
onclick={openPrivacyStatement}
|
||||||
|
class="flex justify-center items-center w-8 h-8 text-lg rounded-xl font-IconFamily bg-zinc-100 dark:bg-zinc-700"
|
||||||
|
aria-label="Privacy Statement"
|
||||||
|
>
|
||||||
|
{"\uecba"}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- <button
|
||||||
onclick={openMinecraftServer}
|
onclick={openMinecraftServer}
|
||||||
class="absolute top-1 right-1 w-8 h-8 bg-zinc-100 dark:bg-zinc-700 rounded-xl p-1"
|
class="flex justify-center items-center p-1 w-8 h-8 rounded-xl bg-zinc-100 dark:bg-zinc-700"
|
||||||
aria-label="Open Minecraft Server"
|
aria-label="Open Minecraft Server"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
@@ -247,7 +269,8 @@
|
|||||||
transform="translate(18,10)"
|
transform="translate(18,10)"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button> -->
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -256,7 +279,7 @@
|
|||||||
{
|
{
|
||||||
title: "Settings",
|
title: "Settings",
|
||||||
Content: Settings,
|
Content: Settings,
|
||||||
props: { showColourPicker: openColourPicker },
|
props: { showColourPicker: openColourPicker, showDisclaimer },
|
||||||
},
|
},
|
||||||
{ title: "Shortcuts", Content: Shortcuts },
|
{ title: "Shortcuts", Content: Shortcuts },
|
||||||
{ title: "Themes", Content: Theme },
|
{ title: "Themes", Content: Theme },
|
||||||
@@ -272,3 +295,27 @@
|
|||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{#if showDisclaimerModal && disclaimerCallbacks}
|
||||||
|
<DisclaimerModal
|
||||||
|
title="Assessment Averages Disclaimer"
|
||||||
|
message="This feature calculates a simple average of your assessment grades. It does not take into account:
|
||||||
|
• Assessment weightings
|
||||||
|
• Different grading scales
|
||||||
|
• Other factors used in official reports
|
||||||
|
|
||||||
|
The displayed average may be inaccurate compared to your actual marks found in reports.
|
||||||
|
|
||||||
|
Do you want to enable this feature?"
|
||||||
|
onConfirm={() => {
|
||||||
|
disclaimerCallbacks?.onConfirm();
|
||||||
|
showDisclaimerModal = false;
|
||||||
|
disclaimerCallbacks = null;
|
||||||
|
}}
|
||||||
|
onCancel={() => {
|
||||||
|
disclaimerCallbacks?.onCancel();
|
||||||
|
showDisclaimerModal = false;
|
||||||
|
disclaimerCallbacks = null;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
import type { SettingsList } from "@/interface/types/SettingsProps"
|
import type { SettingsList } from "@/interface/types/SettingsProps"
|
||||||
import { settingsState } from "@/seqta/utils/listeners/SettingsState.ts"
|
import { settingsState } from "@/seqta/utils/listeners/SettingsState.ts"
|
||||||
import PickerSwatch from "@/interface/components/PickerSwatch.svelte"
|
import PickerSwatch from "@/interface/components/PickerSwatch.svelte"
|
||||||
|
import { showPrivacyNotification } from "@/seqta/utils/Openers/OpenPrivacyNotification"
|
||||||
|
import { closeExtensionPopup } from "@/seqta/utils/Closers/closeExtensionPopup"
|
||||||
|
|
||||||
import { getAllPluginSettings } from "@/plugins"
|
import { getAllPluginSettings } from "@/plugins"
|
||||||
import type { BooleanSetting, StringSetting, NumberSetting, SelectSetting, ButtonSetting, HotkeySetting, ComponentSetting } from "@/plugins/core/types"
|
import type { BooleanSetting, StringSetting, NumberSetting, SelectSetting, ButtonSetting, HotkeySetting, ComponentSetting } from "@/plugins/core/types"
|
||||||
@@ -90,7 +92,10 @@
|
|||||||
loadPluginSettings();
|
loadPluginSettings();
|
||||||
})
|
})
|
||||||
|
|
||||||
const { showColourPicker } = $props<{ showColourPicker: () => void }>();
|
const { showColourPicker, showDisclaimer } = $props<{
|
||||||
|
showColourPicker: () => void;
|
||||||
|
showDisclaimer: (onConfirm: () => void, onCancel: () => void) => void;
|
||||||
|
}>();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#snippet Setting({ title, description, Component, props }: SettingsList) }
|
{#snippet Setting({ title, description, Component, props }: SettingsList) }
|
||||||
@@ -186,15 +191,16 @@
|
|||||||
options: [
|
options: [
|
||||||
{ value: "australia", label: "Australia" },
|
{ value: "australia", label: "Australia" },
|
||||||
{ value: "usa", label: "USA" },
|
{ value: "usa", label: "USA" },
|
||||||
|
{ value: "uk", label: "UK" },
|
||||||
{ value: "taiwan", label: "Taiwan" },
|
{ value: "taiwan", label: "Taiwan" },
|
||||||
{ value: "hong_kong", label: "Hong Kong" },
|
{ value: "hong_kong", label: "Hong Kong" },
|
||||||
{ value: "panama", label: "Panama" },
|
{ value: "panama", label: "Panama" },
|
||||||
{ value: "canada", label: "Canada" },
|
{ value: "canada", label: "Canada" },
|
||||||
{ value: "singapore", label: "Singapore" },
|
{ value: "singapore", label: "Singapore" },
|
||||||
{ value: "uk", label: "UK" },
|
|
||||||
{ value: "japan", label: "Japan" },
|
{ value: "japan", label: "Japan" },
|
||||||
{ value: "netherlands", label: "Netherlands" }
|
{ value: "netherlands", label: "Netherlands" }
|
||||||
]
|
]
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
] as option}
|
] as option}
|
||||||
@@ -221,7 +227,20 @@
|
|||||||
<div>
|
<div>
|
||||||
<Switch
|
<Switch
|
||||||
state={pluginSettingsValues[plugin.pluginId]?.enabled ?? true}
|
state={pluginSettingsValues[plugin.pluginId]?.enabled ?? true}
|
||||||
onChange={(value) => updatePluginSetting(plugin.pluginId, 'enabled', value)}
|
onChange={async (value) => {
|
||||||
|
if (plugin.pluginId === 'assessments-average' && value === true) {
|
||||||
|
showDisclaimer(
|
||||||
|
async () => {
|
||||||
|
await updatePluginSetting(plugin.pluginId, 'enabled', true);
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
// Do nothing on cancel
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await updatePluginSetting(plugin.pluginId, 'enabled', value);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -339,6 +358,25 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex justify-between items-center px-4 py-3">
|
||||||
|
<div class="pr-4">
|
||||||
|
<h2 class="text-sm font-bold">Show Privacy Notification</h2>
|
||||||
|
<p class="text-xs">Show the privacy notification popup on next page load</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
onClick={async () => {
|
||||||
|
settingsState.privacyStatementShown = false;
|
||||||
|
settingsState.privacyStatementLastUpdated = undefined;
|
||||||
|
closeExtensionPopup();
|
||||||
|
// Small delay to ensure popup is closed before showing notification
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
await showPrivacyNotification();
|
||||||
|
}}
|
||||||
|
text="Show Now"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -24,12 +24,18 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
const switchChange = (shortcut: any) => {
|
const switchChange = (shortcut: any) => {
|
||||||
const value = $settingsState.shortcuts.find(s => s.name === shortcut);
|
const idx = $settingsState.shortcuts.findIndex(s => s.name === shortcut);
|
||||||
if (value) {
|
if (idx !== -1) {
|
||||||
value.enabled = !value.enabled;
|
// Create a new array with the toggled value to ensure reactivity
|
||||||
settingsState.shortcuts = settingsState.shortcuts;
|
const updated = settingsState.shortcuts.map(s =>
|
||||||
|
s.name === shortcut ? { ...s, enabled: !s.enabled } : s
|
||||||
|
);
|
||||||
|
settingsState.shortcuts = updated;
|
||||||
} else {
|
} else {
|
||||||
settingsState.shortcuts = [...settingsState.shortcuts, { name: shortcut, enabled: true }];
|
settingsState.shortcuts = [
|
||||||
|
...settingsState.shortcuts,
|
||||||
|
{ name: shortcut, enabled: true }
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,16 +202,6 @@
|
|||||||
</MotionDiv>
|
</MotionDiv>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#each Object.entries(Shortcuts) as shortcut}
|
|
||||||
<div class="flex justify-between items-center px-4 py-3">
|
|
||||||
<div class="pr-4">
|
|
||||||
<!-- Use DisplayName if it exists, otherwise use the key (shortcut[0]) as a fallback -->
|
|
||||||
<h2 class="text-sm">{shortcut[1].DisplayName || shortcut[0]}</h2>
|
|
||||||
</div>
|
|
||||||
<Switch state={$settingsState.shortcuts.find(s => s.name === shortcut[0])?.enabled ?? false} onChange={() => switchChange(shortcut[0])} />
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
|
|
||||||
<!-- Custom Shortcuts Section -->
|
<!-- Custom Shortcuts Section -->
|
||||||
{#each $settingsState.customshortcuts as shortcut, index}
|
{#each $settingsState.customshortcuts as shortcut, index}
|
||||||
<div class="flex justify-between items-center px-4 py-3">
|
<div class="flex justify-between items-center px-4 py-3">
|
||||||
@@ -217,6 +213,16 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
|
{#each Object.entries(Shortcuts) as shortcut}
|
||||||
|
<div class="flex justify-between items-center px-4 py-3">
|
||||||
|
<div class="pr-4">
|
||||||
|
<!-- Use DisplayName if it exists, otherwise use the key (shortcut[0]) as a fallback -->
|
||||||
|
<h2 class="text-sm">{shortcut[1].DisplayName || shortcut[0]}</h2>
|
||||||
|
</div>
|
||||||
|
<Switch state={$settingsState.shortcuts.find(s => s.name === shortcut[0])?.enabled ?? false} onChange={() => switchChange(shortcut[0])} />
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
{:else}
|
{:else}
|
||||||
<div class="p-4 text-center">
|
<div class="p-4 text-center">
|
||||||
Loading shortcuts...
|
Loading shortcuts...
|
||||||
|
|||||||
@@ -21,13 +21,16 @@
|
|||||||
<div class="relative w-full">
|
<div class="relative w-full">
|
||||||
<button
|
<button
|
||||||
onclick={() => editMode = !editMode}
|
onclick={() => editMode = !editMode}
|
||||||
class="absolute top-0 right-0 z-10 w-8 h-8 text-lg rounded-xl font-IconFamily bg-zinc-100 dark:bg-zinc-700">{editMode ? '\ue9e4' : '\uec38'}</button>
|
class="absolute top-0 right-0 z-10 px-2 h-8 text-lg rounded-xl bg-zinc-100 dark:bg-zinc-700">
|
||||||
|
<span class="mr-2">{editMode ? 'Done' : 'Edit'}</span>
|
||||||
|
<span class="font-IconFamily">{editMode ? '\ue9e4' : '\uec38'}</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
<BackgroundSelector isEditMode={editMode} bind:selectedBackground={selectedBackground} bind:selectNoBackground={selectNoBackground} />
|
<BackgroundSelector isEditMode={editMode} bind:selectedBackground={selectedBackground} bind:selectNoBackground={selectNoBackground} />
|
||||||
<ThemeSelector isEditMode={editMode} />
|
<ThemeSelector isEditMode={editMode} />
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex items-center justify-center w-full h-full">
|
<div class="flex justify-center items-center w-full h-full">
|
||||||
<div class="text-lg">
|
<div class="text-lg">
|
||||||
Open SEQTA and use the embedded settings to access theme settings. 🫠
|
Open SEQTA and use the embedded settings to access theme settings. 🫠
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ const updatedFirefoxManifest = {
|
|||||||
},
|
},
|
||||||
browser_specific_settings: {
|
browser_specific_settings: {
|
||||||
gecko: {
|
gecko: {
|
||||||
id: pkg.author.email,
|
id: "betterseqta@betterseqta.com",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import localforage from 'localforage'
|
||||||
|
import { onMount } from 'svelte'
|
||||||
|
|
||||||
|
let fileInput = $state<HTMLInputElement | undefined>(undefined)
|
||||||
|
let dragging = $state(false)
|
||||||
|
let filename = $state<string | undefined>(undefined)
|
||||||
|
let durationText = $state<string | undefined>(undefined)
|
||||||
|
|
||||||
|
const store = localforage.createInstance({
|
||||||
|
name: 'background-music-store',
|
||||||
|
storeName: 'music',
|
||||||
|
})
|
||||||
|
|
||||||
|
async function loadExisting() {
|
||||||
|
const name = await store.getItem<string>('audio-name')
|
||||||
|
filename = name ?? undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => { loadExisting() })
|
||||||
|
|
||||||
|
function triggerSelect() { fileInput?.click() }
|
||||||
|
|
||||||
|
async function handleFiles(files: FileList | null) {
|
||||||
|
const file = files?.[0]
|
||||||
|
if (!file) return
|
||||||
|
// Accept WAV and MP3 files
|
||||||
|
const isSupported = file.type === 'audio/wav' || file.type === 'audio/mpeg' ||
|
||||||
|
file.name.toLowerCase().endsWith('.wav') || file.name.toLowerCase().endsWith('.mp3')
|
||||||
|
if (!isSupported) {
|
||||||
|
alert('Please select a .wav or .mp3 audio file')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await store.setItem('audio-blob', file)
|
||||||
|
await store.setItem('audio-name', file.name)
|
||||||
|
filename = file.name
|
||||||
|
|
||||||
|
// Probe duration
|
||||||
|
try {
|
||||||
|
const url = URL.createObjectURL(file)
|
||||||
|
const audio = new Audio(url)
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
audio.onloadedmetadata = () => resolve()
|
||||||
|
audio.onerror = () => reject()
|
||||||
|
})
|
||||||
|
if (!isNaN(audio.duration) && audio.duration !== Infinity) {
|
||||||
|
const minutes = Math.floor(audio.duration / 60)
|
||||||
|
const seconds = Math.round(audio.duration % 60)
|
||||||
|
durationText = `${minutes}:${seconds.toString().padStart(2, '0')}`
|
||||||
|
} else {
|
||||||
|
durationText = undefined
|
||||||
|
}
|
||||||
|
URL.revokeObjectURL(url)
|
||||||
|
} catch {
|
||||||
|
durationText = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
window.dispatchEvent(new Event('betterseqta-background-music-updated'))
|
||||||
|
}
|
||||||
|
|
||||||
|
function onFileChange() { handleFiles(fileInput?.files || null) }
|
||||||
|
|
||||||
|
function onDrop(event: DragEvent) {
|
||||||
|
event.preventDefault()
|
||||||
|
dragging = false
|
||||||
|
handleFiles(event.dataTransfer?.files || null)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removeAudio() {
|
||||||
|
await store.removeItem('audio-blob')
|
||||||
|
await store.removeItem('audio-name')
|
||||||
|
filename = undefined
|
||||||
|
durationText = undefined
|
||||||
|
window.dispatchEvent(new Event('betterseqta-background-music-stop'))
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="relative cursor-pointer select-none"
|
||||||
|
onclick={() => triggerSelect()}
|
||||||
|
ondragover={(e) => { e.stopPropagation(); dragging = true }}
|
||||||
|
ondragleave={() => dragging = false}
|
||||||
|
ondrop={onDrop}
|
||||||
|
onkeydown={(e) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.preventDefault()
|
||||||
|
triggerSelect()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<div class="flex gap-3 items-center">
|
||||||
|
{#if filename}
|
||||||
|
<div class="flex items-center px-3 py-1 rounded-lg bg-zinc-200 dark:bg-zinc-800">
|
||||||
|
<div class="text-xs text-zinc-600 dark:text-zinc-300">
|
||||||
|
{filename}
|
||||||
|
<p>{durationText}</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="flex justify-center items-center m-1 text-lg dark:text-white size-7"
|
||||||
|
onclick={(e) => { e.stopPropagation(); removeAudio() }}
|
||||||
|
aria-label="Remove audio"
|
||||||
|
>×</button>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="flex gap-2 items-center px-3 py-1 text-xs rounded-lg border border-dashed transition border-zinc-300 dark:border-zinc-600 text-zinc-500 dark:text-zinc-400 hover:text-zinc-700 dark:hover:text-zinc-300 text-nowrap">
|
||||||
|
<span class="text-lg font-IconFamily">{'\ued47'}</span>
|
||||||
|
<span>Upload audio</span>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<input type="file" accept="audio/wav,audio/mpeg" class="hidden" bind:this={fileInput} onchange={onFileChange} />
|
||||||
|
{#if dragging}
|
||||||
|
<div class="absolute inset-0 rounded-lg bg-zinc-200/40 dark:bg-zinc-700/40"></div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,187 @@
|
|||||||
|
import type { Plugin } from "@/plugins/core/types";
|
||||||
|
import { componentSetting, defineSettings, numberSetting, booleanSetting } from "@/plugins/core/settingsHelpers";
|
||||||
|
import styles from "./styles.css?inline";
|
||||||
|
import BackgroundMusicSetting from "./BackgroundMusicSetting.svelte";
|
||||||
|
import localforage from "localforage";
|
||||||
|
|
||||||
|
const settings = defineSettings({
|
||||||
|
uploader: componentSetting({
|
||||||
|
title: "Background Music",
|
||||||
|
description: "Upload a .wav or .mp3 audio file to play in the background.",
|
||||||
|
component: BackgroundMusicSetting,
|
||||||
|
}),
|
||||||
|
volume: numberSetting({
|
||||||
|
title: "Volume",
|
||||||
|
description: "Set background music volume",
|
||||||
|
default: 0.5,
|
||||||
|
min: 0,
|
||||||
|
max: 1,
|
||||||
|
step: 0.05,
|
||||||
|
}),
|
||||||
|
pauseOnHidden: booleanSetting({
|
||||||
|
title: "Pause when tab hidden",
|
||||||
|
description: "Pause music when switching to another tab or minimizing the browser",
|
||||||
|
default: true,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const store = localforage.createInstance({
|
||||||
|
name: "background-music-store",
|
||||||
|
storeName: "music",
|
||||||
|
});
|
||||||
|
|
||||||
|
let currentAudio: HTMLAudioElement | null = null;
|
||||||
|
let currentObjectUrl: string | null = null;
|
||||||
|
let cleanupRegistered = false;
|
||||||
|
let pendingGestureCancel: (() => void) | null = null;
|
||||||
|
let visibilityResumeTimeout: number | null = null;
|
||||||
|
|
||||||
|
async function loadAudioBlob(): Promise<Blob | null> {
|
||||||
|
const blob = await store.getItem<Blob>("audio-blob");
|
||||||
|
return blob && blob instanceof Blob ? blob : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopAndCleanupAudio(): void {
|
||||||
|
if (currentAudio) {
|
||||||
|
currentAudio.pause();
|
||||||
|
currentAudio.src = "";
|
||||||
|
currentAudio.remove();
|
||||||
|
currentAudio = null;
|
||||||
|
}
|
||||||
|
if (currentObjectUrl) {
|
||||||
|
URL.revokeObjectURL(currentObjectUrl);
|
||||||
|
currentObjectUrl = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureGestureStart(handler: () => void): () => void {
|
||||||
|
const eventTypes = ["pointerdown", "keydown", "touchstart"]; // broad user gesture coverage
|
||||||
|
const listener = () => {
|
||||||
|
handler();
|
||||||
|
for (const type of eventTypes) {
|
||||||
|
window.removeEventListener(type, listener);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for (const type of eventTypes) {
|
||||||
|
window.addEventListener(type, listener, { once: true, passive: true });
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
for (const type of eventTypes) {
|
||||||
|
window.removeEventListener(type, listener);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startPlayback(volume: number): Promise<void> {
|
||||||
|
const blob = await loadAudioBlob();
|
||||||
|
if (!blob) return;
|
||||||
|
|
||||||
|
stopAndCleanupAudio();
|
||||||
|
|
||||||
|
currentObjectUrl = URL.createObjectURL(blob);
|
||||||
|
const audio = new Audio(currentObjectUrl);
|
||||||
|
audio.loop = true;
|
||||||
|
audio.volume = Math.max(0, Math.min(1, volume));
|
||||||
|
audio.preload = "auto";
|
||||||
|
audio.crossOrigin = "anonymous";
|
||||||
|
audio.style.display = "none";
|
||||||
|
document.body.appendChild(audio);
|
||||||
|
currentAudio = audio;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Attempt immediate play; may be blocked until gesture
|
||||||
|
await audio.play();
|
||||||
|
} catch {
|
||||||
|
// Ignore; will be started after gesture if enabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const backgroundMusicPlugin: Plugin<typeof settings> = {
|
||||||
|
id: "background-music",
|
||||||
|
name: "Background Music",
|
||||||
|
description: "Play your own music in the background while SEQTA is open.",
|
||||||
|
version: "1.0.0",
|
||||||
|
settings,
|
||||||
|
styles,
|
||||||
|
disableToggle: true,
|
||||||
|
defaultEnabled: false,
|
||||||
|
|
||||||
|
run: async (api) => {
|
||||||
|
await api.storage.loaded;
|
||||||
|
|
||||||
|
// react to specific setting changes
|
||||||
|
api.settings.onChange("volume" as any, (value: any) => {
|
||||||
|
const vol = (typeof value === "number" ? value : 0.5) as number;
|
||||||
|
if (currentAudio) currentAudio.volume = Math.max(0, Math.min(1, vol));
|
||||||
|
});
|
||||||
|
|
||||||
|
api.settings.onChange("pauseOnHidden" as any, (value: any) => {
|
||||||
|
const pauseOnHidden = (typeof value === "boolean" ? value : true) as boolean;
|
||||||
|
// If the setting is disabled and audio is currently paused due to tab being hidden, resume it
|
||||||
|
if (!pauseOnHidden && currentAudio && currentAudio.paused && document.visibilityState === "hidden") {
|
||||||
|
currentAudio.play().catch(() => {});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Note: Stop button/event removed by user; no stop handling needed
|
||||||
|
|
||||||
|
// Start if we have audio and autoplay is enabled
|
||||||
|
const tryStart = async () => {
|
||||||
|
const vol = (api.settings as any).volume ?? 0.5;
|
||||||
|
await startPlayback(vol);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Always arm gesture start and attempt immediate start
|
||||||
|
const cancel = ensureGestureStart(() => { tryStart(); });
|
||||||
|
cleanupRegistered = true;
|
||||||
|
(window as any).__betterseqta_bg_music_cancel__ = cancel;
|
||||||
|
tryStart();
|
||||||
|
|
||||||
|
// Pause on tab hide, resume on show with a small delay (if enabled)
|
||||||
|
const visHandler = () => {
|
||||||
|
if (!currentAudio) return;
|
||||||
|
const pauseOnHidden = (api.settings as any).pauseOnHidden ?? true;
|
||||||
|
if (!pauseOnHidden) return;
|
||||||
|
|
||||||
|
if (document.visibilityState === "hidden") {
|
||||||
|
if (visibilityResumeTimeout !== null) {
|
||||||
|
clearTimeout(visibilityResumeTimeout);
|
||||||
|
visibilityResumeTimeout = null;
|
||||||
|
}
|
||||||
|
currentAudio.pause();
|
||||||
|
} else if (document.visibilityState === "visible") {
|
||||||
|
if (visibilityResumeTimeout !== null) {
|
||||||
|
clearTimeout(visibilityResumeTimeout);
|
||||||
|
}
|
||||||
|
visibilityResumeTimeout = window.setTimeout(() => {
|
||||||
|
visibilityResumeTimeout = null;
|
||||||
|
currentAudio?.play().catch(() => {});
|
||||||
|
}, 200);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener("visibilitychange", visHandler);
|
||||||
|
|
||||||
|
// Allow uploads to trigger refresh
|
||||||
|
const uploadedHandler = () => {
|
||||||
|
const vol = (api.settings as any).volume ?? 0.5;
|
||||||
|
startPlayback(vol);
|
||||||
|
};
|
||||||
|
window.addEventListener("betterseqta-background-music-updated", uploadedHandler);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("visibilitychange", visHandler);
|
||||||
|
window.removeEventListener("betterseqta-background-music-updated", uploadedHandler);
|
||||||
|
if (cleanupRegistered && (window as any).__betterseqta_bg_music_cancel__) {
|
||||||
|
(window as any).__betterseqta_bg_music_cancel__();
|
||||||
|
(window as any).__betterseqta_bg_music_cancel__ = undefined;
|
||||||
|
}
|
||||||
|
if (pendingGestureCancel) { pendingGestureCancel(); pendingGestureCancel = null; }
|
||||||
|
if (visibilityResumeTimeout !== null) { clearTimeout(visibilityResumeTimeout); visibilityResumeTimeout = null; }
|
||||||
|
stopAndCleanupAudio();
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default backgroundMusicPlugin;
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
.background-music-hidden{display:none}
|
||||||
|
|
||||||
@@ -5,6 +5,8 @@ import {
|
|||||||
defineSettings,
|
defineSettings,
|
||||||
hotkeySetting,
|
hotkeySetting,
|
||||||
} from "../../core/settingsHelpers";
|
} from "../../core/settingsHelpers";
|
||||||
|
import styles from "./src/core/styles.css?inline";
|
||||||
|
|
||||||
// Platform-aware default hotkey
|
// Platform-aware default hotkey
|
||||||
const getDefaultHotkey = () => {
|
const getDefaultHotkey = () => {
|
||||||
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
|
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
|
||||||
@@ -82,6 +84,7 @@ export default defineLazyPlugin({
|
|||||||
disableToggle: true,
|
disableToggle: true,
|
||||||
defaultEnabled: false,
|
defaultEnabled: false,
|
||||||
beta: true,
|
beta: true,
|
||||||
|
styles: styles,
|
||||||
|
|
||||||
// Lazy loader - only imports the heavy plugin when actually needed
|
// Lazy loader - only imports the heavy plugin when actually needed
|
||||||
loader: () => import("./src/core/index")
|
loader: () => import("./src/core/index")
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ const notificationCollectorPlugin: Plugin<{}, NotificationCollectorStorage> = {
|
|||||||
"[class*='notifications__bubble___']",
|
"[class*='notifications__bubble___']",
|
||||||
) as HTMLElement;
|
) as HTMLElement;
|
||||||
|
|
||||||
if (api.storage.lastNotificationCount !== 0) {
|
if (alertDiv && api.storage.lastNotificationCount !== 0) {
|
||||||
alertDiv.textContent = api.storage.lastNotificationCount.toString();
|
alertDiv.textContent = api.storage.lastNotificationCount.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,13 +74,17 @@ const notificationCollectorPlugin: Plugin<{}, NotificationCollectorStorage> = {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[BetterSEQTA+] Error fetching notifications:", error);
|
console.error("[BetterSEQTA+] Error fetching notifications:", error);
|
||||||
api.storage.consecutiveErrors = (api.storage.consecutiveErrors || 0) + 1;
|
api.storage.consecutiveErrors =
|
||||||
|
(api.storage.consecutiveErrors || 0) + 1;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getNextInterval = () => {
|
const getNextInterval = () => {
|
||||||
// Exponential backoff on errors, max 5 minutes
|
// Exponential backoff on errors, max 5 minutes
|
||||||
const errorMultiplier = Math.min(Math.pow(2, api.storage.consecutiveErrors || 0), 10);
|
const errorMultiplier = Math.min(
|
||||||
|
Math.pow(2, api.storage.consecutiveErrors || 0),
|
||||||
|
10,
|
||||||
|
);
|
||||||
return Math.min(baseInterval * errorMultiplier, maxInterval);
|
return Math.min(baseInterval * errorMultiplier, maxInterval);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -92,7 +96,8 @@ const notificationCollectorPlugin: Plugin<{}, NotificationCollectorStorage> = {
|
|||||||
const interval = getNextInterval();
|
const interval = getNextInterval();
|
||||||
pollInterval = window.setTimeout(() => {
|
pollInterval = window.setTimeout(() => {
|
||||||
checkNotifications().then(() => {
|
checkNotifications().then(() => {
|
||||||
if (pollInterval) { // Only continue if not stopped
|
if (pollInterval) {
|
||||||
|
// Only continue if not stopped
|
||||||
scheduleNext();
|
scheduleNext();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -124,14 +129,16 @@ const notificationCollectorPlugin: Plugin<{}, NotificationCollectorStorage> = {
|
|||||||
isVisible = !document.hidden;
|
isVisible = !document.hidden;
|
||||||
if (isVisible && !pollInterval) {
|
if (isVisible && !pollInterval) {
|
||||||
// Resume polling when tab becomes visible
|
// Resume polling when tab becomes visible
|
||||||
const alertDiv = document.querySelector("[class*='notifications__bubble___']");
|
const alertDiv = document.querySelector(
|
||||||
|
"[class*='notifications__bubble___']",
|
||||||
|
);
|
||||||
if (alertDiv) {
|
if (alertDiv) {
|
||||||
startPolling();
|
startPolling();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener('visibilitychange', handleVisibilityChange);
|
document.addEventListener("visibilitychange", handleVisibilityChange);
|
||||||
|
|
||||||
api.seqta.onMount("[class*='notifications__bubble___']", (_) => {
|
api.seqta.onMount("[class*='notifications__bubble___']", (_) => {
|
||||||
startPolling();
|
startPolling();
|
||||||
@@ -139,7 +146,7 @@ const notificationCollectorPlugin: Plugin<{}, NotificationCollectorStorage> = {
|
|||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
stopPolling();
|
stopPolling();
|
||||||
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,12 +8,16 @@
|
|||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
z-index: 4;
|
z-index: 4;
|
||||||
box-shadow: 0 0 0 3px #000000;
|
box-shadow: 0 0 0 3px #000000;
|
||||||
|
transition: box-shadow 0.05s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .userInfoImg {
|
.dark .userInfoImg {
|
||||||
box-shadow: 0 0 0 3px #ffffff;
|
box-shadow: 0 0 0 3px #ffffff;
|
||||||
|
transition: box-shadow 0.05s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.userInfosvgdiv {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
filter: invert(0) !important;
|
.userInfoImg {
|
||||||
|
transition: none !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,7 @@ import animatedBackgroundPlugin from "./built-in/animatedBackground";
|
|||||||
import assessmentsAveragePlugin from "./built-in/assessmentsAverage";
|
import assessmentsAveragePlugin from "./built-in/assessmentsAverage";
|
||||||
import profilePicturePlugin from "./built-in/profilePicture";
|
import profilePicturePlugin from "./built-in/profilePicture";
|
||||||
import assessmentsOverviewPlugin from "./built-in/assessmentsOverview";
|
import assessmentsOverviewPlugin from "./built-in/assessmentsOverview";
|
||||||
|
import backgroundMusicPlugin from "./built-in/backgroundMusic";
|
||||||
//import testPlugin from './built-in/test';
|
//import testPlugin from './built-in/test';
|
||||||
|
|
||||||
// Heavy plugins (lazy-loaded only when enabled)
|
// Heavy plugins (lazy-loaded only when enabled)
|
||||||
@@ -24,6 +25,7 @@ pluginManager.registerPlugin(notificationCollectorPlugin);
|
|||||||
pluginManager.registerPlugin(timetablePlugin);
|
pluginManager.registerPlugin(timetablePlugin);
|
||||||
pluginManager.registerPlugin(profilePicturePlugin);
|
pluginManager.registerPlugin(profilePicturePlugin);
|
||||||
pluginManager.registerPlugin(assessmentsOverviewPlugin);
|
pluginManager.registerPlugin(assessmentsOverviewPlugin);
|
||||||
|
pluginManager.registerPlugin(backgroundMusicPlugin);
|
||||||
//pluginManager.registerPlugin(testPlugin);
|
//pluginManager.registerPlugin(testPlugin);
|
||||||
|
|
||||||
// Register heavy plugins with lazy loading
|
// Register heavy plugins with lazy loading
|
||||||
|
|||||||
@@ -23,12 +23,10 @@ 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/Openers/OpenWhatsNewPopup";
|
||||||
//import { OpenMinecraftServerPopup } from "@/seqta/utils/AboutMinecraftServer";
|
import { showPrivacyNotification } from "@/seqta/utils/Openers/OpenPrivacyNotification";
|
||||||
|
|
||||||
import {
|
import { updateTimetableTimes } from "@/seqta/utils/updateTimetableTimes";
|
||||||
updateTimetableTimes,
|
|
||||||
} from "@/seqta/utils/updateTimetableTimes";
|
|
||||||
|
|
||||||
// JSON content
|
// JSON content
|
||||||
import MenuitemSVGKey from "@/seqta/content/MenuItemSVGKey.json";
|
import MenuitemSVGKey from "@/seqta/content/MenuItemSVGKey.json";
|
||||||
@@ -96,7 +94,12 @@ export async function finishLoad() {
|
|||||||
console.error("Error during loading cleanup:", err);
|
console.error("Error during loading cleanup:", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settingsState.justupdated && !document.getElementById("whatsnewbk")) {
|
// Check and show privacy statement notification (before what's new)
|
||||||
|
if (!document.getElementById("privacy-notification")) {
|
||||||
|
await showPrivacyNotification();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settingsState.justupdated && !document.getElementById("whatsnewbk") && !document.getElementById("privacy-notification")) {
|
||||||
OpenWhatsNewPopup();
|
OpenWhatsNewPopup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -16,9 +16,9 @@ export async function main() {
|
|||||||
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
|
// Rather permanent 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;
|
||||||
|
|||||||
@@ -173,28 +173,35 @@ async function updateStudentInfo(students: any) {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
let houseelement1 = document.getElementsByClassName("userInfohouse")[0];
|
const houseelement = document.getElementsByClassName("userInfohouse")[0] as HTMLElement;
|
||||||
const houseelement = houseelement1 as HTMLElement;
|
|
||||||
|
// Fallback to N/A
|
||||||
|
let text = 'N/A';
|
||||||
|
const student = students[index] ?? {};
|
||||||
|
|
||||||
|
// If student has a house, prefer to show year + house. If no year, only show house.
|
||||||
|
if (student.house) {
|
||||||
|
text = `${student.year ?? ""}${student.house}`;
|
||||||
|
|
||||||
|
// If house_colour exists, compute colour
|
||||||
|
if (student.house_colour) {
|
||||||
|
houseelement.style.background = student.house_colour;
|
||||||
|
|
||||||
if (students[index]?.house) {
|
|
||||||
if (students[index]?.house_colour) {
|
|
||||||
houseelement.style.background = students[index].house_colour;
|
|
||||||
try {
|
try {
|
||||||
let colorresult = GetThresholdOfColor(students[index]?.house_colour);
|
const colorresult = GetThresholdOfColor(student.house_colour);
|
||||||
houseelement.style.color =
|
houseelement.style.color =
|
||||||
colorresult && colorresult > 300 ? "black" : "white";
|
colorresult && colorresult > 300 ? "black" : "white";
|
||||||
houseelement.innerText = students[index].year + students[index].house;
|
|
||||||
} catch (error) {
|
|
||||||
houseelement.innerText = students[index].house;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
houseelement.innerText = students[index].year;
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
houseelement.innerText = "N/A";
|
// Colour calculation failed, no text colour set
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (student.year) {
|
||||||
|
// No house, only year will be shown
|
||||||
|
text = student.year;
|
||||||
|
}
|
||||||
|
|
||||||
|
houseelement.innerText = text;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createNewsButton(fragment: DocumentFragment, menu: HTMLElement) {
|
function createNewsButton(fragment: DocumentFragment, menu: HTMLElement) {
|
||||||
|
|||||||
@@ -41,7 +41,11 @@ export function renderSettingsIfNeeded() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const shadow = extensionPopup.attachShadow({ mode: "open" });
|
const shadow = extensionPopup.attachShadow({ mode: "open" });
|
||||||
|
if ('requestIdleCallback' in window) {
|
||||||
requestIdleCallback(() => renderSvelte(Settings, shadow));
|
requestIdleCallback(() => renderSvelte(Settings, shadow));
|
||||||
|
} else {
|
||||||
|
renderSvelte(Settings, shadow);
|
||||||
|
}
|
||||||
isSettingsRendered = true;
|
isSettingsRendered = true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ export function addShortcuts(shortcuts: any) {
|
|||||||
|
|
||||||
function createNewShortcut(link: any, icon: any, viewBox: any, title: any) {
|
function createNewShortcut(link: any, icon: any, viewBox: any, title: any) {
|
||||||
// Creates the stucture and element information for each seperate shortcut
|
// Creates the stucture and element information for each seperate shortcut
|
||||||
|
const container = document.getElementById("shortcuts");
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
let shortcut = document.createElement("a");
|
let shortcut = document.createElement("a");
|
||||||
shortcut.setAttribute("href", link);
|
shortcut.setAttribute("href", link);
|
||||||
shortcut.setAttribute("target", "_blank");
|
shortcut.setAttribute("target", "_blank");
|
||||||
@@ -42,5 +45,5 @@ function createNewShortcut(link: any, icon: any, viewBox: any, title: any) {
|
|||||||
shortcutdiv.append(text);
|
shortcutdiv.append(text);
|
||||||
shortcut.append(shortcutdiv);
|
shortcut.append(shortcutdiv);
|
||||||
|
|
||||||
document.getElementById("shortcuts")!.appendChild(shortcut);
|
container.appendChild(shortcut);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ import stringToHTML from "../stringToHTML";
|
|||||||
|
|
||||||
export function CreateCustomShortcutDiv(element: any) {
|
export function CreateCustomShortcutDiv(element: any) {
|
||||||
// Creates the stucture and element information for each seperate shortcut
|
// Creates the stucture and element information for each seperate shortcut
|
||||||
|
const container = document.getElementById("shortcuts");
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
var shortcut = document.createElement("a");
|
var shortcut = document.createElement("a");
|
||||||
shortcut.setAttribute("href", element.url);
|
shortcut.setAttribute("href", element.url);
|
||||||
shortcut.setAttribute("target", "_blank");
|
shortcut.setAttribute("target", "_blank");
|
||||||
@@ -45,5 +48,5 @@ export function CreateCustomShortcutDiv(element: any) {
|
|||||||
shortcutdiv.append(text);
|
shortcutdiv.append(text);
|
||||||
shortcut.append(shortcutdiv);
|
shortcut.append(shortcutdiv);
|
||||||
|
|
||||||
document.getElementById("shortcuts")!.append(shortcut);
|
container.append(shortcut);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
import links from "@/seqta/content/links.json";
|
|
||||||
|
|
||||||
export function RemoveShortcutDiv(elements: any) {
|
|
||||||
if (elements.length === 0) return;
|
|
||||||
|
|
||||||
elements.forEach((element: any) => {
|
|
||||||
const shortcuts = document.querySelectorAll(".shortcut");
|
|
||||||
shortcuts.forEach((shortcut) => {
|
|
||||||
const anchorElement = shortcut.parentElement; // the <a> element is the parent
|
|
||||||
const textElement = shortcut.querySelector("p"); // <p> is a direct child of .shortcut
|
|
||||||
const title = textElement ? textElement.textContent : "";
|
|
||||||
|
|
||||||
const elementName = links[element.name as keyof typeof links]?.DisplayName || element.name;
|
|
||||||
|
|
||||||
let shouldRemove = title === elementName;
|
|
||||||
|
|
||||||
// Check href only if element.url exists
|
|
||||||
if (element.url) {
|
|
||||||
shouldRemove =
|
|
||||||
shouldRemove && anchorElement!.getAttribute("href") === element.url;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldRemove) {
|
|
||||||
anchorElement!.remove();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -4,12 +4,11 @@ import LogoLight from "@/resources/icons/betterseqta-light-icon.png";
|
|||||||
import assessmentsicon from "@/seqta/icons/assessmentsIcon";
|
import assessmentsicon from "@/seqta/icons/assessmentsIcon";
|
||||||
import coursesicon from "@/seqta/icons/coursesIcon";
|
import coursesicon from "@/seqta/icons/coursesIcon";
|
||||||
import { GetThresholdOfColor } from "@/seqta/ui/colors/getThresholdColour";
|
import { GetThresholdOfColor } from "@/seqta/ui/colors/getThresholdColour";
|
||||||
import { addShortcuts } from "../Adders/AddShortcuts";
|
|
||||||
import { convertTo12HourFormat } from "../convertTo12HourFormat";
|
import { convertTo12HourFormat } from "../convertTo12HourFormat";
|
||||||
import { delay } from "../delay";
|
import { delay } from "../delay";
|
||||||
import { settingsState } from "../listeners/SettingsState";
|
import { settingsState } from "../listeners/SettingsState";
|
||||||
import stringToHTML from "../stringToHTML";
|
import stringToHTML from "../stringToHTML";
|
||||||
import { CreateCustomShortcutDiv } from "@/seqta/utils/CreateEnable/CreateCustomShortcutDiv";
|
import { renderShortcuts } from "@/seqta/utils/Render/renderShortcuts";
|
||||||
import { CreateElement } from "@/seqta/utils/CreateEnable/CreateElement";
|
import { CreateElement } from "@/seqta/utils/CreateEnable/CreateElement";
|
||||||
import { FilterUpcomingAssessments } from "@/seqta/utils/FilterUpcomingAssessments";
|
import { FilterUpcomingAssessments } from "@/seqta/utils/FilterUpcomingAssessments";
|
||||||
import { getMockNotices } from "@/seqta/ui/dev/hideSensitiveContent";
|
import { getMockNotices } from "@/seqta/ui/dev/hideSensitiveContent";
|
||||||
@@ -100,12 +99,7 @@ export async function loadHomePage() {
|
|||||||
|
|
||||||
const cleanup = setupTimetableListeners();
|
const cleanup = setupTimetableListeners();
|
||||||
|
|
||||||
try {
|
renderShortcuts();
|
||||||
addShortcuts(settingsState.shortcuts);
|
|
||||||
} catch (err: any) {
|
|
||||||
console.error("[BetterSEQTA+] Error adding shortcuts:", err.message || err);
|
|
||||||
}
|
|
||||||
AddCustomShortcutsToPage();
|
|
||||||
|
|
||||||
const date = new Date();
|
const date = new Date();
|
||||||
const TodayFormatted = formatDate(date);
|
const TodayFormatted = formatDate(date);
|
||||||
@@ -367,15 +361,6 @@ function comparedate(obj1: any, obj2: any) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function AddCustomShortcutsToPage() {
|
|
||||||
let customshortcuts: any = settingsState.customshortcuts;
|
|
||||||
if (customshortcuts.length > 0) {
|
|
||||||
for (let i = 0; i < customshortcuts.length; i++) {
|
|
||||||
const element = customshortcuts[i];
|
|
||||||
CreateCustomShortcutDiv(element);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function processNotices(response: any, labelArray: string[]) {
|
function processNotices(response: any, labelArray: string[]) {
|
||||||
const NoticeContainer = document.getElementById("notice-container");
|
const NoticeContainer = document.getElementById("notice-container");
|
||||||
@@ -1117,6 +1102,14 @@ async function CreateUpcomingSection(assessments: any, activeSubjects: any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
FilterUpcomingAssessments(settingsState.subjectfilters);
|
FilterUpcomingAssessments(settingsState.subjectfilters);
|
||||||
|
|
||||||
|
if (assessments.length === 0) {
|
||||||
|
upcomingitemcontainer!.innerHTML = `
|
||||||
|
<div class="day-empty">
|
||||||
|
<img src="${browser.runtime.getURL(LogoLight)}" />
|
||||||
|
<p>No assessments available.</p>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createAssessmentDateDiv(date: string, value: any, datecase?: any) {
|
function createAssessmentDateDiv(date: string, value: any, datecase?: any) {
|
||||||
|
|||||||
@@ -1,25 +1,17 @@
|
|||||||
import stringToHTML from "../stringToHTML";
|
import stringToHTML from "../stringToHTML";
|
||||||
import { settingsState } from "../listeners/SettingsState";
|
import { settingsState } from "../listeners/SettingsState";
|
||||||
import { animate, stagger } from "motion";
|
import { openPopup } from "./PopupManager";
|
||||||
import { DeleteWhatsNew } from "../Whatsnew";
|
|
||||||
|
|
||||||
export function OpenAboutPage() {
|
export function OpenAboutPage() {
|
||||||
const background = document.createElement("div");
|
const header = stringToHTML(
|
||||||
background.id = "whatsnewbk";
|
|
||||||
background.classList.add("whatsnewBackground");
|
|
||||||
|
|
||||||
const container = document.createElement("div");
|
|
||||||
container.classList.add("whatsnewContainer");
|
|
||||||
|
|
||||||
var header: any = stringToHTML(
|
|
||||||
/* html */
|
/* html */
|
||||||
`<div class="whatsnewHeader">
|
`<div class="whatsnewHeader">
|
||||||
<h1>About</h1>
|
<h1>About</h1>
|
||||||
<p>About the extension</p>
|
<p>About the extension</p>
|
||||||
</div>`,
|
</div>`,
|
||||||
).firstChild;
|
).firstChild as HTMLElement;
|
||||||
|
|
||||||
let text = stringToHTML(/* html */ `
|
const text = stringToHTML(/* html */ `
|
||||||
<div class="whatsnewTextContainer" style="overflow-y: hidden;">
|
<div class="whatsnewTextContainer" style="overflow-y: hidden;">
|
||||||
<img src="${settingsState.DarkMode ? "https://raw.githubusercontent.com/BetterSEQTA/BetterSEQTA-Plus/main/src/resources/branding/dark.jpg" : "https://raw.githubusercontent.com/BetterSEQTA/BetterSEQTA-Plus/main/src/resources/branding/light.jpg"}" class="aboutImg" />
|
<img src="${settingsState.DarkMode ? "https://raw.githubusercontent.com/BetterSEQTA/BetterSEQTA-Plus/main/src/resources/branding/dark.jpg" : "https://raw.githubusercontent.com/BetterSEQTA/BetterSEQTA-Plus/main/src/resources/branding/light.jpg"}" class="aboutImg" />
|
||||||
<p>BetterSEQTA+ is a fork of BetterSEQTA (originally developed by Nulkem), which was discontinued. BetterSEQTA+ continued development of BetterSEQTA, while incorporating a plethora of features. </p>
|
<p>BetterSEQTA+ is a fork of BetterSEQTA (originally developed by Nulkem), which was discontinued. BetterSEQTA+ continued development of BetterSEQTA, while incorporating a plethora of features. </p>
|
||||||
@@ -37,9 +29,9 @@ export function OpenAboutPage() {
|
|||||||
style="width: 100%; max-width: 500px; height: auto; object-fit: contain; display: block; margin: -110px auto 0;">
|
style="width: 100%; max-width: 500px; height: auto; object-fit: contain; display: block; margin: -110px auto 0;">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`).firstChild;
|
`).firstChild as HTMLElement;
|
||||||
|
|
||||||
let footer = stringToHTML(/* html */ `
|
const footer = stringToHTML(/* html */ `
|
||||||
<div class="whatsnewFooter">
|
<div class="whatsnewFooter">
|
||||||
<div>
|
<div>
|
||||||
Resources and Feedback:
|
Resources and Feedback:
|
||||||
@@ -67,56 +59,10 @@ export function OpenAboutPage() {
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`).firstChild;
|
`).firstChild as HTMLElement;
|
||||||
|
|
||||||
let exitbutton = document.createElement("div");
|
openPopup({
|
||||||
exitbutton.id = "whatsnewclosebutton";
|
header,
|
||||||
|
content: [text, footer],
|
||||||
container.append(header);
|
|
||||||
container.append(text as ChildNode);
|
|
||||||
container.append(footer as ChildNode);
|
|
||||||
container.append(exitbutton);
|
|
||||||
|
|
||||||
background.append(container);
|
|
||||||
|
|
||||||
document.getElementById("container")!.append(background);
|
|
||||||
|
|
||||||
let bkelement = document.getElementById("whatsnewbk");
|
|
||||||
let popup = document.getElementsByClassName("whatsnewContainer")[0];
|
|
||||||
|
|
||||||
if (settingsState.animations) {
|
|
||||||
animate(
|
|
||||||
[popup, bkelement as HTMLElement],
|
|
||||||
{ scale: [0, 1] },
|
|
||||||
{
|
|
||||||
type: "spring",
|
|
||||||
stiffness: 220,
|
|
||||||
damping: 18,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
animate(
|
|
||||||
".whatsnewTextContainer *",
|
|
||||||
{ opacity: [0, 1], y: [10, 0] },
|
|
||||||
{
|
|
||||||
delay: stagger(0.05, { startDelay: 0.1 }),
|
|
||||||
duration: 0.5,
|
|
||||||
ease: [0.22, 0.03, 0.26, 1],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
delete settingsState.justupdated;
|
|
||||||
|
|
||||||
bkelement!.addEventListener("click", function (event) {
|
|
||||||
// Check if the click event originated from the element itself and not any of its children
|
|
||||||
if (event.target === bkelement) {
|
|
||||||
DeleteWhatsNew();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var closeelement = document.getElementById("whatsnewclosebutton");
|
|
||||||
closeelement!.addEventListener("click", function () {
|
|
||||||
DeleteWhatsNew();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-98
@@ -1,24 +1,5 @@
|
|||||||
import { settingsState } from "./listeners/SettingsState";
|
import stringToHTML from "../stringToHTML";
|
||||||
import { animate, stagger } from "motion";
|
import { openPopup } from "./PopupManager";
|
||||||
import stringToHTML from "./stringToHTML";
|
|
||||||
|
|
||||||
export async function DeleteWhatsNew() {
|
|
||||||
const bkelement = document.getElementById("whatsnewbk");
|
|
||||||
const popup = document.querySelector(".whatsnewContainer") as HTMLElement;
|
|
||||||
|
|
||||||
if (!settingsState.animations) {
|
|
||||||
bkelement?.remove();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
animate(
|
|
||||||
[popup, bkelement!],
|
|
||||||
{ opacity: [1, 0], scale: [1, 0] },
|
|
||||||
{ ease: [0.22, 0.03, 0.26, 1] },
|
|
||||||
).then(() => {
|
|
||||||
bkelement?.remove();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function OpenMinecraftServerPopup() {
|
export function OpenMinecraftServerPopup() {
|
||||||
if (!document.querySelector('link[href*="minecraftia"]')) {
|
if (!document.querySelector('link[href*="minecraftia"]')) {
|
||||||
@@ -28,45 +9,36 @@ export function OpenMinecraftServerPopup() {
|
|||||||
document.head.appendChild(fontLink);
|
document.head.appendChild(fontLink);
|
||||||
}
|
}
|
||||||
|
|
||||||
const background = document.createElement("div");
|
const header = stringToHTML(
|
||||||
background.id = "whatsnewbk";
|
|
||||||
background.classList.add("whatsnewBackground");
|
|
||||||
|
|
||||||
const container = document.createElement("div");
|
|
||||||
container.classList.add("whatsnewContainer");
|
|
||||||
|
|
||||||
var header: any = stringToHTML(
|
|
||||||
/* html */
|
/* html */
|
||||||
`<div class="whatsnewHeader">
|
`<div class="whatsnewHeader">
|
||||||
<h1>Minecraft Server</h1>
|
<h1>Minecraft Server</h1>
|
||||||
<p>The official BetterSEQTA+ Minecraft Server</p>
|
<p>The official BetterSEQTA+ Minecraft Server</p>
|
||||||
</div>`,
|
</div>`,
|
||||||
).firstChild;
|
).firstChild as HTMLElement;
|
||||||
|
|
||||||
let imagecont = document.createElement("div");
|
const imageContainer = document.createElement("div");
|
||||||
imagecont.classList.add("whatsnewImgContainer");
|
imageContainer.classList.add("whatsnewImgContainer");
|
||||||
|
|
||||||
let video = document.createElement("video");
|
const video = document.createElement("video");
|
||||||
video.style.aspectRatio = "16/9";
|
video.style.aspectRatio = "16/9";
|
||||||
video.style.background = "black";
|
video.style.background = "black";
|
||||||
let source = document.createElement("source");
|
|
||||||
|
|
||||||
|
const source = document.createElement("source");
|
||||||
source.setAttribute(
|
source.setAttribute(
|
||||||
"src",
|
"src",
|
||||||
"https://raw.githubusercontent.com/BetterSEQTA/BetterSEQTA-Plus/main/src/resources/server-video.mp4",
|
"https://raw.githubusercontent.com/BetterSEQTA/BetterSEQTA-Plus/main/src/resources/server-video.mp4",
|
||||||
);
|
);
|
||||||
|
|
||||||
video.autoplay = true;
|
video.autoplay = true;
|
||||||
video.muted = true;
|
video.muted = true;
|
||||||
video.loop = true;
|
video.loop = true;
|
||||||
video.appendChild(source);
|
video.appendChild(source);
|
||||||
video.classList.add("whatsnewImg");
|
video.classList.add("whatsnewImg");
|
||||||
imagecont.appendChild(video);
|
imageContainer.appendChild(video);
|
||||||
|
|
||||||
let textcontainer = document.createElement("div");
|
const text = stringToHTML(/* html */ `
|
||||||
textcontainer.classList.add("whatsnewTextContainer");
|
<div class="whatsnewTextContainer" style="height: 50%; overflow-y: hidden;">
|
||||||
|
|
||||||
let text = stringToHTML(/* html */ `
|
|
||||||
<div class="whatsnewTextContainer" style="height: 50%; overflow-y: scroll;">
|
|
||||||
<h1>Join our community in Minecraft!</h1>
|
<h1>Join our community in Minecraft!</h1>
|
||||||
<p style="margin-left: 0;">Join the official BetterSEQTA+ Minecraft Server community now!</p>
|
<p style="margin-left: 0;">Join the official BetterSEQTA+ Minecraft Server community now!</p>
|
||||||
|
|
||||||
@@ -92,8 +64,7 @@ export function OpenMinecraftServerPopup() {
|
|||||||
-1px -1px 0 #000,
|
-1px -1px 0 #000,
|
||||||
1px -1px 0 #000,
|
1px -1px 0 #000,
|
||||||
-1px 1px 0 #000,
|
-1px 1px 0 #000,
|
||||||
1px 1px 0 #000;
|
1px 1px 0 #000;">
|
||||||
">
|
|
||||||
mc.betterseqta.org
|
mc.betterseqta.org
|
||||||
</p>
|
</p>
|
||||||
<p style="
|
<p style="
|
||||||
@@ -107,14 +78,13 @@ export function OpenMinecraftServerPopup() {
|
|||||||
-1px -1px 0 #000,
|
-1px -1px 0 #000,
|
||||||
1px -1px 0 #000,
|
1px -1px 0 #000,
|
||||||
-1px 1px 0 #000,
|
-1px 1px 0 #000,
|
||||||
1px 1px 0 #000;
|
1px 1px 0 #000;">
|
||||||
">
|
|
||||||
Version: 1.21.4
|
Version: 1.21.4
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
`).firstChild;
|
`).firstChild as HTMLElement;
|
||||||
|
|
||||||
let footer = stringToHTML(/* html */ `
|
const footer = stringToHTML(/* html */ `
|
||||||
<div class="whatsnewFooter">
|
<div class="whatsnewFooter">
|
||||||
<div>
|
<div>
|
||||||
Resources and Feedback:
|
Resources and Feedback:
|
||||||
@@ -144,59 +114,10 @@ export function OpenMinecraftServerPopup() {
|
|||||||
<div>
|
<div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`).firstChild;
|
`).firstChild as HTMLElement;
|
||||||
|
|
||||||
let exitbutton = document.createElement("div");
|
openPopup({
|
||||||
exitbutton.id = "whatsnewclosebutton";
|
|
||||||
|
|
||||||
container.append(
|
|
||||||
header,
|
header,
|
||||||
imagecont,
|
content: [imageContainer, text, footer],
|
||||||
text as HTMLElement,
|
|
||||||
footer as HTMLElement,
|
|
||||||
exitbutton,
|
|
||||||
);
|
|
||||||
|
|
||||||
background.append(container);
|
|
||||||
|
|
||||||
document.getElementById("container")!.append(background);
|
|
||||||
|
|
||||||
let bkelement = document.getElementById("whatsnewbk");
|
|
||||||
let popup = document.getElementsByClassName("whatsnewContainer")[0];
|
|
||||||
|
|
||||||
if (settingsState.animations) {
|
|
||||||
animate(
|
|
||||||
[popup, bkelement as HTMLElement],
|
|
||||||
{ scale: [0, 1] },
|
|
||||||
{
|
|
||||||
type: "spring",
|
|
||||||
stiffness: 220,
|
|
||||||
damping: 18,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
animate(
|
|
||||||
".whatsnewTextContainer *",
|
|
||||||
{ opacity: [0, 1], y: [10, 0] },
|
|
||||||
{
|
|
||||||
delay: stagger(0.05, { startDelay: 0.1 }),
|
|
||||||
duration: 0.5,
|
|
||||||
ease: [0.22, 0.03, 0.26, 1],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
delete settingsState.justupdated;
|
|
||||||
|
|
||||||
bkelement!.addEventListener("click", function (event) {
|
|
||||||
// Check if the click event originated from the element itself and not any of its children
|
|
||||||
if (event.target === bkelement) {
|
|
||||||
DeleteWhatsNew();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var closeelement = document.getElementById("whatsnewclosebutton");
|
|
||||||
closeelement!.addEventListener("click", function () {
|
|
||||||
DeleteWhatsNew();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import stringToHTML from "../stringToHTML";
|
||||||
|
import { settingsState } from "../listeners/SettingsState";
|
||||||
|
import { openPopup } from "./PopupManager";
|
||||||
|
|
||||||
|
export function showPrivacyNotification() {
|
||||||
|
const lastUpdated = "2025-12-19";
|
||||||
|
|
||||||
|
if (document.getElementById("whatsnewbk")) return;
|
||||||
|
if (settingsState.privacyStatementShown) return;
|
||||||
|
if (settingsState.privacyStatementLastUpdated && new Date(settingsState.privacyStatementLastUpdated) > new Date(lastUpdated)) return;
|
||||||
|
|
||||||
|
const header = stringToHTML(
|
||||||
|
/* html */
|
||||||
|
`<div class="whatsnewHeader">
|
||||||
|
<h1>Privacy Statement</h1>
|
||||||
|
<p>Important Information</p>
|
||||||
|
</div>`,
|
||||||
|
).firstChild as HTMLElement;
|
||||||
|
|
||||||
|
const text = stringToHTML(/* html */ `
|
||||||
|
<div class="whatsnewTextContainer privacyStatement" style="overflow-y: auto; font-size: 1.2rem; line-height: 1.6;">
|
||||||
|
<img style="aspect-ratio: 16/5.8;" src="${settingsState.DarkMode ? "https://raw.githubusercontent.com/BetterSEQTA/BetterSEQTA-Plus/main/src/resources/branding/dark.jpg" : "https://raw.githubusercontent.com/BetterSEQTA/BetterSEQTA-Plus/main/src/resources/branding/light.jpg"}" class="aboutImg" />
|
||||||
|
<p>
|
||||||
|
<strong>Addressing Recent Concerns About BetterSEQTA+</strong><br>
|
||||||
|
We appreciate the feedback we've received from several schools regarding BetterSEQTA+. Transparency and trust are core to our mission, and we want to address these concerns directly.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Our Commitment to Privacy:</strong><br>
|
||||||
|
<span style="display: block; margin-left: 1em;">
|
||||||
|
• We do not collect, store, or share any personal information<br>
|
||||||
|
• All data processing happens locally on your device<br>
|
||||||
|
• Our code is open source and available for review
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>What We're Doing:</strong><br>
|
||||||
|
We're willing to actively work with school administrators to ensure BetterSEQTA+ meets both student needs and institutional requirements. If your school has specific concerns, we encourage them to contact us at <a href="mailto:betterseqta.plus@gmail.com" style="color: inherit; text-decoration: underline;">betterseqta.plus@gmail.com</a> or via github at <a href="https://github.com/BetterSEQTA/BetterSEQTA-Plus" target="_blank" rel="noopener noreferrer" style="color: inherit; text-decoration: underline;">github.com/BetterSEQTA/BetterSEQTA-Plus</a>.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
For complete details about our privacy practices, visit our <a href="https://betterseqta.org/privacy" target="_blank" rel="noopener noreferrer" style="color: inherit; text-decoration: underline;">privacy policy</a> or click the shield icon in settings.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
`).firstChild as HTMLElement;
|
||||||
|
|
||||||
|
settingsState.privacyStatementLastUpdated = "2025-12-20";
|
||||||
|
settingsState.privacyStatementShown = true;
|
||||||
|
|
||||||
|
openPopup({
|
||||||
|
header,
|
||||||
|
content: [text],
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import stringToHTML from "../stringToHTML";
|
||||||
|
import { openPopup } from "./PopupManager";
|
||||||
|
|
||||||
|
export function OpenPrivacyStatement() {
|
||||||
|
const header = stringToHTML(
|
||||||
|
/* html */
|
||||||
|
`<div class="whatsnewHeader">
|
||||||
|
<h1>Privacy Statement</h1>
|
||||||
|
<p>Our commitment to your privacy</p>
|
||||||
|
</div>`,
|
||||||
|
).firstChild as HTMLElement;
|
||||||
|
|
||||||
|
const text = stringToHTML(/* html */ `
|
||||||
|
<div class="whatsnewTextContainer" style="overflow-y: auto; max-height: 60vh;">
|
||||||
|
<h2 style="margin-top: 0;">Privacy Policy</h2>
|
||||||
|
<p>At BetterSEQTA+, we take your privacy seriously. We want to be completely transparent about how we handle your data.</p>
|
||||||
|
|
||||||
|
<h3>Data Collection</h3>
|
||||||
|
<p><strong>We never collect any information from you.</strong> BetterSEQTA+ is designed to work entirely on your device. All processing happens locally in your browser, and we do not send any data to external servers.</p>
|
||||||
|
|
||||||
|
<h3>What We Don't Do</h3>
|
||||||
|
<ul style="text-align: left; margin: 10px 0;">
|
||||||
|
<li>We do not track your browsing activity</li>
|
||||||
|
<li>We do not collect personal information</li>
|
||||||
|
<li>We do not store your SEQTA credentials</li>
|
||||||
|
<li>We do not send data to third-party services</li>
|
||||||
|
<li>We do not use analytics or tracking cookies</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Local Storage</h3>
|
||||||
|
<p>BetterSEQTA+ uses your browser's local storage to save your preferences and settings. This data remains on your device and is never transmitted anywhere. You can clear this data at any time through your browser's settings.</p>
|
||||||
|
|
||||||
|
<h3>Open Source</h3>
|
||||||
|
<p>BetterSEQTA+ is an open-source project. You can review our code on <a href="https://github.com/BetterSEQTA/BetterSEQTA-Plus" target="_blank" style="color: inherit; text-decoration: underline;">GitHub</a> to verify our privacy practices. We believe in transparency and encourage you to inspect the code yourself.</p>
|
||||||
|
|
||||||
|
<h3>Our Commitment</h3>
|
||||||
|
<p>We are committed to providing the best features possible while respecting your privacy. We understand that schools and students have concerns about data privacy, and we want to assure you that BetterSEQTA+ is designed with privacy as a core principle.</p>
|
||||||
|
|
||||||
|
<p style="margin-top: 20px; font-weight: bold;">If you have any questions or concerns about our privacy practices, please reach out to us through our <a href="https://github.com/BetterSEQTA/BetterSEQTA-Plus" target="_blank" style="color: inherit; text-decoration: underline;">GitHub repository</a>.</p>
|
||||||
|
</div>
|
||||||
|
`).firstChild as HTMLElement;
|
||||||
|
|
||||||
|
openPopup({
|
||||||
|
header,
|
||||||
|
content: [text],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,48 +1,22 @@
|
|||||||
import { settingsState } from "./listeners/SettingsState";
|
import stringToHTML from "../stringToHTML";
|
||||||
import { animate, stagger } from "motion";
|
|
||||||
import stringToHTML from "./stringToHTML";
|
|
||||||
import browser from "webextension-polyfill";
|
import browser from "webextension-polyfill";
|
||||||
import kofi from "@/resources/kofi.png?base64";
|
import kofi from "@/resources/kofi.png?base64";
|
||||||
|
import { openPopup } from "./PopupManager";
|
||||||
export async function DeleteWhatsNew() {
|
|
||||||
const bkelement = document.getElementById("whatsnewbk");
|
|
||||||
const popup = document.getElementsByClassName("whatsnewContainer")[0];
|
|
||||||
|
|
||||||
if (!settingsState.animations) {
|
|
||||||
bkelement?.remove();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
animate(
|
|
||||||
[popup, bkelement!],
|
|
||||||
{ opacity: [1, 0], scale: [1, 0] },
|
|
||||||
{ ease: [0.22, 0.03, 0.26, 1] },
|
|
||||||
).then(() => {
|
|
||||||
bkelement?.remove();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function OpenWhatsNewPopup() {
|
export function OpenWhatsNewPopup() {
|
||||||
const background = document.createElement("div");
|
const header = stringToHTML(
|
||||||
background.id = "whatsnewbk";
|
|
||||||
background.classList.add("whatsnewBackground");
|
|
||||||
|
|
||||||
const container = document.createElement("div");
|
|
||||||
container.classList.add("whatsnewContainer");
|
|
||||||
|
|
||||||
var header: any = stringToHTML(
|
|
||||||
/* html */
|
/* html */
|
||||||
`<div class="whatsnewHeader">
|
`<div class="whatsnewHeader">
|
||||||
<h1>What's New</h1>
|
<h1>What's New</h1>
|
||||||
<p>BetterSEQTA+ V${browser.runtime.getManifest().version}</p>
|
<p>BetterSEQTA+ V${browser.runtime.getManifest().version}</p>
|
||||||
</div>`,
|
</div>`,
|
||||||
).firstChild;
|
).firstChild as HTMLElement;
|
||||||
|
|
||||||
let imagecont = document.createElement("div");
|
const imageContainer = document.createElement("div");
|
||||||
imagecont.classList.add("whatsnewImgContainer");
|
imageContainer.classList.add("whatsnewImgContainer");
|
||||||
|
|
||||||
let video = document.createElement("video");
|
const video = document.createElement("video");
|
||||||
let source = document.createElement("source");
|
const source = document.createElement("source");
|
||||||
|
|
||||||
source.setAttribute(
|
source.setAttribute(
|
||||||
"src",
|
"src",
|
||||||
@@ -53,19 +27,41 @@ export function OpenWhatsNewPopup() {
|
|||||||
video.loop = true;
|
video.loop = true;
|
||||||
video.appendChild(source);
|
video.appendChild(source);
|
||||||
video.classList.add("whatsnewImg");
|
video.classList.add("whatsnewImg");
|
||||||
imagecont.appendChild(video);
|
imageContainer.appendChild(video);
|
||||||
|
|
||||||
/* let whatsnewimg = document.createElement("img");
|
const text = stringToHTML(/* html */ `
|
||||||
//whatsnewimg.src = "https://raw.githubusercontent.com/BetterSEQTA/BetterSEQTA-Plus/main/src/resources/update-image.webp";
|
<div class="whatsnewTextContainer" style="height: 50%;overflow-y: auto;">
|
||||||
whatsnewimg.src = browser.runtime.getURL('../../resources/update-image.webp');
|
|
||||||
whatsnewimg.classList.add("whatsnewImg");
|
|
||||||
imagecont.appendChild(whatsnewimg); */
|
|
||||||
|
|
||||||
let textcontainer = document.createElement("div");
|
<h1>3.4.13 - Bug Fixes & Styling Improvements</h1>
|
||||||
textcontainer.classList.add("whatsnewTextContainer");
|
<li>Fixed house/year box hard failing when house_colour does not exist</li>
|
||||||
|
<li>Fixed message of the day being unreadable in light mode</li>
|
||||||
|
<li>Fixed global font styling issues due to SEQTA updates</li>
|
||||||
|
<li>Fixed styling issues with title bar and other elements</li>
|
||||||
|
<li>Other minor bug fixes and improvements</li>
|
||||||
|
|
||||||
|
<h1>3.4.12 - Privacy Updates & Bug Fixes</h1>
|
||||||
|
<li>Added privacy statement</li>
|
||||||
|
<li>Added disclaimer modal to assessment averages switch</li>
|
||||||
|
<li>Improved popup management system</li>
|
||||||
|
<li>Other minor bug fixes and improvements</li>
|
||||||
|
|
||||||
|
<h1>3.4.11 - New Features & Bug Fixes</h1>
|
||||||
|
<li>Added Background Music plugin</li>
|
||||||
|
<li>Added empty state for assessments on homepage</li>
|
||||||
|
<li>Added Colour Picker hex/rgba controls</li>
|
||||||
|
<li>Fixed custom shortcuts positioning (moved above regular shortcuts)</li>
|
||||||
|
<li>Fixed Go to popup not scrolling properly</li>
|
||||||
|
<li>Made theme edit mode more plain</li>
|
||||||
|
<li>Other minor bug fixes and improvements</li>
|
||||||
|
|
||||||
|
<h1>3.4.10 - Minor bug fixes</h1>
|
||||||
|
<li>Fixed UI file styling incorrectly applying to documents</li>
|
||||||
|
<li>Fixed missing styles in global search</li>
|
||||||
|
<li>Added icons for image files in file viewer</li>
|
||||||
|
<li>Added rounded corners when dragging calendar events</li>
|
||||||
|
<li>Improved performance of element scanning</li>
|
||||||
|
<li>Other minor improvements</li>
|
||||||
|
|
||||||
let text = stringToHTML(/* html */ `
|
|
||||||
<div class="whatsnewTextContainer" style="height: 50%;overflow-y: scroll;">
|
|
||||||
<h1>3.4.9 - Bug Fixes and Performance Improvements</h1>
|
<h1>3.4.9 - Bug Fixes and Performance Improvements</h1>
|
||||||
<li>Fixed performance issues with large notices on the homepage</li>
|
<li>Fixed performance issues with large notices on the homepage</li>
|
||||||
<li>Improved performance when global search is disabled</li>
|
<li>Improved performance when global search is disabled</li>
|
||||||
@@ -273,9 +269,9 @@ export function OpenWhatsNewPopup() {
|
|||||||
<h1>Create Custom Shortcuts</h1>
|
<h1>Create Custom Shortcuts</h1>
|
||||||
<li>Found in the BetterSEQTA+ Settings menu, custom shortcuts can now be created with a name and URL of your choice.</li>
|
<li>Found in the BetterSEQTA+ Settings menu, custom shortcuts can now be created with a name and URL of your choice.</li>
|
||||||
</div>
|
</div>
|
||||||
`).firstChild;
|
`).firstChild as HTMLElement;
|
||||||
|
|
||||||
let footer = stringToHTML(/* html */ `
|
const footer = stringToHTML(/* html */ `
|
||||||
<div class="whatsnewFooter">
|
<div class="whatsnewFooter">
|
||||||
<div>
|
<div>
|
||||||
Resources and Feedback:
|
Resources and Feedback:
|
||||||
@@ -308,58 +304,10 @@ export function OpenWhatsNewPopup() {
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`).firstChild;
|
`).firstChild as HTMLElement;
|
||||||
|
|
||||||
let exitbutton = document.createElement("div");
|
openPopup({
|
||||||
exitbutton.id = "whatsnewclosebutton";
|
header,
|
||||||
|
content: [imageContainer, text, footer],
|
||||||
container.append(header);
|
|
||||||
container.append(imagecont);
|
|
||||||
container.append(textcontainer);
|
|
||||||
container.append(text as ChildNode);
|
|
||||||
container.append(footer as ChildNode);
|
|
||||||
container.append(exitbutton);
|
|
||||||
|
|
||||||
background.append(container);
|
|
||||||
|
|
||||||
document.getElementById("container")!.append(background);
|
|
||||||
|
|
||||||
let bkelement = document.getElementById("whatsnewbk");
|
|
||||||
let popup = document.getElementsByClassName("whatsnewContainer")[0];
|
|
||||||
|
|
||||||
if (settingsState.animations) {
|
|
||||||
animate(
|
|
||||||
[popup, bkelement as HTMLElement],
|
|
||||||
{ scale: [0, 1] },
|
|
||||||
{
|
|
||||||
type: "spring",
|
|
||||||
stiffness: 220,
|
|
||||||
damping: 18,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
animate(
|
|
||||||
".whatsnewTextContainer *",
|
|
||||||
{ opacity: [0, 1], y: [10, 0] },
|
|
||||||
{
|
|
||||||
delay: stagger(0.05, { startDelay: 0.1 }),
|
|
||||||
duration: 0.5,
|
|
||||||
ease: [0.22, 0.03, 0.26, 1],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
delete settingsState.justupdated;
|
|
||||||
|
|
||||||
bkelement!.addEventListener("click", function (event) {
|
|
||||||
// Check if the click event originated from the element itself and not any of its children
|
|
||||||
if (event.target === bkelement) {
|
|
||||||
DeleteWhatsNew();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var closeelement = document.getElementById("whatsnewclosebutton");
|
|
||||||
closeelement!.addEventListener("click", function () {
|
|
||||||
DeleteWhatsNew();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
import { settingsState } from "../listeners/SettingsState";
|
||||||
|
import { animate as motionAnimate, stagger } from "motion";
|
||||||
|
|
||||||
|
type AnimationTarget = string | Element | Element[] | NodeList | null;
|
||||||
|
|
||||||
|
let isClosing = false;
|
||||||
|
|
||||||
|
export async function closePopup() {
|
||||||
|
if (isClosing) return;
|
||||||
|
isClosing = true;
|
||||||
|
|
||||||
|
const background = document.getElementById("whatsnewbk");
|
||||||
|
const popup = document.getElementsByClassName("whatsnewContainer")[0] as
|
||||||
|
| HTMLElement
|
||||||
|
| undefined;
|
||||||
|
|
||||||
|
if (!background || !popup) {
|
||||||
|
isClosing = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!settingsState.animations) {
|
||||||
|
background.remove();
|
||||||
|
isClosing = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await (motionAnimate as any)(
|
||||||
|
[popup, background],
|
||||||
|
{ opacity: [1, 0], scale: [1, 0.95] },
|
||||||
|
{ duration: 0.25, easing: [0.22, 0.03, 0.26, 1] },
|
||||||
|
);
|
||||||
|
|
||||||
|
background.remove();
|
||||||
|
isClosing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OpenPopupOptions {
|
||||||
|
header?: Node | null;
|
||||||
|
content?: (Node | null | undefined)[];
|
||||||
|
animateSelector?: AnimationTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function openPopup({
|
||||||
|
header,
|
||||||
|
content = [],
|
||||||
|
animateSelector = ".whatsnewTextContainer *",
|
||||||
|
}: OpenPopupOptions = {}) {
|
||||||
|
const background = document.createElement("div");
|
||||||
|
background.id = "whatsnewbk";
|
||||||
|
background.classList.add("whatsnewBackground");
|
||||||
|
|
||||||
|
const container = document.createElement("div");
|
||||||
|
container.classList.add("whatsnewContainer");
|
||||||
|
|
||||||
|
if (header) container.append(header);
|
||||||
|
for (const node of content) if (node) container.append(node);
|
||||||
|
|
||||||
|
const closeButton = document.createElement("div");
|
||||||
|
closeButton.id = "whatsnewclosebutton";
|
||||||
|
container.append(closeButton);
|
||||||
|
|
||||||
|
background.append(container);
|
||||||
|
document.getElementById("container")!.append(background);
|
||||||
|
|
||||||
|
if (settingsState.animations) {
|
||||||
|
(motionAnimate as any)(
|
||||||
|
[container, background],
|
||||||
|
{ scale: [0, 1] },
|
||||||
|
{ type: "spring", stiffness: 220, damping: 18 },
|
||||||
|
);
|
||||||
|
|
||||||
|
if (animateSelector) {
|
||||||
|
const targets =
|
||||||
|
typeof animateSelector === "string"
|
||||||
|
? document.querySelectorAll(animateSelector)
|
||||||
|
: animateSelector;
|
||||||
|
|
||||||
|
(motionAnimate as any)(
|
||||||
|
targets!,
|
||||||
|
{ opacity: [0, 1], y: [10, 0] },
|
||||||
|
{
|
||||||
|
delay: stagger(0.05, { startDelay: 0.1 }),
|
||||||
|
duration: 0.5,
|
||||||
|
easing: [0.22, 0.03, 0.26, 1],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delete settingsState.justupdated;
|
||||||
|
|
||||||
|
background.addEventListener("click", (event) => {
|
||||||
|
if (event.target === background) void closePopup();
|
||||||
|
});
|
||||||
|
|
||||||
|
closeButton.addEventListener("click", () => void closePopup());
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { settingsState } from "@/seqta/utils/listeners/SettingsState";
|
||||||
|
import { addShortcuts } from "@/seqta/utils/Adders/AddShortcuts";
|
||||||
|
import { CreateCustomShortcutDiv } from "@/seqta/utils/CreateEnable/CreateCustomShortcutDiv";
|
||||||
|
|
||||||
|
export function renderShortcuts() {
|
||||||
|
const container = document.getElementById("shortcuts");
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
container.innerHTML = "";
|
||||||
|
|
||||||
|
try {
|
||||||
|
addShortcuts(settingsState.shortcuts || []);
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error("[BetterSEQTA+] Error adding built-in shortcuts:", err?.message || err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const custom = settingsState.customshortcuts || [];
|
||||||
|
for (const element of custom) {
|
||||||
|
try {
|
||||||
|
CreateCustomShortcutDiv(element);
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error("[BetterSEQTA+] Error adding custom shortcut:", element?.name, err?.message || err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -18,10 +18,25 @@ export async function SendNewsPage() {
|
|||||||
const main = document.getElementById("main");
|
const main = document.getElementById("main");
|
||||||
main!.innerHTML = "";
|
main!.innerHTML = "";
|
||||||
|
|
||||||
|
const displayCountry = (() => {
|
||||||
|
switch (settingsState.newsSource?.toLowerCase()) {
|
||||||
|
case "usa": return "the USA";
|
||||||
|
case "uk": return "the UK";
|
||||||
|
case "netherlands": return "the Netherlands";
|
||||||
|
default:
|
||||||
|
return settingsState.newsSource
|
||||||
|
? settingsState.newsSource
|
||||||
|
.split("_")
|
||||||
|
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
||||||
|
.join(" ")
|
||||||
|
: "Australia";
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
const html = stringToHTML(/* html */ `
|
const html = stringToHTML(/* html */ `
|
||||||
<div class="home-root">
|
<div class="home-root">
|
||||||
<div class="home-container" id="news-container">
|
<div class="home-container" id="news-container">
|
||||||
<h1 class="border">Latest Headlines in ${settingsState.newsSource ? settingsState.newsSource.charAt(0).toUpperCase() + settingsState.newsSource.slice(1) : "Australia"}</h1>
|
<h1 class="border">Latest Headlines in ${displayCountry}</h1>
|
||||||
</div>
|
</div>
|
||||||
</div>`);
|
</div>`);
|
||||||
|
|
||||||
|
|||||||
@@ -57,13 +57,37 @@ class EventManager {
|
|||||||
return { unregister };
|
return { unregister };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private buildSelector(options: EventListenerOptions): string | null {
|
||||||
|
if (options.textContent || options.customCheck) return null;
|
||||||
|
|
||||||
|
let selector = options.elementType || "";
|
||||||
|
if (options.id) {
|
||||||
|
selector += `#${CSS.escape(options.id)}`;
|
||||||
|
}
|
||||||
|
if (options.className) {
|
||||||
|
selector += `.${CSS.escape(options.className)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return selector.trim() || null;
|
||||||
|
}
|
||||||
|
|
||||||
private async scanExistingElements(
|
private async scanExistingElements(
|
||||||
options: EventListenerOptions,
|
options: EventListenerOptions,
|
||||||
callback: (element: Element) => void,
|
callback: (element: Element) => void,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const root = options.parentElement || document.documentElement;
|
const root = options.parentElement || document.documentElement;
|
||||||
const elements = Array.from(root.getElementsByTagName("*"));
|
const selector = this.buildSelector(options);
|
||||||
|
let elements: Element[] = [];
|
||||||
|
|
||||||
|
if (selector) {
|
||||||
|
elements = Array.from(root.querySelectorAll(selector));
|
||||||
|
if (selector && root.matches && root.matches(selector)) {
|
||||||
elements.unshift(root);
|
elements.unshift(root);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
elements = Array.from(root.getElementsByTagName("*"));
|
||||||
|
elements.unshift(root);
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = 0; i < elements.length; i += this.chunkSize) {
|
for (let i = 0; i < elements.length; i += this.chunkSize) {
|
||||||
const chunk = elements.slice(i, i + this.chunkSize);
|
const chunk = elements.slice(i, i + this.chunkSize);
|
||||||
|
|||||||
@@ -30,18 +30,10 @@ class StorageManager {
|
|||||||
set: (target, prop: keyof SettingsState, value) => {
|
set: (target, prop: keyof SettingsState, value) => {
|
||||||
const oldValue = target.data[prop];
|
const oldValue = target.data[prop];
|
||||||
|
|
||||||
// Only save if the value actually changed
|
// Only save if the reference actually changed
|
||||||
if (oldValue !== value) {
|
if (oldValue !== value) {
|
||||||
Reflect.set(target.data, prop, value);
|
Reflect.set(target.data, prop, value);
|
||||||
target.saveToStorage();
|
target.saveToStorage();
|
||||||
|
|
||||||
// Notify listeners immediately for responsiveness
|
|
||||||
const listeners = target.listeners.get(prop as string);
|
|
||||||
if (listeners) {
|
|
||||||
for (const listener of listeners) {
|
|
||||||
listener(value, oldValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { settingsState } from "./SettingsState";
|
import { settingsState } from "./SettingsState";
|
||||||
import { updateAllColors } from "@/seqta/ui/colors/Manager";
|
import { updateAllColors } from "@/seqta/ui/colors/Manager";
|
||||||
|
|
||||||
import { addShortcuts } from "@/seqta/utils/Adders/AddShortcuts";
|
// Shortcuts rendering
|
||||||
import { CreateCustomShortcutDiv } from "@/seqta/utils/CreateEnable/CreateCustomShortcutDiv";
|
import { renderShortcuts } from "@/seqta/utils/Render/renderShortcuts";
|
||||||
import { FilterUpcomingAssessments } from "@/seqta/utils/FilterUpcomingAssessments";
|
import { FilterUpcomingAssessments } from "@/seqta/utils/FilterUpcomingAssessments";
|
||||||
import { RemoveShortcutDiv } from "@/seqta/utils/DisableRemove/RemoveShortcutDiv";
|
|
||||||
|
|
||||||
import browser from "webextension-polyfill";
|
import browser from "webextension-polyfill";
|
||||||
import type { CustomShortcut } from "@/types/storage";
|
import type { CustomShortcut } from "@/types/storage";
|
||||||
@@ -47,21 +46,7 @@ export class StorageChangeHandler {
|
|||||||
oldValue: CustomShortcut[],
|
oldValue: CustomShortcut[],
|
||||||
) {
|
) {
|
||||||
if (!newValue || !oldValue) return;
|
if (!newValue || !oldValue) return;
|
||||||
|
renderShortcuts();
|
||||||
if (newValue.length > oldValue.length) {
|
|
||||||
// New shortcut added - add the last one
|
|
||||||
CreateCustomShortcutDiv(newValue[oldValue.length]);
|
|
||||||
} else if (newValue.length < oldValue.length) {
|
|
||||||
// Shortcut removed - find which one was removed
|
|
||||||
const newSet = new Set(newValue.map(item => JSON.stringify(item)));
|
|
||||||
const removedElement = oldValue.find(
|
|
||||||
(oldItem) => !newSet.has(JSON.stringify(oldItem))
|
|
||||||
);
|
|
||||||
|
|
||||||
if (removedElement) {
|
|
||||||
RemoveShortcutDiv([removedElement]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleShortcutsChange(
|
private handleShortcutsChange(
|
||||||
@@ -69,34 +54,7 @@ export class StorageChangeHandler {
|
|||||||
oldValue: { enabled: boolean; name: string }[],
|
oldValue: { enabled: boolean; name: string }[],
|
||||||
) {
|
) {
|
||||||
if (!newValue || !oldValue) return;
|
if (!newValue || !oldValue) return;
|
||||||
|
renderShortcuts();
|
||||||
// Create map for faster lookup
|
|
||||||
const oldMap = new Map(oldValue.map(item => [item.name, item.enabled]));
|
|
||||||
|
|
||||||
const addedShortcuts: { enabled: boolean; name: string }[] = [];
|
|
||||||
const removedShortcuts: { enabled: boolean; name: string }[] = [];
|
|
||||||
|
|
||||||
// Check for changes in shortcuts
|
|
||||||
for (const newItem of newValue) {
|
|
||||||
const oldEnabled = oldMap.get(newItem.name);
|
|
||||||
|
|
||||||
// Newly enabled shortcuts
|
|
||||||
if (newItem.enabled && (oldEnabled === undefined || !oldEnabled)) {
|
|
||||||
addedShortcuts.push(newItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Newly disabled shortcuts
|
|
||||||
if (!newItem.enabled && oldEnabled === true) {
|
|
||||||
removedShortcuts.push(newItem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (addedShortcuts.length > 0) {
|
|
||||||
addShortcuts(addedShortcuts);
|
|
||||||
}
|
|
||||||
if (removedShortcuts.length > 0) {
|
|
||||||
RemoveShortcutDiv(removedShortcuts);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleTransparencyEffectsChange(newValue: boolean) {
|
private handleTransparencyEffectsChange(newValue: boolean) {
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ export interface SettingsState {
|
|||||||
subjectfilters: Record<string, any>;
|
subjectfilters: Record<string, any>;
|
||||||
transparencyEffects: boolean;
|
transparencyEffects: boolean;
|
||||||
justupdated?: boolean;
|
justupdated?: boolean;
|
||||||
|
privacyStatementShown?: boolean;
|
||||||
|
privacyStatementLastUpdated?: string;
|
||||||
timeFormat?: string;
|
timeFormat?: string;
|
||||||
animations: boolean;
|
animations: boolean;
|
||||||
defaultPage: string;
|
defaultPage: string;
|
||||||
|
|||||||
@@ -84,6 +84,10 @@ export default defineConfig(({ command }) => ({
|
|||||||
settings: join(__dirname, "src", "interface", "index.html"),
|
settings: join(__dirname, "src", "interface", "index.html"),
|
||||||
pageState: join(__dirname, "src", "pageState.js"),
|
pageState: join(__dirname, "src", "pageState.js"),
|
||||||
},
|
},
|
||||||
|
onwarn(warning, warn) {
|
||||||
|
if (warning.code === "FILE_NAME_CONFLICT") return;
|
||||||
|
warn(warning);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|||||||
Reference in New Issue
Block a user