mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-08-28 17:21:05 +00:00
fix(themes): theme wallpapers dont apply on firefox
This commit is contained in:
@@ -8,6 +8,7 @@ import { settingsState } from "@/seqta/utils/listeners/SettingsState";
|
||||
import pageState from "@/pageState.js?url";
|
||||
import { resolveExtensionAssetUrl } from "@/lib/extensionAssetUrl";
|
||||
import { installSeqtaMenuColourPatch } from "@/seqta/utils/patchSeqtaMenuUpdateColours";
|
||||
import { installThemeImagePagePatch } from "@/seqta/utils/patchThemeImagesPageContext";
|
||||
|
||||
// Stylesheets
|
||||
import injectedCSS from "@/css/injected.scss?inline";
|
||||
@@ -18,6 +19,7 @@ export async function main() {
|
||||
if (settingsState.onoff) {
|
||||
injectPageState();
|
||||
installSeqtaMenuColourPatch();
|
||||
installThemeImagePagePatch();
|
||||
|
||||
// Rather permanent FIX for bug! -> this is a hack to get the injected.css file to have HMR in development mode as this import system is currently broken with crxjs
|
||||
if (import.meta.env.MODE === "development") {
|
||||
|
||||
@@ -13,13 +13,14 @@ import { CreateElement } from "@/seqta/utils/CreateEnable/CreateElement";
|
||||
import { FilterUpcomingAssessments } from "@/seqta/utils/FilterUpcomingAssessments";
|
||||
import { getMockNotices } from "@/seqta/ui/dev/hideSensitiveContent";
|
||||
import { setupFixedTooltips } from "@/seqta/utils/fixedTooltip";
|
||||
import { verboseInfo } from "@/utils/verboseLog";
|
||||
|
||||
let LessonInterval: any;
|
||||
let currentSelectedDate = new Date();
|
||||
let loadingTimeout: any;
|
||||
|
||||
export async function loadHomePage() {
|
||||
console.info("[BetterSEQTA+] Started Loading Home Page");
|
||||
verboseInfo("[BetterSEQTA+] Started Loading Home Page");
|
||||
|
||||
currentSelectedDate = new Date();
|
||||
|
||||
|
||||
@@ -5,9 +5,10 @@ import { settingsState } from "./listeners/SettingsState";
|
||||
import browser from "webextension-polyfill";
|
||||
import LogoLightOutline from "@/resources/icons/betterseqta-light-outline.png";
|
||||
import { animate, stagger } from "motion";
|
||||
import { verboseInfo } from "@/utils/verboseLog";
|
||||
|
||||
export async function SendNewsPage() {
|
||||
console.info("[BetterSEQTA+] Started Loading News Page");
|
||||
verboseInfo("[BetterSEQTA+] Started Loading News Page");
|
||||
document.title = "News ― SEQTA Learn";
|
||||
await delay(10);
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ const OPTIONAL_UNSET_MEANS_DEFAULT_KEYS = [
|
||||
"themeOfTheMonthLastSeenId",
|
||||
"justupdated",
|
||||
"devMode",
|
||||
"verboseLogging",
|
||||
"hideSensitiveContent",
|
||||
"mockNotices",
|
||||
"devGhReleaseVersionOverride",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { settingsState } from "./listeners/SettingsState";
|
||||
import { verboseInfo } from "@/utils/verboseLog";
|
||||
|
||||
const STYLE_ID = "bsplus-menuitem-visibility";
|
||||
|
||||
@@ -28,7 +29,7 @@ export function applyMenuItemVisibility(): void {
|
||||
)) {
|
||||
if (config && !config.toggle) {
|
||||
css += hideRule(menuItem);
|
||||
console.info(`[BetterSEQTA+] Hiding ${menuItem} menu item`);
|
||||
verboseInfo(`[BetterSEQTA+] Hiding ${menuItem} menu item`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* Bridge theme CSS and decorative images into PAGE JavaScript context.
|
||||
*/
|
||||
|
||||
import patchScript from "@/seqta/utils/themeImagePagePatch.js?url";
|
||||
import { resolveExtensionAssetUrl } from "@/lib/extensionAssetUrl";
|
||||
import { blobToBase64Data } from "@/plugins/built-in/themes/themeImageUrl";
|
||||
|
||||
const PAGE_PATCH_LOADER_ID = "bsplus-theme-image-page-patch-loader";
|
||||
const BRIDGE_ID = "bsplus-theme-image-bridge";
|
||||
const PAYLOAD_ID = "bsplus-theme-image-payload";
|
||||
|
||||
export type ThemePageSyncInput = {
|
||||
images?: Array<{ variableName: string; blob: Blob }>;
|
||||
customCss?: string;
|
||||
previewCss?: string;
|
||||
clear?: boolean;
|
||||
clearPreview?: boolean;
|
||||
};
|
||||
|
||||
export function installThemeImagePagePatch(): void {
|
||||
if (document.getElementById(PAGE_PATCH_LOADER_ID)) return;
|
||||
|
||||
const script = document.createElement("script");
|
||||
script.id = PAGE_PATCH_LOADER_ID;
|
||||
script.src = resolveExtensionAssetUrl(patchScript);
|
||||
script.addEventListener("load", () => script.remove());
|
||||
(document.documentElement || document.head).appendChild(script);
|
||||
}
|
||||
|
||||
function ensureBridgeElements(): void {
|
||||
if (!document.getElementById(PAYLOAD_ID)) {
|
||||
const payload = document.createElement("textarea");
|
||||
payload.id = PAYLOAD_ID;
|
||||
payload.hidden = true;
|
||||
payload.setAttribute("aria-hidden", "true");
|
||||
payload.tabIndex = -1;
|
||||
document.documentElement.appendChild(payload);
|
||||
}
|
||||
if (!document.getElementById(BRIDGE_ID)) {
|
||||
const bridge = document.createElement("div");
|
||||
bridge.id = BRIDGE_ID;
|
||||
bridge.hidden = true;
|
||||
bridge.setAttribute("aria-hidden", "true");
|
||||
document.documentElement.appendChild(bridge);
|
||||
}
|
||||
}
|
||||
|
||||
function bumpBridgeRevision(): void {
|
||||
const bridge = document.getElementById(BRIDGE_ID);
|
||||
if (!bridge) return;
|
||||
const rev = Number(bridge.getAttribute("data-rev") || "0") + 1;
|
||||
bridge.setAttribute("data-rev", String(rev));
|
||||
}
|
||||
|
||||
function sendPayload(payload: Record<string, unknown>): void {
|
||||
installThemeImagePagePatch();
|
||||
ensureBridgeElements();
|
||||
const payloadEl = document.getElementById(PAYLOAD_ID) as HTMLTextAreaElement;
|
||||
payloadEl.value = JSON.stringify(payload);
|
||||
bumpBridgeRevision();
|
||||
}
|
||||
|
||||
export async function syncThemeToPage(input: ThemePageSyncInput): Promise<void> {
|
||||
const payload: Record<string, unknown> = {};
|
||||
|
||||
if (input.clear) {
|
||||
sendPayload({ clear: true });
|
||||
return;
|
||||
}
|
||||
|
||||
if (input.clearPreview) {
|
||||
payload.clearPreview = true;
|
||||
}
|
||||
|
||||
if (input.customCss !== undefined) {
|
||||
payload.customCss = input.customCss;
|
||||
}
|
||||
|
||||
if (input.previewCss !== undefined) {
|
||||
payload.previewCss = input.previewCss;
|
||||
}
|
||||
|
||||
if (input.images !== undefined) {
|
||||
payload.images = await Promise.all(
|
||||
input.images.map(async (image) => ({
|
||||
variableName: image.variableName,
|
||||
data: await blobToBase64Data(image.blob),
|
||||
mime: image.blob.type || "image/png",
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
if (Object.keys(payload).length === 0) return;
|
||||
sendPayload(payload);
|
||||
}
|
||||
|
||||
export function clearThemeInPage(): void {
|
||||
sendPayload({ clear: true });
|
||||
}
|
||||
|
||||
/** @deprecated Use clearThemeInPage */
|
||||
export function clearThemeImagesInPage(): void {
|
||||
clearThemeInPage();
|
||||
}
|
||||
|
||||
/** @deprecated Use syncThemeToPage */
|
||||
export async function syncThemeImagesToPage(
|
||||
images: Array<{ variableName: string; blob: Blob }>,
|
||||
): Promise<void> {
|
||||
await syncThemeToPage({ images });
|
||||
}
|
||||
@@ -11,6 +11,7 @@
|
||||
var TUTOR_COLOUR_PREF_PREFIX = "timetable.tutor.";
|
||||
|
||||
function log(event, detail) {
|
||||
if (!document.documentElement.hasAttribute("data-bsplus-verbose-log")) return;
|
||||
if (detail !== undefined) {
|
||||
console.info(LOG, event, detail);
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
/**
|
||||
* PAGE context only — theme CustomCSS and decorative image variables must live
|
||||
* in page-origin stylesheets. Extension-injected #custom-theme cannot load
|
||||
* page blob: URLs referenced via var() on Firefox.
|
||||
*/
|
||||
(function () {
|
||||
if (window.__bsplusThemePagePatched) return;
|
||||
window.__bsplusThemePagePatched = true;
|
||||
|
||||
var LOG = "[BetterSEQTA+] theme page:";
|
||||
var BRIDGE_ID = "bsplus-theme-image-bridge";
|
||||
var PAYLOAD_ID = "bsplus-theme-image-payload";
|
||||
var IMAGES_STYLE_ID = "bsplus-theme-images";
|
||||
var THEME_STYLE_ID = "custom-theme";
|
||||
var PREVIEW_STYLE_ID = "custom-theme-preview";
|
||||
var urlCache = {};
|
||||
var state = {
|
||||
customCss: "",
|
||||
previewCss: "",
|
||||
};
|
||||
var headObserver = null;
|
||||
|
||||
function log(event, detail) {
|
||||
if (!document.documentElement.hasAttribute("data-bsplus-verbose-log")) return;
|
||||
if (detail !== undefined) {
|
||||
console.info(LOG, event, detail);
|
||||
} else {
|
||||
console.info(LOG, event);
|
||||
}
|
||||
}
|
||||
|
||||
function base64ToBlob(base64, mime) {
|
||||
var byteString = atob(base64);
|
||||
var ab = new ArrayBuffer(byteString.length);
|
||||
var ia = new Uint8Array(ab);
|
||||
for (var i = 0; i < byteString.length; i++) {
|
||||
ia[i] = byteString.charCodeAt(i);
|
||||
}
|
||||
return new Blob([ab], { type: mime || "image/png" });
|
||||
}
|
||||
|
||||
function releaseCachedUrls() {
|
||||
for (var key in urlCache) {
|
||||
if (!urlCache.hasOwnProperty(key)) continue;
|
||||
try {
|
||||
URL.revokeObjectURL(urlCache[key]);
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
urlCache = {};
|
||||
}
|
||||
|
||||
function ensureStyleElement(id) {
|
||||
var style = document.getElementById(id);
|
||||
if (!style) {
|
||||
style = document.createElement("style");
|
||||
style.id = id;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
return style;
|
||||
}
|
||||
|
||||
function ensureThemeStyleLast() {
|
||||
var style = document.getElementById(THEME_STYLE_ID);
|
||||
if (!style || !document.head.contains(style)) return;
|
||||
if (document.head.lastElementChild === style) return;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
function ensureHeadObserver() {
|
||||
if (headObserver) return;
|
||||
headObserver = new MutationObserver(function () {
|
||||
ensureThemeStyleLast();
|
||||
});
|
||||
headObserver.observe(document.head, { childList: true });
|
||||
}
|
||||
|
||||
function clearAll() {
|
||||
releaseCachedUrls();
|
||||
state.customCss = "";
|
||||
state.previewCss = "";
|
||||
var imagesStyle = document.getElementById(IMAGES_STYLE_ID);
|
||||
if (imagesStyle) imagesStyle.textContent = "";
|
||||
var themeStyle = document.getElementById(THEME_STYLE_ID);
|
||||
if (themeStyle) themeStyle.remove();
|
||||
var previewStyle = document.getElementById(PREVIEW_STYLE_ID);
|
||||
if (previewStyle) previewStyle.remove();
|
||||
if (headObserver) {
|
||||
headObserver.disconnect();
|
||||
headObserver = null;
|
||||
}
|
||||
log("cleared");
|
||||
}
|
||||
|
||||
function applyThemeImages(images) {
|
||||
releaseCachedUrls();
|
||||
if (!images || !images.length) {
|
||||
var emptyStyle = document.getElementById(IMAGES_STYLE_ID);
|
||||
if (emptyStyle) emptyStyle.textContent = "";
|
||||
return;
|
||||
}
|
||||
|
||||
var lines = [":root {"];
|
||||
for (var i = 0; i < images.length; i++) {
|
||||
var img = images[i];
|
||||
if (!img || !img.variableName || !img.data) continue;
|
||||
try {
|
||||
var blob = base64ToBlob(img.data, img.mime);
|
||||
var url = URL.createObjectURL(blob);
|
||||
urlCache[img.variableName] = url;
|
||||
lines.push(" --" + img.variableName + ": url(\"" + url + "\");");
|
||||
} catch (e) {
|
||||
console.warn(LOG, "skip image", img.variableName, e);
|
||||
}
|
||||
}
|
||||
lines.push("}");
|
||||
ensureStyleElement(IMAGES_STYLE_ID).textContent = lines.join("\n");
|
||||
log("images applied", { count: images.length });
|
||||
}
|
||||
|
||||
function applyCustomCss(css) {
|
||||
if (!css) {
|
||||
var existing = document.getElementById(THEME_STYLE_ID);
|
||||
if (existing) existing.remove();
|
||||
return;
|
||||
}
|
||||
ensureStyleElement(THEME_STYLE_ID).textContent = css;
|
||||
ensureHeadObserver();
|
||||
ensureThemeStyleLast();
|
||||
}
|
||||
|
||||
function applyPreviewCss(css) {
|
||||
if (!css) {
|
||||
var existing = document.getElementById(PREVIEW_STYLE_ID);
|
||||
if (existing) existing.remove();
|
||||
return;
|
||||
}
|
||||
ensureStyleElement(PREVIEW_STYLE_ID).textContent = css;
|
||||
}
|
||||
|
||||
function processPayload() {
|
||||
var payloadEl = document.getElementById(PAYLOAD_ID);
|
||||
if (!payloadEl) return;
|
||||
var raw = payloadEl.value;
|
||||
if (!raw) {
|
||||
clearAll();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
var payload = JSON.parse(raw);
|
||||
if (!payload || payload.clear) {
|
||||
clearAll();
|
||||
return;
|
||||
}
|
||||
|
||||
if (payload.images !== undefined) {
|
||||
applyThemeImages(payload.images);
|
||||
}
|
||||
|
||||
if (payload.customCss !== undefined) {
|
||||
state.customCss = payload.customCss || "";
|
||||
applyCustomCss(state.customCss);
|
||||
log("custom css applied");
|
||||
}
|
||||
|
||||
if (payload.previewCss !== undefined) {
|
||||
state.previewCss = payload.previewCss || "";
|
||||
applyPreviewCss(state.previewCss);
|
||||
}
|
||||
|
||||
if (payload.clearPreview) {
|
||||
state.previewCss = "";
|
||||
applyPreviewCss("");
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(LOG, "invalid payload", e);
|
||||
}
|
||||
}
|
||||
|
||||
function ensureBridge() {
|
||||
if (!document.getElementById(PAYLOAD_ID)) {
|
||||
var payload = document.createElement("textarea");
|
||||
payload.id = PAYLOAD_ID;
|
||||
payload.hidden = true;
|
||||
payload.setAttribute("aria-hidden", "true");
|
||||
payload.tabIndex = -1;
|
||||
document.documentElement.appendChild(payload);
|
||||
}
|
||||
if (!document.getElementById(BRIDGE_ID)) {
|
||||
var bridge = document.createElement("div");
|
||||
bridge.id = BRIDGE_ID;
|
||||
bridge.hidden = true;
|
||||
bridge.setAttribute("aria-hidden", "true");
|
||||
document.documentElement.appendChild(bridge);
|
||||
}
|
||||
}
|
||||
|
||||
ensureBridge();
|
||||
var bridge = document.getElementById(BRIDGE_ID);
|
||||
new MutationObserver(function () {
|
||||
processPayload();
|
||||
}).observe(bridge, {
|
||||
attributes: true,
|
||||
attributeFilter: ["data-rev"],
|
||||
});
|
||||
|
||||
processPayload();
|
||||
log("patch active");
|
||||
})();
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
import { dismissStaleColourDialogs } from "@/seqta/utils/patchSeqtaMenuUpdateColours";
|
||||
import { verboseInfo } from "@/utils/verboseLog";
|
||||
|
||||
let attached = false;
|
||||
let dismissTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
@@ -83,7 +84,7 @@ export function attachTimetableColorisRecovery(): void {
|
||||
if (!pickerOpen) {
|
||||
const result = dismissTimetableUiBlockers();
|
||||
if (result.slideRemoved > 0 || result.modalRemoved > 0) {
|
||||
console.info(
|
||||
verboseInfo(
|
||||
"[BetterSEQTA+] timetable colour: content-script cleanup",
|
||||
result,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user