Compare commits

...

47 Commits

Author SHA1 Message Date
Alphons Joseph 5b3c3e5006 feat: initial changes (not fixed yet) 2026-01-24 17:59:10 +08:00
Seth Burkart c205a52f03 Merge pull request #370 from Jaxx7594/icon
fix: Favicon not showing
2026-01-23 15:33:17 +11:00
SethBurkart123 a6d95f27ed chore: update publish-browser extension 2026-01-23 14:13:11 +11:00
Jaxon Lewis-Wilson f05cd66e88 fix: Favicon not showing 2026-01-23 09:27:14 +08:00
SethBurkart123 a151e7a07e feat: 3.4.13 2026-01-23 08:10:38 +11:00
Seth Burkart 5b590512ee Merge pull request #365 from Jaxx7594/line
fix: House/year box hard failing when house_colour does not exist
2026-01-21 21:52:43 +11:00
Seth Burkart 3ff8ef144a Merge pull request #366 from Jones8683/main
Fix the message of the day being unreadable in light mode
2026-01-21 21:52:20 +11:00
Seth Burkart d9abed1c5d Merge pull request #367 from Jaxx7594/bar
fix: Incorrect styling due to SEQTA update
2026-01-21 21:51:40 +11:00
Jaxon Lewis-Wilson 82a789bbec fix: Global fonts
Prior commit was not actually functional upon review
2026-01-21 12:22:45 +08:00
Jaxon Lewis-Wilson ce6538f850 fix: Global font
Overrides SEQTA's new important tag for font under *{}
2026-01-21 11:40:16 +08:00
Jaxon Lewis-Wilson 979ae7149f fix: Incorrect styling due to SEQTA update 2026-01-21 11:17:49 +08:00
codefactor-io 6e71437fe8 [CodeFactor] Apply fixes to commit 940ecf8 2026-01-19 01:41:43 +00:00
Jones8683 940ecf8714 fix: message of the day unreadable in light mode 2026-01-19 12:09:57 +10:30
Jaxon Lewis-Wilson e0cc2e0fdf fix: House/year box hard failing when house_colour does not exist 2026-01-18 00:11:09 +08:00
SethBurkart123 5a19ef92e8 feat: v3.4.12 2025-12-19 17:04:08 +11:00
SethBurkart123 0a3781e9c2 fix: video aspect changing on load 2025-12-19 16:30:38 +11:00
SethBurkart123 a2e39c9d84 feat: add DisclaimerModal component to assessment averages switch 2025-12-19 14:50:55 +11:00
SethBurkart123 520abbb5c3 chore: hide the minecraft server icon 2025-12-19 14:30:26 +11:00
SethBurkart123 d0a11da15f feat: updated privacy statement 2025-12-19 14:29:11 +11:00
Seth Burkart fd5802f9a3 Merge pull request #362 from Jones8683/main
Fix some popup stuff
2025-12-12 13:26:52 +11:00
Jones8683 380d829d19 fix: bad spacing and ordering of popup buttons 2025-12-04 11:52:48 +10:30
Alphons Joseph 702528fb0c Merge pull request #361 from StroepWafel/Privacy-statement
re-add Privacy statement stuff
2025-12-03 18:17:28 +08:00
StroepWafel 2c077bc755 Add dynamic privacy policy notification with API fetch
Implements fetching the privacy policy from the BetterSEQTA+ API and displays a notification if the policy has been updated. Adds sanitization for HTML content, updates settings state to track last shown timestamp, and provides a manual trigger in settings. Refactors notification logic for improved security and maintainability.
2025-11-29 19:47:30 +10:30
StroepWafel fd86e57442 re-add privacy statement
Re-Added privacy statement and ported it over to jones' new system
2025-11-29 16:51:59 +10:30
Alphons Joseph 60ce18280e Merge pull request #357 from Jones8683/main 2025-11-29 09:38:26 +08:00
Jones8683 668dbfd78b fix: remove a comment 2025-11-29 11:57:58 +10:30
Jones8683 810aa17f15 Merge branch 'main' of https://github.com/Jones8683/BetterSEQTA-Plus 2025-11-29 11:46:22 +10:30
Jones8683 b64558e50a fix: whatsnew not scrolling 2025-11-29 11:46:14 +10:30
Jones 9b969bd708 fix: add back the contribute link
Added a section inviting contributions to the README.
2025-11-29 11:37:43 +10:30
Jones 1945f7c592 Merge branch 'BetterSEQTA:main' into main 2025-11-29 11:31:13 +10:30
Alphons Joseph 3e26d9af3c Merge pull request #360 from BetterSEQTA/revert-359-Privacy-statement
Revert "add privacy statement popup"
2025-11-29 08:54:03 +08:00
Alphons Joseph 3c8d7e246b Revert "add privacy statement popup" 2025-11-29 08:53:50 +08:00
Alphons Joseph 2e56518330 Merge pull request #359 from StroepWafel/Privacy-statement
add privacy statement popup
2025-11-29 07:42:45 +08:00
Alphons Joseph e67f3110e0 Update monofile.ts 2025-11-28 22:24:37 +08:00
Alphons Joseph a67f4d2e25 Update monofile.ts 2025-11-28 22:22:11 +08:00
StroepWafel d6025140fd add privacy statement popup 2025-11-28 14:03:17 +10:30
Jones8683 88e9ddf29c fix: uneven spacing on popup buttons 2025-11-18 10:59:43 +10:30
Jones8683 11adc4f933 Merge branch 'main' of https://github.com/Jones8683/BetterSEQTA-Plus 2025-11-12 13:53:47 +10:30
Jones8683 15691e8d94 fix: bottom corners of custom timetable events arent roudned 2025-11-12 13:52:28 +10:30
Jones 754b8d0589 fix: indent in readme 2025-11-11 11:30:40 +10:30
Jones8683 1d634d0da1 feat: seperate file to manage the 3 popups 2025-11-10 19:05:08 +10:30
Jones8683 7136de90be fix: crash in notificatio fetcher 2025-11-10 17:10:53 +10:30
Jones8683 466628479e feat: import close popup function from whatsnew instead of having its own 2025-11-10 16:57:53 +10:30
Jones8683 9c08d0bac2 fix: spam clicking outside a popup restarts closing animation, and remove multiple close popup functions 2025-11-10 16:57:14 +10:30
Jones8683 6c5320007f fix: empty scrollbar in about server popup 2025-11-10 16:29:41 +10:30
Jones8683 4734a443b4 fix: border applying to bottom most item 2025-11-10 16:18:55 +10:30
Jones 7c38e1dc29 fix: no border between submissions when transparency effects on 2025-11-10 11:26:53 +10:30
23 changed files with 2727 additions and 441 deletions
+1924
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "betterseqtaplus",
"version": "3.4.11",
"version": "3.4.13",
"type": "module",
"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",
@@ -47,7 +47,7 @@
"mime-types": "^3.0.1",
"prettier": "^3.5.3",
"process": "^0.11.10",
"publish-browser-extension": "^3.0.1",
"publish-browser-extension": "^4.0.0",
"sass": "^1.85.1",
"sass-loader": "^16.0.5",
"semver": "^7.7.1",
+6 -4
View File
@@ -50,10 +50,12 @@ async function init() {
documentLoadStyle.textContent = documentLoadCSS;
document.head.appendChild(documentLoadStyle);
const icon = document.querySelector(
'link[rel*="icon"]',
)! as HTMLLinkElement;
icon.href = icon48; // Change the icon
const icons =
document.querySelectorAll<HTMLLinkElement>('link[rel*="icon"]');
icons.forEach((link) => {
link.href = icon48;
});
try {
await initializeSettingsState();
+1 -1
View File
@@ -108,7 +108,7 @@ function getDefaultValues(): SettingsState {
originalSelectedColor: "",
DarkMode: true,
animations: !isLowEndDevice,
assessmentsAverage: true,
assessmentsAverage: false,
defaultPage: "home",
shortcuts: [
{
+129 -87
View File
@@ -57,7 +57,8 @@ select {
transition: 200ms;
background: var(--auto-background) !important;
}
* {
:root * {
font-family: Rubik, sans-serif !important;
--theme-fg-parts: white;
}
.extension-editor {
@@ -158,6 +159,16 @@ select {
color: var(--text-primary);
position: relative;
}
#main {
> .timetablepage {
> .quickbar {
.gutter {
border-bottom-left-radius: 15px;
border-bottom-right-radius: 15px;
}
}
}
}
.forums {
color: var(--text-color);
}
@@ -813,8 +824,8 @@ div > ol:has(.uiFileHandlerWrapper) {
[aria-labelledby="lixycoxs-tab-1"] [minlength="0"] {
min-height: 128px !important;
}
.student #menu > ul::before {
background-image: var(--betterseqta-logo);
body.student #menu > ul::before {
background-image: var(--betterseqta-logo) !important;
position: -webkit-sticky;
position: sticky;
top: 0;
@@ -823,11 +834,18 @@ div > ol:has(.uiFileHandlerWrapper) {
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"] {
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 {
display: none;
}
@@ -983,7 +1001,7 @@ html.transparencyEffects [class*="ResourceList__ResourceItem___voTSd"] [class*="
top: 72px;
left: 0px;
z-index: 10;
@media (min-width: 1401px) {
position: absolute;
left: 402px;
@@ -1026,8 +1044,8 @@ html.transparencyEffects [class*="ResourceList__ResourceItem___voTSd"] [class*="
display: none;
}
#title {
background: var(--background-primary);
color: var(--text-primary);
background: var(--background-primary) !important;
color: var(--text-primary) !important;
display: flex;
justify-content: space-between;
padding-right: 56px !important;
@@ -1690,7 +1708,7 @@ iframe.userHTML {
}
.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%;
@@ -1749,7 +1767,7 @@ iframe.userHTML {
&.selected {
background: transparent !important;
}
&::before {
content: "";
position: absolute;
@@ -1761,7 +1779,9 @@ iframe.userHTML {
background: var(--auto-background);
opacity: 0;
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;
pointer-events: none;
}
@@ -1775,13 +1795,12 @@ iframe.userHTML {
opacity: 1;
scale: 1;
}
}
}
}
.pane {
.content:has(.programmeNavigator) {
.content:has(.programmeNavigator) {
margin: 0;
}
@@ -1799,7 +1818,9 @@ iframe.userHTML {
.dark .programmeNavigator .navigator {
.search {
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 {
@@ -2028,7 +2049,7 @@ div.entry.new {
div.liveEntry {
border-radius: 4px;
}
}
div.dailycalMarker {
border-radius: 4px;
@@ -2179,24 +2200,23 @@ div.bar.flat {
background: var(--background-secondary) !important;
}
.dashlet-motd {
padding: 7px !important;
.message {
font-size: 24px !important;
border-radius: 12px !important;
border: none !important;
box-shadow: none !important;
color: #fff !important;
padding: 16px !important;
margin: 0 !important;
height: 100% !important;
max-height: none !important;
display: flex !important;
align-items: flex-start !important;
overflow: hidden !important;
.dashlet-motd {
padding: 7px !important;
.message {
font-size: 24px !important;
border-radius: 12px !important;
border: none !important;
box-shadow: none !important;
padding: 16px !important;
margin: 0 !important;
height: 100% !important;
max-height: none !important;
display: flex !important;
align-items: flex-start !important;
overflow: hidden !important;
color: var(--text-primary) !important;
}
}
}
.cke_toolbox > .cke_toolbar > .cke_toolgroup > .cke_button {
background: var(--background-secondary) !important;
@@ -3339,6 +3359,22 @@ div.day-empty {
color: var(--text-primary);
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 {
margin: 20px;
width: 100%;
@@ -3373,6 +3409,7 @@ div.day-empty {
.whatsnewImg {
margin: 0 auto;
width: 90%;
aspect-ratio: 16 / 10;
border-radius: 16px;
box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.3);
}
@@ -3399,7 +3436,6 @@ div.day-empty {
font-size: 0.75rem;
font-weight: 500;
color: #9a3412;
background-color: #ffedd569;
border-radius: 9999px;
border: 1px solid rgba(253, 186, 140, 0.3);
background-color: #ffedd5;
@@ -3750,14 +3786,19 @@ div.day-empty {
}
.notice-unified-content {
h1, h2, h3, h4, h5, h6 {
h1,
h2,
h3,
h4,
h5,
h6 {
margin: 0 !important;
padding: 0 !important;
font-weight: inherit !important;
color: inherit !important;
text-shadow: none !important;
}
.notice-header {
display: flex;
justify-content: space-between;
@@ -3766,16 +3807,16 @@ div.day-empty {
margin-bottom: 12px;
gap: 16px;
}
.notice-content-title {
font-size: 20px !important; // Nice middle ground - not too big, not too small
font-weight: 600 !important;
color: var(--text-primary) !important;
margin: 0 0 12px 0 !important;
margin: 0 0 12px !important;
line-height: 1.3 !important;
flex-shrink: 0;
}
.notice-content-body {
font-size: 14px !important;
color: var(--text-secondary) !important;
@@ -3787,72 +3828,73 @@ div.day-empty {
min-width: 600px; // Ensure tables have consistent width for layout
width: 100%;
}
// The ONLY difference between states is clipping!
&.notice-card-state {
&.notice-card-state {
.notice-content-body {
// Clip to show only 2 lines but keep full layout
overflow: hidden;
max-height: 3em; // ~2 lines worth of height
}
}
&.notice-modal-state {
.notice-close-btn {
opacity: 1;
}
.notice-content-body {
// Show full content with scrolling
overflow-y: auto;
// Custom scrollbar for long content
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.2);
border-radius: 3px;
}
&::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.3);
}
// Style content elements nicely
p {
margin-bottom: 12px;
&:last-child {
margin-bottom: 0;
}
}
&::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.3);
}
a {
color: var(--theme-primary);
text-decoration: none;
// Style content elements nicely
p {
margin-bottom: 12px;
&:hover {
text-decoration: underline;
}
}
&:last-child {
margin-bottom: 0;
}
}
ul, ol {
margin: 12px 0;
padding-left: 20px;
}
a {
color: var(--theme-primary);
text-decoration: none;
li {
margin-bottom: 4px;
}
}
}
}
&:hover {
text-decoration: underline;
}
}
ul,
ol {
margin: 12px 0;
padding-left: 20px;
}
li {
margin-bottom: 4px;
}
}
}
}
.notice-header {
display: flex;
@@ -3882,7 +3924,6 @@ button.notice-close-btn {
align-items: center;
justify-content: center;
cursor: pointer;
font-size: 18px;
color: var(--text-primary);
transition: all 0.2s ease !important;
flex-shrink: 0;
@@ -3940,33 +3981,33 @@ button.notice-close-btn {
font-size: 24px;
font-weight: 600;
color: var(--text-primary);
margin: 16px 20px 20px 20px;
margin: 16px 20px 20px;
line-height: 1.3;
flex-shrink: 0;
}
.notice-modal-body {
padding: 0 20px 20px 20px;
padding: 0 20px 20px;
font-size: 15px;
line-height: 1.6;
color: var(--text-secondary);
flex: 1;
overflow-y: auto;
// Custom scrollbar
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.2);
border-radius: 3px;
}
&::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.3);
}
@@ -3974,7 +4015,7 @@ button.notice-close-btn {
// Style content elements
p {
margin-bottom: 12px;
&:last-child {
margin-bottom: 0;
}
@@ -3989,7 +4030,8 @@ button.notice-close-btn {
}
}
ul, ol {
ul,
ol {
margin: 12px 0;
padding-left: 20px;
}
@@ -4003,7 +4045,7 @@ button.notice-close-btn {
.dark {
.notice-card {
border-color: rgba(255, 255, 255, 0.05);
&:hover {
border-color: rgba(255, 255, 255, 0.1);
}
@@ -4026,17 +4068,17 @@ button.notice-close-btn {
.notice-modal-title {
font-size: 20px;
margin: 12px 16px 16px 16px;
margin: 12px 16px 16px;
}
.notice-modal-body {
padding: 0 16px 16px 16px;
padding: 0 16px 16px;
}
.notice-card {
padding: 12px;
}
.notice-preview {
font-size: 13px;
}
+4
View File
@@ -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&nbsp;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>"
}
@@ -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>
+69 -22
View File
@@ -11,13 +11,16 @@
import { closeExtensionPopup } from "@/seqta/utils/Closers/closeExtensionPopup";
import { OpenAboutPage } from "@/seqta/utils/Openers/OpenAboutPage";
import { OpenWhatsNewPopup } from "@/seqta/utils/Whatsnew";
import { OpenMinecraftServerPopup } from "@/seqta/utils/AboutMinecraftServer";
import { OpenWhatsNewPopup } from "@/seqta/utils/Openers/OpenWhatsNewPopup";
//import { OpenMinecraftServerPopup } from "@/seqta/utils/Openers/OpenMinecraftServerPopup";
import ColourPicker from "../components/ColourPicker.svelte";
import DisclaimerModal from "../components/DisclaimerModal.svelte";
import { settingsPopup } from "../hooks/SettingsPopup";
let devModeSequence = "";
let showDisclaimerModal = $state(false);
let disclaimerCallbacks = $state<{ onConfirm: () => void, onCancel: () => void } | null>(null);
const handleDevModeToggle = () => {
const handleKeyDown = (event: KeyboardEvent) => {
@@ -50,14 +53,24 @@
closeExtensionPopup();
};
const openMinecraftServer = () => {
/* const openMinecraftServer = () => {
OpenMinecraftServerPopup();
closeExtensionPopup();
}; */
const openPrivacyStatement = () => {
window.open("https://betterseqta.org/privacy", "_blank");
closeExtensionPopup();
};
let { standalone } = $props<{ standalone?: boolean }>();
let showColourPicker = $state<boolean>(false);
const showDisclaimer = (onConfirm: () => void, onCancel: () => void) => {
disclaimerCallbacks = { onConfirm, onCancel };
showDisclaimerModal = true;
};
onMount(async () => {
settingsPopup.addListener(() => {
showColourPicker = false;
@@ -101,25 +114,34 @@
/>
{#if !standalone}
<button
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"
>
{"\ueb73"}
</button>
<div class="flex absolute top-1 right-1 gap-1 items-center">
<button
onclick={openAbout}
class="flex justify-center items-center w-8 h-8 text-lg rounded-xl font-IconFamily bg-zinc-100 dark:bg-zinc-700"
>
{"\ueb73"}
</button>
<button
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"
>
{"\ue929"}
</button>
<button
onclick={openChangelog}
class="flex justify-center items-center w-8 h-8 text-lg rounded-xl font-IconFamily bg-zinc-100 dark:bg-zinc-700"
>
{"\ue929"}
</button>
<button
onclick={openMinecraftServer}
class="absolute top-1 right-1 w-8 h-8 bg-zinc-100 dark:bg-zinc-700 rounded-xl p-1"
aria-label="Open Minecraft Server"
>
<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}
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"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 64 70"
@@ -247,7 +269,8 @@
transform="translate(18,10)"
/>
</svg>
</button>
</button> -->
</div>
{/if}
</div>
@@ -256,7 +279,7 @@
{
title: "Settings",
Content: Settings,
props: { showColourPicker: openColourPicker },
props: { showColourPicker: openColourPicker, showDisclaimer },
},
{ title: "Shortcuts", Content: Shortcuts },
{ title: "Themes", Content: Theme },
@@ -272,3 +295,27 @@
/>
{/if}
</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}
+39 -2
View File
@@ -10,6 +10,8 @@
import type { SettingsList } from "@/interface/types/SettingsProps"
import { settingsState } from "@/seqta/utils/listeners/SettingsState.ts"
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 type { BooleanSetting, StringSetting, NumberSetting, SelectSetting, ButtonSetting, HotkeySetting, ComponentSetting } from "@/plugins/core/types"
@@ -90,7 +92,10 @@
loadPluginSettings();
})
const { showColourPicker } = $props<{ showColourPicker: () => void }>();
const { showColourPicker, showDisclaimer } = $props<{
showColourPicker: () => void;
showDisclaimer: (onConfirm: () => void, onCancel: () => void) => void;
}>();
</script>
{#snippet Setting({ title, description, Component, props }: SettingsList) }
@@ -222,7 +227,20 @@
<div>
<Switch
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>
@@ -340,6 +358,25 @@
/>
</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>
{/if}
</div>
+1 -1
View File
@@ -14,7 +14,7 @@ const updatedFirefoxManifest = {
},
browser_specific_settings: {
gecko: {
id: pkg.author.email,
id: "betterseqta@betterseqta.com",
},
},
};
@@ -39,7 +39,7 @@ const notificationCollectorPlugin: Plugin<{}, NotificationCollectorStorage> = {
"[class*='notifications__bubble___']",
) as HTMLElement;
if (api.storage.lastNotificationCount !== 0) {
if (alertDiv && api.storage.lastNotificationCount !== 0) {
alertDiv.textContent = api.storage.lastNotificationCount.toString();
}
@@ -63,7 +63,7 @@ const notificationCollectorPlugin: Plugin<{}, NotificationCollectorStorage> = {
const notificationCount = data.payload.notifications.length;
api.storage.lastNotificationCount = notificationCount;
api.storage.lastCheckedTime = new Date().toISOString();
// Reset error count on success
api.storage.consecutiveErrors = 0;
@@ -74,31 +74,36 @@ const notificationCollectorPlugin: Plugin<{}, NotificationCollectorStorage> = {
}
} catch (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 = () => {
// 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);
};
const startPolling = () => {
if (pollInterval) return; // Already polling
checkNotifications();
const scheduleNext = () => {
const interval = getNextInterval();
pollInterval = window.setTimeout(() => {
checkNotifications().then(() => {
if (pollInterval) { // Only continue if not stopped
if (pollInterval) {
// Only continue if not stopped
scheduleNext();
}
});
}, interval);
};
scheduleNext();
};
@@ -124,14 +129,16 @@ const notificationCollectorPlugin: Plugin<{}, NotificationCollectorStorage> = {
isVisible = !document.hidden;
if (isVisible && !pollInterval) {
// Resume polling when tab becomes visible
const alertDiv = document.querySelector("[class*='notifications__bubble___']");
const alertDiv = document.querySelector(
"[class*='notifications__bubble___']",
);
if (alertDiv) {
startPolling();
}
}
};
document.addEventListener('visibilitychange', handleVisibilityChange);
document.addEventListener("visibilitychange", handleVisibilityChange);
api.seqta.onMount("[class*='notifications__bubble___']", (_) => {
startPolling();
@@ -139,7 +146,7 @@ const notificationCollectorPlugin: Plugin<{}, NotificationCollectorStorage> = {
return () => {
stopPolling();
document.removeEventListener('visibilitychange', handleVisibilityChange);
document.removeEventListener("visibilitychange", handleVisibilityChange);
};
},
};
@@ -8,12 +8,16 @@
object-fit: cover;
z-index: 4;
box-shadow: 0 0 0 3px #000000;
transition: box-shadow 0.05s ease-in-out;
}
.dark .userInfoImg {
box-shadow: 0 0 0 3px #ffffff;
transition: box-shadow 0.05s ease-in-out;
}
.userInfosvgdiv {
filter: invert(0) !important;
}
@media (prefers-reduced-motion: reduce) {
.userInfoImg {
transition: none !important;
}
}
+9 -2
View File
@@ -147,14 +147,21 @@ export class ThemeManager {
public async initialize(): Promise<void> {
console.debug("[ThemeManager] Starting initialization");
try {
// Check if theme creator was open during reload
const neumorphicThemeId = "9a9786d1-b5fc-4a91-8c7a-f8bf7f7679ad";
const migrationCSS = "#title {\nbackground: transparent !important;\n}";
const theme = (await localforage.getItem(neumorphicThemeId)) as CustomTheme | null;
if (theme && theme.CustomCSS && !theme.CustomCSS.includes("#title {\nbackground: transparent !important;\n}")) {
theme.CustomCSS = theme.CustomCSS + "\n" + migrationCSS;
await localforage.setItem(neumorphicThemeId, theme);
}
const themeCreatorOpen = localStorage.getItem("themeCreatorOpen");
if (themeCreatorOpen === "true") {
console.debug(
"[ThemeManager] Theme creator was open, clearing preview state",
);
this.clearPreview();
// Clean up the flag
localStorage.removeItem("themeCreatorOpen");
}
+15 -6
View File
@@ -23,12 +23,11 @@ import { updateAllColors } from "@/seqta/ui/colors/Manager";
import loading from "@/seqta/ui/Loading";
import { SendNewsPage } from "@/seqta/utils/SendNewsPage";
import { loadHomePage } from "@/seqta/utils/Loaders/LoadHomePage";
import { OpenWhatsNewPopup } from "@/seqta/utils/Whatsnew";
//import { OpenMinecraftServerPopup } from "@/seqta/utils/AboutMinecraftServer";
import { OpenWhatsNewPopup } from "@/seqta/utils/Openers/OpenWhatsNewPopup";
import { showPrivacyNotification } from "@/seqta/utils/Openers/OpenPrivacyNotification";
import {
updateTimetableTimes,
} from "@/seqta/utils/updateTimetableTimes";
import { updateTimetableTimes } from "@/seqta/utils/updateTimetableTimes";
import { fixTimetableColours } from "@/seqta/utils/fixTimetableColours";
// JSON content
import MenuitemSVGKey from "@/seqta/content/MenuItemSVGKey.json";
@@ -96,7 +95,16 @@ export async function finishLoad() {
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();
}
}
@@ -254,6 +262,7 @@ async function LoadPageElements(): Promise<void> {
},
async () => {
await updateTimetableTimes();
await fixTimetableColours();
},
);
+22 -15
View File
@@ -173,28 +173,35 @@ async function updateStudentInfo(students: any) {
);
});
let houseelement1 = document.getElementsByClassName("userInfohouse")[0];
const houseelement = houseelement1 as HTMLElement;
const houseelement = document.getElementsByClassName("userInfohouse")[0] 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 {
let colorresult = GetThresholdOfColor(students[index]?.house_colour);
const colorresult = GetThresholdOfColor(student.house_colour);
houseelement.style.color =
colorresult && colorresult > 300 ? "black" : "white";
houseelement.innerText = students[index].year + students[index].house;
} catch (error) {
houseelement.innerText = students[index].house;
} catch (err) {
// Colour calculation failed, no text colour set
}
}
} else {
try {
houseelement.innerText = students[index].year;
} catch (err) {
houseelement.innerText = "N/A";
}
} else if (student.year) {
// No house, only year will be shown
text = student.year;
}
houseelement.innerText = text;
}
function createNewsButton(fragment: DocumentFragment, menu: HTMLElement) {
+10 -64
View File
@@ -1,25 +1,17 @@
import stringToHTML from "../stringToHTML";
import { settingsState } from "../listeners/SettingsState";
import { animate, stagger } from "motion";
import { DeleteWhatsNew } from "../Whatsnew";
import { openPopup } from "./PopupManager";
export function OpenAboutPage() {
const background = document.createElement("div");
background.id = "whatsnewbk";
background.classList.add("whatsnewBackground");
const container = document.createElement("div");
container.classList.add("whatsnewContainer");
var header: any = stringToHTML(
const header = stringToHTML(
/* html */
`<div class="whatsnewHeader">
<h1>About</h1>
<p>About the extension</p>
</div>`,
).firstChild;
).firstChild as HTMLElement;
let text = stringToHTML(/* html */ `
const text = stringToHTML(/* html */ `
<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" />
<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;">
</div>
</div>
`).firstChild;
`).firstChild as HTMLElement;
let footer = stringToHTML(/* html */ `
const footer = stringToHTML(/* html */ `
<div class="whatsnewFooter">
<div>
Resources and Feedback:
@@ -67,56 +59,10 @@ export function OpenAboutPage() {
</a>
</div>
</div>
`).firstChild;
`).firstChild as HTMLElement;
let exitbutton = document.createElement("div");
exitbutton.id = "whatsnewclosebutton";
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();
openPopup({
header,
content: [text, footer],
});
}
@@ -1,24 +1,5 @@
import { settingsState } from "./listeners/SettingsState";
import { animate, stagger } from "motion";
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();
});
}
import stringToHTML from "../stringToHTML";
import { openPopup } from "./PopupManager";
export function OpenMinecraftServerPopup() {
if (!document.querySelector('link[href*="minecraftia"]')) {
@@ -28,45 +9,36 @@ export function OpenMinecraftServerPopup() {
document.head.appendChild(fontLink);
}
const background = document.createElement("div");
background.id = "whatsnewbk";
background.classList.add("whatsnewBackground");
const container = document.createElement("div");
container.classList.add("whatsnewContainer");
var header: any = stringToHTML(
const header = stringToHTML(
/* html */
`<div class="whatsnewHeader">
<h1>Minecraft Server</h1>
<p>The official BetterSEQTA+ Minecraft Server</p>
</div>`,
).firstChild;
).firstChild as HTMLElement;
let imagecont = document.createElement("div");
imagecont.classList.add("whatsnewImgContainer");
const imageContainer = document.createElement("div");
imageContainer.classList.add("whatsnewImgContainer");
let video = document.createElement("video");
const video = document.createElement("video");
video.style.aspectRatio = "16/9";
video.style.background = "black";
let source = document.createElement("source");
const source = document.createElement("source");
source.setAttribute(
"src",
"https://raw.githubusercontent.com/BetterSEQTA/BetterSEQTA-Plus/main/src/resources/server-video.mp4",
);
video.autoplay = true;
video.muted = true;
video.loop = true;
video.appendChild(source);
video.classList.add("whatsnewImg");
imagecont.appendChild(video);
imageContainer.appendChild(video);
let textcontainer = document.createElement("div");
textcontainer.classList.add("whatsnewTextContainer");
let text = stringToHTML(/* html */ `
<div class="whatsnewTextContainer" style="height: 50%; overflow-y: scroll;">
const text = stringToHTML(/* html */ `
<div class="whatsnewTextContainer" style="height: 50%; overflow-y: hidden;">
<h1>Join our community in Minecraft!</h1>
<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;">
mc.betterseqta.org
</p>
<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;">
Version: 1.21.4
</p>
</div>
`).firstChild;
`).firstChild as HTMLElement;
let footer = stringToHTML(/* html */ `
const footer = stringToHTML(/* html */ `
<div class="whatsnewFooter">
<div>
Resources and Feedback:
@@ -144,59 +114,10 @@ export function OpenMinecraftServerPopup() {
<div>
</div>
</div>
`).firstChild;
`).firstChild as HTMLElement;
let exitbutton = document.createElement("div");
exitbutton.id = "whatsnewclosebutton";
container.append(
openPopup({
header,
imagecont,
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();
content: [imageContainer, text, footer],
});
}
@@ -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 { animate, stagger } from "motion";
import stringToHTML from "./stringToHTML";
import stringToHTML from "../stringToHTML";
import browser from "webextension-polyfill";
import kofi from "@/resources/kofi.png?base64";
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();
});
}
import { openPopup } from "./PopupManager";
export function OpenWhatsNewPopup() {
const background = document.createElement("div");
background.id = "whatsnewbk";
background.classList.add("whatsnewBackground");
const container = document.createElement("div");
container.classList.add("whatsnewContainer");
var header: any = stringToHTML(
const header = stringToHTML(
/* html */
`<div class="whatsnewHeader">
<h1>What's New</h1>
<p>BetterSEQTA+ V${browser.runtime.getManifest().version}</p>
</div>`,
).firstChild;
).firstChild as HTMLElement;
let imagecont = document.createElement("div");
imagecont.classList.add("whatsnewImgContainer");
const imageContainer = document.createElement("div");
imageContainer.classList.add("whatsnewImgContainer");
let video = document.createElement("video");
let source = document.createElement("source");
const video = document.createElement("video");
const source = document.createElement("source");
source.setAttribute(
"src",
@@ -53,19 +27,23 @@ export function OpenWhatsNewPopup() {
video.loop = true;
video.appendChild(source);
video.classList.add("whatsnewImg");
imagecont.appendChild(video);
imageContainer.appendChild(video);
/* let whatsnewimg = document.createElement("img");
//whatsnewimg.src = "https://raw.githubusercontent.com/BetterSEQTA/BetterSEQTA-Plus/main/src/resources/update-image.webp";
whatsnewimg.src = browser.runtime.getURL('../../resources/update-image.webp');
whatsnewimg.classList.add("whatsnewImg");
imagecont.appendChild(whatsnewimg); */
const text = stringToHTML(/* html */ `
<div class="whatsnewTextContainer" style="height: 50%;overflow-y: auto;">
let textcontainer = document.createElement("div");
textcontainer.classList.add("whatsnewTextContainer");
<h1>3.4.13 - Bug Fixes & Styling Improvements</h1>
<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>
let text = stringToHTML(/* html */ `
<div class="whatsnewTextContainer" style="height: 50%;overflow-y: scroll;">
<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>
@@ -133,7 +111,7 @@ export function OpenWhatsNewPopup() {
<li>Fixed discord icon colour in light mode</li>
<li>Fixed subject averages not showing up with letter grades</li>
<li>Tweaked compose UI</li>
<h1>3.4.4 - Bug Fixes and Improvements</h1>
<li>Added vertical zoom to the timetable</li>
<li>Fixed theme importing failing when images were included</li>
@@ -147,15 +125,15 @@ export function OpenWhatsNewPopup() {
<li>Fixed theme application in the creator</li>
<li>Performance improvements</li>
<li>Other minor bug fixes</li>
<h1>3.4.3 - Minor Bug Fixes</h1>
<li>Fixed a bug where timetable colours couldn't be changed</li>
<li>Other minor bug fixes</li>
<h1>3.4.2 - Minor Bug Fixes</h1>
<li>Fixed a bug where Assessment Average wasn't enabled by default</li>
<li>Fixed floating menus would sometimes be placed behind other elements</li>
<h1>3.4.1 - Bug Fixes and Performance Improvements</h1>
<li>Added a new "Subject Average" section to the assessments page</li>
<li>Fixed a bug where animations wouldn't play correctly</li>
@@ -164,7 +142,7 @@ export function OpenWhatsNewPopup() {
<li>Improved animation performance</li>
<li>Better Animations!</li>
<li>Minor style tweaks</li>
<h1>3.4.0 - Major Performance Update</h1>
<li>Completely rebuilt the extension popup using Svelte for dramatically improved performance</li>
<li>Added a brand new background store with search functionality and downloadable backgrounds</li>
@@ -173,10 +151,10 @@ export function OpenWhatsNewPopup() {
<li>Smoother animations and improved scrolling</li>
<li>Fixed Firefox compatibility issues</li>
<li>Other minor bug fixes and under the hood improvements</li>
<h1>3.3.1 - Hot Fix</h1>
<li>Fixed assessments not loading when no notices are available</li>
<h1>3.3.0 - Overhauled Theming System</h1>
<li>Added a theme store!</li>
<li>Added the new theme creator!</li>
@@ -190,12 +168,12 @@ export function OpenWhatsNewPopup() {
<li>Made animations toggle apply to settings</li>
<li>Small styling improvements</li>
<li>Other minor bug fixes</li>
<h1>3.2.7 - Minor Improvements</h1>
<li>Improved performance!</li>
<li>Fixed a bug where the icon wasn't showing up</li>
<h1>3.2.6 - Bug fixes and performance improvements</h1>
<li>Improved contrast for notifications</li>
<li>Added 12-hour time format toggle</li>
@@ -209,7 +187,7 @@ export function OpenWhatsNewPopup() {
<li>Enabled spellcheck inside of direct messages</li>
<li>Fixed timetable dates being misaligned</li>
<li>Other minor bug fixes and under the hood improvements</li>
<h1>3.2.5 - More Bug Fixes</h1>
<li>New direct message scroll animations</li>
<li>Added error message for brave browser shields breaking backgrounds</li>
@@ -218,7 +196,7 @@ export function OpenWhatsNewPopup() {
<li>Made settings panel auto size to height of screen</li>
<li>Fixed timetable dates not visible</li>
<li>Other minor bug fixes</li>
<h1>3.2.4 - Bug Fixes</h1>
<li>Added an open changelog button to settings</li>
<li>Fixed a memory overflow bug with Education Perfect</li>
@@ -226,74 +204,74 @@ export function OpenWhatsNewPopup() {
<li>Fixed news feed not loading</li>
<li>Fixed home items duplicating</li>
<li>Fixed Upcoming assessments not showing</li>
<h1>3.2.2 - Minor Improvements</h1>
<li>Added Settings open-close animation</li>
<li>Minor Bug Fixes</li>
<h1>3.2.0 - Custom Themes</h1>
<li>Added transparency (blur) effects</li>
<li>Added custom themes</li>
<li>Added colour picker history</li>
<li>Heaps of bug fixes</li>
<h1>3.1.3 - Custom Backgrounds</h1>
<li>Added custom backgrounds with support for images and videos</li>
<li>Overhauled topbar</li>
<li>New animated hamburger icon</li>
<li>Minor bug fixes</li>
<h1>3.1.2 - New settings menu!</h1>
<li>Overhauled the settings menu</li>
<li>Added custom gradients</li>
<li>Added HEAPS of animations</li>
<li>Fixed a bug where shortcuts don't show up</li>
<li>Other minor bugs fixed</li>
<h1>3.1.1 - Minor Bug fixes</h1>
<li>Fixed assessments overlapping</li>
<li>Fixed houses not displaying if they aren't a specific color</li>
<li>Fixed Chrome Webstore Link</li>
<h1>3.1.0 - Design Improvements</h1>
<li>Minor UI improvements</li>
<li>Added Animation Speed Slider</li>
<li>Animation now enables and disables without reloading SEQTA</li>
<li>Changed logo</li>
<h1>3.0.0 - BetterSEQTA+ *Complete Overhaul*</h1>
<li>Redesigned appearance</li>
<li>Upgraded to manifest V3 (longer support)</li>
<li>Fixed transitional glitches</li>
<li>Under the hood improvements</li>
<li>Fixed News Feed</li>
<h1>2.0.7 - Added support to other domains + Minor bug fixes</h1>
<li>Fixed BetterSEQTA+ not loading on some pages</li>
<li>Fixed text colour of notices being unreadable</li>
<li>Fixed pages not reloading when saving changes</li>
<h1>2.0.2 - Minor bug fixes</h1>
<li>Fixed indicator for current lesson</li>
<li>Fixed text colour for DM messages list in Light mode</li>
<li>Fixed user info text colour</li>
<h1>Sleek New Layout</h1>
<li>Updated with a new font and presentation, BetterSEQTA+ has never looked better.</li>
<h1>New Updated Sidebar</h1>
<li>Condensed appearance with new updated icons.</li>
<h1>Independent Light Mode and Dark Mode</h1>
<li>Dark mode and Light mode are now available to pick alongside your chosen Theme Colour. Your Theme Colour will now become an accent colour for the page.
Light/Dark mode can be toggled with the new button, found in the top-right of the menu bar.</li>
<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>
</div>
`).firstChild;
`).firstChild as HTMLElement;
let footer = stringToHTML(/* html */ `
const footer = stringToHTML(/* html */ `
<div class="whatsnewFooter">
<div>
Resources and Feedback:
@@ -321,63 +299,15 @@ export function OpenWhatsNewPopup() {
</a>
</div>
<div>
<a href="https://ko-fi.com/sethburkart" target="_blank" style="background: none !important; margin:0;margin-left:6px; padding:0; display: flex; align-items: center;">
<a href="https://ko-fi.com/sethburkart" target="_blank" style="background: none !important; margin:0;margin-left:6px;padding:0; display: flex; align-items: center;">
<img height="25" style="border:0px; height:25px; margin-right: -6px;" src="${kofi}" border="0" alt="Buy Me a Coffee at ko-fi.com" />
</a>
</div>
</div>
`).firstChild;
`).firstChild as HTMLElement;
let exitbutton = document.createElement("div");
exitbutton.id = "whatsnewclosebutton";
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();
openPopup({
header,
content: [imageContainer, text, footer],
});
}
+98
View File
@@ -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());
}
+126
View File
@@ -0,0 +1,126 @@
import { waitForElm } from "./waitForElm";
let timetableObserver: MutationObserver | null = null;
let isOnTimetablePage = false;
let isInitialized = false;
let abortController: AbortController | null = null;
let lastSnapshot: String = "";
function checkIfOnTimetablePage(): boolean {
return window.location.hash.includes("page=/timetable");
}
function startTimetableMonitoring(): void {
if (timetableObserver) return;
const timetablePage = document.querySelector(".timetablepage");
if (!timetablePage) return;
lastSnapshot = Array.from(
timetablePage.querySelectorAll("*"),
(el) => getComputedStyle(el).color,
).join("|");
// Create observer for timetable content changes
timetableObserver = new MutationObserver((mutations) => {
const snapshot = Array.from(
timetablePage.querySelectorAll("*"),
(el) => getComputedStyle(el).color,
).join("|");
if (snapshot !== lastSnapshot) {
// implement colour fix code here
lastSnapshot = snapshot;
}
});
timetableObserver.observe(timetablePage, {
childList: true,
subtree: true,
});
}
function handleUrlChange(): void {
const currentlyOnTimetable = checkIfOnTimetablePage();
if (currentlyOnTimetable !== isOnTimetablePage) {
isOnTimetablePage = currentlyOnTimetable;
if (isOnTimetablePage) {
// Wait a bit for the page to load, then start monitoring
setTimeout(() => {
startTimetableMonitoring();
}, 100);
} else {
stopTimetableMonitoring();
}
} else if (isOnTimetablePage) {
}
}
function startUrlMonitoring(): void {
if (isInitialized) return;
isInitialized = true;
// Create abort controller for cleanup
abortController = new AbortController();
const signal = abortController.signal;
// Listen for hash changes (more efficient than polling)
window.addEventListener("hashchange", handleUrlChange, { signal });
window.addEventListener("popstate", handleUrlChange, { signal });
// Initial check
handleUrlChange();
}
function stopTimetableMonitoring(): void {
if (timetableObserver) {
timetableObserver.disconnect();
timetableObserver = null;
}
}
function stopUrlMonitoring(): void {
if (!isInitialized) return;
isInitialized = false;
// Abort all event listeners at once
if (abortController) {
abortController.abort();
abortController = null;
}
stopTimetableMonitoring();
}
// Initialize monitoring on page load
if (typeof window !== "undefined") {
// Start URL monitoring immediately
startUrlMonitoring();
}
export async function fixTimetableColours(): Promise<void> {
const timetablePage = document.querySelector(".timetablepage");
if (!timetablePage) return;
// Wait for time elements to exist if page is still loading
try {
await waitForElm(".timetablepage .time", true, 10);
} catch {
return;
}
// Start continuous monitoring when this function is called
isOnTimetablePage = checkIfOnTimetablePage();
if (isOnTimetablePage) {
startTimetableMonitoring();
startUrlMonitoring();
}
}
// Cleanup function for when the module is unloaded
export function cleanup(): void {
stopUrlMonitoring();
}
+2
View File
@@ -30,6 +30,8 @@ export interface SettingsState {
subjectfilters: Record<string, any>;
transparencyEffects: boolean;
justupdated?: boolean;
privacyStatementShown?: boolean;
privacyStatementLastUpdated?: string;
timeFormat?: string;
animations: boolean;
defaultPage: string;