mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-06-06 03:34:40 +00:00
fix today's lessons tweaking out (#420)
This commit is contained in:
@@ -3,6 +3,8 @@ interface ElementConfig {
|
|||||||
action: (element: Element) => void;
|
action: (element: Element) => void;
|
||||||
/** When true, element is not added to processedElements so the action runs every time (e.g. overwriting container content) */
|
/** When true, element is not added to processedElements so the action runs every time (e.g. overwriting container content) */
|
||||||
alwaysRun?: boolean;
|
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 {
|
interface ContentConfig {
|
||||||
@@ -12,6 +14,12 @@ interface ContentConfig {
|
|||||||
// Track processed elements to avoid re-randomizing
|
// Track processed elements to avoid re-randomizing
|
||||||
const processedElements = new WeakSet<Element>();
|
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 {
|
function debounce(func: Function, wait: number): Function {
|
||||||
let timeout: NodeJS.Timeout;
|
let timeout: NodeJS.Timeout;
|
||||||
return function executedFunction(...args: any[]) {
|
return function executedFunction(...args: any[]) {
|
||||||
@@ -44,19 +52,19 @@ function getRandomDate(): Date {
|
|||||||
|
|
||||||
const contentConfig: ContentConfig = {
|
const contentConfig: ContentConfig = {
|
||||||
lessonTitle: {
|
lessonTitle: {
|
||||||
selector: ".day h2",
|
selector: `.day:not([${MOCK_DAY_ATTR}]) h2`,
|
||||||
action: (element) => {
|
action: (element) => {
|
||||||
element.textContent = getRandomElement(mockData.subjects);
|
element.textContent = getRandomElement(mockData.subjects);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
teacher: {
|
teacher: {
|
||||||
selector: ".day h3:first-of-type",
|
selector: `.day:not([${MOCK_DAY_ATTR}]) h3:first-of-type`,
|
||||||
action: (element) => {
|
action: (element) => {
|
||||||
element.textContent = getRandomElement(mockData.teachers);
|
element.textContent = getRandomElement(mockData.teachers);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
classroom: {
|
classroom: {
|
||||||
selector: ".day h3:last-of-type",
|
selector: `.day:not([${MOCK_DAY_ATTR}]) h3:last-of-type`,
|
||||||
action: (element) => {
|
action: (element) => {
|
||||||
element.textContent = getRandomElement(mockData.classrooms);
|
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)
|
// Home page: replace entire day with mock schedule (care + 7 lessons 8:55–3:15)
|
||||||
homeDayContainer: {
|
homeDayContainer: {
|
||||||
selector: "#day-container",
|
selector: "#day-container",
|
||||||
alwaysRun: true,
|
neverMarkProcessed: true,
|
||||||
action: (element) => {
|
action: (element) => {
|
||||||
const container = element as HTMLElement;
|
const container = element as HTMLElement;
|
||||||
if (!container.closest(".timetable-container")) return; // only on home
|
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();
|
const schedule = getMockDaySchedule();
|
||||||
container.innerHTML = schedule;
|
container.innerHTML = schedule;
|
||||||
container.classList.remove("loading");
|
container.classList.remove("loading");
|
||||||
|
container.dataset.bspMockSchedule = "1";
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
suppressMockMutations = false;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -665,7 +688,7 @@ function getMockDaySchedule(): string {
|
|||||||
return blocks
|
return blocks
|
||||||
.map(
|
.map(
|
||||||
(b, i) =>
|
(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>
|
<h2>${b.title}</h2>
|
||||||
<h3>${b.teacher}</h3>
|
<h3>${b.teacher}</h3>
|
||||||
<h3>${b.room}</h3>
|
<h3>${b.room}</h3>
|
||||||
@@ -758,12 +781,12 @@ const debouncedProcessElements = debounce(processNewElements, 1);
|
|||||||
|
|
||||||
function processNewElements() {
|
function processNewElements() {
|
||||||
Object.entries(contentConfig).forEach(([_, config]) => {
|
Object.entries(contentConfig).forEach(([_, config]) => {
|
||||||
const { selector, action, alwaysRun } = config;
|
const { selector, action, alwaysRun, neverMarkProcessed } = config;
|
||||||
const elements = document.querySelectorAll(selector);
|
const elements = document.querySelectorAll(selector);
|
||||||
elements.forEach((element: Element) => {
|
elements.forEach((element: Element) => {
|
||||||
if (alwaysRun || !processedElements.has(element)) {
|
if (alwaysRun || neverMarkProcessed || !processedElements.has(element)) {
|
||||||
action(element);
|
action(element);
|
||||||
if (!alwaysRun) {
|
if (!alwaysRun && !neverMarkProcessed) {
|
||||||
processedElements.add(element);
|
processedElements.add(element);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -772,7 +795,6 @@ function processNewElements() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let observer: MutationObserver | null = null;
|
let observer: MutationObserver | null = null;
|
||||||
let intervalId: NodeJS.Timeout | null = null;
|
|
||||||
|
|
||||||
export default function hideSensitiveContent() {
|
export default function hideSensitiveContent() {
|
||||||
// Initial processing of existing elements
|
// Initial processing of existing elements
|
||||||
@@ -781,6 +803,8 @@ export default function hideSensitiveContent() {
|
|||||||
// Set up MutationObserver if not already created
|
// Set up MutationObserver if not already created
|
||||||
if (!observer) {
|
if (!observer) {
|
||||||
observer = new MutationObserver((mutations) => {
|
observer = new MutationObserver((mutations) => {
|
||||||
|
if (suppressMockMutations) return;
|
||||||
|
|
||||||
let shouldProcess = false;
|
let shouldProcess = false;
|
||||||
|
|
||||||
mutations.forEach((mutation) => {
|
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) {
|
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
|
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)
|
// Function to stop observing (useful for cleanup)
|
||||||
@@ -848,8 +881,4 @@ export function stopHidingSensitiveContent() {
|
|||||||
observer.disconnect();
|
observer.disconnect();
|
||||||
observer = null;
|
observer = null;
|
||||||
}
|
}
|
||||||
if (intervalId) {
|
|
||||||
clearInterval(intervalId);
|
|
||||||
intervalId = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user