mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-08-28 17:21:05 +00:00
fix(home): notices via labels API
This commit is contained in:
@@ -18,6 +18,10 @@ import {
|
|||||||
toISODate,
|
toISODate,
|
||||||
weekRangeContaining,
|
weekRangeContaining,
|
||||||
} from "@/seqta/utils/Loaders/engageParentTimetable";
|
} from "@/seqta/utils/Loaders/engageParentTimetable";
|
||||||
|
import {
|
||||||
|
noticeMatchesLabelFilter,
|
||||||
|
resolveNoticeFilterTokens,
|
||||||
|
} from "@/seqta/utils/notices/noticeLabelFilters";
|
||||||
|
|
||||||
export function updateEngageHomeMenuActive(isHome: boolean): void {
|
export function updateEngageHomeMenuActive(isHome: boolean): void {
|
||||||
const home = document.getElementById("homebutton");
|
const home = document.getElementById("homebutton");
|
||||||
@@ -267,30 +271,17 @@ function processEngageNotices(response: any, labelArray: string[]): void {
|
|||||||
const noticeContainer = document.getElementById(ENGAGE_NOTICE_CONTAINER_ID);
|
const noticeContainer = document.getElementById(ENGAGE_NOTICE_CONTAINER_ID);
|
||||||
if (!noticeContainer) return;
|
if (!noticeContainer) return;
|
||||||
|
|
||||||
|
noticeContainer.classList.remove("loading");
|
||||||
noticeContainer.innerHTML = "";
|
noticeContainer.innerHTML = "";
|
||||||
|
|
||||||
const notices = response?.payload;
|
const notices = response?.payload;
|
||||||
if (!Array.isArray(notices)) {
|
if (!Array.isArray(notices)) {
|
||||||
const emptyState = document.createElement("div");
|
appendEngageNoticeEmptyState(noticeContainer, "No notices for today.");
|
||||||
emptyState.classList.add("day-empty");
|
|
||||||
const img = document.createElement("img");
|
|
||||||
img.src = browser.runtime.getURL(LogoLight);
|
|
||||||
const text = document.createElement("p");
|
|
||||||
text.innerText = "No notices for today.";
|
|
||||||
emptyState.append(img, text);
|
|
||||||
noticeContainer.append(emptyState);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!notices.length) {
|
if (!notices.length) {
|
||||||
const emptyState = document.createElement("div");
|
appendEngageNoticeEmptyState(noticeContainer, "No notices for today.");
|
||||||
emptyState.classList.add("day-empty");
|
|
||||||
const img = document.createElement("img");
|
|
||||||
img.src = browser.runtime.getURL(LogoLight);
|
|
||||||
const text = document.createElement("p");
|
|
||||||
text.innerText = "No notices for today.";
|
|
||||||
emptyState.append(img, text);
|
|
||||||
noticeContainer.append(emptyState);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,9 +289,7 @@ function processEngageNotices(response: any, labelArray: string[]): void {
|
|||||||
|
|
||||||
notices.forEach((notice: any) => {
|
notices.forEach((notice: any) => {
|
||||||
const shouldInclude =
|
const shouldInclude =
|
||||||
settingsState.mockNotices ||
|
settingsState.mockNotices || noticeMatchesLabelFilter(notice, labelArray);
|
||||||
labelArray.length === 0 ||
|
|
||||||
labelArray.includes(JSON.stringify(notice.label));
|
|
||||||
|
|
||||||
if (shouldInclude) {
|
if (shouldInclude) {
|
||||||
const colour = processEngageNoticeColor(notice.colour);
|
const colour = processEngageNoticeColor(notice.colour);
|
||||||
@@ -309,9 +298,25 @@ function processEngageNotices(response: any, labelArray: string[]): void {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (fragment.childNodes.length === 0) {
|
||||||
|
appendEngageNoticeEmptyState(noticeContainer, "No notices for today.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
noticeContainer.appendChild(fragment);
|
noticeContainer.appendChild(fragment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function appendEngageNoticeEmptyState(container: HTMLElement, message: string) {
|
||||||
|
const emptyState = document.createElement("div");
|
||||||
|
emptyState.classList.add("day-empty");
|
||||||
|
const img = document.createElement("img");
|
||||||
|
img.src = browser.runtime.getURL(LogoLight);
|
||||||
|
const text = document.createElement("p");
|
||||||
|
text.innerText = message;
|
||||||
|
emptyState.append(img, text);
|
||||||
|
container.append(emptyState);
|
||||||
|
}
|
||||||
|
|
||||||
function createEngageNoticeElement(
|
function createEngageNoticeElement(
|
||||||
notice: any,
|
notice: any,
|
||||||
colour: string | undefined,
|
colour: string | undefined,
|
||||||
@@ -613,6 +618,12 @@ async function fetchEngageNoticesFromApi(
|
|||||||
date: string,
|
date: string,
|
||||||
labelTokens: string[],
|
labelTokens: string[],
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
const noticeContainer = document.getElementById(ENGAGE_NOTICE_CONTAINER_ID);
|
||||||
|
if (noticeContainer) {
|
||||||
|
noticeContainer.classList.add("loading");
|
||||||
|
noticeContainer.innerHTML = "";
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = settingsState.mockNotices
|
const data = settingsState.mockNotices
|
||||||
? getMockNotices()
|
? getMockNotices()
|
||||||
@@ -662,7 +673,7 @@ async function initEngageNoticesUi(todayFormatted: string): Promise<void> {
|
|||||||
const noticeContainer = document.getElementById(ENGAGE_NOTICE_CONTAINER_ID);
|
const noticeContainer = document.getElementById(ENGAGE_NOTICE_CONTAINER_ID);
|
||||||
if (!noticeContainer) return;
|
if (!noticeContainer) return;
|
||||||
|
|
||||||
let labelFilterValues: string[] = [];
|
let prefsPayload: unknown = [];
|
||||||
try {
|
try {
|
||||||
const prefsRes = await fetch(`${location.origin}/seqta/parent/load/prefs?`, {
|
const prefsRes = await fetch(`${location.origin}/seqta/parent/load/prefs?`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -671,21 +682,15 @@ async function initEngageNoticesUi(todayFormatted: string): Promise<void> {
|
|||||||
body: JSON.stringify({ asArray: true, request: "userPrefs" }),
|
body: JSON.stringify({ asArray: true, request: "userPrefs" }),
|
||||||
});
|
});
|
||||||
const prefs = await prefsRes.json();
|
const prefs = await prefsRes.json();
|
||||||
const payload = prefs?.payload;
|
prefsPayload = prefs?.payload ?? [];
|
||||||
if (Array.isArray(payload)) {
|
|
||||||
labelFilterValues = payload
|
|
||||||
.filter((item: { name?: string }) => item.name === "notices.filters")
|
|
||||||
.map((item: { value?: string }) => item.value)
|
|
||||||
.filter((v): v is string => typeof v === "string");
|
|
||||||
}
|
|
||||||
} catch {
|
} catch {
|
||||||
labelFilterValues = [];
|
prefsPayload = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const labelTokens =
|
const labelTokens = await resolveNoticeFilterTokens(
|
||||||
labelFilterValues.length > 0
|
prefsPayload,
|
||||||
? String(labelFilterValues[0]).split(" ").filter(Boolean)
|
`${location.origin}/seqta/parent/load/notices`,
|
||||||
: [];
|
);
|
||||||
|
|
||||||
const dateControl = document.getElementById(ENGAGE_NOTICES_DATE_ID);
|
const dateControl = document.getElementById(ENGAGE_NOTICES_DATE_ID);
|
||||||
if (dateControl) {
|
if (dateControl) {
|
||||||
@@ -696,8 +701,6 @@ async function initEngageNoticesUi(todayFormatted: string): Promise<void> {
|
|||||||
|
|
||||||
const cleanup = bindEngageNoticesDateInput(labelTokens, todayFormatted);
|
const cleanup = bindEngageNoticesDateInput(labelTokens, todayFormatted);
|
||||||
engageMergeNoticeCleanup(cleanup);
|
engageMergeNoticeCleanup(cleanup);
|
||||||
|
|
||||||
noticeContainer.classList.remove("loading");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function engageMergeNoticeCleanup(noticeCleanup: () => void): void {
|
function engageMergeNoticeCleanup(noticeCleanup: () => void): void {
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ import {
|
|||||||
filterAssessmentsForActiveSubjects,
|
filterAssessmentsForActiveSubjects,
|
||||||
subjectsWithUpcomingAssessments,
|
subjectsWithUpcomingAssessments,
|
||||||
} from "@/plugins/built-in/assessmentsOverview/utils";
|
} from "@/plugins/built-in/assessmentsOverview/utils";
|
||||||
|
import {
|
||||||
|
noticeMatchesLabelFilter,
|
||||||
|
resolveNoticeFilterTokens,
|
||||||
|
} from "@/seqta/utils/notices/noticeLabelFilters";
|
||||||
|
|
||||||
let LessonInterval: any;
|
let LessonInterval: any;
|
||||||
let currentSelectedDate = new Date();
|
let currentSelectedDate = new Date();
|
||||||
@@ -131,33 +135,20 @@ export async function loadHomePage() {
|
|||||||
upcomingItems.classList.remove("loading");
|
upcomingItems.classList.remove("loading");
|
||||||
}
|
}
|
||||||
|
|
||||||
const labelArray = prefs.payload
|
const labelTokens = await resolveNoticeFilterTokens(
|
||||||
.filter((item: any) => item.name === "notices.filters")
|
prefs.payload,
|
||||||
.map((item: any) => item.value);
|
`${location.origin}/seqta/student/load/notices?`,
|
||||||
|
);
|
||||||
|
|
||||||
const noticeContainer = document.getElementById("notice-container");
|
const noticeContainer = document.getElementById("notice-container");
|
||||||
if (noticeContainer) {
|
if (noticeContainer) {
|
||||||
if (labelArray.length > 0) {
|
|
||||||
const dateControl = document.querySelector(
|
const dateControl = document.querySelector(
|
||||||
'input[type="date"]',
|
'input[type="date"]',
|
||||||
) as HTMLInputElement;
|
) as HTMLInputElement;
|
||||||
if (dateControl) {
|
if (dateControl) {
|
||||||
dateControl.value = TodayFormatted;
|
dateControl.value = TodayFormatted;
|
||||||
setupNotices(labelArray[0].split(" "), TodayFormatted);
|
|
||||||
}
|
|
||||||
noticeContainer.classList.remove("loading");
|
|
||||||
} else {
|
|
||||||
noticeContainer.classList.remove("loading");
|
|
||||||
noticeContainer.innerHTML = "";
|
|
||||||
const emptyState = document.createElement("div");
|
|
||||||
emptyState.classList.add("day-empty");
|
|
||||||
const img = document.createElement("img");
|
|
||||||
img.src = browser.runtime.getURL(LogoLight);
|
|
||||||
const text = document.createElement("p");
|
|
||||||
text.innerText = "No notices available.";
|
|
||||||
emptyState.append(img, text);
|
|
||||||
noticeContainer.append(emptyState);
|
|
||||||
}
|
}
|
||||||
|
setupNotices(labelTokens, TodayFormatted);
|
||||||
}
|
}
|
||||||
|
|
||||||
return cleanup;
|
return cleanup;
|
||||||
@@ -248,6 +239,12 @@ function setupNotices(labelArray: string[], date: string) {
|
|||||||
) as HTMLInputElement;
|
) as HTMLInputElement;
|
||||||
|
|
||||||
const fetchNotices = async (date: string) => {
|
const fetchNotices = async (date: string) => {
|
||||||
|
const container = document.getElementById("notice-container");
|
||||||
|
if (container) {
|
||||||
|
container.classList.add("loading");
|
||||||
|
container.innerHTML = "";
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = settingsState.mockNotices
|
const data = settingsState.mockNotices
|
||||||
? getMockNotices()
|
? getMockNotices()
|
||||||
@@ -255,13 +252,13 @@ function setupNotices(labelArray: string[], date: string) {
|
|||||||
await fetch(`${location.origin}/seqta/student/load/notices?`, {
|
await fetch(`${location.origin}/seqta/student/load/notices?`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json; charset=utf-8" },
|
headers: { "Content-Type": "application/json; charset=utf-8" },
|
||||||
|
credentials: "include",
|
||||||
body: JSON.stringify({ date }),
|
body: JSON.stringify({ date }),
|
||||||
})
|
})
|
||||||
).json();
|
).json();
|
||||||
|
|
||||||
processNotices(data, labelArray);
|
processNotices(data, labelArray);
|
||||||
} catch {
|
} catch {
|
||||||
// Notices failed to load; processNotices will show empty state if container exists
|
|
||||||
processNotices({ payload: [] }, labelArray);
|
processNotices({ payload: [] }, labelArray);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -294,30 +291,17 @@ function processNotices(response: any, labelArray: string[]) {
|
|||||||
const NoticeContainer = document.getElementById("notice-container");
|
const NoticeContainer = document.getElementById("notice-container");
|
||||||
if (!NoticeContainer) return;
|
if (!NoticeContainer) return;
|
||||||
|
|
||||||
|
NoticeContainer.classList.remove("loading");
|
||||||
NoticeContainer.innerHTML = "";
|
NoticeContainer.innerHTML = "";
|
||||||
|
|
||||||
const notices = response?.payload;
|
const notices = response?.payload;
|
||||||
if (!Array.isArray(notices)) {
|
if (!Array.isArray(notices)) {
|
||||||
const emptyState = document.createElement("div");
|
appendNoticeEmptyState(NoticeContainer, "No notices for today.");
|
||||||
emptyState.classList.add("day-empty");
|
|
||||||
const img = document.createElement("img");
|
|
||||||
img.src = browser.runtime.getURL(LogoLight);
|
|
||||||
const text = document.createElement("p");
|
|
||||||
text.innerText = "No notices for today.";
|
|
||||||
emptyState.append(img, text);
|
|
||||||
NoticeContainer.append(emptyState);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!notices.length) {
|
if (!notices.length) {
|
||||||
const emptyState = document.createElement("div");
|
appendNoticeEmptyState(NoticeContainer, "No notices for today.");
|
||||||
emptyState.classList.add("day-empty");
|
|
||||||
const img = document.createElement("img");
|
|
||||||
img.src = browser.runtime.getURL(LogoLight);
|
|
||||||
const text = document.createElement("p");
|
|
||||||
text.innerText = "No notices for today.";
|
|
||||||
emptyState.append(img, text);
|
|
||||||
NoticeContainer.append(emptyState);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,8 +309,7 @@ function processNotices(response: any, labelArray: string[]) {
|
|||||||
|
|
||||||
notices.forEach((notice: any) => {
|
notices.forEach((notice: any) => {
|
||||||
const shouldInclude =
|
const shouldInclude =
|
||||||
settingsState.mockNotices ||
|
settingsState.mockNotices || noticeMatchesLabelFilter(notice, labelArray);
|
||||||
labelArray.includes(JSON.stringify(notice.label));
|
|
||||||
|
|
||||||
if (shouldInclude) {
|
if (shouldInclude) {
|
||||||
const colour = processNoticeColor(notice.colour);
|
const colour = processNoticeColor(notice.colour);
|
||||||
@@ -335,9 +318,25 @@ function processNotices(response: any, labelArray: string[]) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (fragment.childNodes.length === 0) {
|
||||||
|
appendNoticeEmptyState(NoticeContainer, "No notices for today.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
NoticeContainer.appendChild(fragment);
|
NoticeContainer.appendChild(fragment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function appendNoticeEmptyState(container: HTMLElement, message: string) {
|
||||||
|
const emptyState = document.createElement("div");
|
||||||
|
emptyState.classList.add("day-empty");
|
||||||
|
const img = document.createElement("img");
|
||||||
|
img.src = browser.runtime.getURL(LogoLight);
|
||||||
|
const text = document.createElement("p");
|
||||||
|
text.innerText = message;
|
||||||
|
emptyState.append(img, text);
|
||||||
|
container.append(emptyState);
|
||||||
|
}
|
||||||
|
|
||||||
function processNoticeColor(colour: string): string | undefined {
|
function processNoticeColor(colour: string): string | undefined {
|
||||||
if (typeof colour === "string") {
|
if (typeof colour === "string") {
|
||||||
const rgb = GetThresholdOfColor(colour);
|
const rgb = GetThresholdOfColor(colour);
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
type PrefEntry = { name?: string; value?: unknown };
|
||||||
|
|
||||||
|
/** Parse `notices.filters` pref (space-separated label IDs). */
|
||||||
|
export function parseNoticesFilterPref(prefsPayload: unknown): string[] {
|
||||||
|
if (!Array.isArray(prefsPayload)) return [];
|
||||||
|
const values = (prefsPayload as PrefEntry[])
|
||||||
|
.filter((item) => item?.name === "notices.filters")
|
||||||
|
.map((item) => item?.value)
|
||||||
|
.filter((v): v is string => typeof v === "string");
|
||||||
|
if (values.length === 0) return [];
|
||||||
|
return String(values[0]).split(" ").filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Label IDs from `load/notices` with `{ mode: "labels" }`. */
|
||||||
|
export async function fetchNoticeLabelIds(noticesUrl: string): Promise<string[]> {
|
||||||
|
try {
|
||||||
|
const res = await fetch(noticesUrl, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json; charset=utf-8" },
|
||||||
|
credentials: "include",
|
||||||
|
body: JSON.stringify({ mode: "labels" }),
|
||||||
|
});
|
||||||
|
if (!res.ok) return [];
|
||||||
|
const json = (await res.json()) as { payload?: Array<{ id?: number }> };
|
||||||
|
const payload = json?.payload;
|
||||||
|
if (!Array.isArray(payload)) return [];
|
||||||
|
return payload
|
||||||
|
.map((entry) => entry?.id)
|
||||||
|
.filter((id): id is number => typeof id === "number" && !Number.isNaN(id))
|
||||||
|
.map(String);
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pref filters when set; otherwise all label IDs from the labels API (native SEQTA home). */
|
||||||
|
export async function resolveNoticeFilterTokens(
|
||||||
|
prefsPayload: unknown,
|
||||||
|
noticesUrl: string,
|
||||||
|
): Promise<string[]> {
|
||||||
|
const fromPref = parseNoticesFilterPref(prefsPayload);
|
||||||
|
if (fromPref.length > 0) return fromPref;
|
||||||
|
return await fetchNoticeLabelIds(noticesUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeNoticeLabelId(label: unknown): string | null {
|
||||||
|
if (typeof label === "number" && !Number.isNaN(label)) {
|
||||||
|
return String(label);
|
||||||
|
}
|
||||||
|
if (typeof label === "string" && label.trim()) {
|
||||||
|
return label.trim();
|
||||||
|
}
|
||||||
|
if (label && typeof label === "object") {
|
||||||
|
const obj = label as Record<string, unknown>;
|
||||||
|
if (typeof obj.id === "number" && !Number.isNaN(obj.id)) {
|
||||||
|
return String(obj.id);
|
||||||
|
}
|
||||||
|
if (typeof obj.id === "string" && obj.id.trim()) {
|
||||||
|
return obj.id.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function noticeMatchesLabelFilter(
|
||||||
|
notice: { label?: unknown },
|
||||||
|
filterTokens: string[],
|
||||||
|
): boolean {
|
||||||
|
if (filterTokens.length === 0) return true;
|
||||||
|
const id = normalizeNoticeLabelId(notice?.label);
|
||||||
|
if (id !== null && filterTokens.includes(id)) return true;
|
||||||
|
return filterTokens.includes(JSON.stringify(notice?.label));
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user