From eca8327420aff46862097d8e1e7fab82f550e747 Mon Sep 17 00:00:00 2001 From: StroepWafel Date: Tue, 7 Apr 2026 09:05:13 +0930 Subject: [PATCH] fix today's lessons tweaking out --- src/seqta/ui/dev/hideSensitiveContent.ts | 73 +++++++++++++++++------- 1 file changed, 51 insertions(+), 22 deletions(-) diff --git a/src/seqta/ui/dev/hideSensitiveContent.ts b/src/seqta/ui/dev/hideSensitiveContent.ts index a6bd92ef..145e1f60 100644 --- a/src/seqta/ui/dev/hideSensitiveContent.ts +++ b/src/seqta/ui/dev/hideSensitiveContent.ts @@ -3,6 +3,8 @@ interface ElementConfig { action: (element: Element) => void; /** When true, element is not added to processedElements so the action runs every time (e.g. overwriting container content) */ alwaysRun?: boolean; + /** When true, never add to processedElements so the action can run again after DOM resets (e.g. home day column) */ + neverMarkProcessed?: boolean; } interface ContentConfig { @@ -12,6 +14,12 @@ interface ContentConfig { // Track processed elements to avoid re-randomizing const processedElements = new WeakSet(); +/** Marks mock-generated `.day` rows so granular rules do not re-randomize them */ +const MOCK_DAY_ATTR = "data-bsp-mock-day"; + +/** Skip MutationObserver-driven reprocessing while we inject the home mock (avoids feedback loops) */ +let suppressMockMutations = false; + function debounce(func: Function, wait: number): Function { let timeout: NodeJS.Timeout; return function executedFunction(...args: any[]) { @@ -44,19 +52,19 @@ function getRandomDate(): Date { const contentConfig: ContentConfig = { lessonTitle: { - selector: ".day h2", + selector: `.day:not([${MOCK_DAY_ATTR}]) h2`, action: (element) => { element.textContent = getRandomElement(mockData.subjects); }, }, teacher: { - selector: ".day h3:first-of-type", + selector: `.day:not([${MOCK_DAY_ATTR}]) h3:first-of-type`, action: (element) => { element.textContent = getRandomElement(mockData.teachers); }, }, classroom: { - selector: ".day h3:last-of-type", + selector: `.day:not([${MOCK_DAY_ATTR}]) h3:last-of-type`, action: (element) => { element.textContent = getRandomElement(mockData.classrooms); }, @@ -283,13 +291,28 @@ const contentConfig: ContentConfig = { // Home page: replace entire day with mock schedule (care + 7 lessons 8:55–3:15) homeDayContainer: { selector: "#day-container", - alwaysRun: true, + neverMarkProcessed: true, action: (element) => { const container = element as HTMLElement; if (!container.closest(".timetable-container")) return; // only on home + if (container.classList.contains("loading") || container.innerHTML.trim() === "") { + delete container.dataset.bspMockSchedule; + return; + } + if ( + container.dataset.bspMockSchedule === "1" && + container.querySelector(`[${MOCK_DAY_ATTR}]`) + ) { + return; + } + suppressMockMutations = true; const schedule = getMockDaySchedule(); container.innerHTML = schedule; container.classList.remove("loading"); + container.dataset.bspMockSchedule = "1"; + requestAnimationFrame(() => { + suppressMockMutations = false; + }); }, }, }; @@ -665,7 +688,7 @@ function getMockDaySchedule(): string { return blocks .map( (b, i) => - `
+ `

${b.title}

${b.teacher}

${b.room}

@@ -758,12 +781,12 @@ const debouncedProcessElements = debounce(processNewElements, 1); function processNewElements() { Object.entries(contentConfig).forEach(([_, config]) => { - const { selector, action, alwaysRun } = config; + const { selector, action, alwaysRun, neverMarkProcessed } = config; const elements = document.querySelectorAll(selector); elements.forEach((element: Element) => { - if (alwaysRun || !processedElements.has(element)) { + if (alwaysRun || neverMarkProcessed || !processedElements.has(element)) { action(element); - if (!alwaysRun) { + if (!alwaysRun && !neverMarkProcessed) { processedElements.add(element); } } @@ -772,7 +795,6 @@ function processNewElements() { } let observer: MutationObserver | null = null; -let intervalId: NodeJS.Timeout | null = null; export default function hideSensitiveContent() { // Initial processing of existing elements @@ -781,6 +803,8 @@ export default function hideSensitiveContent() { // Set up MutationObserver if not already created if (!observer) { observer = new MutationObserver((mutations) => { + if (suppressMockMutations) return; + let shouldProcess = false; mutations.forEach((mutation) => { @@ -802,9 +826,25 @@ export default function hideSensitiveContent() { }); } - // Also trigger on large DOM replacements (like page navigation) + // Large DOM replacements (e.g. page navigation). Skip only when #day-container gains many *mock* rows (our inject). if (mutation.addedNodes.length > 5 || mutation.removedNodes.length > 5) { - shouldProcess = true; + const target = mutation.target as Element; + if (target.id === "day-container") { + for (const node of mutation.addedNodes) { + if (node.nodeType === Node.ELEMENT_NODE) { + const el = node as Element; + if ( + el.classList?.contains("day") && + !el.hasAttribute(MOCK_DAY_ATTR) + ) { + shouldProcess = true; + break; + } + } + } + } else { + shouldProcess = true; + } } } @@ -833,13 +873,6 @@ export default function hideSensitiveContent() { attributeFilter: ['class', 'id'] // Watch for class/id changes that might affect our selectors }); } - - // Fallback: periodic check for new elements (especially useful for SPA navigation) - if (!intervalId) { - intervalId = setInterval(() => { - debouncedProcessElements(); - }, 500); // Check every 500ms as a fallback - } } // Function to stop observing (useful for cleanup) @@ -848,8 +881,4 @@ export function stopHidingSensitiveContent() { observer.disconnect(); observer = null; } - if (intervalId) { - clearInterval(intervalId); - intervalId = null; - } }