mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-08-28 09:11:06 +00:00
fix: fix dev command not loading assets and scripts + fix assement averages
This commit is contained in:
Vendored
+16
@@ -10,6 +10,22 @@ declare module "*?inlineWorker" {
|
|||||||
export default value;
|
export default value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** CRXJS dynamic content / main-world script path (relative to extension root). */
|
||||||
|
declare module "*?script" {
|
||||||
|
const path: string;
|
||||||
|
export default path;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "*?script&iife" {
|
||||||
|
const path: string;
|
||||||
|
export default path;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "*?script&module" {
|
||||||
|
const path: string;
|
||||||
|
export default path;
|
||||||
|
}
|
||||||
|
|
||||||
declare module "*.png?base64" {
|
declare module "*.png?base64" {
|
||||||
const value: string;
|
const value: string;
|
||||||
export default value;
|
export default value;
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { describe, expect, it, jest } from "@jest/globals";
|
||||||
|
|
||||||
|
jest.mock("webextension-polyfill", () => ({
|
||||||
|
__esModule: true,
|
||||||
|
default: {
|
||||||
|
runtime: {
|
||||||
|
getURL: (path: string) => `chrome-extension://testid/${path}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
import { extensionPageScriptUrl } from "./extensionPageScriptUrl";
|
||||||
|
|
||||||
|
describe("extensionPageScriptUrl", () => {
|
||||||
|
it("prefixes chrome.runtime.getURL and strips a leading slash", () => {
|
||||||
|
expect(extensionPageScriptUrl("assets/pageState.js")).toBe(
|
||||||
|
"chrome-extension://testid/assets/pageState.js",
|
||||||
|
);
|
||||||
|
expect(extensionPageScriptUrl("/assets/pageState.js")).toBe(
|
||||||
|
"chrome-extension://testid/assets/pageState.js",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import browser from "webextension-polyfill";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a CRXJS `?script` / `?script&iife` import to a web-accessible
|
||||||
|
* chrome-extension:// URL. Works in both `vite build` and `vite dev`
|
||||||
|
* (plain `?url` imports 404 in CRXJS serve because they are not packaged).
|
||||||
|
*/
|
||||||
|
export function extensionPageScriptUrl(scriptPath: string): string {
|
||||||
|
return browser.runtime.getURL(scriptPath.replace(/^\/+/, ""));
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { extractWeightFromCoversheetText } from "./extractWeightFromCoversheetText";
|
||||||
|
|
||||||
|
describe("extractWeightFromCoversheetText", () => {
|
||||||
|
it("matches Weight: N", () => {
|
||||||
|
expect(extractWeightFromCoversheetText("Due date ... Weight: 20 Subject")).toBe(
|
||||||
|
"20",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("matches Weighting: N%", () => {
|
||||||
|
expect(
|
||||||
|
extractWeightFromCoversheetText("Assessment Weighting: 12.5% of semester"),
|
||||||
|
).toBe("12.5");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("matches Assessment weight: N", () => {
|
||||||
|
expect(
|
||||||
|
extractWeightFromCoversheetText("Assessment weight: 15\nCriteria"),
|
||||||
|
).toBe("15");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("matches Weight of N%", () => {
|
||||||
|
expect(extractWeightFromCoversheetText("This task has a weight of 10%")).toBe(
|
||||||
|
"10",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns null when no weighting is present", () => {
|
||||||
|
expect(extractWeightFromCoversheetText("No marks available yet")).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
/** Pull a numeric weighting from coversheet / report PDF text. */
|
||||||
|
export function extractWeightFromCoversheetText(text: string): string | null {
|
||||||
|
const patterns = [
|
||||||
|
/weightings?\s*:\s*(\d+(?:\.\d+)?)\s*%?/i,
|
||||||
|
/weight\s*:\s*(\d+(?:\.\d+)?)\s*%?/i,
|
||||||
|
/assessment\s+weight(?:ing)?\s*:\s*(\d+(?:\.\d+)?)\s*%?/i,
|
||||||
|
/weight(?:ing)?\s+(?:of\s+)?(\d+(?:\.\d+)?)\s*%/i,
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const pattern of patterns) {
|
||||||
|
const match = text.match(pattern);
|
||||||
|
if (match?.[1]) return match[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
@@ -11,6 +11,9 @@ import {
|
|||||||
getPdfjsPageContextUrls,
|
getPdfjsPageContextUrls,
|
||||||
} from "@/lib/pdfjsExtension.ts";
|
} from "@/lib/pdfjsExtension.ts";
|
||||||
import * as pdfjs from "pdfjs-dist";
|
import * as pdfjs from "pdfjs-dist";
|
||||||
|
import { extractWeightFromCoversheetText } from "./extractWeightFromCoversheetText";
|
||||||
|
|
||||||
|
export { extractWeightFromCoversheetText };
|
||||||
|
|
||||||
ensurePdfjsWorker();
|
ensurePdfjsWorker();
|
||||||
|
|
||||||
@@ -552,135 +555,67 @@ export async function extractPDFText(url: string): Promise<string> {
|
|||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const script = document.createElement("script");
|
const script = document.createElement("script");
|
||||||
|
script.type = "module";
|
||||||
const requestId = `pdf-extract-${Date.now()}-${Math.random()}`;
|
const requestId = `pdf-extract-${Date.now()}-${Math.random()}`;
|
||||||
|
|
||||||
const escapedUrl = escJsSingleQuoted(url);
|
const escapedUrl = escJsSingleQuoted(url);
|
||||||
|
|
||||||
|
// Import the legacy build in page context so it can set
|
||||||
|
// globalThis.pdfjsLib, then parse the coversheet PDF.
|
||||||
script.textContent = `
|
script.textContent = `
|
||||||
(function() {
|
const requestId = '${requestId}';
|
||||||
const requestId = '${requestId}';
|
const pageOrigin = '${escapedOrigin}';
|
||||||
const pageOrigin = '${escapedOrigin}';
|
const url = '${escapedUrl}';
|
||||||
const url = '${escapedUrl}';
|
const pdfWorkerSrc = '${pdfWorkerInj}';
|
||||||
const pdfLibSrc = '${pdfLibInj}';
|
|
||||||
const pdfWorkerSrc = '${pdfWorkerInj}';
|
|
||||||
|
|
||||||
if (window.pdfjsLib) {
|
function postResult(payload) {
|
||||||
extractPDF();
|
window.postMessage({ type: requestId, ...payload }, pageOrigin);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await import('${pdfLibInj}');
|
||||||
|
const pdfjsLib = globalThis.pdfjsLib;
|
||||||
|
if (!pdfjsLib?.getDocument) {
|
||||||
|
postResult({ success: false, error: 'pdfjsLib missing after import' });
|
||||||
} else {
|
} else {
|
||||||
const pdfjsScript = document.createElement('script');
|
pdfjsLib.GlobalWorkerOptions.workerSrc = pdfWorkerSrc;
|
||||||
pdfjsScript.src = pdfLibSrc;
|
|
||||||
pdfjsScript.type = 'module';
|
|
||||||
|
|
||||||
pdfjsScript.onload = function() {
|
const response = await fetch(url, {
|
||||||
extractPDF();
|
credentials: 'include',
|
||||||
};
|
redirect: 'follow',
|
||||||
pdfjsScript.onerror = function() {
|
});
|
||||||
window.postMessage({
|
if (!response.ok) {
|
||||||
type: requestId,
|
throw new Error('HTTP ' + response.status + ': ' + response.statusText);
|
||||||
success: false,
|
|
||||||
error: 'Failed to load pdfjs library'
|
|
||||||
}, pageOrigin);
|
|
||||||
};
|
|
||||||
|
|
||||||
document.head.appendChild(pdfjsScript);
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractPDF() {
|
|
||||||
try {
|
|
||||||
window.pdfjsLib.GlobalWorkerOptions.workerSrc = pdfWorkerSrc;
|
|
||||||
|
|
||||||
const xhr = new XMLHttpRequest();
|
|
||||||
xhr.open('GET', url, true);
|
|
||||||
xhr.responseType = 'arraybuffer';
|
|
||||||
xhr.withCredentials = true;
|
|
||||||
|
|
||||||
xhr.onload = function() {
|
|
||||||
if (xhr.status !== 200) {
|
|
||||||
window.postMessage({
|
|
||||||
type: requestId,
|
|
||||||
success: false,
|
|
||||||
error: 'HTTP ' + xhr.status + ': ' + xhr.statusText
|
|
||||||
}, pageOrigin);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const arrayBuffer = xhr.response;
|
|
||||||
if (!arrayBuffer || arrayBuffer.byteLength === 0) {
|
|
||||||
throw new Error('PDF response is empty');
|
|
||||||
}
|
|
||||||
|
|
||||||
window.pdfjsLib.getDocument({
|
|
||||||
data: arrayBuffer,
|
|
||||||
useSystemFonts: true,
|
|
||||||
verbosity: 0,
|
|
||||||
useWorkerFetch: false,
|
|
||||||
isEvalSupported: false
|
|
||||||
}).promise
|
|
||||||
.then(pdf => {
|
|
||||||
const pagePromises = [];
|
|
||||||
for (let i = 1; i <= pdf.numPages; i++) {
|
|
||||||
pagePromises.push(
|
|
||||||
pdf.getPage(i).then(page => {
|
|
||||||
return page.getTextContent().then(content => {
|
|
||||||
return content.items.map(item => item.str).join(' ');
|
|
||||||
});
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return Promise.all(pagePromises);
|
|
||||||
})
|
|
||||||
.then(pages => {
|
|
||||||
const text = pages.join('\\n');
|
|
||||||
window.postMessage({
|
|
||||||
type: requestId,
|
|
||||||
success: true,
|
|
||||||
text: text
|
|
||||||
}, pageOrigin);
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
window.postMessage({
|
|
||||||
type: requestId,
|
|
||||||
success: false,
|
|
||||||
error: 'PDF parsing error: ' + (error.message || String(error))
|
|
||||||
}, pageOrigin);
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
window.postMessage({
|
|
||||||
type: requestId,
|
|
||||||
success: false,
|
|
||||||
error: 'ArrayBuffer error: ' + (error.message || String(error))
|
|
||||||
}, pageOrigin);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
xhr.onerror = function() {
|
|
||||||
window.postMessage({
|
|
||||||
type: requestId,
|
|
||||||
success: false,
|
|
||||||
error: 'Network error fetching PDF'
|
|
||||||
}, pageOrigin);
|
|
||||||
};
|
|
||||||
|
|
||||||
xhr.ontimeout = function() {
|
|
||||||
window.postMessage({
|
|
||||||
type: requestId,
|
|
||||||
success: false,
|
|
||||||
error: 'Timeout fetching PDF'
|
|
||||||
}, pageOrigin);
|
|
||||||
};
|
|
||||||
|
|
||||||
xhr.timeout = 30000;
|
|
||||||
xhr.send();
|
|
||||||
} catch (error) {
|
|
||||||
window.postMessage({
|
|
||||||
type: requestId,
|
|
||||||
success: false,
|
|
||||||
error: 'Setup error: ' + (error.message || String(error))
|
|
||||||
}, pageOrigin);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const arrayBuffer = await response.arrayBuffer();
|
||||||
|
if (!arrayBuffer || arrayBuffer.byteLength === 0) {
|
||||||
|
throw new Error('PDF response is empty');
|
||||||
|
}
|
||||||
|
|
||||||
|
const pdf = await pdfjsLib.getDocument({
|
||||||
|
data: arrayBuffer,
|
||||||
|
useSystemFonts: true,
|
||||||
|
verbosity: 0,
|
||||||
|
useWorkerFetch: false,
|
||||||
|
isEvalSupported: false,
|
||||||
|
}).promise;
|
||||||
|
|
||||||
|
const pages = [];
|
||||||
|
for (let i = 1; i <= pdf.numPages; i++) {
|
||||||
|
const page = await pdf.getPage(i);
|
||||||
|
const content = await page.getTextContent();
|
||||||
|
pages.push(content.items.map((item) => item.str).join(' '));
|
||||||
|
}
|
||||||
|
|
||||||
|
postResult({ success: true, text: pages.join('\\n') });
|
||||||
}
|
}
|
||||||
})();
|
} catch (error) {
|
||||||
|
postResult({
|
||||||
|
success: false,
|
||||||
|
error: 'PDF extraction error: ' + (error?.message || String(error)),
|
||||||
|
});
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const messageHandler = (event: MessageEvent) => {
|
const messageHandler = (event: MessageEvent) => {
|
||||||
@@ -723,6 +658,9 @@ export async function extractPDFText(url: string): Promise<string> {
|
|||||||
const pdf = await pdfjs.getDocument({
|
const pdf = await pdfjs.getDocument({
|
||||||
data: arrayBuffer,
|
data: arrayBuffer,
|
||||||
useSystemFonts: true,
|
useSystemFonts: true,
|
||||||
|
verbosity: 0,
|
||||||
|
useWorkerFetch: false,
|
||||||
|
isEvalSupported: false,
|
||||||
}).promise;
|
}).promise;
|
||||||
|
|
||||||
let text = "";
|
let text = "";
|
||||||
@@ -740,6 +678,85 @@ export async function extractPDFText(url: string): Promise<string> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function randomStudentPdfFileName(): string {
|
||||||
|
// Matches SEQTA Learn coversheet tokens, e.g. "mrr158ct.pdf".
|
||||||
|
return `${Math.random().toString(36).slice(2, 10)}.pdf`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function requestStudentAssessmentPdf(params: {
|
||||||
|
assessmentID: string | number;
|
||||||
|
metaclassID: string | number;
|
||||||
|
studentID: string | number;
|
||||||
|
}): Promise<string> {
|
||||||
|
const fileName = randomStudentPdfFileName();
|
||||||
|
|
||||||
|
const printResponse = await fetch(
|
||||||
|
`${location.origin}/seqta/student/print/assessment`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json; charset=utf-8" },
|
||||||
|
credentials: "include",
|
||||||
|
body: JSON.stringify({
|
||||||
|
id: Number(params.assessmentID),
|
||||||
|
metaclass: Number(params.metaclassID),
|
||||||
|
student: Number(params.studentID),
|
||||||
|
fileName,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!printResponse.ok) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to generate PDF: ${printResponse.status} ${printResponse.statusText}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = (await printResponse.json()) as {
|
||||||
|
payload?: { file?: string };
|
||||||
|
status?: string | number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const resolved = data.payload?.file;
|
||||||
|
if (!resolved) {
|
||||||
|
throw new Error(
|
||||||
|
`Print assessment response missing payload.file (status=${String(data.status)})`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStudentAssessmentReportUrl(fileName: string): string {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
type: "generated_report",
|
||||||
|
file: fileName,
|
||||||
|
});
|
||||||
|
return `${location.origin}/seqta/student/load/file?${params.toString()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function extractPDFTextWithRetry(
|
||||||
|
url: string,
|
||||||
|
attempts = 3,
|
||||||
|
delayMs = 1500,
|
||||||
|
): Promise<string> {
|
||||||
|
let lastError: unknown;
|
||||||
|
for (let attempt = 0; attempt < attempts; attempt++) {
|
||||||
|
try {
|
||||||
|
return await extractPDFText(url);
|
||||||
|
} catch (error) {
|
||||||
|
lastError = error;
|
||||||
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
const retryable =
|
||||||
|
message.includes("404") ||
|
||||||
|
message.includes("empty") ||
|
||||||
|
message.includes("Failed to fetch PDF");
|
||||||
|
if (!retryable || attempt === attempts - 1) throw error;
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw lastError;
|
||||||
|
}
|
||||||
|
|
||||||
async function handleWeightings(mark: any, api: any) {
|
async function handleWeightings(mark: any, api: any) {
|
||||||
const assessmentID = assessmentIdKey(mark);
|
const assessmentID = assessmentIdKey(mark);
|
||||||
const metaclassID = mark.metaclassID;
|
const metaclassID = mark.metaclassID;
|
||||||
@@ -749,13 +766,25 @@ async function handleWeightings(mark: any, api: any) {
|
|||||||
| WeightingEntry
|
| WeightingEntry
|
||||||
| undefined;
|
| undefined;
|
||||||
|
|
||||||
const isFresh =
|
// Skip only when we already have a real numeric weight for this fingerprint.
|
||||||
|
// "N/A" / "processing" / in-flight refreshing must not permanently block retries.
|
||||||
|
const hasNumericWeight =
|
||||||
existing &&
|
existing &&
|
||||||
existing.weight !== "processing" &&
|
existing.weight !== "processing" &&
|
||||||
existing.fingerprint === fingerprint &&
|
existing.weight !== "N/A" &&
|
||||||
existing.pluginVersion === WEIGHTING_SCHEMA_VERSION;
|
!Number.isNaN(parseFloat(existing.weight));
|
||||||
|
|
||||||
if (isFresh) return;
|
const inFlightSameFingerprint =
|
||||||
|
existing &&
|
||||||
|
existing.fingerprint === fingerprint &&
|
||||||
|
(existing.weight === "processing" || existing.refreshing);
|
||||||
|
|
||||||
|
const isFresh =
|
||||||
|
Boolean(hasNumericWeight) &&
|
||||||
|
existing?.fingerprint === fingerprint &&
|
||||||
|
existing?.pluginVersion === WEIGHTING_SCHEMA_VERSION;
|
||||||
|
|
||||||
|
if (isFresh || inFlightSameFingerprint) return;
|
||||||
|
|
||||||
// If we have a previous usable value, keep showing it while we refetch
|
// If we have a previous usable value, keep showing it while we refetch
|
||||||
// by marking the entry as refreshing instead of wiping it. We claim the
|
// by marking the entry as refreshing instead of wiping it. We claim the
|
||||||
@@ -763,7 +792,7 @@ async function handleWeightings(mark: any, api: any) {
|
|||||||
// pass (e.g. a fast re-mount of the wrapper) doesn't kick off a duplicate
|
// pass (e.g. a fast re-mount of the wrapper) doesn't kick off a duplicate
|
||||||
// refetch for the same id while this one is still in flight.
|
// refetch for the same id while this one is still in flight.
|
||||||
const placeholder: WeightingEntry =
|
const placeholder: WeightingEntry =
|
||||||
existing && existing.weight !== "processing"
|
existing && hasNumericWeight
|
||||||
? {
|
? {
|
||||||
...existing,
|
...existing,
|
||||||
fingerprint,
|
fingerprint,
|
||||||
@@ -807,34 +836,13 @@ async function handleWeightings(mark: any, api: any) {
|
|||||||
const userInfo = await getUserInfo();
|
const userInfo = await getUserInfo();
|
||||||
const userID = userInfo.id;
|
const userID = userInfo.id;
|
||||||
|
|
||||||
const filename =
|
const reportFile = await requestStudentAssessmentPdf({
|
||||||
"BetterSEQTA-" +
|
assessmentID,
|
||||||
String(Math.floor(Math.random() * 1e15)).padStart(15, "0");
|
metaclassID,
|
||||||
|
studentID: userID,
|
||||||
const printResponse = await fetch(
|
});
|
||||||
`${location.origin}/seqta/student/print/assessment`,
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
headers: { "Content-Type": "application/json; charset=utf-8" },
|
|
||||||
credentials: "include",
|
|
||||||
body: JSON.stringify({
|
|
||||||
fileName: filename,
|
|
||||||
id: assessmentID,
|
|
||||||
metaclass: metaclassID,
|
|
||||||
student: userID,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!printResponse.ok) {
|
|
||||||
throw new Error(
|
|
||||||
`Failed to generate PDF: ${printResponse.status} ${printResponse.statusText}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
|
pdfUrl = getStudentAssessmentReportUrl(reportFile);
|
||||||
pdfUrl = `${location.origin}/seqta/student/report/get?file=${filename}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pdfUrl.startsWith("blob:")) {
|
if (pdfUrl.startsWith("blob:")) {
|
||||||
@@ -843,32 +851,37 @@ async function handleWeightings(mark: any, api: any) {
|
|||||||
|
|
||||||
let text: string;
|
let text: string;
|
||||||
try {
|
try {
|
||||||
text = await extractPDFText(pdfUrl);
|
text = await extractPDFTextWithRetry(pdfUrl);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (
|
if (
|
||||||
isFirefox &&
|
isFirefox &&
|
||||||
(error?.message?.includes("blob") ||
|
(error?.message?.includes("blob") ||
|
||||||
error?.message?.includes("Security") ||
|
error?.message?.includes("Security") ||
|
||||||
error?.message?.includes("CSP"))
|
error?.message?.includes("CSP") ||
|
||||||
|
error?.message?.includes("empty"))
|
||||||
) {
|
) {
|
||||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||||
text = await extractPDFText(pdfUrl);
|
text = await extractPDFTextWithRetry(pdfUrl, 2, 2000);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`PDF extraction failed: ${error.message}`);
|
throw new Error(`PDF extraction failed: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const match = text.match(/weight:\s*(\d+\.?\d*)/i);
|
const weight = extractWeightFromCoversheetText(text);
|
||||||
|
|
||||||
api.storage.weightings = {
|
api.storage.weightings = {
|
||||||
...api.storage.weightings,
|
...api.storage.weightings,
|
||||||
[assessmentID]: {
|
[assessmentID]: {
|
||||||
weight: match ? match[1] : "N/A",
|
weight: weight ?? "N/A",
|
||||||
fingerprint,
|
fingerprint,
|
||||||
pluginVersion: WEIGHTING_SCHEMA_VERSION,
|
pluginVersion: WEIGHTING_SCHEMA_VERSION,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
console.error(
|
||||||
|
`[BetterSEQTA+] Weighting fetch failed for assessment ${assessmentID}:`,
|
||||||
|
error,
|
||||||
|
);
|
||||||
api.storage.weightings = {
|
api.storage.weightings = {
|
||||||
...api.storage.weightings,
|
...api.storage.weightings,
|
||||||
[assessmentID]: {
|
[assessmentID]: {
|
||||||
|
|||||||
+3
-3
@@ -5,8 +5,8 @@ import browser from "webextension-polyfill";
|
|||||||
import { settingsState } from "@/seqta/utils/listeners/SettingsState";
|
import { settingsState } from "@/seqta/utils/listeners/SettingsState";
|
||||||
|
|
||||||
// UI and theme management
|
// UI and theme management
|
||||||
import pageState from "@/pageState.js?url";
|
import pageState from "@/pageState.js?script&iife";
|
||||||
import { resolveExtensionAssetUrl } from "@/lib/extensionAssetUrl";
|
import { extensionPageScriptUrl } from "@/lib/extensionPageScriptUrl";
|
||||||
import { installSeqtaMenuColourPatch } from "@/seqta/utils/patchSeqtaMenuUpdateColours";
|
import { installSeqtaMenuColourPatch } from "@/seqta/utils/patchSeqtaMenuUpdateColours";
|
||||||
import { installThemeImagePagePatch } from "@/seqta/utils/patchThemeImagesPageContext";
|
import { installThemeImagePagePatch } from "@/seqta/utils/patchThemeImagesPageContext";
|
||||||
|
|
||||||
@@ -40,6 +40,6 @@ export async function main() {
|
|||||||
|
|
||||||
function injectPageState() {
|
function injectPageState() {
|
||||||
const mainScript = document.createElement("script");
|
const mainScript = document.createElement("script");
|
||||||
mainScript.src = resolveExtensionAssetUrl(pageState);
|
mainScript.src = extensionPageScriptUrl(pageState);
|
||||||
document.head.appendChild(mainScript);
|
document.head.appendChild(mainScript);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
* (injected script) plus Coloris / overlay cleanup in the content script.
|
* (injected script) plus Coloris / overlay cleanup in the content script.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import patchScript from "@/seqta/utils/seqtaMenuColourPatch.js?url";
|
import patchScript from "@/seqta/utils/seqtaMenuColourPatch.js?script&iife";
|
||||||
import { resolveExtensionAssetUrl } from "@/lib/extensionAssetUrl";
|
import { extensionPageScriptUrl } from "@/lib/extensionPageScriptUrl";
|
||||||
import { verboseInfo } from "@/utils/verboseLog";
|
import { verboseInfo } from "@/utils/verboseLog";
|
||||||
|
|
||||||
const PAGE_PATCH_LOADER_ID = "bsplus-seqta-menu-colour-patch-loader";
|
const PAGE_PATCH_LOADER_ID = "bsplus-seqta-menu-colour-patch-loader";
|
||||||
@@ -128,7 +128,7 @@ export function installSeqtaMenuColourPatch(): void {
|
|||||||
|
|
||||||
const script = document.createElement("script");
|
const script = document.createElement("script");
|
||||||
script.id = PAGE_PATCH_LOADER_ID;
|
script.id = PAGE_PATCH_LOADER_ID;
|
||||||
script.src = resolveExtensionAssetUrl(patchScript);
|
script.src = extensionPageScriptUrl(patchScript);
|
||||||
script.addEventListener("load", () => script.remove());
|
script.addEventListener("load", () => script.remove());
|
||||||
(document.documentElement || document.head).appendChild(script);
|
(document.documentElement || document.head).appendChild(script);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
* Bridge theme CSS and decorative images into PAGE JavaScript context.
|
* Bridge theme CSS and decorative images into PAGE JavaScript context.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import patchScript from "@/seqta/utils/themeImagePagePatch.js?url";
|
import patchScript from "@/seqta/utils/themeImagePagePatch.js?script&iife";
|
||||||
import { resolveExtensionAssetUrl } from "@/lib/extensionAssetUrl";
|
import { extensionPageScriptUrl } from "@/lib/extensionPageScriptUrl";
|
||||||
import { blobToBase64Data } from "@/plugins/built-in/themes/themeImageUrl";
|
import { blobToBase64Data } from "@/plugins/built-in/themes/themeImageUrl";
|
||||||
|
|
||||||
const PAGE_PATCH_LOADER_ID = "bsplus-theme-image-page-patch-loader";
|
const PAGE_PATCH_LOADER_ID = "bsplus-theme-image-page-patch-loader";
|
||||||
@@ -23,7 +23,7 @@ export function installThemeImagePagePatch(): void {
|
|||||||
|
|
||||||
const script = document.createElement("script");
|
const script = document.createElement("script");
|
||||||
script.id = PAGE_PATCH_LOADER_ID;
|
script.id = PAGE_PATCH_LOADER_ID;
|
||||||
script.src = resolveExtensionAssetUrl(patchScript);
|
script.src = extensionPageScriptUrl(patchScript);
|
||||||
script.addEventListener("load", () => script.remove());
|
script.addEventListener("load", () => script.remove());
|
||||||
(document.documentElement || document.head).appendChild(script);
|
(document.documentElement || document.head).appendChild(script);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user