Compare commits

...

49 Commits

Author SHA1 Message Date
SethBurkart123 f3f90ef2a8 bump(version): 3.4.11 2025-10-13 15:00:08 +11:00
SethBurkart123 9bcc94aa8a feat(homepage): add empty state for assessments 2025-10-13 14:36:23 +11:00
SethBurkart123 ff2431f269 style(homepage): increased max width on days timetable 2025-10-13 14:28:44 +11:00
SethBurkart123 b442194bc5 fix: move custom shortcuts above regular shortcuts 2025-10-13 14:24:27 +11:00
SethBurkart123 b59c0eae25 feat: make edit mode on themes tab more plain #240 2025-10-13 14:11:55 +11:00
SethBurkart123 e895ce9f6b feat: add colorPicker hex/rgba controls back #351 2025-10-13 13:37:26 +11:00
SethBurkart123 7192f41535 feat: improvements to background music plugin 2025-10-13 13:26:15 +11:00
SethBurkart123 f1b707ab25 style: add line clamp 2025-09-15 11:28:18 +10:00
Seth Burkart 7f47cb8183 Merge pull request #348 from BetterSEQTA/goto-fix
Go to popup not scrolling #342
2025-09-15 11:27:09 +10:00
SethBurkart123 7f5d138bc9 fix: Go to popup not scrolling #342 2025-09-15 11:26:31 +10:00
Seth Burkart cef0f29640 Merge pull request #346 from StroepWafel/Fix-dropdowns
fix: Drop down menu styling
2025-09-15 11:20:32 +10:00
SethBurkart123 157343dda9 fix: remove excess arrow 2025-09-15 11:20:22 +10:00
Seth Burkart 7705c0a3cd Merge pull request #347 from StroepWafel/Fix-wrapping
fix: Text now wraps correctly in most divs
2025-09-15 11:11:24 +10:00
SethBurkart123 7def7b190c fix: duplicate #menu selectors 2025-09-15 11:11:05 +10:00
Alphons Joseph c294fb7369 Merge pull request #344 from StroepWafel:main
feat(plugin):Background Music plugin
2025-09-14 09:29:43 +08:00
StroepWafel 0dbbef0eb1 fix: Text now wraps correctly in most divs
Adjusted divs to wrap text, this can cause some issues where substantially long words get chopped up, but scaling down the font size makes it look weird.
2025-09-12 16:17:18 +09:30
StroepWafel c3c747d996 fix: Drop down menu styling
Fix for drop down menu styling so it doesn't look abhorrent
2025-09-12 15:50:24 +09:30
SethBurkart123 cdc8062275 fix: broken shortcut rendering logic #345 2025-09-11 19:14:53 +10:00
codefactor-io 1857b5ff01 [CodeFactor] Apply fixes to commit 700e3eb 2025-09-08 10:21:47 +00:00
StroepWafel 700e3ebb48 feat(plugin):Background Music plugin
Added a plugin so users can upload and play a .wav audio file as background music. added volume setting, made sure the file is stored across version updates/downdates, made the music stop when the tab is unfocussed, and registered the plugin as official.
2025-09-08 19:12:35 +09:30
Seth Burkart 16b9610301 Merge pull request #340 from Jones8683/main
Bump crxjs version
2025-08-28 14:56:35 +10:00
Jones8683 7d11e203a6 bump: more packages 2025-08-27 19:58:54 +09:30
Jones8683 530f07e640 bump(deps): bump more deps 2025-08-23 10:52:42 +09:30
Jones8683 08586781ce feat: proper gramatical naming for news sources 2025-08-19 12:24:03 +09:30
Jones8683 3ca5a49769 bump(deps): updated deps to match 2025-08-19 11:49:19 +09:30
Jones8683 886c79b3ee fix(deps): crxjs beta being used 2025-08-19 11:39:28 +09:30
Jones 30aa39142d fix: icons not loading 2025-08-19 11:26:59 +09:30
Jones8683 4188ef0d67 format: remove extra lines 2025-08-19 08:14:12 +09:30
Jones8683 ad9a013b00 update injected.scss 2025-08-19 08:13:20 +09:30
Jones8683 cd1f954cc7 feat: remove ugly line in transparency effects 2025-08-18 16:24:02 +09:30
Jones8683 6ef6c986dc fix (build): stop duplicate icon bundling warnings temp 2025-08-18 16:04:53 +09:30
Jones8683 f2e28175a0 feat: update crxjs 2025-08-18 15:53:35 +09:30
SethBurkart123 3ddcb204ef bump(version): 3.4.10.2 2025-08-17 21:27:57 +10:00
Alphons Joseph 766f0e6d3f undebump it 2025-08-17 18:48:09 +08:00
Alphons Joseph f1fcba58ef debump vite plugin 2025-08-17 18:30:19 +08:00
SethBurkart123 dba2d13bb3 bump(deps): publish-browser-extension to 3.0.1 2025-08-17 16:17:24 +10:00
SethBurkart123 30bf345b86 feat: add changelog for 3.4.10 2025-08-17 14:29:26 +10:00
SethBurkart123 0e98f52058 fix: UIfile styling applying on documents 2025-08-17 14:15:24 +10:00
SethBurkart123 f89508deb2 bump(version): 3.4.10 2025-08-17 14:14:44 +10:00
SethBurkart123 c7b69ad97b chore: import updates 2025-08-17 11:27:23 +10:00
SethBurkart123 2ef8bb215a perf: improved efficiency of element scanning in eventmanager 2025-08-17 11:02:41 +10:00
SethBurkart123 16273cf012 style: show icon on image files 2025-08-17 09:07:26 +10:00
Seth Burkart 13d3ccd8e4 Merge pull request #334 from Jones8683/main
Fix windows dev script???
2025-08-17 09:05:29 +10:00
SethBurkart123 7ebc4db9db fix: global search missing styles #335 2025-08-17 09:00:37 +10:00
Jones ed9d662ba4 Update README.md 2025-08-16 20:16:45 +09:30
Jones8683 8647e0b272 feat: update crxjs to out of beta 2025-08-16 19:51:38 +09:30
SethBurkart123 d93abec615 docs: update architecture 2025-08-15 17:32:05 +10:00
Seth Burkart 339b409937 Merge pull request #333 from Jones8683/main
Apply rounded corners when dragging event
2025-08-15 17:28:19 +10:00
Jones8683 860916a5b8 feat: apply rounded corners when dragging event 2025-08-13 10:16:05 +09:30
28 changed files with 575 additions and 167 deletions
+4 -2
View File
@@ -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)
+1 -1
View File
@@ -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
View File
@@ -1,6 +1,6 @@
{ {
"name": "betterseqtaplus", "name": "betterseqtaplus",
"version": "3.4.9", "version": "3.4.11",
"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",
+52 -8
View File
@@ -38,6 +38,21 @@ 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;
@@ -379,6 +394,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 +478,6 @@ ul.magicDelete > li.deleting {
} }
} }
#menu li > label,
#menu section > label {
text-transform: none;
font-size: 16px;
}
#userActions { #userActions {
display: none; display: none;
} }
@@ -801,6 +823,11 @@ 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*="ResourceList__ResourceItem___voTSd"],
html.transparencyEffects [class*="ResourceList__ResourceItem___voTSd"] [class*="ResourceList__name___ydvDT"] {
border-bottom: none !important;
}
.assessmentsWrapper .message { .assessmentsWrapper .message {
display: none; display: none;
} }
@@ -1111,7 +1138,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 +1347,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 +1691,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;
@@ -1991,13 +2030,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 +2064,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;
+10
View File
@@ -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) {
+18 -2
View File
@@ -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>
+12 -11
View File
@@ -183,18 +183,19 @@
props: { props: {
state: $settingsState.newsSource, state: $settingsState.newsSource,
onChange: (value: string) => settingsState.newsSource = value, onChange: (value: string) => settingsState.newsSource = value,
options: [ options: [
{ value: "australia", label: "Australia" }, { value: "australia", label: "Australia" },
{ value: "usa", label: "USA" }, { value: "usa", label: "USA" },
{ value: "taiwan", label: "Taiwan" }, { value: "uk", label: "UK" },
{ value: "hong_kong", label: "Hong Kong" }, { value: "taiwan", label: "Taiwan" },
{ value: "panama", label: "Panama" }, { value: "hong_kong", label: "Hong Kong" },
{ value: "canada", label: "Canada" }, { value: "panama", label: "Panama" },
{ value: "singapore", label: "Singapore" }, { value: "canada", label: "Canada" },
{ value: "uk", label: "UK" }, { value: "singapore", label: "Singapore" },
{ value: "japan", label: "Japan" }, { value: "japan", label: "Japan" },
{ value: "netherlands", label: "Netherlands" } { value: "netherlands", label: "Netherlands" }
] ]
} }
} }
] as option} ] as option}
+21 -15
View File
@@ -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...
+5 -2
View File
@@ -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>
@@ -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"
>&#215;</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")
+2
View File
@@ -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
+2 -2
View File
@@ -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;
@@ -41,7 +41,11 @@ export function renderSettingsIfNeeded() {
try { try {
const shadow = extensionPopup.attachShadow({ mode: "open" }); const shadow = extensionPopup.attachShadow({ mode: "open" });
requestIdleCallback(() => renderSvelte(Settings, shadow)); if ('requestIdleCallback' in window) {
requestIdleCallback(() => renderSvelte(Settings, shadow));
} else {
renderSvelte(Settings, shadow);
}
isSettingsRendered = true; isSettingsRendered = true;
} catch (err) { } catch (err) {
console.error(err); console.error(err);
+4 -1
View File
@@ -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();
}
});
});
}
+10 -17
View File
@@ -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) {
+26
View File
@@ -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);
}
}
}
+20 -5
View File
@@ -18,12 +18,27 @@ 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>`);
main!.append(html.firstChild!); main!.append(html.firstChild!);
+18
View File
@@ -66,6 +66,24 @@ export function OpenWhatsNewPopup() {
let text = stringToHTML(/* html */ ` let text = stringToHTML(/* html */ `
<div class="whatsnewTextContainer" style="height: 50%;overflow-y: scroll;"> <div class="whatsnewTextContainer" style="height: 50%;overflow-y: scroll;">
<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>
<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>
+26 -2
View File
@@ -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);
elements.unshift(root); let elements: Element[] = [];
if (selector) {
elements = Array.from(root.querySelectorAll(selector));
if (selector && root.matches && root.matches(selector)) {
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);
+1 -9
View File
@@ -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;
}, },
+4 -46
View File
@@ -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) {
+4
View File
@@ -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);
},
}, },
}, },
})); }));