Compare commits
No commits in common. "main" and "v1.4" have entirely different histories.
@ -1,9 +1,5 @@
|
|||||||
browser.commands.onCommand.addListener((command) => {
|
browser.commands.onCommand.addListener((command) => {
|
||||||
if (
|
if (command === "print-selection" || command === "fill-input") {
|
||||||
command === "print-selection" ||
|
|
||||||
command === "fill-input" ||
|
|
||||||
command === "toggle-stealth"
|
|
||||||
) {
|
|
||||||
browser.tabs.query({ active: true, currentWindow: true }).then((tabs) => {
|
browser.tabs.query({ active: true, currentWindow: true }).then((tabs) => {
|
||||||
browser.tabs.sendMessage(tabs[0].id, { action: command });
|
browser.tabs.sendMessage(tabs[0].id, { action: command });
|
||||||
});
|
});
|
||||||
|
BIN
src/build.xpi
BIN
src/build.xpi
Binary file not shown.
166
src/content.js
166
src/content.js
@ -1,25 +1,21 @@
|
|||||||
// contents
|
// Constants
|
||||||
const DEFAULT_SETTINGS = {
|
const DEFAULT_SETTINGS = {
|
||||||
apiUrl: "https://api.elia.network",
|
apiUrl: "https://api.elia.network",
|
||||||
apiKey: "sk-TvFhtxHAPXEcmRtyva-ctA",
|
apiKey: "sk-TvFhtxHAPXEcmRtyva-ctA",
|
||||||
textOpacity: 10,
|
textOpacity: 10,
|
||||||
model: "llama3.2-90b",
|
model: "llama3.2-90b",
|
||||||
background: false,
|
background: false,
|
||||||
examModeStates: {},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const MAX_CONTEXT_LENGTH = 6000;
|
const MAX_CONTEXT_LENGTH = 6000;
|
||||||
|
|
||||||
// States
|
// States (for later in the code)
|
||||||
let overlay = null;
|
let overlay = null;
|
||||||
let isVisible = false;
|
let isVisible = false;
|
||||||
let lastKnownSelection = null;
|
let lastKnownSelection = null;
|
||||||
let lastKnownRange = null;
|
let lastKnownRange = null;
|
||||||
let scrollTimeout = null;
|
let scrollTimeout = null;
|
||||||
let resizeRAF = null;
|
let resizeRAF = null;
|
||||||
let stealthMode = false;
|
|
||||||
let stealthAnswer = null;
|
|
||||||
let stealthFields = new Set();
|
|
||||||
|
|
||||||
// helper Functions
|
// helper Functions
|
||||||
const getPageContext = () => {
|
const getPageContext = () => {
|
||||||
@ -54,7 +50,7 @@ const queryLLM = async (text) => {
|
|||||||
messages: [
|
messages: [
|
||||||
{
|
{
|
||||||
role: "user",
|
role: "user",
|
||||||
content: `You are tasked to solve an exam. It is a website. Exam's page context: ${pageContext}\n\Answer this question of said exam in a very very short but accurate manner (Utilize context to figure out the exam's topic. ONLY REPLY WITH ANSWER TO THE QUESTION. If you do not know, reply with "."): ${text}`,
|
content: `You are tasked to solve an exam. It is a website. Exam's page context: ${pageContext}\n\Answer this question of said exam in a very short but accurate manner (Utilize context to figure out the exam's topic. ONLY REPLY WITH ANSWER TO THE QUESTION. If you do not know, reply with "I do not know."): ${text}`,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
temperature: 0.1,
|
temperature: 0.1,
|
||||||
@ -71,106 +67,6 @@ const queryLLM = async (text) => {
|
|||||||
return data.choices[0].message.content;
|
return data.choices[0].message.content;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Stealth Mode Functions
|
|
||||||
const handleStealthTyping = (event) => {
|
|
||||||
if (!stealthMode || !stealthAnswer || !stealthFields.has(event.target))
|
|
||||||
return;
|
|
||||||
|
|
||||||
event.preventDefault();
|
|
||||||
const input = event.target;
|
|
||||||
const currentLength = input.value.length;
|
|
||||||
|
|
||||||
if (currentLength < stealthAnswer.length) {
|
|
||||||
input.value = stealthAnswer.substring(0, currentLength + 1);
|
|
||||||
} else {
|
|
||||||
deactivateStealthMode();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const findQuestionTextAbove = (inputElement) => {
|
|
||||||
const inputRect = inputElement.getBoundingClientRect();
|
|
||||||
const spans = Array.from(
|
|
||||||
document.querySelectorAll("span.text-format-content"),
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
spans
|
|
||||||
.filter((span) => {
|
|
||||||
const spanRect = span.getBoundingClientRect();
|
|
||||||
return (
|
|
||||||
spanRect.bottom <= inputRect.top &&
|
|
||||||
Math.abs(spanRect.left - inputRect.left) < 200
|
|
||||||
); // Allow some horizontal tolerance
|
|
||||||
})
|
|
||||||
.sort((a, b) => {
|
|
||||||
const aRect = a.getBoundingClientRect();
|
|
||||||
const bRect = b.getBoundingClientRect();
|
|
||||||
return bRect.bottom - aRect.bottom; // Get the closest span above
|
|
||||||
})[0]
|
|
||||||
?.textContent.trim() || null
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const activateStealthMode = async () => {
|
|
||||||
const activeElement = document.activeElement;
|
|
||||||
const isInputField = activeElement.matches(
|
|
||||||
'input[type="text"], input:not([type]), textarea',
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isInputField) {
|
|
||||||
// Input field is focused, find question text above
|
|
||||||
const questionText = findQuestionTextAbove(activeElement);
|
|
||||||
if (questionText) {
|
|
||||||
try {
|
|
||||||
stealthAnswer = await queryLLM(questionText);
|
|
||||||
stealthMode = true;
|
|
||||||
stealthFields.add(activeElement);
|
|
||||||
activeElement.addEventListener("keypress", handleStealthTyping);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error activating stealth mode:", error);
|
|
||||||
stealthMode = false;
|
|
||||||
stealthAnswer = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Original selection-based functionality
|
|
||||||
const selection = window.getSelection();
|
|
||||||
const selectedText = selection.toString().trim();
|
|
||||||
|
|
||||||
if (!selectedText) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
stealthAnswer = await queryLLM(selectedText);
|
|
||||||
stealthMode = true;
|
|
||||||
|
|
||||||
const inputs = document.querySelectorAll(
|
|
||||||
'input[type="text"], input:not([type]), textarea',
|
|
||||||
);
|
|
||||||
|
|
||||||
inputs.forEach((input) => {
|
|
||||||
stealthFields.add(input);
|
|
||||||
input.addEventListener("keypress", handleStealthTyping);
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error activating stealth mode:", error);
|
|
||||||
stealthMode = false;
|
|
||||||
stealthAnswer = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const deactivateStealthMode = () => {
|
|
||||||
stealthMode = false;
|
|
||||||
stealthAnswer = null;
|
|
||||||
|
|
||||||
stealthFields.forEach((input) => {
|
|
||||||
input.removeEventListener("keypress", handleStealthTyping);
|
|
||||||
});
|
|
||||||
|
|
||||||
stealthFields.clear();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Overlay/input fields
|
// Overlay/input fields
|
||||||
const createOverlay = (text) => {
|
const createOverlay = (text) => {
|
||||||
if (overlay) overlay.remove();
|
if (overlay) overlay.remove();
|
||||||
@ -217,22 +113,29 @@ const positionOverlay = () => {
|
|||||||
const range = selection.getRangeAt(0);
|
const range = selection.getRangeAt(0);
|
||||||
const rect = range.getBoundingClientRect();
|
const rect = range.getBoundingClientRect();
|
||||||
|
|
||||||
|
// Get scroll positions
|
||||||
|
// this is deprecated but currently the only way thank you mr mozilla
|
||||||
const scrollX = window.pageXOffset || document.documentElement.scrollLeft;
|
const scrollX = window.pageXOffset || document.documentElement.scrollLeft;
|
||||||
const scrollY = window.pageYOffset || document.documentElement.scrollTop;
|
const scrollY = window.pageYOffset || document.documentElement.scrollTop;
|
||||||
|
|
||||||
|
// Get viewport dimensions
|
||||||
const viewportWidth =
|
const viewportWidth =
|
||||||
window.innerWidth || document.documentElement.clientWidth;
|
window.innerWidth || document.documentElement.clientWidth;
|
||||||
const viewportHeight =
|
const viewportHeight =
|
||||||
window.innerHeight || document.documentElement.clientHeight;
|
window.innerHeight || document.documentElement.clientHeight;
|
||||||
|
|
||||||
|
// Calculate overlay dimensions
|
||||||
const overlayWidth = overlay.offsetWidth;
|
const overlayWidth = overlay.offsetWidth;
|
||||||
const overlayHeight = overlay.offsetHeight;
|
const overlayHeight = overlay.offsetHeight;
|
||||||
|
|
||||||
|
// Calculate positions
|
||||||
let top = rect.top + scrollY;
|
let top = rect.top + scrollY;
|
||||||
let left = rect.left + scrollX + rect.width / 2 - overlayWidth / 2;
|
let left = rect.left + scrollX + rect.width / 2 - overlayWidth / 2;
|
||||||
|
|
||||||
|
// Position above selection by default
|
||||||
top -= overlayHeight + 5;
|
top -= overlayHeight + 5;
|
||||||
|
|
||||||
|
// If overlay would go above viewport, position it below selection
|
||||||
if (top - scrollY < 0) {
|
if (top - scrollY < 0) {
|
||||||
top = rect.bottom + scrollY + 5;
|
top = rect.bottom + scrollY + 5;
|
||||||
}
|
}
|
||||||
@ -242,6 +145,7 @@ const positionOverlay = () => {
|
|||||||
Math.min(left, scrollX + viewportWidth - overlayWidth - 10),
|
Math.min(left, scrollX + viewportWidth - overlayWidth - 10),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// apply
|
||||||
overlay.style.top = `${top}px`;
|
overlay.style.top = `${top}px`;
|
||||||
overlay.style.left = `${left}px`;
|
overlay.style.left = `${left}px`;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -272,15 +176,18 @@ const handleResize = () => {
|
|||||||
|
|
||||||
window.addEventListener("resize", handleResize, { passive: true });
|
window.addEventListener("resize", handleResize, { passive: true });
|
||||||
|
|
||||||
// Scroll handlers
|
// Shitty fix for microsoft forms (they use containers)
|
||||||
const attachScrollListeners = () => {
|
const attachScrollListeners = () => {
|
||||||
|
// Listen to window scroll
|
||||||
window.addEventListener("scroll", handleScroll, { passive: true });
|
window.addEventListener("scroll", handleScroll, { passive: true });
|
||||||
|
|
||||||
|
// Listen to scroll events on all scrollable elements
|
||||||
document.addEventListener("scroll", handleScroll, {
|
document.addEventListener("scroll", handleScroll, {
|
||||||
capture: true,
|
capture: true,
|
||||||
passive: true,
|
passive: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Find and attach listeners to all scrollable containers
|
||||||
const scrollableElements = document.querySelectorAll(
|
const scrollableElements = document.querySelectorAll(
|
||||||
[
|
[
|
||||||
'*[style*="overflow: auto"]',
|
'*[style*="overflow: auto"]',
|
||||||
@ -298,6 +205,7 @@ const attachScrollListeners = () => {
|
|||||||
element.addEventListener("scroll", handleScroll, { passive: true });
|
element.addEventListener("scroll", handleScroll, { passive: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Handle dynamic content changes (needed for forms after page load)
|
||||||
const observer = new MutationObserver((mutations) => {
|
const observer = new MutationObserver((mutations) => {
|
||||||
mutations.forEach((mutation) => {
|
mutations.forEach((mutation) => {
|
||||||
if (mutation.addedNodes.length) {
|
if (mutation.addedNodes.length) {
|
||||||
@ -332,44 +240,12 @@ const attachScrollListeners = () => {
|
|||||||
|
|
||||||
attachScrollListeners();
|
attachScrollListeners();
|
||||||
|
|
||||||
// Stealth Mode Observer
|
|
||||||
const stealthObserver = new MutationObserver((mutations) => {
|
|
||||||
if (!stealthMode) return;
|
|
||||||
|
|
||||||
mutations.forEach((mutation) => {
|
|
||||||
mutation.addedNodes.forEach((node) => {
|
|
||||||
if (node.nodeType === 1) {
|
|
||||||
const inputs = node.querySelectorAll(
|
|
||||||
'input[type="text"], input:not([type]), textarea',
|
|
||||||
);
|
|
||||||
inputs.forEach((input) => {
|
|
||||||
if (!stealthFields.has(input)) {
|
|
||||||
stealthFields.add(input);
|
|
||||||
input.addEventListener("keypress", handleStealthTyping);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
stealthObserver.observe(document.body, {
|
|
||||||
childList: true,
|
|
||||||
subtree: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Message Handler
|
// Message Handler
|
||||||
browser.runtime.onMessage.addListener((message) => {
|
browser.runtime.onMessage.addListener((message) => {
|
||||||
if (message.action === "print-selection") {
|
if (message.action === "print-selection") {
|
||||||
toggleOverlay();
|
toggleOverlay();
|
||||||
} else if (message.action === "fill-input") {
|
} else if (message.action === "fill-input") {
|
||||||
modifyNearestInput();
|
modifyNearestInput();
|
||||||
} else if (message.action === "toggle-stealth") {
|
|
||||||
if (!stealthMode) {
|
|
||||||
activateStealthMode();
|
|
||||||
} else {
|
|
||||||
deactivateStealthMode();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -407,25 +283,27 @@ function findNearestInput(selectionNode) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!inputs.length) return null;
|
if (!inputs.length) return null;
|
||||||
|
// yes i know this is overkill
|
||||||
const selection = window.getSelection();
|
const selection = window.getSelection();
|
||||||
const range = selection.getRangeAt(0);
|
const range = selection.getRangeAt(0);
|
||||||
const rect = range.getBoundingClientRect();
|
const rect = range.getBoundingClientRect();
|
||||||
|
// calc center point of users selection
|
||||||
const selectionX = rect.left + rect.width / 2;
|
const selectionX = rect.left + rect.width / 2;
|
||||||
const selectionY = rect.top + rect.height / 2;
|
const selectionY = rect.top + rect.height / 2;
|
||||||
|
|
||||||
let nearestInput = null;
|
let nearestInput = null;
|
||||||
let shortestDistance = Infinity;
|
let shortestDistance = Infinity;
|
||||||
|
// loop through all available inputs and figure out the shortest one
|
||||||
inputs.forEach((input) => {
|
inputs.forEach((input) => {
|
||||||
const inputRect = input.getBoundingClientRect();
|
const inputRect = input.getBoundingClientRect();
|
||||||
const inputX = inputRect.left + inputRect.width / 2;
|
const inputX = inputRect.left + inputRect.width / 2;
|
||||||
const inputY = inputRect.top + inputRect.height / 2;
|
const inputY = inputRect.top + inputRect.height / 2;
|
||||||
|
|
||||||
const distance = Math.sqrt(
|
const distance = Math.sqrt(
|
||||||
|
// do pythagora
|
||||||
Math.pow(selectionX - inputX, 2) + Math.pow(selectionY - inputY, 2),
|
Math.pow(selectionX - inputX, 2) + Math.pow(selectionY - inputY, 2),
|
||||||
);
|
);
|
||||||
|
// this could be cleaner but it works as-is
|
||||||
if (distance < shortestDistance) {
|
if (distance < shortestDistance) {
|
||||||
shortestDistance = distance;
|
shortestDistance = distance;
|
||||||
nearestInput = input;
|
nearestInput = input;
|
||||||
@ -434,7 +312,7 @@ function findNearestInput(selectionNode) {
|
|||||||
|
|
||||||
return nearestInput;
|
return nearestInput;
|
||||||
}
|
}
|
||||||
|
// moved to seperate function for cleaner code (it is still a mess)
|
||||||
const modifyNearestInput = async () => {
|
const modifyNearestInput = async () => {
|
||||||
const selection = window.getSelection();
|
const selection = window.getSelection();
|
||||||
const selectedText = selection.toString().trim();
|
const selectedText = selection.toString().trim();
|
||||||
|
@ -18,14 +18,7 @@
|
|||||||
"js": ["content.js"]
|
"js": ["content.js"]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"permissions": [
|
"permissions": ["activeTab", "storage", "*://api.elia.network/*"],
|
||||||
"activeTab",
|
|
||||||
"storage",
|
|
||||||
"*://api.elia.network/*",
|
|
||||||
"scripting",
|
|
||||||
"tabs",
|
|
||||||
"<all_urls>"
|
|
||||||
],
|
|
||||||
"commands": {
|
"commands": {
|
||||||
"print-selection": {
|
"print-selection": {
|
||||||
"suggested_key": {
|
"suggested_key": {
|
||||||
@ -35,15 +28,9 @@
|
|||||||
},
|
},
|
||||||
"fill-input": {
|
"fill-input": {
|
||||||
"suggested_key": {
|
"suggested_key": {
|
||||||
"default": "Ctrl+Alt+Space"
|
"default": "Ctrl+Shift+Space"
|
||||||
},
|
},
|
||||||
"description": "Process question and fill into nearest input field"
|
"description": "Process question and fill into nearest input field"
|
||||||
},
|
|
||||||
"toggle-stealth": {
|
|
||||||
"suggested_key": {
|
|
||||||
"default": "Ctrl+E"
|
|
||||||
},
|
|
||||||
"description": "Toggle stealth mode"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"browser_action": {
|
"browser_action": {
|
||||||
|
@ -163,33 +163,6 @@
|
|||||||
background: var(--primary-hover);
|
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 {
|
#status {
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
@ -208,6 +181,7 @@
|
|||||||
color: #fef2f2;
|
color: #fef2f2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Scrollbar Styling */
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 8px;
|
width: 8px;
|
||||||
height: 8px;
|
height: 8px;
|
||||||
@ -230,7 +204,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<h2>CAS</h2>
|
<h2>CAS</h2>
|
||||||
<span class="version-badge">v1.5</span>
|
<span class="version-badge">v1.4</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form id="settingsForm">
|
<form id="settingsForm">
|
||||||
@ -292,8 +266,6 @@
|
|||||||
<button type="submit" class="save-button">Save Settings</button>
|
<button type="submit" class="save-button">Save Settings</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<button id="examMode" class="exam-mode-button">Enable Exam Mode</button>
|
|
||||||
|
|
||||||
<div id="status"></div>
|
<div id="status"></div>
|
||||||
|
|
||||||
<script src="popup.js"></script>
|
<script src="popup.js"></script>
|
||||||
|
198
src/popup.js
198
src/popup.js
@ -4,128 +4,14 @@ const DEFAULT_SETTINGS = {
|
|||||||
textOpacity: 10,
|
textOpacity: 10,
|
||||||
model: "llama3.2-90b",
|
model: "llama3.2-90b",
|
||||||
background: false,
|
background: false,
|
||||||
examModeStates: {},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// track for each tab
|
// Load settings when the popup opens
|
||||||
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 () => {
|
document.addEventListener("DOMContentLoaded", async () => {
|
||||||
try {
|
try {
|
||||||
const settings = await loadSettings();
|
const settings = await loadSettings();
|
||||||
// load settings into the form
|
|
||||||
|
// Populate form fields
|
||||||
document.getElementById("apiUrl").value = settings.apiUrl;
|
document.getElementById("apiUrl").value = settings.apiUrl;
|
||||||
document.getElementById("model").value = settings.model;
|
document.getElementById("model").value = settings.model;
|
||||||
document.getElementById("apiKey").value = settings.apiKey;
|
document.getElementById("apiKey").value = settings.apiKey;
|
||||||
@ -133,59 +19,23 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
document.getElementById("opacityValue").textContent =
|
document.getElementById("opacityValue").textContent =
|
||||||
`${settings.textOpacity}%`;
|
`${settings.textOpacity}%`;
|
||||||
document.getElementById("background").checked = settings.background;
|
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) {
|
} catch (error) {
|
||||||
console.error("Error loading settings:", error);
|
console.error("Error loading settings:", error);
|
||||||
showStatus("Error loading settings", "error");
|
showStatus("Error loading settings", "error");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// handle opacity slider
|
// Handle opacity slider changes
|
||||||
document.getElementById("textOpacity").addEventListener("input", (e) => {
|
document.getElementById("textOpacity").addEventListener("input", (e) => {
|
||||||
document.getElementById("opacityValue").textContent = `${e.target.value}%`;
|
document.getElementById("opacityValue").textContent = `${e.target.value}%`;
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById("examMode").addEventListener("click", async () => {
|
// Handle form submission
|
||||||
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
|
document
|
||||||
.getElementById("settingsForm")
|
.getElementById("settingsForm")
|
||||||
.addEventListener("submit", async (e) => {
|
.addEventListener("submit", async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const settings = {
|
const settings = {
|
||||||
apiUrl: document.getElementById("apiUrl").value,
|
apiUrl: document.getElementById("apiUrl").value,
|
||||||
apiKey: document.getElementById("apiKey").value,
|
apiKey: document.getElementById("apiKey").value,
|
||||||
@ -193,6 +43,7 @@ document
|
|||||||
textOpacity: parseInt(document.getElementById("textOpacity").value),
|
textOpacity: parseInt(document.getElementById("textOpacity").value),
|
||||||
background: document.getElementById("background").checked,
|
background: document.getElementById("background").checked,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await browser.storage.sync.set({ settings });
|
await browser.storage.sync.set({ settings });
|
||||||
showStatus("Settings saved successfully!", "success");
|
showStatus("Settings saved successfully!", "success");
|
||||||
@ -202,27 +53,30 @@ document
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
browser.tabs.onActivated.addListener(async (activeInfo) => {
|
// Load settings from storage or use defaults
|
||||||
const tabId = activeInfo.tabId;
|
async function loadSettings() {
|
||||||
const tabExamMode = tabStates.get(tabId) || false;
|
|
||||||
updateExamModeButton(tabExamMode);
|
|
||||||
});
|
|
||||||
|
|
||||||
browser.tabs.onRemoved.addListener(async (tabId) => {
|
|
||||||
try {
|
try {
|
||||||
const data = await browser.storage.sync.get("settings");
|
const data = await browser.storage.sync.get("settings");
|
||||||
const settings = { ...DEFAULT_SETTINGS, ...data.settings };
|
return { ...DEFAULT_SETTINGS, ...data.settings };
|
||||||
|
|
||||||
if (settings.examModeStates[tabId]) {
|
|
||||||
delete settings.examModeStates[tabId];
|
|
||||||
await browser.storage.sync.set({ settings });
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error cleaning up exam mode state:", error);
|
console.error("Error loading settings:", error);
|
||||||
|
return DEFAULT_SETTINGS;
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
// errorhandler
|
// 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
|
||||||
window.addEventListener("error", (event) => {
|
window.addEventListener("error", (event) => {
|
||||||
console.error("Error:", event.error);
|
console.error("Error:", event.error);
|
||||||
showStatus("An error occurred", "error");
|
showStatus("An error occurred", "error");
|
||||||
|
Loading…
Reference in New Issue
Block a user