mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-08-28 09:11:06 +00:00
fix(global search): CSS preload killed plugin load
This commit is contained in:
@@ -9,6 +9,9 @@ bun.lock
|
|||||||
# PDF.js extension assets (copied by postinstall from pdfjs-dist)
|
# PDF.js extension assets (copied by postinstall from pdfjs-dist)
|
||||||
src/public/resources/pdfjs/pdf.worker.min.mjs
|
src/public/resources/pdfjs/pdf.worker.min.mjs
|
||||||
src/public/resources/pdfjs/pdf.legacy.min.mjs
|
src/public/resources/pdfjs/pdf.legacy.min.mjs
|
||||||
|
# ONNX Runtime WASM assets (copied by postinstall from @huggingface/transformers)
|
||||||
|
src/public/resources/ort/ort-wasm-simd-threaded.jsep.mjs
|
||||||
|
src/public/resources/ort/ort-wasm-simd-threaded.jsep.wasm
|
||||||
|
|
||||||
# Build
|
# Build
|
||||||
extension.zip
|
extension.zip
|
||||||
|
|||||||
@@ -15,8 +15,15 @@ export function extensionChunkUrls(): Plugin {
|
|||||||
base: "./",
|
base: "./",
|
||||||
experimental: {
|
experimental: {
|
||||||
renderBuiltUrl(filename, { hostType, type }) {
|
renderBuiltUrl(filename, { hostType, type }) {
|
||||||
if (type === "chunk" && hostType === "js") {
|
|
||||||
const path = filename.replace(/^\//, "");
|
const path = filename.replace(/^\//, "");
|
||||||
|
if (type === "chunk" && hostType === "js") {
|
||||||
|
return {
|
||||||
|
runtime: `chrome.runtime.getURL(${JSON.stringify(path)})`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// Rewrite CSS preloads from JS dynamic imports (content scripts).
|
||||||
|
// Do not rewrite hostType "css" — extension HTML pages need static hrefs.
|
||||||
|
if (type === "asset" && hostType === "js" && path.endsWith(".css")) {
|
||||||
return {
|
return {
|
||||||
runtime: `chrome.runtime.getURL(${JSON.stringify(path)})`,
|
runtime: `chrome.runtime.getURL(${JSON.stringify(path)})`,
|
||||||
};
|
};
|
||||||
|
|||||||
+6
-5
@@ -43,12 +43,13 @@ export default function InlineWorkerDevPlugin(): Plugin {
|
|||||||
// Note: Original code had `await fs.readFile(cleanPath, "utf-8");` but `code` wasn't used.
|
// Note: Original code had `await fs.readFile(cleanPath, "utf-8");` but `code` wasn't used.
|
||||||
// `esbuild` directly takes `cleanPath` as an entry point.
|
// `esbuild` directly takes `cleanPath` as an entry point.
|
||||||
const result = await build({
|
const result = await build({
|
||||||
entryPoints: [cleanPath], // esbuild uses the file path directly
|
entryPoints: [cleanPath],
|
||||||
bundle: true,
|
bundle: true,
|
||||||
write: false, // We want the output in memory, not written to disk
|
write: false,
|
||||||
platform: "browser", // Target environment for the worker code
|
platform: "browser",
|
||||||
format: "iife", // Immediately Invoked Function Expression, suitable for workers
|
format: "iife",
|
||||||
target: "esnext", // Transpile to modern JavaScript
|
target: "esnext",
|
||||||
|
external: ["webextension-polyfill"],
|
||||||
});
|
});
|
||||||
|
|
||||||
const workerCode = result.outputFiles[0].text;
|
const workerCode = result.outputFiles[0].text;
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@
|
|||||||
"browserslist": "> 0.5%, last 2 versions, not dead",
|
"browserslist": "> 0.5%, last 2 versions, not dead",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"compile:layerchart": "node scripts/compile-layerchart-vendor.mjs",
|
"compile:layerchart": "node scripts/compile-layerchart-vendor.mjs",
|
||||||
"postinstall": "node scripts/copy-pdfjs-assets.mjs && npm run compile:layerchart",
|
"postinstall": "node scripts/copy-pdfjs-assets.mjs && node scripts/copy-ort-wasm-assets.mjs && npm run compile:layerchart",
|
||||||
"autoaudit": "npm audit && npm audit fix && npm run build",
|
"autoaudit": "npm audit && npm audit fix && npm run build",
|
||||||
"dev": "cross-env MODE=chrome vite dev",
|
"dev": "cross-env MODE=chrome vite dev",
|
||||||
"dev:firefox": "cross-env MODE=firefox vite build --watch",
|
"dev:firefox": "cross-env MODE=firefox vite build --watch",
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { copyFileSync, mkdirSync } from "node:fs";
|
||||||
|
import { dirname, join } from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
|
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
|
||||||
|
const transformersDist = join(
|
||||||
|
root,
|
||||||
|
"node_modules",
|
||||||
|
"@huggingface",
|
||||||
|
"transformers",
|
||||||
|
"dist",
|
||||||
|
);
|
||||||
|
const outDir = join(root, "src", "public", "resources", "ort");
|
||||||
|
|
||||||
|
mkdirSync(outDir, { recursive: true });
|
||||||
|
|
||||||
|
const ortFiles = [
|
||||||
|
"ort-wasm-simd-threaded.jsep.mjs",
|
||||||
|
"ort-wasm-simd-threaded.jsep.wasm",
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const file of ortFiles) {
|
||||||
|
copyFileSync(join(transformersDist, file), join(outDir, file));
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
button {
|
||||||
|
@apply cursor-pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
&:focus {
|
||||||
|
box-shadow: unset !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-scrollbar {
|
||||||
|
scrollbar-width: none !important;
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import "./index.css";
|
|||||||
import Settings from "./pages/settings.svelte";
|
import Settings from "./pages/settings.svelte";
|
||||||
import IconFamily from "@/resources/fonts/IconFamily.woff";
|
import IconFamily from "@/resources/fonts/IconFamily.woff";
|
||||||
import browser from "webextension-polyfill";
|
import browser from "webextension-polyfill";
|
||||||
|
import { resolveExtensionAssetUrl } from "@/lib/extensionAssetUrl";
|
||||||
import renderSvelte from "./main";
|
import renderSvelte from "./main";
|
||||||
import { initializeSettingsState } from "@/seqta/utils/listeners/SettingsState";
|
import { initializeSettingsState } from "@/seqta/utils/listeners/SettingsState";
|
||||||
import { initVerboseLogging, verboseInfo } from "@/utils/verboseLog";
|
import { initVerboseLogging, verboseInfo } from "@/utils/verboseLog";
|
||||||
@@ -14,7 +15,7 @@ function InjectCustomIcons() {
|
|||||||
style.innerHTML = `
|
style.innerHTML = `
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'IconFamily';
|
font-family: 'IconFamily';
|
||||||
src: url('${browser.runtime.getURL(IconFamily)}') format('woff');
|
src: url('${resolveExtensionAssetUrl(IconFamily)}') format('woff');
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}`;
|
}`;
|
||||||
|
|||||||
Vendored
-2
@@ -1,5 +1,3 @@
|
|||||||
import "./index.css";
|
|
||||||
|
|
||||||
declare module "*.png";
|
declare module "*.png";
|
||||||
declare module "*.svg";
|
declare module "*.svg";
|
||||||
declare module "*.jpeg";
|
declare module "*.jpeg";
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { mount } from "svelte";
|
||||||
|
import type { SvelteComponent } from "svelte";
|
||||||
|
import style from "./contentShadow.css?inline";
|
||||||
|
|
||||||
|
/** Mount Svelte UI inside a shadow root from content scripts (decoupled from settings popup CSS). */
|
||||||
|
export default function renderInShadow(
|
||||||
|
Component: SvelteComponent | any,
|
||||||
|
mountPoint: ShadowRoot | HTMLElement,
|
||||||
|
props: Record<string, any> = {},
|
||||||
|
) {
|
||||||
|
const app = mount(Component, {
|
||||||
|
target: mountPoint,
|
||||||
|
props: {
|
||||||
|
standalone: false,
|
||||||
|
...props,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (mountPoint instanceof ShadowRoot) {
|
||||||
|
const styleElement = document.createElement("style");
|
||||||
|
styleElement.textContent = style;
|
||||||
|
mountPoint.appendChild(styleElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import browser from "webextension-polyfill";
|
||||||
|
|
||||||
|
const ORT_RESOURCE_DIR = "resources/ort/";
|
||||||
|
|
||||||
|
let configured = false;
|
||||||
|
|
||||||
|
function extensionAssetUrl(relativePath: string): string {
|
||||||
|
return browser.runtime.getURL(relativePath.replace(/^\/+/, ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Point HuggingFace transformers / onnxruntime at extension-local WASM files
|
||||||
|
* instead of CDN (required on SEQTA pages where page CSP blocks jsdelivr).
|
||||||
|
* Safe to call multiple times; must run before embeddia `initializeModel()`.
|
||||||
|
*/
|
||||||
|
export async function ensureTransformersEnv(
|
||||||
|
ortWasmBase?: string,
|
||||||
|
): Promise<void> {
|
||||||
|
if (configured) return;
|
||||||
|
|
||||||
|
const { env } = await import("@huggingface/transformers");
|
||||||
|
const base = ortWasmBase ?? extensionAssetUrl(ORT_RESOURCE_DIR);
|
||||||
|
|
||||||
|
env.backends.onnx.wasm = env.backends.onnx.wasm ?? {};
|
||||||
|
env.backends.onnx.wasm.wasmPaths = base.endsWith("/") ? base : `${base}/`;
|
||||||
|
|
||||||
|
configured = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getOrtWasmBaseUrl(): string {
|
||||||
|
const base = extensionAssetUrl(ORT_RESOURCE_DIR);
|
||||||
|
return base.endsWith("/") ? base : `${base}/`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** For page-origin blob workers that cannot call `browser.runtime.getURL`. */
|
||||||
|
export async function configureTransformersEnvForBase(
|
||||||
|
ortWasmBase: string,
|
||||||
|
): Promise<void> {
|
||||||
|
configured = false;
|
||||||
|
await ensureTransformersEnv(ortWasmBase);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { ORT_RESOURCE_DIR };
|
||||||
@@ -36,7 +36,9 @@
|
|||||||
"resources/icons/*",
|
"resources/icons/*",
|
||||||
"resources/update-image.webp",
|
"resources/update-image.webp",
|
||||||
"resources/pdfjs/pdf.worker.min.mjs",
|
"resources/pdfjs/pdf.worker.min.mjs",
|
||||||
"resources/pdfjs/pdf.legacy.min.mjs"
|
"resources/pdfjs/pdf.legacy.min.mjs",
|
||||||
|
"resources/ort/*",
|
||||||
|
"assets/*.css"
|
||||||
],
|
],
|
||||||
"matches": ["*://*/*"]
|
"matches": ["*://*/*"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,10 @@ import {
|
|||||||
} from "../../core/settingsHelpers";
|
} from "../../core/settingsHelpers";
|
||||||
import { isSeqtaEngageExperience } from "@/seqta/utils/isSeqtaEngage";
|
import { isSeqtaEngageExperience } from "@/seqta/utils/isSeqtaEngage";
|
||||||
import styles from "./src/core/styles.css?inline";
|
import styles from "./src/core/styles.css?inline";
|
||||||
import { resetSearchIndexes } from "./src/indexing/resetIndexes";
|
import {
|
||||||
|
resetSearchIndexes,
|
||||||
|
notifyOpenTabsResetSearchIndex,
|
||||||
|
} from "./src/indexing/resetIndexes";
|
||||||
|
|
||||||
// Platform-aware default hotkey
|
// Platform-aware default hotkey
|
||||||
const getDefaultHotkey = () => {
|
const getDefaultHotkey = () => {
|
||||||
@@ -52,9 +55,7 @@ const settings = defineSettings({
|
|||||||
if (!confirmed) return;
|
if (!confirmed) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// `resetSearchIndexes` is a tiny statically-imported helper: no
|
await notifyOpenTabsResetSearchIndex();
|
||||||
// dynamic chunks to chase, so the button keeps working even when
|
|
||||||
// the settings page has been open across an extension update.
|
|
||||||
await resetSearchIndexes();
|
await resetSearchIndexes();
|
||||||
alert(
|
alert(
|
||||||
"Search index and storage were reset.\n\nReload this tab to regenerate the index.",
|
"Search index and storage were reset.\n\nReload this tab to regenerate the index.",
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import { verboseDebug, verboseInfo, verboseLog } from "@/utils/verboseLog";
|
|||||||
import styles from "./styles.css?inline";
|
import styles from "./styles.css?inline";
|
||||||
import { waitForElm } from "@/seqta/utils/waitForElm";
|
import { waitForElm } from "@/seqta/utils/waitForElm";
|
||||||
import { runIndexing, ensureSchemaCurrent } from "../indexing/indexer";
|
import { runIndexing, ensureSchemaCurrent } from "../indexing/indexer";
|
||||||
|
import { installResetIndexMessageListener } from "../indexing/resetIndexes";
|
||||||
|
import { isIndexingPaused } from "../indexing/indexingPause";
|
||||||
import { initVectorSearch } from "../search/vector/vectorSearch";
|
import { initVectorSearch } from "../search/vector/vectorSearch";
|
||||||
import { cleanupSearchBar, mountSearchBar } from "./mountSearchBar";
|
import { cleanupSearchBar, mountSearchBar } from "./mountSearchBar";
|
||||||
import { IndexedDbManager } from "embeddia";
|
import { IndexedDbManager } from "embeddia";
|
||||||
@@ -168,6 +170,8 @@ const globalSearchPlugin: Plugin<typeof settings> = {
|
|||||||
run: async (api) => {
|
run: async (api) => {
|
||||||
const appRef = { current: null };
|
const appRef = { current: null };
|
||||||
|
|
||||||
|
installResetIndexMessageListener();
|
||||||
|
|
||||||
// Run the version check BEFORE we open any IndexedDB connections.
|
// Run the version check BEFORE we open any IndexedDB connections.
|
||||||
// On a normal load (no version change) this is just a string compare
|
// On a normal load (no version change) this is just a string compare
|
||||||
// and a manifest read, so the cost is negligible. On a real update,
|
// and a manifest read, so the cost is negligible. On a real update,
|
||||||
@@ -287,8 +291,9 @@ const globalSearchPlugin: Plugin<typeof settings> = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (api.settings.runIndexingOnLoad) {
|
if (api.settings.runIndexingOnLoad && !isIndexingPaused()) {
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
|
if (isIndexingPaused()) return;
|
||||||
await runIndexing();
|
await runIndexing();
|
||||||
}, 2000);
|
}, 2000);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -280,7 +280,7 @@ export async function mountSearchBar(
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { default: renderSvelte } = await import("@/interface/main");
|
const { default: renderSvelte } = await import("@/interface/renderInShadow");
|
||||||
appRef.current = renderSvelte(SearchBar, searchRootShadow, {
|
appRef.current = renderSvelte(SearchBar, searchRootShadow, {
|
||||||
transparencyEffects: api.settings.transparencyEffects ? true : false,
|
transparencyEffects: api.settings.transparencyEffects ? true : false,
|
||||||
showRecentFirst: api.settings.showRecentFirst,
|
showRecentFirst: api.settings.showRecentFirst,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { loadDynamicItems } from "../utils/dynamicItems";
|
|||||||
import { getVectorizedItemIds, pruneOrphanVectorEmbeddings } from "./utils";
|
import { getVectorizedItemIds, pruneOrphanVectorEmbeddings } from "./utils";
|
||||||
import { INDEX_SCHEMA_VERSION, SCHEMA_VERSION_KEY } from "./schemaVersion";
|
import { INDEX_SCHEMA_VERSION, SCHEMA_VERSION_KEY } from "./schemaVersion";
|
||||||
import { resetSearchIndexes } from "./resetIndexes";
|
import { resetSearchIndexes } from "./resetIndexes";
|
||||||
|
import { isIndexingPaused } from "./indexingPause";
|
||||||
|
|
||||||
import { verboseDebug, verboseInfo, verboseLog } from '@/utils/verboseLog';
|
import { verboseDebug, verboseInfo, verboseLog } from '@/utils/verboseLog';
|
||||||
const META_STORE = "meta";
|
const META_STORE = "meta";
|
||||||
@@ -252,8 +253,19 @@ export async function loadAllStoredItems(): Promise<IndexItem[]> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function runIndexing(): Promise<void> {
|
export async function runIndexing(): Promise<void> {
|
||||||
|
if (isIndexingPaused()) {
|
||||||
|
verboseDebug(
|
||||||
|
"[Indexer] Skipping indexing — index was reset; reload the page to rebuild.",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await ensureSchemaCurrent();
|
await ensureSchemaCurrent();
|
||||||
|
|
||||||
|
if (isIndexingPaused()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!(await acquireLock())) {
|
if (!(await acquireLock())) {
|
||||||
verboseDebug(
|
verboseDebug(
|
||||||
"%c[Indexer] Could not acquire lock - another tab is indexing or this tab is already indexing",
|
"%c[Indexer] Could not acquire lock - another tab is indexing or this tab is already indexing",
|
||||||
@@ -272,6 +284,19 @@ export async function runIndexing(): Promise<void> {
|
|||||||
dispatchProgress(completedJobs, totalSteps, true, "Starting jobs");
|
dispatchProgress(completedJobs, totalSteps, true, "Starting jobs");
|
||||||
|
|
||||||
for (const jobId of jobIds) {
|
for (const jobId of jobIds) {
|
||||||
|
if (isIndexingPaused()) {
|
||||||
|
verboseDebug(
|
||||||
|
"[Indexer] Indexing stopped — index was reset; reload the page to rebuild.",
|
||||||
|
);
|
||||||
|
dispatchProgress(
|
||||||
|
completedJobs,
|
||||||
|
totalSteps,
|
||||||
|
false,
|
||||||
|
"Indexing paused — reload to rebuild",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
dispatchProgress(
|
dispatchProgress(
|
||||||
completedJobs,
|
completedJobs,
|
||||||
totalSteps,
|
totalSteps,
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
/** In-memory gate: after a manual reset, skip indexing until the tab reloads. */
|
||||||
|
let pausedUntilReload = false;
|
||||||
|
|
||||||
|
export function pauseIndexingUntilReload(): void {
|
||||||
|
pausedUntilReload = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isIndexingPaused(): boolean {
|
||||||
|
return pausedUntilReload;
|
||||||
|
}
|
||||||
@@ -395,7 +395,7 @@ export const messagesJob: Job = {
|
|||||||
progress.totalEstimated = await estimateMessageCount();
|
progress.totalEstimated = await estimateMessageCount();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await vectorWorker.startStreamingSession(
|
progress.streamingStarted = await vectorWorker.startStreamingSession(
|
||||||
progress.totalEstimated,
|
progress.totalEstimated,
|
||||||
(progressData) => {
|
(progressData) => {
|
||||||
verboseLog(
|
verboseLog(
|
||||||
@@ -405,10 +405,11 @@ export const messagesJob: Job = {
|
|||||||
RATE_LIMIT_CONFIG.vectorBatchSize,
|
RATE_LIMIT_CONFIG.vectorBatchSize,
|
||||||
"messages",
|
"messages",
|
||||||
);
|
);
|
||||||
progress.streamingStarted = true;
|
if (progress.streamingStarted) {
|
||||||
verboseLog(
|
verboseLog(
|
||||||
`[Messages job] Started streaming vectorization session for ~${progress.totalEstimated} items`,
|
`[Messages job] Started streaming vectorization session for ~${progress.totalEstimated} items`,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(
|
console.warn(
|
||||||
"[Messages job] Failed to start streaming session:",
|
"[Messages job] Failed to start streaming session:",
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ export const notificationsJob: Job = {
|
|||||||
const estimatedTotal = Math.min(notifications.length * 1.2, 100);
|
const estimatedTotal = Math.min(notifications.length * 1.2, 100);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await vectorWorker.startStreamingSession(
|
progress.streamingStarted = await vectorWorker.startStreamingSession(
|
||||||
estimatedTotal,
|
estimatedTotal,
|
||||||
(progressData) => {
|
(progressData) => {
|
||||||
verboseLog(
|
verboseLog(
|
||||||
@@ -209,10 +209,11 @@ export const notificationsJob: Job = {
|
|||||||
NOTIFICATIONS_RATE_LIMIT.vectorBatchSize,
|
NOTIFICATIONS_RATE_LIMIT.vectorBatchSize,
|
||||||
"notifications",
|
"notifications",
|
||||||
);
|
);
|
||||||
progress.streamingStarted = true;
|
if (progress.streamingStarted) {
|
||||||
verboseLog(
|
verboseLog(
|
||||||
`[Notifications job] Started streaming vectorization session for ~${estimatedTotal} items`,
|
`[Notifications job] Started streaming vectorization session for ~${estimatedTotal} items`,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(
|
console.warn(
|
||||||
"[Notifications job] Failed to start streaming session:",
|
"[Notifications job] Failed to start streaming session:",
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { verboseDebug, verboseInfo, verboseLog } from "@/utils/verboseLog";
|
|||||||
import { isSensitiveSeqtaPath, normalizeSeqtaPath } from "./api";
|
import { isSensitiveSeqtaPath, normalizeSeqtaPath } from "./api";
|
||||||
import { mergeDynamicItems } from "../utils/dynamicItems";
|
import { mergeDynamicItems } from "../utils/dynamicItems";
|
||||||
import { decorateIndexItems } from "./renderComponents";
|
import { decorateIndexItems } from "./renderComponents";
|
||||||
|
import { isIndexingPaused } from "./indexingPause";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Passive network observer.
|
* Passive network observer.
|
||||||
@@ -380,7 +381,7 @@ function synthesizeItems(
|
|||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
async function persistItems(items: IndexItem[]): Promise<void> {
|
async function persistItems(items: IndexItem[]): Promise<void> {
|
||||||
if (items.length === 0) return;
|
if (items.length === 0 || isIndexingPaused()) return;
|
||||||
|
|
||||||
// Dedupe against existing entries. We replace on collision so the latest
|
// Dedupe against existing entries. We replace on collision so the latest
|
||||||
// observation wins (e.g. if a message changes title).
|
// observation wins (e.g. if a message changes title).
|
||||||
@@ -401,16 +402,27 @@ async function persistItems(items: IndexItem[]): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function scheduleFlush() {
|
function scheduleFlush() {
|
||||||
if (pendingFlush) return;
|
if (pendingFlush || isIndexingPaused()) return;
|
||||||
pendingFlush = setTimeout(() => {
|
pendingFlush = setTimeout(() => {
|
||||||
pendingFlush = null;
|
pendingFlush = null;
|
||||||
if (!pendingDirty) return;
|
if (!pendingDirty || isIndexingPaused()) return;
|
||||||
pendingDirty = false;
|
pendingDirty = false;
|
||||||
void flushDynamicItems();
|
void flushDynamicItems();
|
||||||
}, FLUSH_DEBOUNCE_MS);
|
}, FLUSH_DEBOUNCE_MS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Drop queued passive captures after a manual index reset. */
|
||||||
|
export function pausePassiveObserver(): void {
|
||||||
|
pendingChangedItems.clear();
|
||||||
|
pendingDirty = false;
|
||||||
|
if (pendingFlush) {
|
||||||
|
clearTimeout(pendingFlush);
|
||||||
|
pendingFlush = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function flushDynamicItems(): Promise<void> {
|
async function flushDynamicItems(): Promise<void> {
|
||||||
|
if (isIndexingPaused()) return;
|
||||||
if (pendingChangedItems.size === 0) return;
|
if (pendingChangedItems.size === 0) return;
|
||||||
|
|
||||||
const rawChanged = Array.from(pendingChangedItems.values());
|
const rawChanged = Array.from(pendingChangedItems.values());
|
||||||
|
|||||||
@@ -1,4 +1,48 @@
|
|||||||
import { SCHEMA_VERSION_KEY } from "./schemaVersion";
|
import { SCHEMA_VERSION_KEY } from "./schemaVersion";
|
||||||
|
import { pauseIndexingUntilReload } from "./indexingPause";
|
||||||
|
import { pausePassiveObserver } from "./passiveObserver";
|
||||||
|
import browser from "webextension-polyfill";
|
||||||
|
|
||||||
|
export const RESET_INDEX_MESSAGE = "global-search-reset-index";
|
||||||
|
|
||||||
|
let resetMessageListenerInstalled = false;
|
||||||
|
|
||||||
|
/** Notify open SEQTA tabs to pause indexing and wipe page-origin stores. */
|
||||||
|
export async function notifyOpenTabsResetSearchIndex(): Promise<void> {
|
||||||
|
const tabs = await browser.tabs.query({});
|
||||||
|
await Promise.allSettled(
|
||||||
|
tabs.map((tab) =>
|
||||||
|
tab.id != null
|
||||||
|
? browser.tabs.sendMessage(tab.id, { type: RESET_INDEX_MESSAGE })
|
||||||
|
: Promise.resolve(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Content scripts: handle reset broadcast from the settings popup. */
|
||||||
|
export function installResetIndexMessageListener(): void {
|
||||||
|
if (resetMessageListenerInstalled) return;
|
||||||
|
resetMessageListenerInstalled = true;
|
||||||
|
|
||||||
|
browser.runtime.onMessage.addListener((message) => {
|
||||||
|
if (message?.type !== RESET_INDEX_MESSAGE) return;
|
||||||
|
pauseIndexingUntilReload();
|
||||||
|
pausePassiveObserver();
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
window.dispatchEvent(
|
||||||
|
new CustomEvent("indexing-progress", {
|
||||||
|
detail: {
|
||||||
|
completed: 0,
|
||||||
|
total: 0,
|
||||||
|
indexing: false,
|
||||||
|
status: "Indexing paused — reload to rebuild",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
void resetSearchIndexes();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hard-reset of all global-search persistence.
|
* Hard-reset of all global-search persistence.
|
||||||
|
|||||||
@@ -1,7 +1,15 @@
|
|||||||
import { EmbeddingIndex, getEmbedding, initializeModel } from "embeddia";
|
import { EmbeddingIndex, getEmbedding, initializeModel } from "embeddia";
|
||||||
import type { IndexItem } from "../types";
|
import type { IndexItem } from "../types";
|
||||||
|
|
||||||
import { verboseDebug, verboseInfo, verboseLog } from '@/utils/verboseLog';
|
import { verboseDebug, verboseInfo, verboseLog } from "./workerVerboseLog";
|
||||||
|
|
||||||
|
let ortWasmBase: string | null = null;
|
||||||
|
|
||||||
|
async function configureOrtWasm(base: string): Promise<void> {
|
||||||
|
const { env } = await import("@huggingface/transformers");
|
||||||
|
env.backends.onnx.wasm = env.backends.onnx.wasm ?? {};
|
||||||
|
env.backends.onnx.wasm.wasmPaths = base.endsWith("/") ? base : `${base}/`;
|
||||||
|
}
|
||||||
let vectorIndex: EmbeddingIndex | null = null;
|
let vectorIndex: EmbeddingIndex | null = null;
|
||||||
let isInitialized = false;
|
let isInitialized = false;
|
||||||
let initializationFailed = false;
|
let initializationFailed = false;
|
||||||
@@ -49,6 +57,9 @@ async function initWorker() {
|
|||||||
|
|
||||||
verboseDebug("Initializing vector worker...");
|
verboseDebug("Initializing vector worker...");
|
||||||
try {
|
try {
|
||||||
|
if (ortWasmBase) {
|
||||||
|
await configureOrtWasm(ortWasmBase);
|
||||||
|
}
|
||||||
await initializeModel();
|
await initializeModel();
|
||||||
vectorIndex = new EmbeddingIndex([]);
|
vectorIndex = new EmbeddingIndex([]);
|
||||||
|
|
||||||
@@ -562,6 +573,9 @@ self.addEventListener("message", async (e) => {
|
|||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "init":
|
case "init":
|
||||||
|
if (data?.ortWasmBase) {
|
||||||
|
ortWasmBase = data.ortWasmBase;
|
||||||
|
}
|
||||||
await initWorker();
|
await initWorker();
|
||||||
self.postMessage({ type: "ready" });
|
self.postMessage({ type: "ready" });
|
||||||
break;
|
break;
|
||||||
@@ -594,13 +608,3 @@ self.addEventListener("message", async (e) => {
|
|||||||
console.warn("Unknown message type:", type);
|
console.warn("Unknown message type:", type);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
initWorker()
|
|
||||||
.then(() => {
|
|
||||||
self.postMessage({ type: "ready" });
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.error("Initial worker initialization failed:", err);
|
|
||||||
|
|
||||||
self.postMessage({ type: "ready" });
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { refreshVectorCache } from "../../search/vector/vectorSearch";
|
import { refreshVectorCache } from "../../search/vector/vectorSearch";
|
||||||
import type { IndexItem } from "../types";
|
import type { IndexItem } from "../types";
|
||||||
import { isVectorSearchSupported } from "../../utils/browserDetection";
|
import { isVectorSearchSupported } from "../../utils/browserDetection";
|
||||||
|
import { getOrtWasmBaseUrl } from "@/lib/transformersExtension";
|
||||||
import vectorWorker from "./vectorWorker.ts?inlineWorker";
|
import vectorWorker from "./vectorWorker.ts?inlineWorker";
|
||||||
|
|
||||||
import { verboseDebug, verboseInfo, verboseLog } from '@/utils/verboseLog';
|
import { verboseDebug, verboseInfo, verboseLog } from '@/utils/verboseLog';
|
||||||
@@ -91,7 +92,7 @@ export class VectorWorkerManager {
|
|||||||
this.isInitialized = false;
|
this.isInitialized = false;
|
||||||
|
|
||||||
reject(new Error("Worker initialization timed out"));
|
reject(new Error("Worker initialization timed out"));
|
||||||
}, 10000);
|
}, 60000);
|
||||||
|
|
||||||
this.worker!.addEventListener("message", (e) => {
|
this.worker!.addEventListener("message", (e) => {
|
||||||
const { type, data } = e.data;
|
const { type, data } = e.data;
|
||||||
@@ -158,7 +159,10 @@ export class VectorWorkerManager {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.worker!.postMessage({ type: "init" });
|
this.worker!.postMessage({
|
||||||
|
type: "init",
|
||||||
|
data: { ortWasmBase: getOrtWasmBaseUrl() },
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -388,7 +392,7 @@ export class VectorWorkerManager {
|
|||||||
onProgress?: ProgressCallback,
|
onProgress?: ProgressCallback,
|
||||||
batchSize: number = 10,
|
batchSize: number = 10,
|
||||||
jobId?: string,
|
jobId?: string,
|
||||||
): Promise<void> {
|
): Promise<boolean> {
|
||||||
// Skip if vector search is not supported
|
// Skip if vector search is not supported
|
||||||
if (!isVectorSearchSupported()) {
|
if (!isVectorSearchSupported()) {
|
||||||
verboseDebug("[VectorWorker] Vector search not supported - skipping streaming session");
|
verboseDebug("[VectorWorker] Vector search not supported - skipping streaming session");
|
||||||
@@ -398,13 +402,13 @@ export class VectorWorkerManager {
|
|||||||
message: "Vector search not available - using text search only",
|
message: "Vector search not available - using text search only",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only initialize if we expect items to process
|
// Only initialize if we expect items to process
|
||||||
if (totalExpectedItems === 0) {
|
if (totalExpectedItems === 0) {
|
||||||
verboseDebug("[VectorWorker] No items expected, not starting streaming session");
|
verboseDebug("[VectorWorker] No items expected, not starting streaming session");
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.ensureReady();
|
await this.ensureReady();
|
||||||
@@ -419,7 +423,7 @@ export class VectorWorkerManager {
|
|||||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
} else {
|
} else {
|
||||||
verboseDebug(`Streaming session for job ${jobId} already active`);
|
verboseDebug(`Streaming session for job ${jobId} already active`);
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -455,13 +459,20 @@ export class VectorWorkerManager {
|
|||||||
message: `Starting streaming vectorization for ${jobId}`,
|
message: `Starting streaming vectorization for ${jobId}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async streamItems(items: IndexItem[]): Promise<void> {
|
async streamItems(items: IndexItem[]): Promise<void> {
|
||||||
|
if (!isVectorSearchSupported()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.streamingSession?.isActive) {
|
if (!this.streamingSession?.isActive) {
|
||||||
throw new Error(
|
verboseDebug(
|
||||||
"No active streaming session. Call startStreamingSession first.",
|
"[VectorWorker] streamItems skipped — no active streaming session",
|
||||||
);
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const uniqueItems = items.filter((item, index, arr) => {
|
const uniqueItems = items.filter((item, index, arr) => {
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
/** Worker-safe logging — no webextension-polyfill or SettingsState. */
|
||||||
|
|
||||||
|
export function verboseDebug(...args: unknown[]): void {
|
||||||
|
if (typeof console !== "undefined") console.debug(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function verboseInfo(...args: unknown[]): void {
|
||||||
|
if (typeof console !== "undefined") console.info(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function verboseLog(...args: unknown[]): void {
|
||||||
|
if (typeof console !== "undefined") console.log(...args);
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import { EmbeddingIndex, getEmbedding, initializeModel } from "embeddia";
|
|||||||
import type { IndexItem } from "../../indexing/types";
|
import type { IndexItem } from "../../indexing/types";
|
||||||
import type { SearchResult } from "embeddia";
|
import type { SearchResult } from "embeddia";
|
||||||
import { isVectorSearchSupported } from "../../utils/browserDetection";
|
import { isVectorSearchSupported } from "../../utils/browserDetection";
|
||||||
|
import { ensureTransformersEnv } from "@/lib/transformersExtension";
|
||||||
|
|
||||||
import { verboseDebug, verboseInfo, verboseLog } from '@/utils/verboseLog';
|
import { verboseDebug, verboseInfo, verboseLog } from '@/utils/verboseLog';
|
||||||
let vectorIndex: EmbeddingIndex | null = null;
|
let vectorIndex: EmbeddingIndex | null = null;
|
||||||
@@ -24,6 +25,7 @@ export async function initVectorSearch() {
|
|||||||
initializationAttempted = true;
|
initializationAttempted = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
await ensureTransformersEnv();
|
||||||
await initializeModel();
|
await initializeModel();
|
||||||
vectorIndex = new EmbeddingIndex([]);
|
vectorIndex = new EmbeddingIndex([]);
|
||||||
vectorIndex.preloadIndexedDB();
|
vectorIndex.preloadIndexedDB();
|
||||||
|
|||||||
@@ -49,10 +49,16 @@ export function createLazyPlugin<T extends PluginSettings = PluginSettings, S =
|
|||||||
// Execute the actual plugin's run function
|
// Execute the actual plugin's run function
|
||||||
return await actualPlugin.run(api);
|
return await actualPlugin.run(api);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// Handle Firefox MIME type errors gracefully
|
const msg = error?.message ?? "";
|
||||||
if (error?.message?.includes("MIME type") || error?.message?.includes("NS_ERROR_CORRUPTED_CONTENT")) {
|
// Handle content-script asset loading failures gracefully (Firefox MIME
|
||||||
|
// errors, CSS preload blocked on host page, corrupted chunks).
|
||||||
|
if (
|
||||||
|
msg.includes("MIME type") ||
|
||||||
|
msg.includes("NS_ERROR_CORRUPTED_CONTENT") ||
|
||||||
|
msg.includes("preload CSS")
|
||||||
|
) {
|
||||||
console.error(
|
console.error(
|
||||||
`[BetterSEQTA+] Failed to load plugin "${lazyPlugin.id}" due to Firefox module loading restrictions. ` +
|
`[BetterSEQTA+] Failed to load plugin "${lazyPlugin.id}" due to module/asset loading restrictions. ` +
|
||||||
`This may be a build configuration issue. Error:`,
|
`This may be a build configuration issue. Error:`,
|
||||||
error
|
error
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import { observeMenuItemPosition } from "@/seqta/utils/sidebarMenuIcons";
|
|||||||
|
|
||||||
// Icons and fonts
|
// Icons and fonts
|
||||||
import IconFamily from "@/resources/fonts/IconFamily.woff";
|
import IconFamily from "@/resources/fonts/IconFamily.woff";
|
||||||
|
import { resolveExtensionAssetUrl } from "@/lib/extensionAssetUrl";
|
||||||
|
|
||||||
// Stylesheets
|
// Stylesheets
|
||||||
import iframeCSS from "@/css/iframe.scss?raw";
|
import iframeCSS from "@/css/iframe.scss?raw";
|
||||||
@@ -818,7 +819,7 @@ function InjectCustomIcons() {
|
|||||||
style.innerHTML = `
|
style.innerHTML = `
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'IconFamily';
|
font-family: 'IconFamily';
|
||||||
src: url('${browser.runtime.getURL(IconFamily)}') format('woff');
|
src: url('${resolveExtensionAssetUrl(IconFamily)}') format('woff');
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}`;
|
}`;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { animate } from "motion";
|
import { animate } from "motion";
|
||||||
import browser from "webextension-polyfill";
|
import browser from "webextension-polyfill";
|
||||||
import LogoLight from "@/resources/icons/betterseqta-light-icon.png";
|
import LogoLight from "@/resources/icons/betterseqta-light-icon.png";
|
||||||
|
import { resolveExtensionAssetUrl } from "@/lib/extensionAssetUrl";
|
||||||
import { GetThresholdOfColor } from "@/seqta/ui/colors/getThresholdColour";
|
import { GetThresholdOfColor } from "@/seqta/ui/colors/getThresholdColour";
|
||||||
import { convertTo12HourFormat } from "@/seqta/utils/convertTo12HourFormat";
|
import { convertTo12HourFormat } from "@/seqta/utils/convertTo12HourFormat";
|
||||||
import debounce from "@/seqta/utils/debounce";
|
import debounce from "@/seqta/utils/debounce";
|
||||||
@@ -133,7 +134,7 @@ function renderEngageDayLessons(): void {
|
|||||||
if (lessons.length === 0) {
|
if (lessons.length === 0) {
|
||||||
dayContainer.innerHTML = `
|
dayContainer.innerHTML = `
|
||||||
<div class="day-empty">
|
<div class="day-empty">
|
||||||
<img src="${browser.runtime.getURL(LogoLight)}" alt="" />
|
<img src="${resolveExtensionAssetUrl(LogoLight)}" alt="" />
|
||||||
<p>No lessons for this day.</p>
|
<p>No lessons for this day.</p>
|
||||||
</div>`;
|
</div>`;
|
||||||
} else {
|
} else {
|
||||||
@@ -310,7 +311,7 @@ function appendEngageNoticeEmptyState(container: HTMLElement, message: string) {
|
|||||||
const emptyState = document.createElement("div");
|
const emptyState = document.createElement("div");
|
||||||
emptyState.classList.add("day-empty");
|
emptyState.classList.add("day-empty");
|
||||||
const img = document.createElement("img");
|
const img = document.createElement("img");
|
||||||
img.src = browser.runtime.getURL(LogoLight);
|
img.src = resolveExtensionAssetUrl(LogoLight);
|
||||||
const text = document.createElement("p");
|
const text = document.createElement("p");
|
||||||
text.innerText = message;
|
text.innerText = message;
|
||||||
emptyState.append(img, text);
|
emptyState.append(img, text);
|
||||||
@@ -717,7 +718,7 @@ function showEngageTimetableError(message: string): void {
|
|||||||
dayContainer.classList.remove("loading");
|
dayContainer.classList.remove("loading");
|
||||||
dayContainer.innerHTML = `
|
dayContainer.innerHTML = `
|
||||||
<div class="day-empty">
|
<div class="day-empty">
|
||||||
<img src="${browser.runtime.getURL(LogoLight)}" alt="" />
|
<img src="${resolveExtensionAssetUrl(LogoLight)}" alt="" />
|
||||||
<p>${message}</p>
|
<p>${message}</p>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
@@ -728,7 +729,7 @@ function showEngageNoticesSectionError(message: string): void {
|
|||||||
noticeContainer.classList.remove("loading");
|
noticeContainer.classList.remove("loading");
|
||||||
noticeContainer.innerHTML = `
|
noticeContainer.innerHTML = `
|
||||||
<div class="day-empty">
|
<div class="day-empty">
|
||||||
<img src="${browser.runtime.getURL(LogoLight)}" alt="" />
|
<img src="${resolveExtensionAssetUrl(LogoLight)}" alt="" />
|
||||||
<p>${message}</p>
|
<p>${message}</p>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { animate, stagger } from "motion";
|
import { animate, stagger } from "motion";
|
||||||
import browser from "webextension-polyfill";
|
import browser from "webextension-polyfill";
|
||||||
import LogoLight from "@/resources/icons/betterseqta-light-icon.png";
|
import LogoLight from "@/resources/icons/betterseqta-light-icon.png";
|
||||||
|
import { resolveExtensionAssetUrl } from "@/lib/extensionAssetUrl";
|
||||||
import assessmentsicon from "@/seqta/icons/assessmentsIcon";
|
import assessmentsicon from "@/seqta/icons/assessmentsIcon";
|
||||||
import coursesicon from "@/seqta/icons/coursesIcon";
|
import coursesicon from "@/seqta/icons/coursesIcon";
|
||||||
import { GetThresholdOfColor } from "@/seqta/ui/colors/getThresholdColour";
|
import { GetThresholdOfColor } from "@/seqta/ui/colors/getThresholdColour";
|
||||||
@@ -385,7 +386,7 @@ function appendNoticeEmptyState(container: HTMLElement, message: string) {
|
|||||||
const emptyState = document.createElement("div");
|
const emptyState = document.createElement("div");
|
||||||
emptyState.classList.add("day-empty");
|
emptyState.classList.add("day-empty");
|
||||||
const img = document.createElement("img");
|
const img = document.createElement("img");
|
||||||
img.src = browser.runtime.getURL(LogoLight);
|
img.src = resolveExtensionAssetUrl(LogoLight);
|
||||||
const text = document.createElement("p");
|
const text = document.createElement("p");
|
||||||
text.innerText = message;
|
text.innerText = message;
|
||||||
emptyState.append(img, text);
|
emptyState.append(img, text);
|
||||||
@@ -786,7 +787,7 @@ function callHomeTimetable(date: string, change?: any) {
|
|||||||
const dummyDay = document.createElement("div");
|
const dummyDay = document.createElement("div");
|
||||||
dummyDay.classList.add("day-empty");
|
dummyDay.classList.add("day-empty");
|
||||||
const img = document.createElement("img");
|
const img = document.createElement("img");
|
||||||
img.src = browser.runtime.getURL(LogoLight);
|
img.src = resolveExtensionAssetUrl(LogoLight);
|
||||||
const text = document.createElement("p");
|
const text = document.createElement("p");
|
||||||
text.innerText = "No lessons available.";
|
text.innerText = "No lessons available.";
|
||||||
dummyDay.append(img, text);
|
dummyDay.append(img, text);
|
||||||
@@ -1096,7 +1097,7 @@ async function CreateUpcomingSection(assessments: any, activeSubjects: any) {
|
|||||||
if (assessments.length === 0) {
|
if (assessments.length === 0) {
|
||||||
upcomingitemcontainer!.innerHTML = `
|
upcomingitemcontainer!.innerHTML = `
|
||||||
<div class="day-empty">
|
<div class="day-empty">
|
||||||
<img src="${browser.runtime.getURL(LogoLight)}" />
|
<img src="${resolveExtensionAssetUrl(LogoLight)}" />
|
||||||
<p>No assessments available.</p>
|
<p>No assessments available.</p>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { delay } from "./delay";
|
|||||||
import { settingsState } from "./listeners/SettingsState";
|
import { settingsState } from "./listeners/SettingsState";
|
||||||
import browser from "webextension-polyfill";
|
import browser from "webextension-polyfill";
|
||||||
import LogoLightOutline from "@/resources/icons/betterseqta-light-outline.png";
|
import LogoLightOutline from "@/resources/icons/betterseqta-light-outline.png";
|
||||||
|
import { resolveExtensionAssetUrl } from "@/lib/extensionAssetUrl";
|
||||||
import { animate, stagger } from "motion";
|
import { animate, stagger } from "motion";
|
||||||
import { verboseInfo } from "@/utils/verboseLog";
|
import { verboseInfo } from "@/utils/verboseLog";
|
||||||
|
|
||||||
@@ -65,7 +66,7 @@ export async function SendNewsPage() {
|
|||||||
const emptyState = document.createElement("div");
|
const emptyState = document.createElement("div");
|
||||||
emptyState.classList.add("day-empty");
|
emptyState.classList.add("day-empty");
|
||||||
const img = document.createElement("img");
|
const img = document.createElement("img");
|
||||||
img.src = browser.runtime.getURL(LogoLightOutline);
|
img.src = resolveExtensionAssetUrl(LogoLightOutline);
|
||||||
const text = document.createElement("p");
|
const text = document.createElement("p");
|
||||||
text.innerText = "No news articles available right now.";
|
text.innerText = "No news articles available right now.";
|
||||||
emptyState.append(img, text);
|
emptyState.append(img, text);
|
||||||
@@ -86,7 +87,7 @@ export async function SendNewsPage() {
|
|||||||
|
|
||||||
if (article.urlToImage == "null" || article.urlToImage == null) {
|
if (article.urlToImage == "null" || article.urlToImage == null) {
|
||||||
articleimage.style.cssText = `
|
articleimage.style.cssText = `
|
||||||
background-image: url(${browser.runtime.getURL(LogoLightOutline)});
|
background-image: url(${resolveExtensionAssetUrl(LogoLightOutline)});
|
||||||
width: 20%;
|
width: 20%;
|
||||||
margin: 0 7.5%;
|
margin: 0 7.5%;
|
||||||
`;
|
`;
|
||||||
|
|||||||
Reference in New Issue
Block a user