fix today's lessons tweaking out

This commit is contained in:
2026-04-07 09:05:13 +09:30
parent 05cf380e86
commit eca8327420
+51 -22
View File
@@ -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<Element>();
/** 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:553: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) =>
`<div class="day" style="--item-colour: ${colours[i % colours.length]};">
`<div class="day" ${MOCK_DAY_ATTR} style="--item-colour: ${colours[i % colours.length]};">
<h2>${b.title}</h2>
<h3>${b.teacher}</h3>
<h3>${b.room}</h3>
@@ -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;
}
}