From 771169348f4d69c06ecd750548f1821d1621d47d Mon Sep 17 00:00:00 2001 From: SethBurkart123 Date: Mon, 5 May 2025 17:58:40 +1000 Subject: [PATCH] feat: supporting improved assessments and improved parsing --- .../src/indexing/jobs/assessments.ts | 63 +++++++++++++++++-- .../src/indexing/jobs/messages.ts | 7 +-- .../globalSearch/src/indexing/utils.ts | 32 ++++++++++ 3 files changed, 94 insertions(+), 8 deletions(-) create mode 100644 src/plugins/built-in/globalSearch/src/indexing/utils.ts diff --git a/src/plugins/built-in/globalSearch/src/indexing/jobs/assessments.ts b/src/plugins/built-in/globalSearch/src/indexing/jobs/assessments.ts index d88f4566..ed8ba287 100644 --- a/src/plugins/built-in/globalSearch/src/indexing/jobs/assessments.ts +++ b/src/plugins/built-in/globalSearch/src/indexing/jobs/assessments.ts @@ -1,4 +1,6 @@ import type { Job, IndexItem } from "../types"; +import { htmlToPlainText } from "../utils"; +import { fetchMessageContent } from "./messages"; /* ------------- Notification types ------------- */ interface MessageNotification { @@ -44,6 +46,55 @@ const fetchNotifications = async () => { return (json.notifications ?? []) as Notification[]; }; +const fetchAssessmentName = async ( + assessmentId: number, + metaclassId: number, + programmeId: number +): Promise => { + const searchAssessment = (data: any): string | null => { + // Search syllabus + for (const item of data.syllabus || []) { + const found = (item.assessments || []).find((a: any) => a.id === assessmentId); + if (found) return found.title; + } + + // Search pending + const foundPending = (data.pending || []).find((a: any) => a.id === assessmentId); + if (foundPending) return foundPending.title; + + // Search tasks + const foundTask = (data.tasks || []).find((a: any) => a.id === assessmentId); + if (foundTask) return foundTask.title; + + return null; + }; + + const fetchAssessments = async (endpoint: string) => { + const res = await fetch(`${location.origin}${endpoint}`, { + method: "POST", + credentials: "include", + body: JSON.stringify({ + metaclass: metaclassId, + programme: programmeId, + }), + }); + const json = await res.json(); + return json.payload; + }; + + // Try from /past + let payload = await fetchAssessments("/seqta/student/assessment/list/past"); + let title = searchAssessment(payload); + if (title) return title; + + // Try from /upcoming if not found in /past + const upcomingPayload = await fetchAssessments("/seqta/student/assessment/list/upcoming"); + const foundUpcoming = (upcomingPayload || []).find((a: any) => a.id === assessmentId); + if (foundUpcoming) return foundUpcoming.title; + + throw new Error(`Assessment with ID ${assessmentId} not found in past or upcoming.`); +}; + /* ------------- Job ------------- */ export const assessmentsJob: Job = { id: "assessments", @@ -79,11 +130,13 @@ export const assessmentsJob: Job = { if (notif.type === "coneqtassessments") { const a = notif.coneqtAssessments; + + const content = await fetchAssessmentName(a.assessmentID, a.metaclassID, a.programmeID); items.push({ id, text: a.title, category: "assessments", - content: a.subtitle, + content: content, dateAdded: new Date(notif.timestamp).getTime(), metadata: { assessmentId: a.assessmentID, @@ -96,13 +149,15 @@ export const assessmentsJob: Job = { actionId: "assessment", renderComponentId: "assessment", }); - } else { + } else if (notif.type === "message") { + const content = await fetchMessageContent(notif.message.messageID); + await ctx.addItem( { id, text: notif.message.title, category: "messages", - content: `From: ${notif.message.subtitle}`, + content: `${htmlToPlainText(content.payload.contents)}\nFrom: ${notif.message.subtitle}`, dateAdded: new Date(notif.timestamp).getTime(), metadata: { messageId: notif.message.messageID, @@ -125,7 +180,7 @@ export const assessmentsJob: Job = { ); await ctx.setProgress({ lastTs: latest }); } - + return items; }, diff --git a/src/plugins/built-in/globalSearch/src/indexing/jobs/messages.ts b/src/plugins/built-in/globalSearch/src/indexing/jobs/messages.ts index eb32358c..00f8ab38 100644 --- a/src/plugins/built-in/globalSearch/src/indexing/jobs/messages.ts +++ b/src/plugins/built-in/globalSearch/src/indexing/jobs/messages.ts @@ -1,6 +1,5 @@ import type { Job, IndexItem } from "../types"; - -const stripHtmlTags = (html: string) => html.replace(/<[^>]*>/g, ""); +import { htmlToPlainText } from "../utils"; const fetchMessages = async (offset = 0, limit = 100) => { const res = await fetch(`${location.origin}/seqta/student/load/message`, { @@ -24,7 +23,7 @@ const fetchMessages = async (offset = 0, limit = 100) => { }>; }; -const fetchMessageContent = async (id: number) => { +export const fetchMessageContent = async (id: number) => { const res = await fetch(`${location.origin}/seqta/student/load/message`, { method: "POST", credentials: "include", @@ -96,7 +95,7 @@ export const messagesJob: Job = { id, text: msg.subject, category: "messages", - content: `From: ${msg.sender}\n\n${stripHtmlTags(full.payload.contents)}`, + content: `${htmlToPlainText(full.payload.contents)}\nFrom: ${msg.sender}`, dateAdded: new Date(msg.date).getTime(), metadata: { messageId: msg.id, diff --git a/src/plugins/built-in/globalSearch/src/indexing/utils.ts b/src/plugins/built-in/globalSearch/src/indexing/utils.ts new file mode 100644 index 00000000..6012ee9f --- /dev/null +++ b/src/plugins/built-in/globalSearch/src/indexing/utils.ts @@ -0,0 +1,32 @@ +export function htmlToPlainText(rawHtml: string): string { + const parser = new DOMParser(); + const doc = parser.parseFromString(rawHtml, 'text/html'); + const { body } = doc; + + body.querySelectorAll('script,style,template,noscript,meta,link').forEach(el => el.remove()); + + body.querySelectorAll('.forward').forEach(el => { + let n: ChildNode | null = el; + while (n) { + const next = n.nextSibling as ChildNode | null; + n.remove(); + n = next; + } + }); + + let text = body.innerText || ''; + + text = text + .replace(/\u00A0/g, ' ') + .replace(/[ \t]{2,}/g, ' ') + .replace(/\r\n|\r/g, '\n') + .replace(/\n{3,}/g, '\n\n') + .replace(/^[.\w#][^{]{0,100}\{[^}]*\}$/gm, '') + .split('\n') + .map(line => line.trimEnd()) + .filter(line => line.trim().length > 0 || line === '') + .join('\n') + .trim(); + + return text; +} \ No newline at end of file