Compare commits

...

950 Commits

Author SHA1 Message Date
AdenMGB b535e87023 chore: update changelog 2026-06-05 10:00:38 +09:30
AdenMGB dd0830d349 feat: add new copy rubric button 2026-06-05 09:31:42 +09:30
AdenMGB b4a59330c5 fix: fix assement overview not choosing actuve subjects and improve styling 2026-06-05 09:18:33 +09:30
AdenMGB 314c555d87 fix: ensure the ability to override weightings 2026-06-05 08:55:06 +09:30
Aden Lindsay 24e38870a9 Merge pull request #446 from StroepWafel/SyncPFP
Sync PFP on change
2026-06-04 13:36:57 +09:30
StroepWafel 0878910043 Sync PFP on change 2026-06-04 12:44:34 +09:30
SethBurkart123 ce18412405 fix: text on analytics page reload button 2026-06-03 10:32:36 +10:00
AdenMGB 4f6c978043 finalise compat 2026-06-02 13:33:45 +09:30
AdenMGB 9093553ff1 compat: improve compat for nueromphic theme 2026-06-02 13:11:34 +09:30
AdenMGB acb2c682f3 tweak: tweak priv policy placement and style 2026-06-02 12:52:52 +09:30
SethBurkart123 3987871a6a fix: theme store stuck on loading skeleton after fetch failures
Harden theme list fetching with normalized API responses, timeouts, retries,
and a visible error state so the store no longer stays blank when messaging
or payloads fail (common after extension updates without a SEQTA tab reload).
2026-06-02 12:43:35 +10:00
SethBurkart123 9000cb28cd Replace analytics grade filter with dual-handle range slider.
Use a single track with min/max thumbs instead of two separate sliders for clearer filtering UX.
2026-06-02 12:32:07 +10:00
AdenMGB 337f85c3cc feat: prep fopr v3.7.0 as well as minor tweaks 2026-06-02 08:42:24 +09:30
AdenMGB 9e521722f1 Merge branch 'main' of https://github.com/BetterSEQTA/BetterSEQTA-Plus 2026-06-01 19:53:58 +09:30
AdenMGB 2b7c5e17b6 feat: analytics page 2026-06-01 19:43:47 +09:30
Aden Lindsay 21bc6078d7 Merge pull request #441 from StroepWafel/main
fix: TOTM
2026-06-01 18:37:59 +09:30
Aden Lindsay 6c966a8c38 Merge pull request #444 from StroepWafel/theme-stuff
feat: handlers for night city theme's features
2026-06-01 18:37:24 +09:30
StroepWafel 774be0ceed fix issues with injected sidebar 2026-06-01 14:16:38 +09:30
codefactor-io 2356a49fcd [CodeFactor] Apply fixes to commit 3d13202 2026-05-29 02:01:49 +00:00
StroepWafel efbc734d61 Merge branch 'main' of https://github.com/StroepWafel/BetterSEQTA-Plus 2026-05-29 11:30:05 +09:30
StroepWafel f5034ca0bc clean up TOTM popup. properly remember closes 2026-05-29 11:29:50 +09:30
StroepWafel a1d95ebd40 Update package.json 2026-05-29 11:02:36 +09:30
StroepWafel 943623e0fb Update OpenWhatsNewPopup.ts 2026-05-29 11:02:29 +09:30
StroepWafel a23eda1162 show TOTM popup until dismissed - forgotten commit
i forgot to commit this
basically just shows until dismissed even persistent over reloads so the user doesn't lose it the moment they navigate away or if they load the page and close it immediately
2026-05-29 10:55:55 +09:30
StroepWafel 3d13202779 feat: handlers for night city theme's features 2026-05-29 00:44:02 +09:30
StroepWafel 9f263c8c02 Update package.json 2026-05-28 15:17:38 +09:30
StroepWafel e8d1dadfa7 Update OpenWhatsNewPopup.ts 2026-05-28 15:16:43 +09:30
StroepWafel 7a3cbb50cc fix style 2026-05-25 13:49:46 +09:30
StroepWafel eb49e8d7f1 fix up TOTM 2026-05-25 13:26:33 +09:30
Aden Lindsay 65cd0a1c4f Merge pull request #435 from StroepWafel/improved-global-search
Improved global search
2026-05-25 13:18:28 +09:30
StroepWafel 0007b55c03 Merge branch 'main' into improved-global-search 2026-05-25 13:13:57 +09:30
StroepWafel 93e0a2b123 Update OpenWhatsNewPopup.ts 2026-05-25 13:11:46 +09:30
AdenMGB 1a8f025a04 feat: engage shortcuts on homepage 2026-05-24 17:30:03 +09:30
AdenMGB f0358bec07 feat: make assement overview for SEQTA Engage 2026-05-24 17:28:20 +09:30
AdenMGB 4f6916d8b3 feat: bring assement weighting to engage 2026-05-24 17:21:21 +09:30
AdenMGB fee79e8623 temp: disable global search on engage 2026-05-24 17:14:06 +09:30
AdenMGB 475b865000 feat: apply our exisitng icons to engage sidebar 2026-05-24 17:11:47 +09:30
SethBurkart123 304ce2e128 feat: refine startup announcement cards 2026-05-23 22:53:06 +10:00
AdenMGB 0bc6beb0f1 chore: bump ver & release notes 2026-05-23 09:08:31 +09:30
AdenMGB 68173a8b75 fix: fix custom teacher names not applying to popup 2026-05-23 08:58:21 +09:30
Aden Lindsay 7583d0ee47 Merge pull request #439 from StroepWafel/main
feat: Theme of the Month
2026-05-19 20:36:01 +09:30
codefactor-io 6c79fe3588 [CodeFactor] Apply fixes 2026-05-19 10:53:27 +00:00
StroepWafel c0a8a76105 feat: Theme Of The Month 2026-05-19 20:19:50 +09:30
StroepWafel 6ad214bb09 Merge branch 'main' of https://github.com/StroepWafel/BetterSEQTA-Plus 2026-05-18 20:29:35 +09:30
AdenMGB b4598668d4 feat: re enable message folders with improvments 2026-05-13 13:30:27 +09:30
AdenMGB 01e679eab6 Revert "fix: add some better detection logic for assements widget #429"
This reverts commit 01cd5d1428.
2026-05-06 17:31:41 +09:30
Aden Lindsay f57bd107b9 Merge pull request #436 from Jaxx7594/asessment-average-manual-input
Feat: ability to manually input weightings for assessments
2026-05-05 19:51:00 +09:30
Jaxon Lewis-Wilson aa5d193e55 assessmentsAverage: Fix inaccurate weight when a weight == N/A
N/A weights were automatically set to a weight of 1 for some reason. I removed it from the calculations completely with this commit.
2026-05-05 18:14:06 +08:00
Jaxon Lewis-Wilson da5bc7ab11 assessmentsAverage: Fix weight display upon setting override 2026-05-05 18:10:13 +08:00
Jaxon Lewis-Wilson b0857054eb assessmentsAverage: Fix unmarked/upcoming assessment indexing and weight display 2026-05-05 17:56:06 +08:00
Jaxon Lewis-Wilson f721bf6609 Revert "feat: dont inject weightings page in assements without results"
This reverts commit 2aecd63850.
Reverting so that I can solve the indexing issue. Only marked assessments are getting indexed, which is incorrect behaviour that slipped testing when the plugin was first made.
2026-05-05 16:32:12 +08:00
AdenMGB 2aecd63850 feat: dont inject weightings page in assements without results 2026-05-05 17:44:58 +09:30
Jaxx7594 f35520029f assessmentAverage: Remove remnant comment 2026-05-04 22:53:05 +08:00
Jaxx7594 95994fcd3a Merge branch 'main' into asessment-average-manual-input 2026-05-04 22:46:29 +08:00
Jaxon Lewis-Wilson 999f12e958 assessmentsAverage: Add changes to changelog 2026-05-04 22:39:53 +08:00
Jaxon Lewis-Wilson 260afac294 assessmentsAverage: Fix display of missing weighting, and minor change to override section. 2026-05-04 18:36:16 +08:00
Jaxon Lewis-Wilson 678a958351 assessmentsAverage: Add ability to override/set weighting per assessment. 2026-05-04 18:32:32 +08:00
SethBurkart123 608fc96c4e chore: temporarily disable message folders plugin and remove from changelog 2026-05-01 15:39:20 +10:00
StroepWafel 577478ba7e titles > Content 2026-05-01 14:34:15 +09:30
SethBurkart123 23ccac4836 update bun.lock 2026-05-01 12:15:53 +10:00
AdenMGB f6472ea9bd fix: add timeout lock to ensure completion of vecotrisation 2026-05-01 08:49:00 +09:30
StroepWafel f3f4491f04 fix @SethBurkart123 's comments 2026-04-30 21:17:48 +09:30
StroepWafel c987e4d54e patch: default to on
Forgot to make it default to on
2026-04-30 18:40:32 +09:30
StroepWafel cc7f2bc634 update what's new 2026-04-30 18:30:19 +09:30
StroepWafel 189a30a611 fix: various ui/ux improvements and duplicate rm'd 2026-04-30 18:20:19 +09:30
StroepWafel 710c03f463 Feat: Updated global search
- Add shared SEQTA fetch layer, extract helpers, passive JSON capture, many new index jobs
- Schema version + shared reset; auto-reset on extension update; fix manual reset (no dynamic import)
- Stabilize hybrid search: lexical title scoring, stale-query guard, vector guardrails, drop route from Fuse keys
- Improve passive titles/routing; tighten message/people handling
- Fix assignment/course indexing (lenient envelopes, subjects mode:list, student id fallback)
- Top bar: indexing label + stacked progress bar under quick search trigger
2026-04-30 17:09:34 +09:30
Aden Lindsay a875f35f1a Merge pull request #432 from StroepWafel/patch-3 2026-04-29 22:25:18 +09:30
StroepWafel e2cf9afbf9 Fix typos in WhatsNewPopup text 2026-04-29 22:05:46 +09:30
StroepWafel a1131cf6cd Merge branch 'main' of https://github.com/StroepWafel/BetterSEQTA-Plus 2026-04-29 21:47:11 +09:30
Aden Lindsay 3f493ac716 chore: add forgoten custom messages editor to whatsnew 2026-04-29 18:53:13 +09:30
Aden Lindsay e64ef7f95c Custom Message Folders (#431)
* feat: start custom messages plugin

* feat: finish custom message folders
2026-04-29 18:51:05 +09:30
AdenMGB c118b5b8dd chore: update whats new 2026-04-29 11:59:21 +09:30
AdenMGB 6535ec6932 feat: update whatsnew and update video 2026-04-29 11:59:03 +09:30
AdenMGB fba5d09c75 feat: theme flavours for theme varients 2026-04-29 11:13:32 +09:30
AdenMGB b88d29967d fix: ensure the theme store triggers the cloud sync upon install 2026-04-29 10:25:41 +09:30
AdenMGB 1b87d20a27 feat: auto install themes if not present locally with BS Cloud 2026-04-29 10:21:45 +09:30
Aden Lindsay 7bd3158b05 fix: fix wording 2026-04-29 09:56:00 +09:30
StroepWafel 7a4fa1e5bf feat: add RGB handler for alpine theme (#427)
* add handlers for individual Channels

* add notes

* patch fix theme overrides for adaptive colour

* idk

* Update package.json

* fix issue spelling

* Update OpenWhatsNewPopup.ts

* Update OpenWhatsNewPopup.ts

* fix: remove debug line from .gitignore

* chore: fix up patch notes to be a bit more user friendly

* chore: finalise patch notes and fix grammer

* Add empty line to .gitignore

---------

Co-authored-by: Aden Lindsay <140392385+AdenMGB@users.noreply.github.com>
2026-04-29 09:54:26 +09:30
Jaxon Lewis-Wilson f7d9199500 assessmentsAverage: Minor WEIGHT label styling fixes 2026-04-27 00:28:51 +08:00
AdenMGB 01cd5d1428 fix: add some better detection logic for assements widget #429 2026-04-23 17:26:58 +09:30
StroepWafel 5178408f39 Update OpenWhatsNewPopup.ts 2026-04-22 06:28:48 +09:30
StroepWafel 0b51db5434 Update OpenWhatsNewPopup.ts 2026-04-22 06:27:01 +09:30
StroepWafel 9c47fa38ae fix issue spelling 2026-04-22 06:25:47 +09:30
StroepWafel 5c4d7e1be3 Update package.json 2026-04-21 21:35:41 +09:30
StroepWafel acbbac8266 Merge branch 'main' into main 2026-04-21 21:34:17 +09:30
StroepWafel fa8f36f3d5 idk 2026-04-21 20:31:06 +09:30
SethBurkart123 fcc856e798 fix: resolve assessments overview failing to load in production builds
Replace dynamic import of ./ui with static import to prevent Vite from
code-splitting into a separate chunk that CRXJS cannot resolve at runtime.

Bumps version to 3.6.3.
2026-04-21 19:08:30 +10:00
StroepWafel 44116edca5 patch fix theme overrides for adaptive colour 2026-04-20 22:50:39 +09:30
StroepWafel 37be31859f add notes 2026-04-20 21:47:05 +09:30
StroepWafel 10667f17b4 Merge branch 'main' of https://github.com/StroepWafel/BetterSEQTA-Plus 2026-04-20 21:43:15 +09:30
StroepWafel 0ca0c7cf43 add handlers for individual Channels 2026-04-20 21:43:05 +09:30
SethBurkart123 a0038ac871 chore: bump version to 3.6.2 2026-04-20 15:53:20 +10:00
SethBurkart123 49824e9eab refactor: remove alarms permission, throttle cloud sync to once per day on page load 2026-04-20 15:47:22 +10:00
SethBurkart123 01eeb18638 perf: throttle theme update check to daily and remove redundant cloud poll on page load 2026-04-20 15:39:37 +10:00
SethBurkart123 3702443ece chore: bump version to 3.6.1 2026-04-20 15:33:04 +10:00
SethBurkart123 87ba75ff41 feat: simplify startup popups and convert engage announcement to toast
Remove auto-showing privacy statement and BS Cloud announcement from
startup queue. Convert SEQTA Engage announcement from a blocking modal
to a subtle bottom-right dismissable toast.
2026-04-20 14:59:24 +10:00
SethBurkart123 f9406fb469 feat: redesign Cloud settings UI and switch to OAuth redirect login
- Move Cloud section inline with other settings, remove dedicated header bar
- Replace in-extension login form with browser redirect to accounts.betterseqta.org
- Background script intercepts OAuth callback URL to capture tokens
- Add animated CloudPanel overlay (same pattern as ColourPicker)
- Hide cloud sync details and profile picture setting when not signed in
- Simplify CloudSettingsSync UI, reduce text verbosity
- Fix settings download to merge keys instead of clear+set
- Add legacy-to-plugin settings migration for cloud sync
- Shorten profile picture and default page descriptions
- Make DisclaimerModal title/message dynamic
- Update CloudHeader button styling to match other buttons
2026-04-20 13:42:49 +10:00
AdenMGB 690792fd62 chore: update chaneglog 2026-04-17 15:59:04 +09:30
AdenMGB f6ac112329 fix: fix the timetable edit plugin 2026-04-17 15:55:32 +09:30
AdenMGB ec68cec0ca feat: add smooth animation to notifications opening like settings 2026-04-17 15:51:25 +09:30
AdenMGB e2270602a3 feat: finish custom message folders 2026-04-17 15:39:58 +09:30
AdenMGB 8b1e5b2ee7 feat: start custom messages plugin 2026-04-16 20:22:00 +09:30
AdenMGB 44a029057a feat: auto download settings upon login 2026-04-13 19:42:11 +09:30
AdenMGB 249d1c1b4a feat: auto sync popoup 2026-04-13 19:39:57 +09:30
AdenMGB 6748b15024 feat: tweak engage popup 2026-04-13 19:33:28 +09:30
Aden Lindsay 8b26d07865 Merge pull request #426 from StroepWafel/main
update video (and edit notes)
2026-04-13 19:12:35 +09:30
Aden Lindsay 55d96a5e8f Update theme name in WhatsNewPopup 2026-04-13 19:12:18 +09:30
StroepWafel 133a5197aa Update update-video.webm 2026-04-13 19:10:32 +09:30
StroepWafel c0fa1576f3 Update update-video.webm 2026-04-12 23:28:30 +09:30
AdenMGB 48fbcde6ae fix: fix last two engage bugs 2026-04-12 20:14:52 +09:30
AdenMGB 89f50f774f feat: image full screen overlay for popoups 2026-04-12 20:06:03 +09:30
AdenMGB 1d9b8f3747 feat: queue popups and new engage popup 2026-04-12 19:54:43 +09:30
StroepWafel 0a5359df72 update video (and edit notes) 2026-04-12 19:19:19 +09:30
AdenMGB 2e9a643a8c fix: fix engage not always applying 2026-04-10 21:21:19 +09:30
AdenMGB 39d0b60024 feat: engage homepag 2026-04-08 11:27:38 +09:30
AdenMGB c7a6bf051c chore: bump ver and changelog 2026-04-08 08:49:37 +09:30
AdenMGB ea4a2c1ff0 feat: auto sync for cloud and fix some firefox weirdness 2026-04-08 08:29:25 +09:30
AdenMGB 71b7c9eb64 tweak: tweak cloud UI a lil bit 2026-04-08 07:40:20 +09:30
Aden Lindsay 0cac3022f5 Merge pull request #425 from StroepWafel/cloud-settings-backup
Cloud settings backup
2026-04-08 07:25:09 +09:30
StroepWafel 8b16a21d48 tweaks and fixes to UI 2026-04-07 22:39:09 +09:30
Aden Lindsay 2085ebe189 Upgrade project to Vite 8 + some cleanup (#424)
* Upgrade project to Vite 8

* Fix vite
2026-04-07 21:29:20 +09:30
AdenMGB 01c657d247 Merge branch 'main' of https://github.com/BetterSEQTA/BetterSEQTA-Plus 2026-04-07 21:25:15 +09:30
AdenMGB 1f26fb26d7 fix: somehwat fix overlap with vanilla seqta averages 2026-04-07 21:25:07 +09:30
Jones8683 dbd8e2be8e Merge branch 'main' of https://github.com/Jones8683/BetterSEQTA-Plus 2026-04-07 21:22:47 +09:30
Jones8683 6b5f00add0 Fix vite 2026-04-07 21:22:45 +09:30
Jones Jankovic 1f5eef2fb1 Merge branch 'BetterSEQTA:main' into main 2026-04-07 21:21:42 +09:30
StroepWafel 1e6e57ddcd feat: bs cloud header in settings, cloud pfp as local pfp option & doc updates
updates to docs and also profile
2026-04-07 21:15:29 +09:30
Aden Lindsay e18853ba3a Merge pull request #422 from StroepWafel/patch-2
Update README.md
2026-04-07 21:10:43 +09:30
Aden Lindsay 140cd66c9b chore: update old and non existant links 2026-04-07 21:09:55 +09:30
Jones8683 5684857456 Upgrade project to Vite 8 2026-04-07 20:13:47 +09:30
StroepWafel 24fee7a743 updates to docs and also profile 2026-04-07 20:02:30 +09:30
StroepWafel cef99b7278 Update README.md 2026-04-07 19:55:06 +09:30
StroepWafel aad5bcd97e Update README.md 2026-04-07 19:50:24 +09:30
StroepWafel 423aaa6b84 Update README.md 2026-04-07 19:41:45 +09:30
AdenMGB 97a1226eaf feat(engage): add icon-only sidebar support and Engage-specific titlebar styles
Made-with: Cursor
2026-04-07 15:11:26 +09:30
AdenMGB 72cab5905e fix(engage): disable assessments overview plugin on Engage
Made-with: Cursor
2026-04-07 15:11:15 +09:30
AdenMGB 088b745600 feat(engage): inject settings button, dark/light toggle, and user info with logout
Made-with: Cursor
2026-04-07 15:11:07 +09:30
AdenMGB 8801d5a435 feat(engage): fix loading screen hang and handle login/logout flow
Made-with: Cursor
2026-04-07 15:10:58 +09:30
Aden Lindsay b63a3d95f3 fix today's lessons tweaking out (#420) 2026-04-07 09:14:08 +09:30
StroepWafel eca8327420 fix today's lessons tweaking out 2026-04-07 09:05:13 +09:30
AdenMGB 05cf380e86 feat: some messed up stuff to fix some stuff 2026-04-07 08:46:28 +09:30
StroepWafel 73f005d645 Better theme displaying (#419)
* add more data

add more info for users about themes and also make related themes actually show related themes

* sorting and similar
2026-04-07 08:22:56 +09:30
StroepWafel f2fa9c39a9 Merge pull request #418 from StroepWafel/theme-updates
Theme updates
2026-04-07 08:13:27 +09:30
Aden Lindsay 783ff65fb5 Merge pull request #417 from StroepWafel/updated-sign-in
feat: Unified portaled sign-in overlay
2026-04-06 17:22:10 +09:30
Aden Lindsay 3e7ea3bc03 Adaptive custom themes (#416)
* feat: Smooth change in colour, no hard cut

Added option smoothing on colour change so there is no hard cut made when switching subjects

* feat: Themes can adapt to colour

* quick fix to forced mode as well

* [CodeFactor] Apply fixes

---------

Co-authored-by: codefactor-io <support@codefactor.io>
2026-04-06 17:21:38 +09:30
StroepWafel 398029eecd Merge branch 'main' into adaptive-custom-themes 2026-04-06 15:01:38 +09:30
StroepWafel a55cb84a69 feat: Smooth change in colour, no hard cut (#415)
Added option smoothing on colour change so there is no hard cut made when switching subjects
2026-04-06 14:58:09 +09:30
StroepWafel 94d54f65bf feat: Unified portaled sign-in overlay
Updated the sign-in overlay to be unified across the site, improving UX
2026-04-06 14:48:03 +09:30
codefactor-io 8123c5dd33 [CodeFactor] Apply fixes 2026-04-06 04:55:44 +00:00
StroepWafel ac1ee702ae quick fix to forced mode as well 2026-04-06 14:24:08 +09:30
StroepWafel e657152e3f feat: Themes can adapt to colour 2026-04-06 14:11:19 +09:30
StroepWafel f667ff9e9b feat: Smooth change in colour, no hard cut
Added option smoothing on colour change so there is no hard cut made when switching subjects
2026-04-06 13:39:25 +09:30
AdenMGB 3c613f4938 chore: bump ver 2026-04-03 10:55:41 +10:30
AdenMGB 04843a90fe fix: fix adaptive themeing to support current year subjects 2026-04-03 10:47:56 +10:30
AdenMGB 834d585ac7 chore: release ntoe 2026-03-29 20:26:19 +10:30
AdenMGB 343fa7ca9f feat: migrate pdfjs to local & bump ver 2026-03-29 20:25:06 +10:30
AdenMGB e049f34a5e feat: WIP Engage progress 2026-03-28 09:06:54 +10:30
AdenMGB d692f60291 fix: fix qr code to use safer methoed & bump ver 2026-03-25 08:48:47 +10:30
Jones Jankovic a0367be686 feat: Group settings better and make their descriptions more consistent (#405)
* Reorder and make description mroe consistent of settings

* Make soft gradient description consistent

* Consistent description for music plugin descriptionns

* Update src/plugins/built-in/profilePicture/index.ts

Co-authored-by: Aden Lindsay <140392385+AdenMGB@users.noreply.github.com>

* Update src/plugins/built-in/backgroundMusic/index.ts

Co-authored-by: Aden Lindsay <140392385+AdenMGB@users.noreply.github.com>

* Update src/plugins/built-in/backgroundMusic/index.ts

Co-authored-by: Aden Lindsay <140392385+AdenMGB@users.noreply.github.com>

* Update src/plugins/built-in/backgroundMusic/index.ts

Co-authored-by: Aden Lindsay <140392385+AdenMGB@users.noreply.github.com>

* Update src/plugins/built-in/backgroundMusic/index.ts

Co-authored-by: Aden Lindsay <140392385+AdenMGB@users.noreply.github.com>

* Update src/plugins/built-in/backgroundMusic/index.ts

Co-authored-by: Aden Lindsay <140392385+AdenMGB@users.noreply.github.com>

* Update src/plugins/built-in/backgroundMusic/index.ts

Co-authored-by: Aden Lindsay <140392385+AdenMGB@users.noreply.github.com>

* Update src/plugins/built-in/backgroundMusic/index.ts

Co-authored-by: Aden Lindsay <140392385+AdenMGB@users.noreply.github.com>

* Update src/plugins/built-in/profilePicture/index.ts

Co-authored-by: Aden Lindsay <140392385+AdenMGB@users.noreply.github.com>

* Formatting

* Formatting

* Formatting

* Formatting

* Formatting

* Prettier ignore

* Formatting

* format

* Update src/plugins/built-in/timetableEdit/index.ts

Co-authored-by: Aden Lindsay <140392385+AdenMGB@users.noreply.github.com>

* Update src/plugins/built-in/timetableEdit/index.ts

Co-authored-by: Aden Lindsay <140392385+AdenMGB@users.noreply.github.com>

* Surely thats it

* Finally

* Add back stuff

* fix: fix formatting

---------

Co-authored-by: Aden Lindsay <140392385+AdenMGB@users.noreply.github.com>
Co-authored-by: Aden Lindsay <lindsaya542@gmail.com>
2026-03-24 12:22:53 +10:30
Aden Lindsay aa6b15aa1b Merge pull request #410 from Jaxx7594/qr
Fix: QR code gen issues on Firefox
2026-03-21 09:10:44 +10:30
Jaxon Lewis-Wilson 08342c3873 Fix: QR code in embedded settings (firefox)
Firefox has an issue where a backdrop filter within a shadow dom breaks the drawing of the popup. This disables the filter only in the embedded menu to avoid this issue altogether.
2026-03-20 19:43:40 +08:00
Aden Lindsay 6527a33e38 Merge pull request #407 from Jaxx7594/background
Fix: Animated background race condition
2026-03-20 08:05:58 +10:30
Jaxon Lewis-Wilson 43f125e45d Fix: QR code gen in extension settings (firefox) 2026-03-19 23:24:51 +08:00
Jaxon Lewis-Wilson 49cc1e26c0 Fix: Animated background race condition 2026-03-19 23:09:53 +08:00
SethBurkart123 809a82f31d fix: update publish-extension 2026-03-18 13:01:55 +11:00
SethBurkart123 3c8c68ce2f fix: re-enable clicks on menu items during sidebar editing 2026-03-18 11:54:19 +11:00
SethBurkart123 18603026a3 fix: update aspect ratio for WhatsNew image 2026-03-18 11:52:27 +11:00
SethBurkart123 52d13cbdc2 fix: dropdowns on windows hopefully 2026-03-18 11:47:46 +11:00
SethBurkart123 26c04f1c24 feat: update changelog 2026-03-18 11:43:42 +11:00
SethBurkart123 e11e402c80 fix: improve dropdown styling on windows 2026-03-18 11:35:23 +11:00
SethBurkart123 bcc7d58ddd fix: keep compact sidebar items icon-only on focus 2026-03-18 11:31:50 +11:00
SethBurkart123 685c6ad771 fix: sidebar breaking on tab 2026-03-18 11:19:54 +11:00
SethBurkart123 4b0372aa56 fix: hide empty titlebar metadata 2026-03-18 10:45:12 +11:00
SethBurkart123 27aa28740e fix: reduce unnecessary notice modal scrolling 2026-03-18 10:45:12 +11:00
AdenMGB 6291b7d0a7 feat: add the RIGHT blurred update video 2026-03-18 10:07:14 +10:30
SethBurkart123 d9f0d89450 fix: align news loader animation 2026-03-18 10:29:50 +11:00
SethBurkart123 6ad221fcb5 fix: use iconfamily eye for timetable toggle 2026-03-18 10:26:56 +11:00
SethBurkart123 f1c55e127c fix: adaptive theme colour looking all wonky 2026-03-18 10:21:37 +11:00
SethBurkart123 2f6e551e22 fix: align home empty state sizing 2026-03-18 10:06:17 +11:00
SethBurkart123 6edffd0306 fix: icons not loading on seqta pages 2026-03-18 09:52:43 +11:00
SethBurkart123 50de668d01 style: add iconfamily icon to cloud signin 2026-03-18 09:52:30 +11:00
SethBurkart123 8a05d85344 fix: news not loading 2026-03-18 09:43:32 +11:00
SethBurkart123 915ce6f5f1 chore: clean up debug logging 2026-03-18 09:27:08 +11:00
SethBurkart123 67f98b13ad feat: add empty state to notices 2026-03-18 09:26:11 +11:00
SethBurkart123 2a147c1d3a fix: auto apply icon only sidebar on load 2026-03-18 09:17:53 +11:00
Aden Lindsay aae9aa6073 Merge pull request #403 from StroepWafel/main
actually do merge well
2026-03-18 08:46:00 +10:30
StroepWafel 9a9885066f actually do WISP correctly 2026-03-18 08:44:49 +10:30
StroepWafel 760d3349c2 Merge branch 'BetterSEQTA:main' into main 2026-03-18 08:43:55 +10:30
StroepWafel ec0dd70a4b ok ONL:Y wisp content this time 2026-03-18 08:42:02 +10:30
Aden Lindsay 4b2184955a Merge pull request #401 from BetterSEQTA/revert-400-main
Revert "automatic WISP content support"
2026-03-18 08:41:26 +10:30
StroepWafel 8d214ff6a3 Revert "Update monofile.ts"
This reverts commit 725d2b2987.
2026-03-18 08:41:17 +10:30
Aden Lindsay 441df9cdf2 Revert "automatic WISP content support" 2026-03-18 08:41:09 +10:30
SethBurkart123 e6e2789a82 fix: transparent background on rich text composer controls 2026-03-18 09:11:05 +11:00
Seth Burkart 70ceb50acd Merge pull request #400 from StroepWafel/main
automatic WISP content support
2026-03-18 09:08:02 +11:00
SethBurkart123 46d5c2e9fc fix: inter font overriden by seqta 2026-03-18 09:07:01 +11:00
StroepWafel 725d2b2987 Update monofile.ts 2026-03-18 08:36:09 +10:30
SethBurkart123 9581b793b5 feat: render floating popup at extension root 2026-03-18 08:44:20 +11:00
Seth Burkart 3fc3f1191c Merge pull request #399 from StroepWafel/hide-more-sensitive-info
Update hideSensitiveContent.ts
2026-03-18 08:19:07 +11:00
StroepWafel 098c79bc99 Update hideSensitiveContent.ts
hides more info
2026-03-18 01:56:42 +10:30
AdenMGB 45b558373b feat: bump to 3.5.0 and minor fixes 2026-03-17 21:38:10 +10:30
AdenMGB 3a2c438223 feat: adaptive themeing 2026-03-16 15:40:16 +10:30
AdenMGB 577287b8a8 feat: timetable editor plugin 2026-03-16 15:16:02 +10:30
Aden Lindsay 1d13b054ee Merge pull request #396 from AdenMGB/assement-overview
Assement overview improvements, notices fix and icon only sidebar
2026-03-15 10:59:35 +10:30
AdenMGB dc3423df13 feat: icon only sidebar 2026-03-15 10:58:48 +10:30
AdenMGB 9791454d62 fix: fix colouring on assement details result bars 2026-03-15 10:39:43 +10:30
AdenMGB 17f648f3ce fix: fix notices on the homepage not being scrollable 2026-03-15 10:30:22 +10:30
AdenMGB 7d89733f96 feat: add more sorting options to kaban view 2026-03-15 10:28:55 +10:30
Aden Lindsay a0e6bdfb20 Merge pull request #391 from BetterSEQTA/revert-390-fix-389
Revert "Fix notice modal scrolling on Home tab (#389)"
2026-03-10 08:26:13 +10:30
Aden Lindsay ac76ce3f03 Revert "Fix notice modal scrolling on Home tab (#389)" 2026-03-10 08:25:51 +10:30
Aden Lindsay 4bef51a3be Merge pull request #390 from jsodf78h823f/fix-389 2026-03-10 08:20:25 +10:30
FemtoClaw Bot 781171d60a Fix notice modal scrolling on home tab 2026-03-09 20:44:01 +00:00
AdenMGB c01342a86c feat: add desqta qr code instant sign in for schools without normal qr code 2026-03-08 09:18:04 +10:30
Aden Lindsay d73a9b2acf Merge pull request #384 from AdenMGB/theme-store
Online Enhanced Theme store
2026-02-25 12:59:59 +10:30
AdenMGB 1d3643a1fc chore: remove unused code 2026-02-25 12:46:36 +10:30
AdenMGB e50de00d08 feat: move bs cloud to a more out of the way location and some theme store tweaks 2026-02-25 10:55:36 +10:30
Alphons Joseph 8c87278850 Merge pull request #385 from AdenMGB/reload-fix
fix: fix bug in Vanilla SEQTA Causing unesasary reloads
2026-02-22 18:49:21 +08:00
AdenMGB 520da46daf fix: fix bug in Vanilla SEQTA Causing unesasary reloads 2026-02-22 19:23:48 +10:30
AdenMGB 01f5e8f61d fix: cf is very annoying 2026-02-20 19:13:06 +10:30
AdenMGB 2faef2ae8d fix: fix cf like too many times 2026-02-20 19:08:36 +10:30
AdenMGB 9d24d07c12 chore: appease codefactor AGAIN 2026-02-20 18:43:02 +10:30
AdenMGB d21ce90a5c feat: download & like count + UI tweaks and cleanup 2026-02-20 18:29:11 +10:30
AdenMGB 889175f3de chore: appease codefactor 2026-02-20 18:06:43 +10:30
AdenMGB 7a70b008c8 feat: betterseqta cloud for favouriting items and future stuff 2026-02-20 10:49:38 +10:30
AdenMGB 4b251e0ea4 feat: add github fallback 2026-02-20 10:28:13 +10:30
AdenMGB f242928682 feat: query the download api for download counts 2026-02-20 10:27:53 +10:30
AdenMGB d64962147a feat: implement cloud store 2026-02-20 10:27:17 +10:30
SethBurkart123 c2cd034556 feat: update to 3.4.16 2026-02-20 07:36:01 +11:00
SethBurkart123 1039ea8137 feat: update publish-browser-extension 2026-02-20 07:31:01 +11:00
Seth Burkart ead8cf80f3 Merge pull request #383 from Jaxx7594/fix_pdfjs_api_mismatch
fix: PDF.js api mismatch
2026-02-20 07:30:15 +11:00
Jaxon Lewis-Wilson 1f0b2d6627 fix: dynamically get the correct pdfjs worker version based on pdfjs package version 2026-02-19 18:07:56 +08:00
SethBurkart123 40d7ece12b fix: patch new SEQTA UI compatibility in 3.4.15 2026-02-19 20:32:14 +11:00
Seth Burkart 098ab27a01 Merge pull request #382 from AdenMGB/font-fix
fix: fix the dodge stying by seqta
2026-02-19 17:56:49 +11:00
SethBurkart123 170b1cf5c3 fix: patch sidebar item hover and font family 2026-02-19 17:56:34 +11:00
AdenMGB 004c3cc61d fix: fix the dodge stying by seqta 2026-02-19 17:01:19 +10:30
SethBurkart123 1b938e2748 feat: 3.4.14 update details 2026-02-16 13:44:15 +11:00
Seth Burkart 652e84783b Merge pull request #369 from StroepWafel/globalSearch-improvements
Global search improvements
2026-02-16 12:30:22 +11:00
StroepWafel 17c2685cae replaced loading font 2026-02-03 15:07:58 +10:30
Seth Burkart 5e89507276 Merge pull request #378 from Jones8683/main
Fix tutorial room not showing in the upcoming lessons block on homepage
2026-02-01 15:00:54 +11:00
Jones8683 0204b97de8 Merge branch 'main' of https://github.com/Jones8683/BetterSEQTA-Plus 2026-02-01 11:37:11 +10:30
Jones8683 d849951a66 fix: tutorial room doesn't show on upcoming lessons 2026-02-01 11:37:05 +10:30
Seth Burkart 1531afd046 Merge pull request #375 from Jones8683/main
Add tutorial room to home page and begin cleaning up some stuff
2026-02-01 10:45:58 +11:00
Jones Jankovic add8a90c77 Merge branch 'BetterSEQTA:main' into main 2026-02-01 09:51:38 +10:30
Jones8683 b9c3c2b5c5 Reset LoadHomePage.ts to upstream version 2026-02-01 09:51:11 +10:30
Seth Burkart 1cc34c38a8 Merge pull request #377 from StroepWafel/theme-docs
Update THEME_CREATION.md
2026-02-01 10:02:21 +11:00
Seth Burkart e5859f419a Merge pull request #374 from Jaxx7594/weightings
feat: Assessments Average weightings
2026-02-01 10:00:25 +11:00
Seth Burkart aadc295bdb Merge branch 'main' into weightings 2026-02-01 09:59:48 +11:00
Seth Burkart af2ef23078 Merge pull request #373 from Jaxx7594/icon-race
fix: Favicon race condition
2026-02-01 09:53:14 +11:00
StroepWafel 7150f03d77 Update THEME_CREATION.md 2026-01-31 21:46:36 +10:30
Jones8683 a381de7c9b final shorthand fix 2026-01-30 20:27:59 +10:30
Jones8683 ce6a5cfdc4 fix: background shorthand warning 2026-01-30 20:11:09 +10:30
Jones8683 de75468f2b most of the rest of the dups 2026-01-30 20:00:30 +10:30
Jones8683 011c1eddb4 remove more duplicates and slight css touch ups 2026-01-30 19:45:44 +10:30
Jones8683 c9443bad27 git please work 2026-01-30 19:29:14 +10:30
Jones8683 445aa9d071 Revert "resolve codefactor warnings about animation shorthand"
This reverts commit e0009ad8dc.
2026-01-30 19:26:38 +10:30
Jones8683 14a2e93b3a styff 2026-01-30 19:25:47 +10:30
Jones8683 3746b05af2 Reapply "Add back GPL license header to iframe.scss"
This reverts commit 1d215d8c75.
2026-01-30 19:19:07 +10:30
Jones8683 1d215d8c75 Revert "Add back GPL license header to iframe.scss"
This reverts commit 401947031b.
2026-01-30 19:17:50 +10:30
Jones8683 b4b1fed576 Merge branch 'main' of https://github.com/Jones8683/BetterSEQTA-Plus 2026-01-30 19:16:14 +10:30
Jones8683 e0009ad8dc resolve codefactor warnings about animation shorthand 2026-01-30 19:16:01 +10:30
Jones Jankovic 401947031b Add back GPL license header to iframe.scss 2026-01-30 16:41:39 +10:30
Jones Jankovic 9da8e104a8 Update src/seqta/utils/Loaders/LoadHomePage.ts
Co-authored-by: Aden Lindsay <140392385+AdenMGB@users.noreply.github.com>
2026-01-30 16:37:52 +10:30
SethBurkart123 3aef2312d0 feat: cleanup and comment removal 2026-01-30 16:07:12 +11:00
Jones8683 b402221477 fix build 2026-01-30 14:58:24 +10:30
Jones8683 8b6bda6dff more cleanup 2026-01-30 08:37:38 +10:30
Jones8683 eed8bac45a fix: clean up more duplicate selectors 2026-01-29 20:53:12 +10:30
Jones Jankovic 3b7bbc9bc6 Merge Codefactor fixes
Apply fixes from CodeFactor
2026-01-29 20:29:52 +10:30
codefactor-io 9820595a70 [CodeFactor] Apply fixes 2026-01-29 09:56:34 +00:00
Jones8683 0a33ca7f6e trigger codefactor for fork 2026-01-29 20:25:22 +10:30
Jones8683 bba96d5159 format: remove some duplicate selectors 2026-01-29 20:21:30 +10:30
Jones8683 d9fe70f442 fix: remove duplicate css block 2026-01-29 20:07:13 +10:30
Jones8683 32c5c8392b fix: tutorials show room as N/A even if room is specified on timetable 2026-01-29 19:40:28 +10:30
Jaxon Lewis-Wilson cc3f06b383 Small refactor (hopefully appease CodeFactor?) 2026-01-29 14:50:48 +08:00
codefactor-io 9ad90e9416 [CodeFactor] Apply fixes to commit 87fdda4 2026-01-28 16:08:12 +00:00
Jaxon Lewis-Wilson 87fdda459a Small refactor 2026-01-29 00:07:36 +08:00
Jaxx7594 6c11bb8143 Merge pull request #2 from StroepWafel/weightings
fix try catch error
2026-01-28 23:16:42 +08:00
Jaxon Lewis-Wilson 391fcfb9dd Various changes/fixes/polish
- Adds weight label below each assessment
 - Correlates assessment name with its ID, so each dom element can be identified: {trimmed_title: assessmentID}
 - Slightly changed regex to be a little more permissive
 - Changed pdfjs src/type due to CORS issues on firefox
 - Permitted weightings to be zero, but not less than zero
 - Modified how assessment items are iterated through, as the previous approach assumed they're in the same order as they are in react
 - Changed parseAssessments() to immediately dispatch parsing for all pdfs asynchronously, as doing it serially is painstakingly slow
 - Discard useless decimals when displaying weight (.0)
2026-01-28 23:06:02 +08:00
StroepWafel 355c5f2d46 fix try catch error 2026-01-28 19:38:20 +10:30
Jaxx7594 afdb4336f8 Merge pull request #1 from StroepWafel/weightings
Weightings
2026-01-28 16:01:51 +08:00
StroepWafel 7a04b22b22 Update index.ts 2026-01-28 17:21:32 +10:30
StroepWafel f594ed4902 i think i fixed it? 2026-01-28 17:19:08 +10:30
StroepWafel f1afa74ee6 Add average grade display and Fix CORS violation
Add average grade display and also fix the CORS violation caused by pdfjs trying to load PDFs from URLs that Firefox extensions can't access.

fixed by instead:
- Fetching the PDF as an ArrayBuffer directly from the URL
- Passing the ArrayBuffer to pdfjs using { data: arrayBuffer } instead of passing a URL
2026-01-28 16:36:16 +10:30
StroepWafel db3f0e0d81 Merge pull request #2 from Jaxx7594/weightings
feat: Assessments Average weightings parsing
2026-01-28 16:12:16 +10:30
Jaxon Lewis-Wilson aceefa16c0 feat: Assessments Average weightings parsing
ONLY PARSING SIDE IS COMPLETE. Does not factor into the average yet.
2026-01-28 13:25:58 +08:00
StroepWafel 89589fe3dc Merge branch 'BetterSEQTA:main' into globalSearch-improvements 2026-01-27 21:25:07 +10:30
Jaxon Lewis-Wilson 2afe2364e4 Narrowed trigger criteria
Instead of running every time document.head mutates, only run when mutation is an attribute change, target is a link, rel includes icon, and attribute is href.
2026-01-27 16:09:15 +08:00
Jaxon Lewis-Wilson 2c0f48877f Appease codefactor 2026-01-27 14:40:02 +08:00
Jaxon Lewis-Wilson 8791038bcf fix: Favicon race condition
Fixes a race condition due to the way the Tes logo has been implemented.
2026-01-27 14:31:33 +08:00
Alphons Joseph aba2ba5bfa Merge pull request #371 from BetterSEQTA/tutor-fix
fix [BUG] Seqta Tutor Lessons Color Not In Home Page And Assesments/Courses showing when not meant to
2026-01-24 18:02:42 +08:00
Alphons Joseph 2b01834765 Merge pull request #372 from BetterSEQTA/copilot/sub-pr-371
[WIP] Fix Seqta Tutor lessons color and course visibility
2026-01-24 12:27:58 +08:00
copilot-swe-agent[bot] 79c4fb511b Remove console.info(lesson) to prevent logging sensitive data
Co-authored-by: Crazypersonalph <93847055+Crazypersonalph@users.noreply.github.com>
2026-01-24 04:26:14 +00:00
Alphons Joseph 14823dcc91 Update src/seqta/utils/Loaders/LoadHomePage.ts
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-24 12:24:14 +08:00
copilot-swe-agent[bot] 9d3494eb56 Initial plan 2026-01-24 04:23:49 +00:00
Alphons Joseph 6af7c32c88 fix [BUG] Seqta Tutor Lessons Color Not In Home Page And Assesments/Courses showing when not meant to
Fixes #343
2026-01-24 12:14:39 +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
StroepWafel 1f49fa4bae Update src/plugins/built-in/globalSearch/src/indexing/actions.ts
Co-authored-by: Aden Lindsay <140392385+AdenMGB@users.noreply.github.com>
2026-01-22 19:35:26 +10:30
StroepWafel 86d9cfe50c Update src/plugins/built-in/globalSearch/src/core/index.ts
Co-authored-by: Aden Lindsay <140392385+AdenMGB@users.noreply.github.com>
2026-01-22 19:34:55 +10:30
StroepWafel ae59640162 Update src/plugins/built-in/globalSearch/src/indexing/actions.ts
Co-authored-by: Aden Lindsay <140392385+AdenMGB@users.noreply.github.com>
2026-01-22 19:34:30 +10:30
StroepWafel 5f935cd819 Update src/plugins/built-in/globalSearch/src/indexing/actions.ts
Co-authored-by: Aden Lindsay <140392385+AdenMGB@users.noreply.github.com>
2026-01-22 19:34:18 +10:30
codefactor-io 90e3a946bf [CodeFactor] Apply fixes 2026-01-22 08:42:29 +00:00
StroepWafel 51c940cdd9 Fixed CSS Preload Error 2026-01-22 19:07:48 +10:30
StroepWafel 07ff6d25ca indexing progressbar fixes 2026-01-22 19:05:39 +10:30
StroepWafel 89fd9bbd89 styles 2026-01-22 18:58:33 +10:30
StroepWafel c7033e61fb show indexing progress 2026-01-22 18:58:20 +10:30
StroepWafel 39f8cb1634 include all past assessments 2026-01-22 18:58:03 +10:30
StroepWafel 705c106da8 updates reset cache (clears dead cache from old updates causing issues)
YESS I FINALLY GOT DATABASE INDEXING WORKING TOO
2026-01-22 18:42:45 +10:30
StroepWafel 9b52bae404 update search to hopefully index correctly this time 2026-01-22 18:42:23 +10:30
StroepWafel 9a002d18b0 audit command 2026-01-22 18:42:13 +10:30
StroepWafel 3847ef4269 hopefully fix the issues 2026-01-22 18:41:42 +10:30
StroepWafel f0d0068a2e Add command for compile 2026-01-22 18:40:57 +10:30
StroepWafel b89a6c634c fix vector initialisation? 2026-01-22 11:37:09 +10:30
StroepWafel 29cfb4c792 improve global search 2026-01-22 11:28:43 +10:30
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
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
SethBurkart123 0fb05c7f26 bump(version): 3.4.9 2025-08-15 17:16:32 +10:00
SethBurkart123 b866dde6e2 style: file pills 2025-08-15 17:13:43 +10:00
SethBurkart123 a42d781955 perf: lazy loading improvements 2025-08-15 16:12:27 +10:00
SethBurkart123 b03e99faa2 perf: only load svelte app on click 2025-08-15 11:04:31 +10:00
SethBurkart123 c87cbce218 perf: only enable sensitive hider in devmode 2025-08-15 10:51:44 +10:00
SethBurkart123 0d6aa1e5fd perf: settingsstate storage performance improvements 2025-08-15 10:49:41 +10:00
SethBurkart123 a396aa8a9d perf: settingstate caching improvements 2025-08-15 10:44:14 +10:00
SethBurkart123 f3048d0cae feat: mc popup on update, improved styles 2025-08-15 10:37:33 +10:00
SethBurkart123 adb3beb2b1 perf: limit notice length in preview 2025-08-15 10:22:10 +10:00
Jones8683 860916a5b8 feat: apply rounded corners when dragging event 2025-08-13 10:16:05 +09:30
Seth Burkart 21e0b0a05e Merge pull request #330 from Jones8683/main
Advertisement of MC in extension
2025-08-07 08:56:59 +10:00
Jones8683 f7ca1c7ddd chore: update all code to correct repo 2025-08-05 17:56:20 +09:30
Jones8683 3fb70f280a feat: replace image with mc trailer vid 2025-08-05 17:50:07 +09:30
Jones8683 58b1a70cc9 feat: youtube link in popups 2025-08-05 14:58:48 +09:30
Jones8683 ce2b376469 feat: add version number 2025-08-05 09:32:19 +09:30
Jones8683 2ded9b3f83 fixes 2025-08-04 14:36:26 +09:30
Jones8683 a0e8fc2233 fix: images pull from repo fork 2025-08-04 13:46:50 +09:30
Jones8683 3527817ed1 feat: update list content 2025-08-04 12:47:24 +09:30
Jones8683 5cf0a928c9 fix: apply fix for all popups 2025-08-04 12:45:07 +09:30
Jones8683 ae84a22128 fix: official website icon not displaying properly in light mode 2025-08-04 12:42:44 +09:30
Jones8683 b16a48c26c feat: make new icon work for dark mode 2025-08-04 10:04:41 +09:30
Jones8683 ceb9424ab9 fix: use font instead of image for ip adress 2025-08-04 09:57:51 +09:30
Jones8683 52192002e7 fix: content not showing properly 2025-08-04 08:40:34 +09:30
Jones8683 4160f6ee10 new image 2025-08-03 20:32:44 +09:30
Jones8683 028c011a98 feat: finalise content 2025-08-03 20:31:05 +09:30
Jones8683 bb6bf7bfb2 fix errors 2025-08-03 20:10:35 +09:30
Jones8683 c5cef0c9a7 feat: image 2025-08-03 20:09:19 +09:30
Jones8683 e6d418d569 feat: template prepped for styling 2025-08-03 19:55:09 +09:30
Jones8683 c4ff994e38 feat: finalise icon 2025-08-03 19:41:35 +09:30
Jones8683 da9a1e8c0b feat: placeholder popup & icon 2025-08-03 19:00:50 +09:30
Jones8683 6eebb6911a feat: image for mc popup 2025-08-01 15:12:26 +09:30
Jones8683 c0271968e2 settings 2025-08-01 15:08:35 +09:30
Jones8683 871b893532 fix: popup casuing error, removed until ready 2025-08-01 14:55:23 +09:30
Jones8683 0cad870c28 feat: template for mc server popup 2025-08-01 14:54:00 +09:30
Jones8683 4f38a28d9c feat: prep for mc popup 2025-08-01 14:47:51 +09:30
Jones8683 f3029d6d9a Merge branch 'main' of https://github.com/jones8683/BetterSEQTA-Plus 2025-08-01 14:15:29 +09:30
Jones8683 10f67c8d60 feat: betterseqta.org in whats new and about page 2025-08-01 14:15:12 +09:30
Seth Burkart 9030f20540 Merge pull request #329 from Jones8683/main
Update about page for 14 contributers
2025-07-30 19:34:31 +10:00
Jones e12a724ab8 Update about page for 14 contributers 2025-07-30 13:47:42 +09:30
Seth Burkart b5f418938a Merge pull request #327 from NNIDNHU/main
Make new-contributor.md a yaml file to fit with other files
2025-07-23 09:34:45 +10:00
NNIDNHU 743deb9fe0 Delete .github/ISSUE_TEMPLATE/new-old-contributor.md 2025-07-22 14:01:41 +09:30
NNIDNHU a696f5b333 Update and rename new-contributor.md to new-old-contributor.md 2025-07-22 14:01:12 +09:30
NNIDNHU 397e440b6f Update new_contributor.yml 2025-07-22 14:00:47 +09:30
NNIDNHU a6f0e5bc55 Update new_contributor.yml 2025-07-22 13:59:57 +09:30
NNIDNHU cadb8f6269 Update new_contributor.yml 2025-07-22 13:59:13 +09:30
NNIDNHU 0f3f5fca83 Create new_contributor.yml 2025-07-22 13:57:50 +09:30
Seth Burkart e12fe43ed8 Merge pull request #324 from Jones8683/main
feat: Apply 12-hour time to more elements and restyle motd
2025-07-19 18:26:00 +10:00
Jones 5b94c2c9b5 fix: put back message box on dashboard 2025-07-18 16:11:24 +09:30
Jones f2d748baf9 Merge branch 'main' of https://github.com/jones8683/BetterSEQTA-Plus 2025-07-17 20:17:05 +09:30
Jones 5dfd738848 restyle motd 2025-07-17 20:16:53 +09:30
Jones e88b2e0404 Merge branch 'BetterSEQTA:main' into main 2025-07-16 18:08:41 +09:30
Jones 10f3c1e942 fix: remove ID that wont work 2025-07-16 18:07:57 +09:30
Jones 9911966fe7 feat: apply 12 hour time while event is being made and give rounded corners while creating new event 2025-07-16 17:58:31 +09:30
Seth Burkart d49f4c539c Merge pull request #323 from Jones8683/main
Add rounded corners for custom timetable events while editor is open
2025-07-16 18:03:43 +10:00
Jones 77074f085a Merge branch 'main' of https://github.com/jones8683/BetterSEQTA-Plus 2025-07-16 16:46:29 +09:30
Jones ae8b890282 feat: apply rounded corners to custom timetable events while they are being edited (It has a different ID to once its been added) 2025-07-16 16:45:39 +09:30
Jones 3d5aa7ebd9 Update README.md 2025-07-10 16:44:32 +09:30
SethBurkart123 7251e4eee5 fix: weird colouring on courses cover page button #303 2025-07-04 14:03:08 +10:00
SethBurkart123 ad0a329331 fix: auto read message on notification click 2025-07-02 06:39:40 +10:00
SethBurkart123 43a780de8e fix: retrieval of state 2025-07-02 06:34:45 +10:00
SethBurkart123 de9c6bc481 style: improve transparency effects 2025-07-01 16:09:34 +10:00
Seth Burkart bf1fe51e94 Merge pull request #321 from Jones8683/main
feat: apply 12 hour time to timetable details
2025-07-01 16:03:32 +10:00
SethBurkart123 1f3dea55bb fix: more reliable zoom buttons on timetable page 2025-07-01 13:15:11 +10:00
Jones8683 980432c501 feat: apply 12 hour time to timetable details 2025-06-30 19:42:33 +09:30
Seth Burkart b5c3a0fce8 Merge pull request #319 from Jones8683/patch-1
Fix grammar
2025-06-29 15:45:10 +10:00
SethBurkart123 64bc9e6cad fix: profile picture inverted with neumorphic theme 2025-06-29 15:14:17 +10:00
Jones 839366432e fix: add missing full stop to readme 2025-06-29 11:40:58 +09:30
SethBurkart123 e5a410ff58 docs: update email 2025-06-29 10:46:42 +10:00
SethBurkart123 26613beb02 docs: more comprehensive documentation 2025-06-29 09:44:39 +10:00
Seth Burkart db92af7405 Merge pull request #315 from Jones8683/main
fix: tell people to fork it from 'main' not 'master'
2025-06-25 13:49:58 +10:00
Jones 78909bc242 Merge branch 'BetterSEQTA:main' into main 2025-06-25 12:56:05 +09:30
SethBurkart123 b503363d64 fix: assessment tooltips on homepage 2025-06-25 09:46:35 +10:00
SethBurkart123 b69d5f47fc feat: update changelog 2025-06-25 09:17:23 +10:00
SethBurkart123 404d3c02f3 feat: new update video 2025-06-25 09:13:19 +10:00
Jones e28a3e1bc6 fix: missing word in changelog 2025-06-24 11:09:20 +09:30
Jones f1b7c3475e fix: tell people to fork it from 'main' not 'master'
since when was it called "master"???
2025-06-23 19:42:11 +09:30
SethBurkart123 964a026e7a feat: improved sensitive content hider (dev) 2025-06-23 12:33:37 +10:00
SethBurkart123 c7d9e1d955 feat: remove rounded corners on custom shortcut icons 2025-06-23 11:58:18 +10:00
SethBurkart123 e305b70035 chore: update changelog 2025-06-20 10:01:56 +10:00
SethBurkart123 c4140a2a9d feat: homepage styling improvements 2025-06-18 14:14:48 +10:00
SethBurkart123 566f326dce feat: modern and animated notices on homepage 2025-06-18 12:49:35 +10:00
SethBurkart123 c9fe4e0e1c feat: add loading animations to lessons on home page 2025-06-18 10:08:01 +10:00
SethBurkart123 fbd8d9e9e8 fix: Home page todays lessons not properly changing days #310 2025-06-18 09:41:21 +10:00
SethBurkart123 3f0d3f87fe fix: search bar searches being delayed 2025-06-13 10:59:15 +10:00
SethBurkart123 2292585e60 feat: add confetti and render overview with svelte 2025-06-13 09:06:56 +10:00
SethBurkart123 bb7c27dfea feat: hide empty overview columns + transparency effects support 2025-06-12 17:12:26 +10:00
SethBurkart123 8c1df8f829 fix (perf): background flickering on page load #305 2025-06-12 17:02:26 +10:00
SethBurkart123 7462e6ab5d fix: builds failing 2025-06-12 16:59:21 +10:00
SethBurkart123 66ff6e3468 fix: standalone settingsState not loading 2025-06-12 16:42:01 +10:00
SethBurkart123 8bd9b1dae7 perf: reduce timetable resource consuption and update notification collector 2025-06-12 16:24:28 +10:00
SethBurkart123 d377329bf9 chore: remove unused code 2025-06-12 15:15:34 +10:00
SethBurkart123 ec38502747 perf: only load vectorWorker when required 2025-06-12 15:15:07 +10:00
SethBurkart123 57b4daa9b7 feat: global search bug fixes and performance improvements 2025-06-12 14:45:36 +10:00
Seth Burkart fa37fe9d21 Merge pull request #307 from BlastedMeteor44/main
Added easter egg to AddBetterSEQTAElements.ts
2025-06-08 10:24:01 +10:00
SethBurkart123 ccb4354b26 feat: assessments overview mark as complete 2025-06-07 23:49:28 +10:00
SethBurkart123 841426d7ec fix: delayed flicker in 12 hour times on timetable 2025-06-07 18:24:52 +10:00
SethBurkart123 b9f0675c4f fix: oversized empty lessons image 2025-06-07 18:21:14 +10:00
BlastedMeteor44 8ff5fd8d2f Added easter egg to AddBetterSEQTAElements.ts
never gonna give you up shows if you toggle light/dark mode 10 times
2025-06-06 17:16:42 +08:00
SethBurkart123 ff01b44ead fix: flashing news sidebar button #303 2025-06-06 12:40:24 +10:00
SethBurkart123 72f7eeb935 fix: 12 hour time in timetable not applying correctly #303 2025-06-06 12:30:36 +10:00
SethBurkart123 a009f40ac2 feat: submitted column + bug fixes 2025-06-06 12:21:46 +10:00
SethBurkart123 c202af9688 feat: horizontal scrolling on timetable in homepage 2025-06-06 12:12:51 +10:00
SethBurkart123 7a91550de5 feat: cleanup assessmentsOverview 2025-06-06 11:52:24 +10:00
SethBurkart123 c4dc4b58b8 feat: assessment overview get letter grades from Assessment Averages plugin 2025-06-06 10:36:19 +10:00
SethBurkart123 d59802d4c3 fix: settings popup not appearing on disabled pages 2025-06-06 10:27:00 +10:00
SethBurkart123 074c2ff4bb feat: performance and visual improvements 2025-06-06 10:16:42 +10:00
Seth Burkart e94008efba Merge pull request #304 from Jones8683/main
Fix profile pic and add light mode styling
2025-06-05 23:18:03 +10:00
SethBurkart123 972783eb13 feat: assessments kanban overview 2025-06-05 23:14:34 +10:00
Jones 9af3ca4516 Merge branch 'BetterSEQTA:main' into main 2025-06-05 14:08:03 +09:30
Jones8683 60b4438552 remove e 2025-06-05 14:07:29 +09:30
Jones8683 d3dadad982 Fix profile pic and add light mode styling 2025-06-05 14:07:04 +09:30
SethBurkart123 bf01c0ca7b feat: custom shortcut icons #258 2025-06-04 21:42:32 +10:00
SethBurkart123 3821034a5c feat: profile picture add border 2025-06-04 17:13:12 +10:00
SethBurkart123 c218f184c0 feat: performance improvements to profile picture plugin 2025-06-04 16:14:54 +10:00
Seth Burkart 4b67736da2 Merge pull request #291 from AdenMGB/main
Add comments to code
2025-06-04 16:12:12 +10:00
Seth Burkart 8e107800f1 Merge branch 'main' into main 2025-06-04 16:12:05 +10:00
Seth Burkart f44d28c2b8 Merge pull request #301 from Jones8683/patch-1
Update injected.scss - Add rounded corners for music lessons/tutorial…
2025-06-04 16:10:36 +10:00
Seth Burkart 058cbb0bfa Merge pull request #300 from Jones8683/main
Remove "here" button and replace with contrib.rocks graph
2025-06-04 16:10:08 +10:00
SethBurkart123 9b13e7571a feat: profile picture plugin #256 2025-06-04 16:08:01 +10:00
SethBurkart123 6c12f5cf00 feat: apply 12 hour time to timetable #280 2025-06-04 15:44:51 +10:00
Jones df385775d9 Update injected.scss - Add rounded corners for music lessons/tutorials and custom events 2025-06-04 14:26:45 +09:30
Jones dce112d129 Merge branch 'BetterSEQTA:main' into main 2025-06-04 14:12:50 +09:30
SethBurkart123 f62d712549 fix: sidebar icons revert back to old style after reload #282 2025-06-04 12:02:00 +10:00
Jones8683 148559556a realign 'buy me a cofffeee! 2025-06-04 08:30:43 +09:30
Jones8683 0c2bdf36cf Align links for parity with openaboutpage.ts 2025-06-04 08:27:53 +09:30
Jones ee002991af Update injected.scss
remove CSS for old "here" button
2025-06-04 07:43:45 +09:30
Jones8683 c53de6ed8d Adjust alignment 2025-06-03 19:11:31 +09:30
Jones8683 98560af0a3 align links properly 2025-06-03 19:10:35 +09:30
Jones8683 f9fa334e40 fix 2025-06-03 18:58:02 +09:30
Jones8683 a7e250a86d commit 2025-06-03 18:57:00 +09:30
Jones8683 b3db85c565 Neaten pfps 2025-06-03 18:42:08 +09:30
Jones8683 c9d9611e3e Re-align 'All Contributers' to left 2025-06-03 18:32:51 +09:30
Jones Jankovic a1f480855e add contributer graph to openaboutpage.ts 2025-06-03 14:51:10 +09:30
Seth Burkart 9c3c63e497 Merge pull request #293 from Jones8683/main
Restyle "here" button in openaboutpage.ts
2025-06-03 13:35:37 +10:00
Jones 418c3c010e Delete Push Procedure.txt idk why this was here it was an acciadent 2025-06-02 19:13:00 +09:30
Jones Jankovic 4c55cf4331 Change name of class selector 2025-06-02 19:10:52 +09:30
Alphons Joseph 0b93223b84 capitalise here 2025-06-02 17:00:01 +08:00
Jones 84ab19eee7 Update injected.scss - fix indents 2025-06-01 10:13:24 +09:30
Jones 94b479519a Merge branch 'BetterSEQTA:main' into main 2025-06-01 10:11:34 +09:30
Seth Burkart af68eb5534 Merge pull request #298 from Jones8683/patch-1
Update README.md - Fix indents
2025-06-01 05:22:16 +10:00
Jones 94e527c73d Merge branch 'BetterSEQTA:main' into main 2025-05-31 19:45:22 +09:30
Jones 3b9f8124cf Update README.md - Fix indents 2025-05-31 19:39:03 +09:30
Seth Burkart 4c69ba7794 Merge pull request #295 from Jones8683/patch-1
Fix numbering in instructions
2025-05-31 19:50:25 +10:00
Jones 6e43be2a18 Update README.md - Fix numbering for instructions 2025-05-30 19:49:17 +09:30
Jones 37a13cba07 Update README.md - Fix numbering for instructions 2025-05-30 19:47:31 +09:30
Jones a855fbe9ec Update README.md - Fix spelling
Somehow "preferred" was misspelled three whole times
2025-05-30 18:45:26 +09:30
Jones8683 1206fb655d Update injected.scss - add styling for openaboutpage.ts 2025-05-30 12:33:40 +09:30
Jones8683 1ae9bd0652 Add about-here-link styles to injected.scss 2025-05-30 12:28:47 +09:30
Jones 280163111e Update OpenAboutPage.ts - Move css to injected.scss 2025-05-30 12:16:53 +09:30
Jones 59e195d2aa Update OpenAboutPage.ts - Fix coloring 2025-05-30 12:10:36 +09:30
Jones 37d62cf2a8 Neaten "here" button in openaboutpage.ts 2025-05-30 12:08:11 +09:30
Jones8683 6e5c3b4733 Neaten 'here' button 2025-05-30 12:05:23 +09:30
Seth Burkart ac9761286c Merge pull request #292 from Jones8683/main
Increase chunk size limit warning so that it doesn't appear
2025-05-30 11:59:51 +10:00
Jones d7fc7582d1 Remove chunk size warning 2025-05-30 09:57:22 +09:30
Aden Lindsay 425860c7cc Merge pull request #3 from AdenMGB/add-jsdoc-comments
Add jsdoc comments
2025-05-29 22:27:08 +09:30
google-labs-jules[bot] 69ac159bad I've added JSDoc comments to various files in the lib directory.
This change introduces JSDoc-style comments to several TypeScript and JavaScript files within the `lib` directory. These files primarily consist of Vite plugins, build scripts, and type definitions.

Comments were added or improved in:
- `lib/base64loader.ts`: I documented the Vite plugin for loading files as base64 data URLs.
- `lib/createManifest.ts`: I enhanced existing comments for functions that create extension manifest objects.
- `lib/inlineWorker.ts`: I documented the Vite plugin for bundling and inlining web worker scripts.
- `lib/utils.ts`: I added comments to utility types and the `createEnum` function, including a note on its type signature vs. runtime behavior.
- `lib/closePlugin.ts`: I documented the Vite plugin for handling build completion and exiting the process.
- `lib/publish.js`: I added comments to functions within the command-line script used for publishing the extension.
- `lib/touchGlobalCSS.ts`: I documented the Vite plugin for improving HMR reliability for global CSS files.
- `lib/types.ts`: I added comments to various type definitions, interfaces, and enum-like objects related to manifests, build configurations, and supported technologies.
2025-05-29 12:55:09 +00:00
google-labs-jules[bot] 4c93bcd0d7 I've added JSDoc comments to interface hooks and utils.
This change introduces JSDoc-style comments to several TypeScript files within your `src/interface` directory to improve code understanding and maintainability, focusing on hooks and utility functions.

- `src/interface/hooks/BackgroundDataLoader.ts`: I added comments to all exported functions and the `BackgroundDB` interface, detailing IndexedDB interactions for background image storage.
- `src/interface/hooks/SettingsPopup.ts`: I documented the public methods of the `SettingsPopup` singleton, which handles event notifications for settings popup closures.
- `src/interface/utils/themeImageHandlers.ts`: I added comments to all exported functions, explaining their roles in managing images within custom themes (uploading, removing, etc.).
- `src/interface/hooks/BackgroundUpdates.ts`: I documented the `BackgroundUpdates` singleton class and its methods, used for broadcasting generic background update events.
- `src/interface/hooks/ThemeUpdates.ts`: I documented the `ThemeUpdates` singleton class and its methods, responsible for broadcasting theme-related update events.
2025-05-29 12:41:31 +00:00
Aden Lindsay c596e65449 Merge pull request #2 from AdenMGB/add-jsdoc-comments
added JSDoc comments to background, plugin core, and settings fi…
2025-05-29 21:58:45 +09:30
google-labs-jules[bot] afdbfe3190 I've added JSDoc comments to background, plugin core, and settings files.
This change introduces JSDoc-style comments to several key areas of the extension to improve your code's understanding and maintainability:

- `src/background/news.ts`: I added comments to `fetchNews`, `fetchAustraliaNews`, and `rssFeedsByCountry` to explain news fetching logic.
- `src/plugins/core/manager.ts`: I added comprehensive JSDoc comments to the `PluginManager` class and its methods, detailing its role in the plugin lifecycle.
- `src/plugins/core/createAPI.ts`: I documented `createPluginAPI` (which creates the main API for plugins) and `createSettingsAPI` (responsible for plugin settings management, initially misidentified as `createPluginSettings`).
- `src/plugins/core/settingsHelpers.ts`: I added comments to functions that define the structure of plugin settings (e.g., `numberSetting`, `stringSetting`, `defineSettings`, `Setting` decorator), clarifying their definition-time role.
2025-05-29 12:27:47 +00:00
Aden Lindsay f1137763a6 Merge pull request #1 from AdenMGB/add-jsdoc-comments
Add JSDoc comments to various utility functions and core files.
2025-05-29 21:52:12 +09:30
google-labs-jules[bot] 074e73b0fd Add JSDoc comments to various utility functions and core files.
This change adds JSDoc-style comments to several functions and classes across the codebase to improve readability and maintainability.

Comments were added to:
- `src/SEQTA.ts`: Explained the `init()` function.
- `src/seqta/utils/waitForElm.ts`: Detailed the `waitForElm()` function, its parameters, and behavior.
- `src/seqta/utils/stringToHTML.ts`: Clarified the `stringToHTML()` function, including its sanitization and styling features.
- `src/seqta/utils/delay.ts`: Added a brief explanation for the `delay()` utility.
- `src/seqta/utils/mutex.ts`: Documented the `Mutex` class and its `acquire` method (renamed from `lock`), explaining its asynchronous locking mechanism and the role of the returned unlock function.
2025-05-29 12:19:57 +00:00
Seth Burkart fc4b121d30 Merge pull request #262 from ar-cyber/patch-27
add a type to bug and fr reports
2025-05-29 13:48:17 +10:00
Seth Burkart 55c48cbe5c Merge pull request #286 from BlastedMeteor44/main
OpenAboutPages.ts update to include all contributors
2025-05-29 13:47:56 +10:00
Seth Burkart 72b18dfb7d Merge branch 'main' into main 2025-05-29 13:47:32 +10:00
Seth Burkart d0cb352e74 Merge pull request #285 from Jones8683/main
Remove un-needed scrollbar in openaboutpage.ts
2025-05-29 13:46:34 +10:00
BlastedMeteor44 8fee6ddb76 OpenAboutPages.ts update to include all contributors 2025-05-28 10:42:33 +08:00
Jones 599f20e6d0 Add oxford comma 2025-05-28 10:00:22 +09:30
Jones 07af33eb78 Update OpenAboutPage.ts - Remove scrollbar to nowhere 2025-05-28 08:33:26 +09:30
Jones f03d25f918 Update OpenAboutPage.ts - Remove scrollbar to nowhere 2025-05-28 08:30:10 +09:30
SethBurkart123 1adb18ca42 feat: cleanup and changelog for update 2025-05-28 07:41:36 +10:00
SethBurkart123 134dfcb5a2 feat: remove background migration 2025-05-26 22:46:13 +10:00
SethBurkart123 c2d701266a feat: auto lesson navigation command 2025-05-26 22:43:38 +10:00
SethBurkart123 34024d70c2 feat: performance and visual improvements 2025-05-26 21:45:17 +10:00
SethBurkart123 70a1ebf881 feat: improved calculator 2025-05-26 20:40:55 +10:00
SethBurkart123 731ce42e74 feat: improved commands and interface for globalsearch 2025-05-26 17:19:06 +10:00
SethBurkart123 2749e07a1b feat: compose message command 2025-05-26 13:17:57 +10:00
SethBurkart123 0bed8b875b feat: visual improvements to search 2025-05-26 12:27:42 +10:00
SethBurkart123 35c005f347 fix: search button in incorrect placement 2025-05-26 12:05:34 +10:00
SethBurkart123 a251827c4b fix: themes always locking after reload 2025-05-26 11:52:59 +10:00
SethBurkart123 854c6ea826 fix: indexer saving infinite items, other improvements 2025-05-25 22:28:40 +10:00
SethBurkart123 cefeac95ea feat: major indexing performance improvements + visual fixes 2025-05-25 20:11:45 +10:00
SethBurkart123 e09eeccfee style: visual tweaks to settings page 2025-05-25 18:30:37 +10:00
SethBurkart123 991f80d316 feat: improved hotkey support and controls 2025-05-25 18:15:06 +10:00
SethBurkart123 f66340cb63 feat: add beta tag to global search plugin 2025-05-25 10:21:24 +10:00
SethBurkart123 fc288bdf01 fix: incorrect imports 2025-05-25 10:15:24 +10:00
SethBurkart123 244e667d90 fix: themes always forcing current mode 2025-05-23 12:34:17 +10:00
SethBurkart123 8adba647d8 feat: use web transitions api for themes 2025-05-23 12:30:43 +10:00
SethBurkart123 da3e11e208 fix: requiring reload on install to function 2025-05-22 23:15:19 +10:00
SethBurkart123 843a0a4c7a style: visual tweaks to courses page 2025-05-22 21:52:41 +10:00
SethBurkart123 b339745697 feat: beautiful modern visual tweaks 2025-05-22 19:06:39 +10:00
SethBurkart123 efdd03ce8e feat: make message items in search open the message 2025-05-22 14:49:13 +10:00
SethBurkart123 6846d945f2 fix: adjust boosting scores to work better 2025-05-21 10:18:16 +10:00
SethBurkart123 bff48f0397 feat: boosted active subjects + visual indication of inactive subjects 2025-05-21 00:01:36 +10:00
SethBurkart123 d8512e44cf feat: button item + search storage reset 2025-05-20 21:03:55 +10:00
SethBurkart123 25623339f8 feat: safer text highlighting 2025-05-20 20:43:16 +10:00
Seth Burkart 281842ea48 Merge pull request #274 from NIDNHU/patch-8
Update README.md - remove random dot point and update credits
2025-05-14 09:37:07 +10:00
SethBurkart123 eaf8ec51cd fix: incorrect usage and cleanup 2025-05-14 09:32:24 +10:00
Seth Burkart 65921845ec Merge pull request #264 from AdenMGB/main
Subject Searching in Global Search
2025-05-14 09:15:31 +10:00
StroepWafel 68d7861afa Update README.md - remove random dot point and update credits 2025-05-13 13:42:58 +09:30
Seth Burkart 2dcb6db3b5 Merge pull request #268 from NIDNHU/patch-7
Update OpenAboutPage.ts - fix grammar + adjust credits
2025-05-12 12:10:46 +10:00
StroepWafel 50b9218224 Update OpenAboutPage.ts 2025-05-11 19:01:32 +09:30
StroepWafel a4d2743f4c Update OpenAboutPage.ts - fix grammar + adjust credits 2025-05-11 14:24:27 +09:30
Seth Burkart 53074d5534 Merge pull request #267 from NIDNHU/patch-5
Update OpenAboutPage.ts - add code contribution welcome
2025-05-11 14:33:40 +10:00
StroepWafel 64ac9019a3 Update OpenAboutPage.ts - add code contribution welcome
stated that we are always looking for more contributors
2025-05-11 14:00:06 +09:30
AdenMGB 27f357cc82 fix(sorting): re-work sorting system to use api response more effectivly, and some styling improvements 2025-05-11 13:27:58 +09:30
codefactor-io ed767131ad [CodeFactor] Apply fixes 2025-05-09 10:53:51 +00:00
AdenMGB 7499880d9d fix(packages): remove old unused package 2025-05-09 20:18:18 +09:30
AdenMGB 908bf8c759 feat(globalSearch): subject course & assesment indexing and searchabilty 2025-05-09 20:12:22 +09:30
SethBurkart123 297c30dc98 fix: shortcuts relying on displayname not being removed 2025-05-09 10:11:27 +10:00
SethBurkart123 f4711ae3d4 style: fixed viewbox for shortcut links 2025-05-09 10:04:46 +10:00
Seth Burkart 97d3098fa3 Merge pull request #260 from NIDNHU/main
Update links.json - add more sites
2025-05-09 09:51:42 +10:00
StroepWafel 6ffacc83a7 Update AddShortcuts.ts - use DisplayName
made use DisplayName for name with fallback if value is null, empty or invalid
2025-05-08 21:52:09 +09:30
StroepWafel fa41542ec6 Update shortcuts.svelte - use "DisplayName" for displayname
made code use "DisplayName" in object for display name instead of object name, includes fallback in case the object is missing displayname
2025-05-08 21:32:59 +09:30
StroepWafel 7a19074c4f Update links.json - fix referencer viewbox 2025-05-08 21:25:00 +09:30
Alphons Joseph ad93a2eb54 fix Aesthetic bug in sidebar of BetterSEQTA+ #251 2025-05-08 19:41:21 +08:00
StroepWafel b8bc54f967 Update links.json - remove social medias 2025-05-08 08:56:13 +09:30
StroepWafel 11c30226f0 Update links.json - fix names and add displayname field 2025-05-08 08:34:50 +09:30
StroepWafel e0e4ba65c7 Update links.json - fix viewBoxes and names 2025-05-07 21:36:38 +09:30
StroepWafel 2c9d24355e Merge pull request #1 from BetterSEQTA/main
Merge changes for testing PR
2025-05-07 20:40:54 +09:30
SethBurkart123 c206e38ee2 fix: change shortcuts to rely on links list 2025-05-07 21:03:46 +10:00
SethBurkart123 87bf526dc6 feat: update job title 2025-05-07 20:28:39 +10:00
Andrew R 8f7a9b655a Update feature_request.yml 2025-05-07 19:55:48 +09:30
Andrew R 899ba46995 Update feature_request.yml 2025-05-07 19:55:02 +09:30
Andrew R ccb465cc2d add a type to bug and fr reports 2025-05-07 19:54:18 +09:30
StroepWafel 49b9428fbb Update links.json 2025-05-07 13:49:25 +09:30
StroepWafel 6d904ff6f9 Update links.json 2025-05-07 13:37:28 +09:30
StroepWafel 14424b167e Update links.json 2025-05-07 12:15:29 +09:30
StroepWafel da68d9628d Update links.json - add more sites
Added: 
- Deezer
- Google Classroom
- Reddit
- Instagram
- Snapchat
- Harvard referencing Generator
2025-05-07 12:00:57 +09:30
SethBurkart123 79ed998edf style: fix light mode gradients 2025-05-05 23:00:56 +10:00
SethBurkart123 364a5c2f22 style: major interface improvements 2025-05-05 22:58:15 +10:00
SethBurkart123 eeb63b5d1a feat: improved search results 2025-05-05 21:56:50 +10:00
SethBurkart123 9aef4c7204 style: remove "Custom Shortcut" overlay text 2025-05-05 20:31:23 +10:00
SethBurkart123 91035172d2 feat: visual improvements 2025-05-05 20:03:57 +10:00
SethBurkart123 d3d9b45caa feat: forums + improvements 2025-05-05 19:49:19 +10:00
SethBurkart123 0f9f618164 format: run prettify 2025-05-05 18:04:10 +10:00
SethBurkart123 771169348f feat: supporting improved assessments and improved parsing 2025-05-05 17:58:40 +10:00
SethBurkart123 ec42f1bb27 feat: improved job indexing 2025-05-05 17:09:44 +10:00
Seth Burkart cd247cfde4 Merge pull request #259 from NIDNHU/patch-4
fixed a couple of wording issues in FEATURE_REQUEST.yml
2025-05-05 11:08:57 +10:00
StroepWafel 44bf8efd71 fixed a couple of wording issues in FEATURE_REQUEST.yml
some wording made no sense so I fixed it
2025-05-05 09:47:12 +09:30
SethBurkart123 955213d577 fix: indexer not saving vectorized items properly 2025-05-04 12:01:03 +10:00
SethBurkart123 40924b5b33 fix: initial load not loading betterseqta 2025-05-04 07:22:24 +10:00
SethBurkart123 69b6b116a0 feat: update crxjs and remove extra included files 2025-05-04 07:20:45 +10:00
SethBurkart123 63a4bd4211 feat: refresh vector cache on complete 2025-05-03 20:40:39 +10:00
SethBurkart123 6ac54eae4b feat: add default enabled toggle 2025-05-03 20:17:53 +10:00
SethBurkart123 c791998b30 feat: migrate to embeddia (from local dir) 2025-05-03 19:27:18 +10:00
Seth Burkart 8a5100c06f Merge branch 'global-search' into main 2025-05-03 18:58:33 +10:00
Alphons Joseph cf2778951f feat: old scrolling sidebar system 2025-05-03 18:57:31 +10:00
StroepWafel 6fa1af2f68 Update feature_request.yml 2025-05-03 18:57:31 +10:00
StroepWafel b8286b6f22 fix validations indentation 2025-05-03 18:57:31 +10:00
StroepWafel 8466ef7691 fix feature_request.yml 2025-05-03 18:57:31 +10:00
StroepWafel d75959eeb1 fix feature_request.yml 2025-05-03 18:57:31 +10:00
StroepWafel 94a2f4ac34 Update and retype feature_request.md to feature_request.yml 2025-05-03 18:57:31 +10:00
StroepWafel 1647870186 Update bug_report.yml 2025-05-03 18:57:31 +10:00
StroepWafel b332de52ff Update bug_report.yml 2025-05-03 18:57:31 +10:00
StroepWafel daf7ea8e83 Update bug_report.yml 2025-05-03 18:57:31 +10:00
StroepWafel 341087b6a0 Update bug_report.yml 2025-05-03 18:57:31 +10:00
StroepWafel a0d8e05fd0 Update bug_report.yml 2025-05-03 18:57:31 +10:00
StroepWafel 399f68c547 Update bug_report.yml 2025-05-03 18:57:31 +10:00
StroepWafel 3ddf1d0c4f Update bug_report.yml 2025-05-03 18:57:31 +10:00
StroepWafel 10a6c458b1 Update bug_report.yml 2025-05-03 18:57:31 +10:00
StroepWafel 33825843b7 Update and retyped bug_report.md to bug_report.yml 2025-05-03 18:57:31 +10:00
SethBurkart123 56dabc8fd5 fix: news not loading 2025-05-03 18:57:31 +10:00
SethBurkart123 0c1a71f398 fix: sidebar layout not being applied on pageload #249 2025-05-03 18:56:50 +10:00
SethBurkart123 bb6ee72159 bump(version): 3.4.6.1 + changelog 2025-05-03 18:56:50 +10:00
SethBurkart123 d52a59ae48 fix: storage always resetting to default 2025-05-03 18:56:50 +10:00
SethBurkart123 1c9e361f78 fix: builds running incorrectly 2025-05-03 18:56:50 +10:00
SethBurkart123 ec3396c52e fix: dynamic seqta classes failing to load #248 2025-05-03 18:56:50 +10:00
SethBurkart123 5b5dba69dc fix: handle failed sortmessagepageitems 2025-05-03 18:56:50 +10:00
Andrew R 449b54ae32 Update README.md 2025-05-03 18:56:19 +10:00
codefactor-io c29dc45697 [CodeFactor] Apply fixes to commit d412762 2025-05-01 11:34:34 +00:00
SethBurkart123 d4127626b1 feat: cleaned code + improved performance 2025-05-01 21:34:17 +10:00
Alphons Joseph 907f970018 feat: old scrolling sidebar system 2025-04-28 21:32:08 +08:00
codefactor-io d9b1482255 [CodeFactor] Apply fixes to commit 454ab28 2025-04-13 00:58:22 +00:00
SethBurkart123 454ab283ab feat: improve results and performance (syncronous) 2025-04-13 10:58:05 +10:00
Seth Burkart 0ef43eb9b5 Merge pull request #254 from NIDNHU/main
complete revamp of bug report system
2025-04-13 09:06:55 +10:00
StroepWafel ecbdffbbde Update feature_request.yml 2025-04-13 00:05:07 +09:30
StroepWafel 92344400e1 fix validations indentation 2025-04-13 00:04:34 +09:30
StroepWafel ca20ba4e07 fix feature_request.yml 2025-04-13 00:02:15 +09:30
StroepWafel 694d4ea0a1 fix feature_request.yml 2025-04-13 00:01:35 +09:30
StroepWafel 72a529ee1d Update and retype feature_request.md to feature_request.yml 2025-04-13 00:00:31 +09:30
StroepWafel 0a3ee5c666 Update bug_report.yml 2025-04-12 23:50:50 +09:30
StroepWafel ef6176b6a4 Update bug_report.yml 2025-04-12 23:50:32 +09:30
StroepWafel b3c395cca1 Update bug_report.yml 2025-04-12 23:47:13 +09:30
StroepWafel 8c2539f130 Update bug_report.yml 2025-04-12 23:46:44 +09:30
StroepWafel 442ea04a2f Update bug_report.yml 2025-04-12 23:45:57 +09:30
StroepWafel bd812ffdae Update bug_report.yml 2025-04-12 23:45:28 +09:30
StroepWafel 6377a0c909 Update bug_report.yml 2025-04-12 23:44:57 +09:30
StroepWafel d8829d5716 Update bug_report.yml 2025-04-12 23:43:21 +09:30
StroepWafel 7fd85a5529 Update and retyped bug_report.md to bug_report.yml 2025-04-12 23:42:25 +09:30
SethBurkart123 9562368157 feat: vector search worker improvements 2025-04-11 07:10:55 +10:00
codefactor-io ab867af57d [CodeFactor] Apply fixes to commit 886d0a9 2025-04-10 14:07:58 +00:00
SethBurkart123 886d0a95f1 feat: add working workers with builds 2025-04-11 00:07:29 +10:00
SethBurkart123 dd47deb954 fix: news not loading 2025-04-09 14:46:57 +10:00
SethBurkart123 fbf066cea8 fix: sidebar layout not being applied on pageload #249 2025-04-06 14:03:51 +10:00
SethBurkart123 eb2c665843 bump(version): 3.4.6.1 + changelog 2025-04-03 22:28:50 +11:00
SethBurkart123 45a16de405 fix: storage always resetting to default 2025-04-03 22:24:25 +11:00
SethBurkart123 048ccb248e fix: builds running incorrectly 2025-04-03 14:43:07 +11:00
SethBurkart123 363fbfa3c8 fix: dynamic seqta classes failing to load #248 2025-04-03 14:35:06 +11:00
SethBurkart123 0bf4ed8157 fix: handle failed sortmessagepageitems 2025-04-03 13:05:33 +11:00
codefactor-io 814647e835 [CodeFactor] Apply fixes 2025-04-01 12:16:13 +00:00
SethBurkart123 07aa9524aa feat: early vector search testing 2025-04-01 23:14:45 +11:00
SethBurkart123 13f830ee16 feat: add custom items 2025-04-01 22:01:18 +11:00
SethBurkart123 1b4708261d feat: improve scrolling with calculator 2025-04-01 19:25:27 +11:00
SethBurkart123 6a556b6940 feat: further interface tweaks 2025-04-01 18:20:17 +11:00
codefactor-io d0edad8134 [CodeFactor] Apply fixes 2025-04-01 06:46:45 +00:00
SethBurkart123 5e93ae6e4b feat: add bottom bar 2025-04-01 17:45:30 +11:00
SethBurkart123 0788b78e73 feat: improve units 2025-04-01 17:24:18 +11:00
SethBurkart123 e884b0526b feat: improve searchbar 2025-04-01 16:02:54 +11:00
SethBurkart123 ea77224c75 feat: add calculator 2025-04-01 15:27:46 +11:00
SethBurkart123 18441712c9 feat: complete fuzzy search rebuild 2025-04-01 13:51:45 +11:00
Seth Burkart 3dc77dd398 Merge pull request #245 from ar-cyber/patch-26
make some updates to readme
2025-04-01 08:55:31 +11:00
Andrew R e7c5357c64 Update README.md 2025-04-01 06:47:49 +10:30
SethBurkart123 8df138a374 feat: upgrade to flexsearch for better keyword search performance 2025-03-31 23:11:41 +11:00
SethBurkart123 068e4ab778 style: further UI tweaks 2025-03-31 23:06:56 +11:00
SethBurkart123 adbba730c4 style: search styling improvements 2025-03-31 23:03:45 +11:00
SethBurkart123 1f3354c47b feat: add global search UI 2025-03-31 22:54:03 +11:00
SethBurkart123 7a80dc2cc3 docs: api reference improvements 2025-03-31 20:20:26 +11:00
SethBurkart123 68e8c89b35 docs: improve plugin documentation 2025-03-31 19:25:06 +11:00
Seth Burkart 77582a4d00 Merge pull request #230 from BetterSEQTA/en-masse-upgrade
Upgrade every package to respective newer versions
2025-03-31 18:43:55 +11:00
SethBurkart123 3f97049451 feat: update changelog 2025-03-31 18:42:23 +11:00
SethBurkart123 ebc7baaacc chore: remove unnecessary log 2025-03-31 18:40:18 +11:00
SethBurkart123 35ca292c04 feat: improve bkslider migration 2025-03-31 18:39:22 +11:00
SethBurkart123 e928399066 feat: add auto migration 2025-03-31 18:27:53 +11:00
SethBurkart123 a4033862c9 feat: interface clean up + organisation 2025-03-30 13:20:42 +11:00
codefactor-io 22ddb4bc41 [CodeFactor] Apply fixes to commit b8d8b10 2025-03-30 02:17:33 +00:00
SethBurkart123 b8d8b108c3 feat: improve apis + add animated background and assessment average plugins 2025-03-30 13:17:19 +11:00
SethBurkart123 aeaf5d9e59 style: improve button 2025-03-30 12:33:33 +11:00
codefactor-io 1acda4f399 [CodeFactor] Apply fixes 2025-03-30 01:11:57 +00:00
SethBurkart123 121888c1c3 feat: hide and group settings based on plugin 2025-03-30 12:11:40 +11:00
SethBurkart123 647a32fbac fix: settings props not being correctly set 2025-03-30 12:04:39 +11:00
SethBurkart123 19cc1a5600 dev 2025-03-30 10:41:19 +11:00
SethBurkart123 e3f4b59d9c docs: cleanup and improvements 2025-03-30 09:14:29 +11:00
SethBurkart123 a07323499c feat: add more callback listeners to the theme system 2025-03-30 09:11:41 +11:00
SethBurkart123 600456f28e chore: remove dev themes file 2025-03-30 09:09:55 +11:00
SethBurkart123 3ecd7205ed feat: add global theme toggle 2025-03-30 08:49:13 +11:00
SethBurkart123 6147e96cc9 feat: remove theme toggle 2025-03-29 22:33:06 +11:00
SethBurkart123 09855c9ef5 fix: themes randomly disabling after quick succesive page loads 2025-03-29 21:56:46 +11:00
SethBurkart123 9542cb13f5 fix: initial install not loading seqta 2025-03-28 17:18:23 +11:00
SethBurkart123 d19f573093 fix: theme creator fullscreen view not working 2025-03-28 12:29:26 +11:00
SethBurkart123 7af6acaf38 fix: themes custom colour not being completely applied 2025-03-28 12:17:37 +11:00
SethBurkart123 c4c50f2c30 fix: themes somtimes override default custom accent colour 2025-03-28 11:50:52 +11:00
SethBurkart123 a33f4f3f00 fix: imported themes without images rendering incorrectly 2025-03-28 11:38:52 +11:00
SethBurkart123 1f023574b8 fix: selected colour being lost if page is reloaded with themecreator 2025-03-28 00:36:28 +11:00
SethBurkart123 dc4499e8a2 refactor: remove legacy theme handling and streamline plugin initialization 2025-03-28 00:19:40 +11:00
SethBurkart123 ad2ad4d456 feat: debounce creator + general improvements 2025-03-28 00:14:29 +11:00
SethBurkart123 5413286f56 feat: improve dom application method 2025-03-27 23:45:04 +11:00
SethBurkart123 f0c5b1dace feat: build themes into a centralised plugin 2025-03-27 21:31:41 +11:00
Seth Burkart ad14dc3aa5 Merge pull request #242 from NIDNHU/patch-2
Create pull_request_template.md
2025-03-26 20:39:28 +11:00
SethBurkart123 64bf1d88e8 fix: background type error 2025-03-26 17:38:19 +11:00
SethBurkart123 7196a85f7d fix: downgrade to tailwindcss v3 because of issues 2025-03-26 17:35:35 +11:00
SethBurkart123 f2b594a13b fix: crxjs plugin issues 2025-03-26 17:00:58 +11:00
Seth Burkart a17a9a50c1 Merge pull request #232 from ar-cyber/patch-25
Update README.md
2025-03-26 15:57:15 +11:00
StroepWafel 207832640f Create pull_request_template.md
add template for creating pull requests
2025-03-26 14:49:07 +10:30
Seth Burkart b76999cb13 Merge pull request #239 from NIDNHU/main
Update SECURITY.md - add quick-create link
2025-03-25 15:54:22 +11:00
StroepWafel fc0e491ea7 Update SECURITY.md - add quick-create link 2025-03-25 11:04:19 +10:30
SethBurkart123 68159ddd0e chore: hide test plugin 2025-03-21 17:59:28 +11:00
Seth Burkart 4696529964 Merge pull request #238 from NIDNHU/main
Create config.yml
2025-03-21 16:57:19 +11:00
StroepWafel a9e198ea68 Create config.yml 2025-03-21 09:39:51 +10:30
Seth Burkart 620d168d28 Update creating-plugins.md 2025-03-18 22:19:32 +11:00
SethBurkart123 1c63c06b72 feat: add docs and dev plugins 2025-03-18 22:15:44 +11:00
Alphons Joseph 7a76d3f4eb bugfix: Finally fix theme application 2025-03-18 19:03:23 +08:00
Alphons Joseph 8e34db4a67 feat: synchronise settingstate and theme properly 2025-03-18 18:45:09 +08:00
Alphons Joseph 9fc24767ec bugfix: theme defaultColor being overridden at all times by default betterseqta+ colour 2025-03-18 18:37:34 +08:00
Seth Burkart 331c9a9d81 Merge pull request #236 from BetterSEQTA/main
Merge main into dev branch
2025-03-18 20:47:15 +11:00
SethBurkart123 74e92ddb53 feat: add types to storage api 2025-03-18 20:45:29 +11:00
Seth Burkart 1a6dc9ebb9 Merge pull request #235 from NIDNHU/patch-1
Update SECURITY.md - modify vulnerability reporting method
2025-03-18 18:38:04 +11:00
Alphons Joseph be54816d83 Merge branch 'en-masse-upgrade' of https://github.com/BetterSEQTA/BetterSEQTA-Plus into en-masse-upgrade 2025-03-18 15:37:15 +08:00
Alphons Joseph b644dbbbc7 feat: convert base64 in browser to url reference 2025-03-18 15:37:12 +08:00
SethBurkart123 d06356101a feat: display plugin settings in interface 2025-03-18 18:31:20 +11:00
StroepWafel 7eacf345d0 Update SECURITY.md
change vulnerability reporting method
2025-03-18 18:00:36 +10:30
Alphons Joseph 9a71a5241a vuln-fix: removed image urls, relying on blobs now 2025-03-18 15:23:04 +08:00
Alphons Joseph f4ae9098d8 bug: change theme export to json to avoid accidental execution 2025-03-18 14:59:32 +08:00
Alphons Joseph 325f6c5f9b handle vulnerabilities privately through github instead of in issues 2025-03-18 14:49:56 +08:00
SethBurkart123 ea46ab41ce fix: update types 2025-03-18 07:54:50 +11:00
codefactor-io e6f36edabf [CodeFactor] Apply fixes to commit 587aa5e 2025-03-17 20:52:33 +00:00
SethBurkart123 587aa5eb89 feat: add plugin system 2025-03-18 07:52:16 +11:00
Alphons Joseph da3a680455 refactor: small code quality update 2025-03-17 20:54:40 +08:00
Alphons Joseph 77c3761947 codefix: comment out unused function (may be required later) 2025-03-17 20:35:17 +08:00
Alphons Joseph 6fb4ea5372 feat min: fix spelling mistake 2025-03-17 19:23:59 +08:00
SethBurkart123 5c0044a4d4 feat: cleanup work on plugins system 2025-03-17 15:06:26 +11:00
Andrew R dba688d3cd Update README.md 2025-03-17 13:49:14 +10:30
SethBurkart123 75446c6855 chore: clean up imports in monofile.ts 2025-03-17 13:55:29 +11:00
SethBurkart123 fe2fa87cb5 feat: add ReactAdaptor.svelte 2025-03-17 13:46:38 +11:00
SethBurkart123 9f7b46d2ad feat: add back react colour picker 2025-03-17 13:45:16 +11:00
SethBurkart123 ef890ee776 feat: add dev colourpicker with irojs 2025-03-17 13:33:25 +11:00
Alphons Joseph d42dc79415 feat: sourcemaps are an env variable now 2025-03-17 10:17:42 +08:00
Alphons Joseph e072b3f5c8 feat: remove sourcemaps from production build, add to new development build 2025-03-17 08:15:04 +08:00
Alphons Joseph e32218bf07 include sourcemaps for better debugging 2025-03-16 20:55:14 +08:00
Alphons Joseph 286375c662 remove last remnants of react 2025-03-12 22:56:24 +08:00
Alphons Joseph f2d197e8f0 Merge branch 'en-masse-upgrade' of https://github.com/BetterSEQTA/BetterSEQTA-Plus into en-masse-upgrade 2025-03-12 22:52:17 +08:00
Alphons Joseph 85beb62a37 remove react colour picker (@SethBurkart123 needs prettifying, works on basic level) 2025-03-12 22:52:14 +08:00
codefactor-io 0b908cb251 [CodeFactor] Apply fixes to commit c9f0f9c 2025-03-12 13:45:46 +00:00
Alphons Joseph c9f0f9cf16 start modularisation and breaking down the monofile 2025-03-12 21:45:23 +08:00
Alphons Joseph 3c65e6d6c5 dynamically import all plugins 2025-03-12 20:11:26 +08:00
Alphons Joseph 2cb607c5a9 commenting 2025-03-12 19:08:20 +08:00
codefactor-io 695357a639 [CodeFactor] Apply fixes 2025-03-12 11:02:58 +00:00
Alphons Joseph 8cb052f2ff Merge branch 'en-masse-upgrade' of https://github.com/BetterSEQTA/BetterSEQTA-Plus into en-masse-upgrade 2025-03-12 19:02:36 +08:00
Alphons Joseph 6b39f60db7 very very very basic plugin system works 2025-03-12 19:02:32 +08:00
SethBurkart123 1638dd4989 feat: remove postcss 2025-03-12 21:48:58 +11:00
SethBurkart123 ca7e6b9137 feat: upgrade to tailwindcss v4 2025-03-12 21:46:01 +11:00
SethBurkart123 1263c1c8ef feat: remove colour pallete flattening 2025-03-12 20:57:33 +11:00
SethBurkart123 5eb92bc87a fix: builds failing and css failing to load in frontend 2025-03-12 20:52:48 +11:00
Seth Burkart ecff10a991 Merge pull request #229 from NIDNHU/main
Edit bug reporting system
2025-03-12 16:09:44 +11:00
StroepWafel 4745df7ace Merge branch 'BetterSEQTA:main' into main 2025-03-12 10:19:27 +10:30
Alphons Joseph c7bdd86967 code commenting 2025-03-11 20:44:39 +08:00
Alphons Joseph f920980948 change to an eye icon 2025-03-11 20:20:42 +08:00
Alphons Joseph 8c2f36033f add support for hiding non-assessments (discord issue) 2025-03-11 20:13:44 +08:00
Alphons Joseph 75e687f934 bump every package, remove postcss 2025-03-11 19:53:56 +08:00
Seth Burkart 5cd0f47fe5 feat: move firefox out of experimental 2025-03-08 20:18:33 +11:00
StroepWafel 84cfaccded Update vulnerability.md
update example for issue explanation and make prompts a bit clearer
2025-03-08 17:59:25 +10:30
StroepWafel 0c55098bc7 Update feature_request.md
ask user to link to bug if exists (clean up bug report) and provide reference images for graphical changes
2025-03-08 17:56:58 +10:30
StroepWafel 50157f24fd Update bug_report.md
make the wording a bit more clear
2025-03-08 17:54:41 +10:30
Seth Burkart b77e2b2247 Merge pull request #226 from BlastedMeteor44/main
Update README.md
2025-03-06 17:33:03 +11:00
BlastedMeteor44 0c0fabe661 Update README.md 2025-03-06 10:34:47 +08:00
SethBurkart123 f39bfce5c3 feat: auto collapsing alignment toolbar in direct messages 2025-03-04 22:06:57 +11:00
SethBurkart123 2d26f729e3 fix: news source country starting with lowercase 2025-03-04 18:59:51 +11:00
SethBurkart123 d7b541c814 feat: remove hover animation for tabbedcontainer + fix publish script with detailed versioning 2025-03-04 18:54:54 +11:00
Seth Burkart 41bb5996df Merge pull request #220 from ar-cyber/patch-24
fix: remove ABC news from news page #219
2025-03-04 17:14:20 +11:00
Andrew R d3d7a1199f fix: remove ABC news from news page 2025-03-03 13:00:58 +10:30
SethBurkart123 3277b02dfb feat: kofi + update dompurify function 2025-02-27 17:20:27 +11:00
SethBurkart123 4703d68bac bump(version): 3.4.5 + changelog 2025-02-27 16:58:14 +11:00
SethBurkart123 696043e01a feat: add alternative news feed sources 2025-02-24 18:41:03 +11:00
SethBurkart123 24ef85c39e feat: add warning if betterseqta is installed 2025-02-24 17:49:17 +11:00
SethBurkart123 35fc996e37 style: improved dark mode colour picker inputs in theme creator 2025-02-24 16:57:20 +11:00
SethBurkart123 edb0a0f929 feat: add fullscreen custom css editor 2025-02-24 16:51:40 +11:00
SethBurkart123 5051d04451 feat: remove maximum width from theme creator 2025-02-24 16:11:34 +11:00
SethBurkart123 ffc695f022 style: improved compose UI tweaks 2025-02-24 16:06:10 +11:00
SethBurkart123 ca5d232e47 feat: reduce permissions for pageState script 2025-02-24 15:52:29 +11:00
Seth Burkart 44325f0d49 fix: update bug_report.md #214 2025-02-24 15:43:22 +11:00
SethBurkart123 c446217916 feat: notifications open the message #10 2025-02-23 21:54:59 +11:00
SethBurkart123 a51049154b feat: complete react fiber control loop 2025-02-23 20:49:09 +11:00
SethBurkart123 f41da95f7e feat: magic button that crashes chrome tabs (yes rly idk why) 2025-02-23 17:54:58 +11:00
SethBurkart123 3e405cc453 feat: html syntax highlighting for more strings 2025-02-21 17:41:09 +11:00
SethBurkart123 d3ae21b7fa fix: page may fail to load due to shortcut links function failing 2025-02-21 17:40:38 +11:00
SethBurkart123 6247e17d70 dev: add dependency cruiser to help visualise and cleanup imports 2025-02-21 17:37:42 +11:00
Seth Burkart 550f2cab54 Merge pull request #208 from ar-cyber/patch-21
Fix subject averages
2025-02-19 17:31:08 +11:00
Andrew R ddb94e6b07 Update SEQTA.ts 2025-02-19 16:52:16 +10:30
Andrew R 12270d28b9 Update package.json 2025-02-19 16:51:45 +10:30
Andrew R 639d35b2f5 mod: change name of toggle to "Letter Grade Averages" 2025-02-19 16:51:04 +10:30
Andrew R 410bd0e54e Update SEQTA.ts 2025-02-19 11:53:10 +10:30
Andrew R c1bc3d3d22 Update package.json 2025-02-19 08:46:47 +10:30
Andrew R 17b093b5ea fix: A BLOODY PERCENT WAS MISSING 2025-02-19 06:51:16 +10:30
Andrew R c8330091ca Update SEQTA.ts 2025-02-19 06:46:03 +10:30
Andrew R 2a00344243 fix incorrect variable 2025-02-19 06:41:48 +10:30
Andrew R cd2c98bd65 fix: implement toggle for letter/number averages 2025-02-19 06:39:55 +10:30
Andrew R 81b690ec9a Update general.svelte 2025-02-19 06:36:36 +10:30
Andrew R 13095cef19 fix: fix codefactor complaints 2025-02-18 21:14:10 +10:30
Andrew R 36ecbd37ed Update SEQTA.ts 2025-02-18 21:00:26 +10:30
Seth Burkart 2cc5ce3f1a Update feature_request.md 2025-02-18 17:06:32 +11:00
Seth Burkart 14aa511198 Update bug_report.md 2025-02-18 17:06:19 +11:00
Andrew R 4e397e3c57 fix: comp errors 2025-02-18 11:32:30 +10:30
Andrew R af311d9b3e Update SEQTA.ts 2025-02-18 10:44:35 +10:30
Andrew R 3af28f574b Update SEQTA.ts 2025-02-18 10:41:34 +10:30
Andrew R 9f1c3e3bc8 fix: only just figured out that parseFloat is for str property only 2025-02-18 10:38:33 +10:30
Andrew R e7df2abc6d fix: add logic for diving / grades 2025-02-18 10:35:18 +10:30
Andrew R c7ae2e1ab6 fix: round the average to 5 so indexing works with other numbers 2025-02-18 10:26:08 +10:30
Andrew R a0888eb091 Update SEQTA.ts 2025-02-18 10:18:17 +10:30
Andrew R e8d9dc7a6b whoops im still in python mode 2025-02-18 10:17:27 +10:30
Andrew R 32934593d8 edit: need a debugger because of issues 2025-02-18 10:13:56 +10:30
Andrew R 855d979b7f Update SEQTA.ts 2025-02-18 10:06:57 +10:30
Andrew R 083dfad5c2 Update SEQTA.ts 2025-02-18 10:05:16 +10:30
Andrew R 9de863be02 incl: add the letter grades to the subject average 2025-02-18 10:03:22 +10:30
SethBurkart123 9d7dab84f1 feat: remove background migration 2025-02-14 18:04:38 +11:00
SethBurkart123 a6999051c4 fix: add back publish-browser-extension for auto publishing 2025-02-14 17:58:39 +11:00
SethBurkart123 c35855559b feat: update changelog 2025-02-14 17:50:37 +11:00
SethBurkart123 8972a5a8bf fix: theme exports and imports sometimes failing due to attached images 2025-02-14 17:49:31 +11:00
SethBurkart123 a321a482cc feat: show manual colour picker tools only during theme creation 2025-02-14 17:15:45 +11:00
SethBurkart123 5f561f516c feat: rely on package.json for version and description 2025-02-14 17:11:33 +11:00
SethBurkart123 395ec3291e fix: custom sidebar layouts not applying on page load #205 2025-02-14 17:07:45 +11:00
SethBurkart123 96b17c7eeb feat: update changelog 2025-02-14 16:37:04 +11:00
SethBurkart123 fad50e6eba feat: change order of zoom timetable buttons 2025-02-14 16:36:17 +11:00
Alphons Joseph f74ad97c0a Merge branch 'main' of https://github.com/BetterSEQTA/BetterSEQTA-Plus 2025-02-11 19:42:46 +08:00
Alphons Joseph 7f4e6cf5ec create function to collapse sidebar 2025-02-11 19:42:41 +08:00
SethBurkart123 677f17c418 fix: colour of timetable buttons in dark mode 2025-02-11 22:06:01 +11:00
Alphons Joseph e58584a55a Merge branch 'main' of https://github.com/BetterSEQTA/BetterSEQTA-Plus 2025-02-11 19:04:04 +08:00
Alphons Joseph 59444dc904 patch timetable not being centred upon zoom 2025-02-11 19:03:47 +08:00
SethBurkart123 178c4fdef4 feat: update changelog 2025-02-11 22:02:01 +11:00
SethBurkart123 cdaaceade7 fix: vite hanging after completing builds 2025-02-11 21:53:37 +11:00
SethBurkart123 d65bfa8c46 feat: add zoom scaling to timetable page #202 2025-02-11 21:40:57 +11:00
SethBurkart123 694d11477d fix: timetable quickbar arrow when placed above recieving incorrect colour 2025-02-11 19:23:11 +11:00
SethBurkart123 61e1bcdae9 fix: timetable clipped at 4pm 2025-02-11 19:05:25 +11:00
SethBurkart123 23a09004d8 feat: keep theme enabled after editing 2025-02-11 19:03:19 +11:00
SethBurkart123 3ce075cd47 fix: theme disabling when opening editor #204 2025-02-11 18:59:57 +11:00
SethBurkart123 92a51daf36 fix: codemirror failing to run 2025-02-11 18:55:57 +11:00
SethBurkart123 479b2878a9 fix: builds failing in some cases + extension failing to load due to vite legacy api 2025-02-11 18:19:07 +11:00
SethBurkart123 18ffa1b47d feat: clean up eventmanager class 2025-02-11 18:03:48 +11:00
Alphons Joseph 6098cf9608 FR #203 2025-02-10 19:49:43 +08:00
Alphons Joseph 5fde2a3660 fix broken build process 2025-02-06 21:17:26 +08:00
SethBurkart123 e4d5f7fd3f chore: remove unused dependencies 2025-02-06 17:45:44 +11:00
Alphons Joseph 31b069056d General updates and 2025-02-05 19:43:51 +08:00
SethBurkart123 3e5ebe8ef4 feat: change theme store to use marquee image for everything 2025-02-05 17:13:36 +11:00
SethBurkart123 338292ac15 style: remove gradient colours on toolbar buttons 2025-02-05 11:20:54 +11:00
SethBurkart123 187c484901 feat: add parsing for letter grades #191 2025-02-05 11:08:26 +11:00
SethBurkart123 24d0616110 feat: clean up compose buttons 2025-02-04 09:58:46 +11:00
SethBurkart123 260ac4aaea style: improved compose UI 2025-02-04 09:48:06 +11:00
SethBurkart123 4311a8fe76 chore: minor css cleanup 2025-02-04 09:09:20 +11:00
SethBurkart123 251e09941b chore: remove unused notices network request and add Svelte module declaration 2025-02-04 09:05:54 +11:00
Seth Burkart bb1541ab2d Merge pull request #195 from ar-cyber/patch-18
fix: anything before this update is now unusable
2025-02-03 16:41:31 +11:00
Andrew R 1c6ec3ee91 fix: anything before this update is now unusable
As a major bug was found impacting some functionality, it is reasonable to push this to make every other version deprecated.
2025-02-03 12:35:19 +10:30
SethBurkart123 0ef0078fb7 bump(version): 3.4.3 + changelog 2025-02-03 13:00:09 +11:00
SethBurkart123 834b8b41af chore: clean up source files 2024-12-05 18:10:32 +11:00
SethBurkart123 f1512ba6e1 chore: clean up code 2024-12-05 14:43:12 +11:00
SethBurkart123 13fc077686 bump(version): 3.4.2 + changelog 2024-12-05 14:38:02 +11:00
SethBurkart123 7cf765121c fix(style): z-index of panels increased 2024-12-05 14:36:04 +11:00
SethBurkart123 4e393f14bb fix: enable assessments average by default 2024-12-05 14:35:23 +11:00
Seth Burkart 98347e038d Merge pull request #192 from ar-cyber/patch-16
Add mention for grades calc in readme
2024-12-03 17:23:35 +11:00
Andrew R f2bdb22ea8 fix(doc): make the readme mention the grade calculator 2024-12-03 13:52:16 +10:30
SethBurkart123 4afab2c52a perf: refactor AddBetterSEQTAElements for improved performance 2024-12-03 07:15:50 +11:00
SethBurkart123 4c6b43d7c7 fix(initial): make assessments average enabled by default 2024-12-03 06:59:06 +11:00
SethBurkart123 9e26d2c192 fix(changelog): versions and changelog fixed 2024-12-02 17:34:39 +11:00
SethBurkart123 7445e8be78 feat(changelog): update changelog + bump version 2024-12-02 12:30:25 +11:00
SethBurkart123 d1a876ff22 fix(home): sidebar button sometimes not selecting 2024-12-02 12:25:14 +11:00
SethBurkart123 e2176ea2fa fix(assessments): placing subject average trice 2024-12-02 12:23:47 +11:00
SethBurkart123 a999e4384b feat(notices): add animations to notices 2024-12-02 12:17:39 +11:00
SethBurkart123 4bf5420140 fix(eventManager): not triggering for already created elements' 2024-12-02 12:17:28 +11:00
SethBurkart123 8fb29f7f21 feat(settings): add subject average setting 2024-12-02 12:01:03 +11:00
SethBurkart123 44e3ed34d0 feat(assessments): add subject averages 2024-12-02 11:58:42 +11:00
SethBurkart123 32228ee4db style(changelog): reduce video padding for better screen realestate 2024-12-02 11:11:49 +11:00
SethBurkart123 1692bd3e92 fix(home): notices date picker not working 2024-12-02 11:11:03 +11:00
SethBurkart123 71cf9dbca8 feat(messages): improvements to direct message animations 2024-12-02 11:01:54 +11:00
SethBurkart123 372b591b16 feat(home): async home page loading 2024-12-02 10:58:24 +11:00
SethBurkart123 7578ecee74 chore(logging): improve logging 2024-12-02 09:27:33 +11:00
SethBurkart123 a2b4f81b86 feat(home): add skeleton loaders to homepage 2024-12-02 09:26:49 +11:00
SethBurkart123 fcd95f6823 perf(homePage): add fragmentation and refactor code 2024-11-30 21:55:10 +11:00
SethBurkart123 f2ea7c8104 fix: builds failing 2024-11-29 17:53:47 +11:00
SethBurkart123 379a3ebda0 chore: code cleanup 2024-11-29 17:34:16 +11:00
SethBurkart123 d1850e8ddb chore: fix CI/CD pipeline 2024-11-29 17:24:02 +11:00
SethBurkart123 88a87692cd fix(icon): discord icon scaled incorrectly 2024-11-29 17:18:42 +11:00
Seth Burkart 4e6e4870b0 Merge pull request #189 from MEGA-Dawg68/main
Added Discord icon
2024-11-29 17:15:53 +11:00
SethBurkart123 18f215fa5f chore: code cleanup 2024-11-29 17:14:32 +11:00
SethBurkart123 2ea8ada439 fix(popups): backdrop filter failing to appear during animation 2024-11-29 17:13:59 +11:00
SethBurkart123 34b2501617 chore(changelog): update changelog 2024-11-29 14:57:41 +11:00
SethBurkart123 88d4d3aa11 feat(news): add animations to news page 2024-11-29 14:57:17 +11:00
SethBurkart123 5ed3a05f6a perf(news): improved news page performance 2024-11-29 14:52:26 +11:00
SethBurkart123 430f158957 style(news): improved news styles 2024-11-29 14:38:41 +11:00
SethBurkart123 547caabc45 bump(version): 3.4.1 + changelog 2024-11-29 14:25:58 +11:00
SethBurkart123 f6e549c5da feat(animations): refine home page animations 2024-11-29 11:40:37 +11:00
SethBurkart123 dc1ae9c0a1 feat(animations): migrate to motion-one@11 2024-11-29 11:32:43 +11:00
SethBurkart123 34306e77cf fix(studentInfo): some cases year may be undefined 2024-11-29 10:29:35 +11:00
SethBurkart123 00c9f03827 bump: version to 3.4.0.4 2024-11-27 09:23:40 +11:00
SethBurkart123 6c93477998 fix: background migration not working properly 2024-11-27 09:22:34 +11:00
MEGA_Dawg68 9784b6162f Added Discord icon 2024-11-25 19:57:45 +08:00
SethBurkart123 96cf8e3eac feat: clean up CI/CD piplelines 2024-11-15 17:24:26 +11:00
SethBurkart123 0e23ea0cc3 fix: build running twice in CI/CD 2024-11-15 17:23:34 +11:00
SethBurkart123 7dfe347562 chore: move install to build section 2024-11-15 17:14:15 +11:00
SethBurkart123 21a8472c94 fix: themes failing to install from coverImage decoding error 2024-11-15 16:35:28 +11:00
358 changed files with 77608 additions and 22710 deletions
+397
View File
@@ -0,0 +1,397 @@
/** @type {import('dependency-cruiser').IConfiguration} */
module.exports = {
forbidden: [
{
name: "no-circular",
severity: "warn",
comment:
"This dependency is part of a circular relationship. You might want to revise " +
"your solution (i.e. use dependency inversion, make sure the modules have a single responsibility) ",
from: {},
to: {
circular: true,
},
},
{
name: "no-orphans",
comment:
"This is an orphan module - it's likely not used (anymore?). Either use it or " +
"remove it. If it's logical this module is an orphan (i.e. it's a config file), " +
"add an exception for it in your dependency-cruiser configuration. By default " +
"this rule does not scrutinize dot-files (e.g. .eslintrc.js), TypeScript declaration " +
"files (.d.ts), tsconfig.json and some of the babel and webpack configs.",
severity: "warn",
from: {
orphan: true,
pathNot: [
"(^|/)[.][^/]+[.](?:js|cjs|mjs|ts|cts|mts|json)$", // dot files
"[.]d[.]ts$", // TypeScript declaration files
"(^|/)tsconfig[.]json$", // TypeScript config
"(^|/)(?:babel|webpack)[.]config[.](?:js|cjs|mjs|ts|cts|mts|json)$", // other configs
],
},
to: {},
},
{
name: "no-deprecated-core",
comment:
"A module depends on a node core module that has been deprecated. Find an alternative - these are " +
"bound to exist - node doesn't deprecate lightly.",
severity: "warn",
from: {},
to: {
dependencyTypes: ["core"],
path: [
"^v8/tools/codemap$",
"^v8/tools/consarray$",
"^v8/tools/csvparser$",
"^v8/tools/logreader$",
"^v8/tools/profile_view$",
"^v8/tools/profile$",
"^v8/tools/SourceMap$",
"^v8/tools/splaytree$",
"^v8/tools/tickprocessor-driver$",
"^v8/tools/tickprocessor$",
"^node-inspect/lib/_inspect$",
"^node-inspect/lib/internal/inspect_client$",
"^node-inspect/lib/internal/inspect_repl$",
"^async_hooks$",
"^punycode$",
"^domain$",
"^constants$",
"^sys$",
"^_linklist$",
"^_stream_wrap$",
],
},
},
{
name: "not-to-deprecated",
comment:
"This module uses a (version of an) npm module that has been deprecated. Either upgrade to a later " +
"version of that module, or find an alternative. Deprecated modules are a security risk.",
severity: "warn",
from: {},
to: {
dependencyTypes: ["deprecated"],
},
},
{
name: "no-non-package-json",
severity: "error",
comment:
"This module depends on an npm package that isn't in the 'dependencies' section of your package.json. " +
"That's problematic as the package either (1) won't be available on live (2 - worse) will be " +
"available on live with an non-guaranteed version. Fix it by adding the package to the dependencies " +
"in your package.json.",
from: {},
to: {
dependencyTypes: ["npm-no-pkg", "npm-unknown"],
},
},
{
name: "not-to-unresolvable",
comment:
"This module depends on a module that cannot be found ('resolved to disk'). If it's an npm " +
"module: add it to your package.json. In all other cases you likely already know what to do.",
severity: "error",
from: {},
to: {
couldNotResolve: true,
},
},
{
name: "no-duplicate-dep-types",
comment:
"Likely this module depends on an external ('npm') package that occurs more than once " +
"in your package.json i.e. bot as a devDependencies and in dependencies. This will cause " +
"maintenance problems later on.",
severity: "warn",
from: {},
to: {
moreThanOneDependencyType: true,
// as it's pretty common to have a type import be a type only import
// _and_ (e.g.) a devDependency - don't consider type-only dependency
// types for this rule
dependencyTypesNot: ["type-only"],
},
},
/* rules you might want to tweak for your specific situation: */
{
name: "not-to-spec",
comment:
"This module depends on a spec (test) file. The sole responsibility of a spec file is to test code. " +
"If there's something in a spec that's of use to other modules, it doesn't have that single " +
"responsibility anymore. Factor it out into (e.g.) a separate utility/ helper or a mock.",
severity: "error",
from: {},
to: {
path: "[.](?:spec|test)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$",
},
},
{
name: "not-to-dev-dep",
severity: "error",
comment:
"This module depends on an npm package from the 'devDependencies' section of your " +
"package.json. It looks like something that ships to production, though. To prevent problems " +
"with npm packages that aren't there on production declare it (only!) in the 'dependencies'" +
"section of your package.json. If this module is development only - add it to the " +
"from.pathNot re of the not-to-dev-dep rule in the dependency-cruiser configuration",
from: {
path: "^(src)",
pathNot: "[.](?:spec|test)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$",
},
to: {
dependencyTypes: ["npm-dev"],
// type only dependencies are not a problem as they don't end up in the
// production code or are ignored by the runtime.
dependencyTypesNot: ["type-only"],
pathNot: ["node_modules/@types/"],
},
},
{
name: "optional-deps-used",
severity: "info",
comment:
"This module depends on an npm package that is declared as an optional dependency " +
"in your package.json. As this makes sense in limited situations only, it's flagged here. " +
"If you're using an optional dependency here by design - add an exception to your" +
"dependency-cruiser configuration.",
from: {},
to: {
dependencyTypes: ["npm-optional"],
},
},
{
name: "peer-deps-used",
comment:
"This module depends on an npm package that is declared as a peer dependency " +
"in your package.json. This makes sense if your package is e.g. a plugin, but in " +
"other cases - maybe not so much. If the use of a peer dependency is intentional " +
"add an exception to your dependency-cruiser configuration.",
severity: "warn",
from: {},
to: {
dependencyTypes: ["npm-peer"],
},
},
],
options: {
/* Which modules not to follow further when encountered */
doNotFollow: {
/* path: an array of regular expressions in strings to match against */
path: ["node_modules"],
},
/* Which modules to exclude */
// exclude : {
// /* path: an array of regular expressions in strings to match against */
// path: '',
// },
/* Which modules to exclusively include (array of regular expressions in strings)
dependency-cruiser will skip everything not matching this pattern
*/
// includeOnly : [''],
/* List of module systems to cruise.
When left out dependency-cruiser will fall back to the list of _all_
module systems it knows of. It's the default because it's the safe option
It might come at a performance penalty, though.
moduleSystems: ['amd', 'cjs', 'es6', 'tsd']
As in practice only commonjs ('cjs') and ecmascript modules ('es6')
are widely used, you can limit the moduleSystems to those.
*/
// moduleSystems: ['cjs', 'es6'],
/*
false: don't look at JSDoc imports (the default)
true: dependency-cruiser will detect dependencies in JSDoc-style
import statements. Implies "parser": "tsc", so the dependency-cruiser
will use the typescript parser for JavaScript files.
For this to work the typescript compiler will need to be installed in the
same spot as you're running dependency-cruiser from.
*/
// detectJSDocImports: true,
/* prefix for links in html and svg output (e.g. 'https://github.com/you/yourrepo/blob/main/'
to open it on your online repo or `vscode://file/${process.cwd()}/` to
open it in visual studio code),
*/
// prefix: `vscode://file/${process.cwd()}/`,
/* false (the default): ignore dependencies that only exist before typescript-to-javascript compilation
true: also detect dependencies that only exist before typescript-to-javascript compilation
"specify": for each dependency identify whether it only exists before compilation or also after
*/
tsPreCompilationDeps: true,
/* list of extensions to scan that aren't javascript or compile-to-javascript.
Empty by default. Only put extensions in here that you want to take into
account that are _not_ parsable.
*/
// extraExtensionsToScan: [".json", ".jpg", ".png", ".svg", ".webp"],
/* if true combines the package.jsons found from the module up to the base
folder the cruise is initiated from. Useful for how (some) mono-repos
manage dependencies & dependency definitions.
*/
// combinedDependencies: false,
/* if true leave symlinks untouched, otherwise use the realpath */
// preserveSymlinks: false,
/* TypeScript project file ('tsconfig.json') to use for
(1) compilation and
(2) resolution (e.g. with the paths property)
The (optional) fileName attribute specifies which file to take (relative to
dependency-cruiser's current working directory). When not provided
defaults to './tsconfig.json'.
*/
tsConfig: {
fileName: "tsconfig.json",
},
/* Webpack configuration to use to get resolve options from.
The (optional) fileName attribute specifies which file to take (relative
to dependency-cruiser's current working directory. When not provided defaults
to './webpack.conf.js'.
The (optional) `env` and `arguments` attributes contain the parameters
to be passed if your webpack config is a function and takes them (see
webpack documentation for details)
*/
// webpackConfig: {
// fileName: 'webpack.config.js',
// env: {},
// arguments: {}
// },
/* Babel config ('.babelrc', '.babelrc.json', '.babelrc.json5', ...) to use
for compilation
*/
// babelConfig: {
// fileName: '.babelrc',
// },
/* List of strings you have in use in addition to cjs/ es6 requires
& imports to declare module dependencies. Use this e.g. if you've
re-declared require, use a require-wrapper or use window.require as
a hack.
*/
// exoticRequireStrings: [],
/* options to pass on to enhanced-resolve, the package dependency-cruiser
uses to resolve module references to disk. The values below should be
suitable for most situations
If you use webpack: you can also set these in webpack.conf.js. The set
there will override the ones specified here.
*/
enhancedResolveOptions: {
/* What to consider as an 'exports' field in package.jsons */
exportsFields: ["exports"],
/* List of conditions to check for in the exports field.
Only works when the 'exportsFields' array is non-empty.
*/
conditionNames: ["import", "require", "node", "default", "types"],
/* The extensions, by default are the same as the ones dependency-cruiser
can access (run `npx depcruise --info` to see which ones that are in
_your_ environment). If that list is larger than you need you can pass
the extensions you actually use (e.g. [".js", ".jsx"]). This can speed
up module resolution, which is the most expensive step.
*/
// extensions: [".js", ".jsx", ".ts", ".tsx", ".d.ts"],
/* What to consider a 'main' field in package.json */
mainFields: ["module", "main", "types", "typings"],
/* A list of alias fields in package.jsons
See [this specification](https://github.com/defunctzombie/package-browser-field-spec) and
the webpack [resolve.alias](https://webpack.js.org/configuration/resolve/#resolvealiasfields)
documentation.
Defaults to an empty array (= don't use alias fields).
*/
// aliasFields: ["browser"],
},
/* skipAnalysisNotInRules will make dependency-cruiser execute
analysis strictly necessary for checking the rule set only.
See https://github.com/sverweij/dependency-cruiser/blob/main/doc/options-reference.md#skipanalysisnotinrules
for details
*/
skipAnalysisNotInRules: true,
/* List of built-in modules to use on top of the ones node declares.
See https://github.com/sverweij/dependency-cruiser/blob/main/doc/options-reference.md#builtinmodules-influencing-what-to-consider-built-in--core-modules
for details
*/
builtInModules: {
add: [
"bun",
"bun:ffi",
"bun:jsc",
"bun:sqlite",
"bun:test",
"bun:wrap",
"detect-libc",
"undici",
"ws",
],
},
reporterOptions: {
dot: {
/* pattern of modules that can be consolidated in the detailed
graphical dependency graph. The default pattern in this configuration
collapses everything in node_modules to one folder deep so you see
the external modules, but their innards.
*/
collapsePattern: "node_modules/(?:@[^/]+/[^/]+|[^/]+)",
/* Options to tweak the appearance of your graph.See
https://github.com/sverweij/dependency-cruiser/blob/main/doc/options-reference.md#reporteroptions
for details and some examples. If you don't specify a theme
dependency-cruiser falls back to a built-in one.
*/
// theme: {
// graph: {
// /* splines: "ortho" gives straight lines, but is slow on big graphs
// splines: "true" gives bezier curves (fast, not as nice as ortho)
// */
// splines: "true"
// },
// }
},
archi: {
/* pattern of modules that can be consolidated in the high level
graphical dependency graph. If you use the high level graphical
dependency graph reporter (`archi`) you probably want to tweak
this collapsePattern to your situation.
*/
collapsePattern:
"^(?:packages|src|lib(s?)|app(s?)|bin|test(s?)|spec(s?))/[^/]+|node_modules/(?:@[^/]+/[^/]+|[^/]+)",
/* Options to tweak the appearance of your graph. If you don't specify a
theme for 'archi' dependency-cruiser will use the one specified in the
dot section above and otherwise use the default one.
*/
// theme: { },
},
text: {
highlightFocused: true,
},
},
},
};
// generated: dependency-cruiser@16.10.0 on 2025-02-16T22:32:01.621Z
+10 -7
View File
@@ -12,12 +12,15 @@
},
"rules": {
// allow importing ts extensions
"sort-imports": ["error", {
"ignoreCase": true,
"ignoreDeclarationSort": true,
"ignoreMemberSort": false,
"memberSyntaxSortOrder": ["none", "all", "multiple", "single"]
}],
"sort-imports": [
"error",
{
"ignoreCase": true,
"ignoreDeclarationSort": true,
"ignoreMemberSort": false,
"memberSyntaxSortOrder": ["none", "all", "multiple", "single"]
}
],
"import/extensions": [
"error",
"ignorePackages",
@@ -29,4 +32,4 @@
]
},
"plugins": ["import"]
}
}
-27
View File
@@ -1,27 +0,0 @@
---
name: Report A bug.
about: Create a report of a present bug.
title: "[BUG]"
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Please indicate how did you make this happen.
**Expected behaviuor**
Please add a clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- If using Windows, the build number. Find this by using ```winver``` and copying down the build id.
**Additional context**
Add any other context about the problem here.
+57
View File
@@ -0,0 +1,57 @@
name: Bug report
description: Report an issue with the modpack in its unmodified state. For other issues, use Discord.
labels: bug
title: "[BUG]"
type: "Bug"
body:
- type: markdown
attributes:
value: |
Before reporting an issue, [please search](https://github.com/BetterSEQTA/BetterSEQTA-Plus/issues) to make sure it has not already been reported (make sure to search closed issues as well!).
- type: textarea
attributes:
label: Describe the bug
description: Describe your issue. For general issues and questions you'll get a faster answer [from our Discord.](https://discord.gg/YzmbnCDkat)
validations:
required: true
- type: input
attributes:
label: Extension version
description: What version of the extension are you using?
placeholder: Find it by opening the config menu and clicking the about icon in the top right.
validations:
required: true
- type: dropdown
attributes:
label: Browser
description: Which Browser are you using?
options:
- Chrome
- Firefox
- Brave
- Safari
- DuckDuckGO
- Microsoft Edge
- Other Chromium-Based Browser
- Other Non-Chromium-Based Browser
validations:
required: true
- type: checkboxes
attributes:
label: Confirm
options:
- label: This bug report is about an issue with the extension itself. I have not modified the extension nor added any unsupported plugins. If this is not the case, I know that I should post the issue to the extension's Discord support channel instead.
required: true
- type: textarea
attributes:
label: Additional context
description: Screenshots, video or any other information. Include photos of the console if possible
placeholder: |
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
validations:
required: false
+4
View File
@@ -0,0 +1,4 @@
contact_links:
- name: BetterSEQTA Community Support
url: https://discord.gg/YzmbnCDkat
about: Join our discord for community updates, discussion, and more!
-20
View File
@@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: "[FR] "
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
@@ -0,0 +1,52 @@
name: Feature request
description: Suggest a new Feature to be added or replaced in BetterSeqtaPLUS
labels: enhancement
title: "[FR]"
body:
- type: checkboxes
attributes:
label: Confirm
options:
- label: "Is this feature request related to a Bug report?"
required: false
- type: input
attributes:
label: Bug report link
description: "If this feature request is related to a bug report, please insert the link to the bug report here"
placeholder: "https://github.com/BetterSEQTA/BetterSEQTA-Plus/issues/..."
validations:
required: false
- type: markdown
attributes:
value: |
## Feature details
Before you request a feature, [please search](https://github.com/BetterSEQTA/BetterSEQTA-Plus/issues) if it has already been requested. (Make sure to check closed issues as well!)
- type: dropdown
attributes:
label: Feature type
multiple: false
options:
- Graphical
- Functional
- Not Sure
validations:
required: true
- type: input
attributes:
label: Feature Details
description: Please write, with as much detail as possible, what you would like to see from this feature.
placeholder: it would be cool if
validations:
required: false
- type: textarea
attributes:
label: Additional details
description: Anything else that would help describe your vision (reference images, descriptions, etc)
validations:
required: false
+114
View File
@@ -0,0 +1,114 @@
name: 🙋 New Contributor - Need Help Getting Started
description: Perfect for first-time contributors who need guidance
labels: ["help wanted", "documentation"]
title: "[NEW CONTRIBUTOR] "
body:
- type: markdown
attributes:
value: |
## Hi there! 👋
Welcome to BetterSEQTA+! We're excited to have you join our community.
- type: checkboxes
attributes:
label: Tell us about yourself (check all that apply)
options:
- label: "This is my first time contributing to open source"
required: false
- label: "I'm new to browser extensions"
required: false
- label: "I'm new to TypeScript/JavaScript"
required: false
- label: "I have some coding experience but new to this project"
required: false
- type: checkboxes
attributes:
label: What would you like to work on? (check all that apply)
options:
- label: "Fix a bug 🐛"
required: false
- label: "Add a new feature ✨"
required: false
- label: "Improve documentation 📚"
required: false
- label: "Create a plugin 🧩"
required: false
- label: "Improve the UI/design 🎨"
required: false
- label: "Write tests 🧪"
required: false
- label: "Not sure - I want to help but need guidance!"
required: false
- type: checkboxes
attributes:
label: Have you read our guides?
options:
- label: "Getting Started Guide (see docs/GETTING_STARTED_CONTRIBUTING.md)"
required: true
- label: "Architecture Guide (see docs/ARCHITECTURE.md)"
required: true
- label: "Plugin Development Guide (see docs/plugins/README.md)"
required: true
- type: checkboxes
attributes:
label: Have you set up the development environment yet?
options:
- label: Yes, everything works! 🎉
required: false
- label: Partially - I can run `npm run dev` but having some issues
required: false
- label: No, I need help with setup
required: false
- label: I tried but ran into errors (please describe below)
required: false
- type: input
attributes:
label: Errors
description: "Please list any encountered errors here:"
placeholder: "I am encountering issues with..."
validations:
required: false
- type: input
attributes:
label: Questions or Issues
description: "Tell us:
1. What specifically would you like help with?
2. Are you stuck on anything?
3. Do you have any questions about the codebase?
4. Is there anything in our documentation that's unclear?"
placeholder: "I want help with..."
validations:
required: false
- type: input
attributes:
label: Ideas or Suggestions
description: "If you have any ideas for features, improvements, or just want to share your thoughts:"
placeholder: "It would be cool if I could help add..."
validations:
required: false
- type: markdown
attributes:
value: |
## What happens next?
A maintainer will respond within 24-48 hours to:
- Answer your questions
- Suggest some good issues to work on
- Help you with setup if needed
- Point you to relevant documentation
Don't worry if you're new to this - we're here to help! Every expert was once a beginner. 🚀
**Join our [Discord server](https://discord.gg/YzmbnCDkat) for real-time help and community chat!**
-17
View File
@@ -1,17 +0,0 @@
---
name: Vulnerability
about: Report a vulnerability in this extension.
title: "[VUL] "
labels: ''
assignees: ''
---
**What is the vulnerability?**
Describe the vulnerability in concise language.
**Where is the vulnerability found?**
Describe where the vulnerability is found.
**What does this affect?**
Explain what it affects. E.G: It opens up my school email to the world. etc.
@@ -0,0 +1,14 @@
## Description
Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change.
Fixes # (issue)
## Type of change
Please delete options that are not relevant.
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update
+24 -59
View File
@@ -1,73 +1,38 @@
name: MVP - make, version & publish
name: NodeJS Build
on:
push:
branches:
- main
workflow_dispatch: # This line adds manual triggering from the GitHub UI
concurrency: ${{ github.workflow }}-${{ github.ref }}
branches: ["main"]
pull_request:
branches: ["main"]
jobs:
make_version_publish:
name: Make, Version & Publish
build:
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@v4
- name: Setup Node 20.x
strategy:
matrix:
node-version: [20.x]
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: 20.x
node-version: ${{ matrix.node-version }}
- name: Install bun & Deps
- name: Build
run: |
npm install bun -g
bun install
npm install --legacy-peer-deps
npm run build
- name: 'Build - all browsers'
id: buildProject
run: MODE=chrome vite build && MODE=firefox vite build
- name: Zip dist folder
run: |
zip -r dist.zip dist
- name: '[ V E R S I O N ] : Create or Update Release Pull Request - Version Changes'
id: changesets
uses: changesets/action@v1
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
version: bun run version
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: 'Get current version info from package.json'
if: steps.changesets.outputs.hasChangesets == 'false'
id: package
run: |
echo "::set-output name=PACKAGE_NAME::$(jq -r .name package.json)"
echo "::set-output name=PACKAGE_VERSION::$(jq -r .version package.json)"
working-directory: ${{ github.workspace }}
- name: 'Check if a git release already exists for current version'
if: steps.changesets.outputs.hasChangesets == 'false'
id: checkRelease
run: |
TAG_NAME=${{ steps.package.outputs.PACKAGE_NAME }}@${{ steps.package.outputs.PACKAGE_VERSION }}
if gh release view $TAG_NAME &>/dev/null; then
echo "Release $TAG_NAME already exists."
echo "RELEASE_EXISTS=true" >> $GITHUB_ENV
else
echo "RELEASE_EXISTS=false" >> $GITHUB_ENV
fi
- name: 'Create Release Archive(s) - zip 🫰 it 🫰 up 🫰 !'
id: zip
if: steps.changesets.outputs.hasChangesets == 'false'
run: bun run zip
- name: 'Create a git release w/ notes & release archive(s)'
id: gitRelease
if: steps.changesets.outputs.hasChangesets == 'false' && env.RELEASE_EXISTS != 'true'
run: bun run release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PACKAGE_NAME: ${{ steps.package.outputs.PACKAGE_NAME }}
PACKAGE_VERSION: ${{ steps.package.outputs.PACKAGE_VERSION }}
name: dist-zip
path: dist.zip
-38
View File
@@ -1,38 +0,0 @@
name: NodeJS Build
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20.x]
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Build
run: |
npm install
npm run build
- name: Zip dist folder
run: |
zip -r dist.zip dist
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: dist-zip
path: dist.zip
+9 -4
View File
@@ -4,10 +4,11 @@ package-lock.json
bun.lockb
pnpm-lock.yaml
yarn.lock
bun.lock
.parcel-cache
.env
.env.submit
# PDF.js extension assets (copied by postinstall from pdfjs-dist)
src/public/resources/pdfjs/pdf.worker.min.mjs
src/public/resources/pdfjs/pdf.legacy.min.mjs
# Build
extension.zip
@@ -17,5 +18,9 @@ betterseqtaplus-safari/
.million/
.vscode/
**/.DS_Store
.parcel-cache
.env
.env.submit
dependency-graph.svg
**/.DS_Store
+2
View File
@@ -0,0 +1,2 @@
legacy-peer-deps=true
-5
View File
@@ -1,5 +0,0 @@
{
"plugins": {
"tailwindcss": {}
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
{
"tabWidth": 2,
"useTabs": false,
"semi": false
"semi": true
}
+11 -11
View File
@@ -17,23 +17,23 @@ diverse, inclusive, and healthy community.
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
- Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
- The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
@@ -106,7 +106,7 @@ Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
+37 -4
View File
@@ -1,12 +1,45 @@
# Contributing
# Contributing to BetterSEQTA+
When contributing to this repository, please first discuss the change you wish to make via issue,
email, or any other method with the owners of this repository before making a change.
Hey there! 👋 Thanks for your interest in contributing to BetterSEQTA+! We're excited to have you join our community of contributors.
## 🚀 New Contributors Start Here!
**Never contributed to an open source project before?** No worries! We've made it super easy to get started:
- **📖 Read our [contributing guide](https://docs.betterseqta.org/contributing/)** - This walks you through everything step-by-step, from setting up your development environment to making your first pull request.
- **🏗️ Understand the codebase** with the [architecture guide](https://docs.betterseqta.org/architecture/)
- **🔧 Having issues?** Check the [troubleshooting guide](https://docs.betterseqta.org/troubleshooting/)
We have lots of [`good first issue`](https://github.com/BetterSEQTA/BetterSEQTA-plus/labels/good%20first%20issue) labels that are perfect for beginners!
## Discussion Before Contributing
For significant changes, please first discuss what you'd like to change via:
- Opening an issue
- Joining our Discord server
- Emailing the maintainers
This helps ensure your contribution aligns with the project's goals and saves you time!
## Community
Join our community channels to discuss the project, get help, and connect with other contributors:
- **Discord Server**: [Join our Discord](https://discord.gg/YzmbnCDkat)
- **GitHub Discussions**: For longer-form conversations
- **GitHub Issues**: For bug reports and feature requests
## Creating Plugins
If you're interested in creating plugins for BetterSEQTA+, check out our plugin development guides:
- [Plugin development](https://docs.betterseqta.org/plugin-development/)
- [Plugin API](https://docs.betterseqta.org/plugin-api/)
## Pull Request Process
1. It is recommended to start by opening an issue to discuss the change you wish to make. This will allow us to discuss the change and ensure it is a good fit for the project.
2. Fork the repo and create your branch from `master`.
2. Fork the repo and create your branch from `main`.
3. When writing your pull request, make sure to use the pull request template.
### Pull Request Template
+38 -81
View File
@@ -1,6 +1,3 @@
#
<a href="https://chromewebstore.google.com/detail/betterseqta+/afdgaoaclhkhemfkkkonemoapeinchel">
<img src="https://socialify.git.ci/betterseqta/betterseqta-plus/image?description=1&font=Inter&forks=1&issues=1&logo=data%3Aimage%2Fsvg%2Bxml%2C%253Csvg%20height%3D%27656pt%27%20fill%3D%27white%27%20preserveAspectRatio%3D%27xMidYMid%20meet%27%20viewBox%3D%270%200%20658%20656%27%20width%3D%27658pt%27%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%253E%253Cg%20transform%3D%27matrix(.1%200%200%20-.1%200%20656)%27%253E%253Cpath%20d%3D%27m2960%206499c-918-100-1726-561-2278-1299-196-262-374-609-475-925-171-533-203-1109-91-1655%20228-1115%201030-2032%202104-2408%20356-124%20680-177%201080-176%20269%201%20403%2014%20650%2064%20790%20159%201503%20624%201980%201290%20714%20998%20799%202342%20217%203420-488%20902-1361%201515-2382%201671-113%2017-196%2022-430%2024-159%202-328-1-375-6zm566-1443c476-99%20885-385%201134-791%20190-309%20282-696%20250-1045-22-240-73-420-180-635-78-156-159-275-274-401l-77-84h445%20446v-235-236l-1162%204-1163%203-100%2023c-449%20101-812%20337-1071%20697-77%20107-193%20335-233%20459-115%20358-116%20726-1%201078%20209%20644%20766%201101%201446%201187%20128%2016%20405%204%20540-24z%27%2F%253E%253Cpath%20d%3D%27m3065%204604c-250-36-396-89-576-209-280-187-470-478-535-821-25-135-16-395%2019-525%2095-351%20331-644%20651-806%2098-49%20225-93%20331-114%2092-18%20368-18%20460%200%20481%2095%20853%20444%20982%20921%2035%20129%2044%20389%2019%20524-36%20191-121%20387-228%20531-186%20249-476%20428-783%20485-65%2012-291%2021-340%2014z%27%2F%253E%253C%2Fg%253E%253C%2Fsvg%253E&name=1&owner=1&pattern=Signal&stargazers=1&theme=Dark" />
</a>
@@ -11,7 +8,7 @@
<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://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>
<div>
@@ -19,17 +16,15 @@
<img src="https://img.shields.io/chrome-web-store/rating/afdgaoaclhkhemfkkkonemoapeinchel" />
</div>
## Table of contents
## 📚 Documentation
All documentation has been moved to the [official docs site](https://docs.betterseqta.org):
- [Features](#features)
- [Creating Custom Themes](#creating-custom-themes)
- [Getting Started](#getting-started)
- [Running Development](#running-development)
- [Building for production](#building-for-production)
- [Folder Structure](#folder-structure)
- [Contributors](#contributors)
- [Credits](#credits)
- [Star History](#star-history)
Includes:
- Getting started
- Development setup
- Architecture
- Plugin system
- Theme creation
## Features
@@ -43,84 +38,42 @@
- Easier Access Notices
- Assessments
- Options to remove certain items from the side menu
- Fully customisable themes and an offical theme store
- Grades calculator
- Fully customisable themes and an official theme store
- Notification for next lesson (sent 5 minutes before end of the lesson)
- Browser Support
- Chrome Supported
- Edge Supported
- Brave Supported
- Opera Supported
- Vivaldi Supported
- Chromium-based browsers are supported
- Firefox (Experimental - available [here](https://addons.mozilla.org/en-US/firefox/addon/betterseqta-plus/)
- Safari (Experimental - only available via compilation)
- Chrome, Edge, Brave, Opera and other Chromium-Based browsers are supported
- Firefox Supported: [here](https://addons.mozilla.org/en-US/firefox/addon/betterseqta-plus/)!
- Safari (Experimental and not recommended - only available via compilation)
## Creating Custom Themes
If you are looking to create custom themes, I would recommend you start at the official documentation [here](https://betterseqta.gitbook.io/betterseqta-docs). You can see some premade examples along with a compilation script that can be used to allow for CSS frameworks and libraries such as SCSS to be used [here](https://github.com/BetterSEQTA/BetterSEQTA-Theme-Generator).
If you are looking to create custom themes, I would recommend you start at the official documentation [here](https://docs.betterseqta.org/theme-creation/). You can see some premade examples along with a compilation script that can be used to allow for CSS frameworks and libraries such as SCSS to be used [here](https://github.com/BetterSEQTA/BetterSEQTA-Theme-Generator).
Don't worry- if you get stuck feel free to ask around in the discord. We're open and happy to help out! Happy creating :)
Don't worry- if you get stuck feel free to ask around in the [discord](https://discord.gg/YzmbnCDkat). We're open and happy to help out! Happy creating :)
## Getting started
## 🚀 Contributing
**New contributors welcome!**
- 📖 Start here: https://docs.betterseqta.org/install/
- 🧠 Architecture: https://docs.betterseqta.org/architecture/
- 🧩 Plugins: https://docs.betterseqta.org/plugins/
- 💬 Discord: https://discord.gg/YzmbnCDkat
1. Clone the repository
```
git clone https://github.com/BetterSEQTA/BetterSEQTA-Plus
```
## ⚡ Quick Start
### Running Development
1. Install dependencies
```
npm install # or your preferred package manager like pnpm or yarn
```
2. Run the dev script (it updates as you save files)
```
```bash
git clone https://github.com/YOUR_USERNAME_FORKED_WITH/BetterSEQTA-Plus
cd BetterSEQTA-Plus
npm install --legacy-peer-deps
npm run dev
```
````
3. Load the extension into chrome
Then load `dist` in `chrome://extensions` (Developer Mode → Load unpacked).
- Go to `chrome://extensions`
- Enable developer mode
- Click `Load unpacked`
- Select the `dist` folder
Just remember, in order to update changes to the extension, you need to click the refresh button on the extension in `chrome://extensions` whenever anything's changed.
### Building for production
1. Install dependencies
```
npm install # or your preferred package manager like pnpm or yarn
```
2. Run the build script
```
npm run build
```
3. Package it up (optional)
```
npm run zip # This requires 7-Zip to be installed in order to work
```
## Folder Structure
The folder structure is as follows:
- The `src` folder contains source files that are compiled to the build directory.
- The `src/interface` folder contains source React & Svelte files that are required for the Settings page.
- The `dist` folder is where the compiled code ends up, this is the folder what you need to load into chrome as an unpacked extension for development.
Full setup guide:
[https://betterseqta.github.io/BetterSEQTA-Docs/install/#for-developers-development-environment](https://betterseqta.github.io/BetterSEQTA-Docs/install/#for-developers-development-environment)
## Contributors
@@ -128,11 +81,15 @@ The folder structure is as follows:
<img src="https://contrib.rocks/image?repo=betterseqta/betterseqta-plus" />
</a>
Want to contribute? [Click Here!](https://github.com/BetterSEQTA/BetterSEQTA-Plus/blob/main/CONTRIBUTING.md)
Want to contribute? [Click Here!](https://docs.betterseqta.org/contributing/)
## Credits
This extension was initially developed by [Nulkem](https://github.com/Nulkem/betterseqta), was ported to manifest V3 by [MEGA-Dawg68](https://github.com/MEGA-Dawg68) and is currently under active development by [SethBurkart123](https://github.com/SethBurkart123) and [Crazypersonalph](https://github.com/Crazypersonalph)
This extension was initially developed by [Nulkem](https://github.com/Nulkem/betterseqta), was ported to manifest V3 by [MEGA-Dawg68](https://github.com/MEGA-Dawg68) and is currently under active development from lead developers [SethBurkart123](https://github.com/SethBurkart123) and [Crazypersonalph](https://github.com/Crazypersonalph) with help from other volunteers.
Originally developed by [Nulkem](https://github.com/Nulkem/betterseqta)
Ported to Manifest V3 by [MEGA-Dawg68](https://github.com/MEGA-Dawg68)
Maintained by [SethBurkart123](https://github.com/SethBurkart123), [Crazypersonalph](https://github.com/Crazypersonalph) & the rest of the BetterSEQTA team!
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=BetterSEQTA/BetterSEQTA-Plus&type=Date)](https://star-history.com/#sethburkart123/EvenBetterSEQTA&Date)
[![Star History Chart](https://api.star-history.com/svg?repos=BetterSEQTA/BetterSEQTA-Plus&type=Date)](https://star-history.com/#BetterSEQTA/BetterSEQTA-Plus&Date)
+5 -5
View File
@@ -4,13 +4,13 @@
Below here is the supported versions of BetterSEQTA+. Anything older than this is not supported and contains bugs.
| Version | Supported |
| ------- | ------------------ |
| 3.4.0 | :white_check_mark: |
| <= 3.3 | :x: |
| Version | Supported |
| ------- | --------- |
| 3.4.3 | |
| < 3.4.3 | :x: |
`*` May not work on other devices.
## Reporting a Vulnerability
If you find vulnerabilities, REPORT IT IMMEDIATELY. Make an issue and use the template provided for vulnerabilities.
If you find vulnerabilities, REPORT IT IMMEDIATELY. open the [advisories tab](https://github.com/BetterSEQTA/BetterSEQTA-Plus/security/advisories) on the left and click the green "report a vulnerability" button or use [this quick-link](https://github.com/BetterSEQTA/BetterSEQTA-Plus/security/advisories/new) to create a new report
+2138
View File
File diff suppressed because it is too large Load Diff
+237
View File
@@ -0,0 +1,237 @@
# BetterSEQTA+ Architecture
**Published version:** [docs.betterseqta.org/architecture/](https://docs.betterseqta.org/architecture/)
Hey there! 👋 New to the codebase and feeling a bit lost? Don't worry - this guide will help you understand how everything fits together!
## Table of Contents
- [Overview](#overview)
- [High-Level Architecture](#high-level-architecture)
- [Core Components](#core-components)
- [Plugin System](#plugin-system)
- [File Structure Explained](#file-structure-explained)
- [Data Flow](#data-flow)
- [Browser Extension Basics](#browser-extension-basics)
## Overview
BetterSEQTA+ is a browser extension that enhances SEQTA Learn by:
- Adding new features through a plugin system
- Providing customizable themes and UI improvements
- Offering better navigation and user experience
Think of it like this: **SEQTA Learn + BetterSEQTA+ = Enhanced SEQTA Experience**
## High-Level Architecture
```
┌─────────────────────────────────────────────────────────────┐
│ BROWSER EXTENSION │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌──────────────────┐ │
│ │ Background │ │ Content Script │ │
│ │ Script │ │ (SEQTA.ts) │ │
│ │ │ │ │ │
│ │ - Settings │◄───┤ - Page Detection│ │
│ │ - Storage │ │ - Plugin Loading│ │
│ │ - Updates │ │ - UI Injection │ │
│ └─────────────────┘ └──────────────────┘ │
│ │ │
│ ┌─────────▼─────────┐ │
│ │ Plugin System │ │
│ │ │ │
│ │ ┌─────────────┐ │ │
│ │ │ Built-in │ │ │
│ │ │ Plugins │ │ │
│ │ │ │ │ │
│ │ │ - Themes │ │ │
│ │ │ - Search │ │ │
│ │ │ - Timetable │ │ │
│ │ │ - etc... │ │ │
│ │ └─────────────┘ │ │
│ └───────────────────┘ │
│ │ │
│ ┌─────────▼─────────┐ │
│ │ Settings UI │ │
│ │ (Svelte App) │ │
│ │ │ │
│ │ - Plugin Config │ │
│ │ - Theme Creator │ │
│ │ - General Settings│ │
│ └───────────────────┘ │
└─────────────────────────────────────────────────────────────┘
┌─────────▼─────────┐
│ SEQTA Learn │
│ Website │
└───────────────────┘
```
## Core Components
### 1. Entry Point (`src/SEQTA.ts`)
This is where it all begins! When you visit a SEQTA page:
1. Detects if you're on a SEQTA Learn page
2. Injects our CSS styles
3. Changes the favicon to BetterSEQTA+ icon
4. Loads settings from storage
5. Initializes the plugin system
### 2. Plugin System (`src/plugins/`)
The heart of BetterSEQTA+! This is what makes it extensible:
- **Plugin Manager**: Registers and manages all plugins
- **Built-in Plugins**: Pre-made plugins (themes, search, etc.)
- **Plugin API**: Provides plugins with tools to interact with SEQTA
### 3. Settings UI (`src/interface/`)
A Svelte application that lets users:
- Enable/disable plugins
- Configure plugin settings
- Create custom themes
- Browse the theme store
### 4. Background Script (`src/background.ts`)
Runs in the background and handles:
- Extension-wide settings storage
- Communication between different parts
- Update notifications
## Plugin System
Our plugin system is what makes BetterSEQTA+ so powerful. Here's how it works:
### Plugin Lifecycle
```
Plugin Registration → Settings Loading → Plugin Initialization → Running → Cleanup
```
### Built-in Plugins Overview
| Plugin | What it does | Files |
|--------|-------------|-------|
| **Themes** | Custom CSS themes and backgrounds | `src/plugins/built-in/themes/` |
| **Global Search** | Search across all SEQTA content | `src/plugins/built-in/globalSearch/` |
| **Timetable** | Enhanced timetable features | `src/plugins/built-in/timetable/` |
| **Profile Picture** | Custom profile pictures | `src/plugins/built-in/profilePicture/` |
| **Animated Background** | Moving background animations | `src/plugins/built-in/animatedBackground/` |
### Creating a Plugin
Every plugin follows this structure:
```typescript
const myPlugin: Plugin = {
id: "unique-plugin-id",
name: "Human Readable Name",
description: "What does this plugin do?",
version: "1.0.0",
settings: { /* user configurable options */ },
run: async (api) => {
// Your plugin code goes here!
}
};
```
## File Structure Explained
```
src/
├── SEQTA.ts # 🚀 Main entry point - start reading here!
├── background.ts # 🔧 Background script for extension
├── manifests/ # 📦 Browser extension manifests
├── plugins/ # 🧩 Plugin system (the magic happens here!)
│ ├── core/ # 🏗️ Plugin infrastructure
│ ├── built-in/ # 🎁 Pre-made plugins
│ └── index.ts # 📋 Plugin registration
├── interface/ # 🎨 Settings UI (Svelte app)
│ ├── pages/ # 📄 Settings pages
│ ├── components/ # 🧱 Reusable UI components
│ └── main.ts # 🏠 Settings app entry point
├── seqta/ # 🔗 SEQTA-specific utilities
│ ├── main.ts # 🎯 Core SEQTA modifications
│ ├── ui/ # 🎨 UI manipulation helpers
│ └── utils/ # 🛠️ Helper functions
└── css/ # 💄 Styles and themes
```
### Where to Start Reading?
1. **New to the project?** Start with `src/SEQTA.ts`
2. **Want to understand plugins?** Look at `src/plugins/core/types.ts`
3. **Want to see a simple plugin?** Check out `src/plugins/built-in/profilePicture/`
4. **Interested in the UI?** Explore `src/interface/main.ts`
## Data Flow
Here's how data flows through the system:
```
User visits SEQTA → SEQTA.ts detects page → Loads settings from storage
Plugin Manager initializes → Each plugin gets API access → Plugins modify SEQTA
User opens settings → Svelte UI loads → Settings changed → Storage updated
Storage change detected → Plugins notified → UI updates automatically
```
## Browser Extension Basics
Never worked on a browser extension before? Here's what you need to know:
### Content Scripts vs Background Scripts
- **Content Script** (`SEQTA.ts`): Runs on SEQTA pages, can access and modify the page
- **Background Script** (`background.ts`): Runs in the background, handles storage and messaging
### Manifest Files
Each browser needs a slightly different manifest file:
- `manifests/chrome.ts` - Chrome, Edge, Brave
- `manifests/firefox.ts` - Firefox
- `manifests/safari.ts` - Safari (experimental)
### Communication
Different parts of the extension communicate using:
- `browser.runtime.sendMessage()` - Send messages
- `browser.storage` - Shared storage, but we have created a custom storage system that is easier to use:
```ts
settingsState.[the setting name] = [whatever you want to set it to]
console.log(settingsState.[the setting name])
```
- Custom events for plugin communication
## Development Tips
### Debugging
1. **Chrome DevTools**: Right-click → Inspect → Console tab
2. **Extension Console**: `chrome://extensions` → BetterSEQTA+ → "Inspect views: background page"
3. **Look for logs**: We log everything with `[BetterSEQTA+]` prefix
### Making Changes
1. Edit code → Save → Browser auto-reloads extension → Refresh SEQTA page
2. For UI changes: The dev server hot-reloads automatically
3. For plugin changes: May need to disable/enable the plugin in settings
### Common Gotchas
- Settings take a moment to load (use `api.settings.loaded` promise)
- Some SEQTA elements load dynamically (use `api.seqta.onMount()`)
- Plugin cleanup is important (always return a cleanup function)
## Next Steps
Ready to contribute? Here's what to do next:
1. **Read the code**: Start with `src/SEQTA.ts` and follow the flow
2. **Try creating a simple plugin**: Follow the [plugin documentation](https://docs.betterseqta.org/plugins/)
3. **Look at existing issues**: Check our [GitHub issues](https://github.com/BetterSEQTA/BetterSEQTA-plus/issues) for "good first issue" labels
4. **Join our Discord**: Get help from the community!
## Questions?
Still confused about something? That's totally normal! Here are your options:
- 💬 Ask in our [Discord server](https://discord.gg/YzmbnCDkat)
- 🐛 Open an issue on GitHub
- 📧 Email us at betterseqta.plus@gmail.com
Remember: **Every expert was once a beginner!** We're here to help you learn and contribute. 🚀
+141
View File
@@ -0,0 +1,141 @@
# BetterSEQTA Cloud — settings sync (server specification)
This document describes the HTTP API the BetterSEQTA+ extension expects for **cloud backup of extension settings**. The client is implemented in the extension repo; the accounts service (`accounts.betterseqta.org`) must implement these endpoints.
## Purpose
- Store **one JSON document per authenticated BetterSEQTA Cloud user** representing a snapshot of the extensions `chrome.storage.local` data (theme, layout, plugin settings, `plugin.*` keys, etc.).
- The extension **does not upload OAuth tokens** (`bsplus_token`, `bsplus_refresh_token`, `bsplus_client_id`, `bsplus_user`). Those remain only on the client.
- **Download** replaces local storage with the stored snapshot, then the client reapplies the current devices session tokens so the user stays signed in.
## Base URL
All routes below are relative to:
`https://accounts.betterseqta.org`
## Authentication
Every request must include:
```http
Authorization: Bearer <access_token>
```
Use the same **access tokens** issued by the existing BetterSEQTA+ OAuth flows (`/api/bsplus/login`, `/api/bsplus/refresh`). Resolve the user from the token; the document is scoped to that user.
## Endpoints
### `PUT /api/bsplus/settings/sync`
Upserts the callers settings backup.
**Request body (JSON):**
```json
{
"schemaVersion": 1,
"themeId": "uuid-string-or-empty",
"data": {
"...": "flat key-value map mirroring extension storage (see Payload shape)",
"selectedTheme": "uuid-or-empty-string"
}
}
```
- **`schemaVersion`**: integer. The extension currently sends `1`. The server may reject unknown major versions or store it for future migrations.
- **`themeId`**: optional but recommended duplicate of **`data.selectedTheme`**. Should be the UUID of the **installed** BetterSEQTA store theme (`selectedTheme`). This may be a normal theme id **or** a **flavour (slave) variant** id from themes with **`flavours[]`** — the extension uses it after restore to prefetch `theme.json` when missing locally (same **`GET …/themes/{id}/download`** as the store UI). Persist and return **`themeId`** in sync with **`data.selectedTheme`**.
- **`data`**: object whose keys are storage keys (strings) and values are JSON-serializable values (same types as stored in `chrome.storage.local`).
**Success response:** HTTP `200` (or `201` if you prefer create semantics). Example:
```json
{
"updated_at": "2026-04-07T12:00:00.000Z"
}
```
`updated_at` should be an ISO 8601 timestamp of the save time. The extension displays success without requiring extra fields.
**Error responses:** Standard JSON error body if applicable, e.g. `{ "error": "message" }`, with appropriate HTTP status (`401`, `413`, `422`, etc.).
---
### `GET /api/bsplus/settings/sync`
Returns the callers latest settings backup.
**Success response:** HTTP `200` with body:
```json
{
"schemaVersion": 1,
"themeId": "uuid-string-or-empty",
"data": { },
"updated_at": "2026-04-07T12:00:00.000Z"
}
```
- **`data`**: required for restore; must be the same shape as accepted in `PUT` (flat map of storage keys).
- **`themeId`**: optional; if present must match **`data.selectedTheme`** (see `PUT`).
- **`schemaVersion`**: optional but recommended; should match what was stored.
- **`updated_at`**: optional; included for UX if the client shows “last backup” time.
The extension resolves **`themeId`** (if non-empty), else **`data.selectedTheme`,** to [`resolveThemeIdForPostSyncDownload`](../src/seqta/utils/cloudSettingsSync.ts) after downloading the envelope — used only to reinstall theme assets from **`betterseqta.org`** when IndexedDB lacks that id (see **BetterSEQTA Cloud** flavour note in **[THEME_STORE_FLAVOURS_API](./THEME_STORE_FLAVOURS_API.md)** section “Cloud settings sync compatibility”).
**No backup yet:** HTTP **`404`**. The extension treats this as “nothing in the cloud” and shows an error to the user.
**Error responses:** `401` if the token is invalid, etc.
---
## Suggested database shape
Example relational layout:
| Column | Type | Notes |
|---------------|-------------|--------|
| `user_id` | FK → users | Unique per backup row (one row per user). |
| `payload` | JSON / JSONB| Store `{ "schemaVersion", "data" }` or only `data` + separate `schema_version` column. |
| `updated_at` | timestamptz | Set on each successful `PUT`. |
Unique constraint on `user_id`.
## Semantics
- **Last write wins:** each `PUT` replaces the stored backup for that user.
- **Optional later:** `If-Unmodified-Since` or a `revision` field for conflict detection (not required for v1).
## Security and privacy
- **Encryption at rest** for `payload` is recommended.
- Payload may contain **school-related UI preferences** and plugin data; treat as **user data** under your privacy policy.
- **Do not require or store** refresh/access tokens in the payload; the extension already strips them on upload.
### Never included in the sync payload (`chrome.storage.local` only)
The backup is a flat JSON map of **`chrome.storage.local`** keys. It does **not** include:
- **IndexedDB** — e.g. the Global Search index (`betterseqta-index` and related DBs) lives outside extension storage and is never serialized here.
- **OAuth / session keys** — `bsplus_token`, `bsplus_refresh_token`, `bsplus_client_id`, `bsplus_user`, plus legacy `cloudAccessToken` / `cloudUsername`.
- **Assessment Averages caches** — `plugin.assessments-average.storage.assessments`, `plugin.assessments-average.storage.weightings` (school assessment data).
- **Keys under** `plugin.global-search.storage.*` — reserved so any future plugin storage cache there is not synced.
- **Grade Analytics** — keys under `bsplus.analytics.*` (synced assessment cache and per-school chart preferences).
- **`bsplus_cloud_settings_known_remote_updated_at`** — client-only watermark for auto-sync (not part of the cloud backup blob).
On restore, those keys are **not** taken from the server; the device keeps its current local values.
## Related endpoint: `GET /api/user/cloud-summary`
The extension may call **`GET /api/user/cloud-summary`** (same host, `Authorization: Bearer`) for a **small** JSON summary (e.g. whether DesQTA / BetterSEQTA+ cloud settings exist and **`bsplus.updated_at`** / **`schemaVersion`**). It does **not** return the large settings `data` blob.
- **Auto-sync flow:** compare `bsplus.updated_at` to a **client-only** watermark stored in extension storage as **`bsplus_cloud_settings_known_remote_updated_at`** (never uploaded, never applied from the server payload; preserved on restore).
- If the server timestamp is newer (and `schemaVersion` is not ahead of the client), the client then calls **`GET /api/bsplus/settings/sync`** and applies the full envelope as usual.
This uses standard **WebExtension** APIs (`browser.alarms`, `runtime` messages, `storage`) and works on **Chromium and Firefox** builds (see `webextension-polyfill`).
## Client reference (extension)
- Upload / dev export: `buildUploadPayload` / `getSnapshotForUpload` in `src/seqta/utils/cloudSettingsSync.ts` (strips OAuth-related keys, sensitive device keys, **`bsplus_pending_theme_ensure_after_cloud`**, and **`bsplus_cloud_settings_known_remote_updated_at`** — includes **`themeId`** aligned with **`selectedTheme`**).
- Download: resolve id via **`resolveThemeIdForPostSyncDownload`** → **`applyDownloadedEnvelope`** after `GET` → prefetch theme blobs in page context if needed (**`prepareThemeAfterCloudSync`** in **`ThemeManager`**) → reload SEQTA tabs; local auth keys, sensitive device keys, client-only watermark, and **`bsplus_pending_theme_ensure_after_cloud`** semantics preserved as documented above.
- Auto sync (summary, debounced upload, alarms): `src/background/cloudSettingsAutoSync.ts`; content script triggers a poll on each verified SEQTA Learn/Engage page load (top frame) via `cloudSettingsPoll`.
+287
View File
@@ -0,0 +1,287 @@
# Getting Started as a Contributor
**Published version:** [docs.betterseqta.org/contributing/](https://docs.betterseqta.org/contributing/)
Welcome to BetterSEQTA+! 🎉 This guide will walk you through making your first contribution, even if you're completely new to the project.
## Table of Contents
- [Before You Start](#before-you-start)
- [Your First 30 Minutes](#your-first-30-minutes)
- [Making Your First Contribution](#making-your-first-contribution)
- [Types of Contributions](#types-of-contributions)
- [Finding Something to Work On](#finding-something-to-work-on)
- [Development Workflow](#development-workflow)
- [Getting Help](#getting-help)
## Before You Start
### What You'll Need
- **Node.js** (v16 or higher) - [Download here](https://nodejs.org/)
- **Git** - [Download here](https://git-scm.com/)
- **A code editor** - We recommend [VS Code](https://code.visualstudio.com/)
- **A Chromium browser** (Chrome, Edge, Brave) for testing (recommended, however you can use firefox although it requires being built every time you make a change)
### Helpful Background (but not required!)
- Basic JavaScript/TypeScript knowledge
- Some familiarity with HTML/CSS
- Understanding of browser extensions (we'll teach you!)
**Don't worry if you're missing some of these!** We're happy to help you learn. 🤗
## Your First 30 Minutes
Let's get you up and running quickly:
### 1. Get the Code (3 minutes)
```bash
# Fork the repository on GitHub first, then:
git clone https://github.com/YOUR_USERNAME/BetterSEQTA-plus.git
cd BetterSEQTA-plus
```
### 2. Install Dependencies (3 minutes)
```bash
npm install --legacy-peer-deps
```
### 3. Start Development Server (2 minutes)
```bash
npm run dev
```
### 4. Load Extension in Browser (4 minutes)
1. Open Chrome and go to `chrome://extensions`
2. Enable "Developer mode" (toggle in top right)
3. Click "Load unpacked"
4. Select the `dist` folder in your project
5. Visit a SEQTA Learn page to see BetterSEQTA+ in action!
### 5. Make a Tiny Change (5 minutes)
Let's prove everything works:
1. Open `src/SEQTA.ts`
2. Find the line that says `"[BetterSEQTA+] Successfully initialised"`
3. Change it to `"[BetterSEQTA+] Successfully initialised - Hello [YOUR_NAME]!"`
4. Save the file
5. Go to `chrome://extensions`, click the refresh icon on BetterSEQTA+
6. Refresh a SEQTA page and check the browser console (F12) - you should see your message!
### 6. Reset Your Change (3 minutes)
```bash
git checkout -- src/SEQTA.ts
```
**Congratulations! 🎉 You've successfully set up BetterSEQTA+ for development!**
## Making Your First Contribution
### Easy First Contributions
Here are some great starter contributions:
1. **Fix a typo in documentation** - Super easy and always appreciated!
2. **Improve error messages** - Make them more helpful
3. **Add comments to code** - Help other contributors understand
4. **Create a simple plugin** - Follow our plugin guide
5. **Fix a bug you found** - If you found a bug, fix it!
### Step-by-Step: Your First Pull Request
#### Step 1: Pick an Issue
- Go to our [Issues page](https://github.com/BetterSEQTA/BetterSEQTA-plus/issues)
- Look for labels like:
- `good first issue` - Perfect for beginners
- `help wanted` - We'd love help with these
- `documentation` - Improve our docs
- `bug` - Fix something broken
#### Step 2: Claim the Issue
Comment on the issue saying "I'd like to work on this!" We'll assign it to you.
#### Step 3: Create a Branch
```bash
git checkout -b fix-issue-123 # Replace 123 with the issue number
```
#### Step 4: Make Your Changes
- Follow the patterns you see in existing code
- Test your changes thoroughly
- Keep changes focused and small
#### Step 5: Test Everything
```bash
# Test the extension still loads
npm run dev
# Test in browser
# 1. Reload extension at chrome://extensions
# 2. Visit SEQTA page
# 3. Verify everything still works
```
#### Step 6: Commit Your Changes
```bash
git add .
git commit -m "Fix issue #123: Brief description of what you fixed"
```
#### Step 7: Push and Create Pull Request
```bash
git push origin fix-issue-123
```
Then go to GitHub and create a pull request with:
- **Clear title**: "Fix issue #123: Brief description"
- **Description**: Explain what you changed and why
- **Testing**: Describe how you tested it
## Types of Contributions
### 🐛 Bug Fixes
- Fix broken features
- Improve error handling
- Resolve compatibility issues
**Example**: "The theme selector doesn't work on Firefox"
### ✨ New Features
- Add new plugins
- Enhance existing functionality
- Improve user experience
**Example**: "Add keyboard shortcuts for common actions"
### 📚 Documentation
- Fix typos and unclear explanations
- Add examples and tutorials
- Improve code comments
**Example**: "Add more examples to the plugin guide"
### 🎨 Design & UI
- Improve the settings interface
- Make things more user-friendly
- Add animations and polish
**Example**: "Make the theme creator more intuitive"
### 🔧 Technical Improvements
- Refactor code for clarity
- Add tests
- Improve performance
**Example**: "Simplify the plugin loading logic"
## Finding Something to Work On
### Browse Issues by Label
- [`good first issue`](https://github.com/BetterSEQTA/BetterSEQTA-plus/labels/good%20first%20issue) - Perfect for beginners
- [`help wanted`](https://github.com/BetterSEQTA/BetterSEQTA-plus/labels/help%20wanted) - We need help with these
- [`documentation`](https://github.com/BetterSEQTA/BetterSEQTA-plus/labels/documentation) - Improve our docs
- [`bug`](https://github.com/BetterSEQTA/BetterSEQTA-plus/labels/bug) - Fix something broken
- [`enhancement`](https://github.com/BetterSEQTA/BetterSEQTA-plus/labels/enhancement) - Add new features
### Create Your Own Issue
Found a bug or have an idea? Create an issue first to discuss it!
### Plugin Ideas
Want to create a plugin? Here are some ideas:
- **Study Timer**: Track study time across SEQTA pages
- **Grade Tracker**: Better visualization of grades over time
- **Quick Notes**: Add notes to any SEQTA page
- **Homework Reminder**: Smart notifications for upcoming due dates
- **Custom Shortcuts**: User-defined keyboard shortcuts
## Development Workflow
### Daily Development
```bash
# Start working
git pull origin main
npm run dev
# Make changes, test, commit
git add .
git commit -m "Descriptive commit message"
# Push when ready
git push origin your-branch-name
```
### Before Submitting PR
1. **Test thoroughly** - Make sure nothing breaks
2. **Check console** - No new errors
3. **Test in different browsers** - Chrome and Firefox
4. **Update documentation** - If you changed how something works
### Code Style
- Use TypeScript where possible
- Follow existing naming conventions
- Add comments for complex logic
- Keep functions small and focused
## Getting Help
### Stuck? Here's How to Get Unstuck
1. **Check the docs** - The [architecture guide](https://docs.betterseqta.org/architecture/) explains everything
2. **Search existing issues** - Someone might have had the same problem
3. **Ask in Discord** - Our community is super helpful
4. **Create an issue** - If you found a bug or need help
### Discord Community
Join our [Discord server](https://discord.gg/YzmbnCDkat) for:
- Real-time help and discussion
- Collaboration on features
- Sharing ideas and feedback
- Getting to know the community
### Code Review Process
- All contributions need code review
- We'll provide helpful feedback
- Don't worry about making mistakes - we're here to help!
- Reviews usually happen within 24-48 hours
## Common Questions
**Q: I'm new to browser extensions. Is this too advanced for me?**
A: Not at all! We have lots of beginner-friendly issues, and our plugin system makes it easy to add features without understanding all the browser extension complexities.
**Q: How long does it take to get my first PR merged?**
A: For simple fixes, usually 1-3 days. For larger features, it might take a week or two as we discuss the best approach.
**Q: I made a mistake in my PR. What do I do?**
A: No worries! Just push more commits to the same branch and they'll be added to your PR automatically.
**Q: Can I work on multiple issues at once?**
A: It's better to focus on one issue at a time, especially when starting out. This makes code review easier and reduces conflicts.
**Q: What if I start working on something and get stuck?**
A: Ask for help! Create a draft PR with what you have so far, and we'll help you figure out the next steps.
## Recognition
All contributors get:
- Recognition in our README
- Contributor badge in Discord
- Our eternal gratitude! 🙏
Significant contributors may also get:
- Special Discord roles
- Input on project direction
- Maintainer status
## Next Steps
Ready to contribute? Here's what to do:
1.**Set up your development environment** (follow the 30-minute guide above)
2. 🔍 **Find an issue to work on** (check the "good first issue" label)
3. 💬 **Join our Discord** and introduce yourself
4. 🚀 **Make your first contribution** and submit a PR
Remember: **Every expert was once a beginner!** We're excited to help you learn and grow as a contributor. Welcome to the team! 🎉
---
*Questions? Suggestions for improving this guide? Open an issue or message us on Discord!*
+55
View File
@@ -0,0 +1,55 @@
# BetterSEQTA+ Documentation
**Canonical documentation lives at [docs.betterseqta.org](https://docs.betterseqta.org/).** The pages below are the same topics; use the site for search, navigation, and the latest updates.
## Table of Contents
### Getting Started
- [Documentation home](https://docs.betterseqta.org/)
- [Installation](https://docs.betterseqta.org/install/)
- [Contributing](https://docs.betterseqta.org/contributing/)
- [Architecture](https://docs.betterseqta.org/architecture/)
- [Contribution guidelines (repository)](../CONTRIBUTING.md)
- [Troubleshooting](https://docs.betterseqta.org/troubleshooting/)
### Features & customization
- [Features](https://docs.betterseqta.org/features/)
- [Themes & customization](https://docs.betterseqta.org/customization/)
- [Theme creation](https://docs.betterseqta.org/theme-creation/)
### Plugin system
- [Plugins overview](https://docs.betterseqta.org/plugins/)
- [Plugin development](https://docs.betterseqta.org/plugin-development/)
- [Plugin API](https://docs.betterseqta.org/plugin-api/)
- [Example plugin](https://docs.betterseqta.org/example-plugin/)
## Core Concepts
BetterSEQTA+ is built around several core concepts:
1. **Plugin System**: BetterSEQTA+ uses a plugin system to extend SEQTA with new features. Plugins are self-contained pieces of code that can be enabled or disabled by the user. See the [plugins documentation](https://docs.betterseqta.org/plugins/).
2. **Type-Safe Settings**: Each plugin can define settings that are type-safe and automatically rendered in the settings UI. The settings system uses TypeScript decorators to make it easy to define settings with proper typing.
3. **Storage API**: Plugins can use the Storage API to persist data between sessions. The Storage API is also type-safe, ensuring that plugins can only access their own data.
4. **SEQTA Integration**: BetterSEQTA+ integrates with SEQTA Learn by injecting code into the page. This allows plugins to modify the SEQTA UI and add new features.
## Getting Help
If you need help with BetterSEQTA+, you can:
- [Open an Issue](https://github.com/BetterSEQTA/BetterSEQTA-Plus/issues) - Report bugs or request features
- [Join the Discord](https://discord.gg/YzmbnCDkat) - Chat with the community
- [Email the Maintainers](mailto:betterseqta.plus@gmail.com) - Contact the maintainers directly
## Contributing to the Documentation
We welcome contributions to the documentation. The published site is built from the documentation repository; see [Contributing](https://docs.betterseqta.org/contributing/) for how to help.
## License
BetterSEQTA+ is licensed under the [MIT License](../LICENSE).
+587
View File
@@ -0,0 +1,587 @@
# Theme Creation Guide
**Published version:** [docs.betterseqta.org/theme-creation/](https://docs.betterseqta.org/theme-creation/)
This guide covers everything you need to know about creating custom themes for BetterSEQTA+.
## Table of Contents
1. [Overview](#overview)
2. [Theme Structure](#theme-structure)
3. [CSS Variables](#css-variables)
4. [CSS Selectors & Classes](#css-selectors--classes)
5. [Custom Images](#custom-images)
6. [Theme Settings](#theme-settings)
7. [Best Practices](#best-practices)
8. [Examples](#examples)
## Overview
Themes in BetterSEQTA+ allow you to completely customize the appearance of SEQTA Learn. A theme consists of:
- **Custom CSS**: CSS rules that override default styles
- **Custom Images**: Images that can be referenced via CSS variables
- **Theme Metadata**: Name, description, default color, etc.
- **Theme Settings**: Options like forcing dark/light mode
Themes are applied by injecting CSS into the SEQTA page and setting CSS custom properties (variables) on the document root.
## CSS Variables
BetterSEQTA+ provides a comprehensive set of CSS variables that you can use in your themes. These variables automatically adapt to light/dark mode and user preferences.
### Core Background Variables
| Variable | Light Mode | Dark Mode | Description |
|----------|------------|-----------|-------------|
| `--background-primary` | `#ffffff` | `#232323` | Main background color |
| `--background-secondary` | `#e5e7eb` | `#1a1a1a` | Secondary background color |
| `--theme-primary` | `#ffffff` | `#232323` | Primary theme color (same as background-primary) |
| `--theme-secondary` | `#e5e7eb` | `#1a1a1a` | Secondary theme color (same as background-secondary) |
| `--text-primary` | `black` | `white` | Primary text color |
| `--text-color` | `black` | `white` | Text color (alias for text-primary) |
### BetterSEQTA+ Specific Variables
| Variable | Description | Notes |
|----------|-------------|-------|
| `--better-main` | User's selected accent color | Dynamically set based on color picker |
| `--better-sub` | Dark navy color | Always `#161616` |
| `--better-pale` | Lightened version of accent color | Only available in light mode |
| `--better-light` | Lighter version of accent color | Calculated based on brightness |
| `--better-alert-highlight` | Alert/highlight color | `#c61851` |
| `--betterseqta-logo` | Logo URL | Changes based on dark/light mode |
| `--auto-background` | Auto background color | Falls back to `--better-pale` or `--background-secondary` |
| `--navy` | Navy color | `#1a1a1a` |
| `--theme-fg-parts` | Theme foreground parts | `white` |
### Subject/Item Color Variables
| Variable | Description |
|----------|-------------|
| `--item-colour` | Subject/item color | Set dynamically per subject/item |
| `--colour` | Generic color variable | Used in various contexts |
| `--person-colour` | Person/avatar color | `var(--better-light)` for staff |
### Transparency Effects
When transparency effects are enabled, background variables become semi-transparent:
| Variable | Light Mode (Transparent) | Dark Mode (Transparent) |
|----------|--------------------------|-------------------------|
| `--background-primary` | `rgba(255, 255, 255, 0.6)` | `rgba(35, 35, 35, 0.6)` |
| `--background-secondary` | `rgba(229, 231, 235, 0.6)` | `rgba(26, 26, 26, 0.6)` |
### Using CSS Variables
You can use these variables in your custom CSS:
```css
/* Example: Style a custom element */
.my-custom-element {
background: var(--background-primary);
color: var(--text-primary);
border: 1px solid var(--better-main);
}
/* Example: Create a gradient */
.gradient-box {
background: linear-gradient(
to bottom,
var(--better-main),
var(--background-secondary)
);
}
```
## CSS Selectors & Classes
BetterSEQTA+ uses specific CSS selectors and classes that you can target in your themes. Here are the most important ones:
### Main Layout Elements
| Selector | Description |
|----------|-------------|
| `#container` | Main container element |
| `#content` | Content area |
| `#main` | Main content wrapper |
| `#title` | Top title bar |
| `#menu` | Sidebar menu |
### Dark Mode
The `dark` class is added to `html` when dark mode is active:
```css
/* Target dark mode specifically */
html.dark #main {
background: var(--background-primary);
}
/* Target light mode */
html:not(.dark) #main {
background: var(--background-primary);
}
```
### Transparency Effects
When transparency effects are enabled, the `transparencyEffects` class is added to `html`:
```css
html.transparencyEffects .notice {
backdrop-filter: blur(80px);
}
```
### Common SEQTA Classes
| Class/Selector | Description |
|----------------|-------------|
| `.notice` | Notice cards |
| `.day` | Day containers in timetable |
| `.dashboard` | Dashboard sections |
| `.dashlet` | Dashboard widgets |
| `.document` | Document elements |
| `.quickbar` | Quick action bar |
| `.calendar` | Calendar elements |
| `.message` | Message elements |
| `.thread` | Forum threads |
| `.shortcut` | Shortcut buttons |
| `.upcoming-assessment` | Upcoming assessments |
| `.entry.class` | Timetable entries |
### BetterSEQTA+ Specific Classes
| Class | Description |
|-------|-------------|
| `.addedButton` | BetterSEQTA+ added buttons |
| `.tooltip` | Tooltip elements |
| `.notice-unified-content` | Unified notice content |
| `.home-container` | Home page container |
| `.timetable-container` | Timetable container |
| `.notices-container` | Notices container |
### Attribute Selectors
SEQTA uses data attributes that you can target:
```css
/* Target specific data types */
[data-type="student"] .header {
color: var(--text-primary);
}
/* Target specific labels */
[data-label="inbox"] {
/* Styles */
}
```
### CSS Modules
SEQTA uses CSS modules with hashed class names. You can target them using attribute selectors:
```css
/* Target CSS module classes */
[class*="MessageList__MessageList___"] {
background: var(--background-primary);
}
[class*="BasicPanel__BasicPanel___"] {
border-radius: 16px;
}
```
## Custom Images
Themes can include custom images that are made available as CSS variables.
### Adding Images
1. Upload an image in the theme creator
2. Set a CSS variable name (e.g., `custom-background`)
3. The image will be available as `var(--custom-background)`
### Using Image Variables
```css
/* Use as background */
.my-element {
background-image: var(--custom-background);
background-size: cover;
background-position: center;
}
/* Use in content */
.my-icon::before {
content: '';
background-image: var(--custom-icon);
width: 24px;
height: 24px;
}
```
### Image Variable Format
Images are stored as `url()` values:
```css
/* The variable contains: url(blob:...) */
--custom-background: url(blob:chrome-extension://...);
```
## Theme Settings
### Force Dark/Light Mode
You can force a theme to always use dark or light mode:
```typescript
forceDark: true // Force dark mode
forceDark: false // Force light mode
forceDark: undefined // Use user's preference (default)
```
When `forceDark` is set, users cannot toggle dark/light mode while the theme is active.
### Default Color
Set a default accent color for your theme:
```typescript
defaultColour: "rgba(0, 123, 255, 1)" // Blue
defaultColour: "#ff6b6b" // Red (hex format)
```
### Allow Color Changes
Control whether users can change the accent color:
```typescript
CanChangeColour: true // Users can change color
CanChangeColour: false // Color is locked
```
## Best Practices
### 1. Use CSS Variables
Always use CSS variables instead of hardcoded colors:
```css
/* Good */
.my-element {
background: var(--background-primary);
color: var(--text-primary);
}
/* Bad */
.my-element {
background: #ffffff;
color: #000000;
}
```
### 2. Support Both Light and Dark Modes
Unless your theme forces a specific mode, ensure it works in both:
```css
/* Use variables that adapt automatically */
.my-element {
background: var(--background-primary);
color: var(--text-primary);
}
/* Or explicitly handle both modes */
html.dark .my-element {
background: #1a1a1a;
}
html:not(.dark) .my-element {
background: #ffffff;
}
```
### 3. Use !important Sparingly
Only use `!important` when necessary to override SEQTA's default styles:
```css
/* Good - necessary override */
#title {
background: var(--background-primary) !important;
}
/* Bad - unnecessary */
.my-element {
color: var(--text-primary) !important;
}
```
### 4. Test Responsive Design
SEQTA is responsive. Test your theme at different screen sizes:
```css
/* Example: Mobile-specific styles */
@media (max-width: 900px) {
#menu {
transform: translate(-270px);
}
}
```
### 5. Use Semantic Selectors
Prefer semantic selectors over fragile ones:
```css
/* Good - stable selector */
#main > .dashboard > section {
border-radius: 16px;
}
/* Caution - CSS module classes may change */
[class*="Dashboard__Dashboard___"] {
border-radius: 16px;
}
```
### 6. Optimize Images
Keep image file sizes reasonable:
- Use appropriate formats (PNG for transparency, JPG for photos)
- Compress images before uploading
- Consider using CSS for simple graphics instead of images
### 7. Document Your Theme
Include comments in your CSS explaining complex styles:
```css
/*
* Custom gradient background for dashboard
* Uses the user's accent color for a cohesive look
*/
#main > .dashboard {
background: linear-gradient(
135deg,
var(--better-main),
var(--background-secondary)
);
}
```
## Examples
### Example 1: Simple Color Theme
```css
/* Change accent color throughout */
:root {
--better-main: #ff6b6b;
}
/* Style the menu */
#menu {
background: var(--background-primary);
border-right: 3px solid var(--better-main);
}
/* Style buttons */
.uiButton {
background: var(--better-main);
color: var(--text-color);
border-radius: 8px;
}
```
### Example 2: Custom Background Image
```css
/* Use a custom background image */
body {
background-image: var(--custom-background);
background-size: cover;
background-attachment: fixed;
}
/* Add overlay for readability */
#main::before {
content: '';
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.3);
z-index: -1;
}
```
### Example 3: Rounded Corners Theme
```css
/* Make everything more rounded */
#main > .dashboard > section,
.dashlet,
.notice,
.document {
border-radius: 20px !important;
}
/* Round buttons */
.uiButton {
border-radius: 25px !important;
}
```
### Example 4: Minimal Theme
```css
/* Remove shadows and borders */
#main > .dashboard > section,
.dashlet,
.notice {
box-shadow: none !important;
border: 1px solid var(--background-secondary) !important;
}
/* Simplify colors */
#menu {
background: var(--background-primary) !important;
}
/* Remove gradients */
.day {
background: var(--background-primary) !important;
}
```
### Example 5: High Contrast Theme
```css
/* Increase contrast */
:root {
--background-primary: #000000;
--background-secondary: #1a1a1a;
--text-primary: #ffffff;
}
html:not(.dark) {
--background-primary: #ffffff;
--background-secondary: #f0f0f0;
--text-primary: #000000;
}
/* Add borders for clarity */
.dashlet,
.notice,
.document {
border: 2px solid var(--better-main) !important;
}
```
## Advanced Techniques
### CSS Custom Properties Override
You can override CSS variables in your theme:
```css
/* Override a variable */
:root {
--better-main: #your-color;
}
/* Override conditionally */
html.dark {
--background-primary: #your-dark-color;
}
```
### Animations
Add smooth transitions:
```css
/* Smooth color transitions */
#menu li {
transition: background-color 0.3s ease;
}
/* Hover effects */
.dashlet:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
transition: all 0.3s ease;
}
```
### Pseudo-elements
Use pseudo-elements for decorative elements:
```css
/* Add decorative border */
.notice::before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 4px;
background: var(--better-main);
}
```
## Troubleshooting
### Theme Not Applying
1. Check browser console for CSS errors
2. Verify CSS syntax is correct
3. Ensure selectors are specific enough
4. Check if `!important` is needed
### Colors Not Changing
1. Verify you're using CSS variables
2. Check if `forceDark` is overriding your styles
3. Ensure variables are set on `:root` or `html`
### Images Not Showing
1. Verify image variable name matches CSS
2. Check image format is supported
3. Ensure image size is reasonable
4. Verify `url()` wrapper in CSS
### Dark Mode Issues
1. Test with `forceDark: true` and `forceDark: false`
2. Check if transparency effects are interfering
3. Verify `html.dark` selector is correct
## Resources
- **Theme Creator**: Access via BetterSEQTA+ settings
- **CSS Variables Reference**: See [CSS Variables](#css-variables) section above
- **SEQTA DOM Structure**: Inspect SEQTA pages in browser DevTools
- **BetterSEQTA+ Source**: Check `src/css/injected.scss` for default styles
## Contributing Themes
If you create a great theme, consider sharing it:
1. Export your theme (Share button in theme creator)
2. Submit to the BetterSEQTA+ theme store
3. Or share on GitHub/Discord
---
**Note**: This documentation is based on BetterSEQTA+ v3.4.13. Some details may change in future versions.
+350
View File
@@ -0,0 +1,350 @@
# Troubleshooting Guide
**Published version:** [docs.betterseqta.org/troubleshooting/](https://docs.betterseqta.org/troubleshooting/)
Having issues with BetterSEQTA+ development? This guide covers the most common problems and their solutions.
## Table of Contents
- [Installation Issues](#installation-issues)
- [Development Server Issues](#development-server-issues)
- [Browser Extension Issues](#browser-extension-issues)
- [Plugin Development Issues](#plugin-development-issues)
- [Build Issues](#build-issues)
- [Still Stuck?](#still-stuck)
## Installation Issues
### ❌ "npm install" fails with peer dependency errors
**Problem**: You see errors about peer dependencies or conflicting packages.
**Solution**:
```bash
rm -rf node_modules package-lock.json
npm install --legacy-peer-deps
```
### ❌ "Cannot find module" errors
**Problem**: Node.js can't find required packages.
**Solutions**:
1. **Clear and reinstall**:
```bash
rm -rf node_modules
npm install --legacy-peer-deps
```
2. **Check Node.js version**:
```bash
node --version # Should be v16 or higher
```
3. **Try with npm cache clean**:
```bash
npm cache clean --force
npm install --legacy-peer-deps
```
### ❌ Permission errors on macOS/Linux
**Problem**: "EACCES" or permission denied errors.
**Solution**:
```bash
sudo chown -R $(whoami) ~/.npm
sudo chown -R $(whoami) /usr/local/lib/node_modules
```
## Development Server Issues
### ❌ "npm run dev" fails
**Problem**: Development server won't start.
**Solutions**:
1. **Check if port is in use**:
```bash
lsof -i :5173 # Kill the process using the port
```
2. **Clear dist folder**:
```bash
rm -rf dist
npm run dev
```
3. **Check for TypeScript errors**:
```bash
npx tsc --noEmit # Check for type errors
```
### ❌ Changes not reflecting in browser
**Problem**: You make code changes but don't see them in the browser.
**Solutions**:
1. **Reload the extension**:
- Go to `chrome://extensions`
- Find BetterSEQTA+ and click the refresh icon
- Refresh your SEQTA page
2. **Check if dev server is running**:
- Look for "Build completed" in your terminal
- If not, restart `npm run dev`
3. **Hard refresh the page**:
- Press `Ctrl+Shift+R` (or `Cmd+Shift+R` on Mac)
## Browser Extension Issues
### ❌ Extension doesn't load in Chrome
**Problem**: Extension appears in `chrome://extensions` but doesn't work.
**Solutions**:
1. **Check for errors**:
- Go to `chrome://extensions`
- Click "Errors" button on BetterSEQTA+
- Fix any JavaScript errors shown
2. **Verify manifest**:
- Check if `dist/manifest.json` exists
- Ensure it has proper structure
3. **Check permissions**:
- Extension needs permission to access SEQTA pages
- Click "Details" → "Site access" → "On all sites"
### ❌ Extension doesn't appear on SEQTA pages
**Problem**: Extension loads but doesn't modify SEQTA.
**Solutions**:
1. **Check if you're on a SEQTA Learn page**:
- URL should contain "seqta" or "learn"
- Page title should include "SEQTA Learn"
2. **Check browser console**:
- Press `F12` → Console tab
- Look for "[BetterSEQTA+]" messages
- If no messages, extension isn't running
3. **Verify page detection**:
- Extension only runs on actual SEQTA Learn pages
- Test on a real SEQTA instance
### ❌ Settings page won't open
**Problem**: Clicking the extension icon doesn't open settings.
**Solutions**:
1. **Check popup errors**:
- Right-click extension icon → "Inspect popup"
- Look for JavaScript errors
2. **Clear extension storage**:
```javascript
// In browser console on any page:
chrome.storage.local.clear()
```
3. **Reload extension and try again**
## Plugin Development Issues
### ❌ My plugin doesn't appear in settings
**Problem**: Created a plugin but it's not showing up.
**Solutions**:
1. **Check plugin registration**:
- Ensure your plugin is imported in `src/plugins/index.ts`
- Verify `pluginManager.registerPlugin(yourPlugin)` is called
2. **Check plugin structure**:
```typescript
// Ensure your plugin has all required fields
const myPlugin: Plugin = {
id: "unique-id", // Must be unique
name: "Display Name",
description: "What it does",
version: "1.0.0",
run: async (api) => {
// Your code here
}
};
```
3. **Check for errors**:
- Look in browser console for plugin loading errors
### ❌ Plugin settings not working
**Problem**: Plugin settings don't save or load properly.
**Solutions**:
1. **Check settings definition**:
```typescript
import { defineSettings, booleanSetting } from "@/plugins/core/settingsHelpers";
const settings = defineSettings({
myOption: booleanSetting({
default: true,
title: "My Option",
description: "What this does"
})
});
```
2. **Wait for settings to load**:
```typescript
run: async (api) => {
await api.settings.loaded; // Wait for settings to load
console.log(api.settings.myOption); // Now you can use settings
}
```
### ❌ Plugin API functions not working
**Problem**: `api.seqta.onMount()` or other API functions don't work.
**Solutions**:
1. **Check selector specificity**:
```typescript
// Be specific with selectors
api.seqta.onMount(".home-page", (element) => {
// Your code
});
```
2. **Wait for elements**:
```typescript
// Some elements load after page navigation
api.seqta.onPageChange((page) => {
if (page === "home") {
api.seqta.onMount(".home-content", (element) => {
// Now element should exist
});
}
});
```
## Build Issues
### ❌ "npm run build" fails
**Problem**: Production build fails with errors.
**Solutions**:
1. **Check TypeScript errors**:
```bash
npx tsc --noEmit
```
2. **Clear cache and rebuild**:
```bash
rm -rf dist node_modules
npm install --legacy-peer-deps
npm run build
```
3. **Check for import errors**:
- Ensure all imports use correct paths
- Check for missing files
### ❌ Built extension doesn't work
**Problem**: `npm run build` succeeds but extension doesn't work.
**Solutions**:
1. **Test the built extension**:
- Load the `dist` folder as unpacked extension
- Check console for errors
2. **Compare with dev version**:
- If dev works but build doesn't, there might be a build configuration issue
3. **Check manifest generation**:
- Verify `dist/manifest.json` looks correct
- Compare with working version
## Common Error Messages
### "Cannot access contents of the URL"
- **Cause**: Extension permissions issue
- **Fix**: Go to `chrome://extensions` → BetterSEQTA+ → Details → Site access → "On all sites"
### "Extension context invalidated"
- **Cause**: Extension was reloaded while page was open
- **Fix**: Refresh the SEQTA page
### "Uncaught ReferenceError: browser is not defined"
- **Cause**: Missing webextension-polyfill import
- **Fix**: Add `import browser from "webextension-polyfill";` at top of file
### "Module not found: Can't resolve '@/...' "
- **Cause**: TypeScript path mapping issue
- **Fix**: Check `tsconfig.json` and `vite.config.ts` for path configuration
## Performance Issues
### Extension makes SEQTA slow
1. **Check for memory leaks**:
- Use Chrome DevTools → Performance tab
- Look for growing memory usage
2. **Optimize plugin code**:
- Remove unnecessary listeners
- Clean up intervals/timeouts
- Use efficient selectors
3. **Profile your changes**:
- Test with extension disabled vs enabled
- Identify which plugin is causing issues
## Still Stuck?
If none of these solutions work:
1. **🔍 Search existing issues**: [GitHub Issues](https://github.com/BetterSEQTA/BetterSEQTA-plus/issues)
2. **💬 Ask on Discord**: [Join our server](https://discord.gg/YzmbnCDkat) - fastest way to get help!
3. **📝 Create a new issue**: Include:
- Your operating system
- Node.js version (`node --version`)
- Browser version
- Exact error message
- Steps to reproduce
- What you've already tried
4. **📧 Email us**: betterseqta.plus@gmail.com for urgent issues
## Getting More Debug Info
### Enable verbose logging
Add this to your plugin's `run` function:
```typescript
console.log("[DEBUG] Plugin starting:", api);
```
### Check extension background page
1. Go to `chrome://extensions`
2. Click "Details" on BetterSEQTA+
3. Click "Inspect views: background page"
4. Check console for background script errors
### Export debug info
Run this in browser console on a SEQTA page:
```javascript
console.log("Extension info:", {
version: chrome.runtime.getManifest().version,
url: window.location.href,
userAgent: navigator.userAgent,
storage: await chrome.storage.local.get()
});
```
Remember: **Don't give up!** Every developer faces these issues. The community is here to help, and solving these problems makes you a better developer. 💪
+270
View File
@@ -0,0 +1,270 @@
# Contributing to BetterSEQTA+
**Published version:** [docs.betterseqta.org/contributing/](https://docs.betterseqta.org/contributing/)
Thank you for your interest in contributing to BetterSEQTA+! This document provides guidelines and instructions for contributing to the project.
## Table of Contents
- [Code of Conduct](#code-of-conduct)
- [Getting Started](#getting-started)
- [Setting Up Your Development Environment](#setting-up-your-development-environment)
- [Project Structure](#project-structure)
- [Contributing Code](#contributing-code)
- [Branching Strategy](#branching-strategy)
- [Pull Request Process](#pull-request-process)
- [Coding Standards](#coding-standards)
- [Reporting Bugs](#reporting-bugs)
- [Suggesting Features](#suggesting-features)
- [Writing Documentation](#writing-documentation)
- [Community](#community)
## Code of Conduct
BetterSEQTA+ is committed to providing a welcoming and inclusive environment for all contributors. We expect all participants to adhere to our Code of Conduct, which promotes respectful and harassment-free interaction.
Key points:
- Be respectful and inclusive
- Focus on what is best for the community
- Show empathy towards other community members
- Be open to constructive feedback
## Getting Started
### Setting Up Your Development Environment
1. **Fork the Repository**
Start by forking the BetterSEQTA+ repository to your GitHub account.
2. **Clone Your Fork**
```bash
git clone https://github.com/yourusername/betterseqta-plus.git
cd betterseqta-plus
```
3. **Install Dependencies**
```bash
npm install
```
4. **Set Up Development Environment**
```bash
npm run dev
```
5. **Install in Chrome/Firefox**
Follow the [installation instructions](https://docs.betterseqta.org/install/) to load the development version into your browser.
### Project Structure
Understanding the project structure will help you navigate the codebase:
```
betterseqta-plus/
├── src/ # Source code
│ ├── plugins/ # Plugin system
│ │ ├── built-in/ # Built-in plugins
│ │ ├── core/ # Plugin core functionality
│ ├── settings/ # Settings system
│ ├── utils/ # Utility functions
│ ├── extension/ # Browser extension code
├── docs/ # Documentation
├── test/ # Test files
├── dist/ # Build output (generated)
├── package.json # Project dependencies
├── tsconfig.json # TypeScript configuration
└── README.md # Project README
```
## Contributing Code
### Branching Strategy
We follow a simple branching strategy:
- `main` - The main development branch
- `feature/*` - Feature branches
- `bugfix/*` - Bug fix branches
- `docs/*` - Documentation branches
Always create a new branch for your changes:
```bash
git checkout -b feature/my-new-feature
```
### Pull Request Process
1. **Keep PRs Focused**
Each pull request should address a single concern. If you're working on multiple features, create separate PRs for each.
2. **Write Clear Commit Messages**
Follow the conventional commits format:
```
feat: add new feature
fix: resolve bug with timetable
docs: update installation instructions
```
3. **Update Documentation**
If your changes require documentation updates, include them in the same PR.
4. **Run Tests**
Make sure all tests pass before submitting your PR:
```bash
npm test
```
5. **Submit Your PR**
When you're ready, push your branch and create a pull request on GitHub.
6. **Code Review**
All PRs will be reviewed by maintainers. Be responsive to feedback and make requested changes.
7. **Merge**
Once approved, a maintainer will merge your PR.
### Coding Standards
We follow TypeScript best practices and have a consistent code style:
1. **Use TypeScript**
All new code should be written in TypeScript with proper typing.
2. **Follow Existing Patterns**
Match the coding style of the existing codebase.
3. **Write Tests**
Add tests for new features and bug fixes.
4. **Document Your Code**
Add comments for complex logic and JSDoc comments for functions.
5. **Use Linters**
We use ESLint and Prettier. Run them before submitting your PR:
```bash
npm run lint
npm run format
```
## Reporting Bugs
If you find a bug, please report it by creating an issue on GitHub:
1. **Search Existing Issues**
Check if the bug has already been reported.
2. **Use the Bug Report Template**
Fill in all sections of the bug report template:
- Description
- Steps to reproduce
- Expected behavior
- Actual behavior
- Screenshots (if applicable)
- Environment (browser, OS, etc.)
3. **Be Specific**
The more details you provide, the easier it will be to fix the bug.
## Suggesting Features
We welcome feature suggestions! To suggest a new feature:
1. **Search Existing Suggestions**
Check if your idea has already been suggested.
2. **Use the Feature Request Template**
Fill in all sections of the feature request template:
- Description
- Use case
- Potential implementation
- Alternatives considered
3. **Be Patient**
Feature requests are evaluated based on alignment with project goals, feasibility, and maintainer bandwidth.
## Writing Documentation
Good documentation is crucial for the project. To contribute to documentation:
1. **Identify Gaps**
Look for areas where documentation is missing or unclear.
2. **Follow Documentation Style**
Maintain a consistent style and format.
3. **Use Clear Language**
Write in simple, clear English. Avoid jargon when possible.
4. **Include Examples**
Code examples and screenshots help users understand.
5. **Submit a PR**
Follow the same process as code contributions, but create a branch with a `docs/` prefix.
## Community
Join our community channels to discuss the project, get help, and connect with other contributors:
- **Discord Server**: [Join our Discord](https://discord.gg/betterseqta)
- **GitHub Discussions**: For longer-form conversations
- **GitHub Issues**: For bug reports and feature requests
## Creating Plugins
If you're interested in creating plugins for BetterSEQTA+, check out our plugin development guides:
- [Plugin development](https://docs.betterseqta.org/plugin-development/)
- [Plugin API](https://docs.betterseqta.org/plugin-api/)
## Recognition
Contributors are recognized in several ways:
1. **CONTRIBUTORS.md**: All contributors are listed in this file
2. **Release Notes**: Significant contributions are highlighted in release notes
3. **Community Recognition**: Regular shout-outs in community channels
## Questions?
If you have any questions about contributing, please:
1. Check the documentation
2. Ask in the Discord server
3. Open a GitHub Discussion
Thank you for contributing to BetterSEQTA+! Your efforts help make SEQTA better for students and teachers everywhere.
+184
View File
@@ -0,0 +1,184 @@
# Installing BetterSEQTA+
**Published version:** [docs.betterseqta.org/install/](https://docs.betterseqta.org/install/)
This guide will walk you through the process of installing and setting up BetterSEQTA+ for development or usage.
## Prerequisites
Before you begin, make sure you have the following installed:
- [npm](https://www.npmjs.com/) (v7 or higher) or [Bun](https://bun.sh/) (recommended)
- A modern web browser (Chrome, Firefox, Edge, etc.)
## Installation Methods
There are two ways to install BetterSEQTA+:
1. **For Users**: Install the browser extension
2. **For Developers**: Clone the repository and set up the development environment
## For Users: Installing the Browser Extension
BetterSEQTA+ is available as a browser extension for Chrome, Firefox, and Edge.
### Chrome/Edge
1. Visit the [Chrome Web Store page for BetterSEQTA+](https://chrome.google.com/webstore/detail/betterseqta)
2. Click the "Add to Chrome" button
3. Confirm the installation when prompted
4. The extension will be installed and ready to use
### Firefox
1. Visit the [Firefox Add-ons page for BetterSEQTA+](https://addons.mozilla.org/en-US/firefox/addon/betterseqta)
2. Click the "Add to Firefox" button
3. Confirm the installation when prompted
4. The extension will be installed and ready to use
## For Developers: Setting Up the Development Environment
If you want to develop for BetterSEQTA+ or modify the code, follow these steps:
### 1. Clone the Repository
```bash
git clone https://github.com/SeqtaLearning/betterseqta-plus.git
cd betterseqta-plus
```
### 2. Install Dependencies
Using npm:
```bash
npm install --legacy-peer-deps
```
Using Bun (recommended):
```bash
bun install
```
### 3. Set Up Environment Variables - Only required for pushing to extension stores from the command line
Copy the example environment file:
```bash
cp .env.submit.example .env
```
Edit the `.env` file with your SEQTA credentials and settings.
### 4. Start the Development Server
Using npm:
```bash
npm run dev
```
Using Bun:
```bash
bun run dev
```
This will start a development server and build the extension in watch mode.
### 5. Load the Extension in Your Browser
#### Chrome/Edge
1. Open Chrome/Edge and navigate to `chrome://extensions` or `edge://extensions`
2. Enable "Developer mode" using the toggle in the top right
3. Click "Load unpacked" and select the `dist` folder in your BetterSEQTA+ directory
4. The extension should now appear in your extensions list
#### Firefox
1. Open Firefox and navigate to `about:debugging#/runtime/this-firefox`
2. Click "Load Temporary Add-on..."
3. Select the `manifest.json` file in the `dist` folder
4. The extension should now appear in your add-ons list
### 6. Test Your Changes
After making changes to the code, the development server will automatically rebuild the extension. However, you may need to reload the extension in your browser to see the changes:
1. Go to the extensions page in your browser
2. Find BetterSEQTA+ and click the reload icon
3. Refresh any SEQTA Learn pages you have open
## Troubleshooting Installation
### Common Issues
#### "Cannot find module" errors
If you see errors about missing modules, try:
```bash
rm -rf node_modules
npm install
```
Or with Bun:
```bash
rm -rf node_modules
bun install
```
#### Extension not appearing in SEQTA
Make sure:
- You're visiting a SEQTA Learn page
- The extension is enabled
- You've refreshed the page after installing the extension
#### Development build not updating
Try:
1. Stopping the development server
2. Clearing your browser cache
3. Removing the extension from your browser
4. Rebuilding the extension
5. Loading it again
## Updating BetterSEQTA+
### For Users
Browser extensions update automatically, but you can manually check for updates:
- **Chrome/Edge**: Go to `chrome://extensions` or `edge://extensions`, enable Developer mode, and click "Update"
- **Firefox**: Go to `about:addons`, click the gear icon, and select "Check for Updates"
### For Developers
If you're working on the code, pull the latest changes and reinstall dependencies:
```bash
git pull
npm install
npm run dev
```
Or with Bun:
```bash
git pull
bun install
bun run dev
```
## Next Steps
Now that you have BetterSEQTA+ installed, you can:
- [Plugins](https://docs.betterseqta.org/plugins/)
- [Contribute to the project](https://docs.betterseqta.org/contributing/) · [Repository CONTRIBUTING.md](../CONTRIBUTING.md)
+337
View File
@@ -0,0 +1,337 @@
# Example Plugin Template
**Published version:** [docs.betterseqta.org/example-plugin/](https://docs.betterseqta.org/example-plugin/)
This is a complete, working example of a simple BetterSEQTA+ plugin. You can copy this code and modify it to create your own plugin!
## What This Example Does
This plugin adds a friendly welcome message to the SEQTA homepage and lets users customize the message through settings.
## Complete Plugin Code
Create a new file in `src/plugins/built-in/my-first-plugin/index.ts`:
```typescript
import type { Plugin } from "@/plugins/core/types";
import { BasePlugin } from "@/plugins/core/settings";
import {
defineSettings,
booleanSetting,
stringSetting
} from "@/plugins/core/settingsHelpers";
import { Setting } from "@/plugins/core/settingsHelpers";
// Define the plugin settings
const settings = defineSettings({
enabled: booleanSetting({
default: true,
title: "Show Welcome Message",
description: "Display a welcome message on the SEQTA homepage"
}),
customMessage: stringSetting({
default: "Welcome to SEQTA! 🎉",
title: "Custom Message",
description: "The message to display on the homepage",
maxLength: 100
}),
showEmoji: booleanSetting({
default: true,
title: "Show Emoji",
description: "Include emojis in the welcome message"
})
});
// Create settings class
class MyFirstPluginSettings extends BasePlugin<typeof settings> {
@Setting(settings.enabled)
enabled!: boolean;
@Setting(settings.customMessage)
customMessage!: string;
@Setting(settings.showEmoji)
showEmoji!: boolean;
}
// Create settings instance
const settingsInstance = new MyFirstPluginSettings();
// Define the plugin
const myFirstPlugin: Plugin<typeof settings> = {
id: "my-first-plugin",
name: "My First Plugin",
description: "Adds a customizable welcome message to the SEQTA homepage",
version: "1.0.0",
// Link our settings
settings: settingsInstance.settings,
// Mark as beta (optional)
beta: true,
// Add some CSS styles (optional)
styles: `
.my-plugin-welcome {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px;
border-radius: 12px;
margin: 20px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
text-align: center;
font-size: 18px;
animation: slideIn 0.5s ease-out;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.my-plugin-welcome .close-btn {
float: right;
background: rgba(255, 255, 255, 0.2);
border: none;
color: white;
padding: 5px 10px;
border-radius: 20px;
cursor: pointer;
font-size: 12px;
}
.my-plugin-welcome .close-btn:hover {
background: rgba(255, 255, 255, 0.3);
}
`,
// Main plugin function
run: async (api) => {
console.log("[My First Plugin] Starting up! 🚀");
// Wait for settings to load
await api.settings.loaded;
let welcomeElement: HTMLElement | null = null;
// Function to create the welcome message
const createWelcomeMessage = () => {
// Only show if enabled in settings
if (!api.settings.enabled) {
return;
}
// Remove existing message if it exists
if (welcomeElement) {
welcomeElement.remove();
}
// Create the message element
welcomeElement = document.createElement("div");
welcomeElement.className = "my-plugin-welcome";
// Build the message content
let message = api.settings.customMessage;
if (!api.settings.showEmoji) {
// Remove emojis if disabled
message = message.replace(/[\u{1F600}-\u{1F64F}]|[\u{1F300}-\u{1F5FF}]|[\u{1F680}-\u{1F6FF}]|[\u{1F1E0}-\u{1F1FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]/gu, '');
}
welcomeElement.innerHTML = `
<button class="close-btn" onclick="this.parentElement.remove()">×</button>
<div>${message}</div>
<small style="opacity: 0.8; margin-top: 10px; display: block;">
Powered by My First Plugin
</small>
`;
return welcomeElement;
};
// Function to add message to homepage
const addToHomepage = () => {
api.seqta.onMount(".home-page, .dashboard, [class*='home']", (homePage) => {
console.log("[My First Plugin] Found homepage, adding welcome message");
const message = createWelcomeMessage();
if (message) {
// Add to the top of the homepage
homePage.insertBefore(message, homePage.firstChild);
}
});
};
// Add message when plugin starts
addToHomepage();
// Re-add message when user navigates to homepage
api.seqta.onPageChange((page) => {
console.log("[My First Plugin] Page changed to:", page);
if (page.includes("home") || page.includes("dashboard")) {
// Small delay to let the page load
setTimeout(addToHomepage, 500);
}
});
// Listen for settings changes and update the message
api.settings.onChange("enabled", (enabled) => {
console.log("[My First Plugin] Enabled setting changed:", enabled);
if (enabled) {
addToHomepage();
} else if (welcomeElement) {
welcomeElement.remove();
welcomeElement = null;
}
});
api.settings.onChange("customMessage", (newMessage) => {
console.log("[My First Plugin] Message changed:", newMessage);
if (welcomeElement && api.settings.enabled) {
// Update existing message
addToHomepage();
}
});
api.settings.onChange("showEmoji", (showEmoji) => {
console.log("[My First Plugin] Show emoji changed:", showEmoji);
if (welcomeElement && api.settings.enabled) {
// Update existing message
addToHomepage();
}
});
// Return cleanup function (called when plugin is disabled)
return () => {
console.log("[My First Plugin] Cleaning up...");
if (welcomeElement) {
welcomeElement.remove();
welcomeElement = null;
}
};
}
};
export default myFirstPlugin;
```
## How to Use This Example
### Step 1: Create the Plugin File
1. Create a new folder: `src/plugins/built-in/my-first-plugin/`
2. Create `index.ts` in that folder
3. Copy the code above into `index.ts`
### Step 2: Register the Plugin
Add this to `src/plugins/index.ts`:
```typescript
// Add this import at the top
import myFirstPlugin from "./built-in/my-first-plugin";
// Add this line where other plugins are registered
pluginManager.registerPlugin(myFirstPlugin);
```
### Step 3: Test It
1. Run `npm run dev`
2. Reload your extension in Chrome
3. Visit a SEQTA page
4. You should see your welcome message!
5. Open BetterSEQTA+ settings to customize it
## Key Concepts Explained
### 1. Plugin Structure
```typescript
const myPlugin: Plugin = {
id: "unique-id", // Must be unique across all plugins
name: "Display Name", // Shown in settings
description: "What it does", // Shown in settings
version: "1.0.0", // Plugin version
settings: settingsObject, // User-configurable options
styles: "/* CSS here */", // Optional CSS styles
run: async (api) => { // Main plugin code
// Your code here
}
};
```
### 2. Settings System
```typescript
// Define what settings your plugin has
const settings = defineSettings({
myOption: booleanSetting({
default: true,
title: "My Option",
description: "What this option does"
})
});
// Use in your plugin
if (api.settings.myOption) {
// Do something
}
```
### 3. SEQTA Integration
```typescript
// Wait for elements to appear
api.seqta.onMount(".some-selector", (element) => {
// Modify the element
});
// Detect page changes
api.seqta.onPageChange((page) => {
if (page === "home") {
// User navigated to homepage
}
});
```
### 4. Cleanup
Always return a cleanup function to remove your changes when the plugin is disabled:
```typescript
run: async (api) => {
// Add your features
return () => {
// Remove your features
};
}
```
## Customization Ideas
Want to modify this example? Here are some ideas:
1. **Change the styling**: Modify the CSS to use different colors, animations, or layouts
2. **Add more settings**: Number settings, select dropdowns, hotkeys
3. **Different trigger**: Show on different pages, or based on time of day
4. **Add interactions**: Buttons that do things when clicked
5. **Store data**: Use `api.storage` to remember user preferences
6. **Communicate with other plugins**: Use `api.events` to send/receive events
## Next Steps
Once you've got this working:
1. **Experiment**: Try changing things and see what happens
2. **Read other plugins**: Look at the built-in plugins for inspiration
3. **Check the API docs**: Learn about all available API functions
4. **Share your creation**: Show it off in Discord!
## Need Help?
- 💬 Ask in our [Discord server](https://discord.gg/YzmbnCDkat)
- 📚 Read the [plugin documentation](https://docs.betterseqta.org/plugins/)
- 🐛 Check the [troubleshooting guide](https://docs.betterseqta.org/troubleshooting/)
- 📝 Open an issue on GitHub
Happy coding! 🎉
+299
View File
@@ -0,0 +1,299 @@
# Creating Plugins for BetterSEQTA+
**Published version:** [docs.betterseqta.org/plugins/](https://docs.betterseqta.org/plugins/) · [Plugin development](https://docs.betterseqta.org/plugin-development/) · [Plugin API](https://docs.betterseqta.org/plugin-api/)
Hey there! 👋 So you want to create a plugin for BetterSEQTA+? That's awesome! This guide will walk you through everything you need to know, from the very basics to more advanced features. Don't worry if you're new to this - we'll explain everything step by step.
## What is a Plugin?
In BetterSEQTA+, a plugin is like a mini-app that adds new features to SEQTA. Think of it as a piece of LEGO that you can snap onto SEQTA to make it do new things. For example, you could create a plugin that:
- Changes how SEQTA looks
- Adds new buttons or features
- Shows extra information on your timetable
- Collects notifications in a better way
- Really, anything you can imagine!
## Your First Plugin
Let's create a super simple plugin together. We'll make one that adds a friendly message to the SEQTA homepage. Here's what we'll need:
```typescript
import type { Plugin } from "@/plugins/core/types";
const myFirstPlugin: Plugin = {
// Every plugin needs these basic details
id: "my-first-plugin",
name: "My First Plugin",
description: "Adds a friendly message to SEQTA",
version: "1.0.0",
// This tells BetterSEQTA+ that users can turn our plugin on/off
disableToggle: true,
// Optional: Mark your plugin as beta to show a "Beta" tag in settings
beta: true,
// This is where the magic happens!
run: async (api) => {
// Wait for the homepage to load
api.seqta.onMount(".home-page", (homePage) => {
// Create our message
const message = document.createElement("div");
message.textContent = "Hello from my first plugin! 🎉";
message.style.padding = "20px";
message.style.backgroundColor = "#e9f5ff";
message.style.borderRadius = "8px";
message.style.margin = "20px";
// Add it to the page
homePage.prepend(message);
});
// Return a cleanup function that removes our message when the plugin is disabled
return () => {
const message = document.querySelector(".home-page > div");
message?.remove();
};
},
};
export default myFirstPlugin;
```
Let's break down what's happening here:
1. First, we import the `Plugin` type that tells TypeScript what a plugin should look like
2. We create our plugin object with some basic information:
- `id`: A unique name for your plugin (use lowercase and dashes)
- `name`: A friendly name that users will see
- `description`: Explain what your plugin does
- `version`: Your plugin's version number
3. We set `disableToggle: true` so users can turn our plugin on/off in settings
4. We set `beta: true` to mark the plugin as beta
5. The `run` function is where we put our plugin's code
6. We use `api.seqta.onMount` to wait for the homepage to load
7. We create and style a message element
8. We return a cleanup function that removes our changes when the plugin is disabled
## The Plugin API
When your plugin runs, it gets access to a powerful API that lets you do all sorts of things. Let's look at what you can do:
### SEQTA API (`api.seqta`)
This helps you interact with SEQTA's pages:
```typescript
// Wait for an element to appear on the page
api.seqta.onMount(".some-class", (element) => {
// Do something with the element
});
// Know when the user changes pages
api.seqta.onPageChange((page) => {
console.log("User went to:", page);
});
// Get the current page
const currentPage = api.seqta.getCurrentPage();
```
### Settings API (`api.settings`)
Want to let users customize your plugin? Use settings!
```typescript
import { BasePlugin } from "@/plugins/core/settings";
import {
booleanSetting,
defineSettings,
Setting,
} from "@/plugins/core/settingsHelpers";
// Define your settings
const settings = defineSettings({
showMessage: booleanSetting({
default: true,
title: "Show Welcome Message",
description: "Show a friendly message on the homepage",
}),
});
// Create a class for your plugin
class MyPluginClass extends BasePlugin<typeof settings> {
@Setting(settings.showMessage)
showMessage!: boolean;
}
// Create your plugin
const settingsInstance = new MyPluginClass();
const myPlugin: Plugin<typeof settings> = {
// ... other plugin details ...
settings: settingsInstance.settings,
run: async (api) => {
// Use the setting
if (api.settings.showMessage) {
// Show the message
}
// Listen for setting changes
api.settings.onChange("showMessage", (newValue) => {
if (newValue) {
// Show the message
} else {
// Hide the message
}
});
},
};
```
### Storage API (`api.storage`)
Need to save some data? The storage API has got you covered:
```typescript
// Save some data
await api.storage.set("lastVisit", new Date().toISOString());
// Get it back later
const lastVisit = await api.storage.get("lastVisit");
// Listen for changes
api.storage.onChange("lastVisit", (newValue) => {
console.log("Last visit updated:", newValue);
});
```
### Events API (`api.events`)
Want your plugin to be able to interface with other plugins? Then use events!
```typescript
// Listen for an event
api.events.on("myCustomEvent", (data) => {
console.log("Got event:", data);
});
// Send an event
api.events.emit("myCustomEvent", { some: "data" });
```
## Adding Styles
Want to make your plugin look pretty? You can add CSS styles:
```typescript
const myPlugin: Plugin = {
// ... other plugin details ...
// Add your CSS here
styles: `
.my-plugin-message {
background: linear-gradient(135deg, #6e8efb, #a777e3);
color: white;
padding: 20px;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
margin: 20px;
animation: slide-in 0.3s ease-out;
}
@keyframes slide-in {
from { transform: translateY(-20px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
`,
run: async (api) => {
// Your plugin code here
},
};
```
## Best Practices
Here are some tips to make your plugin awesome:
1. **Always Clean Up**: When your plugin is disabled, clean up any changes you made:
```typescript
run: async (api) => {
// Add stuff to the page
const element = document.createElement("div");
document.body.appendChild(element);
// Return a cleanup function
return () => {
element.remove();
};
};
```
2. **Use TypeScript**: It helps catch errors before they happen and makes your code easier to understand.
3. **Test Your Plugin**: Make sure it works in different situations:
- When SEQTA is loading
- When the user switches pages
- When the plugin is enabled/disabled
- When settings are changed
4. **Keep It Fast**: Don't slow down SEQTA:
- Use `onMount` instead of intervals or timeouts
- Clean up event listeners when they're not needed
- Don't do heavy calculations on the main thread
5. **Make It User-Friendly**:
- Add clear settings with good descriptions
- Use `disableToggle: true` so users can turn it off if needed
- Add helpful error messages if something goes wrong
- Use `beta: true` for experimental features to let users know they're trying something new
## Plugin Metadata Options
Your plugin object supports several optional flags to customize how it appears and behaves:
```typescript
const myPlugin: Plugin = {
id: "my-plugin",
name: "My Plugin",
description: "What my plugin does",
version: "1.0.0",
// Optional flags:
disableToggle: true, // Show enable/disable toggle in settings
defaultEnabled: false, // Start disabled by default (requires disableToggle: true)
beta: true, // Show "Beta" tag in settings UI
// Your plugin code...
run: async (api) => { /* ... */ },
};
```
- **`disableToggle`**: When `true`, users can enable/disable your plugin in settings
- **`defaultEnabled`**: When `false`, your plugin starts disabled (only works with `disableToggle: true`)
- **`beta`**: When `true`, shows an orange "Beta" tag next to your plugin name in settings
## Examples
Want to see more examples? Check out our built-in plugins:
- [themes](../../src/plugins/built-in/themes/index.ts): Shows how to change SEQTA's appearance
- [notificationCollector](../../src/plugins/built-in/notificationCollector/index.ts): Shows how to work with SEQTA's notifications
- [timetable](../../src/plugins/built-in/timetable/index.ts): Shows how to modify SEQTA's timetable view
- [assessmentsAverage](../../src/plugins/built-in/assessmentsAverage/index.ts): Shows how to add new features to existing pages
## Need Help?
Got stuck? No worries! Here's where you can get help:
- Join our [Discord server](https://discord.gg/YzmbnCDkat)
- Check out the built-in plugins in the `src/plugins/built-in` folder
- Open an issue on our [GitHub page](https://github.com/betterseqta/betterseqta-plus/issues)
Happy coding and feel free to check out the [plugin API](https://docs.betterseqta.org/plugin-api/) on the documentation site.
+368
View File
@@ -0,0 +1,368 @@
# Plugin API Reference
**Published version:** [docs.betterseqta.org/plugin-api/](https://docs.betterseqta.org/plugin-api/)
This document provides detailed technical information about BetterSEQTA+'s plugin APIs. For a beginner-friendly introduction, see the [plugins section](https://docs.betterseqta.org/plugins/) at [docs.betterseqta.org](https://docs.betterseqta.org/).
## Plugin Structure
Here's how a plugin is structured:
```typescript
import type { Plugin } from "@/plugins/core/types";
import { BasePlugin } from "@/plugins/core/settings";
import {
booleanSetting,
defineSettings,
Setting,
} from "@/plugins/core/settingsHelpers";
// First, define your settings
const settings = defineSettings({
enabled: booleanSetting({
default: true,
title: "Enable Feature",
description: "Turn this feature on or off",
}),
});
// Create a class to handle your settings
class MyPluginClass extends BasePlugin<typeof settings> {
@Setting(settings.enabled)
enabled!: boolean;
}
// Create an instance of your settings
const settingsInstance = new MyPluginClass();
// Create your plugin
const myPlugin: Plugin<typeof settings> = {
id: "my-plugin",
name: "My Plugin",
description: "A cool plugin that does things",
version: "1.0.0",
settings: settingsInstance.settings,
disableToggle: true,
beta: true,
run: async (api) => {
console.log("Plugin is running!");
// Do stuff when settings change
api.settings.onChange("enabled", (enabled) => {
if (enabled) {
console.log("Feature enabled!");
}
});
// Return a cleanup function
return () => {
console.log("Plugin cleanup");
};
},
};
export default myPlugin;
```
## Plugin Metadata
The plugin object supports several metadata fields and options:
```typescript
interface Plugin {
// Required fields
id: string; // Unique identifier (lowercase, dashes)
name: string; // Display name shown to users
description: string; // Brief description of what the plugin does
version: string; // Semantic version (e.g., "1.0.0")
settings: PluginSettings; // Plugin settings object
run: (api: PluginAPI) => void; // Main plugin function
// Optional fields
styles?: string; // CSS styles to inject
disableToggle?: boolean; // Show enable/disable toggle in settings
defaultEnabled?: boolean; // Start enabled/disabled (requires disableToggle)
beta?: boolean; // Show "Beta" tag in settings UI
}
```
### Metadata Options
- **`disableToggle`**: When `true`, users can enable/disable your plugin in the settings page
- **`defaultEnabled`**: When `false`, your plugin starts disabled by default (only works with `disableToggle: true`)
- **`beta`**: When `true`, displays an orange "Beta" tag next to your plugin name in the settings UI
- **`styles`**: CSS string that gets injected into the page when your plugin runs
## SEQTA API
The SEQTA API helps you interact with SEQTA's pages:
```typescript
import type { Plugin } from "@/plugins/core/types";
const seqtaPlugin: Plugin<typeof settings> = {
id: "seqta-example",
name: "SEQTA Example",
description: "Shows how to use the SEQTA API",
version: "1.0.0",
settings: {},
disableToggle: true,
run: async (api) => {
// Wait for elements to appear
const { unregister: timetableUnregister } = api.seqta.onMount(
".timetable",
(timetable) => {
const button = document.createElement("button");
button.textContent = "Export";
timetable.appendChild(button);
},
);
// Track page changes
const { unregister: pageUnregister } = api.seqta.onPageChange((page) => {
console.log("User went to:", page);
});
// Clean up when disabled
return () => {
timetableUnregister();
pageUnregister();
};
},
};
export default seqtaPlugin;
```
## Settings API
Here's how to add settings to your plugin:
```typescript
import type { Plugin } from "@/plugins/core/types";
import { BasePlugin } from "@/plugins/core/settings";
import {
booleanSetting,
stringSetting,
numberSetting,
selectSetting,
defineSettings,
Setting,
} from "@/plugins/core/settingsHelpers";
// Define your settings
const settings = defineSettings({
darkMode: booleanSetting({
default: false,
title: "Dark Mode",
description: "Enable dark mode",
}),
userName: stringSetting({
default: "",
title: "User Name",
description: "Your display name",
placeholder: "Enter your name...",
}),
theme: selectSetting({
default: "light",
title: "Theme",
description: "Choose your theme",
options: [
{ value: "light", label: "Light" },
{ value: "dark", label: "Dark" },
],
}),
});
// Create your settings class
class ThemePluginClass extends BasePlugin<typeof settings> {
@Setting(settings.darkMode)
darkMode!: boolean;
@Setting(settings.userName)
userName!: string;
@Setting(settings.theme)
theme!: string;
}
// Create the plugin
const themePlugin: Plugin<typeof settings> = {
id: "theme-example",
name: "Theme Example",
description: "Shows how to use settings",
version: "1.0.0",
settings: new ThemePluginClass().settings,
disableToggle: true,
run: async (api) => {
// Apply initial settings
if (api.settings.darkMode) {
document.body.classList.add("dark");
}
// Listen for changes
const { unregister } = api.settings.onChange("darkMode", (enabled) => {
document.body.classList.toggle("dark", enabled);
});
return () => {
unregister();
document.body.classList.remove("dark");
};
},
};
export default themePlugin;
```
## Storage API
Here's how to use storage in your plugin:
```typescript
import type { Plugin } from "@/plugins/core/types";
const storagePlugin: Plugin<typeof settings> = {
id: "storage-example",
name: "Storage Example",
description: "Shows how to use storage",
version: "1.0.0",
settings: {},
disableToggle: true,
run: async (api) => {
// Wait for storage to be ready
await api.storage.loaded;
// Save some data
await api.storage.set("lastVisit", new Date().toISOString());
// Get saved data
const lastVisit = await api.storage.get("lastVisit");
console.log("Last visit:", lastVisit);
// Listen for changes
const { unregister } = api.storage.onChange("lastVisit", (newValue) => {
console.log("Last visit updated:", newValue);
});
return () => {
unregister();
};
},
};
export default storagePlugin;
```
## Events API
Here's how to use events in your plugin:
```typescript
import type { Plugin } from "@/plugins/core/types";
const eventsPlugin: Plugin<typeof settings> = {
id: "events-example",
name: "Events Example",
description: "Shows how to use events",
version: "1.0.0",
settings: {},
disableToggle: true,
run: async (api) => {
// Listen for theme changes
const { unregister: themeListener } = api.events.on(
"theme.changed",
(theme) => {
console.log("Theme changed to:", theme);
},
);
// Listen for notifications
const { unregister: notifyListener } = api.events.on(
"notification.new",
(notification) => {
console.log("New notification:", notification);
},
);
// Clean up listeners
return () => {
themeListener();
notifyListener();
};
},
};
export default eventsPlugin;
```
## Performance Tips
Here's how to write efficient plugins:
```typescript
import type { Plugin } from "@/plugins/core/types";
const efficientPlugin: Plugin<typeof settings> = {
id: "efficient-example",
name: "Efficient Example",
description: "Shows performance best practices",
version: "1.0.0",
settings: {},
disableToggle: true,
run: async (api) => {
// ✅ Good: Use onMount
const { unregister } = api.seqta.onMount(".timetable", (el) => {
el.classList.add("enhanced");
});
// ❌ Bad: Don't use intervals
// const interval = setInterval(() => {
// const el = document.querySelector('.timetable');
// if (el) el.classList.add('enhanced');
// }, 100);
// ✅ Good: Cache DOM elements
const header = document.querySelector(".header");
if (header) {
// Reuse header instead of querying again
}
// ✅ Good: Batch DOM updates
const fragment = document.createDocumentFragment();
for (let i = 0; i < 10; i++) {
const div = document.createElement("div");
fragment.appendChild(div);
}
document.body.appendChild(fragment);
return () => {
unregister();
// clearInterval(interval); // If you used the bad approach
};
},
};
export default efficientPlugin;
```
Each plugin should be in its own file and exported as the default export. The plugin should:
1. Import necessary types and helpers
2. Define settings if needed
3. Create a settings class if using settings
4. Create the plugin object with proper type annotation
5. Export the plugin as default
Remember to always:
- Use proper TypeScript types
- Clean up when your plugin is disabled
- Handle errors gracefully
- Follow the plugin structure shown above
+17
View File
@@ -0,0 +1,17 @@
export default {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/src'],
testMatch: [
'**/__tests__/**/*.ts',
'**/?(*.)+(spec|test).ts'
],
transform: {
'^.+\\.ts$': 'ts-jest',
},
moduleFileExtensions: ['ts', 'js', 'json'],
collectCoverageFrom: [
'src/**/*.ts',
'!src/**/*.d.ts',
],
};
+35 -2
View File
@@ -1,16 +1,49 @@
import fs from "fs";
import mime from "mime-types";
/**
* A Vite plugin designed to load files as base64 encoded data URLs.
* This plugin intercepts module imports that have a `?base64` query parameter
* appended to the file path. It then reads the targeted file, converts its content
* to a base64 string, and constructs a data URL which is then exported as the
* default export of a new JavaScript module.
*
* @example
* // To use this loader, import a file with ?base64 query:
* // import myImageBase64 from './path/to/myimage.png?base64';
* // myImageBase64 will then be a string like "data:image/png;base64,..."
*/
export const base64Loader = {
/**
* The name of the Vite plugin.
* @type {string}
*/
name: "base64-loader",
/**
* The core transformation function of the Vite plugin.
* It is called by Vite for modules that might need transformation. This function
* checks if the module ID includes the `?base64` query. If so, it reads the
* specified file, converts it to a base64 data URL, and returns a new
* JavaScript module that default exports this data URL.
*
* @param {any} _ The original code of the file. This parameter is unused by this loader.
* @param {string} id The ID of the module being transformed. This string typically
* contains the absolute file path and any query parameters
* (e.g., "/path/to/file.png?base64").
* @returns {string | null} If the module ID does not contain `?base64` query,
* it returns `null` to indicate no transformation.
* Otherwise, it returns a string of JavaScript code
* that default exports the base64 data URL of the file.
* For example: `export default 'data:image/png;base64,xxxx';`
*/
transform(_: any, id: string) {
const [filePath, query] = id.split("?");
if (query !== "base64") return null;
const data = fs.readFileSync(filePath, { encoding: 'base64' });
const data = fs.readFileSync(filePath, { encoding: "base64" });
const mimeType = mime.lookup(filePath);
const dataURL = `data:${mimeType};base64,${data}`;
return `export default '${dataURL}';`;
},
};
};
+58
View File
@@ -0,0 +1,58 @@
// ref: https://stackoverflow.com/a/76920975
import type { Plugin } from "vite";
/**
* Creates a Vite plugin designed to gracefully handle the conclusion of the build process.
* This plugin utilizes the `buildEnd` and `closeBundle` hooks provided by Vite.
* It checks for errors at the end of the build:
* - If an error occurred during the build (`buildEnd` hook receives an error), it logs the error
* and explicitly exits the Node.js process with a status code of 1 (indicating failure).
* - If the build completes without errors and the bundle is successfully generated
* (`closeBundle` hook is called), it logs a success message and exits the process
* with a status code of 0 (indicating success).
* This explicit process exiting can be useful in CI/CD environments or scripts that
* rely on the process status code to determine the build outcome.
* The core logic for using these hooks to exit the process is inspired by
* a solution found on StackOverflow (https://stackoverflow.com/a/76920975).
*
* @returns {Plugin} A Vite plugin object configured with `name`, `buildEnd`, and `closeBundle` hooks.
*/
export default function ClosePlugin(): Plugin {
return {
/**
* The unique name of this Vite plugin. This name is used by Vite for identification
* purposes and will appear in warnings, errors, and logs related to this plugin.
* @type {string}
*/
name: "ClosePlugin", // required, will show up in warnings and errors
/**
* A Vite hook that is called when the build process has finished, regardless of
* whether it was successful or encountered an error.
*
* @param {Error} [error] An optional error object. If the build failed, this parameter
* will contain the error that occurred. If the build was successful,
* this parameter will be undefined or null.
*/
buildEnd(error) {
if (error) {
console.error("Error bundling");
console.error(error);
process.exit(1); // Exit with status 1 indicating an error
} else {
console.log("Build ended"); // Log successful completion of the build phase
}
},
/**
* A Vite hook that is called after the `buildEnd` hook, but only if the build
* was successful (i.e., no errors were passed to `buildEnd`) and all output
* files have been generated and written to disk. This signifies the successful
* completion of the entire bundling process.
*/
closeBundle() {
console.log("Bundle closed"); // Log successful closure of the bundle
process.exit(0); // Exit with status 0 indicating a successful build
},
};
}
+28 -15
View File
@@ -1,12 +1,22 @@
import type { Browser, BuildTarget, Manifest } from './types'
import type { AnyCase } from './utils'
import type { Browser, BuildTarget, Manifest } from "./types";
import type { AnyCase } from "./utils";
/**
*
* Packages a given manifest object with a specific target browser identifier.
* This function is typically used in multi-browser extension build processes
* to create a configuration object that pairs the manifest data with the browser
* it's intended for. The `AnyCase<Browser>` type for the browser parameter
* implies that browser names like 'chrome', 'firefox', etc., can be provided
* in various casings.
*
* @export
* @param {Manifest} manifest
* @param {AnyCase<Browser>} browser
* @return {*} {@link BuildTarget}
* @param {Manifest} manifest The core manifest data for the extension,
* compatible with `chrome.runtime.ManifestV3` as defined by the {@link Manifest} type.
* @param {AnyCase<Browser>} browser The target browser identifier (e.g., 'chrome', 'firefox', 'CHROME').
* Refers to the {@link Browser} type, allowing for flexible casing.
* @returns {BuildTarget} An object that pairs the `manifest` with its target `browser`.
* The structure is `{ manifest: Manifest; browser: AnyCase<Browser>; }`
* as defined by the {@link BuildTarget} type.
*/
export function createManifest(
manifest: Manifest,
@@ -15,19 +25,22 @@ export function createManifest(
return {
manifest,
browser,
}
};
}
/**
* create a base Manifest to inherit from
* type Manifest = chrome.runtime.ManifestV3
*
* use as shared base to extend inBrowser manifests
* Defines a base manifest object.
* This function is typically used to establish a common, shared foundation for an extension's manifest
* (compatible with `chrome.runtime.ManifestV3` as per the {@link Manifest} type).
* This base can then be extended or modified for different browsers or specific build configurations.
* For example, you might define core permissions and properties here, and then add
* browser-specific keys in subsequent steps.
*
* @export
* @param {Manifest} manifest
* @return {*} {@link Manifest}
* @param {Manifest} manifest The core manifest data to be used as a base.
* This should conform to the {@link Manifest} type structure.
* @returns {Manifest} The provided manifest object, intended to serve as a reusable base.
*/
export function createManifestBase(manifest: Manifest): Manifest {
return manifest
}
return manifest;
}
+43
View File
@@ -0,0 +1,43 @@
import type { Plugin } from "vite";
/**
* Firefox extension pages forbid eval / `Function` constructor. Some deps still emit:
* - `Function(\`return this\`)()` (lodash-style global)
* - `try { return Function(\`\`) / new Function("") … }` (feature probes, e.g. PDF.js / ORT)
*/
export function firefoxStripFunctionProbe(): Plugin {
return {
name: "firefox-strip-function-probe",
apply: "build",
enforce: "post",
generateBundle(_options, bundle) {
if ((process.env.MODE || "chrome").toLowerCase() !== "firefox") return;
const literalReplacements: [string, string][] = [
['try{return new Function(""),!0}catch{return!1}', "return!1"],
["try{return new Function(''),!0}catch{return!1}", "return!1"],
['try{return new Function(""),true}catch{return false}', "return false"],
["try{return new Function(''),true}catch{return false}", "return false"],
// Empty template literal probe (minifier output)
["try{return Function(``),!0}catch{return!1}", "return!1"],
];
for (const chunk of Object.values(bundle)) {
if (chunk.type !== "chunk" || typeof chunk.code !== "string") continue;
let { code } = chunk;
code = code.replace(/Function\(`return this`\)\(\)/g, "(globalThis)");
code = code.replace(/Function\("return this"\)\(\)/g, "(globalThis)");
code = code.replace(/Function\('return this'\)\(\)/g, "(globalThis)");
for (const [from, to] of literalReplacements) {
if (code.includes(from)) {
code = code.split(from).join(to);
}
}
chunk.code = code;
}
},
};
}
+70
View File
@@ -0,0 +1,70 @@
// vite-plugin-inline-worker-dev.ts
// vite-plugin-inline-worker-dev.ts
import { Plugin } from "vite";
import fs from "fs/promises";
import { build } from "esbuild";
/**
* Creates a Vite plugin designed for bundling and inlining web worker scripts during development.
* This plugin specifically targets module imports that include a `?inlineWorker` query parameter.
* When such an import is encountered, the plugin bundles the worker script using `esbuild`
* and then generates JavaScript code that inlines this bundled worker as a Blob,
* creating the worker instance via `URL.createObjectURL()`.
* The name "vite:inline-worker-dev" suggests it's primarily intended for development builds.
*
* @returns {Plugin} A Vite plugin object with `name` and `load` properties.
*/
export default function InlineWorkerDevPlugin(): Plugin {
return {
/**
* The unique name of this Vite plugin.
* @type {string}
*/
name: "vite:inline-worker-dev",
/**
* The Vite hook responsible for loading and transforming modules.
* This function intercepts modules imported with `?inlineWorker`.
* For such modules, it bundles the worker script and returns JavaScript code
* that, when executed, will create an instance of this worker from an inlined Blob.
*
* @async
* @param {string} id The path or ID of the module Vite is attempting to load,
* potentially including query parameters (e.g., "/path/to/worker.ts?inlineWorker").
* @returns {Promise<string | null>} A promise that resolves to:
* - `null` if the module ID does not include `?inlineWorker`.
* - A string of JavaScript code if the module is an inline worker.
* This code will define a default export function (e.g., `InlineWorker`)
* that, when called, creates and returns a new `Worker` instance
* from the bundled and inlined worker script.
*/
async load(id) {
if (id.includes("?inlineWorker")) {
const [cleanPath] = id.split("?");
// Note: Original code had `await fs.readFile(cleanPath, "utf-8");` but `code` wasn't used.
// `esbuild` directly takes `cleanPath` as an entry point.
const result = await build({
entryPoints: [cleanPath], // esbuild uses the file path directly
bundle: true,
write: false, // We want the output in memory, not written to disk
platform: "browser", // Target environment for the worker code
format: "iife", // Immediately Invoked Function Expression, suitable for workers
target: "esnext", // Transpile to modern JavaScript
});
const workerCode = result.outputFiles[0].text;
// Construct JavaScript code that will create the worker from a Blob.
// This code is what gets returned to Vite and replaces the original import.
const workerBlobCode = `
const code = ${JSON.stringify(workerCode)};
export default function InlineWorker() {
const blob = new Blob([code], { type: 'application/javascript' });
return new Worker(URL.createObjectURL(blob), { type: 'module' });
}
`;
return workerBlobCode;
}
return null; // Let Vite handle other modules normally
},
};
}
-65
View File
@@ -1,65 +0,0 @@
/*
TEMPORARY FIX FOR CHROME 130+ builds
*/
import path from 'node:path';
import fs from 'fs';
import { PluginOption } from 'vite';
import { ManifestV3Export } from '@crxjs/vite-plugin';
const manifestPath = path.resolve('dist/chrome/manifest.json');
export function updateManifestPlugin(): PluginOption {
return {
name: 'update-manifest-plugin',
enforce: 'post',
closeBundle() {
forceDisableUseDynamicUrl();
},
configureServer(server) {
server.httpServer?.once('listening', () => {
const updated = forceDisableUseDynamicUrl();
if (updated) {
server.ws.send({ type: 'full-reload' });
console.log('** updated **');
}
fs.watchFile(manifestPath, () => {
console.log('** watchFile ** ');
const manifestContents = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
if (manifestContents.web_accessible_resources.some((resource: any) => resource.use_dynamic_url)) {
const updated = forceDisableUseDynamicUrl();
if (updated) {
server.ws.send({ type: 'full-reload' });
console.log('** updated **');
}
}
});
});
},
writeBundle() {
console.log('### writeBundle ##');
forceDisableUseDynamicUrl();
},
};
}
function forceDisableUseDynamicUrl() {
if (!fs.existsSync(manifestPath)) {
return false;
}
const manifestContents = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as Awaited<ManifestV3Export>;
if (typeof manifestContents === 'function' || !manifestContents.web_accessible_resources) return false;
if (manifestContents.web_accessible_resources.every((resource) => !resource.use_dynamic_url)) return false;
manifestContents.web_accessible_resources.forEach((resource) => {
if (resource.use_dynamic_url) resource.use_dynamic_url = false;
});
fs.writeFileSync(manifestPath, JSON.stringify(manifestContents, null, 2));
return true;
}
+172 -48
View File
@@ -1,76 +1,199 @@
const glob = require('glob');
const semver = require('semver');
const { execSync } = require('child_process');
const path = require('path');
/**
* @fileoverview
* This script is a command-line utility for publishing the BetterSEQTA+ extension.
* It automates the process of finding the latest built extension ZIP files for specified
* browsers, zipping the project source code (for Firefox), and then invoking the
* `publish-extension` tool with the appropriate arguments.
*
* To use this script, invoke it with Node.js followed by browser arguments:
* e.g., `node lib/publish.js --b chrome firefox`
* or `node lib/publish.js --b chrome`
* or `node lib/publish.js --b firefox`
*/
const glob = require("glob");
const semver = require("semver");
const { execSync } = require("child_process");
const path = require("path");
/**
* Determines the latest version string from a list of filenames that include version numbers.
* Filenames are expected to follow a pattern like `betterseqtaplus@3.4.5.1-chrome.zip`.
* This function handles potential 4-part versions (e.g., `3.4.5.1`) by trimming them
* to 3 parts (e.g., `3.4.5`) for comparison using the `semver` library. After identifying
* the latest semver-compatible version, it returns the original full version string
* (e.g., "3.4.5.1") that corresponds to this latest version.
*
* @param {string[]} files An array of filenames.
* @returns {string | null} The latest version string (e.g., "3.4.5.1") found among the files,
* or `null` if no valid version numbers are found or no files are provided.
*/
function getLatestVersion(files) {
console.log('Files passed to getLatestVersion:', files);
const versions = files.map(file => {
const match = file.match(/@(\d+\.\d+\.\d+)-/);
console.log('Matching file:', file, 'Version found:', match ? match[1] : 'None');
return match ? match[1] : null;
}).filter(Boolean);
console.log("Files passed to getLatestVersion:", files);
console.log('Extracted versions:', versions);
const latestVersion = semver.maxSatisfying(versions, '*');
console.log('Latest version:', latestVersion);
return latestVersion;
const versions = files
.map((file) => {
const match = file.match(/@([\d\.]+)-/);
console.log(
"Matching file:",
file,
"Version found:",
match ? match[1] : "None",
);
if (!match) return null;
const fullVersion = match[1]; // Original version (e.g., 3.4.5.1)
// Trim to 3 parts for semver comparison, as semver typically handles X.Y.Z
const semverVersion = fullVersion.split(".").slice(0, 3).join(".");
return { fullVersion, semverVersion };
})
.filter(Boolean); // Remove null entries if any file didn't match
console.log(
"Extracted versions:",
versions.map((v) => v.semverVersion),
);
if (versions.length === 0) {
console.log("No versions extracted.");
return null;
}
// Find latest version using the trimmed semver format
const latestSemver = semver.maxSatisfying(
versions.map((v) => v.semverVersion),
"*", // Satisfy any version, effectively finding the max
);
console.log("Latest SemVer-compatible version:", latestSemver);
if (!latestSemver) {
console.log("Could not determine latest semver version.");
return null;
}
// Get the original full version string that matches the identified latest SemVer version
const latestVersionData = versions.find(
(v) => v.semverVersion === latestSemver,
);
const latestFullVersion = latestVersionData ? latestVersionData.fullVersion : null;
console.log("Final selected latest version:", latestFullVersion);
return latestFullVersion;
}
/**
* Finds the path to the latest built ZIP file for a specific browser.
* It constructs a glob pattern based on the browser name (e.g., `dist/betterseqtaplus@*-*chrome.zip`),
* finds all matching files, and then uses `getLatestVersion` to identify the version string
* of the most recent file. Finally, it returns the full path to that specific file.
*
* @param {string} browser A string indicating the target browser (e.g., "chrome", "firefox").
* @returns {string | undefined} The filepath string to the latest ZIP file for the specified browser,
* or `undefined` if no matching file is found or if the latest version
* cannot be determined.
*/
function getLatestFiles(browser) {
const pattern = `dist/betterseqtaplus@*-*${browser}.zip`;
console.log('Glob pattern:', pattern);
const files = glob.sync(pattern);
console.log('Files found for browser', browser, ':', files);
const latestVersion = getLatestVersion(files);
console.log("Glob pattern:", pattern);
const latestFile = files.find(file => file.includes(latestVersion));
console.log('Latest file for browser', browser, ':', latestFile);
const files = glob.sync(pattern);
console.log("Files found for browser", browser, ":", files);
if (files.length === 0) {
console.log("No files found for browser", browser);
return undefined;
}
const latestVersion = getLatestVersion(files);
if (!latestVersion) {
console.log("Could not determine latest version for browser", browser);
return undefined;
}
// Find the exact file by matching the original full version string
const latestFile = files.find((file) => file.includes(`@${latestVersion}-`));
console.log("Latest file for browser", browser, ":", latestFile);
return latestFile;
}
/**
* Creates a ZIP file of the project's source code, excluding specified development-related
* files and directories such as `node_modules`, `dist`, `.git`, etc.
* It uses the `7z` command-line tool to perform the archiving.
* The output filename is fixed as `dist/betterseqtaplus@latest-sources.zip`.
*
* @returns {string} The filename of the created ZIP file (e.g., `dist/betterseqtaplus@latest-sources.zip`).
*/
function zipSources() {
const zipFileName = `dist/betterseqtaplus@latest-sources.zip`;
const excludePatterns = [
'node_modules',
'dist',
'.env*',
'.git',
'.github',
'.vscode',
'LICENSE',
'package.json'
].map(pattern => `-x!${pattern}`).join(' ');
"node_modules",
"dist",
".env*",
".git",
".github",
".vscode",
"LICENSE",
"package.json",
]
.map((pattern) => `-x!${pattern}`) // Format for 7z exclude syntax
.join(" ");
// Command to zip the current directory's contents into zipFileName, applying exclude patterns
const zipCommand = `7z a ${zipFileName} . ${excludePatterns}`;
console.log('Zipping project sources with command:', zipCommand);
execSync(zipCommand, { stdio: 'inherit' });
console.log("Zipping project sources with command:", zipCommand);
execSync(zipCommand, { stdio: "inherit" }); // Execute synchronously and show output
return zipFileName;
}
/**
* Orchestrates the extension publishing process for the specified browsers.
* This function performs the following steps:
* 1. Calls `getLatestFiles` to find the latest built ZIP for Chrome if "chrome" is in `browsers`.
* 2. Calls `getLatestFiles` to find the latest built ZIP for Firefox if "firefox" is in `browsers`.
* 3. Calls `zipSources` to create a source code ZIP if "firefox" is in `browsers` (required for Mozilla Add-ons).
* 4. Validates that all required files were found and that at least one browser was specified. Exits if not.
* 5. Constructs the `publish-extension` command-line string with the appropriate arguments
* based on the found ZIP files for the specified browsers.
* 6. Executes the constructed `publish-extension` command.
*
* @param {string[]} browsers An array of browser strings (e.g., ["chrome", "firefox"]) for which to publish the extension.
*/
function runPublishCommand(browsers) {
const chromeZip = browsers.includes('chrome') ? getLatestFiles('chrome') : null;
const firefoxZip = browsers.includes('firefox') ? getLatestFiles('firefox') : null;
const firefoxSourcesZip = browsers.includes('firefox') ? zipSources() : null;
const chromeZip = browsers.includes("chrome")
? getLatestFiles("chrome")
: null;
const firefoxZip = browsers.includes("firefox")
? getLatestFiles("firefox")
: null;
// Sources are typically only needed for Firefox submissions
const firefoxSourcesZip = browsers.includes("firefox") ? zipSources() : null;
console.log('Chrome zip:', chromeZip);
console.log('Firefox zip:', firefoxZip);
console.log('Firefox sources zip:', firefoxSourcesZip);
console.log("Chrome zip:", chromeZip);
console.log("Firefox zip:", firefoxZip);
console.log("Firefox sources zip:", firefoxSourcesZip);
if (browsers.length === 0) {
console.log('No browsers specified. Exiting.');
process.exit(0);
console.log("No browsers specified. Exiting.");
process.exit(0); // Exit gracefully if no action is needed
}
if ((browsers.includes('chrome') && !chromeZip) || (browsers.includes('firefox') && (!firefoxZip || !firefoxSourcesZip))) {
console.error('Could not find required zip files for specified browsers.');
process.exit(1);
// Check if required files are missing for the specified browsers
if (
(browsers.includes("chrome") && !chromeZip) ||
(browsers.includes("firefox") && (!firefoxZip || !firefoxSourcesZip))
) {
console.error("Could not find required zip files for specified browsers.");
process.exit(1); // Exit with error status
}
let command = 'publish-extension';
let command = "publish-extension";
if (chromeZip) {
command += ` --chrome-zip ${chromeZip}`;
}
@@ -78,13 +201,14 @@ function runPublishCommand(browsers) {
command += ` --firefox-zip ${firefoxZip} --firefox-sources-zip ${firefoxSourcesZip}`;
}
console.log('Running command:', command);
execSync(command, { stdio: 'inherit' });
console.log("Running command:", command);
execSync(command, { stdio: "inherit" }); // Execute and show output
}
// Parse command-line arguments
// Parse command-line arguments to determine which browsers to publish for
const args = process.argv.slice(2);
const browserIndex = args.indexOf('--b');
const browserIndex = args.indexOf("--b"); // Find the --b flag
// If --b is found, take all subsequent arguments as browser names
const browsers = browserIndex !== -1 ? args.slice(browserIndex + 1) : [];
runPublishCommand(browsers);
runPublishCommand(browsers);
+55
View File
@@ -0,0 +1,55 @@
import fs from "fs";
/**
* Creates a Vite plugin designed to improve the reliability of Hot Module Replacement (HMR)
* for global CSS files.
*
* When a JavaScript/TypeScript module that imports a CSS file is updated, Vite's HMR
* might not always reliably update the styles injected by that global CSS. This plugin
* attempts to mitigate this by listening for hot updates. If an updated module
* has direct importers that are CSS files (e.g., a JS file imports a global CSS file),
* this plugin will "touch" those CSS files by updating their access and modification
* timestamps using `fs.utimesSync`. This action can help signal to Vite or the browser
* that the CSS file has changed, potentially triggering a more reliable style reload.
*
* @returns {import('vite').Plugin} A Vite plugin object configured with `name` and `handleHotUpdate` hooks.
*/
export default function touchGlobalCSSPlugin() {
return {
/**
* The unique name of this Vite plugin.
* This name is used by Vite for identification purposes and will appear in logs.
* @type {string}
*/
name: "touch-global-css",
/**
* A Vite hook that is called when a module is hot-updated.
* This function inspects the importers of the updated module. If any of these
* importers are CSS files, their filesystem timestamps are updated ("touched").
*
* @param {object} context The context object provided by Vite's `handleHotUpdate` hook.
* @param {Array<import('vite').ModuleNode>} context.modules An array of `ModuleNode` instances that have been updated.
* This plugin specifically accesses `modules[0]._clientModule.importers`
* to find CSS files that import the updated module.
*/
handleHotUpdate({ modules }) {
// It's assumed `modules[0]` is the primary updated module of interest.
// `_clientModule` and `importers` might be internal or less stable Vite APIs.
const importers = modules[0]?._clientModule?.importers;
if (importers) {
importers.forEach((importer) => {
// Check if the importer is a CSS file
if (importer.file && importer.file.includes(".css")) {
console.log("[touch-global-css] touching", importer.file);
try {
// Update the access and modification times of the CSS file to the current time
fs.utimesSync(importer.file, new Date(), new Date());
} catch (err) {
console.error(`[touch-global-css] Error touching file ${importer.file}:`, err);
}
}
});
}
},
};
}
+205 -69
View File
@@ -1,104 +1,240 @@
import type { ManifestV3Export } from '@crxjs/vite-plugin'
import { type AnyCase, createEnum } from './utils'
import type { ManifestV3Export } from "@crxjs/vite-plugin";
import { type AnyCase, createEnum, ObjectValues } from "./utils";
/**
* Enumerates supported JavaScript frameworks for project generation or configuration.
*/
export const FrameworkEnum = {
React: 'React',
Vanilla: 'Vanilla',
Preact: 'Preact',
Lit: 'Lit',
Svelte: 'Svelte',
Vue: 'Vue',
} as const
React: "React",
Vanilla: "Vanilla",
Preact: "Preact",
Lit: "Lit",
Svelte: "Svelte",
Vue: "Vue",
} as const;
/**
* Enumerates supported web browsers, typically for targeting builds or configurations.
*/
export const BrowserEnum = {
Chrome: 'Chrome',
Brave: 'Brave',
Opera: 'Opera',
Edge: 'Edge',
Firefox: 'Firefox',
Safari: 'Safari',
} as const
Chrome: "Chrome",
Brave: "Brave",
Opera: "Opera",
Edge: "Edge",
Firefox: "Firefox",
Safari: "Safari",
} as const;
/**
* @private
* Enumerates supported programming languages for project setup.
* This enum is not exported, suggesting it's for internal use within this module or related modules.
*/
const LanguageEnum = {
TypeScript: 'TypeScript',
JavaScript: 'JavaScript',
} as const
TypeScript: "TypeScript",
JavaScript: "JavaScript",
} as const;
/**
* Enumerates supported styling options or libraries.
*/
export const StyleEnum = {
Tailwind: 'Tailwind',
} as const
Tailwind: "Tailwind",
} as const;
/**
* Enumerates supported package managers.
*/
export const PackageManagerEnum = {
Bun: 'Bun',
PnPm: 'PnPm',
Npm: 'Npm',
Yarn: 'Yarn',
} as const
Bun: "Bun",
PnPm: "PnPm",
Npm: "Npm",
Yarn: "Yarn",
} as const;
// see: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/firefox-webext-browser/index.d.ts
/**
* Defines the structure for browser-specific settings within a web extension manifest.
* This is particularly used for Firefox (gecko) extensions to specify properties like
* an extension ID, and minimum/maximum supported browser versions.
* The structure is based on common manifest extensions for Firefox.
* See: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/browser_specific_settings
* The link in the original code (// see: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/firefox-webext-browser/index.d.ts)
* also points to type definitions that include this structure.
*
* @property {object} [browser_specific_settings] - Container for browser-specific settings.
* @property {object} [browser_specific_settings.gecko] - Settings specific to Gecko-based browsers (e.g., Firefox).
* @property {string} [browser_specific_settings.gecko.id] - The unique identifier for the extension in Firefox.
* @property {string} [browser_specific_settings.gecko.strict_min_version] - The minimum version of Firefox the extension is compatible with.
* @property {string} [browser_specific_settings.gecko.strict_max_version] - The maximum version of Firefox the extension is compatible with.
*/
export type BrowserSpecificSettings = {
browser_specific_settings?: {
gecko?: {
id: string
strict_min_version?: string
strict_max_version?: string
}
}
}
id: string;
strict_min_version?: string;
strict_max_version?: string;
};
};
};
export type Manifest = ManifestV3Export
export type ManifestIcons = chrome.runtime.ManifestIcons
export type ManifestBackground = chrome.runtime.ManifestV3['background']
/**
* Represents the structure of a Chrome Manifest V3 file.
* This type is an alias for `ManifestV3Export` from the `@crxjs/vite-plugin`,
* which provides a comprehensive definition for Chrome extension manifests.
*/
export type Manifest = ManifestV3Export;
/** Alias for the `icons` property within a Chrome Manifest V3. */
export type ManifestIcons = chrome.runtime.ManifestIcons;
/** Alias for the `background` property within a Chrome Manifest V3. */
export type ManifestBackground = chrome.runtime.ManifestV3["background"];
/** Alias for the `content_scripts` property within a Chrome Manifest V3. */
export type ManifestContentScripts =
chrome.runtime.ManifestV3['content_scripts']
chrome.runtime.ManifestV3["content_scripts"];
/** Alias for the `web_accessible_resources` property within a Chrome Manifest V3. */
export type ManifestWebAccessibleResources =
chrome.runtime.ManifestV3['web_accessible_resources']
export type ManifestCommands = chrome.runtime.ManifestV3['commands']
export type ManifestAction = chrome.runtime.ManifestV3['action']
export type ManifestPermissions = chrome.runtime.ManifestV3['permissions']
export type ManifestOptionsUI = chrome.runtime.ManifestV3['options_ui']
chrome.runtime.ManifestV3["web_accessible_resources"];
/** Alias for the `commands` property within a Chrome Manifest V3. */
export type ManifestCommands = chrome.runtime.ManifestV3["commands"];
/** Alias for the `action` property (or `browser_action`/`page_action`) within a Chrome Manifest V3. */
export type ManifestAction = chrome.runtime.ManifestV3["action"];
/** Alias for the `permissions` property within a Chrome Manifest V3. */
export type ManifestPermissions = chrome.runtime.ManifestV3["permissions"];
/** Alias for the `options_ui` property within a Chrome Manifest V3. */
export type ManifestOptionsUI = chrome.runtime.ManifestV3["options_ui"];
/** Alias for the `chrome_url_overrides` property within a Chrome Manifest V3. */
export type ManifestURLOverrides =
chrome.runtime.ManifestV3['chrome_url_overrides']
chrome.runtime.ManifestV3["chrome_url_overrides"];
export type BrowserName<T extends string> = Capitalize<T> | Lowercase<T>
/**
* Creates a type that accepts a string literal `T` in either its capitalized or lowercase form.
* Useful for defining types that should be case-insensitive for specific known strings.
* @template T - A string literal type.
*/
export type BrowserName<T extends string> = Capitalize<T> | Lowercase<T>;
/**
* Creates a record type where both keys and values are derived from a string literal `T`,
* specifically using `BrowserName<T>` which allows for capitalized or lowercase forms.
* This could be used to define an object where, for example, keys are 'Chrome' or 'chrome'
* and values are also 'Chrome' or 'chrome'.
* @template T - A string literal type, typically representing a browser name.
*/
export type BrowserEnumType<T extends string> = {
[browser in BrowserName<T>]: BrowserName<T>
}
[browser in BrowserName<T>]: BrowserName<T>;
};
export type BuildMode = AnyCase<Browser>
/**
* Represents the target browser for a build, allowing for various casings of browser names
* (e.g., "chrome", "Chrome", "CHROME") through the `AnyCase<Browser>` utility type.
* `Browser` itself is a union of specific browser name strings (e.g., "Chrome" | "Firefox").
*/
export type BuildMode = AnyCase<Browser>;
/**
* Defines an object structure that pairs a web extension `Manifest`
* with its target `browser` (represented as `AnyCase<Browser>`).
* This is commonly used in build processes to manage configurations for different browsers.
*/
export type BuildTarget = {
manifest: Manifest
browser: AnyCase<Browser>
}
manifest: Manifest;
browser: AnyCase<Browser>;
};
/**
* Defines the configuration options for a build process.
* @property {"build" | "serve"} [command] - The type of build command (e.g., 'build' for production, 'serve' for development).
* @property {AnyCase<Browser> | string | undefined} [mode] - The target build mode, typically a browser name (allowing various casings)
* or potentially other custom mode strings.
*/
export type BuildConfig = {
command?: 'build' | 'serve'
mode?: AnyCase<Browser> | string | undefined
}
command?: "build" | "serve";
mode?: AnyCase<Browser> | string | undefined;
};
/**
* Defines the structure for repository information, commonly found in `package.json`.
* @property {string} type - The type of the repository (e.g., "git").
* @property {string} [url] - The URL of the repository.
* @property {Bugs} [bugs] - An object containing information about where to report bugs.
*/
export interface Repository {
type: string
url?: string
bugs?: Bugs
type: string;
url?: string;
bugs?: Bugs;
}
/**
* Defines the structure for bug reporting information, often part of the `Repository` interface.
* @property {string} [url] - The URL of the issue tracker.
* @property {string} [email] - The email address for reporting bugs.
*/
export interface Bugs {
url?: string
email?: string
url?: string;
email?: string;
}
export type Browser = (typeof BrowserEnum)[keyof typeof BrowserEnum]
export const Browser: AnyCase<Browser> = createEnum(BrowserEnum)
/**
* A string literal union type representing supported browser names, derived from the values of `BrowserEnum`.
* e.g., "Chrome" | "Firefox" | ...
*/
export type Browser = ObjectValues<typeof BrowserEnum>;
export type PackageManager =
(typeof PackageManagerEnum)[keyof typeof PackageManagerEnum]
/**
* A constant intended to provide access to browser names, potentially in various casings.
* Its type `AnyCase<Browser>` suggests it can be used where case-insensitivity for browser names is needed.
* The `createEnum(BrowserEnum)` call aims to produce a representation of browser names from `BrowserEnum`.
* Note: `createEnum` from `lib/utils.ts` has a declared return type of `ObjectValues<T>` (a union of values),
* while its implementation uses `Object.values()` which returns an array. This constant will hold the
* runtime array value, but its JSDoc type refers to the more restrictive `AnyCase<Browser>` union type.
*/
export const Browser: AnyCase<Browser> = createEnum(BrowserEnum);
/**
* A string literal union type representing supported package managers, derived from the values of `PackageManagerEnum`.
* e.g., "Bun" | "PnPm" | "Npm" | "Yarn"
*/
export type PackageManager = ObjectValues<typeof PackageManagerEnum>;
/**
* A constant intended to provide access to package manager names, potentially in various casings.
* Its type `AnyCase<PackageManager>` suggests it can be used where case-insensitivity for package manager names is needed.
* Utilizes `createEnum(PackageManagerEnum)`. Refer to notes on `Browser` constant regarding `createEnum` behavior.
*/
export const PackageManager: AnyCase<PackageManager> =
createEnum(PackageManagerEnum)
createEnum(PackageManagerEnum);
export type Framework = (typeof FrameworkEnum)[keyof typeof FrameworkEnum]
export const Framework: AnyCase<Framework> = createEnum(FrameworkEnum)
/**
* A string literal union type representing supported JavaScript frameworks, derived from the values of `FrameworkEnum`.
* e.g., "React" | "Vanilla" | ...
*/
export type Framework = ObjectValues<typeof FrameworkEnum>;
/**
* A constant intended to provide access to framework names, potentially in various casings.
* Its type `AnyCase<Framework>` suggests it can be used where case-insensitivity for framework names is needed.
* Utilizes `createEnum(FrameworkEnum)`. Refer to notes on `Browser` constant regarding `createEnum` behavior.
*/
export const Framework: AnyCase<Framework> = createEnum(FrameworkEnum);
export type Style = (typeof StyleEnum)[keyof typeof StyleEnum]
export const Style: AnyCase<Style> = createEnum(StyleEnum)
/**
* A string literal union type representing supported styling options, derived from the values of `StyleEnum`.
* e.g., "Tailwind"
*/
export type Style = ObjectValues<typeof StyleEnum>;
/**
* A constant intended to provide access to style option names, potentially in various casings.
* Its type `AnyCase<Style>` suggests it can be used where case-insensitivity for style names is needed.
* Utilizes `createEnum(StyleEnum)`. Refer to notes on `Browser` constant regarding `createEnum` behavior.
*/
export const Style: AnyCase<Style> = createEnum(StyleEnum);
export type Language = (typeof LanguageEnum)[keyof typeof LanguageEnum]
export const Language: AnyCase<Language> = createEnum(LanguageEnum)
/**
* A string literal union type representing supported programming languages, derived from the values of `LanguageEnum`.
* e.g., "TypeScript" | "JavaScript"
*/
export type Language = ObjectValues<typeof LanguageEnum>;
/**
* A constant intended to provide access to programming language names, potentially in various casings.
* Its type `AnyCase<Language>` suggests it can be used where case-insensitivity for language names is needed.
* Utilizes `createEnum(LanguageEnum)`. Refer to notes on `Browser` constant regarding `createEnum` behavior.
*/
export const Language: AnyCase<Language> = createEnum(LanguageEnum);
+69 -6
View File
@@ -1,21 +1,84 @@
export type ObjectValues<T> = T[keyof T]
/**
* Extracts a union type of all values from the properties of an object type `T`.
*
* @template T - An object type (typically a Record or an enum-like object).
* @example
* type MyObject = { a: "foo", b: "bar", c: 123 };
* type MyObjectValues = ObjectValues<MyObject>; // "foo" | "bar" | 123
*/
export type ObjectValues<T> = T[keyof T];
/**
* Creates a union of an object's string values, often used to represent the set of possible values for an enum-like object.
* Note: The implementation `Object.values(enumObj) as unknown as ObjectValues<T>` returns an array at runtime,
* but the declared return type `ObjectValues<T>` is a union of the object's property values.
* This type signature suggests it's intended to represent the set of possible string values from `enumObj`.
*
* @template T - An object type where keys are strings and values are strings (e.g., `const MyEnum = { VAL_A: "A", VAL_B: "B" }`).
* @param {T} enumObj - The object from which to extract values.
* @returns {ObjectValues<T>} A union type representing all possible string values of the `enumObj`.
* For example, if `enumObj` is `{ A: "valA", B: "valB" }`, the return type is `"valA" | "valB"`.
* (Runtime behavior of `Object.values()` is to return an array like `["valA", "valB"]`).
*/
export function createEnum<T extends Record<string, string>>(enumObj: T) {
return Object.values(enumObj) as unknown as ObjectValues<T>
return Object.values(enumObj) as unknown as ObjectValues<T>;
}
/**
* Creates a union type that includes various case formats (uppercase, lowercase, capitalized, uncapitalized)
* of a given string literal type `T`.
*
* @template T - A string literal type.
* @example
* type MyString = "example";
* type MyStringAnyCase = AnyCase<MyString>; // "EXAMPLE" | "example" | "Example" | "example" (Uncapitalize<"Example"> is "example")
*/
export type AnyCase<T extends string> =
| Uppercase<T>
| Lowercase<T>
| Capitalize<T>
| Uncapitalize<T>
| Uncapitalize<T>;
/**
* Creates a union type that includes various case formats (uppercase, lowercase, capitalized, uncapitalized)
* of the union of two given string literal types `T` and `K`.
* This is useful for representing a combined set of related string constants where case variations are permitted for each.
*
* @template T - A string literal type.
* @template K - Another string literal type.
* @example
* type Lang1 = "english";
* type Lang2 = "french";
* type CombinedLangsAnyCase = AnyCaseLanguage<Lang1, Lang2>;
* // Result includes: "ENGLISH" | "english" | "English" | "FRENCH" | "french" | "French" etc.
* // for all case variations of "english" and "french".
*/
export type AnyCaseLanguage<T extends string, K extends string> =
| Uppercase<T | K>
| Lowercase<T | K>
| Capitalize<T | K>
| Uncapitalize<T | K>
| Uncapitalize<T | K>;
/**
* Extracts a new object type containing only the keys of `T` whose properties are optional
* (i.e., their type includes `undefined`). The values associated with these keys retain their original types.
*
* @template T - An object type.
* @example
* type MyObject = {
* requiredProp: string;
* optionalProp?: number;
* anotherOptional?: boolean | undefined;
* nullProp: string | null;
* };
* type MyOptionalProps = OptionalKeys<MyObject>;
* // MyOptionalProps would be conceptually equivalent to:
* // {
* // optionalProp?: number;
* // anotherOptional?: boolean | undefined;
* // }
* // The actual resulting type is an object type with only these optional keys.
*/
export type OptionalKeys<T> = {
[K in keyof T as undefined extends T[K] ? K : never]: T[K]
}
[K in keyof T as undefined extends T[K] ? K : never]: T[K];
};
+76 -53
View File
@@ -1,17 +1,22 @@
{
"name": "betterseqtaplus",
"version": "3.4.0",
"version": "3.7.0",
"type": "module",
"description": "Enhance SEQTA Learn's usability and aesthetics! A fork of BetterSEQTA to continue development, while incorporating a plethora of new and improved features!",
"description": "Enhance SEQTA Learn's usability and aesthetics! A fork of BetterSEQTA to continue development and add heaps more features!",
"browserslist": "> 0.5%, last 2 versions, not dead",
"scripts": {
"postinstall": "node scripts/copy-pdfjs-assets.mjs",
"autoaudit": "npm audit && npm audit fix && npm run build",
"dev": "cross-env MODE=chrome vite dev",
"dev:firefox": "cross-env MODE=firefox vite build --watch",
"compile": "npm i && npm run build",
"build": "cross-env MODE=chrome vite build && cross-env MODE=firefox vite build",
"build:chrome": "cross-env MODE=chrome vite build",
"build:firefox": "cross-env MODE=firefox vite build",
"build:safari": "cross-env MODE=safari vite build",
"build:dev": "cross-env MODE=chrome SOURCEMAP=true vite build && cross-env MODE=firefox SOURCEMAP=true vite build",
"convert:safari": "xcrun safari-web-extension-converter dist/safari --project-location . --app-name $npm_package_name-safari",
"dependency-graph": "depcruise src --include-only \"^src\" --output-type dot | dot -T svg > dependency-graph.svg",
"release": "gh release create $npm_package_name@$npm_package_version ./dist/*.zip --generate-notes",
"publish": "bun lib/publish.js --b",
"zip": "bedframe zip"
@@ -26,71 +31,89 @@
"keywords": [],
"author": {
"name": "SethBurkart123",
"email": "betterseqta@betterseqta.com",
"url": "https://github.com/BetterSEQTA/BetterSEQTA-plus"
"email": "betterseqta.plus@gmail.com",
"url": "https://github.com/BetterSEQTA/BetterSEQTA-Plus"
},
"license": "MIT",
"devDependencies": {
"@crxjs/vite-plugin": "2.0.0-beta.25",
"@types/mime-types": "^2.1.4",
"@vitejs/plugin-react-swc": "^3.7.0",
"cross-env": "^7.0.3",
"eslint": "^8.57.0",
"glob": "^11.0.0",
"mime-types": "^2.1.35",
"prettier": "^3.3.3",
"@babel/plugin-transform-runtime": "^7.26.9",
"@babel/runtime": "^7.26.9",
"@bedframe/cli": "^0.1.2",
"@crxjs/vite-plugin": "^2.4.0",
"@types/d3-scale": "^4.0.9",
"@types/d3-shape": "^3.1.8",
"@types/mime-types": "^3.0.1",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"cross-env": "^10.0.0",
"dependency-cruiser": "^17.0.1",
"eslint": "^9.33.0",
"glob": "^11.0.1",
"mime-types": "^3.0.1",
"prettier": "^3.5.3",
"process": "^0.11.10",
"sass": "^1.78.0",
"sass-loader": "^13.3.3",
"semver": "^7.6.3",
"publish-browser-extension": "^4.0.4",
"sass": "^1.85.1",
"sass-loader": "^16.0.5",
"semver": "^7.7.1",
"tailwindcss": "3",
"url": "^0.11.4"
},
"dependencies": {
"@bedframe/cli": "^0.0.85",
"@codemirror/lang-css": "^6.3.0",
"@codemirror/lang-less": "^6.0.2",
"@codemirror/theme-one-dark": "^6.1.2",
"@sveltejs/vite-plugin-svelte": "^4.0.0",
"@tailwindcss/forms": "^0.5.9",
"@bedframe/core": "^0.1.0",
"@codemirror/autocomplete": "^6.18.6",
"@codemirror/commands": "^6.8.0",
"@codemirror/lang-css": "^6.3.1",
"@codemirror/language": "^6.10.8",
"@codemirror/search": "^6.5.10",
"@codemirror/state": "^6.5.2",
"@codemirror/view": "^6.36.4",
"@sveltejs/vite-plugin-svelte": "^7.0.0",
"@tailwindcss/forms": "^0.5.10",
"@tsconfig/svelte": "^5.0.4",
"@types/chrome": "^0.0.270",
"@types/color": "^3.0.6",
"@types/dompurify": "^3.0.5",
"@types/lodash": "^4.17.7",
"@types/node": "^20.16.5",
"@types/react": "17",
"@types/react-dom": "17",
"@types/chrome": "^0.1.4",
"@types/color": "^4.2.0",
"@types/lodash": "^4.17.16",
"@types/node": "^24.3.0",
"@types/qrcode": "^1.5.6",
"@types/sortablejs": "^1.15.8",
"@types/uuid": "^9.0.8",
"@types/webextension-polyfill": "^0.10.7",
"@uiw/codemirror-extensions-color": "^4.23.3",
"@uiw/codemirror-theme-github": "^4.23.3",
"@vitejs/plugin-react": "^4.3.1",
"autoprefixer": "^10.4.20",
"classnames": "^2.5.1",
"@types/uuid": "^10.0.0",
"@types/webextension-polyfill": "^0.12.3",
"@uiw/codemirror-extensions-color": "^4.23.10",
"@uiw/codemirror-theme-github": "^4.23.10",
"autoprefixer": "^10.4.21",
"canvas-confetti": "^1.9.3",
"codemirror": "^6.0.1",
"color": "^4.2.3",
"dompurify": "^3.1.6",
"embla-carousel-autoplay": "^8.3.1",
"embla-carousel-svelte": "^8.3.1",
"fuse.js": "^7.0.0",
"idb": "^8.0.0",
"kolorist": "^1.8.0",
"color": "^5.0.0",
"d3-scale": "^4.0.2",
"d3-shape": "^3.2.0",
"dompurify": "^3.2.4",
"embeddia": "^1.3.0",
"embla-carousel-autoplay": "^8.5.2",
"embla-carousel-svelte": "^8.5.2",
"esbuild": "^0.25.3",
"events": "^3.3.0",
"flexsearch": "^0.8.147",
"fuse.js": "^7.1.0",
"idb": "^8.0.2",
"layerchart": "2.0.0-next.27",
"localforage": "^1.10.0",
"lodash": "^4.17.21",
"mathjs": "^14.4.0",
"million": "^3.1.11",
"motion": "^10.18.0",
"postcss": "^8.4.45",
"publish-browser-extension": "^2.2.1",
"motion": "^12.4.12",
"pdfjs-dist": "^5.4.530",
"postcss": "^8.5.3",
"qrcode": "^1.5.4",
"react": "17",
"react-best-gradient-color-picker": "^3.0.10",
"react-best-gradient-color-picker": "3.0.11",
"react-dom": "17",
"sortablejs": "^1.15.3",
"svelte": "^5.1.9",
"tailwindcss": "^3.4.11",
"typescript": "^5.6.2",
"uuid": "^9.0.1",
"vite": "^5.4.4",
"webextension-polyfill": "^0.10.0"
"rss-parser": "^3.13.0",
"sortablejs": "^1.15.6",
"svelte": "^5.46.4",
"typescript": "^5.8.2",
"uuid": "^11.1.0",
"vite": "^8.0.5",
"webextension-polyfill": "^0.12.0"
}
}
+17
View File
@@ -0,0 +1,17 @@
import { copyFileSync, mkdirSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
const pdfjsRoot = join(root, "node_modules", "pdfjs-dist");
const outDir = join(root, "src", "public", "resources", "pdfjs");
mkdirSync(outDir, { recursive: true });
copyFileSync(
join(pdfjsRoot, "build", "pdf.worker.min.mjs"),
join(outDir, "pdf.worker.min.mjs"),
);
copyFileSync(
join(pdfjsRoot, "legacy", "build", "pdf.min.mjs"),
join(outDir, "pdf.legacy.min.mjs"),
);
+107 -2555
View File
File diff suppressed because it is too large Load Diff
+650 -243
View File
@@ -1,280 +1,687 @@
import browser from 'webextension-polyfill'
import browser from "webextension-polyfill";
import semver from "semver";
import type { SettingsState } from "@/types/storage";
import { fetchNews } from "./background/news";
import {
initCloudSettingsAutoSync,
performCloudSettingsDownloadWithRetry,
performCloudSettingsUploadWithRetry,
requestCloudSettingsDebouncedUpload,
runCloudSettingsPoll,
} from "./background/cloudSettingsAutoSync";
export const openDB = () => {
return new Promise((resolve, reject) => {
const request = indexedDB.open('MyDatabase', 1);
request.onupgradeneeded = (event: any) => {
const db = event.target.result;
db.createObjectStore('backgrounds', { keyPath: 'id' });
};
request.onsuccess = () => {
resolve(request.result);
};
request.onerror = (event: any) => {
reject('Error opening database: ' + event.target.errorCode);
};
});
};
export const writeData = async (type: any, data: any) => {
const db: any = await openDB();
const tx = db.transaction('backgrounds', 'readwrite');
const store = tx.objectStore('backgrounds');
const request = await store.put({ id: 'customBackground', type, data });
return request.result;
};
export const readData = () => {
return new Promise((resolve, reject) => {
openDB()
.then((db: any) => {
const tx = db.transaction('backgrounds', 'readonly');
const store = tx.objectStore('backgrounds');
// Retrieve the custom background
const getRequest = store.get('customBackground');
// Attach success and error event handlers
getRequest.onsuccess = function(event: any) {
resolve(event.target.result);
};
getRequest.onerror = function(event: any) {
console.error('An error occurred:', event);
reject(event);
};
})
.catch(error => {
console.error('An error occurred:', error);
reject(error);
});
});
};
/**
* Session-only dev-mode override of the content API base.
*
* Stored in a module-level variable (not `chrome.storage`) so it is wiped
* automatically when the browser/service-worker process restarts. Content
* scripts re-sync this on every page load via `setDevApiBase` so the value
* survives transient service-worker terminations within the same browser
* session.
*/
const DEFAULT_API_BASE = "https://betterseqta.org";
let DEV_API_BASE: string | null = null;
function apiBase(): string {
return DEV_API_BASE ?? DEFAULT_API_BASE;
}
function reloadSeqtaPages() {
const result = browser.tabs.query({})
function open (tabs: any) {
const result = browser.tabs.query({});
function open(tabs: any) {
for (let tab of tabs) {
if (tab.title.includes('SEQTA Learn')) {
if (
tab.title?.includes("SEQTA Learn") ||
tab.title?.includes("SEQTA Engage")
) {
browser.tabs.reload(tab.id);
}
}
}
result.then(open, console.error)
result.then(open, console.error);
}
// Main message listener
browser.runtime.onMessage.addListener((request: any, _sender: any, sendResponse: any) => {
switch (request.type) {
case 'reloadTabs':
reloadSeqtaPages();
break;
case 'extensionPages':
browser.tabs.query({}).then(function (tabs) {
for (let tab of tabs) {
if (tab.url?.includes('chrome-extension://')) {
browser.tabs.sendMessage(tab.id!, request);
}
}
});
break;
case 'currentTab':
browser.tabs.query({ active: true, currentWindow: true }).then(function (tabs) {
browser.tabs.sendMessage(tabs[0].id!, request).then(function (response) {
sendResponse(response);
});
});
return true;
case 'githubTab':
browser.tabs.create({ url: 'github.com/BetterSEQTA/BetterSEQTA-Plus' });
break;
case 'setDefaultStorage':
SetStorageValue(DefaultValues);
break;
/** Callback for sending a response back to the message sender */
type MessageSender = { (response?: unknown): void };
case 'sendNews':
const date = new Date();
const from =
date.getFullYear() +
'-' +
(date.getMonth() + 1) +
'-' +
(date.getDate() - 5);
const url = `https://newsapi.org/v2/everything?domains=abc.net.au&from=${from}&apiKey=17c0da766ba347c89d094449504e3080`;
GetNews(sendResponse, url);
return true;
default:
console.log('Unknown request type');
/** Accept API + GitHub fallback shapes; always return `{ success, data?: { themes } }`. */
function normalizeFetchThemesResponse(json: unknown): {
success: boolean;
data?: { themes: unknown[] };
error?: string;
} {
if (!json || typeof json !== "object") {
return { success: false, error: "Invalid themes response" };
}
});
function GetNews(sendResponse: any, url: string) {
fetch(url)
.then((result) => result.json())
.then((response) => {
if (response.code == 'rateLimited') {
GetNews(sendResponse, url += '%00');
} else {
sendResponse({ news: response });
}
});
const body = json as Record<string, unknown>;
if (body.success === false) {
return {
success: false,
error: typeof body.error === "string" ? body.error : "Failed to fetch themes",
};
}
const data = body.data;
let themes: unknown[] | null = null;
if (data && typeof data === "object" && !Array.isArray(data)) {
const nested = (data as Record<string, unknown>).themes;
if (Array.isArray(nested)) themes = nested;
} else if (Array.isArray(data)) {
themes = data;
}
if (!themes && Array.isArray(body.themes)) {
themes = body.themes;
}
if (!themes) {
return { success: false, error: "Themes list missing from response" };
}
return { success: true, data: { themes } };
}
const DefaultValues: SettingsState = {
onoff: true,
animatedbk: true,
bksliderinput: "50",
transparencyEffects: false,
lessonalert: true,
notificationcollector: true,
defaultmenuorder: [],
menuitems: {
assessments: { toggle: true },
courses: { toggle: true },
dashboard: { toggle: true },
documents: { toggle: true },
forums: { toggle: true },
goals: { toggle: true },
home: { toggle: true },
messages: { toggle: true },
myed: { toggle: true },
news: { toggle: true },
notices: { toggle: true },
portals: { toggle: true },
reports: { toggle: true },
settings: { toggle: true },
timetable: { toggle: true },
welcome: { toggle: true },
},
menuorder: [],
subjectfilters: {},
selectedTheme: '',
selectedColor: 'linear-gradient(40deg, rgba(201,61,0,1) 0%, RGBA(170, 5, 58, 1) 100%)',
originalSelectedColor: '',
DarkMode: true,
animations: true,
defaultPage: 'home',
shortcuts: [
{
name: 'YouTube',
enabled: false,
},
{
name: 'Outlook',
enabled: true,
},
{
name: 'Office',
enabled: true,
},
{
name: 'Spotify',
enabled: false,
},
{
name: 'Google',
enabled: true,
},
{
name: 'DuckDuckGo',
enabled: false,
},
{
name: 'Cool Math Games',
enabled: false,
},
{
name: 'SACE',
enabled: false,
},
{
name: 'Google Scholar',
enabled: false,
},
{
name: 'Gmail',
enabled: false,
},
{
name: 'Netflix',
enabled: false,
},
{
name: 'Education Perfect',
enabled: false,
},
],
customshortcuts: [],
function handleFetchThemes(request: any, sendResponse: MessageSender): boolean {
const { token } = request;
const apiUrl = `${apiBase()}/api/themes?type=betterseqta&limit=100&nocache=${Date.now()}`;
const githubUrl = `https://raw.githubusercontent.com/BetterSEQTA/BetterSEQTA-Themes/main/store/themes.json?nocache=${Date.now()}`;
const headers: Record<string, string> = {};
if (token) headers["Authorization"] = `Bearer ${token}`;
fetch(apiUrl, { cache: "no-store", headers })
.then(async (r) => {
const json = await r.json();
if (!r.ok) {
throw new Error(
(json && typeof json === "object" && "error" in json && typeof (json as { error?: string }).error === "string"
? (json as { error: string }).error
: null) ?? `Themes API HTTP ${r.status}`,
);
}
return normalizeFetchThemesResponse(json);
})
.then(sendResponse)
.catch((err) => {
console.warn("[Background] fetchThemes API failed, trying GitHub fallback:", err?.message);
fetch(githubUrl, { cache: "no-store" })
.then(async (r) => {
if (!r.ok) throw new Error(`GitHub fallback HTTP ${r.status}`);
const data = await r.json();
const themes = Array.isArray(data) ? data : (data?.themes ?? []);
return normalizeFetchThemesResponse({ success: true, data: { themes } });
})
.then(sendResponse)
.catch((fallbackErr) => {
console.error("[Background] fetchThemes GitHub fallback error:", fallbackErr);
sendResponse({ success: false, error: fallbackErr?.message });
});
});
return true;
}
function handleFetchThemeDetails(request: any, sendResponse: MessageSender): boolean {
const { themeId, token } = request;
if (!themeId || typeof themeId !== "string") {
sendResponse({ success: false, error: "Missing themeId" });
return false;
}
const headers: Record<string, string> = {};
if (token) headers["Authorization"] = `Bearer ${token}`;
fetch(`${apiBase()}/api/themes/${themeId}`, { cache: "no-store", headers })
.then((r) => r.json())
.then(sendResponse)
.catch((err) => {
console.error("[Background] fetchThemeDetails error:", err);
sendResponse({ success: false, error: err?.message });
});
return true;
}
function handleFetchFromUrl(request: any, sendResponse: MessageSender): boolean {
const { url } = request;
if (!url || typeof url !== "string") {
sendResponse({ error: "Missing url" });
return false;
}
fetch(url, { cache: "no-store" })
.then((r) => r.json())
.then((data) => sendResponse({ data }))
.catch((err) => {
console.error("[Background] fetchFromUrl error:", err);
sendResponse({ error: err?.message });
});
return true;
}
async function parseJsonResponse(r: Response): Promise<any> {
const text = await r.text();
try {
return text ? JSON.parse(text) : {};
} catch {
return {};
}
}
function handleCloudReserveClient(request: any, sendResponse: MessageSender): boolean {
const redirect_uri = request.redirect_uri ?? "https://accounts.betterseqta.org/auth/bsplus/callback";
fetch("https://accounts.betterseqta.org/api/bsplus/client/reserve", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ redirect_uri }),
})
.then(async (r) => {
const data = await parseJsonResponse(r);
if (!r.ok) sendResponse({ error: data?.error ?? `Reserve failed (${r.status})` });
else sendResponse(data);
})
.catch((err) => {
console.error("[Background] cloudReserveClient error:", err);
sendResponse({ error: err?.message ?? "Network error" });
});
return true;
}
function handleCloudLogin(request: any, sendResponse: MessageSender): boolean {
const { client_id, redirect_uri, login, password } = request;
if (!client_id || !redirect_uri || !login || !password) {
sendResponse({ error: "Missing client_id, redirect_uri, login, or password" });
return false;
}
fetch("https://accounts.betterseqta.org/api/bsplus/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ client_id, redirect_uri, login, password }),
})
.then(async (r) => {
const data = await parseJsonResponse(r);
if (!r.ok) sendResponse({ error: data?.error ?? "Login failed" });
else sendResponse(data);
})
.catch((err) => {
console.error("[Background] cloudLogin error:", err);
sendResponse({ error: err?.message ?? "Network error" });
});
return true;
}
function handleCloudStartLogin(request: any, sendResponse: MessageSender): boolean {
const { client_id, redirect_uri } = request;
if (!client_id || !redirect_uri) {
sendResponse({ error: "Missing client_id or redirect_uri" });
return true;
}
const authorizeUrl = `https://accounts.betterseqta.org/login?redirect=${encodeURIComponent(`/oauth/authorize?client_id=${client_id}&redirect_uri=${encodeURIComponent(redirect_uri)}`)}`;
browser.tabs.create({ url: authorizeUrl }).then(() => {
sendResponse({ success: true });
}).catch((err) => {
console.error("[Background] cloudStartLogin error:", err);
sendResponse({ error: err?.message ?? "Failed to open login page" });
});
return true;
}
const CALLBACK_URL_PREFIX = "https://accounts.betterseqta.org/auth/bsplus/callback";
function initCloudLoginCallbackListener() {
browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
if (changeInfo.url && changeInfo.url.startsWith(CALLBACK_URL_PREFIX)) {
try {
const url = new URL(changeInfo.url);
const token = url.searchParams.get("token");
const refreshToken = url.searchParams.get("refresh_token");
const userId = url.searchParams.get("user_id");
if (token && refreshToken) {
// Store tokens
void (async () => {
try {
await browser.storage.local.set({
bsplus_token: token,
bsplus_refresh_token: refreshToken,
});
// Fetch full user info
const userRes = await fetch("https://accounts.betterseqta.org/api/auth/me", {
headers: { Authorization: `Bearer ${token}` },
});
if (userRes.ok) {
const user = await userRes.json();
await browser.storage.local.set({ bsplus_user: user });
} else if (userId) {
await browser.storage.local.set({ bsplus_user: { id: userId } });
}
// Trigger cloud settings download
void performCloudSettingsDownloadWithRetry(token).catch((err) => {
console.warn("[Background] Cloud settings download after login:", err);
});
} catch (err) {
console.error("[Background] Failed to process login callback:", err);
}
})();
// Close the callback tab
void browser.tabs.remove(tabId);
}
} catch (err) {
console.error("[Background] Error parsing callback URL:", err);
}
}
});
}
initCloudLoginCallbackListener();
function handleCloudRefresh(request: any, sendResponse: MessageSender): boolean {
const { refresh_token, client_id } = request;
if (!refresh_token || !client_id) {
sendResponse({ error: "Missing refresh_token or client_id" });
return false;
}
fetch("https://accounts.betterseqta.org/api/bsplus/refresh", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ refresh_token, client_id }),
})
.then(async (r) => {
const data = await parseJsonResponse(r);
if (!r.ok) sendResponse({ error: data?.error ?? "Refresh failed" });
else sendResponse(data);
})
.catch((err) => {
console.error("[Background] cloudRefresh error:", err);
sendResponse({ error: err?.message ?? "Network error" });
});
return true;
}
function handleCloudSettingsUpload(request: any, sendResponse: MessageSender): boolean {
void (async () => {
try {
const token = request.token as string | undefined;
if (!token) {
sendResponse({ success: false, error: "Not authenticated" });
return;
}
const res = await performCloudSettingsUploadWithRetry(token);
sendResponse({
success: res.success,
error: res.error,
updated_at: res.updated_at,
});
} catch (err) {
console.error("[Background] cloudSettingsUpload error:", err);
sendResponse({
success: false,
error: err instanceof Error ? err.message : "Upload failed",
});
}
})();
return true;
}
function handleCloudSettingsDownload(request: any, sendResponse: MessageSender): boolean {
void (async () => {
try {
const token = request.token as string | undefined;
if (!token) {
sendResponse({ success: false, error: "Not authenticated" });
return;
}
const res = await performCloudSettingsDownloadWithRetry(token);
sendResponse({
success: res.success,
notFound: res.notFound,
error: res.error,
updated_at: res.updated_at,
});
} catch (err) {
console.error("[Background] cloudSettingsDownload error:", err);
sendResponse({
success: false,
error: err instanceof Error ? err.message : "Download failed",
});
}
})();
return true;
}
function handleCloudFavorite(request: any, sendResponse: MessageSender): boolean {
const { themeId, token, action } = request;
if (!themeId || !token) {
sendResponse({ success: false, error: "Theme ID and token required" });
return false;
}
const isFavorite = action === "favorite";
fetch(`${apiBase()}/api/themes/${themeId}/favorite`, {
method: isFavorite ? "POST" : "DELETE",
headers: { Authorization: `Bearer ${token}` },
})
.then((r) => r.json())
.then(sendResponse)
.catch((err) => {
console.error("[Background] cloudFavorite error:", err);
sendResponse({ success: false, error: err?.message });
});
return true;
}
/** Handler for a message type; receives request, sendResponse, and optional sender (for tab routing) */
type MessageHandler = {
(request: any, sendResponse: MessageSender, sender?: browser.Runtime.MessageSender): boolean | void;
};
function isSeqtaOrigin(origin: string): boolean {
try {
const u = new URL(origin);
return u.hostname.includes("seqta") || u.hostname.endsWith(".edu.au");
} catch {
return false;
}
}
function handleSetDevApiBase(request: any): boolean {
const url = typeof request?.url === "string" ? request.url.trim() : null;
if (url && /^https?:\/\//.test(url)) {
DEV_API_BASE = url.replace(/\/$/, "");
} else {
DEV_API_BASE = null;
}
return false;
}
const MESSAGE_HANDLERS: Record<string, MessageHandler> = {
reloadTabs: () => reloadSeqtaPages(),
setDevApiBase: handleSetDevApiBase,
extensionPages: (req) => {
browser.tabs.query({}).then((tabs) => {
for (const tab of tabs) {
if (tab.url?.includes("chrome-extension://")) browser.tabs.sendMessage(tab.id!, req);
}
});
},
currentTab: (req, sendResponse) => {
browser.tabs.query({ active: true, currentWindow: true }).then((tabs) => {
browser.tabs.sendMessage(tabs[0].id!, req).then(sendResponse);
});
return true;
},
githubTab: () => {
void browser.tabs.create({ url: "github.com/BetterSEQTA/BetterSEQTA-Plus" });
},
setDefaultStorage: () => SetStorageValue(getDefaultValues()),
sendNews: (req, sendResponse) => {
fetchNews(req.source ?? "australia", sendResponse);
return true;
},
fetchThemes: handleFetchThemes,
fetchThemeDetails: handleFetchThemeDetails,
fetchFromUrl: handleFetchFromUrl,
cloudReserveClient: handleCloudReserveClient,
cloudLogin: handleCloudLogin,
cloudStartLogin: handleCloudStartLogin,
cloudRefresh: handleCloudRefresh,
cloudFavorite: handleCloudFavorite,
cloudSettingsUpload: handleCloudSettingsUpload,
cloudSettingsDownload: handleCloudSettingsDownload,
cloudSettingsPoll: () => {
void runCloudSettingsPoll();
return false;
},
cloudSettingsRequestDebouncedUpload: () => {
requestCloudSettingsDebouncedUpload();
return false;
},
getSeqtaSession: (req: { baseUrl?: string }, sendResponse: MessageSender, sender?: browser.Runtime.MessageSender) => {
(async () => {
try {
let tabId = sender?.tab?.id;
let originForCheck: string | undefined = req.baseUrl;
if (tabId == null) {
const tabs = await browser.tabs.query({ active: true, lastFocusedWindow: true });
const tab = tabs[0];
if (!tab?.id || !tab.url) {
sendResponse({ appLink: null });
return;
}
tabId = tab.id;
if (!originForCheck) originForCheck = new URL(tab.url).origin;
} else if (!originForCheck && sender?.tab?.url) {
originForCheck = new URL(sender.tab.url).origin;
}
if (!originForCheck || !isSeqtaOrigin(originForCheck)) {
sendResponse({ appLink: null });
return;
}
const reply = (await browser.tabs.sendMessage(tabId, { type: "fetchSeqtaAppLink" })) as
| { appLink?: string | null }
| undefined;
const appLink = typeof reply?.appLink === "string" && reply.appLink.length > 0 ? reply.appLink : null;
sendResponse({ appLink });
} catch (err) {
console.error("[Background] getSeqtaSession error:", err);
sendResponse({ appLink: null });
}
})();
return true;
},
};
browser.runtime.onMessage.addListener(
// @ts-ignore - OnMessageListener expects literal true for async, we return boolean
(request: any, sender: browser.Runtime.MessageSender, sendResponse: MessageSender) => {
const handler = MESSAGE_HANDLERS[request.type];
if (handler) {
const result = handler(request, sendResponse, sender);
return result === true;
}
console.log("Unknown request type");
return false;
},
);
function detectLowEndDevice(): boolean {
// Check for low-end hardware indicators
const lowCoreCount = navigator.hardwareConcurrency && navigator.hardwareConcurrency < 4;
const lowMemory = (navigator as any).deviceMemory && (navigator as any).deviceMemory <= 2;
return lowCoreCount || lowMemory;
}
function getDefaultValues(): SettingsState {
const isLowEndDevice = detectLowEndDevice();
return {
onoff: true,
animatedbk: true,
bksliderinput: "50",
transparencyEffects: false,
lessonalert: true,
defaultmenuorder: [],
menuitems: {
assessments: { toggle: true },
courses: { toggle: true },
dashboard: { toggle: true },
documents: { toggle: true },
forums: { toggle: true },
goals: { toggle: true },
home: { toggle: true },
messages: { toggle: true },
myed: { toggle: true },
news: { toggle: true },
notices: { toggle: true },
portals: { toggle: true },
reports: { toggle: true },
settings: { toggle: true },
timetable: { toggle: true },
welcome: { toggle: true },
},
menuorder: [],
subjectfilters: {},
selectedTheme: "",
selectedColor:
"linear-gradient(40deg, rgba(201,61,0,1) 0%, RGBA(170, 5, 58, 1) 100%)",
originalSelectedColor: "",
DarkMode: true,
animations: !isLowEndDevice,
assessmentsAverage: false,
defaultPage: "home",
shortcuts: [
{
name: "Outlook",
enabled: true,
},
{
name: "Office",
enabled: true,
},
{
name: "Google",
enabled: true,
},
],
customshortcuts: [],
lettergrade: false,
newsSource: "australia",
iconOnlySidebar: false,
adaptiveThemeColour: false,
adaptiveThemeGradient: false,
adaptiveThemeColourTransition: true,
themeOfTheMonthDisabled: false,
autoCloudSettingsSync: true,
};
}
function SetStorageValue(object: any) {
for (var i in object) {
browser.storage.local.set({ [i]: object[i] });
}
}
async function UpdateCurrentValues() {
/** One-time migration for 3.6.5: opt upgraders into Global Search + indexing + transparency defaults. */
const GLOBAL_SEARCH_PLUGIN_SETTINGS_KEY = "plugin.global-search.settings";
const GLOBAL_SEARCH_MIGRATION_VERSION = "3.6.5";
async function migrateGlobalSearchDefaultsFor365Upgrade(
previousVersion: string,
): Promise<void> {
try {
const items = await browser.storage.local.get();
const CurrentValues = items;
const NewValue = Object.assign({}, DefaultValues, CurrentValues);
function CheckInnerElement(element: any) {
for (let i in element) {
if (typeof element[i] === 'object') {
// @ts-expect-error
if (!Array.isArray(DefaultValues[i])) {
// @ts-expect-error
NewValue[i] = Object.assign({}, DefaultValues[i], CurrentValues[i]);
} else {
// @ts-expect-error
const length = DefaultValues[i].length;
// @ts-expect-error
NewValue[i] = Object.assign({}, DefaultValues[i], CurrentValues[i]);
let NewArray = [];
for (let j = 0; j < length; j++) {
NewArray.push(NewValue[i][j]);
}
NewValue[i] = NewArray;
}
}
}
const currRaw = browser.runtime.getManifest().version;
const prev = semver.coerce(previousVersion);
const curr = semver.coerce(currRaw);
if (
prev == null ||
curr == null ||
semver.lt(curr, GLOBAL_SEARCH_MIGRATION_VERSION) ||
!semver.lt(prev, GLOBAL_SEARCH_MIGRATION_VERSION)
) {
return;
}
CheckInnerElement(DefaultValues);
const got = await browser.storage.local.get(GLOBAL_SEARCH_PLUGIN_SETTINGS_KEY);
const existing = (got[GLOBAL_SEARCH_PLUGIN_SETTINGS_KEY] ?? {}) as Record<
string,
unknown
>;
if (items['customshortcuts']) {
NewValue['customshortcuts'] = items['customshortcuts'];
await browser.storage.local.set({
[GLOBAL_SEARCH_PLUGIN_SETTINGS_KEY]: {
...existing,
enabled: true,
transparencyEffects: true,
runIndexingOnLoad: true,
passiveIndexing: true,
},
});
console.info(
`[BetterSEQTA+] Migration ${GLOBAL_SEARCH_MIGRATION_VERSION}: Global Search and related settings enabled (from ${previousVersion}).`,
);
} catch (e) {
console.warn("[BetterSEQTA+] Global Search 3.6.5 settings migration failed:", e);
}
}
/** One-time reset for 3.6.6: re-enable Theme of the Month for existing users. */
const THEME_OF_THE_MONTH_RESET_VERSION = "3.6.6";
async function resetThemeOfTheMonthDisabledFor366Upgrade(
previousVersion: string,
): Promise<void> {
try {
const currRaw = browser.runtime.getManifest().version;
const prev = semver.coerce(previousVersion);
const curr = semver.coerce(currRaw);
if (
prev == null ||
curr == null ||
semver.lt(curr, THEME_OF_THE_MONTH_RESET_VERSION) ||
!semver.lt(prev, THEME_OF_THE_MONTH_RESET_VERSION)
) {
return;
}
SetStorageValue(NewValue);
console.log('[BetterSEQTA+] Values updated successfully');
} catch (error) {
console.error('[BetterSEQTA+] Error updating values:', error);
await browser.storage.local.set({
themeOfTheMonthDisabled: false,
themeOfTheMonthLastSeenId: undefined,
});
console.info(
`[BetterSEQTA+] Migration ${THEME_OF_THE_MONTH_RESET_VERSION}: Theme of the Month re-enabled (from ${previousVersion}).`,
);
} catch (e) {
console.warn(
"[BetterSEQTA+] Theme of the Month 3.6.6 reset migration failed:",
e,
);
}
}
/** 3.7.0: Close no longer marks entries seen — clear legacy dismissal keys. */
const THEME_OF_THE_MONTH_RELOAD_VERSION = "3.7.0";
async function resetThemeOfTheMonthDismissalFor370Upgrade(
previousVersion: string,
): Promise<void> {
try {
const currRaw = browser.runtime.getManifest().version;
const prev = semver.coerce(previousVersion);
const curr = semver.coerce(currRaw);
if (
prev == null ||
curr == null ||
semver.lt(curr, THEME_OF_THE_MONTH_RELOAD_VERSION) ||
!semver.lt(prev, THEME_OF_THE_MONTH_RELOAD_VERSION)
) {
return;
}
await browser.storage.local.set({
themeOfTheMonthLastSeenId: undefined,
themeOfTheMonthDismissedMonth: undefined,
});
console.info(
`[BetterSEQTA+] Migration ${THEME_OF_THE_MONTH_RELOAD_VERSION}: Theme of the Month shows again until dismissed for the month (from ${previousVersion}).`,
);
} catch (e) {
console.warn(
"[BetterSEQTA+] Theme of the Month 3.7.0 dismissal migration failed:",
e,
);
}
}
browser.runtime.onInstalled.addListener(function (event) {
browser.storage.local.remove(['justupdated']);
browser.storage.local.remove(['data']);
browser.storage.local.remove(["justupdated"]);
browser.storage.local.remove(["data"]);
UpdateCurrentValues();
if ( event.reason == 'install', event.reason == 'update' ) {
if (event.reason == "install" || event.reason == "update") {
browser.storage.local.set({ justupdated: true });
}
if (event.reason === "update" && event.previousVersion) {
void migrateGlobalSearchDefaultsFor365Upgrade(event.previousVersion);
void resetThemeOfTheMonthDisabledFor366Upgrade(event.previousVersion);
void resetThemeOfTheMonthDismissalFor370Upgrade(event.previousVersion);
}
});
initCloudSettingsAutoSync({ reloadSeqtaPages });
+420
View File
@@ -0,0 +1,420 @@
import browser from "webextension-polyfill";
import {
applyDownloadedEnvelope,
buildUploadPayload,
BSPLUS_CLOUD_KNOWN_REMOTE_UPDATED_AT_KEY,
BSPLUS_PENDING_THEME_ENSURE_AFTER_CLOUD_KEY,
CLOUD_SETTINGS_SYNC_SCHEMA_VERSION,
isKeyIncludedInCloudUploadPayload,
resolveThemeIdForPostSyncDownload,
setKnownRemoteUpdatedAt,
} from "@/seqta/utils/cloudSettingsSync";
const ACCOUNTS_BASE = "https://accounts.betterseqta.org";
export const CLOUD_SUMMARY_URL = `${ACCOUNTS_BASE}/api/user/cloud-summary`;
const CLOUD_SETTINGS_SYNC_URL = `${ACCOUNTS_BASE}/api/bsplus/settings/sync`;
const REFRESH_URL = `${ACCOUNTS_BASE}/api/bsplus/refresh`;
const UPLOAD_DEBOUNCE_MS = 2000;
const POLL_THROTTLE_MS = 24 * 60 * 60 * 1000;
const POLL_THROTTLE_KEY = "bsplus_lastCloudPoll";
type CloudSummaryResponse = {
desqta?: unknown;
bsplus?: { updated_at: string; schemaVersion: number } | null;
};
let reloadSeqtaPagesFn: (() => void) | null = null;
let suppressAutoUploadDuringRestore = false;
let debounceTimer: ReturnType<typeof setTimeout> | null = null;
let pollInFlight: Promise<void> | null = null;
function isAutoCloudSyncEnabled(all: Record<string, unknown>): boolean {
return all.autoCloudSettingsSync !== false;
}
async function parseJsonResponse(r: Response): Promise<any> {
const text = await r.text();
try {
return text ? JSON.parse(text) : {};
} catch {
return {};
}
}
async function getAccessToken(): Promise<string | null> {
const { bsplus_token } = await browser.storage.local.get("bsplus_token");
return typeof bsplus_token === "string" && bsplus_token.length > 0 ? bsplus_token : null;
}
async function tryRefreshTokens(): Promise<boolean> {
const result = await browser.storage.local.get([
"bsplus_refresh_token",
"bsplus_client_id",
"bsplus_user",
]);
const refresh_token = result.bsplus_refresh_token as string | undefined;
const client_id = result.bsplus_client_id as string | undefined;
if (!refresh_token || !client_id) return false;
try {
const r = await fetch(REFRESH_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ refresh_token, client_id }),
});
const data = await parseJsonResponse(r);
if (!r.ok || !data.access_token || !data.refresh_token) return false;
await browser.storage.local.set({
bsplus_token: data.access_token,
bsplus_refresh_token: data.refresh_token,
bsplus_user: data.user ?? result.bsplus_user,
});
return true;
} catch {
return false;
}
}
function isServerTimestampNewer(serverIso: string, localIso: string | undefined): boolean {
const a = Date.parse(serverIso);
if (Number.isNaN(a)) return false;
if (localIso === undefined || localIso === "") return true;
const b = Date.parse(localIso);
if (Number.isNaN(b)) return true;
return a > b;
}
async function fetchCloudSummaryOnce(
token: string,
): Promise<
| { ok: true; data: CloudSummaryResponse }
| { ok: false; unauthorized: boolean; error?: string }
> {
try {
const r = await fetch(CLOUD_SUMMARY_URL, {
headers: { Authorization: `Bearer ${token}` },
cache: "no-store",
});
const data = (await parseJsonResponse(r)) as CloudSummaryResponse;
if (r.status === 401) return { ok: false, unauthorized: true };
if (!r.ok) {
return {
ok: false,
unauthorized: false,
error: (data as { error?: string })?.error ?? `Summary failed (${r.status})`,
};
}
return { ok: true, data };
} catch (e) {
return {
ok: false,
unauthorized: false,
error: e instanceof Error ? e.message : "Network error",
};
}
}
async function fetchCloudSummaryWithAuthRetry(
token: string,
): Promise<CloudSummaryResponse | null> {
let t = token;
for (let attempt = 0; attempt < 2; attempt++) {
const res = await fetchCloudSummaryOnce(t);
if (res.ok) return res.data;
if (res.unauthorized && attempt === 0) {
const refreshed = await tryRefreshTokens();
if (!refreshed) break;
const next = await getAccessToken();
if (!next) break;
t = next;
continue;
}
if (res.error) console.warn("[BS+ cloud sync] cloud-summary:", res.error);
break;
}
return null;
}
type PutResult =
| { ok: true; updated_at?: string }
| { ok: false; unauthorized: boolean; error?: string };
async function putSettingsOnce(token: string): Promise<PutResult> {
try {
const all = await browser.storage.local.get();
const payload = buildUploadPayload(all as Record<string, unknown>);
const r = await fetch(CLOUD_SETTINGS_SYNC_URL, {
method: "PUT",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});
const data = await parseJsonResponse(r);
if (r.status === 401) return { ok: false, unauthorized: true };
if (!r.ok) {
return {
ok: false,
unauthorized: false,
error: data?.error ?? `Upload failed (${r.status})`,
};
}
const updated_at = data?.updated_at as string | undefined;
await setKnownRemoteUpdatedAt(updated_at);
return { ok: true, updated_at };
} catch (e) {
return {
ok: false,
unauthorized: false,
error: e instanceof Error ? e.message : "Upload failed",
};
}
}
export async function performCloudSettingsUploadWithRetry(
token: string,
): Promise<{ success: boolean; error?: string; updated_at?: string }> {
let t = token;
for (let attempt = 0; attempt < 2; attempt++) {
const res = await putSettingsOnce(t);
if (res.ok) return { success: true, updated_at: res.updated_at };
if (res.unauthorized && attempt === 0) {
const refreshed = await tryRefreshTokens();
if (!refreshed) return { success: false, error: "Not authenticated" };
const next = await getAccessToken();
if (!next) return { success: false, error: "Not authenticated" };
t = next;
continue;
}
return { success: false, error: res.error ?? "Upload failed" };
}
return { success: false, error: "Upload failed" };
}
type GetResult =
| { ok: true; updated_at?: string }
| { ok: false; notFound?: boolean; unauthorized: boolean; error?: string };
async function getSettingsAndApplyOnce(token: string): Promise<GetResult> {
try {
const r = await fetch(CLOUD_SETTINGS_SYNC_URL, {
method: "GET",
headers: { Authorization: `Bearer ${token}` },
cache: "no-store",
});
const data = await parseJsonResponse(r);
if (r.status === 401) return { ok: false, unauthorized: true };
if (r.status === 404) {
return {
ok: false,
notFound: true,
unauthorized: false,
error: "No settings backup found in the cloud",
};
}
if (!r.ok) {
return {
ok: false,
unauthorized: false,
error: data?.error ?? `Download failed (${r.status})`,
};
}
const themeIdToEnsure = resolveThemeIdForPostSyncDownload(data);
await applyDownloadedEnvelope(data);
if (themeIdToEnsure) {
await browser.storage.local.set({
[BSPLUS_PENDING_THEME_ENSURE_AFTER_CLOUD_KEY]: themeIdToEnsure,
});
} else {
await browser.storage.local.remove(BSPLUS_PENDING_THEME_ENSURE_AFTER_CLOUD_KEY);
}
reloadSeqtaPagesFn?.();
const updated_at = data?.updated_at as string | undefined;
await setKnownRemoteUpdatedAt(updated_at);
return { ok: true, updated_at };
} catch (e) {
return {
ok: false,
unauthorized: false,
error: e instanceof Error ? e.message : "Download failed",
};
}
}
export async function performCloudSettingsDownloadWithRetry(
token: string,
): Promise<{ success: boolean; notFound?: boolean; error?: string; updated_at?: string }> {
suppressAutoUploadDuringRestore = true;
try {
let t = token;
for (let attempt = 0; attempt < 2; attempt++) {
const res = await getSettingsAndApplyOnce(t);
if (res.ok) return { success: true, updated_at: res.updated_at };
if (res.unauthorized && attempt === 0) {
const refreshed = await tryRefreshTokens();
if (!refreshed) return { success: false, error: "Not authenticated" };
const next = await getAccessToken();
if (!next) return { success: false, error: "Not authenticated" };
t = next;
continue;
}
return {
success: false,
notFound: res.notFound,
error: res.error ?? "Download failed",
};
}
return { success: false, error: "Download failed" };
} finally {
suppressAutoUploadDuringRestore = false;
}
}
async function maybeUploadBaseline(token: string): Promise<void> {
const res = await performCloudSettingsUploadWithRetry(token);
if (!res.success) {
console.warn("[BS+ cloud sync] Baseline upload failed:", res.error);
}
}
async function downloadIfNeeded(token: string): Promise<void> {
const res = await performCloudSettingsDownloadWithRetry(token);
if (!res.success && !res.notFound) {
console.warn("[BS+ cloud sync] Auto-download failed:", res.error);
}
}
async function runCloudSettingsPollInner(): Promise<void> {
const all = (await browser.storage.local.get()) as Record<string, unknown>;
if (!isAutoCloudSyncEnabled(all)) return;
let token = await getAccessToken();
if (!token) return;
const summary = await fetchCloudSummaryWithAuthRetry(token);
if (!summary) return;
const bsplus = summary.bsplus;
const watermark = all[BSPLUS_CLOUD_KNOWN_REMOTE_UPDATED_AT_KEY] as string | undefined;
if (
bsplus &&
typeof bsplus.schemaVersion === "number" &&
bsplus.schemaVersion > CLOUD_SETTINGS_SYNC_SCHEMA_VERSION
) {
console.warn(
"[BS+ cloud sync] Server schemaVersion newer than client; skip auto-download",
);
return;
}
token = (await getAccessToken()) ?? token;
if (!watermark) {
if (!bsplus?.updated_at) {
await maybeUploadBaseline(token);
return;
}
await downloadIfNeeded(token);
return;
}
if (!bsplus?.updated_at) return;
if (isServerTimestampNewer(bsplus.updated_at, watermark)) {
await downloadIfNeeded(token);
}
}
export function runCloudSettingsPoll(): Promise<void> {
if (pollInFlight) return pollInFlight;
pollInFlight = (async () => {
try {
const { [POLL_THROTTLE_KEY]: last } = await browser.storage.local.get(POLL_THROTTLE_KEY);
if (Date.now() - (Number(last) || 0) < POLL_THROTTLE_MS) return;
await browser.storage.local.set({ [POLL_THROTTLE_KEY]: Date.now() });
await runCloudSettingsPollInner();
} catch (e) {
console.error("[BS+ cloud sync] Poll error:", e);
} finally {
pollInFlight = null;
}
})();
return pollInFlight;
}
function clearUploadDebounce(): void {
if (debounceTimer) {
clearTimeout(debounceTimer);
debounceTimer = null;
}
}
function scheduleDebouncedUpload(): void {
if (suppressAutoUploadDuringRestore) return;
clearUploadDebounce();
debounceTimer = setTimeout(() => {
debounceTimer = null;
void runDebouncedUploadJob();
}, UPLOAD_DEBOUNCE_MS);
}
/** Call after store theme install (and similar) so cloud upload runs even if storage events are flaky. */
export function requestCloudSettingsDebouncedUpload(): void {
void (async () => {
const all = (await browser.storage.local.get()) as Record<string, unknown>;
if (!isAutoCloudSyncEnabled(all)) return;
if (suppressAutoUploadDuringRestore) return;
if (!(await getAccessToken())) return;
scheduleDebouncedUpload();
})();
}
async function runDebouncedUploadJob(): Promise<void> {
const all = (await browser.storage.local.get()) as Record<string, unknown>;
if (!isAutoCloudSyncEnabled(all)) return;
const token = await getAccessToken();
if (!token) return;
const res = await performCloudSettingsUploadWithRetry(token);
if (!res.success) {
console.warn("[BS+ cloud sync] Auto-upload failed:", res.error);
}
}
async function syncAutoUploadWithStorage(): Promise<void> {
const all = (await browser.storage.local.get()) as Record<string, unknown>;
if (!isAutoCloudSyncEnabled(all)) {
clearUploadDebounce();
}
}
function onStorageChanged(
changes: Record<string, browser.storage.StorageChange>,
area: string,
): void {
if (area !== "local") return;
if (Object.prototype.hasOwnProperty.call(changes, "autoCloudSettingsSync")) {
void syncAutoUploadWithStorage();
}
const keys = Object.keys(changes);
if (!keys.some((k) => isKeyIncludedInCloudUploadPayload(k))) return;
void (async () => {
const all = (await browser.storage.local.get()) as Record<string, unknown>;
if (!isAutoCloudSyncEnabled(all)) return;
if (suppressAutoUploadDuringRestore) return;
if (!(await getAccessToken())) return;
scheduleDebouncedUpload();
})();
}
export function initCloudSettingsAutoSync(deps: { reloadSeqtaPages: () => void }): void {
reloadSeqtaPagesFn = deps.reloadSeqtaPages;
browser.storage.onChanged.addListener(onStorageChanged);
}
+151
View File
@@ -0,0 +1,151 @@
import Parser from "rss-parser";
/**
* Fetches news articles specifically for Australia from the NewsAPI.
*
* This function handles a specific case for fetching Australian news. It includes a
* mechanism to retry the fetch operation by appending "%00" to the URL if a
* rate limit error (`response.code == "rateLimited"`) is encountered. This is
* likely a workaround for cache-busting or bypassing certain rate-limiting measures.
*
* @param {string} url The NewsAPI URL to fetch Australian news from.
* @param {any} sendResponse A callback function (likely from a browser extension message listener)
* to send the fetched news data back to the caller.
* It's called with an object like `{ news: responseData }`.
*/
const fetchAustraliaNews = async (url: string, sendResponse: any) => {
fetch(url)
.then((result) => result.json())
.then((response) => {
if (response.code == "rateLimited") {
fetchAustraliaNews((url += "%00"), sendResponse);
} else {
sendResponse({ news: response });
}
});
};
/**
* A record mapping lowercase country codes (e.g., "usa", "canada") to an array
* of RSS feed URLs for news sources in that country.
*
* @type {Record<string, string[]>}
*/
const rssFeedsByCountry: Record<string, string[]> = {
usa: [
"https://rss.nytimes.com/services/xml/rss/nyt/HomePage.xml",
"https://www.huffpost.com/section/front-page/feed",
"https://www.npr.org/rss/rss.php",
],
taiwan: [
"https://news.ltn.com.tw/rss/all.xml",
"https://www.taipeitimes.com/xml/index.rss",
"https://international.thenewslens.com/rss",
],
hong_kong: [
"https://rthk9.rthk.hk/rthk/news/rss/e_expressnews_elocal.xml",
"https://www.scmp.com/rss/91/feed",
],
panama: [
"https://critica.com.pa/rss.xml",
"https://www.panamaamerica.com.pa/rss.xml",
"https://noticiassin.com/feed/",
"https://elcapitalfinanciero.com/feed/",
],
canada: [
"https://www.cbc.ca/cmlink/rss-topstories",
"https://calgaryherald.com/feed",
"https://ottawacitizen.com/feed",
"https://www.montrealgazette.com/feed",
],
singapore: [
"https://www.straitstimes.com/news/singapore/rss.xml",
"https://www.channelnewsasia.com/rssfeeds/8395986",
],
uk: [
"http://feeds.bbci.co.uk/news/rss.xml",
"https://www.theguardian.com/uk/rss",
],
japan: [
"https://www3.nhk.or.jp/nhkworld/en/news/feeds/",
"https://news.livedoor.com/topics/rss/int.xml",
],
netherlands: ["https://www.dutchnews.nl/feed/", "https://www.nrc.nl/rss/"],
};
/**
* Fetches news articles based on a specified source.
*
* The source can be:
* 1. The string "australia": Fetches news from Australian sources via NewsAPI,
* handled by the `fetchAustraliaNews` function.
* 2. A lowercase country code (e.g., "usa", "canada"): Fetches news from a predefined
* list of RSS feeds for that country, as specified in `rssFeedsByCountry`.
* 3. A direct RSS feed URL (starting with "http"): Fetches news directly from this URL.
*
* The fetched articles are then sent back to the caller using the `sendResponse` callback.
*
* @param {string} source The news source identifier. This can be "australia", a
* lowercase country code, or a direct RSS feed URL.
* @param {any} sendResponse A callback function (typically from a browser extension
* message listener, like `chrome.runtime.onMessage`)
* used to send the fetched news data back to the caller.
* It's called with an object like `{ news: { articles: [...] } }`.
*/
export async function fetchNews(source: string | undefined, sendResponse: any) {
const normalizedSource = typeof source === "string" && source.trim()
? source.trim()
: "australia";
if (normalizedSource === "australia") {
const date = new Date();
const from =
date.getFullYear() +
"-" +
(date.getMonth() + 1) +
"-" +
(date.getDate() - 5);
const url = `https://newsapi.org/v2/everything?domains=abc.net.au&from=${from}&apiKey=17c0da766ba347c89d094449504e3080`;
fetchAustraliaNews(url, sendResponse);
return;
}
const parser = new Parser();
let feeds: string[];
console.log("fetchNews", normalizedSource);
if (rssFeedsByCountry[normalizedSource.toLowerCase()]) {
feeds = rssFeedsByCountry[normalizedSource.toLowerCase()];
} else if (normalizedSource.startsWith("http")) {
feeds = [normalizedSource];
} else {
console.warn("[BetterSEQTA+] Invalid news source, falling back to Australia", normalizedSource);
return fetchNews("australia", sendResponse);
}
const articlesPromises = feeds.map(async (feedUrl) => {
try {
const response = await fetch(feedUrl);
const feedString = await response.text();
const feed = await parser.parseString(feedString);
return feed.items.map((item) => ({
title: item.title || "",
description: item.contentSnippet || "",
url: item.link || "",
urlToImage: null,
}));
} catch (error) {
console.error(`Failed to fetch RSS feed: ${feedUrl}`, error);
return [];
}
});
const articlesArray = await Promise.all(articlesPromises);
const articles = articlesArray.flat();
sendResponse({ news: { articles } });
}
+93 -4
View File
@@ -15,12 +15,45 @@
* along with EvenBetterSEQTA. If not, see <https://www.gnu.org/licenses/>.
*/
@import './injected/popup.scss';
@use "injected/popup.scss";
@font-face {
font-family: "Roboto";
src: url("https://fonts.gstatic.com/s/roboto/v50/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3yUBA.woff2")
format("woff2");
font-weight: 700;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "IconFamily";
src: url("@/resources/fonts/IconFamily.woff") format("woff");
font-weight: normal;
font-style: normal;
font-display: block;
}
@layer base, override;
@layer override {
.legacy-root,
.legacy-root * {
font-family: var(--betterseqta-font-family, Rubik), sans-serif !important;
}
.iconFamily,
.iconFamily *,
[class~="iconFamily"],
[class~="iconFamily"] * {
font-family: "IconFamily" !important;
}
}
html {
background: #161616 !important;
background-color: #161616;
font-family: Rubik, Roboto !important;
font-family: Roboto, system-ui, -apple-system, sans-serif !important;
}
.tooltip svg {
@@ -77,7 +110,9 @@ html {
transform-origin: top;
transition: transform 0.2s;
}
body:has(.outside-container:not(.hide)) #AddedSettings.tooltip:hover > .tooltiptext {
body:has(.outside-container:not(.hide))
#AddedSettings.tooltip:hover
> .tooltiptext {
transform: scale(0);
}
.assessmenttooltip svg {
@@ -91,4 +126,58 @@ body:has(.outside-container:not(.hide)) #AddedSettings.tooltip:hover > .tooltipt
top: 115% !important;
background: var(--text-primary) !important;
color: var(--theme-primary) !important;
}
}
.fixed-tooltip {
display: inline-block;
z-index: 5 !important;
width: 28px;
background: none;
box-shadow: none;
padding: 2px;
position: absolute;
}
.fixed-tooltip svg {
fill: var(--theme-primary);
}
.tooltiptext-fixed {
width: 120px;
transform: scale(0);
transition: transform 0.2s;
transform-origin: top;
background: var(--background-primary);
color: var(--text-primary);
text-align: center;
border-radius: 6px;
padding: 2px;
position: fixed;
z-index: 1000;
top: 0;
left: 0;
margin-left: -62px;
}
.tooltiptext-fixed::after {
content: "";
position: absolute;
bottom: 100%;
left: 50%;
margin-left: -5px;
border-width: 5px;
border-style: solid;
border-color: transparent transparent var(--text-primary) transparent;
}
.tooltiptext-fixed.show {
transform: scale(1);
transform-origin: top;
transition: transform 0.2s;
}
.tooltiptext-fixed p:hover {
cursor: pointer;
background: rgba(0, 0, 0, 0.3) !important;
transition: 200ms;
}
.tooltiptext-fixed p {
border-radius: 8px !important;
padding-top: 2px;
padding-bottom: 2px;
margin: 2px;
}
+1 -1
View File
@@ -1 +1 @@
import './documentload.scss';
import "./documentload.scss";
+11 -3
View File
@@ -15,7 +15,7 @@
* along with EvenBetterSEQTA. If not, see <https://www.gnu.org/licenses/>.
*/
body {
body {
background: transparent;
}
@@ -25,7 +25,9 @@
span,
body {
color: white !important;
text-shadow: 1px 1px 2px #161616, 0 0 1em #161616;
text-shadow:
1px 1px 2px #161616,
0 0 1em #161616;
}
body {
@@ -111,4 +113,10 @@
text-decoration: underline;
transition: text-shadow 0.5s;
}
}
}
.cke_panel_listItem > a {
&:hover {
background: #3d3d3e !important;
}
}
+2189 -503
View File
File diff suppressed because it is too large Load Diff
+4 -2
View File
@@ -36,5 +36,7 @@
transform-origin: 70% 0;
will-change: opacity, transform;
transform: translateZ(0); // promotes GPU rendering
transition: opacity 0.05s, transform 0.05s;
}
transition:
opacity 0.05s,
transform 0.05s;
}
+13 -3
View File
@@ -25,7 +25,7 @@
padding-top: 2px;
}
.sub:has(ul>li.hasChildren.active) > .nav > .back {
.sub:has(ul > li.hasChildren.active) > .nav > .back {
display: none !important;
}
@@ -35,9 +35,19 @@
}
#menu .sub {
transition: transform 0.3s ease;
transition: transform 0.3s ease, left 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
#menu > ul:has(li.hasChildren.active) > li.active {
background: transparent !important;
}
}
/* Icon-only collapsed: submenu slides over narrow icons */
body.icon-only-sidebar:not(:has(#menu li.hasChildren.active)) #menu > ul:has(li.hasChildren.active) > li::before,
body.icon-only-sidebar:not(:has(#menu li.hasChildren.active)) #menu > ul ul:has(li.hasChildren.active) > li::before,
body.icon-only-sidebar:not(:has(#menu li.hasChildren.active)) #menu > ul:has(li.hasChildren.active) > li > label,
body.icon-only-sidebar:not(:has(#menu li.hasChildren.active)) #menu > ul:has(li.hasChildren.active) > li > svg,
body.icon-only-sidebar:not(:has(#menu li.hasChildren.active)) #menu > ul ul:has(li.hasChildren.active) > li > label,
body.icon-only-sidebar:not(:has(#menu li.hasChildren.active)) #menu > ul ul:has(li.hasChildren.active) > li > svg {
transform: translateX(-70px);
}
+1 -1
View File
@@ -12,4 +12,4 @@ html:not(.dark) {
--theme-primary: #ffffff;
--theme-secondary: #e5e7eb;
--text-primary: black;
}
}
+18 -21
View File
@@ -8,10 +8,9 @@ html.transparencyEffects:not(.dark) {
--background-secondary: rgba(229, 231, 235, 0.6);
}
html.transparencyEffects {
/* Background Fixes */
.notifications__item___2ErJN,
[class*="notifications__item___"],
#shortcuts {
backdrop-filter: unset !important;
}
@@ -22,53 +21,51 @@ html.transparencyEffects {
}
/* Blurs */
.search,
.document,
.border,
.draggable,
.notice,
.BasicPanel__BasicPanel___1GP6s,
[class*="BasicPanel__BasicPanel___"],
.message.addMessage,
.singleSelect,
.uiFileHandlerPanel,
.Module__wrapper___2sbOo,
.notifications__list___rp2L2,
[class*="Module__wrapper___"],
[class*="notifications__list___"],
.thread,
.calendar,
.navigator,
#title,
.LabelList__selected___3Egk7,
[class*="LabelList__selected___"],
.buttonChecklist,
.pane,
.legacy-root button, .legacy-root a,
.MessageList__MessageList___3DxoC {
.legacy-root button,
.legacy-root a,
[class*="MessageList__MessageList___"] {
backdrop-filter: blur(80px);
}
.filter-select,
.uiShortText.search,
.report {
backdrop-filter: blur(10px) !important;
}
#menu,
.kanban-column,
.whatsnewContainer,
.Message__Message___3oJaU {
[class*="Message__Message___"] {
backdrop-filter: blur(50px);
}
#menu {
backdrop-filter: blur(20px);
}
.title > a {
backdrop-filter: blur(0px) !important;
}
.search,
.document,
.border {
backdrop-filter: blur(80px);
}
#main > .dashboard {
section,
.dashlet {
backdrop-filter: blur(8px);
}
}
}
}
+12 -6
View File
@@ -1,8 +1,14 @@
declare module '*.mp4';
declare module '*.woff';
declare module '*.scss';
declare module '*.png';
declare module '*.html';
declare module "*.mp4";
declare module "*.woff";
declare module "*.scss";
declare module "*.png";
declare module "*.html";
declare module "*.svelte";
declare module "*?inlineWorker" {
const value: () => Worker;
export default value;
}
declare module "*.png?base64" {
const value: string;
@@ -27,4 +33,4 @@ declare module "*.gif?base64" {
declare module "*.svg?base64" {
const value: string;
export default value;
}
}
+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>"
}
+15 -2
View File
@@ -1,7 +1,20 @@
<script lang="ts">
let { onClick, text } = $props<{ onClick: () => void, text: string, [key: string]: any }>();
let {
onClick,
text,
disabled = false,
} = $props<{
onClick: () => void;
text: string;
disabled?: boolean;
}>();
</script>
<button onclick={onClick} class='px-4 py-1 text-[0.75rem] dark:bg-[#38373D] bg-[#DDDDDD] dark:text-white rounded-md'>
<button
type="button"
onclick={onClick}
disabled={disabled}
class="px-5 py-1.5 text-[0.75rem] shadow-2xl border dark:bg-[#38373D]/50 bg-[#DDDDDD]/50 border-[#DDDDDD]/30 dark:border-[#38373D]/30 dark:text-white rounded-lg disabled:cursor-not-allowed disabled:opacity-50"
>
{text}
</button>
+157
View File
@@ -0,0 +1,157 @@
<script lang="ts">
import { onMount } from "svelte";
import { animate } from "motion";
import { delay } from "@/seqta/utils/delay.ts";
import { cloudAuth } from "@/seqta/utils/CloudAuth";
const { hidePanel } = $props<{
hidePanel: () => void;
}>();
let cloudState = $state(cloudAuth.state);
let background = $state<HTMLDivElement | null>(null);
let content = $state<HTMLDivElement | null>(null);
let loginError = $state<string | null>(null);
onMount(() => {
const unsub = cloudAuth.subscribe((s) => {
cloudState = s;
});
if (background && content) {
animate(
background,
{ opacity: [0, 1] },
{ duration: 0.3, ease: [0.4, 0, 0.2, 1] }
);
animate(
content,
{ scale: [0.4, 1], opacity: [0, 1] },
{ type: "spring", stiffness: 400, damping: 30 }
);
}
const handleEscapeKey = (e: KeyboardEvent) => {
if (e.key === "Escape") closePanel();
};
document.addEventListener("keydown", handleEscapeKey);
return () => {
unsub();
document.removeEventListener("keydown", handleEscapeKey);
};
});
async function closePanel() {
if (!background || !content) return;
animate(
content,
{ scale: [1, 0.4], opacity: [1, 0] },
{ type: "spring", stiffness: 400, damping: 30 }
);
animate(
background,
{ opacity: [1, 0] },
{ ease: [0.4, 0, 0.2, 1] }
);
await delay(400);
hidePanel();
}
function handleBackgroundClick(event: MouseEvent) {
if (event.target === background) closePanel();
}
async function handleSignIn() {
loginError = null;
const result = await cloudAuth.startLogin();
if (result.success) {
closePanel();
} else {
loginError = result.error ?? "Failed to open login page";
}
}
async function handleLogout() {
await cloudAuth.logout();
}
function getInitials(): string {
const u = cloudState.user;
if (!u) return "?";
if (u.displayName) return u.displayName.slice(0, 2).toUpperCase();
if (u.username) return u.username.slice(0, 2).toUpperCase();
if (u.email) return u.email.slice(0, 2).toUpperCase();
return "?";
}
</script>
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
bind:this={background}
class="flex absolute top-0 left-0 z-50 justify-center items-center w-full h-full cursor-pointer bg-black/50"
onclick={handleBackgroundClick}
onkeydown={(e) => { if (e.key === "Enter") handleBackgroundClick; }}
>
<div
bind:this={content}
class="p-5 w-[320px] bg-white rounded-xl border shadow-lg cursor-auto dark:bg-zinc-800 border-zinc-100 dark:border-zinc-700"
>
<h3 class="text-lg font-bold text-zinc-900 dark:text-white">BetterSEQTA Cloud</h3>
<p class="mt-0.5 text-sm text-zinc-500 dark:text-zinc-400">Account & sync</p>
<div class="mt-4">
{#if cloudState.isLoggedIn}
<div class="flex flex-col gap-4">
<div class="flex items-center gap-3">
{#if cloudState.user?.pfpUrl}
<img
src={cloudState.user.pfpUrl}
alt=""
class="w-12 h-12 rounded-full object-cover ring-2 ring-zinc-200 dark:ring-zinc-600"
/>
{:else}
<div class="flex items-center justify-center w-12 h-12 rounded-full bg-zinc-300 dark:bg-zinc-600 text-zinc-700 dark:text-zinc-200 font-semibold text-base">
{getInitials()}
</div>
{/if}
<div class="min-w-0 flex-1">
<p class="text-sm font-medium text-zinc-900 dark:text-white truncate">
{cloudState.user?.displayName || cloudState.user?.username || cloudState.user?.email || "User"}
</p>
{#if cloudState.user?.email && cloudState.user?.email !== (cloudState.user?.displayName || cloudState.user?.username)}
<p class="text-xs text-zinc-500 dark:text-zinc-400 truncate">{cloudState.user.email}</p>
{/if}
</div>
</div>
<button
type="button"
onclick={handleLogout}
class="w-full px-4 py-2.5 text-sm font-medium rounded-lg bg-zinc-200 dark:bg-zinc-700 text-zinc-900 dark:text-white hover:bg-zinc-300 dark:hover:bg-zinc-600 transition-colors duration-200"
>
Sign out
</button>
</div>
{:else}
<div class="flex flex-col gap-3">
<p class="text-sm text-zinc-600 dark:text-zinc-400">
Sign in to sync settings across devices, use your cloud profile picture, and more.
</p>
<button
type="button"
onclick={handleSignIn}
class="w-full px-4 py-2.5 text-sm font-medium rounded-lg bg-zinc-800 dark:bg-zinc-200 text-white dark:text-zinc-900 hover:bg-zinc-700 dark:hover:bg-zinc-300 transition-colors duration-200"
>
Sign in with BetterSEQTA Cloud
</button>
{#if loginError}
<p class="text-xs text-red-600 dark:text-red-400">{loginError}</p>
{/if}
<p class="text-xs text-center text-zinc-400 dark:text-zinc-500">
Opens accounts.betterseqta.org in a new tab
</p>
</div>
{/if}
</div>
</div>
</div>
@@ -0,0 +1,120 @@
<script lang="ts">
import browser from "webextension-polyfill";
import { cloudAuth } from "@/seqta/utils/CloudAuth";
import { settingsState } from "@/seqta/utils/listeners/SettingsState";
import Button from "./Button.svelte";
import Switch from "./Switch.svelte";
let { showDisclaimer } = $props<{
showDisclaimer: (onConfirm: () => void, onCancel: () => void) => void;
}>();
let cloudState = $state(cloudAuth.state);
let busy = $state(false);
let statusMessage = $state<string | null>(null);
let statusError = $state<string | null>(null);
$effect(() => {
const unsub = cloudAuth.subscribe((s) => {
cloudState = s;
});
return unsub;
});
async function upload() {
const token = await cloudAuth.getStoredToken();
if (!token) return;
busy = true;
statusError = null;
statusMessage = null;
try {
const res = (await browser.runtime.sendMessage({
type: "cloudSettingsUpload",
token,
})) as { success?: boolean; error?: string };
if (res?.success) {
statusMessage = "Settings uploaded.";
} else {
statusError = res?.error ?? "Upload failed";
}
} catch (e) {
statusError = e instanceof Error ? e.message : "Upload failed";
} finally {
busy = false;
}
}
function promptDownload() {
showDisclaimer(confirmDownload, () => {});
}
async function confirmDownload() {
const token = await cloudAuth.getStoredToken();
if (!token) return;
busy = true;
statusError = null;
statusMessage = null;
try {
const res = (await browser.runtime.sendMessage({
type: "cloudSettingsDownload",
token,
})) as { success?: boolean; error?: string; notFound?: boolean };
if (res?.success) {
statusMessage = "Settings restored.";
} else {
statusError = res?.error ?? "Download failed";
}
} catch (e) {
statusError = e instanceof Error ? e.message : "Download failed";
} finally {
busy = false;
}
}
</script>
{#if cloudState.isLoggedIn}
<div class="flex flex-col gap-2.5">
<div class="flex items-center justify-between gap-3">
<div>
<p class="text-[11px] font-semibold text-zinc-800 dark:text-zinc-100">Automatic sync</p>
<p class="text-[10px] text-zinc-500 dark:text-zinc-400">Syncs settings when SEQTA loads and when you make changes</p>
</div>
<div class="shrink-0">
<Switch
state={$settingsState.autoCloudSettingsSync !== false}
onChange={(isOn: boolean) => (settingsState.autoCloudSettingsSync = isOn)}
/>
</div>
</div>
<div class="flex flex-wrap gap-2">
<Button
text={busy ? "Please wait\u2026" : "Upload"}
onClick={upload}
disabled={busy}
/>
<Button
text={busy ? "Please wait\u2026" : "Download"}
onClick={promptDownload}
disabled={busy}
/>
</div>
{#if statusMessage}
<p class="text-[11px] text-emerald-600 dark:text-emerald-400">{statusMessage}</p>
{/if}
{#if statusError}
<p class="text-[11px] text-red-600 dark:text-red-400">{statusError}</p>
{/if}
<p class="text-[10px] text-zinc-400 dark:text-zinc-500">
Passwords and tokens are never synced.
<a
href="https://betterseqta.org/privacy"
target="_blank"
rel="noopener noreferrer"
class="font-medium text-emerald-600 underline decoration-emerald-600/50 underline-offset-2 hover:text-emerald-700 dark:text-emerald-400 dark:hover:text-emerald-300 rounded-sm"
>Privacy policy</a>
</p>
</div>
{/if}
+2 -2
View File
@@ -20,7 +20,7 @@
let editor = $state<HTMLDivElement | null>(null)
let view: EditorView | null = null;
let editorTheme = new Compartment();
let { value, onChange } = $props<{value: string, onChange: (value: string) => void}>()
let { value, onChange, className } = $props<{value: string, onChange: (value: string) => void, className?: string}>()
function createEditorState(initialContents: string) {
let extensions = [
@@ -91,4 +91,4 @@
})
</script>
<div class="rounded-lg text-[13px] overflow-clip w-full bg-white dark:bg-zinc-900" bind:this={editor}></div>
<div class={`rounded-lg text-[13px] overflow-clip w-full bg-white dark:bg-zinc-900 ${className}`} bind:this={editor}></div>
+17
View File
@@ -2,12 +2,29 @@ div:has(> #rbgcp-wrapper) {
background: transparent !important;
}
#rbgcp-inputs-wrap {
padding-top: 4px !important;
margin-bottom: -8px;
#rbgcp-hex-input,
#rbgcp-input {
height: 28px !important;
}
}
.dark {
#rbgcp-wrapper {
div[style="padding-top: 11px; position: relative;"] div {
color: white !important;
}
#rbgcp-inputs-wrap #rbgcp-hex-input,
#rbgcp-inputs-wrap #rbgcp-input {
color: white !important;
background-color: #37373b !important;
border: none !important;
}
div:has(> #rbgcp-solid-btn),
div:has(> #rbgcp-advanced-btn),
#rbgcp-color-model-btn > div,
+16 -8
View File
@@ -2,7 +2,7 @@
import { onMount } from 'svelte'
import ColourPicker from './ColourPicker.tsx';
import ReactAdapter from './utils/ReactAdapter.svelte';
import { animate, spring } from 'motion';
import { animate } from 'motion';
import { delay } from '@/seqta/utils/delay.ts'
const { hidePicker, standalone = false, savePresets = true, customOnChange = null, customState = null } = $props<{
@@ -23,13 +23,17 @@
animate(
content,
{ scale: [1, 0.4], opacity: [1, 0] },
{ easing: spring({ stiffness: 400, damping: 30 }) }
{
type: 'spring',
stiffness: 400,
damping: 30
}
);
animate(
background,
{ opacity: [1, 0] },
{ easing: [0.4, 0, 0.2, 1] }
{ ease: [0.4, 0, 0.2, 1] }
);
await delay(400);
@@ -43,13 +47,17 @@
animate(
background,
{ opacity: [0, 1] },
{ duration: 0.3, easing: [0.4, 0, 0.2, 1] }
{ duration: 0.3, ease: [0.4, 0, 0.2, 1] }
);
animate(
content,
{ scale: [0.4, 1], opacity: [0, 1] },
{ easing: spring({ stiffness: 400, damping: 30 }) }
{
type: 'spring',
stiffness: 400,
damping: 30
}
);
const handleEscapeKey = (e: KeyboardEvent) => {
@@ -73,20 +81,20 @@
</script>
{#if standalone}
<div class="h-auto rounded-xl overflow-clip">
<div class="h-auto overflow-clip rounded-xl">
<ReactAdapter customOnChange={customOnChange} customState={customState} savePresets={savePresets} el={ColourPicker} />
</div>
{:else}
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
bind:this={background}
class="absolute top-0 left-0 z-50 flex items-center justify-center w-full h-full cursor-pointer bg-black/20"
class="flex absolute top-0 left-0 z-50 justify-center items-center w-full h-full shadow-2xl cursor-pointer bg-black/20 border border-[#DDDDDD]/30 dark:border-[#38373D]/30"
onclick={handleBackgroundClick}
onkeydown={(e) => { e.key === 'Enter' && handleBackgroundClick }}
>
<div
bind:this={content}
class="h-auto p-4 bg-white border shadow-lg cursor-auto rounded-xl dark:bg-zinc-800 border-zinc-100 dark:border-zinc-700"
class="p-4 h-auto bg-white rounded-xl border shadow-lg cursor-auto dark:bg-zinc-800 border-zinc-100 dark:border-zinc-700"
>
<ReactAdapter customOnChange={customOnChange} customState={customState} savePresets={savePresets} el={ColourPicker} />
</div>
+45 -31
View File
@@ -1,6 +1,6 @@
import ColorPicker from "react-best-gradient-color-picker"
import { useEffect, useRef, useState } from "react"
import { settingsState } from "@/seqta/utils/listeners/SettingsState.ts"
import ColorPicker from "react-best-gradient-color-picker";
import { useEffect, useRef, useState } from "react";
import { settingsState } from "@/seqta/utils/listeners/SettingsState.ts";
const defaultPresets = [
"linear-gradient(30deg, rgba(229,209,218,1) 0%, RGBA(235,169,202,1) 46%, rgba(214,155,162,1) 100%)",
@@ -22,12 +22,12 @@ const defaultPresets = [
"rgba(30, 64, 175, 0.89)",
"rgba(134, 25, 143, 1)",
"rgba(14, 165, 233, 0.9)",
]
];
interface PickerProps {
customOnChange?: (color: string) => void
customState?: string
savePresets?: boolean
customOnChange?: (color: string) => void;
customState?: string;
savePresets?: boolean;
}
export default function Picker({
@@ -35,37 +35,49 @@ export default function Picker({
customState,
savePresets = true,
}: PickerProps) {
const [customThemeColor, setCustomThemeColor] = useState<string | null>()
const [presets, setPresets] = useState<string[]>()
const [customThemeColor, setCustomThemeColor] = useState<string | null>();
const [presets, setPresets] = useState<string[]>();
const latestValuesRef = useRef({ customThemeColor, customOnChange, savePresets, presets });
const latestValuesRef = useRef({
customThemeColor,
customOnChange,
savePresets,
presets,
});
useEffect(() => {
if (customState !== undefined && customState !== null) {
setCustomThemeColor(customState)
setCustomThemeColor(customState);
} else {
setCustomThemeColor(settingsState.selectedColor ?? null)
setCustomThemeColor(settingsState.selectedColor ?? null);
}
if (presets === undefined) {
const savedPresets = localStorage.getItem("colorPickerPresets")
setPresets(savedPresets ? JSON.parse(savedPresets) : defaultPresets)
const savedPresets = localStorage.getItem("colorPickerPresets");
setPresets(savedPresets ? JSON.parse(savedPresets) : defaultPresets);
}
}, [])
}, []);
useEffect(() => {
latestValuesRef.current = { customThemeColor, customOnChange, savePresets, presets };
latestValuesRef.current = {
customThemeColor,
customOnChange,
savePresets,
presets,
};
}, [customThemeColor, customOnChange, savePresets, presets]);
useEffect(() => {
return () => {
const { customThemeColor, customOnChange, savePresets, presets } = latestValuesRef.current;
if (!(customThemeColor && !customOnChange && savePresets && presets)) return;
const { customThemeColor, customOnChange, savePresets, presets } =
latestValuesRef.current;
if (!(customThemeColor && !customOnChange && savePresets && presets))
return;
// Only proceed if presets are different (avoid unnecessary updates)
const existingIndex = presets.indexOf(customThemeColor);
let updatedPresets;
if (existingIndex === 0) {
// No need to update if the selected color is already the first element
return;
@@ -78,31 +90,33 @@ export default function Picker({
} else {
updatedPresets = [customThemeColor, ...presets].slice(0, 18);
}
localStorage.setItem("colorPickerPresets", JSON.stringify(updatedPresets));
}
}, [])
localStorage.setItem(
"colorPickerPresets",
JSON.stringify(updatedPresets),
);
};
}, []);
useEffect(() => {
if (customThemeColor && !customOnChange) {
settingsState.selectedColor = customThemeColor
settingsState.selectedColor = customThemeColor;
}
}, [customThemeColor, customOnChange])
}, [customThemeColor, customOnChange]);
return (
<ColorPicker
disableDarkMode={true}
presets={presets}
hideInputs={true}
value={customThemeColor ?? ""}
onChange={(color: string) => {
if (customOnChange) {
customOnChange(color)
setCustomThemeColor(color)
customOnChange(color);
setCustomThemeColor(color);
} else {
setCustomThemeColor(color)
setCustomThemeColor(color);
}
}}
/>
)
);
}
@@ -0,0 +1,172 @@
<script lang="ts">
import { fade } from "svelte/transition";
import browser from "webextension-polyfill";
import QRCode from "qrcode";
import { portal } from "../utils/portal";
let showQrModal = $state(false);
let qrDataUrl = $state<string | null>(null);
let appLink = $state<string | null>(null);
let errorMessage = $state<string | null>(null);
let isLoading = $state(false);
let isStandalone = $state(false);
function isExtensionPage(): boolean {
return (
window.location.protocol === "chrome-extension:" ||
window.location.protocol === "moz-extension:"
);
}
function isSeqtaUrl(url: string): boolean {
try {
const u = new URL(url);
return u.hostname.includes("seqta") || u.hostname.endsWith(".edu.au");
} catch {
return false;
}
}
function normalizeBaseUrl(url: string): string {
try {
const u = new URL(url);
return u.origin;
} catch {
return url;
}
}
async function getAppLink(): Promise<string | null> {
let baseUrl: string | undefined;
if (isExtensionPage()) {
baseUrl = undefined;
} else {
baseUrl = normalizeBaseUrl(window.location.href);
if (!isSeqtaUrl(baseUrl)) return null;
}
const { appLink: link } = (await browser.runtime.sendMessage({
type: "getSeqtaSession",
baseUrl,
})) as { appLink: string | null };
return link ?? null;
}
async function generateQrCode() {
errorMessage = null;
qrDataUrl = null;
isLoading = true;
try {
isStandalone = isExtensionPage();
const link = await getAppLink();
if (!link) {
if (isStandalone) {
errorMessage =
"Open SEQTA Learn in a tab and log in, then open settings from that tab to generate a QR code.";
} else {
errorMessage = "Please log in to SEQTA Learn first.";
}
return;
}
const dataUrl = await QRCode.toDataURL(link, { width: 256, margin: 2 });
appLink = link;
qrDataUrl = dataUrl;
showQrModal = true;
} catch (err) {
console.error("[ConnectMobileApp] Failed to generate QR:", err);
errorMessage = "Failed to generate QR code. Please try again.";
} finally {
isLoading = false;
}
}
function closeModal() {
showQrModal = false;
qrDataUrl = null;
appLink = null;
errorMessage = null;
}
function openAppLink() {
if (appLink) window.location.href = appLink;
}
function downloadQrImage() {
if (!qrDataUrl) return;
const link = document.createElement("a");
link.href = qrDataUrl;
link.download = "desqta-login-qr.png";
link.click();
}
</script>
<div class="flex flex-col gap-1 items-end">
<button
type="button"
onclick={generateQrCode}
disabled={isLoading}
class="px-5 py-1.5 text-[0.75rem] text-nowrap shadow-2xl border dark:bg-[#38373D]/50 bg-[#DDDDDD]/50 border-[#DDDDDD]/30 dark:border-[#38373D]/30 dark:text-white rounded-lg disabled:opacity-50 disabled:cursor-not-allowed transition-opacity">
{isLoading ? "Generating..." : "Generate QR"}
</button>
{#if errorMessage}
<p class="text-xs text-right text-amber-600 dark:text-amber-400">{errorMessage}</p>
{/if}
</div>
{#if showQrModal && qrDataUrl}
<div
use:portal
class="fixed cursor-auto inset-0 z-[10000] flex justify-center items-center bg-black/50 {isStandalone ? 'backdrop-blur-sm' : ''}"
role="button"
tabindex="-1"
onclick={(e) => {
if (e.target === e.currentTarget) closeModal();
}}
onkeydown={(e) => {
if (e.key === "Escape") closeModal();
}}
transition:fade={{ duration: 150 }}>
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
class="p-6 mx-4 w-full max-w-sm bg-white rounded-2xl shadow-2xl dark:bg-zinc-800"
onclick={(e) => e.stopPropagation()}
onkeydown={(e) => e.stopPropagation()}>
<div class="flex justify-between items-center mb-4">
<h2 class="text-lg font-bold text-zinc-900 dark:text-white">Scan with DesQTA</h2>
<button
type="button"
onclick={closeModal}
class="p-2 rounded-lg transition-colors text-zinc-500 hover:text-zinc-700 hover:bg-zinc-100 dark:hover:text-zinc-400 dark:hover:bg-zinc-700"
aria-label="Close">
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<div class="flex justify-center p-4 bg-white rounded-xl dark:bg-zinc-900">
<img src={qrDataUrl} alt="SEQTA Learn app link QR code" class="w-64 h-64" />
</div>
<div class="flex flex-col gap-2 mt-4">
<button
type="button"
onclick={openAppLink}
class="px-4 py-2.5 w-full text-sm font-medium text-white bg-indigo-600 rounded-lg transition-colors dark:bg-indigo-500 hover:bg-indigo-700 dark:hover:bg-indigo-600">
Sign into DesQTA Desktop
</button>
<button
type="button"
onclick={downloadQrImage}
class="px-4 py-2 w-full text-xs font-medium rounded-lg border transition-colors text-zinc-500 dark:text-zinc-400 border-zinc-200 dark:border-zinc-600 hover:bg-zinc-50 dark:hover:bg-zinc-800/50">
Download QR as image
</button>
</div>
<p class="mt-2 text-sm text-center text-zinc-600 dark:text-zinc-400">
Or scan this QR code with DesQTA on your phone.
</p>
</div>
</div>
{/if}
@@ -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>
@@ -0,0 +1,141 @@
<script lang="ts">
import { fade } from "svelte/transition";
import { onMount } from "svelte";
import { settingsState } from "@/seqta/utils/listeners/SettingsState";
import { FONT_PRESETS, DEFAULT_FONT_ID, getFontPreset } from "@/seqta/ui/fonts/presets";
import {
applySelectedFont,
buildFontPreviewCss,
ensureFontPickerFontsLoaded,
} from "@/seqta/ui/fonts/Manager";
import { portal } from "@/interface/utils/portal";
import { syncPageThemeToElement } from "@/interface/utils/syncPageTheme";
import fontPickerStyles from "./fontPickerModal.css?inline";
let { hidePicker } = $props<{ hidePicker: () => void }>();
let rootEl = $state<HTMLElement | null>(null);
let selectedId = $state(getFontPreset($settingsState.selectedFont).id);
let styleEl: HTMLStyleElement | null = null;
function selectFont(id: string) {
selectedId = id;
settingsState.selectedFont = id;
applySelectedFont(id);
}
function resetToDefault() {
selectFont(DEFAULT_FONT_ID);
}
function handleBackdropClick(event: MouseEvent) {
if (event.target === event.currentTarget) hidePicker();
}
function syncTheme() {
if (rootEl) syncPageThemeToElement(rootEl);
}
onMount(() => {
void ensureFontPickerFontsLoaded();
styleEl = document.getElementById(
"betterseqta-font-picker-styles",
) as HTMLStyleElement | null;
if (!styleEl) {
styleEl = document.createElement("style");
styleEl.id = "betterseqta-font-picker-styles";
document.head.appendChild(styleEl);
}
styleEl.textContent = `${fontPickerStyles}\n${buildFontPreviewCss()}`;
syncTheme();
const themeObserver = new MutationObserver(() => syncTheme());
themeObserver.observe(document.documentElement, {
attributes: true,
attributeFilter: ["style", "class"],
});
const handleEscapeKey = (event: KeyboardEvent) => {
if (event.key === "Escape") hidePicker();
};
document.addEventListener("keydown", handleEscapeKey);
return () => {
themeObserver.disconnect();
document.removeEventListener("keydown", handleEscapeKey);
};
});
</script>
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
bind:this={rootEl}
use:portal={document.body}
class="bsplus-font-picker-overlay bsplus-font-picker-root"
onclick={handleBackdropClick}
onkeydown={(event) => {
if (event.key === "Enter" || event.key === " ") handleBackdropClick(event as unknown as MouseEvent);
}}
role="presentation"
transition:fade={{ duration: 200 }}
>
<div
class="bsplus-font-picker-dialog"
onclick={(event) => event.stopPropagation()}
onkeydown={(event) => event.stopPropagation()}
role="dialog"
aria-modal="true"
aria-labelledby="font-picker-title"
>
<header class="bsplus-font-picker-header">
<div class="bsplus-font-picker-header-actions">
<button
type="button"
onclick={resetToDefault}
disabled={selectedId === DEFAULT_FONT_ID}
class="bsplus-font-picker-reset"
aria-label="Reset font to default"
>
Reset to default
</button>
<button
type="button"
onclick={hidePicker}
class="bsplus-font-picker-done"
aria-label="Close font picker"
>
Done
</button>
</div>
<div class="bsplus-font-picker-header-text">
<h2 id="font-picker-title" class="bsplus-font-picker-title">
Choose a font
</h2>
<p class="bsplus-font-picker-desc">
Choose a typeface for SEQTA Learn.
</p>
</div>
</header>
<div class="bsplus-font-picker-list">
{#each FONT_PRESETS as preset (preset.id)}
<button
type="button"
onclick={() => selectFont(preset.id)}
class="bsplus-font-picker-option {selectedId === preset.id ? 'is-selected' : ''}"
data-font-id={preset.id}
>
<div class="bsplus-font-picker-option-head">
<span class="bsplus-font-picker-option-name">{preset.name}</span>
{#if selectedId === preset.id}
<span class="bsplus-font-picker-badge">Selected</span>
{/if}
</div>
</button>
{/each}
</div>
</div>
</div>
+227
View File
@@ -0,0 +1,227 @@
<script lang="ts">
import { isValidHotkey, parseHotkey } from '@/plugins/built-in/globalSearch/src/utils/hotkeyUtils';
let { value, onChange } = $props<{
value: string,
onChange: (newValue: string) => void
}>();
let isRecording = $state(false);
let recordedKeys = $state<Set<string>>(new Set());
let inputElement = $state<HTMLInputElement>();
const formatKeyForHotkey = (key: string): string => {
// Map special keys to their hotkey format
const keyMap: Record<string, string> = {
'Control': 'ctrl',
'Meta': 'cmd',
'Alt': 'alt',
'Shift': 'shift',
' ': 'space',
'ArrowUp': 'up',
'ArrowDown': 'down',
'ArrowLeft': 'left',
'ArrowRight': 'right',
'Escape': 'esc',
'Enter': 'enter',
'Tab': 'tab',
'Backspace': 'backspace',
'Delete': 'delete',
};
return keyMap[key] || key.toLowerCase();
};
const formatKeyForDisplay = (key: string): string => {
// Map keys to their display format
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
const keyMap: Record<string, string> = {
'ctrl': isMac ? '⌃' : 'Ctrl',
'cmd': '⌘',
'meta': '⌘',
'alt': isMac ? '⌥' : 'Alt',
'shift': isMac ? '⇧' : 'Shift',
'space': 'Space',
'up': '↑',
'down': '↓',
'left': '←',
'right': '→',
'esc': 'Esc',
'enter': 'Enter',
'tab': 'Tab',
'backspace': 'Backspace',
'delete': 'Delete',
};
return keyMap[key.toLowerCase()] || key.toUpperCase();
};
const getHotkeyParts = (hotkeyString: string): string[] => {
if (!hotkeyString || !isValidHotkey(hotkeyString)) {
return [];
}
const parsed = parseHotkey(hotkeyString);
const parts: string[] = [];
// Add modifiers in a consistent order
if (parsed.ctrl) parts.push('ctrl');
if (parsed.meta) parts.push('cmd');
if (parsed.alt) parts.push('alt');
if (parsed.shift) parts.push('shift');
// Add the main key
if (parsed.key) parts.push(parsed.key);
return parts;
};
const startRecording = () => {
isRecording = true;
recordedKeys.clear();
inputElement?.focus();
};
const stopRecording = () => {
if (recordedKeys.size > 0) {
if (recordedKeys.has('esc')) {
onChange('');
isRecording = false;
recordedKeys.clear();
inputElement?.blur();
return;
}
// Build the hotkey string
const modifiers: string[] = [];
let mainKey = '';
for (const key of recordedKeys) {
if (['ctrl', 'cmd', 'alt', 'shift'].includes(key)) {
modifiers.push(key);
} else {
mainKey = key;
}
}
if (mainKey) {
const hotkeyString = [...modifiers, mainKey].join('+');
if (isValidHotkey(hotkeyString)) {
onChange(hotkeyString);
}
}
}
isRecording = false;
recordedKeys.clear();
inputElement?.blur();
};
const handleKeyDown = (e: KeyboardEvent) => {
if (!isRecording) return;
e.preventDefault();
e.stopPropagation();
const key = formatKeyForHotkey(e.key);
// Add modifiers
if (e.ctrlKey) recordedKeys.add('ctrl');
if (e.metaKey) recordedKeys.add('cmd');
if (e.altKey) recordedKeys.add('alt');
if (e.shiftKey) recordedKeys.add('shift');
// Add the main key (ignore modifier keys themselves)
if (!['ctrl', 'cmd', 'alt', 'shift'].includes(key)) {
recordedKeys.add(key);
}
// Auto-stop recording if we have a main key
if (!['ctrl', 'cmd', 'alt', 'shift'].includes(key)) {
setTimeout(stopRecording, 100);
}
};
const handleKeyUp = (e: KeyboardEvent) => {
if (!isRecording) return;
e.preventDefault();
e.stopPropagation();
};
const handleBlur = () => {
if (isRecording) {
stopRecording();
}
};
$effect(() => {
if (isRecording && inputElement) {
inputElement.focus();
}
});
// Get the parts to display
const hotkeyParts = $derived(isRecording
? Array.from(recordedKeys).map(formatKeyForDisplay)
: getHotkeyParts(value).map(formatKeyForDisplay));
</script>
<div class="flex gap-2 items-center">
<div class="relative">
{#if isRecording}
<!-- Recording state -->
<div
class="flex items-center justify-center px-3 py-1.5 text-sm rounded-md dark:bg-[#38373D]/50 bg-[#DDDDDD]/50 border-[#DDDDDD]/30 dark:border-[#38373D]/30 dark:text-white border cursor-pointer text-nowrap"
onclick={startRecording}
onkeydown={startRecording}
role="button"
tabindex="0"
>
Press keys...
</div>
{:else if hotkeyParts.length > 0}
<!-- Display current hotkey -->
<div
class="flex gap-1 items-center text-sm rounded-md border-none cursor-pointer dark:text-white"
onclick={startRecording}
onkeydown={startRecording}
role="button"
tabindex="0"
>
{#each hotkeyParts as part}
<div class="size-8 text-sm flex items-center justify-center rounded-md border dark:bg-[#38373D]/50 bg-[#DDDDDD]/50 border-[#DDDDDD]/30 dark:border-[#38373D]/30">
{part}
</div>
{/each}
</div>
{:else}
<!-- Empty state -->
<div
class="flex items-center justify-center px-3 py-2 text-sm rounded-md dark:bg-[#38373D]/50 bg-[#DDDDDD] dark:text-white border-none cursor-pointer text-nowrap"
onclick={startRecording}
onkeydown={startRecording}
role="button"
tabindex="0"
>
<span class="text-gray-500 dark:text-gray-400">Click to set</span>
</div>
{/if}
<!-- Hidden input for focus management -->
<input
bind:this={inputElement}
type="text"
readonly
class="absolute inset-0 opacity-0 pointer-events-none"
onkeydown={handleKeyDown}
onkeyup={handleKeyUp}
onblur={handleBlur}
/>
</div>
</div>
<style>
input:focus {
outline: none;
}
</style>
+13 -17
View File
@@ -1,6 +1,6 @@
<script lang="ts">
import { onMount, onDestroy, createEventDispatcher } from 'svelte';
import { animate as motionAnimate, spring } from 'motion';
import { onMount, onDestroy } from 'svelte';
import { animate as motionAnimate } from 'motion';
let { initial, animate, exit, transition, children, class: className } = $props<{
initial?: any,
@@ -12,11 +12,9 @@
}>();
let divElement: HTMLElement;
const dispatch = createEventDispatcher();
const playAnimation = (keyframe: any) => {
if (divElement && keyframe) {
let animationOptions = transition;
let finalKeyframe = { ...keyframe };
if (finalKeyframe.height === 'auto') {
@@ -36,16 +34,18 @@
finalKeyframe.height = `${autoHeight}px`;
}
if (!transition || transition.type === 'spring') {
const springConfig = transition?.config || { stiffness: 250, damping: 25 };
animationOptions = {
...transition,
easing: spring(springConfig)
};
}
const defaultSpringConfig = { stiffness: 250, damping: 25 };
const animation = motionAnimate(divElement, finalKeyframe, animationOptions);
return animation.finished;
const animation = motionAnimate(
[divElement],
finalKeyframe,
{
type: 'spring',
stiffness: transition?.stiffness || defaultSpringConfig.stiffness,
damping: transition?.damping || defaultSpringConfig.damping
}
);
return animation;
}
return Promise.resolve();
};
@@ -57,16 +57,12 @@
} else if (animate) {
await playAnimation(animate);
}
dispatch('animationend');
});
$effect(() => {
if (animate) {
playAnimation(animate);
}
dispatch('animationend');
});
onDestroy(async () => {
+2 -1
View File
@@ -5,7 +5,8 @@
</script>
<button
aria-label="Color Picker Swatch"
onclick={onClick}
style="background: {$settingsState.selectedColor}"
class="w-16 h-8 rounded-md"
class="w-16 h-8 rounded-md shadow-2xl ring-[1px] ring-[#DDDDDD]/30 dark:ring-[#38373D]/30"
></button>
+72 -12
View File
@@ -8,15 +8,75 @@
let select: HTMLSelectElement;
</script>
<select
bind:this={select}
value={state}
onchange={() => onChange(select.value)}
class="px-4 py-1 text-[0.75rem] dark:bg-[#38373D] bg-[#DDDDDD] dark:text-white rounded-md w-full"
>
{#each options as option}
<option value={option.value}>
{option.label}
</option>
{/each}
</select>
<div class="select-wrapper relative w-full overflow-hidden rounded-2xl border shadow-2xl">
<select
bind:this={select}
value={state}
onchange={() => onChange(select.value)}
class="select-input w-full appearance-none border-none bg-transparent px-4 py-2.5 pr-10 text-[0.875rem] font-medium transition-colors"
>
{#each options as option}
<option value={option.value}>
{option.label}
</option>
{/each}
</select>
<span class="select-icon pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3" aria-hidden="true">
<svg viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4">
<path fill-rule="evenodd" d="M5.23 7.21a.75.75 0 0 1 1.06.02L10 11.168l3.71-3.938a.75.75 0 1 1 1.08 1.04l-4.25 4.5a.75.75 0 0 1-1.08 0l-4.25-4.5a.75.75 0 0 1 .02-1.06Z" clip-rule="evenodd"></path>
</svg>
</span>
</div>
<style>
.select-wrapper {
background: color-mix(in srgb, var(--background-primary) 88%, transparent);
border-color: color-mix(in srgb, var(--theme-offset-bg, var(--background-secondary)) 72%, transparent);
border-radius: 18px;
color: var(--text-primary);
transition:
background-color 180ms ease,
border-color 180ms ease,
box-shadow 180ms ease,
transform 180ms ease;
}
.select-wrapper:hover {
background: color-mix(in srgb, var(--background-primary) 94%, var(--background-secondary) 6%);
border-color: color-mix(in srgb, var(--theme-offset-bg, var(--background-secondary)) 88%, transparent);
}
.select-wrapper:focus-within {
background: color-mix(in srgb, var(--background-primary) 96%, var(--background-secondary) 4%);
border-color: color-mix(in srgb, var(--text-primary) 22%, var(--theme-offset-bg, var(--background-secondary)) 78%);
box-shadow: 0 0 0 1px color-mix(in srgb, var(--text-primary) 12%, transparent);
}
.select-input {
color: var(--text-primary);
outline: none;
text-overflow: ellipsis;
}
.select-input:hover,
.select-input:focus {
background: transparent;
}
.select-input option {
background: var(--background-primary);
color: var(--text-primary);
}
.select-icon {
color: color-mix(in srgb, var(--text-primary) 60%, transparent);
}
.select-input {
color-scheme: light;
}
:global(.dark) .select-input {
color-scheme: dark;
}
</style>
@@ -0,0 +1,79 @@
<script lang="ts">
import { fade } from "svelte/transition";
import { animate } from "motion";
import { onMount } from "svelte";
import { cloudAuth } from "@/seqta/utils/CloudAuth";
let { onClose } = $props<{ onClose: () => void }>();
let modalElement: HTMLElement;
onMount(() => {
return cloudAuth.subscribe((s) => {
if (s.isLoggedIn) onClose();
});
});
$effect(() => {
if (modalElement) {
animate(
modalElement,
{ scale: [0.9, 1], opacity: [0, 1] },
{ type: "spring", stiffness: 300, damping: 25 },
);
}
});
async function handleSignIn() {
await cloudAuth.startLogin();
}
</script>
<div
class="flex fixed inset-0 z-[99999] justify-center items-center bg-black/50"
onclick={(e) => {
if (e.target === e.currentTarget) onClose();
}}
onkeydown={(e) => {
if (e.key === "Escape") onClose();
}}
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 max-h-[90vh] overflow-y-auto bg-white rounded-2xl shadow-2xl dark:bg-zinc-800 dark:text-white"
onclick={(e) => e.stopPropagation()}
onkeydown={(e) => e.stopPropagation()}
>
<h2 class="mb-3 text-xl font-bold text-zinc-900 dark:text-white">
Sign in to favorite themes
</h2>
<p class="mb-4 text-sm text-zinc-600 dark:text-zinc-400">
Sign in to the Theme Store to save favorites across devices, or create an account to get started.
</p>
<button
type="button"
onclick={handleSignIn}
class="w-full px-4 py-2.5 text-sm font-medium rounded-lg bg-zinc-800 dark:bg-zinc-200 text-white dark:text-zinc-900 hover:bg-zinc-700 dark:hover:bg-zinc-300 transition-colors duration-200"
>
Sign in with BetterSEQTA Cloud
</button>
<p class="mt-2 text-xs text-center text-zinc-400 dark:text-zinc-500">
Opens accounts.betterseqta.org in a new tab
</p>
<div class="flex justify-end mt-4">
<button
type="button"
onclick={onClose}
class="px-4 py-2 text-sm font-medium rounded-lg bg-zinc-200 dark:bg-zinc-700 text-zinc-700 dark:text-zinc-200 hover:bg-zinc-300 dark:hover:bg-zinc-600 transition-colors duration-200"
>
Close
</button>
</div>
</div>
</div>
+15 -7
View File
@@ -1,17 +1,24 @@
<script lang="ts">
let { state, onChange } = $props<{ state: number, onChange: (value: number) => void }>();
let percentage = $derived((state / 100) * 100);
let { state, onChange, min = 0, max = 100, step = 1 } = $props<{
state: number,
onChange: (value: number) => void,
min?: number,
max?: number,
step?: number
}>();
let percentage = $derived(((state - min) / (max - min)) * 100);
</script>
<div class="relative w-full max-w-lg mx-auto">
<div class="relative w-full min-w-0">
<input
type="range"
min="0"
max="100"
min={min}
max={max}
step={step}
bind:value={state}
style={`background: linear-gradient(to right, #30D259 ${percentage}%, #dddddd ${percentage}%)`}
style={`background: linear-gradient(to right, #30d259ad 0%, #30D259 ${percentage}%, #dddddd ${percentage}%)`}
onchange={(e) => onChange(Number(e.currentTarget.value))}
class="w-full h-1 rounded-full appearance-none cursor-pointer dark:bg-[#38373D] bg-[#DDDDDD] slider"
class="w-full h-1 rounded-full appearance-none cursor-pointer slider"
/>
</div>
@@ -31,6 +38,7 @@
height: 24px;
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.3);
background: white;
color: #30d259ad;
cursor: pointer;
border-radius: 50%;
}
+2 -2
View File
@@ -1,4 +1,4 @@
.dark .switch[data-ison="true"],
.switch[data-ison="true"] {
background-color: #30D259;
}
background-color: #30d259;
}
+6 -11
View File
@@ -1,5 +1,5 @@
<script lang="ts">
import { animate, spring } from 'motion';
import { animate } from 'motion';
import { standalone } from '../utils/standalone.svelte'
let { state, onChange } = $props<{ state: boolean, onChange: (newState: boolean) => void }>();
@@ -18,7 +18,9 @@
x: enabled ? (standalone.standalone ? 24 : 20) : 0,
},
{
easing: spring(springParams),
type: 'spring',
stiffness: springParams.stiffness,
damping: springParams.damping,
}
);
};
@@ -28,8 +30,7 @@
</script>
<div
class="flex w-14 p-1 cursor-pointer transition-all duration-150 rounded-full dark:bg-[#38373D] bg-[#DDDDDD] switch select-none"
data-ison={state}
class="flex w-14 p-1 cursor-pointer transition-all duration-150 rounded-full bg-gradient-to-tr select-none shadow-2xl ring-[1px] ring-[#DDDDDD]/30 dark:ring-[#38373D]/30 {state ? 'to-[#30D259]/80 from-[#30D259] dark:from-[#30D259]/40 dark:to-[#30D259]' : 'dark:from-[#38373D]/50 dark:to-[#38373D] to-[#DDDDDD]/50 from-[#DDDDDD]'}"
onclick={() => onChange(!state)}
onkeydown={(e) => e.key === "Enter" && onChange(!state)}
role="switch"
@@ -40,10 +41,4 @@
bind:this={handle}
class="w-6 h-6 bg-white dark:bg-[#FEFEFE] rounded-full drop-shadow-md"
></div>
</div>
<style>
.switch[data-ison="true"] {
background-color: #30D259;
}
</style>
</div>
+1 -1
View File
@@ -1,3 +1,3 @@
.tab-width {
width: var(--tab-width);
}
}
+10 -17
View File
@@ -3,9 +3,7 @@
import './TabbedContainer.css';
import { onMount } from 'svelte';
let { tabs } = $props<{ tabs: { title: string, Content: any, props?: any }[] }>();
let activeTab = $state(0);
let hoveredTab = $state<number | null>(null);
let { tabs, activeTab = $bindable(0) } = $props<{ tabs: { title: string, Content: any, props?: any }[]; activeTab?: number }>();
let containerRef: HTMLElement | null = null;
let tabWidth = $state(0);
@@ -24,10 +22,6 @@
return 0;
};
$effect(() => {
calcXPos(hoveredTab);
});
onMount(() => {
updateTabWidth();
@@ -45,26 +39,24 @@
</script>
<div class="flex flex-col h-full">
<div bind:this={containerRef} class="top-0 z-10 text-[0.875rem] pb-0.5 mx-4 tab-width-container">
<div class="relative flex">
<div class="top-0 z-10 text-[0.875rem] pb-0.5 mx-4 px-2 tab-width-container">
<div bind:this={containerRef} class="flex relative">
<MotionDiv
class="absolute top-0 left-0 z-0 h-full bg-[#DDDDDD] dark:bg-[#38373D] rounded-full opacity-40 tab-width"
animate={{ x: calcXPos(hoveredTab) }}
class="absolute top-0 left-0 z-0 h-full bg-gradient-to-tr dark:from-[#38373D]/80 dark:to-[#38373D] from-[#DDDDDD]/80 to-[#DDDDDD] rounded-full opacity-40 tab-width"
animate={{ x: calcXPos(activeTab) }}
transition={springTransition}
/>
{#each tabs as { title }, index}
<button
class="relative z-10 flex-1 px-4 py-2 focus-visible:outline-none"
onclick={() => activeTab = index}
onmouseenter={() => hoveredTab = index}
onmouseleave={() => hoveredTab = null}
>
{title}
</button>
{/each}
</div>
</div>
<div class="h-full px-4 overflow-hidden">
<div class="overflow-hidden px-4 h-full">
<MotionDiv
class="h-full"
animate={{ x: `${-activeTab * 100}%` }}
@@ -72,12 +64,13 @@
>
<div class="flex">
{#each tabs as { Content, props }, index}
<div class="absolute focus:outline-none w-full transition-opacity duration-300 overflow-y-scroll no-scrollbar h-full tab {activeTab === index ? 'opacity-100 active' : 'opacity-0'}"
style="left: {index * 100}%;">
<div class="absolute focus:outline-none w-full pt-2 transition-opacity duration-300 overflow-y-scroll no-scrollbar pb-2 h-full tab {activeTab === index ? 'opacity-100 active' : 'opacity-0'}"
style="left: {index * 100}%;">
<div style="left: {index * 100}%;" class="fixed top-0 w-full h-8 bg-gradient-to-b to-transparent pointer-events-none z-[100] from-white dark:from-zinc-800 dark:to-transparent"></div>
<Content {...props} />
</div>
{/each}
</div>
</MotionDiv>
</div>
</div>
</div>
@@ -0,0 +1,311 @@
/* Font picker — analytics design tokens & components */
.bsplus-font-picker-overlay {
position: fixed;
inset: 0;
z-index: 50000;
display: flex;
align-items: center;
justify-content: center;
padding: 1.25rem;
cursor: pointer;
background: color-mix(in srgb, #000 52%, transparent);
backdrop-filter: blur(6px);
-webkit-backdrop-filter: blur(6px);
}
.bsplus-font-picker-root {
--bsplus-analytics-radius: 16px;
--bsplus-analytics-radius-sm: 12px;
--bsplus-analytics-ease: cubic-bezier(0.4, 0, 0.2, 1);
--bsplus-analytics-surface: var(--background-primary, #ffffff);
--bsplus-analytics-surface-2: var(--background-secondary, #f8fafc);
--bsplus-analytics-text: var(--text-primary, #1a1a1a);
--bsplus-analytics-muted: color-mix(
in srgb,
var(--bsplus-analytics-text) 55%,
transparent
);
--bsplus-analytics-border: color-mix(
in srgb,
var(--theme-offset-bg, var(--background-secondary, #e2e8f0)) 78%,
transparent
);
--bsplus-analytics-shadow: 0 5px 16px 6px rgba(0, 0, 0, 0.12);
--bsplus-analytics-shadow-hover: 0 8px 24px 8px rgba(0, 0, 0, 0.16);
--bsplus-analytics-accent: var(--better-main, #007bff);
font-family: Rubik, system-ui, sans-serif;
font-size: 0.875rem;
color: var(--bsplus-analytics-text);
}
.bsplus-font-picker-root.dark {
--bsplus-analytics-shadow: 0 5px 20px 6px rgba(0, 0, 0, 0.45);
--bsplus-analytics-shadow-hover: 0 10px 28px 10px rgba(0, 0, 0, 0.55);
}
@keyframes bsplus-font-picker-in {
from {
opacity: 0;
transform: translateY(18px) scale(0.985);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
.bsplus-font-picker-dialog {
width: min(100%, 22rem);
max-height: min(88vh, 820px);
display: flex;
flex-direction: column;
overflow: hidden;
cursor: auto;
border-radius: var(--bsplus-analytics-radius);
background: var(--bsplus-analytics-surface);
border: 1px solid var(--bsplus-analytics-border);
box-shadow: var(--bsplus-analytics-shadow-hover);
animation: bsplus-font-picker-in 0.45s var(--bsplus-analytics-ease) forwards;
}
@media (min-width: 640px) {
.bsplus-font-picker-dialog {
width: min(92vw, 22rem);
}
}
@media (prefers-reduced-motion: reduce) {
.bsplus-font-picker-dialog {
animation: none;
}
}
.bsplus-font-picker-header {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 0.85rem;
flex-shrink: 0;
padding: 1.15rem 1.25rem;
border-bottom: 1px solid var(--bsplus-analytics-border);
}
.bsplus-font-picker-header-actions {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: flex-end;
gap: 0.5rem;
flex-wrap: wrap;
}
.bsplus-font-picker-header-text {
min-width: 0;
}
.bsplus-font-picker-title {
margin: 0;
font-size: 1.1rem;
font-weight: 700;
color: var(--bsplus-analytics-text);
}
.bsplus-font-picker-desc {
margin: 0.35rem 0 0;
font-size: 0.8125rem;
color: var(--bsplus-analytics-muted);
line-height: 1.5;
}
.bsplus-font-picker-reset {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.65rem 1rem;
border-radius: var(--bsplus-analytics-radius-sm);
border: 2px solid var(--bsplus-analytics-border);
background: transparent;
font-family: inherit;
font-size: 0.8125rem;
font-weight: 600;
line-height: 1.2;
cursor: pointer;
color: var(--bsplus-analytics-text);
transition:
transform 0.2s var(--bsplus-analytics-ease),
background 0.2s var(--bsplus-analytics-ease),
border-color 0.2s var(--bsplus-analytics-ease),
opacity 0.2s ease;
}
.bsplus-font-picker-reset:hover:not(:disabled) {
transform: scale(1.02);
background: color-mix(
in srgb,
var(--bsplus-analytics-surface-2) 80%,
transparent
);
}
.bsplus-font-picker-reset:active:not(:disabled) {
transform: scale(0.98);
}
.bsplus-font-picker-reset:focus-visible {
outline: none;
box-shadow: 0 0 0 3px
color-mix(in srgb, var(--bsplus-analytics-accent) 35%, transparent);
}
.bsplus-font-picker-reset:disabled {
opacity: 0.45;
cursor: not-allowed;
transform: none;
}
.bsplus-font-picker-done {
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
padding: 0.65rem 1.25rem;
border-radius: var(--bsplus-analytics-radius-sm);
border: none;
font-family: inherit;
font-size: 0.875rem;
font-weight: 600;
line-height: 1.2;
cursor: pointer;
background: var(--bsplus-analytics-accent);
color: var(--text-color, #ffffff);
box-shadow: 0 2px 8px
color-mix(in srgb, var(--bsplus-analytics-accent) 40%, transparent);
transition:
transform 0.2s var(--bsplus-analytics-ease),
box-shadow 0.2s var(--bsplus-analytics-ease);
}
.bsplus-font-picker-done:hover {
transform: scale(1.03);
box-shadow: 0 4px 14px
color-mix(in srgb, var(--bsplus-analytics-accent) 45%, transparent);
}
.bsplus-font-picker-done:active {
transform: scale(0.97);
}
.bsplus-font-picker-done:focus-visible {
outline: none;
box-shadow: 0 0 0 3px
color-mix(in srgb, var(--bsplus-analytics-accent) 35%, transparent);
}
.bsplus-font-picker-list {
flex: 1;
min-height: 0;
overflow-y: auto;
overscroll-behavior: contain;
padding: 1rem 1rem 1.25rem;
display: flex;
flex-direction: column;
gap: 0.65rem;
scrollbar-width: thin;
scrollbar-color: color-mix(in srgb, var(--bsplus-analytics-accent) 35%, transparent)
transparent;
}
.bsplus-font-picker-list::-webkit-scrollbar {
width: 8px;
}
.bsplus-font-picker-list::-webkit-scrollbar-thumb {
border-radius: 999px;
background: color-mix(in srgb, var(--bsplus-analytics-accent) 35%, transparent);
}
.bsplus-font-picker-option {
width: 100%;
padding: 0.9rem 1rem;
text-align: left;
border-radius: var(--bsplus-analytics-radius-sm);
border: 1px solid var(--bsplus-analytics-border);
background: var(--bsplus-analytics-surface);
box-shadow: var(--bsplus-analytics-shadow);
cursor: pointer;
font-family: Rubik, system-ui, sans-serif;
flex-shrink: 0;
transition:
transform 0.25s var(--bsplus-analytics-ease),
box-shadow 0.25s var(--bsplus-analytics-ease),
border-color 0.2s ease,
background 0.2s ease;
}
.bsplus-font-picker-option:hover {
transform: translateY(-2px);
box-shadow: var(--bsplus-analytics-shadow-hover);
background: color-mix(
in srgb,
var(--bsplus-analytics-surface-2) 55%,
var(--bsplus-analytics-surface)
);
}
.bsplus-font-picker-option:focus-visible {
outline: none;
box-shadow:
var(--bsplus-analytics-shadow-hover),
0 0 0 3px color-mix(in srgb, var(--bsplus-analytics-accent) 30%, transparent);
}
.bsplus-font-picker-option.is-selected {
border-color: color-mix(
in srgb,
var(--bsplus-analytics-accent) 45%,
var(--bsplus-analytics-border)
);
background: color-mix(
in srgb,
var(--bsplus-analytics-accent) 10%,
var(--bsplus-analytics-surface)
);
box-shadow: 0 4px 16px
color-mix(in srgb, var(--bsplus-analytics-accent) 22%, transparent);
}
.bsplus-font-picker-option-head {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
gap: 0.75rem;
margin-bottom: 0;
}
.bsplus-font-picker-root .bsplus-font-picker-option-name {
font-size: 1rem;
font-weight: 700;
color: var(--bsplus-analytics-text);
text-align: left;
min-width: 0;
}
.bsplus-font-picker-badge {
display: inline-flex;
align-items: center;
flex-shrink: 0;
margin-left: auto;
padding: 0.2rem 0.65rem;
border-radius: 999px;
font-size: 0.7rem;
font-weight: 600;
background: color-mix(
in srgb,
var(--bsplus-analytics-accent) 18%,
transparent
);
color: var(--bsplus-analytics-accent);
}
@@ -1,10 +1,12 @@
<script lang="ts">
import { hasEnoughStorageSpace, isIndexedDBSupported, writeData, openDatabase, readAllData, deleteData } from '@/interface/hooks/BackgroundDataLoader';
import { setTheme } from '@/seqta/ui/themes/setTheme';
import Spinner from '../Spinner.svelte';
import { settingsState } from '@/seqta/utils/listeners/SettingsState'
import Fuse from 'fuse.js';
import { Index } from 'flexsearch';
import { backgroundUpdates } from '@/interface/hooks/BackgroundUpdates'
import { ThemeManager } from '@/plugins/built-in/themes/theme-manager'
const themeManager = ThemeManager.getInstance();
type Background = { id: string; category: string; type: string; lowResUrl: string; highResUrl: string; name: string; description: string; featured?: boolean };
let { searchTerm } = $props<{ searchTerm: string }>();
@@ -18,19 +20,12 @@
let savedBackgrounds = $state<string[]>([]);
let installingBackgrounds = $state<Set<string>>(new Set());
let debugInfo = $state<string>('');
let searchIndex = $state<Index | null>(null);
// New state variables
let activeTab = $state<'all' | 'installed' | 'photos' | 'videos'>('all');
let sortBy = $state<'newest' | 'popular' | 'name'>('newest');
// Add Fuse.js options
const fuseOptions = {
keys: ['name', 'description'],
threshold: 0.4,
ignoreLocation: true
};
let fuse: Fuse<Background>;
// Existing functions
const loadStore = async () => {
try {
@@ -41,7 +36,19 @@
}
const data = await response.json();
backgrounds = data.backgrounds;
fuse = new Fuse(backgrounds, fuseOptions);
// Initialize FlexSearch index
const index = new Index({
tokenize: "forward",
preset: "score"
});
// Add backgrounds to the index
backgrounds.forEach((bg, i) => {
index.add(i, bg.name + " " + bg.description);
});
searchIndex = index;
debugInfo = `Loaded ${backgrounds.length} backgrounds`;
await loadSavedBackgrounds();
} catch (e) {
@@ -72,14 +79,10 @@
let filteredBackgrounds = $derived((() => {
let filtered = backgrounds;
// Use Fuse.js search if there's a search term
if (searchTerm.trim()) {
// @ts-ignore
if (fuse) {
filtered = fuse.search(searchTerm).map((result: any) => result.item) ?? [];
} else {
filtered = backgrounds.filter(bg => bg.name.toLowerCase().includes(searchTerm.toLowerCase()));
}
// Use FlexSearch if there's a search term
if (searchTerm.trim() && searchIndex) {
const results = searchIndex.search(searchTerm) as number[];
filtered = results.map(i => backgrounds[i]);
}
// Apply category filtering
@@ -170,13 +173,13 @@
function selectNoBackground() {
selectedBackground = null;
setTheme('');
themeManager.setTheme('');
}
</script>
<div class="flex h-full">
<!-- Sidebar -->
<div class="w-64 h-full p-4 border-r border-zinc-200 dark:border-zinc-700">
<div class="p-4 w-64 h-full border-r border-zinc-200 dark:border-zinc-700">
<div class="mb-8">
<h2 class="mb-4 text-lg font-semibold">Categories</h2>
<nav class="space-y-2">
@@ -208,15 +211,15 @@
</div>
<!-- Main Content -->
<div class="flex-1 overflow-auto">
<div class="overflow-auto flex-1">
<!-- Header -->
<div class="sticky top-0 z-10 p-4 border-b bg-[#F1F1F3] dark:bg-zinc-900 dark:border-zinc-700">
<div class="flex items-center justify-between mb-4">
<div class="flex justify-between items-center mb-4">
<h1 class="text-2xl font-bold">Explore Backgrounds {searchTerm ? `- "${searchTerm}"` : ''}</h1>
<div class="flex items-center gap-4">
<div class="flex gap-4 items-center">
<select
bind:value={sortBy}
class="p-2 border rounded-lg border-zinc-200 dark:border-zinc-700 dark:bg-zinc-800"
class="p-2 rounded-lg border border-zinc-200 dark:border-zinc-700 dark:bg-zinc-800"
>
<option value="newest">Newest</option>
<option value="name">Name</option>
@@ -230,7 +233,7 @@
<button
class={`px-4 py-2 text-sm font-medium transition-colors rounded-full
${activeTab === tab.toLowerCase() ? 'bg-zinc-100 dark:bg-zinc-800 hover:bg-zinc-200 dark:hover:bg-zinc-700' :
'bg-zinc-100 dark:bg-transparent dark:outline dark:outline-1 dark:outline-zinc-700 hover:bg-zinc-200 dark:hover:bg-zinc-700/20'}`}
'bg-zinc-100 dark:bg-transparent dark:outline dark:outline-zinc-700 hover:bg-zinc-200 dark:hover:bg-zinc-700/20'}`}
onclick={() => activeTab = tab.toLowerCase() as typeof activeTab}
>
{tab}
@@ -244,15 +247,15 @@
{#if isLoading}
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
{#each Array(9) as _}
<div class="relative overflow-hidden rounded-lg animate-pulse">
<div class="overflow-hidden relative rounded-lg animate-pulse">
<!-- Image placeholder -->
<div class="w-full h-48 bg-zinc-200 dark:bg-zinc-800"></div>
<!-- Gradient overlay -->
<div class="absolute bottom-0 left-0 right-0 h-16 bg-gradient-to-t from-zinc-300 dark:from-zinc-700 to-transparent">
<div class="absolute right-0 bottom-0 left-0 h-16 to-transparent bg-linear-to-t from-zinc-300 dark:from-zinc-700">
<!-- Title placeholder -->
<div class="absolute bottom-2 left-2 right-2">
<div class="absolute right-2 bottom-2 left-2">
<div class="w-2/3 h-4 rounded-full bg-zinc-200 dark:bg-zinc-800"></div>
<div class="w-1/2 h-3 mt-2 rounded-full bg-zinc-200 dark:bg-zinc-800"></div>
<div class="mt-2 w-1/2 h-3 rounded-full bg-zinc-200 dark:bg-zinc-800"></div>
</div>
</div>
</div>
@@ -271,7 +274,7 @@
return true;
}) as background (background.id)}
<div
class="relative overflow-hidden rounded-lg shadow-lg cursor-pointer group"
class="overflow-hidden relative rounded-lg shadow-lg cursor-pointer group"
onclick={() => toggleBackgroundInstallation(background)}
onkeydown={(event) => {
if (event.key === 'Enter' || event.key === ' ') {
@@ -286,7 +289,7 @@
{:else}
<video src={background.lowResUrl} class="object-cover w-full h-48" muted loop autoplay></video>
{/if}
<div class="absolute inset-0 flex items-center justify-center transition-opacity duration-300 bg-black bg-opacity-50 opacity-0 group-hover:opacity-100">
<div class={`flex absolute inset-0 justify-center items-center opacity-0 transition-opacity duration-300 bg-black/50 group-hover:opacity-100 ${installingBackgrounds.has(background.id) ? 'opacity-100' : ''}`}>
{#if installingBackgrounds.has(background.id)}
<Spinner />
{:else if savedBackgrounds.includes(background.id)}
@@ -0,0 +1,161 @@
<script lang="ts">
import { onMount } from "svelte";
import { cloudAuth } from "@/seqta/utils/CloudAuth";
let { alwaysShowUserName = false, onClick = undefined } = $props<{
alwaysShowUserName?: boolean;
onClick?: () => void;
}>();
let cloudState = $state(cloudAuth.state);
let open = $state(false);
let dropdownEl: HTMLElement;
onMount(() => {
const unsubscribe = cloudAuth.subscribe((state) => {
cloudState = state;
});
return unsubscribe;
});
function handleClickOutside(e: MouseEvent) {
if (dropdownEl && !dropdownEl.contains(e.target as Node)) {
open = false;
}
}
$effect(() => {
if (open) {
const timer = setTimeout(() => {
document.addEventListener("click", handleClickOutside);
}, 0);
return () => {
clearTimeout(timer);
document.removeEventListener("click", handleClickOutside);
};
}
});
async function handleLogout() {
await cloudAuth.logout();
open = false;
}
async function handleSignIn() {
await cloudAuth.startLogin();
open = false;
}
function handleButtonClick() {
if (onClick) {
onClick();
} else {
open = !open;
}
}
function getInitials(): string {
const u = cloudState.user;
if (!u) return "?";
if (u.displayName) return u.displayName.slice(0, 2).toUpperCase();
if (u.username) return u.username.slice(0, 2).toUpperCase();
if (u.email) return u.email.slice(0, 2).toUpperCase();
return "?";
}
</script>
<div class="relative flex items-center" bind:this={dropdownEl}>
<button
type="button"
onclick={handleButtonClick}
class="flex items-center gap-2 px-3 py-1.5 text-[0.75rem] rounded-lg shadow-2xl border dark:bg-[#38373D]/50 bg-[#DDDDDD]/50 border-[#DDDDDD]/30 dark:border-[#38373D]/30 dark:text-white transition-colors duration-200"
>
{#if cloudState.isLoggedIn}
{#if cloudState.user?.pfpUrl}
<img
src={cloudState.user.pfpUrl}
alt=""
class="w-5 h-5 rounded-full object-cover ring-1 ring-zinc-200 dark:ring-zinc-600"
/>
{:else}
<div class="flex items-center justify-center w-5 h-5 rounded-full bg-zinc-300 dark:bg-zinc-600 text-zinc-700 dark:text-zinc-200 font-semibold text-[0.6rem]">
{getInitials()}
</div>
{/if}
<span
class={alwaysShowUserName
? "inline max-w-[10rem] truncate text-[0.75rem]"
: "hidden max-w-24 truncate sm:inline text-[0.75rem]"}
>
{cloudState.user?.displayName || cloudState.user?.username || cloudState.user?.email || "User"}
</span>
{:else}
<span class="text-sm font-IconFamily" aria-hidden="true">{'\ued53'}</span>
<span class="text-[0.75rem]">Sign in</span>
{/if}
</button>
{#if !onClick && open}
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
class="absolute right-0 top-full mt-2 w-80 rounded-xl border border-zinc-200 dark:border-zinc-600 bg-white dark:bg-zinc-800 shadow-xl z-[100] overflow-hidden"
onclick={(e) => e.stopPropagation()}
>
<div class="p-4 border-b border-zinc-200 dark:border-zinc-600">
<h3 class="text-xl font-bold text-zinc-900 dark:text-white">BetterSEQTA Cloud</h3>
<p class="text-base text-zinc-500 dark:text-zinc-400">Sync favorites across devices</p>
</div>
<div class="p-4">
{#if cloudState.isLoggedIn}
<div class="flex flex-col gap-3">
<div class="flex items-center gap-3">
{#if cloudState.user?.pfpUrl}
<img
src={cloudState.user.pfpUrl}
alt=""
class="w-12 h-12 rounded-full object-cover ring-2 ring-zinc-200 dark:ring-zinc-600"
/>
{:else}
<div class="flex items-center justify-center w-12 h-12 rounded-full bg-zinc-300 dark:bg-zinc-600 text-zinc-700 dark:text-zinc-200 font-semibold text-base">
{getInitials()}
</div>
{/if}
<div class="min-w-0 flex-1">
<p class="text-base font-medium text-zinc-900 dark:text-white truncate">
{cloudState.user?.displayName || cloudState.user?.username || cloudState.user?.email || "User"}
</p>
{#if cloudState.user?.email && cloudState.user?.email !== (cloudState.user?.displayName || cloudState.user?.username)}
<p class="text-base text-zinc-500 dark:text-zinc-400 truncate">{cloudState.user.email}</p>
{/if}
</div>
</div>
<button
type="button"
onclick={handleLogout}
class="w-full px-4 py-3 text-base font-medium rounded-lg bg-zinc-200 dark:bg-zinc-700 text-zinc-900 dark:text-white hover:bg-zinc-300 dark:hover:bg-zinc-600 transition-colors duration-200"
>
Sign out
</button>
</div>
{:else}
<div class="flex flex-col gap-3">
<p class="text-sm text-zinc-600 dark:text-zinc-400">
Sign in to sync favorites across devices.
</p>
<button
type="button"
onclick={handleSignIn}
class="w-full px-4 py-3 text-base font-medium rounded-lg bg-zinc-800 dark:bg-zinc-200 text-white dark:text-zinc-900 hover:bg-zinc-700 dark:hover:bg-zinc-300 transition-colors duration-200"
>
Sign in with BetterSEQTA Cloud
</button>
<p class="text-xs text-center text-zinc-400 dark:text-zinc-500">
Opens accounts.betterseqta.org in a new tab
</p>
</div>
{/if}
</div>
</div>
{/if}
</div>
@@ -0,0 +1,111 @@
<script lang="ts">
import { cloudAuth } from "@/seqta/utils/CloudAuth";
let {
introText,
onSuccess,
compact = false,
} = $props<{
introText?: string;
onSuccess?: () => void;
/** Smaller padding/text for overlays (e.g. SignInToFavoriteModal) */
compact?: boolean;
}>();
let username = $state("");
let password = $state("");
let loading = $state(false);
let error = $state<string | null>(null);
const inputClass = $derived(
compact
? "w-full px-4 py-2 text-sm rounded-lg bg-zinc-100 dark:bg-zinc-800 dark:text-white border border-zinc-200 dark:border-zinc-600 focus:outline-none focus:ring-2 focus:ring-accent-ring focus:border-transparent transition-colors duration-200"
: "w-full px-4 py-3 text-base rounded-lg bg-zinc-100 dark:bg-zinc-800 dark:text-white border border-zinc-200 dark:border-zinc-600 focus:outline-none focus:ring-2 focus:ring-accent-ring focus:border-transparent transition-colors duration-200",
);
const btnClass = $derived(
compact
? "w-full px-4 py-2 text-sm font-medium rounded-lg bg-zinc-800 dark:bg-zinc-200 text-white dark:text-zinc-900 hover:bg-zinc-700 dark:hover:bg-zinc-300 disabled:opacity-50 transition-colors duration-200"
: "w-full px-4 py-3 text-base font-medium rounded-lg bg-zinc-800 dark:bg-zinc-200 text-white dark:text-zinc-900 hover:bg-zinc-700 dark:hover:bg-zinc-300 disabled:opacity-50 transition-colors duration-200",
);
const linkClass = $derived(
compact
? "inline-flex items-center justify-center gap-2 px-4 py-2 text-sm font-medium rounded-lg border border-zinc-200 dark:border-zinc-600 text-zinc-700 dark:text-zinc-300 hover:bg-zinc-100 dark:hover:bg-zinc-800 transition-all duration-200"
: "inline-flex items-center justify-center gap-2 px-4 py-3 text-base font-medium rounded-lg border border-zinc-200 dark:border-zinc-600 text-zinc-700 dark:text-zinc-300 hover:bg-zinc-100 dark:hover:bg-zinc-800 transition-all duration-200",
);
async function handleLogin() {
if (loading) return;
error = null;
if (!username.trim() || !password) {
error = "Please enter username and password";
return;
}
loading = true;
try {
const result = await cloudAuth.login(username.trim(), password);
if (result.success) {
password = "";
onSuccess?.();
} else {
error = result.error ?? "Login failed";
}
} finally {
loading = false;
}
}
</script>
{#if introText}
<p
class="mb-4 text-zinc-600 dark:text-zinc-400 {compact ? 'text-sm' : 'text-base'}"
>
{introText}
</p>
{/if}
<form
class="flex flex-col gap-3"
autocomplete="off"
onsubmit={(e) => {
e.preventDefault();
handleLogin();
}}
>
<input
type="text"
name="betterseqta-cloud-username"
autocomplete="off"
placeholder="Email or username"
bind:value={username}
disabled={loading}
readonly
onfocus={(e) => e.currentTarget.removeAttribute("readonly")}
class={inputClass}
/>
<input
type="password"
name="betterseqta-cloud-password"
autocomplete="new-password"
placeholder="Password"
bind:value={password}
disabled={loading}
readonly
onfocus={(e) => e.currentTarget.removeAttribute("readonly")}
class={inputClass}
/>
{#if error}
<p class="text-red-600 dark:text-red-400 {compact ? 'text-sm' : 'text-base'}">{error}</p>
{/if}
<button type="submit" disabled={loading} class={btnClass}>
{loading ? "Signing in..." : "Sign in"}
</button>
<a
href="https://accounts.betterseqta.org/register"
target="_blank"
rel="noopener noreferrer"
class={linkClass}
>
Create account
</a>
</form>
@@ -1,20 +1,27 @@
<script lang="ts">
import { fade } from 'svelte/transition';
import type { Theme } from '@/interface/types/Theme';
import type { ThemeCoverSlide } from '@/interface/types/Theme';
import emblaCarouselSvelte from 'embla-carousel-svelte';
import Autoplay from 'embla-carousel-autoplay';
let { coverThemes, setDisplayTheme } = $props<{ coverThemes: Theme[], setDisplayTheme: (theme: Theme) => void }>();
let { slides, setDisplayTheme } = $props<{
slides: ThemeCoverSlide[];
setDisplayTheme: (theme: import('@/interface/types/Theme').Theme) => void;
}>();
let emblaApi = $state();
const options = { loop: true };
const plugins = [
Autoplay({
delay: 5000,
stopOnInteraction: false,
stopOnMouseEnter: true
})
];
const options = $derived({ loop: slides.length > 1 });
const plugins = $derived(
slides.length > 1
? [
Autoplay({
delay: 5000,
stopOnInteraction: false,
stopOnMouseEnter: true,
}),
]
: [],
);
function onInit(event: CustomEvent) {
emblaApi = event.detail;
@@ -26,41 +33,77 @@
const slideNext = () => emblaApi?.scrollNext();
</script>
{#if coverThemes.length > 0}
<div class="relative w-full transition-opacity rounded-xl overflow-clip" transition:fade>
<div
class="w-full aspect-[8/3]"
use:emblaCarouselSvelte={{ options, plugins }}
{#if slides.length > 0}
<div class="relative w-full overflow-clip rounded-xl transition-opacity" transition:fade>
<div
class="w-full aspect-[5/1] max-h-[500px]"
use:emblaCarouselSvelte={{ options, plugins }}
onemblaInit={onInit}
>
<div class="flex">
{#each coverThemes as theme}
{#each slides as slide (slide.imageUrl + slide.title + (slide.subtitle ?? ''))}
<div
class="relative flex-[0_0_100%] cursor-pointer rounded-xl overflow-clip"
role="button"
tabindex="0"
onkeydown={(e) => { if (e.key === 'Enter') setDisplayTheme(theme) }}
onclick={() => setDisplayTheme(theme)}
onkeydown={(e) => {
if (e.key === 'Enter') setDisplayTheme(slide.openTheme);
}}
onclick={() => setDisplayTheme(slide.openTheme)}
>
<img src={theme.marqueeImage} alt="Theme Preview" class="object-cover w-full h-full" />
<div class='absolute bottom-0 left-0 p-8 z-[1]'>
<h2 class='text-4xl font-bold text-white'>{theme.name}</h2>
<p class='text-lg text-white'>{theme.description}</p>
<img src={slide.imageUrl} alt="" class="object-cover w-full h-full" />
{#if slide.badgeFeatured === true}
<div class="absolute top-4 left-4 z-[2] pointer-events-none">
<span
class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-semibold bg-amber-100 text-amber-900 dark:bg-amber-950 dark:text-amber-100 shadow-sm"
aria-label="Featured theme"
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-3.5 h-3.5">
<path
fill-rule="evenodd"
d="M10.788 3.21c.448-1.077 1.976-1.077 2.424 0l2.082 5.006 5.404.434c1.164.093 1.636 1.545.749 2.305l-4.117 3.527 1.257 5.273c.271 1.136-.964 2.033-1.96 1.425L12 18.354 7.373 21.18c-.996.608-2.231-.29-1.96-1.425l1.257-5.273-4.117-3.527c-.887-.76-.415-2.212.749-2.305l5.404-.434 2.082-5.005Z"
clip-rule="evenodd"
/>
</svg>
Featured
</span>
</div>
{/if}
<div class="absolute bottom-0 left-0 p-8 z-[1]">
<h2 class="text-4xl font-bold text-white">{slide.title}</h2>
{#if slide.subtitle}
<p class="text-lg font-medium text-white/95 mt-1 line-clamp-2">{slide.subtitle}</p>
{/if}
{#if slide.openTheme.author && !slide.subtitle}
<p class="text-sm text-white/90 mt-1 mb-1 line-clamp-1">By {slide.openTheme.author}</p>
{/if}
{#if slide.openTheme.description && !slide.subtitle}
<p class="text-lg text-white line-clamp-3">{slide.openTheme.description}</p>
{/if}
</div>
<div class='absolute bottom-0 left-0 w-full h-1/2 bg-gradient-to-t from-black/80 to-transparent'></div>
<div class="absolute bottom-0 left-0 w-full h-1/2 to-transparent bg-linear-to-t from-black/80"></div>
</div>
{/each}
</div>
</div>
<!-- Navigation buttons -->
<div class='absolute z-10 flex gap-2 bottom-2 right-2'>
<button aria-label="Previous" onclick={slidePrev} class='flex items-center justify-center w-8 h-8 text-white bg-black bg-opacity-50 rounded-full dark:bg-zinc-800 dark:bg-opacity-50'>
<div class="flex absolute right-2 bottom-2 z-10 gap-2">
<button
aria-label="Previous"
onclick={slidePrev}
type="button"
class="flex justify-center items-center w-8 h-8 text-white rounded-full bg-black/50 dark:bg-zinc-800 transition-all duration-200"
>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width={1.5} stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="m15.75 19.5-7.5-7.5 7.5-7.5" />
</svg>
</button>
<button aria-label="Next" onclick={slideNext} class='flex items-center justify-center w-8 h-8 text-white bg-black bg-opacity-50 rounded-full dark:bg-zinc-800 dark:bg-opacity-50'>
<button
aria-label="Next"
onclick={slideNext}
type="button"
class="flex justify-center items-center w-8 h-8 text-white rounded-full bg-black/50 dark:bg-zinc-800 transition-all duration-200"
>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width={1.5} stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" />
</svg>
@@ -1,9 +1,5 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
import type { Background } from './types';
export let filteredBackgrounds: Background[];
let dispatch = createEventDispatcher();
let filters = $state({
@@ -13,9 +9,9 @@
orientation: [] as string[]
});
$: {
$effect(() => {
dispatch('filter', filters);
}
});
function toggleFilter(category: keyof typeof filters, value: string) {
if (filters[category].includes(value)) {
@@ -42,21 +38,19 @@
<h3 class="mb-2 font-medium">Type</h3>
<div class="space-y-2">
<label class="flex items-center">
<input type="checkbox" checked={filters.type.includes('image')} on:change={() => toggleFilter('type', 'image')}>
<input type="checkbox" checked={filters.type.includes('image')} onchange={() => toggleFilter('type', 'image')}>
<span class="ml-2">Image</span>
</label>
<label class="flex items-center">
<input type="checkbox" checked={filters.type.includes('video')} on:change={() => toggleFilter('type', 'video')}>
<input type="checkbox" checked={filters.type.includes('video')} onchange={() => toggleFilter('type', 'video')}>
<span class="ml-2">Video</span>
</label>
</div>
</div>
<!-- Add similar sections for color, resolution, and orientation -->
<button
class="px-4 py-2 mt-4 text-white bg-red-500 rounded hover:bg-red-600"
on:click={clearFilters}
onclick={clearFilters}
>
Clear Filters
</button>
+7 -4
View File
@@ -3,6 +3,7 @@
import logoDark from '@/resources/icons/betterseqta-light-full.png';
import { closeStore } from '@/seqta/ui/renderStore'
import browser from 'webextension-polyfill';
import CloudHeader from './CloudHeader.svelte';
// Props
let { searchTerm, setSearchTerm, darkMode, activeTab, setActiveTab } = $props<{
@@ -20,8 +21,8 @@
</script>
<header class="fixed top-0 z-50 w-full h-[4.25rem] bg-white border-b shadow-md border-b-white/10 dark:bg-zinc-950/90 backdrop-blur-xl dark:text-white">
<div class="flex items-center justify-between px-4 py-1">
<div class="flex gap-4 cursor-pointer place-items-center" onkeydown={(e) => { if (e.key === 'Enter') clearSearch() }} onclick={clearSearch} role="button" tabindex="0">
<div class="flex justify-between items-center px-4 py-1">
<div class="flex gap-4 place-items-center cursor-pointer" onkeydown={(e) => { if (e.key === 'Enter') clearSearch() }} onclick={clearSearch} role="button" tabindex="0">
<img src={browser.runtime.getURL(logo)} class="h-14 {darkMode ? 'hidden' : ''}" alt="Logo" />
<img src={browser.runtime.getURL(logoDark)} class="h-14 {darkMode ? '' : 'hidden'}" alt="Dark Logo" />
@@ -39,9 +40,11 @@
>
Backgrounds
</button>
<CloudHeader />
</div>
<div class="relative flex gap-2">
<div class="flex relative gap-2">
<input
type="text"
placeholder="Search themes..."
@@ -49,7 +52,7 @@
oninput={(e: any) => setSearchTerm(e.target.value)}
class="px-4 py-2 pl-10 text-lg transition bg-gray-100/80 rounded-lg ring-0 focus:bg-gray-100/0 dark:focus:bg-zinc-700/50 focus:ring-[1px] ring-zinc-200 dark:ring-zinc-600 dark:bg-zinc-700/80 dark:text-gray-100 focus:outline-none focus:border-transparent" />
<svg
class="absolute w-5 h-5 text-gray-400 transform -translate-y-1/2 left-3 top-1/2 dark:text-gray-200"
class="absolute left-3 top-1/2 w-5 h-5 text-gray-400 transform -translate-y-1/2 dark:text-gray-200"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
+193 -9
View File
@@ -1,17 +1,201 @@
<script lang="ts">
let { theme, onClick } = $props<{ theme: Theme; onClick: () => void }>();
import type { Theme } from '@/interface/types/Theme'
import {
masterGridDisplayDownloadCount,
gridCardPreviewImageUrls,
} from '@/interface/utils/themeStoreFlavours'
import { fade } from 'svelte/transition';
import { onMount } from 'svelte';
import emblaCarouselSvelte from 'embla-carousel-svelte';
import Autoplay from 'embla-carousel-autoplay';
let { theme, onClick, toggleFavorite, isLoggedIn, onRequestSignIn, allStoreThemeRows } = $props<{
theme: Theme;
onClick: () => void;
toggleFavorite: (theme: Theme) => void;
isLoggedIn: boolean;
onRequestSignIn?: () => void;
/** Raw API themes (includes hidden slaves) for aggregated master download totals */
allStoreThemeRows?: Theme[];
}>();
const displayDownloadCount = $derived(
allStoreThemeRows != null
? masterGridDisplayDownloadCount(theme, allStoreThemeRows)
: (theme.download_count ?? 0),
);
const gridRotatorUrls = $derived(gridCardPreviewImageUrls(theme, allStoreThemeRows));
/** Mirrors CoverSwiper (featured bar): horizontal slides + autoplay */
function prefersReducedMotion(): boolean {
return typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
}
/** Read once synchronously where `window` exists so reduced-motion doesnt briefly mount carousel */
let allowSlideAutoplay = $state(!prefersReducedMotion());
const gridEmblaKey = $derived(gridRotatorUrls.join('|'));
const gridEmblaOptions = $derived({ loop: gridRotatorUrls.length > 1 });
const gridEmblaPlugins = $derived.by(() => {
if (!allowSlideAutoplay || gridRotatorUrls.length <= 1) return [];
return [
Autoplay({
delay: 2000,
stopOnInteraction: false,
stopOnMouseEnter: true,
}),
];
});
let menuOpen = $state(false);
let menuRef: HTMLDivElement;
onMount(() => {
const closeMenu = (e: MouseEvent) => {
if (menuOpen && menuRef && !menuRef.contains(e.target as Node)) {
menuOpen = false;
}
};
document.addEventListener('click', closeMenu);
return () => document.removeEventListener('click', closeMenu);
});
function handleCardClick(e: MouseEvent) {
if ((e.target as HTMLElement).closest('[data-theme-menu]')) return;
onClick();
}
function handleFavoriteClick(e: MouseEvent) {
e.stopPropagation();
if (isLoggedIn) {
toggleFavorite(theme);
} else {
onRequestSignIn?.();
}
menuOpen = false;
}
</script>
<div class="w-full cursor-pointer" role="button" tabindex="-1" onkeydown={onClick} onclick={onClick}>
<div class="bg-gray-50 w-full transition-all hover:scale-105 duration-500 relative group flex flex-col hover:shadow-2xl dark:hover:shadow-white/[0.1] hover:shadow-white/[0.8] dark:bg-zinc-800 dark:border-white/[0.1] h-auto rounded-xl overflow-clip border" transition:fade>
<div class="absolute z-10 mb-1 text-xl font-bold text-white bottom-1 left-3">
{theme.name}
<div
class="relative z-0 hover:z-20 w-full cursor-pointer"
role="button"
tabindex="-1"
onkeydown={onClick}
onclick={handleCardClick}
>
<div
class="bg-gray-50 w-full transition-all duration-500 ease-out relative group flex flex-col rounded-xl overflow-clip border hover:scale-105 hover:shadow-2xl dark:hover:shadow-white/[0.1] dark:hover:shadow-white/[0.8] dark:bg-zinc-800 dark:border-white/[0.1] h-auto"
transition:fade
>
{#if theme.featured === true}
<div class="absolute top-2 left-2 z-20 pointer-events-none">
<span
class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-semibold bg-amber-100 text-amber-900 dark:bg-amber-950 dark:text-amber-100 shadow-sm"
aria-label="Featured theme"
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-3.5 h-3.5">
<path fill-rule="evenodd" d="M10.788 3.21c.448-1.077 1.976-1.077 2.424 0l2.082 5.006 5.404.434c1.164.093 1.636 1.545.749 2.305l-4.117 3.527 1.257 5.273c.271 1.136-.964 2.033-1.96 1.425L12 18.354 7.373 21.18c-.996.608-2.231-.29-1.96-1.425l1.257-5.273-4.117-3.527c-.887-.76-.415-2.212.749-2.305l5.404-.434 2.082-5.005Z" clip-rule="evenodd" />
</svg>
Featured
</span>
</div>
{/if}
<!-- Menu dropdown -->
<div class="absolute top-2 right-2 z-20" data-theme-menu bind:this={menuRef}>
<button
type="button"
class="flex justify-center items-center w-8 h-8 rounded-lg bg-black/40 hover:bg-black/60 text-white transition-all"
onclick={(e) => { e.stopPropagation(); menuOpen = !menuOpen; }}
aria-label="Theme options"
>
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24" class="w-5 h-5">
<path d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"/>
</svg>
</button>
{#if menuOpen}
<div
class="absolute right-0 top-full mt-1 py-1 min-w-[140px] rounded-lg bg-white dark:bg-zinc-800 shadow-lg border border-zinc-200 dark:border-zinc-700"
role="menu"
>
<button
type="button"
class="flex gap-2 items-center w-full px-3 py-2 text-left text-sm hover:bg-zinc-100 dark:hover:bg-zinc-700"
role="menuitem"
onclick={handleFavoriteClick}
title={isLoggedIn ? (theme.is_favorited ? 'Remove from favorites' : 'Add to favorites') : 'Sign in to favorite themes'}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill={theme.is_favorited ? 'currentColor' : 'none'}
stroke="currentColor"
stroke-width="2"
class="w-5 h-5 {theme.is_favorited ? 'text-red-500' : ''}"
>
<path stroke-linecap="round" stroke-linejoin="round" d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z" />
</svg>
{theme.is_favorited ? 'Favorited' : 'Favorite'}
</button>
</div>
{/if}
</div>
<div class='absolute bottom-0 z-0 w-full h-3/4 bg-gradient-to-t from-black/80 to-transparent'></div>
<div class='w-full'>
<img src={theme.coverImage} alt="Theme Preview" class="object-cover w-full h-48 rounded-md" />
<div class="absolute bottom-1 left-3 right-3 z-10 mb-1 flex flex-col gap-0.5">
<span class="text-xl font-bold text-white drop-shadow-md">{theme.name}</span>
{#if theme.author}
<span class="text-xs text-white/85 drop-shadow-md line-clamp-1">By {theme.author}</span>
{/if}
<div class="flex gap-3 text-xs font-medium text-white/90 drop-shadow-sm">
<span class="flex items-center gap-1">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-3.5 h-3.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3" />
</svg>
{displayDownloadCount.toLocaleString()}
</span>
<span class="flex items-center gap-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill={theme.is_favorited ? 'currentColor' : 'none'} stroke="currentColor" stroke-width="1.5" class="w-3.5 h-3.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z" />
</svg>
{(theme.favorite_count ?? 0).toLocaleString()}
</span>
</div>
</div>
<div class='absolute bottom-0 z-0 w-full h-3/4 bg-linear-to-t to-transparent from-black/80'></div>
{#if gridRotatorUrls.length === 0}
<div class="relative w-full h-48 overflow-hidden rounded-md bg-zinc-200 dark:bg-zinc-700" aria-hidden="true"></div>
{:else if !allowSlideAutoplay || gridRotatorUrls.length === 1}
<div class="relative w-full h-48 overflow-hidden rounded-md">
<img
src={gridRotatorUrls[0] ?? theme.marqueeImage ?? theme.coverImage}
alt=""
class="object-cover w-full h-full"
draggable="false"
/>
</div>
{:else}
{#key gridEmblaKey}
<div
class="relative w-full h-48 overflow-hidden rounded-md"
use:emblaCarouselSvelte={{
options: gridEmblaOptions,
plugins: gridEmblaPlugins,
}}
>
<div class="flex h-full">
{#each gridRotatorUrls as url (url)}
<div class="relative flex-[0_0_100%] min-w-0 h-full shrink-0">
<img
src={url}
alt=""
class="object-cover w-full h-full select-none"
draggable="false"
/>
</div>
{/each}
</div>
</div>
{/key}
{/if}
</div>
</div>
@@ -2,22 +2,49 @@
import type { Theme } from '@/interface/types/Theme'
import ThemeCard from './ThemeCard.svelte';
let { themes, searchTerm, setDisplayTheme } = $props<{ themes: Theme[]; searchTerm: string, setDisplayTheme: (theme: Theme) => void }>();
let {
themes,
searchTerm,
setDisplayTheme,
toggleFavorite,
isLoggedIn,
onRequestSignIn,
allStoreThemeRows,
} = $props<{
themes: Theme[];
searchTerm: string;
setDisplayTheme: (theme: Theme) => void;
toggleFavorite: (theme: Theme) => void;
isLoggedIn: boolean;
onRequestSignIn?: () => void;
/** Raw API list (includes `slave` rows) for master download aggregation */
allStoreThemeRows?: Theme[];
}>();
let filteredThemes = $derived(themes.filter((theme: Theme) =>
theme.name.toLowerCase().includes(searchTerm.toLowerCase()) || theme.description.toLowerCase().includes(searchTerm.toLowerCase())
));
let filteredThemes = $derived(themes.filter((theme: Theme) => {
const q = searchTerm.toLowerCase();
const name = (theme.name ?? '').toLowerCase();
const description = (theme.description ?? '').toLowerCase();
return name.includes(q) || description.includes(q);
}));
</script>
<div class="relative" >
<div class="grid grid-cols-1 gap-4 py-12 mx-auto sm:grid-cols-2 lg:grid-cols-3">
{#each filteredThemes as theme (theme.id)}
<ThemeCard theme={theme} onClick={() => setDisplayTheme(theme)} />
<ThemeCard
{theme}
onClick={() => setDisplayTheme(theme)}
{toggleFavorite}
{isLoggedIn}
{onRequestSignIn}
{allStoreThemeRows}
/>
{/each}
{#if filteredThemes.length !== 0}
<a href="https://betterseqta.gitbook.io/betterseqta-docs" class='w-full cursor-pointer'>
<div class="bg-zinc-50 h-48 w-full transition-all hover:scale-105 duration-500 relative justify-center items-center group group/card flex flex-col hover:shadow-2xl dark:hover:shadow-white/[0.1] hover:shadow-white/[0.8] dark:bg-zinc-800 dark:border-white/[0.1] rounded-xl overflow-clip border">
<a href="https://docs.betterseqta.org/theme-creation/" class="block relative z-0 hover:z-20 w-full cursor-pointer">
<div class="bg-zinc-50 h-48 w-full transition-all duration-500 ease-out relative overflow-clip rounded-xl border group group/card flex flex-col justify-center items-center hover:scale-105 hover:shadow-2xl dark:hover:shadow-white/[0.1] hover:shadow-white/[0.8] dark:bg-zinc-800 dark:border-white/[0.1]">
<div class="text-2xl font-IconFamily">{'\uecb3'}</div>
<div class="text-xl font-bold text-center transition-all duration-500 dark:text-white">
Got a Theme Idea?
@@ -32,7 +59,7 @@
<div class="absolute top-0 flex flex-col items-center justify-center w-full text-center h-96">
<h1 class="mt-4 text-3xl font-bold tracking-tight text-zinc-900 dark:text-zinc-100 sm:text-5xl">That doesn't exist! 😭😭😭</h1>
<p class="mt-6 text-lg leading-7 text-zinc-600 dark:text-zinc-300">Sorry, we couldn't find the theme you're looking for. Maybe... you could create it?</p>
<a href="https://betterseqta.gitbook.io/betterseqta-docs" class='p-2 px-3 mt-4 transition rounded-md cursor-pointer dark:text-white bg-zinc-500/10 hover:scale-105'>
<a href="https://docs.betterseqta.org/theme-creation/" class='p-2 px-3 mt-4 transition rounded-md cursor-pointer dark:text-white bg-zinc-500/10 hover:scale-105'>
Show me how!
</a>
</div>
File diff suppressed because it is too large Load Diff
@@ -15,7 +15,7 @@
onkeydown={onClick}
tabindex="-1"
role="button"
class="relative w-16 h-16 cursor-pointer rounded-xl transition ring dark:ring-zinc-500/50 ring-zinc-300 {isEditMode ? 'animate-shake' : ''} {isSelected ? 'dark:ring-4 ring-4' : 'ring-0'}"
class="relative w-16 h-16 cursor-pointer rounded-xl transition ring-3 dark:ring-zinc-500/50 ring-zinc-300 {isEditMode ? 'animate-shake' : ''} {isSelected ? 'dark:ring-4 ring-4' : 'ring-0'}"
>
{#if isEditMode}
<div
@@ -1,29 +1,42 @@
<script lang="ts">
import type { CustomTheme, ThemeList } from '@/types/CustomThemes'
import { getAvailableThemes } from '@/seqta/ui/themes/getAvailableThemes'
import { onDestroy, onMount } from 'svelte'
import { OpenThemeCreator } from '@/seqta/ui/ThemeCreator'
import shareTheme from '@/seqta/ui/themes/shareTheme'
import { InstallTheme } from '@/seqta/ui/themes/downloadTheme'
import { disableTheme } from '@/seqta/ui/themes/disableTheme'
import { setTheme } from '@/seqta/ui/themes/setTheme'
import { deleteTheme } from '@/seqta/ui/themes/deleteTheme'
import browser from 'webextension-polyfill'
import { OpenThemeCreator } from '@/plugins/built-in/themes/ThemeCreator'
import { OpenStorePage } from '@/seqta/ui/renderStore'
import { themeUpdates } from '@/interface/hooks/ThemeUpdates'
import { closeExtensionPopup } from '@/SEQTA'
import { closeExtensionPopup } from '@/seqta/utils/Closers/closeExtensionPopup'
import { ThemeManager } from '@/plugins/built-in/themes/theme-manager'
import { cloudAuth } from '@/seqta/utils/CloudAuth'
import SignInToFavoriteModal from '@/interface/components/SignInToFavoriteModal.svelte'
const themeManager = ThemeManager.getInstance();
let themes = $state<ThemeList | null>(null);
let { isEditMode } = $props<{ isEditMode: boolean }>();
let isDragging = $state(false);
let tempTheme = $state(null);
let favoriteStatus = $state<Record<string, boolean>>({});
let cloudLoggedIn = $state(cloudAuth.state.isLoggedIn);
let prevLoggedIn = $state(false);
let showSignInModal = $state(false);
const handleThemeClick = async (theme: CustomTheme) => {
cloudAuth.subscribe((s) => {
const now = s.isLoggedIn;
if (now && !prevLoggedIn && themes) void fetchThemes();
prevLoggedIn = now;
cloudLoggedIn = now;
});
const handleThemeClick = async (theme: CustomTheme, e: MouseEvent) => {
if (isEditMode) return;
if (theme.id === themes?.selectedTheme) {
await disableTheme();
themeManager.setTransitionPoint(e.clientX, e.clientY);
await themeManager.disableTheme();
themes.selectedTheme = '';
} else {
await setTheme(theme.id);
themeManager.setTransitionPoint(e.clientX, e.clientY);
await themeManager.setTheme(theme.id);
if (!themes) return;
themes.selectedTheme = theme.id;
}
@@ -31,13 +44,13 @@
const handleThemeDelete = async (themeId: string) => {
try {
await deleteTheme(themeId);
await themeManager.deleteTheme(themeId);
if (!themes) return;
themes.themes = themes.themes.filter(theme => theme.id !== themeId);
if (themeId === themes.selectedTheme) {
themes.selectedTheme = '';
await disableTheme();
await themeManager.disableTheme();
}
} catch (error) {
console.error('Error deleting theme:', error);
@@ -46,7 +59,7 @@
const handleShareTheme = async (theme: CustomTheme) => {
try {
await shareTheme(theme.id);
await themeManager.shareTheme(theme.id);
} catch (error) {
console.error('Error sharing theme:', error);
}
@@ -72,9 +85,10 @@
try {
const result = JSON.parse(event.target?.result as string);
tempTheme = result;
await InstallTheme(result);
await themeManager.installTheme(result, { fromStore: false });
await fetchThemes();
} catch (error) {
console.error('Error parsing file:', error);
alert('Error parsing file. Please upload a valid JSON theme file.');
}
tempTheme = null;
@@ -83,12 +97,59 @@
}
const fetchThemes = async () => {
themes = await getAvailableThemes();
themes = {
themes: await themeManager.getAvailableThemes(),
selectedTheme: themeManager.getSelectedThemeId() || '',
}
if (themes && cloudLoggedIn) {
const token = await cloudAuth.getStoredToken();
if (token) {
const status: Record<string, boolean> = {};
await Promise.all(
themes.themes.map(async (t) => {
try {
const res = (await browser.runtime.sendMessage({
type: 'fetchThemeDetails',
themeId: t.id,
token,
})) as { success?: boolean; data?: { theme?: { is_favorited?: boolean } } };
if (res?.success && res?.data?.theme) {
status[t.id] = !!res.data.theme.is_favorited;
}
} catch {
// Theme may not exist on store (e.g. locally created)
}
})
);
favoriteStatus = status;
}
} else {
favoriteStatus = {};
}
}
const handleToggleFavorite = async (theme: CustomTheme, e: MouseEvent) => {
e.stopPropagation();
if (!cloudLoggedIn) {
showSignInModal = true;
return;
}
const token = await cloudAuth.getStoredToken();
if (!token) return;
const isFavorite = !favoriteStatus[theme.id];
const result = (await browser.runtime.sendMessage({
type: 'cloudFavorite',
themeId: theme.id,
token,
action: isFavorite ? 'favorite' : 'unfavorite',
})) as { success?: boolean };
if (result?.success) {
favoriteStatus = { ...favoriteStatus, [theme.id]: isFavorite };
}
}
onMount(async () => {
await fetchThemes();
themeUpdates.addListener(fetchThemes);
})
@@ -98,7 +159,7 @@
</script>
<div
class="w-full pt-5 mb-1"
class="pt-5 mb-1 w-full"
role="list"
tabindex="-1"
ondragover={handleDragOver}
@@ -106,9 +167,9 @@
ondrop={handleDrop}
>
<div class="{isDragging ? 'opacity-100' : 'opacity-0'} transition pointer-events-none absolute w-full p-2 z-50">
<div class="sticky w-full h-64 bg-white shadow-xl dark:bg-zinc-900 top-5 dark:text-white rounded-xl outline-dashed outline-4 outline-zinc-200 dark:outline-zinc-700">
<div class="flex items-center justify-center h-full">
<div class="flex flex-col items-center justify-center">
<div class="sticky top-5 w-full h-64 bg-white rounded-xl shadow-xl dark:bg-zinc-900 dark:text-white outline-dashed outline-4 outline-zinc-200 dark:outline-zinc-700">
<div class="flex justify-center items-center h-full">
<div class="flex flex-col justify-center items-center">
<svg height="48" width="48" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
<g fill="currentColor">
<path d="M44,31a1,1,0,0,0-1,1v8a3,3,0,0,1-3,3H8a3,3,0,0,1-3-3V32a1,1,0,0,0-2,0v8a5.006,5.006,0,0,0,5,5H40a5.006,5.006,0,0,0,5-5V32A1,1,0,0,0,44,31Z" fill="currentColor"/>
@@ -126,11 +187,11 @@
{#each themes.themes as theme (theme.id)}
<button
class="relative group w-full aspect-theme flex justify-center items-center rounded-xl transition ring dark:ring-white ring-zinc-300 {theme.id === themes.selectedTheme ? 'dark:ring-2 ring-4' : 'ring-0'}"
onclick={() => handleThemeClick(theme)}
onclick={(e) => handleThemeClick(theme, e)}
>
{#if isEditMode}
<div
class="absolute z-20 flex w-6 h-6 p-2 text-white bg-red-600 rounded-full opacity-100 right-2 place-items-center top-2"
class="flex absolute top-2 right-2 z-20 place-items-center p-2 w-6 h-6 text-white bg-red-600 rounded-full opacity-100"
onclick={(event) => { event.stopPropagation(); handleThemeDelete(theme.id) }}
onkeydown={(event) => { if (event.key === 'Enter' || event.key === ' ') handleThemeDelete(theme.id) }}
role="button"
@@ -141,6 +202,18 @@
{/if}
{#if !isEditMode}
<div
class="flex absolute right-24 top-1/4 z-20 place-items-center p-2 w-8 h-8 text-center rounded-full opacity-0 transition-all -translate-y-1/2 group-hover:opacity-100 group-hover:top-1/2 {(favoriteStatus[theme.id] ?? false) ? 'text-red-400' : 'text-white/80'} bg-black/50"
onclick={(event) => handleToggleFavorite(theme, event)}
onkeydown={(event) => { if (event.key === 'Enter' || event.key === ' ') handleToggleFavorite(theme, event as any) }}
role="button"
tabindex="-1"
title={cloudLoggedIn ? ((favoriteStatus[theme.id] ?? false) ? 'Remove from favorites' : 'Add to favorites') : 'Sign in to favorite themes'}
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill={(favoriteStatus[theme.id] ?? false) ? 'currentColor' : 'none'} stroke="currentColor" stroke-width="2" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z" />
</svg>
</div>
<div
class="absolute z-20 flex w-8 h-8 p-2 text-white transition-all rounded-full delay-[20ms] opacity-0 top-1/4 right-2 bg-black/50 place-items-center group-hover:opacity-100 group-hover:top-1/2 -translate-y-1/2"
onclick={(event) => { event.stopPropagation(); OpenThemeCreator(theme.id); closeExtensionPopup() }}
@@ -152,7 +225,7 @@
</div>
<div
class="absolute z-20 flex w-8 h-8 p-2 text-center transition-all -translate-y-1/2 rounded-full opacity-0 text-white/80 top-1/4 right-12 bg-black/50 place-items-center group-hover:opacity-100 group-hover:top-1/2"
class="flex absolute right-12 top-1/4 z-20 place-items-center p-2 w-8 h-8 text-center rounded-full opacity-0 transition-all -translate-y-1/2 text-white/80 bg-black/50 group-hover:opacity-100 group-hover:top-1/2"
onclick={(event) => { event.stopPropagation(); handleShareTheme(theme) }}
onkeydown={(event) => { if (event.key === 'Enter' || event.key === ' ') handleShareTheme(theme) }}
role="button"
@@ -167,7 +240,7 @@
<img
src={typeof theme.coverImage === 'string' ? theme.coverImage : URL.createObjectURL(theme.coverImage)}
alt={theme.name}
class="absolute inset-0 z-0 object-cover w-full h-full pointer-events-none"
class="object-cover absolute inset-0 z-0 w-full h-full pointer-events-none"
/>
{/if}
{#if !theme.hideThemeName}
@@ -179,7 +252,7 @@
{/if}
{#if tempTheme}
<div class="flex justify-center w-full bg-gray-200 rounded-xl dark:bg-zinc-700/50 place-items-center aspect-theme animate-pulse">
<div class="flex justify-center place-items-center w-full bg-gray-200 rounded-xl animate-pulse dark:bg-zinc-700/50 aspect-theme">
<svg class="w-5 h-5 text-white animate-spin" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
@@ -193,7 +266,7 @@
<button
onclick={() => OpenStorePage()}
class="flex items-center justify-center w-full transition aspect-theme rounded-xl bg-zinc-100 dark:bg-zinc-900 dark:text-white"
class="flex justify-center items-center w-full rounded-xl transition aspect-theme bg-zinc-100 dark:bg-zinc-900 dark:text-white"
>
<span class="text-xl font-IconFamily">&#xecc5;</span>
<span class="ml-2">Theme Store</span>
@@ -201,10 +274,14 @@
<button
onclick={() => { OpenThemeCreator(); closeExtensionPopup() }}
class="flex items-center justify-center w-full transition aspect-theme rounded-xl bg-zinc-100 dark:bg-zinc-900 dark:text-white"
class="flex justify-center items-center w-full rounded-xl transition aspect-theme bg-zinc-100 dark:bg-zinc-900 dark:text-white"
>
<span class="text-xl font-IconFamily">&#xec60;</span>
<span class="ml-2">Create your own</span>
</button>
</div>
</div>
{#if showSignInModal}
<SignInToFavoriteModal onClose={() => (showSignInModal = false)} />
{/if}
+102 -19
View File
@@ -1,8 +1,20 @@
import { type DBSchema, type IDBPDatabase, openDB } from 'idb';
import { type DBSchema, type IDBPDatabase, openDB } from "idb";
/**
* Defines the schema for the IndexedDB database used for storing background image data.
*
* @interface BackgroundDB
* @extends {DBSchema}
* @property {object} backgrounds - The object store for background images.
* @property {string} backgrounds.key - The type of the key for the object store (in this case, it's `id` as defined in `keyPath`).
* @property {object} backgrounds.value - The structure of the objects stored.
* @property {string} backgrounds.value.id - The unique identifier for the background image record.
* @property {string} backgrounds.value.type - The MIME type of the image (e.g., "image/png", "image/jpeg").
* @property {Blob} backgrounds.value.blob - The binary large object (Blob) containing the image data.
*/
interface BackgroundDB extends DBSchema {
backgrounds: {
key: string;
key: string; // Corresponds to the 'id' property due to keyPath: "id"
value: {
id: string;
type: string;
@@ -13,43 +25,100 @@ interface BackgroundDB extends DBSchema {
let db: IDBPDatabase<BackgroundDB> | null = null;
/**
* Initializes and opens an IndexedDB connection or returns an existing one.
* If the database doesn't exist or needs an upgrade, the `upgrade` callback
* creates the 'backgrounds' object store with 'id' as the keyPath.
*
* @async
* @returns {Promise<IDBPDatabase<BackgroundDB>>} A promise that resolves with the database instance.
*/
export async function openDatabase(): Promise<IDBPDatabase<BackgroundDB>> {
if (db) return db;
db = await openDB<BackgroundDB>('BackgroundDB', 1, {
db = await openDB<BackgroundDB>("BackgroundDB", 1, {
upgrade(db: IDBPDatabase<BackgroundDB>) {
db.createObjectStore('backgrounds', { keyPath: 'id' });
db.createObjectStore("backgrounds", { keyPath: "id" });
},
});
return db;
}
export async function readAllData(): Promise<Array<{ id: string; type: string; blob: Blob }>> {
/**
* Retrieves all background image records from the 'backgrounds' object store in IndexedDB.
*
* @async
* @returns {Promise<Array<{id: string, type: string, blob: Blob}>>} A promise that resolves with an array of all background image records.
*/
export async function readAllData(): Promise<
Array<{ id: string; type: string; blob: Blob }>
> {
const db = await openDatabase();
return db.getAll('backgrounds');
return db.getAll("backgrounds");
}
export async function writeData(id: string, type: string, blob: Blob): Promise<void> {
/**
* Writes or updates a background image record in the 'backgrounds' object store.
* If a record with the given `id` already exists, it will be updated. Otherwise, a new record is created.
*
* @async
* @param {string} id - The unique identifier for the background image record.
* @param {string} type - The MIME type of the image (e.g., "image/png").
* @param {Blob} blob - The Blob object containing the image data.
* @returns {Promise<void>} A promise that resolves when the data has been successfully written.
*/
export async function writeData(
id: string,
type: string,
blob: Blob,
): Promise<void> {
const db = await openDatabase();
await db.put('backgrounds', { id, type, blob });
await db.put("backgrounds", { id, type, blob });
}
/**
* Deletes a background image record from the 'backgrounds' object store by its ID.
*
* @async
* @param {string} id - The unique identifier of the background image record to delete.
* @returns {Promise<void>} A promise that resolves when the data has been successfully deleted.
*/
export async function deleteData(id: string): Promise<void> {
const db = await openDatabase();
await db.delete('backgrounds', id);
await db.delete("backgrounds", id);
}
/**
* Clears all records from the 'backgrounds' object store in IndexedDB.
*
* @async
* @returns {Promise<void>} A promise that resolves when all data has been successfully cleared.
*/
export async function clearAllData(): Promise<void> {
const db = await openDatabase();
await db.clear('backgrounds');
await db.clear("backgrounds");
}
export async function getDataById(id: string): Promise<{ id: string; type: string; blob: Blob } | undefined> {
/**
* Retrieves a single background image record from the 'backgrounds' object store by its ID.
*
* @async
* @param {string} id - The unique identifier of the background image record to retrieve.
* @returns {Promise<{id: string, type: string, blob: Blob} | undefined>} A promise that resolves with the
* background image record if found, or undefined otherwise.
*/
export async function getDataById(
id: string,
): Promise<{ id: string; type: string; blob: Blob } | undefined> {
const db = await openDatabase();
return db.get('backgrounds', id);
return db.get("backgrounds", id);
}
/**
* Closes the active IndexedDB connection and nullifies the global `db` variable.
* This is important to release resources and allow for proper database management.
*/
export function closeDatabase(): void {
if (db) {
db.close();
@@ -57,19 +126,33 @@ export function closeDatabase(): void {
}
}
// Helper function to check if IndexedDB is supported
/**
* Checks if IndexedDB is supported by the current browser environment.
*
* @returns {boolean} True if IndexedDB is supported, false otherwise.
*/
export function isIndexedDBSupported(): boolean {
return 'indexedDB' in window;
return "indexedDB" in window;
}
// Helper function to check if there's enough storage space
export async function hasEnoughStorageSpace(requiredSpace: number): Promise<boolean> {
if ('storage' in navigator && 'estimate' in navigator.storage) {
/**
* Estimates available storage space and checks if it's sufficient for the specified `requiredSpace`.
* Uses the `navigator.storage.estimate()` API if available.
* If the API is not available or cannot determine space, it defaults to assuming enough space is available.
*
* @async
* @param {number} requiredSpace - The amount of storage space required, in bytes.
* @returns {Promise<boolean>} A promise that resolves with true if enough space is estimated to be available, false otherwise.
*/
export async function hasEnoughStorageSpace(
requiredSpace: number,
): Promise<boolean> {
if ("storage" in navigator && "estimate" in navigator.storage) {
const { quota, usage } = await navigator.storage.estimate();
if (quota !== undefined && usage !== undefined) {
return (quota - usage) > requiredSpace;
return quota - usage > requiredSpace;
}
}
// If we can't determine, assume there's enough space
return true;
}
}
+28 -1
View File
@@ -1,11 +1,21 @@
type BackgroundUpdateCallback = () => void;
/**
* A singleton class used to notify listeners about generic background updates or events.
* These updates typically signify that UI components or other parts of the application
* might need to refresh or re-evaluate background-related data (e.g., after a new background
* image is added, removed, or changed).
*/
class BackgroundUpdates {
private static instance: BackgroundUpdates;
private listeners: Set<BackgroundUpdateCallback> = new Set();
private constructor() {}
/**
* Gets the singleton instance of the BackgroundUpdates class.
* @returns {BackgroundUpdates} The singleton instance.
*/
public static getInstance(): BackgroundUpdates {
if (!BackgroundUpdates.instance) {
BackgroundUpdates.instance = new BackgroundUpdates();
@@ -13,16 +23,33 @@ class BackgroundUpdates {
return BackgroundUpdates.instance;
}
/**
* Registers a callback function to be invoked when a background update is triggered.
*
* @param {BackgroundUpdateCallback} callback The function to call when a background update occurs.
* This callback takes no arguments and returns void.
*/
public addListener(callback: BackgroundUpdateCallback): void {
this.listeners.add(callback);
}
/**
* Unregisters a previously added callback function.
* After calling this method, the provided callback will no longer be invoked when a background update is triggered.
*
* @param {BackgroundUpdateCallback} callback The callback function to remove from the listeners.
*/
public removeListener(callback: BackgroundUpdateCallback): void {
this.listeners.delete(callback);
}
/**
* Invokes all registered listener callbacks, signifying that a background update has occurred.
* This method should be called whenever a change to background data happens that requires
* other parts of the application to be notified.
*/
public triggerUpdate(): void {
this.listeners.forEach(callback => callback());
this.listeners.forEach((callback) => callback());
}
}
+19 -3
View File
@@ -1,13 +1,13 @@
type SettingsPopupCallback = () => void;
/**
/**
* This is a singleton that triggers an update when the settings popup is closed.
* This is used to close the colour picker.
* Usage:
* settingsPopup.addListener(() => {
* console.log('Settings popup closed');
* });
*/
*/
class SettingsPopup {
private static instance: SettingsPopup;
private listeners: Set<SettingsPopupCallback> = new Set();
@@ -21,16 +21,32 @@ class SettingsPopup {
return SettingsPopup.instance;
}
/**
* Registers a callback function to be invoked when the settings popup is closed.
*
* @param {SettingsPopupCallback} callback The function to call when the settings popup closes.
* This callback takes no arguments and returns void.
*/
public addListener(callback: SettingsPopupCallback): void {
this.listeners.add(callback);
}
/**
* Unregisters a previously added callback function.
* After calling this method, the provided callback will no longer be invoked when the settings popup closes.
*
* @param {SettingsPopupCallback} callback The callback function to remove from the listeners.
*/
public removeListener(callback: SettingsPopupCallback): void {
this.listeners.delete(callback);
}
/**
* Invokes all registered listener callbacks.
* This method should be called when the settings popup is closed to notify all subscribed components or services.
*/
public triggerClose(): void {
this.listeners.forEach(callback => callback());
this.listeners.forEach((callback) => callback());
}
}
+28 -1
View File
@@ -1,11 +1,21 @@
type ThemeUpdateCallback = () => void;
/**
* A singleton class used to notify listeners about theme-related updates.
* These updates can include events like theme changes, custom theme modifications,
* or any other event that might require UI components to refresh their appearance
* or re-apply theme styles.
*/
class ThemeUpdates {
private static instance: ThemeUpdates;
private listeners: Set<ThemeUpdateCallback> = new Set();
private constructor() {}
/**
* Gets the singleton instance of the ThemeUpdates class.
* @returns {ThemeUpdates} The singleton instance.
*/
public static getInstance(): ThemeUpdates {
if (!ThemeUpdates.instance) {
ThemeUpdates.instance = new ThemeUpdates();
@@ -13,16 +23,33 @@ class ThemeUpdates {
return ThemeUpdates.instance;
}
/**
* Registers a callback function to be invoked when a theme update is triggered.
*
* @param {ThemeUpdateCallback} callback The function to call when a theme update occurs.
* This callback takes no arguments and returns void.
*/
public addListener(callback: ThemeUpdateCallback): void {
this.listeners.add(callback);
}
/**
* Unregisters a previously added callback function.
* After calling this method, the provided callback will no longer be invoked when a theme update is triggered.
*
* @param {ThemeUpdateCallback} callback The callback function to remove from the listeners.
*/
public removeListener(callback: ThemeUpdateCallback): void {
this.listeners.delete(callback);
}
/**
* Invokes all registered listener callbacks, signifying that a theme-related update has occurred.
* This method should be called whenever a change related to themes happens that requires
* other parts of the application to be notified.
*/
public triggerUpdate(): void {
this.listeners.forEach(callback => callback());
this.listeners.forEach((callback) => callback());
}
}
@@ -1 +1 @@
export let selectedBackground = $state<string | null>(null);
export let selectedBackground = $state<string | null>(null);
+9 -11
View File
@@ -1,17 +1,11 @@
@import './components/ColourPicker.css';
@import "./components/ColourPicker.css";
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
button {
@apply cursor-pointer;
}
::-webkit-scrollbar {
@@ -48,5 +42,9 @@ input {
.cm-editor {
width: 100%;
min-height: 100px;
max-height: 400px;
}
height: inherit;
}
.editorHeight {
height: calc(100vh - 58px);
}
+3 -3
View File
@@ -5,8 +5,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>BetterSEQTA+ Settings</title>
</head>
<body>
<div id="app"></div>
<body class="h-[600px]">
<div id="app" style="height: 100%"></div>
<script type="module" src="./index.ts"></script>
</body>
</html>
</html>
+20 -32
View File
@@ -1,46 +1,34 @@
import "./index.css"
import { mount } from "svelte"
import type { ComponentType } from "svelte"
import Settings from "./pages/settings.svelte"
import IconFamily from '@/resources/fonts/IconFamily.woff'
import browser from "webextension-polyfill"
export default function renderSvelte(
Component: ComponentType | any,
mountPoint: ShadowRoot | HTMLElement,
props: Record<string, any> = {},
) {
const app = mount(Component, {
target: mountPoint,
props: {
standalone: true,
...props,
},
})
return app
}
import "./index.css";
import Settings from "./pages/settings.svelte";
import IconFamily from "@/resources/fonts/IconFamily.woff";
import browser from "webextension-polyfill";
import renderSvelte from "./main";
import { initializeSettingsState } from "@/seqta/utils/listeners/SettingsState";
function InjectCustomIcons() {
console.info('[BetterSEQTA+] Injecting Icons')
console.info("[BetterSEQTA+] Injecting Icons");
const style = document.createElement('style')
style.setAttribute('type', 'text/css')
const style = document.createElement("style");
style.setAttribute("type", "text/css");
style.innerHTML = `
@font-face {
font-family: 'IconFamily';
src: url('${browser.runtime.getURL(IconFamily)}') format('woff');
font-weight: normal;
font-style: normal;
}`
document.head.appendChild(style)
}`;
document.head.appendChild(style);
}
const mountPoint = document.getElementById('app')
const mountPoint = document.getElementById("app");
if (!mountPoint) {
console.error('Mount point #app not found')
throw new Error('Mount point #app not found')
console.error("Mount point #app not found");
throw new Error("Mount point #app not found");
}
InjectCustomIcons()
renderSvelte(Settings, mountPoint)
InjectCustomIcons();
(async () => {
await initializeSettingsState();
renderSvelte(Settings, mountPoint, { standalone: true });
})();
+2 -2
View File
@@ -1,6 +1,6 @@
import './index.css';
import "./index.css";
declare module "*.png";
declare module "*.svg";
declare module "*.jpeg";
declare module "*.jpg";
declare module "*.jpg";

Some files were not shown because too many files have changed in this diff Show More