chore: reduce complexity

This commit is contained in:
2026-06-26 19:41:27 +09:30
parent a3bdb0fe09
commit ca4cbea0f0
6 changed files with 484 additions and 419 deletions
@@ -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 {
+91 -48
View File
@@ -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));
}
}
};