feat: prep fopr v3.7.0 as well as minor tweaks

This commit is contained in:
2026-06-02 08:42:24 +09:30
parent 9e521722f1
commit 337f85c3cc
13 changed files with 176 additions and 31 deletions
+1
View File
@@ -120,6 +120,7 @@ The backup is a flat JSON map of **`chrome.storage.local`** keys. It does **not*
- **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.
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "betterseqtaplus",
"version": "3.6.6",
"version": "3.7.0",
"type": "module",
"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",
+26
View File
@@ -3558,6 +3558,32 @@ div.day-empty {
color: var(--text-primary);
transform-origin: center center;
}
/* Text-only popups (privacy notices): body fills remaining height, scrolls inside */
.whatsnewContainer.whatsnewContainer--scrollBody {
.whatsnewHeader {
flex-shrink: 0;
height: auto;
min-height: 3em;
}
> .whatsnewTextContainer {
flex: 1 1 auto;
min-height: 0;
overflow-x: hidden;
overflow-y: auto;
max-height: none;
width: 90%;
margin: 0 auto 0.75rem;
padding-bottom: 0.5rem;
box-sizing: border-box;
}
> .whatsnewTextContainer.privacyStatement {
font-size: 1.1rem;
line-height: 1.6;
}
}
.whatsnewTextContainer.privacyStatement p {
margin-bottom: 1.5ex;
@@ -16,6 +16,7 @@
TIME_RANGE_OPTIONS,
type TimeRange,
} from "./timeRange";
import { openAnalyticsPrivacyPopup } from "./openAnalyticsPrivacyPopup";
let analyticsData: Assessment[] | null = $state(null);
let loading = $state(true);
@@ -208,14 +209,23 @@
<p class="bsplus-analytics-meta">Last updated: {formattedTimestamp()}</p>
{/if}
</div>
<button
type="button"
class="bsplus-analytics-btn bsplus-analytics-btn-primary"
disabled={syncing}
onclick={() => runSync()}
>
{syncing ? "Syncing…" : "Refresh data"}
</button>
<div class="bsplus-analytics-header-actions">
<button
type="button"
class="bsplus-analytics-btn bsplus-analytics-btn-primary"
disabled={syncing}
onclick={() => runSync()}
>
{syncing ? "Syncing…" : "Refresh data"}
</button>
<button
type="button"
class="bsplus-analytics-btn bsplus-analytics-btn-ghost"
onclick={() => openAnalyticsPrivacyPopup()}
>
Privacy notice
</button>
</div>
</header>
{#if error}
@@ -1,9 +1,13 @@
import type { Plugin } from "@/plugins/core/types";
import MenuitemSVGKey from "@/seqta/content/MenuItemSVGKey.json";
import { waitForElm } from "@/seqta/utils/waitForElm";
import { isSeqtaEngageExperience } from "@/seqta/utils/isSeqtaEngage";
import { processMenuItemNode } from "@/seqta/utils/sidebarMenuIcons";
import { loadAnalyticsPage } from "../loadAnalyticsPage";
import styles from "../styles.css?inline";
const ANALYTICS_MENU_ICON = MenuitemSVGKey.analytics;
const ANALYTICS_MENU_CLASS = "betterseqta-grade-analytics-item";
const gradeAnalyticsPlugin: Plugin<{}> = {
@@ -30,7 +34,7 @@ const gradeAnalyticsPlugin: Plugin<{}> = {
analyticsItem.dataset.key = "analytics";
analyticsItem.dataset.path = "/analytics";
analyticsItem.dataset.betterseqta = "true";
analyticsItem.innerHTML = `<label><svg style="width:24px;height:24px" viewBox="0 0 24 24" aria-hidden="true"><path fill="currentColor" d="M3 13h2v-2H3v2zm0 4h2v-2H3v2zm0-8h2V7H3v2zm4 8h14v-2H7v2zm0 4h14v-2H7v2zM7 7v2h14V7H7z"/></svg><span>Analytics</span></label>`;
analyticsItem.innerHTML = `<label>${ANALYTICS_MENU_ICON}<span>Analytics</span></label>`;
const homeButton = document.getElementById("homebutton");
if (homeButton?.parentElement === menuList) {
@@ -39,6 +43,8 @@ const gradeAnalyticsPlugin: Plugin<{}> = {
menuList.insertBefore(analyticsItem, menuList.firstChild);
}
processMenuItemNode(analyticsItem);
const menuObserver = new MutationObserver(() => {
if (!menuList.contains(analyticsItem)) {
if (homeButton?.parentElement === menuList) {
@@ -46,6 +52,7 @@ const gradeAnalyticsPlugin: Plugin<{}> = {
} else {
menuList.insertBefore(analyticsItem, menuList.firstChild);
}
processMenuItemNode(analyticsItem);
}
});
menuObserver.observe(menuList, { childList: true });
@@ -0,0 +1,61 @@
import stringToHTML from "@/seqta/utils/stringToHTML";
import { openPopup } from "@/seqta/utils/Openers/PopupManager";
/** Grade Analytics privacy — uses the shared BetterSEQTA+ whatsnew popup shell. */
export function openAnalyticsPrivacyPopup() {
const header = stringToHTML(
/* html */
`<div class="whatsnewHeader">
<h1>Privacy notice</h1>
<p>Grade Analytics on this device</p>
</div>`,
).firstChild as HTMLElement;
const text = stringToHTML(/* html */ `
<div class="whatsnewTextContainer privacyStatement">
<p style="margin-top: 0;">
<strong>Your grade history and charts stay on this device.</strong>
BetterSEQTA+ does not collect or store your analytics on our servers.
</p>
<h3>What we store locally</h3>
<ul style="text-align: left; margin: 10px 0;">
<li>Assessment results and subjects used for trends, distribution, and the table</li>
<li>Chart preferences (for example, grade distribution grouping) for your school account</li>
<li>A cache timestamp so Refresh data knows when to fetch from SEQTA again</li>
</ul>
<h3>What we never do</h3>
<ul style="text-align: left; margin: 10px 0;">
<li>Upload analytics data to BetterSEQTA Cloud or any BetterSEQTA server</li>
<li>Include analytics in automatic cloud settings backup or restore</li>
<li>Send your grades to third-party analytics or tracking services</li>
</ul>
<h3>How refresh works</h3>
<p>
Refresh data loads released marks directly from SEQTA while you are logged in.
That traffic is between your browser and your schools SEQTA site — not to us.
</p>
<h3>Clearing your data</h3>
<p>
You can remove cached analytics any time by clearing this extensions storage in
your browser settings.
</p>
<p style="font-weight: 600;">
General plugin settings (such as cache duration in the Grade Analytics plugin
panel) may still sync if you use BetterSEQTA Cloud — but never your assessment
results or charts.
</p>
</div>
`).firstChild as HTMLElement;
openPopup({
header,
content: [text],
animateSelector: ".whatsnewTextContainer *",
containerClass: "whatsnewContainer--scrollBody",
});
}
@@ -103,6 +103,20 @@
gap: 1.25rem;
}
.bsplus-analytics-header-actions {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 0.5rem;
flex-shrink: 0;
}
@media (min-width: 480px) {
.bsplus-analytics-header-actions {
min-width: 10.5rem;
}
}
.bsplus-analytics-header-text h1 {
margin: 0 0 0.35rem;
font-size: 1.875rem;
Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

+2 -1
View File
@@ -8,5 +8,6 @@
"portals": "<svg style=\"width:24px;height:24px\" viewBox=\"0 0 24 24\">\n <path fill=\"currentColor\" d=\"M15,4A8,8 0 0,1 23,12A8,8 0 0,1 15,20A8,8 0 0,1 7,12A8,8 0 0,1 15,4M15,18A6,6 0 0,0 21,12A6,6 0 0,0 15,6A6,6 0 0,0 9,12A6,6 0 0,0 15,18M3,12C3,14.61 4.67,16.83 7,17.65V19.74C3.55,18.85 1,15.73 1,12C1,8.27 3.55,5.15 7,4.26V6.35C4.67,7.17 3,9.39 3,12Z\" />\n</svg>",
"reports": "<svg style=\"width:24px;height:24px\" viewBox=\"0 0 24 24\">\n <path fill=\"currentColor\" d=\"M18 2H12V9L9.5 7.5L7 9V2H6A2 2 0 0 0 4 4V20A2 2 0 0 0 6 22H18A2 2 0 0 0 20 20V4A2 2 0 0 0 18 2M14 12A2 2 0 1 1 12 14A2 2 0 0 1 14 12M18 20H10V19C10 17.67 12.67 17 14 17S18 17.67 18 19Z\" />\n</svg>",
"settings": "<svg style=\"width:24px;height:24px\" viewBox=\"0 0 24 24\">\n <path fill=\"currentColor\" d=\"M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z\" />\n</svg>",
"timetable": "<svg style=\"width:24px;height:24px\" viewBox=\"0 0 24 24\">\n <path fill=\"currentColor\" d=\"M9,10V12H7V10H9M13,10V12H11V10H13M17,10V12H15V10H17M19,3A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5C3.89,21 3,20.1 3,19V5A2,2 0 0,1 5,3H6V1H8V3H16V1H18V3H19M19,19V8H5V19H19M9,14V16H7V14H9M13,14V16H11V14H13M17,14V16H15V14H17Z\" />\n</svg>"
"timetable": "<svg style=\"width:24px;height:24px\" viewBox=\"0 0 24 24\">\n <path fill=\"currentColor\" d=\"M9,10V12H7V10H9M13,10V12H11V10H13M17,10V12H15V10H17M19,3A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5C3.89,21 3,20.1 3,19V5A2,2 0 0,1 5,3H6V1H8V3H16V1H18V3H19M19,19V8H5V19H19M9,14V16H7V14H9M13,14V16H11V14H13M17,14V16H15V14H17Z\" />\n</svg>",
"analytics": "<svg style=\"width:24px;height:24px\" viewBox=\"0 0 32 32\" aria-hidden=\"true\">\n <path fill=\"currentColor\" d=\"M4,2H2V28a2,2,0,0,0,2,2H30V28H4Z\" />\n <path fill=\"currentColor\" d=\"M30,9H23v2h3.59L19,18.59l-4.29-4.3a1,1,0,0,0-1.42,0L6,21.59,7.41,23,14,16.41l4.29,4.3a1,1,0,0,0,1.42,0L28,12.41V16h2Z\" />\n</svg>"
}
@@ -11,7 +11,7 @@ export function OpenPrivacyStatement() {
).firstChild as HTMLElement;
const text = stringToHTML(/* html */ `
<div class="whatsnewTextContainer" style="overflow-y: auto; max-height: 60vh;">
<div class="whatsnewTextContainer privacyStatement">
<h2 style="margin-top: 0;">Privacy Policy</h2>
<p>At BetterSEQTA+, we take your privacy seriously. We want to be completely transparent about how we handle your data.</p>
@@ -43,6 +43,7 @@ export function OpenPrivacyStatement() {
openPopup({
header,
content: [text],
containerClass: "whatsnewContainer--scrollBody",
});
}
+30 -17
View File
@@ -19,27 +19,40 @@ export function OpenWhatsNewPopup(onDismissed?: () => void) {
const video = document.createElement("video");
const source = document.createElement("source");
source.setAttribute(
"src",
"https://raw.githubusercontent.com/BetterSEQTA/BetterSEQTA-Plus/main/src/resources/update-video.webm",
);
video.autoplay = true;
video.muted = true;
video.loop = true;
video.appendChild(source);
video.classList.add("whatsnewImg");
imageContainer.appendChild(video);
attachPopupMediaFullscreen(video);
//source.setAttribute(
// "src",
// "https://raw.githubusercontent.com/BetterSEQTA/BetterSEQTA-Plus/main/src/resources/update-video.webm",
//);
//video.autoplay = true;
//video.muted = true;
//video.loop = true;
//video.appendChild(source);
//video.classList.add("whatsnewImg");
//imageContainer.appendChild(video);
//attachPopupMediaFullscreen(video);
const heroImage = document.createElement("img");
heroImage.src =
"https://raw.githubusercontent.com/BetterSEQTA/BetterSEQTA-Plus/main/src/resources/updateimage1.webp";
heroImage.alt = "BetterSEQTA+ update preview";
heroImage.classList.add("whatsnewImg");
imageContainer.appendChild(heroImage);
attachPopupMediaFullscreen(heroImage);
const text = stringToHTML(/* html */ `
<div class="whatsnewTextContainer" style="height: 50%;overflow-y: auto;">
<h1>3.6.6 Global Search improvements!</h1>
<li>Tuned hybrid search and indexing reliability.</li>
<li>Clearer progress UI and green “Done!” when a pass finishes.</li>
<li>Merged duplicate course hits that opened the same page.</li>
<li>Reset Index reminds you to reload the tab to rebuild.</li>
<li>Index now captures all content accross the site, not just assessments and courses.</li>
<h1>3.7.0 Grade Analytics, Global Search & SEQTA Engage Improvements</h1>
<li>Added Grade Analytics, new sidebar page with grade trend charts synced from SEQTA.</li>
<li>Added Grade distribution auto-detects your schools letter scale from released marks for analytics page.</li>
<li>Added documents, notices, portals, folios, goals, and more to Global Search.</li>
<li>Added shortcuts to SEQTA Engage home page.</li>
<li>Added assessments overview and assessment weighting overrides for SEQTA Engage.</li>
<li>Added BetterSEQTA sidebar icons to SEQTA Engage.</li>
<li>Added runtime handlers for upcoming interactive theme.</li>
<li>Fixed BetterSEQTA sidebar injection issues on some pages.</li>
<li>Tweak Theme of the Month popup making it more clear about dismissals and respecting “Dont show again”.</li>
<li>Fixed duplicate-result fixes.</li>
<h1>3.6.5 - Theme of the Month, custom message folders & assessment weighting overrides</h1>
<li>Added Theme of the Month — a monthly featured theme popup with a link to view it in the theme store.</li>
+8
View File
@@ -53,6 +53,8 @@ interface OpenPopupOptions {
afterClose?: () => void;
/** When true, clears the post-update flag when this popup opens (What's New only). */
clearJustUpdated?: boolean;
/** Extra classes on `.whatsnewContainer` (e.g. `whatsnewContainer--scrollBody`). */
containerClass?: string;
}
export function openPopup({
@@ -61,6 +63,7 @@ export function openPopup({
animateSelector = ".whatsnewTextContainer *",
afterClose,
clearJustUpdated = false,
containerClass,
}: OpenPopupOptions = {}) {
pendingAfterClose = afterClose;
@@ -70,6 +73,11 @@ export function openPopup({
const container = document.createElement("div");
container.classList.add("whatsnewContainer");
if (containerClass) {
for (const name of containerClass.split(/\s+/)) {
if (name) container.classList.add(name);
}
}
if (header) container.append(header);
for (const node of content) if (node) container.append(node);
+5 -2
View File
@@ -40,8 +40,11 @@ export const SENSITIVE_DEVICE_STORAGE_KEYS_EXACT = [
"plugin.assessments-average.storage.weightings",
] as const;
/** e.g. any future `plugin.global-search.storage.*` keys in chrome.storage */
export const SENSITIVE_DEVICE_STORAGE_KEY_PREFIXES = ["plugin.global-search.storage."] as const;
/** School-specific caches; never sync across devices. */
export const SENSITIVE_DEVICE_STORAGE_KEY_PREFIXES = [
"plugin.global-search.storage.",
"bsplus.analytics.",
] as const;
const CLIENT_ONLY_CLOUD_KEYS_EXACT = [
BSPLUS_CLOUD_KNOWN_REMOTE_UPDATED_AT_KEY,