mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-08-28 17:21:05 +00:00
chore: reduce complexity
This commit is contained in:
@@ -1040,7 +1040,15 @@ function resolveTabSetClasses(): Record<string, string> {
|
||||
return resolved;
|
||||
}
|
||||
|
||||
async function buildWeightingsTabContent(api: any, sheet: HTMLElement) {
|
||||
interface WeightingTabContext {
|
||||
assessmentID?: string;
|
||||
autoWeight?: number;
|
||||
override?: number | string;
|
||||
weightingUnavailable: boolean;
|
||||
statusNote: string;
|
||||
}
|
||||
|
||||
async function resolveWeightingTabContext(api: any): Promise<WeightingTabContext> {
|
||||
const selectedItem = document.querySelector(
|
||||
"[class*='AssessmentItem__AssessmentItem___'][class*='selected___']",
|
||||
) as HTMLElement | null;
|
||||
@@ -1056,14 +1064,11 @@ async function buildWeightingsTabContent(api: any, sheet: HTMLElement) {
|
||||
? (api.storage.weightings?.[assessmentID] as WeightingEntry | undefined)
|
||||
: undefined;
|
||||
const rawWeight = entry?.weight;
|
||||
|
||||
const weightingUnavailable = rawWeight === "N/A";
|
||||
|
||||
const autoWeight =
|
||||
rawWeight && rawWeight !== "processing" && rawWeight !== "N/A"
|
||||
? rawWeight
|
||||
: undefined;
|
||||
|
||||
const override = assessmentID
|
||||
? api.storage.weightingOverrides?.[assessmentID]
|
||||
: undefined;
|
||||
@@ -1076,6 +1081,22 @@ async function buildWeightingsTabContent(api: any, sheet: HTMLElement) {
|
||||
? "No weighting was found in the marksheet. Set one manually."
|
||||
: "Overrides the auto-detected value.";
|
||||
|
||||
return {
|
||||
assessmentID,
|
||||
autoWeight,
|
||||
override,
|
||||
weightingUnavailable,
|
||||
statusNote,
|
||||
};
|
||||
}
|
||||
|
||||
function renderWeightingTabHtml(
|
||||
sheet: HTMLElement,
|
||||
context: WeightingTabContext,
|
||||
) {
|
||||
const { assessmentID, autoWeight, override, weightingUnavailable, statusNote } =
|
||||
context;
|
||||
|
||||
sheet.innerHTML = `
|
||||
<style>
|
||||
#betterseqta-weight-override::placeholder {
|
||||
@@ -1120,9 +1141,13 @@ async function buildWeightingsTabContent(api: any, sheet: HTMLElement) {
|
||||
${!assessmentID ? `<p style="font-size:12px;color:rgba(255,80,80,0.8);margin-top:8px">Assessment not yet indexed — try refreshing.</p>` : ""}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
if (!assessmentID) return;
|
||||
|
||||
function attachWeightingInputHandlers(
|
||||
sheet: HTMLElement,
|
||||
api: any,
|
||||
assessmentID: string,
|
||||
) {
|
||||
const input = sheet.querySelector(
|
||||
"#betterseqta-weight-override",
|
||||
) as HTMLInputElement;
|
||||
@@ -1132,20 +1157,17 @@ async function buildWeightingsTabContent(api: any, sheet: HTMLElement) {
|
||||
|
||||
const save = () => {
|
||||
const raw = input.value.trim();
|
||||
if (raw === "") {
|
||||
const result = saveWeightingOverride(api, assessmentID, "");
|
||||
if (!result.ok) return;
|
||||
input.style.borderColor = "rgba(128,128,128,0.3)";
|
||||
} else {
|
||||
const result = saveWeightingOverride(api, assessmentID, raw);
|
||||
if (!result.ok) {
|
||||
const result = saveWeightingOverride(api, assessmentID, raw);
|
||||
if (!result.ok) {
|
||||
if (raw !== "") {
|
||||
input.style.borderColor = "rgba(255,80,80,0.6)";
|
||||
statusEl.textContent = result.error ?? "Invalid. Must be 0 or greater";
|
||||
statusEl.style.color = "rgba(255,80,80,0.8)";
|
||||
return;
|
||||
}
|
||||
input.style.borderColor = "rgba(128,128,128,0.3)";
|
||||
return;
|
||||
}
|
||||
|
||||
input.style.borderColor = "rgba(128,128,128,0.3)";
|
||||
statusEl.textContent = "Saved";
|
||||
statusEl.style.color = "";
|
||||
setTimeout(() => (statusEl.textContent = ""), 2000);
|
||||
@@ -1165,6 +1187,13 @@ async function buildWeightingsTabContent(api: any, sheet: HTMLElement) {
|
||||
});
|
||||
}
|
||||
|
||||
async function buildWeightingsTabContent(api: any, sheet: HTMLElement) {
|
||||
const context = await resolveWeightingTabContext(api);
|
||||
renderWeightingTabHtml(sheet, context);
|
||||
if (!context.assessmentID) return;
|
||||
attachWeightingInputHandlers(sheet, api, context.assessmentID);
|
||||
}
|
||||
|
||||
export function injectWeightingsTab(api: any) {
|
||||
const tabList = document.querySelector(
|
||||
'[class*="TabSet__tabs___"]',
|
||||
|
||||
@@ -128,102 +128,9 @@ export async function mountSearchBar(
|
||||
}
|
||||
};
|
||||
|
||||
const updateProgressDisplay = () => {
|
||||
const indexingStoppedThisTick = indexingJustStoppedFlag;
|
||||
indexingJustStoppedFlag = false;
|
||||
|
||||
const active = isIndexing && totalJobs > 0;
|
||||
|
||||
// Stray pulses (missing total, 0 completed, etc.) used to hit the idle
|
||||
// branch and call clearDoneFlashTimer(), killing the Done! hold/fade.
|
||||
if (doneFlashTimer !== null || doneFadeTimer !== null) {
|
||||
if (!active) {
|
||||
return;
|
||||
}
|
||||
clearDoneFlashTimer();
|
||||
}
|
||||
|
||||
const completionEligible =
|
||||
ranIndexingCycle &&
|
||||
!active &&
|
||||
totalJobs > 0 &&
|
||||
(completedJobs >= totalJobs || indexingStoppedThisTick);
|
||||
|
||||
if (active) {
|
||||
clearDoneFlashTimer();
|
||||
progressBarWrapper.classList.remove("is-rough-complete");
|
||||
progressText.classList.remove(
|
||||
"is-rough",
|
||||
"is-fading-done",
|
||||
"is-done-message",
|
||||
);
|
||||
const percentage = Math.round((completedJobs / totalJobs) * 100);
|
||||
progressBar.style.width = `${Math.max(2, percentage)}%`;
|
||||
progressBarWrapper.classList.add("is-active");
|
||||
searchAnchor.classList.add("is-indexing");
|
||||
searchButton.classList.add("is-indexing");
|
||||
|
||||
if (indexingStatus) {
|
||||
progressText.textContent = `${truncateStatus(indexingStatus)} · ${percentage}%`;
|
||||
} else {
|
||||
progressText.textContent = `Indexing ${completedJobs}/${totalJobs} (${percentage}%)`;
|
||||
}
|
||||
progressText.classList.add("is-active");
|
||||
return;
|
||||
}
|
||||
|
||||
if (completionEligible) {
|
||||
// Duplicate end-of-run ticks must not reschedule hold/fade timers
|
||||
if (doneFlashTimer !== null || doneFadeTimer !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rough =
|
||||
indexingStatus != null && statusLooksRough(indexingStatus);
|
||||
|
||||
progressBar.style.width = "0%";
|
||||
progressBarWrapper.classList.remove("is-active");
|
||||
searchAnchor.classList.remove("is-indexing");
|
||||
searchButton.classList.remove("is-indexing");
|
||||
progressText.classList.remove("is-fading-done");
|
||||
|
||||
progressText.textContent = rough ? truncateStatus(indexingStatus!, 52) : "Done!";
|
||||
if (rough) {
|
||||
progressText.classList.add("is-rough");
|
||||
progressBarWrapper.classList.add("is-rough-complete");
|
||||
} else {
|
||||
progressText.classList.remove("is-rough");
|
||||
progressBarWrapper.classList.remove("is-rough-complete");
|
||||
}
|
||||
progressText.classList.add("is-active", "is-done-message");
|
||||
|
||||
doneFlashTimer = setTimeout(() => {
|
||||
doneFlashTimer = null;
|
||||
progressText.classList.add("is-fading-done");
|
||||
doneFadeTimer = setTimeout(() => {
|
||||
doneFadeTimer = null;
|
||||
ranIndexingCycle = false;
|
||||
indexingStatus = null;
|
||||
progressBar.style.width = "0%";
|
||||
progressBarWrapper.classList.remove("is-active");
|
||||
progressBarWrapper.classList.remove("is-rough-complete");
|
||||
searchAnchor.classList.remove("is-indexing");
|
||||
searchButton.classList.remove("is-indexing");
|
||||
progressText.classList.remove(
|
||||
"is-active",
|
||||
"is-rough",
|
||||
"is-fading-done",
|
||||
"is-done-message",
|
||||
);
|
||||
progressText.textContent = "";
|
||||
}, DONE_FADE_MS);
|
||||
}, DONE_HOLD_MS);
|
||||
return;
|
||||
}
|
||||
|
||||
const resetIdleProgressUi = () => {
|
||||
clearDoneFlashTimer();
|
||||
progressBarWrapper.classList.remove("is-active");
|
||||
progressBarWrapper.classList.remove("is-rough-complete");
|
||||
progressBarWrapper.classList.remove("is-active", "is-rough-complete");
|
||||
searchAnchor.classList.remove("is-indexing");
|
||||
searchButton.classList.remove("is-indexing");
|
||||
progressText.classList.remove(
|
||||
@@ -238,6 +145,75 @@ export async function mountSearchBar(
|
||||
indexingStatus = null;
|
||||
};
|
||||
|
||||
const showActiveIndexingUi = (percentage: number) => {
|
||||
clearDoneFlashTimer();
|
||||
progressBarWrapper.classList.remove("is-rough-complete");
|
||||
progressText.classList.remove("is-rough", "is-fading-done", "is-done-message");
|
||||
progressBar.style.width = `${Math.max(2, percentage)}%`;
|
||||
progressBarWrapper.classList.add("is-active");
|
||||
searchAnchor.classList.add("is-indexing");
|
||||
searchButton.classList.add("is-indexing");
|
||||
progressText.textContent = indexingStatus
|
||||
? `${truncateStatus(indexingStatus)} · ${percentage}%`
|
||||
: `Indexing ${completedJobs}/${totalJobs} (${percentage}%)`;
|
||||
progressText.classList.add("is-active");
|
||||
};
|
||||
|
||||
const scheduleCompletionFlash = (rough: boolean) => {
|
||||
progressBar.style.width = "0%";
|
||||
progressBarWrapper.classList.remove("is-active");
|
||||
searchAnchor.classList.remove("is-indexing");
|
||||
searchButton.classList.remove("is-indexing");
|
||||
progressText.classList.remove("is-fading-done");
|
||||
progressText.textContent = rough ? truncateStatus(indexingStatus!, 52) : "Done!";
|
||||
progressText.classList.toggle("is-rough", rough);
|
||||
progressBarWrapper.classList.toggle("is-rough-complete", rough);
|
||||
progressText.classList.add("is-active", "is-done-message");
|
||||
|
||||
doneFlashTimer = setTimeout(() => {
|
||||
doneFlashTimer = null;
|
||||
progressText.classList.add("is-fading-done");
|
||||
doneFadeTimer = setTimeout(() => {
|
||||
doneFadeTimer = null;
|
||||
resetIdleProgressUi();
|
||||
}, DONE_FADE_MS);
|
||||
}, DONE_HOLD_MS);
|
||||
};
|
||||
|
||||
const updateProgressDisplay = () => {
|
||||
const indexingStoppedThisTick = indexingJustStoppedFlag;
|
||||
indexingJustStoppedFlag = false;
|
||||
|
||||
const active = isIndexing && totalJobs > 0;
|
||||
|
||||
// Stray pulses (missing total, 0 completed, etc.) used to hit the idle
|
||||
// branch and call clearDoneFlashTimer(), killing the Done! hold/fade.
|
||||
if (doneFlashTimer !== null || doneFadeTimer !== null) {
|
||||
if (!active) return;
|
||||
clearDoneFlashTimer();
|
||||
}
|
||||
|
||||
const completionEligible =
|
||||
ranIndexingCycle &&
|
||||
!active &&
|
||||
totalJobs > 0 &&
|
||||
(completedJobs >= totalJobs || indexingStoppedThisTick);
|
||||
|
||||
if (active) {
|
||||
showActiveIndexingUi(Math.round((completedJobs / totalJobs) * 100));
|
||||
return;
|
||||
}
|
||||
|
||||
if (completionEligible) {
|
||||
if (doneFlashTimer !== null || doneFadeTimer !== null) return;
|
||||
const rough = indexingStatus != null && statusLooksRough(indexingStatus);
|
||||
scheduleCompletionFlash(rough);
|
||||
return;
|
||||
}
|
||||
|
||||
resetIdleProgressUi();
|
||||
};
|
||||
|
||||
// Listen for indexing progress events
|
||||
const progressHandler = (event: CustomEvent) => {
|
||||
const { completed, total, indexing, status } = event.detail as {
|
||||
|
||||
@@ -105,6 +105,78 @@ function appendFolderBadgeContent(badge: HTMLElement, folder: Folder): void {
|
||||
badge.appendChild(document.createTextNode(folder.name));
|
||||
}
|
||||
|
||||
const MESSAGE_LIST_ITEM_SELECTOR =
|
||||
"[class*='MessageList__MessageList___'] ol > li[data-message]";
|
||||
|
||||
function getMessageListItems(): NodeListOf<Element> {
|
||||
return document.querySelectorAll(MESSAGE_LIST_ITEM_SELECTOR);
|
||||
}
|
||||
|
||||
function clearMessageListBadges(
|
||||
messageItems: NodeListOf<Element>,
|
||||
restoreSubjectPlain: (subject: Element) => void,
|
||||
): void {
|
||||
for (const li of messageItems) {
|
||||
const subject = li.querySelector("[class*='MessageList__subject___']");
|
||||
if (
|
||||
subject &&
|
||||
(subject.querySelector(".bsplus-msg-badges") ||
|
||||
subject.querySelector(".bsplus-subject-text"))
|
||||
) {
|
||||
restoreSubjectPlain(subject);
|
||||
} else {
|
||||
li.querySelector(".bsplus-msg-badges")?.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getAssignedFolderIds(
|
||||
msgId: string,
|
||||
assignments: Record<string, string[]>,
|
||||
): string[] {
|
||||
return Object.entries(assignments)
|
||||
.filter(([, messageIds]) => messageIds.includes(msgId))
|
||||
.map(([folderId]) => folderId);
|
||||
}
|
||||
|
||||
function ensureMessageBadgeContainer(li: Element): HTMLElement {
|
||||
const existing = li.querySelector(".bsplus-msg-badges") as HTMLElement | null;
|
||||
if (existing) return existing;
|
||||
|
||||
const badgeContainer = document.createElement("div");
|
||||
badgeContainer.className = "bsplus-msg-badges";
|
||||
const subject = li.querySelector("[class*='MessageList__subject___']");
|
||||
if (subject) {
|
||||
if (!subject.querySelector(".bsplus-subject-text")) {
|
||||
const textWrap = document.createElement("span");
|
||||
textWrap.className = "bsplus-subject-text";
|
||||
textWrap.textContent = subject.textContent;
|
||||
subject.textContent = "";
|
||||
subject.appendChild(textWrap);
|
||||
}
|
||||
subject.appendChild(badgeContainer);
|
||||
} else {
|
||||
li.appendChild(badgeContainer);
|
||||
}
|
||||
return badgeContainer;
|
||||
}
|
||||
|
||||
function createFolderBadge(
|
||||
folder: Folder,
|
||||
onFilter: (folderId: string) => void,
|
||||
): HTMLElement {
|
||||
const badge = document.createElement("span");
|
||||
badge.className = "bsplus-msg-badge";
|
||||
badge.style.background = folder.color;
|
||||
appendFolderBadgeContent(badge, folder);
|
||||
badge.title = `Filter by "${folder.name}"`;
|
||||
badge.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
onFilter(folder.id);
|
||||
});
|
||||
return badge;
|
||||
}
|
||||
|
||||
const messageFoldersPlugin: Plugin<typeof messageFoldersSettings, MessageFoldersStorage> = {
|
||||
id: "messageFolders",
|
||||
name: "Message Folders",
|
||||
@@ -799,66 +871,37 @@ const messageFoldersPlugin: Plugin<typeof messageFoldersSettings, MessageFolders
|
||||
};
|
||||
|
||||
const applyBadges = () => {
|
||||
const messageItems = document.querySelectorAll("[class*='MessageList__MessageList___'] ol > li[data-message]");
|
||||
const messageItems = getMessageListItems();
|
||||
if (!shouldShowBadgesInList()) {
|
||||
for (const li of messageItems) {
|
||||
const subject = li.querySelector("[class*='MessageList__subject___']");
|
||||
if (subject && (subject.querySelector(".bsplus-msg-badges") || subject.querySelector(".bsplus-subject-text"))) {
|
||||
restoreSubjectPlain(subject);
|
||||
} else {
|
||||
li.querySelector(".bsplus-msg-badges")?.remove();
|
||||
}
|
||||
}
|
||||
clearMessageListBadges(messageItems, restoreSubjectPlain);
|
||||
return;
|
||||
}
|
||||
|
||||
const folders = getFolders();
|
||||
const assignments = getAssignments();
|
||||
const selectFolder = (folderId: string) => {
|
||||
activeFolderId = folderId;
|
||||
applyFolderFilter();
|
||||
applyBadges();
|
||||
renderSidebarFolders();
|
||||
};
|
||||
|
||||
for (const li of messageItems) {
|
||||
const msgId = li.getAttribute("data-message");
|
||||
if (!msgId) continue;
|
||||
let badgeContainer = li.querySelector(".bsplus-msg-badges") as HTMLElement | null;
|
||||
const folderIds: string[] = [];
|
||||
for (const [fId, mIds] of Object.entries(assignments)) {
|
||||
if (mIds.includes(msgId)) folderIds.push(fId);
|
||||
}
|
||||
|
||||
const folderIds = getAssignedFolderIds(msgId, assignments);
|
||||
if (folderIds.length === 0) {
|
||||
badgeContainer?.remove();
|
||||
li.querySelector(".bsplus-msg-badges")?.remove();
|
||||
continue;
|
||||
}
|
||||
if (!badgeContainer) {
|
||||
badgeContainer = document.createElement("div");
|
||||
badgeContainer.className = "bsplus-msg-badges";
|
||||
const subject = li.querySelector("[class*='MessageList__subject___']");
|
||||
if (subject) {
|
||||
if (!subject.querySelector(".bsplus-subject-text")) {
|
||||
const textWrap = document.createElement("span");
|
||||
textWrap.className = "bsplus-subject-text";
|
||||
textWrap.textContent = subject.textContent;
|
||||
subject.textContent = "";
|
||||
subject.appendChild(textWrap);
|
||||
}
|
||||
subject.appendChild(badgeContainer);
|
||||
} else {
|
||||
li.appendChild(badgeContainer);
|
||||
}
|
||||
}
|
||||
badgeContainer.innerHTML = "";
|
||||
for (const fId of folderIds) {
|
||||
const folder = folders.find((f) => f.id === fId);
|
||||
|
||||
const badgeContainer = ensureMessageBadgeContainer(li);
|
||||
badgeContainer.replaceChildren();
|
||||
for (const folderId of folderIds) {
|
||||
const folder = folders.find((f) => f.id === folderId);
|
||||
if (!folder) continue;
|
||||
const badge = document.createElement("span");
|
||||
badge.className = "bsplus-msg-badge";
|
||||
badge.style.background = folder.color;
|
||||
appendFolderBadgeContent(badge, folder);
|
||||
badge.title = `Filter by "${folder.name}"`;
|
||||
badge.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
activeFolderId = folder.id;
|
||||
applyFolderFilter();
|
||||
applyBadges();
|
||||
renderSidebarFolders();
|
||||
});
|
||||
badgeContainer.appendChild(badge);
|
||||
badgeContainer.appendChild(createFolderBadge(folder, selectFolder));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user