refactor(sidebar): analytics start below courses

This commit is contained in:
2026-06-22 18:09:28 +09:30
parent f570e47e27
commit fadbca5969
7 changed files with 147 additions and 47 deletions
+7 -33
View File
@@ -2794,39 +2794,9 @@ body {
.defaultWelcomeWrapper { .defaultWelcomeWrapper {
background: unset !important; background: unset !important;
} }
.clr-swatches button::after,
.clr-dark .clr-preview::after, /* Coloris (timetable subject colours): cosmetic only — do not unset
.clr-field button::after { transforms/animations on ::after (breaks picker reopen). */
opacity: unset;
padding-top: unset;
-webkit-transform: unset;
transform: unset;
-webkit-transform-origin: unset;
transform-origin: unset;
visibility: unset;
-webkit-animation-name: unset !important;
animation-name: unset !important;
background-color: currentColor !important;
}
.clr-swatches button {
align-items: unset;
display: block;
padding: unset;
transition: none;
}
.clr-clear {
display: none !important;
}
.clr-preview::before,
.clr-preview::after {
visibility: unset;
-webkit-transform-origin: unset;
transform-origin: unset;
-webkit-transform: unset;
transform: unset;
padding-top: unset;
opacity: unset;
}
#clr-color-preview { #clr-color-preview {
margin: 15px 0 20px 20px; margin: 15px 0 20px 20px;
border: 0; border: 0;
@@ -2835,6 +2805,10 @@ body {
cursor: pointer; cursor: pointer;
} }
.clr-swatches button {
border-radius: 4px;
}
.dark .dark
[class*="MessageList__MessageList___"] [class*="MessageList__MessageList___"]
> ol > ol
@@ -2,8 +2,14 @@ import type { Plugin } from "@/plugins/core/types";
import MenuitemSVGKey from "@/seqta/content/MenuItemSVGKey.json"; import MenuitemSVGKey from "@/seqta/content/MenuItemSVGKey.json";
import { waitForElm } from "@/seqta/utils/waitForElm"; import { waitForElm } from "@/seqta/utils/waitForElm";
import { isSeqtaEngageExperience } from "@/seqta/utils/isSeqtaEngage"; import { isSeqtaEngageExperience } from "@/seqta/utils/isSeqtaEngage";
import { processMenuItemNode } from "@/seqta/utils/sidebarMenuIcons"; import {
ChangeMenuItemPositions,
ensureAnalyticsMenuOrder,
insertMenuItemAfterKey,
processMenuItemNode,
} from "@/seqta/utils/sidebarMenuIcons";
import { MenuOptionsOpen } from "@/seqta/utils/Openers/OpenMenuOptions"; import { MenuOptionsOpen } from "@/seqta/utils/Openers/OpenMenuOptions";
import { settingsState } from "@/seqta/utils/listeners/SettingsState";
import { import {
applyMenuItemVisibility, applyMenuItemVisibility,
isMenuItemHidden, isMenuItemHidden,
@@ -45,11 +51,14 @@ const gradeAnalyticsPlugin: Plugin<{}> = {
analyticsItem.dataset.betterseqta = "true"; analyticsItem.dataset.betterseqta = "true";
analyticsItem.innerHTML = `<label>${ANALYTICS_MENU_ICON}<span>Analytics</span></label>`; analyticsItem.innerHTML = `<label>${ANALYTICS_MENU_ICON}<span>Analytics</span></label>`;
const homeButton = document.getElementById("homebutton"); const placeAnalyticsItem = () => {
if (homeButton?.parentElement === menuList) { insertMenuItemAfterKey(menuList, analyticsItem, "courses");
homeButton.insertAdjacentElement("afterend", analyticsItem); };
} else {
menuList.insertBefore(analyticsItem, menuList.firstChild); placeAnalyticsItem();
ensureAnalyticsMenuOrder();
if (settingsState.menuorder.length > 0) {
ChangeMenuItemPositions(settingsState.menuorder);
} }
processMenuItemNode(analyticsItem); processMenuItemNode(analyticsItem);
@@ -59,10 +68,10 @@ const gradeAnalyticsPlugin: Plugin<{}> = {
if (MenuOptionsOpen) return; if (MenuOptionsOpen) return;
if (isMenuItemHidden("analytics")) return; if (isMenuItemHidden("analytics")) return;
if (!menuList.contains(analyticsItem)) { if (!menuList.contains(analyticsItem)) {
if (homeButton?.parentElement === menuList) { placeAnalyticsItem();
homeButton.insertAdjacentElement("afterend", analyticsItem); ensureAnalyticsMenuOrder();
} else { if (settingsState.menuorder.length > 0) {
menuList.insertBefore(analyticsItem, menuList.firstChild); ChangeMenuItemPositions(settingsState.menuorder);
} }
processMenuItemNode(analyticsItem); processMenuItemNode(analyticsItem);
applyMenuItemVisibility(); applyMenuItemVisibility();
+7 -1
View File
@@ -1,6 +1,9 @@
import { settingsState } from "@/seqta/utils/listeners/SettingsState"; import { settingsState } from "@/seqta/utils/listeners/SettingsState";
import type { Plugin } from "../../core/types"; import type { Plugin } from "../../core/types";
import { convertTo12HourFormat } from "@/seqta/utils/convertTo12HourFormat"; import {
attachTimetableColorisRecovery,
resetStuckColorisPicker,
} from "@/seqta/utils/timetableColoris";
import { waitForElm } from "@/seqta/utils/waitForElm"; import { waitForElm } from "@/seqta/utils/waitForElm";
const timetablePlugin: Plugin<{}, {}> = { const timetablePlugin: Plugin<{}, {}> = {
@@ -63,6 +66,9 @@ function resetTimetableStyles(): void {
} }
async function handleTimetable(): Promise<void> { async function handleTimetable(): Promise<void> {
attachTimetableColorisRecovery();
resetStuckColorisPicker();
// SEQTA uses `.times` blocks on entries, not necessarily `.time`; avoid infinite polling on a missing selector. // SEQTA uses `.times` blocks on entries, not necessarily `.time`; avoid infinite polling on a missing selector.
try { try {
await waitForElm(".timetablepage .times, .timetablepage .entry.class", true, 50, 200); await waitForElm(".timetablepage .times, .timetablepage .entry.class", true, 50, 200);
+2
View File
@@ -30,6 +30,7 @@ import { loadAnalyticsPage } from "@/plugins/built-in/gradeAnalytics/loadAnalyti
import { runStartupPopupQueue } from "@/seqta/utils/Openers/StartupPopupQueue"; import { runStartupPopupQueue } from "@/seqta/utils/Openers/StartupPopupQueue";
import { updateTimetableTimes } from "@/seqta/utils/updateTimetableTimes"; import { updateTimetableTimes } from "@/seqta/utils/updateTimetableTimes";
import { attachTimetableColorisRecovery } from "@/seqta/utils/timetableColoris";
// JSON content // JSON content
import { observeMenuItemPosition } from "@/seqta/utils/sidebarMenuIcons"; import { observeMenuItemPosition } from "@/seqta/utils/sidebarMenuIcons";
@@ -247,6 +248,7 @@ async function LoadPageElements(): Promise<void> {
className: "timetablepage", className: "timetablepage",
}, },
async () => { async () => {
attachTimetableColorisRecovery();
await updateTimetableTimes(); await updateTimetableTimes();
}, },
); );
+13 -3
View File
@@ -1,6 +1,7 @@
import type { SettingsState } from "@/types/storage"; import type { SettingsState } from "@/types/storage";
import { settingsState } from "../listeners/SettingsState"; import { settingsState } from "../listeners/SettingsState";
import { applyMenuItemVisibility } from "../menuItemVisibility"; import { applyMenuItemVisibility } from "../menuItemVisibility";
import { insertKeyAfterInOrder } from "@/seqta/utils/sidebarMenuIcons";
import stringToHTML from "../stringToHTML"; import stringToHTML from "../stringToHTML";
import Sortable from "sortablejs"; import Sortable from "sortablejs";
@@ -57,9 +58,18 @@ export function OpenMenuOptions() {
key && key &&
settingsState.defaultmenuorder.indexOf(key) === -1 settingsState.defaultmenuorder.indexOf(key) === -1
) { ) {
let newdefaultmenuorder = settingsState.defaultmenuorder; if (key === "analytics") {
newdefaultmenuorder.push(key); settingsState.defaultmenuorder = insertKeyAfterInOrder(
settingsState.defaultmenuorder = newdefaultmenuorder; settingsState.defaultmenuorder,
key,
"courses",
);
} else {
settingsState.defaultmenuorder = [
...settingsState.defaultmenuorder,
key,
];
}
} }
} }
} }
+52
View File
@@ -12,6 +12,58 @@ import { isSeqtaEngageExperience } from "@/seqta/utils/isSeqtaEngage";
const BETTERSEQTA_ICON_ATTR = "data-betterseqta-icon"; const BETTERSEQTA_ICON_ATTR = "data-betterseqta-icon";
export function insertMenuItemAfterKey(
menuList: HTMLElement,
item: HTMLElement,
afterKey: string,
): void {
const after = menuList.querySelector(
`:scope > li[data-key="${afterKey}"], :scope > section[data-key="${afterKey}"]`,
);
if (after instanceof HTMLElement) {
after.insertAdjacentElement("afterend", item);
} else {
menuList.appendChild(item);
}
}
export function insertKeyAfterInOrder(
order: string[],
key: string,
afterKey: string,
): string[] {
if (order.includes(key)) return order;
const copy = [...order];
const afterIdx = copy.indexOf(afterKey);
if (afterIdx >= 0) {
copy.splice(afterIdx + 1, 0, key);
} else {
copy.push(key);
}
return copy;
}
/** Default Analytics immediately below Courses in saved menu order. */
export function ensureAnalyticsMenuOrder(): void {
if (!settingsState.defaultmenuorder.includes("analytics")) {
settingsState.defaultmenuorder = insertKeyAfterInOrder(
settingsState.defaultmenuorder,
"analytics",
"courses",
);
}
if (
settingsState.menuorder.length > 0 &&
!settingsState.menuorder.includes("analytics")
) {
settingsState.menuorder = insertKeyAfterInOrder(
settingsState.menuorder,
"analytics",
"courses",
);
}
}
function getMenuLabel(element: HTMLElement): HTMLElement | null { function getMenuLabel(element: HTMLElement): HTMLElement | null {
const label = element.querySelector(":scope > label"); const label = element.querySelector(":scope > label");
return label instanceof HTMLElement ? label : null; return label instanceof HTMLElement ? label : null;
+47
View File
@@ -0,0 +1,47 @@
/**
* SEQTA timetable uses Coloris for subject colours. Extension CSS previously
* unset Coloris ::after animations, which left the picker unable to reopen.
* This module clears stuck `clr-open` / hidden picker state after each use.
*/
let attached = false;
export function resetStuckColorisPicker(): void {
document.body.classList.remove("clr-open");
document.documentElement.classList.remove("clr-open");
for (const picker of document.querySelectorAll(".clr-picker")) {
picker.classList.remove("clr-open");
if (picker instanceof HTMLElement) {
picker.style.removeProperty("display");
picker.style.removeProperty("pointer-events");
picker.style.removeProperty("visibility");
}
}
for (const field of document.querySelectorAll(".clr-field")) {
field.classList.remove("clr-open");
}
}
export function attachTimetableColorisRecovery(): void {
if (attached) return;
attached = true;
const afterColorisEvent = () => {
requestAnimationFrame(() => resetStuckColorisPicker());
};
document.addEventListener("coloris:pick", afterColorisEvent);
document.addEventListener("coloris:close", afterColorisEvent);
document.addEventListener(
"click",
(event) => {
const target = event.target as HTMLElement;
if (!target.closest(".timetablepage [title='Choose a colour']")) return;
resetStuckColorisPicker();
},
true,
);
}