diff --git a/README.md b/README.md index 738a787..0c11e8a 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ A simple, versatile language processing interface for websites. - Adjust opacity of responses - Hidden - Only visible to those who know where to look +- Exam mode + - Make text selection hard to see to avoid detection ## Usage After installing the extensions, selecting text and pressing `Ctrl+Space` will display the answer above the selected text in the configured opacity. To hide the answer, press `Ctrl+Space` again without selecting any text. diff --git a/src/content.js b/src/content.js index a414bc2..1e7c7dc 100644 --- a/src/content.js +++ b/src/content.js @@ -1,10 +1,11 @@ -// Constants +// contents const DEFAULT_SETTINGS = { apiUrl: "https://api.elia.network", apiKey: "sk-TvFhtxHAPXEcmRtyva-ctA", textOpacity: 10, model: "llama3.2-90b", background: false, + examModeStates: {}, }; const MAX_CONTEXT_LENGTH = 6000; diff --git a/src/manifest.json b/src/manifest.json index ea56ca6..554a408 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -18,7 +18,14 @@ "js": ["content.js"] } ], - "permissions": ["activeTab", "storage", "*://api.elia.network/*"], + "permissions": [ + "activeTab", + "storage", + "*://api.elia.network/*", + "scripting", + "tabs", + "" + ], "commands": { "print-selection": { "suggested_key": { diff --git a/src/popup.html b/src/popup.html index 013bb50..8942f35 100644 --- a/src/popup.html +++ b/src/popup.html @@ -163,6 +163,33 @@ background: var(--primary-hover); } + .exam-mode-button { + width: 100%; + padding: 10px; + background: var(--surface); + color: var(--text); + border: 1px solid var(--border); + border-radius: 6px; + font-size: 13px; + font-weight: 500; + cursor: pointer; + transition: all 0.15s ease; + margin-top: 12px; + } + + .exam-mode-button:hover { + background: var(--surface-hover); + } + + .exam-mode-button.active { + background: #065f46; + border-color: #059669; + } + + .exam-mode-button.active:hover { + background: #047857; + } + #status { margin-top: 12px; padding: 8px 12px; @@ -181,7 +208,6 @@ color: #fef2f2; } - /* Scrollbar Styling */ ::-webkit-scrollbar { width: 8px; height: 8px; @@ -204,7 +230,7 @@

CAS

- v1.4 + v1.5
@@ -266,6 +292,8 @@
+ +
diff --git a/src/popup.js b/src/popup.js index f51f868..11d51ce 100644 --- a/src/popup.js +++ b/src/popup.js @@ -4,14 +4,128 @@ const DEFAULT_SETTINGS = { textOpacity: 10, model: "llama3.2-90b", background: false, + examModeStates: {}, }; -// Load settings when the popup opens +// track for each tab +const tabStates = new Map(); + +async function updateExamModeState(tabId, enabled) { + try { + const data = await browser.storage.sync.get("settings"); + const settings = { ...DEFAULT_SETTINGS, ...data.settings }; + + settings.examModeStates = { + ...settings.examModeStates, + [tabId]: enabled, + }; + + await browser.storage.sync.set({ settings }); + } catch (error) { + console.error("Error updating exam mode state:", error); + } +} + +// scriptinjector +async function injectExamModeScript(enabled) { + try { + const tabs = await browser.tabs.query({ + active: true, + currentWindow: true, + }); + const tabId = tabs[0].id; + + // update for this tab + tabStates.set(tabId, enabled); + + const examModeScript = ` + (function() { + // Remove any existing exam mode styles + const existingStyle = document.getElementById('exam-mode-style'); + if (existingStyle) { + existingStyle.remove(); + } + + // If enabled, add the new style + if (${enabled}) { + const style = document.createElement('style'); + style.id = 'exam-mode-style'; + style.textContent = \` + ::selection { + background-color: rgba(0, 0, 255, 0.05) !important; + } + ::-moz-selection { + background-color: rgba(0, 0, 255, 0.05) !important; + } + \`; + document.head.appendChild(style); + } + return true; + })(); + `; + + // inject ;3 + await browser.tabs.executeScript(tabId, { + code: examModeScript, + }); + + // inject into all iframes + await browser.tabs.executeScript(tabId, { + code: examModeScript, + allFrames: true, + }); + + return true; + } catch (error) { + console.error("Error injecting exam mode script:", error); + if (error.message.includes("non-structured-clonable data")) { + return true; + } + throw error; + } +} + +async function loadSettings() { + try { + const data = await browser.storage.sync.get("settings"); + const settings = { ...DEFAULT_SETTINGS, ...data.settings }; + + // get exam mode status for this tab + const tabs = await browser.tabs.query({ + active: true, + currentWindow: true, + }); + const tabId = tabs[0].id; + const tabExamMode = settings.examModeStates[tabId] || false; + + return { ...settings, examMode: tabExamMode }; + } catch (error) { + console.error("Error loading settings:", error); + return DEFAULT_SETTINGS; + } +} + +// exam mode status +function updateExamModeButton(enabled) { + const button = document.getElementById("examMode"); + button.textContent = enabled ? "Disable Exam Mode" : "Enable Exam Mode"; + button.classList.toggle("active", enabled); +} + +function showStatus(message, type) { + const status = document.getElementById("status"); + status.textContent = message; + status.className = type; + status.style.display = "block"; + setTimeout(() => { + status.style.display = "none"; + }, 2000); +} + document.addEventListener("DOMContentLoaded", async () => { try { const settings = await loadSettings(); - - // Populate form fields + // load settings into the form document.getElementById("apiUrl").value = settings.apiUrl; document.getElementById("model").value = settings.model; document.getElementById("apiKey").value = settings.apiKey; @@ -19,23 +133,59 @@ document.addEventListener("DOMContentLoaded", async () => { document.getElementById("opacityValue").textContent = `${settings.textOpacity}%`; document.getElementById("background").checked = settings.background; + + // update button state + updateExamModeButton(settings.examMode); + + // if exam mode is enabled, inject the script + if (settings.examMode) { + await injectExamModeScript(true); + } } catch (error) { console.error("Error loading settings:", error); showStatus("Error loading settings", "error"); } }); -// Handle opacity slider changes +// handle opacity slider document.getElementById("textOpacity").addEventListener("input", (e) => { document.getElementById("opacityValue").textContent = `${e.target.value}%`; }); -// Handle form submission +document.getElementById("examMode").addEventListener("click", async () => { + try { + const tabs = await browser.tabs.query({ + active: true, + currentWindow: true, + }); + const tabId = tabs[0].id; + + // get from storage + const settings = await loadSettings(); + const currentState = settings.examMode; + const newExamMode = !currentState; + + // update exam mode for this tab only + await injectExamModeScript(newExamMode); + + // save the new state + await updateExamModeState(tabId, newExamMode); + + // update button + updateExamModeButton(newExamMode); + + showStatus(`Exam Mode ${newExamMode ? "enabled" : "disabled"}`, "success"); + } catch (error) { + console.error("Error toggling exam mode:", error); + showStatus("Error toggling exam mode", "error"); + } +}); + +// form submission handler document .getElementById("settingsForm") .addEventListener("submit", async (e) => { e.preventDefault(); - const settings = { apiUrl: document.getElementById("apiUrl").value, apiKey: document.getElementById("apiKey").value, @@ -43,7 +193,6 @@ document textOpacity: parseInt(document.getElementById("textOpacity").value), background: document.getElementById("background").checked, }; - try { await browser.storage.sync.set({ settings }); showStatus("Settings saved successfully!", "success"); @@ -53,30 +202,27 @@ document } }); -// Load settings from storage or use defaults -async function loadSettings() { +browser.tabs.onActivated.addListener(async (activeInfo) => { + const tabId = activeInfo.tabId; + const tabExamMode = tabStates.get(tabId) || false; + updateExamModeButton(tabExamMode); +}); + +browser.tabs.onRemoved.addListener(async (tabId) => { try { const data = await browser.storage.sync.get("settings"); - return { ...DEFAULT_SETTINGS, ...data.settings }; + const settings = { ...DEFAULT_SETTINGS, ...data.settings }; + + if (settings.examModeStates[tabId]) { + delete settings.examModeStates[tabId]; + await browser.storage.sync.set({ settings }); + } } catch (error) { - console.error("Error loading settings:", error); - return DEFAULT_SETTINGS; + console.error("Error cleaning up exam mode state:", error); } -} +}); -// Show status message -function showStatus(message, type) { - const status = document.getElementById("status"); - status.textContent = message; - status.className = type; - status.style.display = "block"; - - setTimeout(() => { - status.style.display = "none"; - }, 2000); -} - -// Handle errors +// errorhandler window.addEventListener("error", (event) => { console.error("Error:", event.error); showStatus("An error occurred", "error");