mirror of
https://github.com/onyx-dot-app/onyx.git
synced 2026-02-16 23:35:46 +00:00
chore(extensions): pull in chrome extension (#7703)
This commit is contained in:
21
extensions/chrome/LICENSE
Normal file
21
extensions/chrome/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 DanswerAI, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
30
extensions/chrome/README.md
Normal file
30
extensions/chrome/README.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Onyx Chrome Extension
|
||||
|
||||
The Onyx chrome extension lets you research, create, and automate with LLMs powered by your team's unique knowledge. Just hit Ctrl + O on Mac or Alt + O on Windows to instantly access Onyx in your browser:
|
||||
|
||||
💡 Know what your company knows, instantly with the Onyx sidebar
|
||||
💬 Chat: Onyx provides a natural language chat interface as the main way of interacting with the features.
|
||||
🌎 Internal Search: Ask questions and get answers from all your team's knowledge, powered by Onyx's 50+ connectors to all the tools your team uses
|
||||
🚀 With a simple Ctrl + O on Mac or Alt + O on Windows - instantly summarize information from any work application
|
||||
|
||||
⚡️ Get quick access to the work resources you need.
|
||||
🆕 Onyx new tab page puts all of your company’s knowledge at your fingertips
|
||||
🤖 Access custom AI Agents for unique use cases, and give them access to tools to take action.
|
||||
|
||||
—
|
||||
|
||||
Onyx connects with dozens of popular workplace apps like Google Drive, Jira, Confluence, Slack, and more. Use this extension if you have an account created by your team admin.
|
||||
|
||||
## Installation
|
||||
|
||||
For Onyx Cloud Users, please visit the Chrome Plugin Store (pending approval still)
|
||||
|
||||
## Development
|
||||
|
||||
- Load unpacked extension in your browser
|
||||
- Modify files in `src` directory
|
||||
- Refresh extension in Chrome
|
||||
|
||||
## Contributing
|
||||
|
||||
Submit issues or pull requests for improvements
|
||||
70
extensions/chrome/manifest.json
Normal file
70
extensions/chrome/manifest.json
Normal file
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "Onyx",
|
||||
"version": "1.0",
|
||||
"description": "Onyx lets you research, create, and automate with LLMs powered by your team's unique knowledge",
|
||||
"permissions": [
|
||||
"sidePanel",
|
||||
"storage",
|
||||
"activeTab",
|
||||
"tabs"
|
||||
],
|
||||
"host_permissions": ["<all_urls>"],
|
||||
"background": {
|
||||
"service_worker": "service_worker.js",
|
||||
"type": "module"
|
||||
},
|
||||
"action": {
|
||||
"default_icon": {
|
||||
"16": "public/icon16.png",
|
||||
"48": "public/icon48.png",
|
||||
"128": "public/icon128.png"
|
||||
},
|
||||
"default_popup": "src/pages/popup.html"
|
||||
},
|
||||
"icons": {
|
||||
"16": "public/icon16.png",
|
||||
"48": "public/icon48.png",
|
||||
"128": "public/icon128.png"
|
||||
},
|
||||
"options_page": "src/pages/options.html",
|
||||
"chrome_url_overrides": {
|
||||
"newtab": "src/pages/onyx_home.html"
|
||||
},
|
||||
"commands": {
|
||||
"toggleNewTabOverride": {
|
||||
"suggested_key": {
|
||||
"default": "Ctrl+Shift+O",
|
||||
"mac": "Command+Shift+O"
|
||||
},
|
||||
"description": "Toggle Onyx New Tab Override"
|
||||
},
|
||||
"openSidePanel": {
|
||||
"suggested_key": {
|
||||
"default": "Ctrl+O",
|
||||
"windows": "Alt+O",
|
||||
"mac": "MacCtrl+O"
|
||||
},
|
||||
"description": "Open Onyx Side Panel"
|
||||
}
|
||||
},
|
||||
"side_panel": {
|
||||
"default_path": "src/pages/panel.html"
|
||||
},
|
||||
"omnibox": {
|
||||
"keyword": "onyx"
|
||||
},
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": ["<all_urls>"],
|
||||
"js": ["src/utils/selection-icon.js"],
|
||||
"css": ["src/styles/selection-icon.css"]
|
||||
}
|
||||
],
|
||||
"web_accessible_resources": [
|
||||
{
|
||||
"resources": ["public/icon32.png"],
|
||||
"matches": ["<all_urls>"]
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
extensions/chrome/public/icon128.png
Normal file
BIN
extensions/chrome/public/icon128.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.5 KiB |
BIN
extensions/chrome/public/icon16.png
Normal file
BIN
extensions/chrome/public/icon16.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 235 B |
BIN
extensions/chrome/public/icon32.png
Normal file
BIN
extensions/chrome/public/icon32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 551 B |
BIN
extensions/chrome/public/icon48.png
Normal file
BIN
extensions/chrome/public/icon48.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
extensions/chrome/public/logo.png
Normal file
BIN
extensions/chrome/public/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.6 KiB |
276
extensions/chrome/service_worker.js
Normal file
276
extensions/chrome/service_worker.js
Normal file
@@ -0,0 +1,276 @@
|
||||
import {
|
||||
DEFAULT_ONYX_DOMAIN,
|
||||
CHROME_SPECIFIC_STORAGE_KEYS,
|
||||
ACTIONS,
|
||||
SIDE_PANEL_PATH,
|
||||
} from "./src/utils/constants.js";
|
||||
|
||||
// Track side panel state per window
|
||||
const sidePanelOpenState = new Map();
|
||||
|
||||
// Open welcome page on first install
|
||||
chrome.runtime.onInstalled.addListener((details) => {
|
||||
if (details.reason === "install") {
|
||||
chrome.storage.local.get(
|
||||
{ [CHROME_SPECIFIC_STORAGE_KEYS.ONBOARDING_COMPLETE]: false },
|
||||
(result) => {
|
||||
if (!result[CHROME_SPECIFIC_STORAGE_KEYS.ONBOARDING_COMPLETE]) {
|
||||
chrome.tabs.create({ url: "src/pages/welcome.html" });
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
async function setupSidePanel() {
|
||||
if (chrome.sidePanel) {
|
||||
try {
|
||||
// Don't auto-open side panel on action click since we have a popup menu
|
||||
await chrome.sidePanel.setPanelBehavior({
|
||||
openPanelOnActionClick: false,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error setting up side panel:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function openSidePanel(tabId) {
|
||||
try {
|
||||
await chrome.sidePanel.open({ tabId });
|
||||
} catch (error) {
|
||||
console.error("Error opening side panel:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async function sendToOnyx(info, tab) {
|
||||
const selectedText = encodeURIComponent(info.selectionText);
|
||||
const currentUrl = encodeURIComponent(tab.url);
|
||||
|
||||
try {
|
||||
const result = await chrome.storage.local.get({
|
||||
[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN]: DEFAULT_ONYX_DOMAIN,
|
||||
});
|
||||
const url = `${
|
||||
result[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN]
|
||||
}${SIDE_PANEL_PATH}?user-prompt=${selectedText}`;
|
||||
|
||||
await openSidePanel(tab.id);
|
||||
chrome.runtime.sendMessage({
|
||||
action: ACTIONS.OPEN_SIDE_PANEL_WITH_INPUT,
|
||||
url: url,
|
||||
pageUrl: tab.url,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error sending to Onyx:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleNewTabOverride() {
|
||||
try {
|
||||
const result = await chrome.storage.local.get(
|
||||
CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB,
|
||||
);
|
||||
const newValue =
|
||||
!result[CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB];
|
||||
await chrome.storage.local.set({
|
||||
[CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB]: newValue,
|
||||
});
|
||||
|
||||
chrome.notifications.create({
|
||||
type: "basic",
|
||||
iconUrl: "icon.png",
|
||||
title: "Onyx New Tab",
|
||||
message: `New Tab Override ${newValue ? "enabled" : "disabled"}`,
|
||||
});
|
||||
|
||||
// Send a message to inform all tabs about the change
|
||||
chrome.tabs.query({}, (tabs) => {
|
||||
tabs.forEach((tab) => {
|
||||
chrome.tabs.sendMessage(tab.id, {
|
||||
action: "newTabOverrideToggled",
|
||||
value: newValue,
|
||||
});
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error toggling new tab override:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Note: This listener won't fire when a popup is defined in manifest.json
|
||||
// The popup will show instead. This is kept as a fallback if popup is removed.
|
||||
chrome.action.onClicked.addListener((tab) => {
|
||||
openSidePanel(tab.id);
|
||||
});
|
||||
|
||||
chrome.commands.onCommand.addListener(async (command) => {
|
||||
if (command === ACTIONS.SEND_TO_ONYX) {
|
||||
try {
|
||||
const [tab] = await chrome.tabs.query({
|
||||
active: true,
|
||||
lastFocusedWindow: true,
|
||||
});
|
||||
if (tab) {
|
||||
const response = await chrome.tabs.sendMessage(tab.id, {
|
||||
action: ACTIONS.GET_SELECTED_TEXT,
|
||||
});
|
||||
const selectedText = response?.selectedText || "";
|
||||
sendToOnyx({ selectionText: selectedText }, tab);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error sending to Onyx:", error);
|
||||
}
|
||||
} else if (command === ACTIONS.TOGGLE_NEW_TAB_OVERRIDE) {
|
||||
toggleNewTabOverride();
|
||||
} else if (command === ACTIONS.CLOSE_SIDE_PANEL) {
|
||||
try {
|
||||
await chrome.sidePanel.hide();
|
||||
} catch (error) {
|
||||
console.error("Error closing side panel via command:", error);
|
||||
}
|
||||
} else if (command === ACTIONS.OPEN_SIDE_PANEL) {
|
||||
chrome.tabs.query({ active: true, lastFocusedWindow: true }, (tabs) => {
|
||||
if (tabs && tabs.length > 0) {
|
||||
const tab = tabs[0];
|
||||
const windowId = tab.windowId;
|
||||
const isOpen = sidePanelOpenState.get(windowId) || false;
|
||||
|
||||
if (isOpen) {
|
||||
chrome.sidePanel.setOptions({ enabled: false }, () => {
|
||||
chrome.sidePanel.setOptions({ enabled: true });
|
||||
sidePanelOpenState.set(windowId, false);
|
||||
});
|
||||
} else {
|
||||
chrome.sidePanel.open({ tabId: tab.id });
|
||||
sidePanelOpenState.set(windowId, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
console.log("Unhandled command:", command);
|
||||
}
|
||||
});
|
||||
|
||||
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
||||
if (request.action === ACTIONS.GET_CURRENT_ONYX_DOMAIN) {
|
||||
chrome.storage.local.get(
|
||||
{ [CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN]: DEFAULT_ONYX_DOMAIN },
|
||||
(result) => {
|
||||
sendResponse({
|
||||
[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN]:
|
||||
result[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN],
|
||||
});
|
||||
},
|
||||
);
|
||||
return true;
|
||||
}
|
||||
if (request.action === ACTIONS.CLOSE_SIDE_PANEL) {
|
||||
closeSidePanel();
|
||||
chrome.storage.local.get(
|
||||
{ [CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN]: DEFAULT_ONYX_DOMAIN },
|
||||
(result) => {
|
||||
chrome.tabs.create({
|
||||
url: `${result[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN]}/auth/login`,
|
||||
active: true,
|
||||
});
|
||||
},
|
||||
);
|
||||
return true;
|
||||
}
|
||||
if (request.action === ACTIONS.OPEN_SIDE_PANEL_WITH_INPUT) {
|
||||
const { selectedText, pageUrl } = request;
|
||||
const tabId = sender.tab?.id;
|
||||
const windowId = sender.tab?.windowId;
|
||||
|
||||
if (tabId && windowId) {
|
||||
chrome.storage.local.get(
|
||||
{ [CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN]: DEFAULT_ONYX_DOMAIN },
|
||||
(result) => {
|
||||
const encodedText = encodeURIComponent(selectedText);
|
||||
const onyxDomain = result[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN];
|
||||
const url = `${onyxDomain}${SIDE_PANEL_PATH}?user-prompt=${encodedText}`;
|
||||
|
||||
chrome.storage.session.set({
|
||||
pendingInput: {
|
||||
url: url,
|
||||
pageUrl: pageUrl,
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
});
|
||||
|
||||
chrome.sidePanel
|
||||
.open({ windowId })
|
||||
.then(() => {
|
||||
chrome.runtime.sendMessage({
|
||||
action: ACTIONS.OPEN_ONYX_WITH_INPUT,
|
||||
url: url,
|
||||
pageUrl: pageUrl,
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(
|
||||
"[Onyx SW] Error opening side panel with text:",
|
||||
error,
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
} else {
|
||||
console.error("[Onyx SW] Missing tabId or windowId");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
chrome.storage.onChanged.addListener((changes, namespace) => {
|
||||
if (
|
||||
namespace === "local" &&
|
||||
changes[CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB]
|
||||
) {
|
||||
const newValue =
|
||||
changes[CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB]
|
||||
.newValue;
|
||||
|
||||
if (newValue === false) {
|
||||
chrome.runtime.openOptionsPage();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
chrome.windows.onRemoved.addListener((windowId) => {
|
||||
sidePanelOpenState.delete(windowId);
|
||||
});
|
||||
|
||||
chrome.omnibox.setDefaultSuggestion({
|
||||
description: 'Search Onyx for "%s"',
|
||||
});
|
||||
|
||||
chrome.omnibox.onInputEntered.addListener(async (text) => {
|
||||
try {
|
||||
const result = await chrome.storage.local.get({
|
||||
[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN]: DEFAULT_ONYX_DOMAIN,
|
||||
});
|
||||
|
||||
const domain = result[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN];
|
||||
const searchUrl = `${domain}/chat?user-prompt=${encodeURIComponent(text)}`;
|
||||
|
||||
chrome.tabs.update({ url: searchUrl });
|
||||
} catch (error) {
|
||||
console.error("Error handling omnibox search:", error);
|
||||
}
|
||||
});
|
||||
|
||||
chrome.omnibox.onInputChanged.addListener((text, suggest) => {
|
||||
if (text.trim()) {
|
||||
suggest([
|
||||
{
|
||||
content: text,
|
||||
description: `Search Onyx for "<match>${text}</match>"`,
|
||||
},
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
setupSidePanel();
|
||||
BIN
extensions/chrome/src/.DS_Store
vendored
Normal file
BIN
extensions/chrome/src/.DS_Store
vendored
Normal file
Binary file not shown.
76
extensions/chrome/src/pages/onyx_home.html
Normal file
76
extensions/chrome/src/pages/onyx_home.html
Normal file
@@ -0,0 +1,76 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="Permissions-Policy" content="clipboard-write=(self)" />
|
||||
<title>Onyx Home</title>
|
||||
<link rel="stylesheet" href="../styles/shared.css" />
|
||||
<style>
|
||||
body,
|
||||
html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html,
|
||||
body {
|
||||
background-color: #000;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
html,
|
||||
body {
|
||||
background-color: #f6f6f6;
|
||||
}
|
||||
}
|
||||
|
||||
#background {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
transition: opacity 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
#content {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0;
|
||||
transition: opacity 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
iframe {
|
||||
border: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="background"></div>
|
||||
<div id="content">
|
||||
<iframe
|
||||
id="onyx-iframe"
|
||||
allowfullscreen
|
||||
allow="clipboard-read; clipboard-write"
|
||||
></iframe>
|
||||
</div>
|
||||
<script src="onyx_home.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
248
extensions/chrome/src/pages/onyx_home.js
Normal file
248
extensions/chrome/src/pages/onyx_home.js
Normal file
@@ -0,0 +1,248 @@
|
||||
import {
|
||||
CHROME_MESSAGE,
|
||||
CHROME_SPECIFIC_STORAGE_KEYS,
|
||||
WEB_MESSAGE,
|
||||
} from "../utils/constants.js";
|
||||
import {
|
||||
showErrorModal,
|
||||
hideErrorModal,
|
||||
initErrorModal,
|
||||
} from "../utils/error-modal.js";
|
||||
import { getOnyxDomain } from "../utils/storage.js";
|
||||
|
||||
(function () {
|
||||
let mainIframe = document.getElementById("onyx-iframe");
|
||||
let preloadedIframe = null;
|
||||
const background = document.getElementById("background");
|
||||
const content = document.getElementById("content");
|
||||
const DEFAULT_LIGHT_BACKGROUND_IMAGE =
|
||||
"https://images.unsplash.com/photo-1692520883599-d543cfe6d43d?q=80&w=2666&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D";
|
||||
const DEFAULT_DARK_BACKGROUND_IMAGE =
|
||||
"https://images.unsplash.com/photo-1692520883599-d543cfe6d43d?q=80&w=2666&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D";
|
||||
|
||||
let iframeLoadTimeout;
|
||||
let iframeLoaded = false;
|
||||
|
||||
initErrorModal();
|
||||
|
||||
async function preloadChatInterface() {
|
||||
preloadedIframe = document.createElement("iframe");
|
||||
|
||||
const domain = await getOnyxDomain();
|
||||
preloadedIframe.src = domain + "/chat";
|
||||
preloadedIframe.style.opacity = "0";
|
||||
preloadedIframe.style.visibility = "hidden";
|
||||
preloadedIframe.style.transition = "opacity 0.3s ease-in";
|
||||
preloadedIframe.style.border = "none";
|
||||
preloadedIframe.style.width = "100%";
|
||||
preloadedIframe.style.height = "100%";
|
||||
preloadedIframe.style.position = "absolute";
|
||||
preloadedIframe.style.top = "0";
|
||||
preloadedIframe.style.left = "0";
|
||||
preloadedIframe.style.zIndex = "1";
|
||||
content.appendChild(preloadedIframe);
|
||||
}
|
||||
|
||||
function setIframeSrc(url) {
|
||||
mainIframe.src = url;
|
||||
startIframeLoadTimeout();
|
||||
iframeLoaded = false;
|
||||
}
|
||||
|
||||
function startIframeLoadTimeout() {
|
||||
clearTimeout(iframeLoadTimeout);
|
||||
iframeLoadTimeout = setTimeout(() => {
|
||||
if (!iframeLoaded) {
|
||||
try {
|
||||
if (
|
||||
mainIframe.contentWindow.location.pathname.includes("/auth/login")
|
||||
) {
|
||||
showLoginPage();
|
||||
} else {
|
||||
showErrorModal(mainIframe.src);
|
||||
}
|
||||
} catch (error) {
|
||||
showErrorModal(mainIframe.src);
|
||||
}
|
||||
}
|
||||
}, 2500);
|
||||
}
|
||||
|
||||
function showLoginPage() {
|
||||
background.style.opacity = "0";
|
||||
mainIframe.style.opacity = "1";
|
||||
mainIframe.style.visibility = "visible";
|
||||
content.style.opacity = "1";
|
||||
hideErrorModal();
|
||||
}
|
||||
|
||||
function setTheme(theme, customBackgroundImage) {
|
||||
const imageUrl =
|
||||
customBackgroundImage ||
|
||||
(theme === "dark"
|
||||
? DEFAULT_DARK_BACKGROUND_IMAGE
|
||||
: DEFAULT_LIGHT_BACKGROUND_IMAGE);
|
||||
background.style.backgroundImage = `url('${imageUrl}')`;
|
||||
}
|
||||
|
||||
function fadeInContent() {
|
||||
content.style.transition = "opacity 0.5s ease-in";
|
||||
mainIframe.style.transition = "opacity 0.5s ease-in";
|
||||
content.style.opacity = "0";
|
||||
mainIframe.style.opacity = "0";
|
||||
mainIframe.style.visibility = "visible";
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
content.style.opacity = "1";
|
||||
mainIframe.style.opacity = "1";
|
||||
|
||||
setTimeout(() => {
|
||||
background.style.transition = "opacity 0.3s ease-out";
|
||||
background.style.opacity = "0";
|
||||
}, 500);
|
||||
});
|
||||
}
|
||||
|
||||
function checkOnyxPreference() {
|
||||
chrome.storage.local.get(
|
||||
[
|
||||
CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB,
|
||||
CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN,
|
||||
],
|
||||
(items) => {
|
||||
let useOnyxAsDefaultNewTab =
|
||||
items[CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB];
|
||||
|
||||
if (useOnyxAsDefaultNewTab === undefined) {
|
||||
useOnyxAsDefaultNewTab = !!(
|
||||
localStorage.getItem(
|
||||
CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB,
|
||||
) === "1"
|
||||
);
|
||||
chrome.storage.local.set({
|
||||
[CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB]:
|
||||
useOnyxAsDefaultNewTab,
|
||||
});
|
||||
}
|
||||
|
||||
if (!useOnyxAsDefaultNewTab) {
|
||||
chrome.tabs.update({
|
||||
url: "chrome://new-tab-page",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setIframeSrc(
|
||||
items[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN] + "/chat/nrf",
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function loadThemeAndBackground() {
|
||||
chrome.storage.local.get(
|
||||
[
|
||||
CHROME_SPECIFIC_STORAGE_KEYS.THEME,
|
||||
CHROME_SPECIFIC_STORAGE_KEYS.BACKGROUND_IMAGE,
|
||||
CHROME_SPECIFIC_STORAGE_KEYS.DARK_BG_URL,
|
||||
CHROME_SPECIFIC_STORAGE_KEYS.LIGHT_BG_URL,
|
||||
],
|
||||
function (result) {
|
||||
const theme = result[CHROME_SPECIFIC_STORAGE_KEYS.THEME] || "light";
|
||||
const customBackgroundImage =
|
||||
result[CHROME_SPECIFIC_STORAGE_KEYS.BACKGROUND_IMAGE];
|
||||
const darkBgUrl = result[CHROME_SPECIFIC_STORAGE_KEYS.DARK_BG_URL];
|
||||
const lightBgUrl = result[CHROME_SPECIFIC_STORAGE_KEYS.LIGHT_BG_URL];
|
||||
|
||||
let backgroundImage;
|
||||
if (customBackgroundImage) {
|
||||
backgroundImage = customBackgroundImage;
|
||||
} else if (theme === "dark" && darkBgUrl) {
|
||||
backgroundImage = darkBgUrl;
|
||||
} else if (theme === "light" && lightBgUrl) {
|
||||
backgroundImage = lightBgUrl;
|
||||
}
|
||||
|
||||
setTheme(theme, backgroundImage);
|
||||
checkOnyxPreference();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function loadNewPage(newSrc) {
|
||||
if (preloadedIframe && preloadedIframe.contentWindow) {
|
||||
preloadedIframe.contentWindow.postMessage(
|
||||
{ type: WEB_MESSAGE.PAGE_CHANGE, href: newSrc },
|
||||
"*",
|
||||
);
|
||||
} else {
|
||||
console.error("Preloaded iframe not available");
|
||||
}
|
||||
}
|
||||
|
||||
function completePendingPageLoad() {
|
||||
if (preloadedIframe) {
|
||||
preloadedIframe.style.visibility = "visible";
|
||||
preloadedIframe.style.opacity = "1";
|
||||
preloadedIframe.style.zIndex = "1";
|
||||
mainIframe.style.zIndex = "2";
|
||||
mainIframe.style.opacity = "0";
|
||||
|
||||
setTimeout(() => {
|
||||
if (content.contains(mainIframe)) {
|
||||
content.removeChild(mainIframe);
|
||||
}
|
||||
|
||||
mainIframe = preloadedIframe;
|
||||
mainIframe.id = "onyx-iframe";
|
||||
mainIframe.style.zIndex = "";
|
||||
iframeLoaded = true;
|
||||
clearTimeout(iframeLoadTimeout);
|
||||
}, 200);
|
||||
} else {
|
||||
console.warn("No preloaded iframe available");
|
||||
}
|
||||
}
|
||||
|
||||
chrome.storage.onChanged.addListener(function (changes, namespace) {
|
||||
if (namespace === "local" && changes.useOnyxAsDefaultNewTab) {
|
||||
checkOnyxPreference();
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener("message", function (event) {
|
||||
if (event.data.type === CHROME_MESSAGE.SET_DEFAULT_NEW_TAB) {
|
||||
chrome.storage.local.set({ useOnyxAsDefaultNewTab: event.data.value });
|
||||
} else if (event.data.type === CHROME_MESSAGE.ONYX_APP_LOADED) {
|
||||
clearTimeout(iframeLoadTimeout);
|
||||
hideErrorModal();
|
||||
fadeInContent();
|
||||
iframeLoaded = true;
|
||||
} else if (event.data.type === CHROME_MESSAGE.PREFERENCES_UPDATED) {
|
||||
const { theme, backgroundUrl } = event.data.payload;
|
||||
chrome.storage.local.set(
|
||||
{
|
||||
[CHROME_SPECIFIC_STORAGE_KEYS.THEME]: theme,
|
||||
[CHROME_SPECIFIC_STORAGE_KEYS.BACKGROUND_IMAGE]: backgroundUrl,
|
||||
},
|
||||
() => {},
|
||||
);
|
||||
} else if (event.data.type === CHROME_MESSAGE.LOAD_NEW_PAGE) {
|
||||
loadNewPage(event.data.href);
|
||||
} else if (event.data.type === CHROME_MESSAGE.LOAD_NEW_CHAT_PAGE) {
|
||||
completePendingPageLoad();
|
||||
}
|
||||
});
|
||||
|
||||
mainIframe.onload = function () {
|
||||
clearTimeout(iframeLoadTimeout);
|
||||
startIframeLoadTimeout();
|
||||
};
|
||||
|
||||
mainIframe.onerror = function (error) {
|
||||
showErrorModal(mainIframe.src);
|
||||
};
|
||||
|
||||
loadThemeAndBackground();
|
||||
preloadChatInterface();
|
||||
})();
|
||||
515
extensions/chrome/src/pages/options.html
Normal file
515
extensions/chrome/src/pages/options.html
Normal file
@@ -0,0 +1,515 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="Permissions-Policy" content="clipboard-write=(self)" />
|
||||
<title>Onyx - Settings</title>
|
||||
<link rel="stylesheet" href="../styles/shared.css" />
|
||||
<style>
|
||||
:root {
|
||||
--background-900: #0a0a0a;
|
||||
--background-800: #1a1a1a;
|
||||
--text-light-05: rgba(255, 255, 255, 0.95);
|
||||
--text-light-03: rgba(255, 255, 255, 0.6);
|
||||
--white-10: rgba(255, 255, 255, 0.1);
|
||||
--white-15: rgba(255, 255, 255, 0.15);
|
||||
--white-20: rgba(255, 255, 255, 0.2);
|
||||
--white-30: rgba(255, 255, 255, 0.3);
|
||||
--white-40: rgba(255, 255, 255, 0.4);
|
||||
--white-80: rgba(255, 255, 255, 0.8);
|
||||
--black-40: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: var(--font-hanken-grotesk);
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--background-900) 0%,
|
||||
var(--background-800) 100%
|
||||
);
|
||||
min-height: 100vh;
|
||||
color: var(--text-light-05);
|
||||
transition: background 0.3s ease;
|
||||
}
|
||||
|
||||
body.light-theme {
|
||||
--background-900: #f5f5f5;
|
||||
--background-800: #ffffff;
|
||||
--text-light-05: rgba(0, 0, 0, 0.95);
|
||||
--text-light-03: rgba(0, 0, 0, 0.6);
|
||||
background: linear-gradient(135deg, #f5f5f5 0%, #ffffff 100%);
|
||||
}
|
||||
|
||||
body.light-theme .settings-panel {
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
rgba(255, 255, 255, 0.95),
|
||||
rgba(245, 245, 245, 0.95)
|
||||
);
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
body.light-theme .settings-header {
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
body.light-theme .settings-icon {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
body.light-theme .theme-toggle {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
body.light-theme .theme-toggle:hover {
|
||||
background: rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
body.light-theme .theme-toggle svg {
|
||||
stroke: rgba(0, 0, 0, 0.95);
|
||||
}
|
||||
|
||||
body.light-theme .settings-group {
|
||||
background: rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
|
||||
body.light-theme .setting-divider {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
body.light-theme .input-field {
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
color: rgba(0, 0, 0, 0.95);
|
||||
}
|
||||
|
||||
body.light-theme .input-field:focus {
|
||||
outline: none;
|
||||
border-color: rgba(0, 0, 0, 0.25);
|
||||
background: rgba(0, 0, 0, 0.08);
|
||||
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.05);
|
||||
color: rgba(0, 0, 0, 0.95);
|
||||
}
|
||||
|
||||
body.light-theme .status-container {
|
||||
background: rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
|
||||
body.light-theme .button.secondary {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
color: rgba(0, 0, 0, 0.95);
|
||||
}
|
||||
|
||||
body.light-theme .button.secondary:hover {
|
||||
background: rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
body.light-theme .toggle-slider {
|
||||
background-color: rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
body.light-theme input:checked + .toggle-slider {
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
body.light-theme .toggle-slider:before {
|
||||
background-color: white;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.settings-container {
|
||||
max-width: 500px;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
.settings-panel {
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
rgba(10, 10, 10, 0.95),
|
||||
rgba(26, 26, 26, 0.95)
|
||||
);
|
||||
backdrop-filter: blur(24px);
|
||||
border-radius: 16px;
|
||||
border: 1px solid var(--white-10);
|
||||
overflow: hidden;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.settings-header {
|
||||
padding: 24px;
|
||||
border-bottom: 1px solid var(--white-10);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.settings-header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.settings-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 12px;
|
||||
background: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.settings-icon img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.settings-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: var(--text-light-05);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.theme-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 12px;
|
||||
border-radius: 999px;
|
||||
background: var(--white-10);
|
||||
border: 1px solid var(--white-10);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.theme-toggle:hover {
|
||||
background: var(--white-15);
|
||||
}
|
||||
|
||||
.theme-toggle svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
stroke: var(--text-light-05);
|
||||
}
|
||||
|
||||
.settings-content {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.settings-section {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.settings-section:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: var(--text-light-03);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.settings-group {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 16px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.setting-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.setting-row-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.setting-label {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: var(--text-light-05);
|
||||
}
|
||||
|
||||
.setting-description {
|
||||
font-size: 12px;
|
||||
color: var(--text-light-03);
|
||||
}
|
||||
|
||||
.setting-divider {
|
||||
height: 1px;
|
||||
background: var(--white-10);
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
.input-field {
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--white-10);
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
color: var(--text-light-05);
|
||||
font-family: var(--font-hanken-grotesk);
|
||||
transition: all 0.2s;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.input-field:focus {
|
||||
outline: none;
|
||||
border-color: var(--white-30);
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1);
|
||||
color: var(--text-light-05);
|
||||
}
|
||||
|
||||
.input-field::placeholder {
|
||||
color: var(--text-light-03);
|
||||
}
|
||||
|
||||
.setting-row .input-field {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.toggle-switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 44px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.toggle-switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.toggle-slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
transition: 0.3s;
|
||||
border-radius: 24px;
|
||||
}
|
||||
|
||||
.toggle-slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
left: 3px;
|
||||
bottom: 3px;
|
||||
background-color: white;
|
||||
transition: 0.3s;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
input:checked + .toggle-slider {
|
||||
background-color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
input:checked + .toggle-slider:before {
|
||||
transform: translateX(20px);
|
||||
}
|
||||
|
||||
.status-container {
|
||||
margin-top: 20px;
|
||||
padding: 12px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 8px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.status-container.show {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.status-message {
|
||||
margin: 0 0 12px 0;
|
||||
color: var(--text-light-05);
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.button {
|
||||
padding: 10px 20px;
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s;
|
||||
font-family: var(--font-hanken-grotesk);
|
||||
}
|
||||
|
||||
.button.secondary {
|
||||
background: var(--white-10);
|
||||
color: var(--text-light-05);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.button.secondary:hover {
|
||||
background: var(--white-15);
|
||||
}
|
||||
|
||||
kbd {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border: 1px solid var(--white-10);
|
||||
border-radius: 4px;
|
||||
padding: 2px 6px;
|
||||
font-family: monospace;
|
||||
font-weight: 500;
|
||||
color: var(--text-light-05);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.settings-container {
|
||||
padding: 20px 16px;
|
||||
}
|
||||
|
||||
.settings-header {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.settings-content {
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="settings-container">
|
||||
<div class="settings-panel">
|
||||
<div class="settings-header">
|
||||
<div class="settings-header-left">
|
||||
<div class="settings-icon">
|
||||
<img src="../../public/icon48.png" alt="Onyx" />
|
||||
</div>
|
||||
<h1 class="settings-title">Settings</h1>
|
||||
</div>
|
||||
<button
|
||||
class="theme-toggle"
|
||||
id="themeToggle"
|
||||
aria-label="Toggle theme"
|
||||
>
|
||||
<svg
|
||||
id="themeIcon"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<circle cx="12" cy="12" r="4"></circle>
|
||||
<path
|
||||
d="M12 2v2m0 16v2M4.93 4.93l1.41 1.41m11.32 11.32l1.41 1.41M2 12h2m16 0h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="settings-content">
|
||||
<!-- General Section -->
|
||||
<section class="settings-section">
|
||||
<div class="section-title">General</div>
|
||||
<div class="settings-group">
|
||||
<div class="setting-row">
|
||||
<div class="setting-row-content">
|
||||
<label class="setting-label" for="onyxDomain"
|
||||
>Root Domain</label
|
||||
>
|
||||
<div class="setting-description">
|
||||
The root URL for your Onyx instance
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-divider"></div>
|
||||
<div class="setting-row" style="padding: 12px">
|
||||
<input
|
||||
type="text"
|
||||
id="onyxDomain"
|
||||
class="input-field"
|
||||
placeholder="https://cloud.onyx.app"
|
||||
/>
|
||||
</div>
|
||||
<div class="setting-divider"></div>
|
||||
<div class="setting-row">
|
||||
<div class="setting-row-content">
|
||||
<label class="setting-label" for="useOnyxAsDefault"
|
||||
>Use Onyx as new tab page</label
|
||||
>
|
||||
</div>
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" id="useOnyxAsDefault" />
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Search Engine Section -->
|
||||
<section class="settings-section">
|
||||
<div class="section-title">Search Engine</div>
|
||||
<div class="settings-group">
|
||||
<div class="setting-row">
|
||||
<div class="setting-row-content">
|
||||
<label class="setting-label">Use Onyx in Address Bar</label>
|
||||
<div class="setting-description">
|
||||
Type <kbd>onyx</kbd> followed by a space in Chrome's address
|
||||
bar, then enter your search query and press Enter
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-divider"></div>
|
||||
<div class="setting-row">
|
||||
<div class="setting-row-content">
|
||||
<div class="setting-description">
|
||||
Searches will be directed to your configured Onyx instance
|
||||
at the Root Domain above
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Status Message -->
|
||||
<div id="statusContainer" class="status-container">
|
||||
<p id="status" class="status-message"></p>
|
||||
<button id="newTab" class="button secondary" style="display: none">
|
||||
Open New Tab to Test
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="options.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
142
extensions/chrome/src/pages/options.js
Normal file
142
extensions/chrome/src/pages/options.js
Normal file
@@ -0,0 +1,142 @@
|
||||
import {
|
||||
CHROME_SPECIFIC_STORAGE_KEYS,
|
||||
DEFAULT_ONYX_DOMAIN,
|
||||
} from "../utils/constants.js";
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const domainInput = document.getElementById("onyxDomain");
|
||||
const useOnyxAsDefaultToggle = document.getElementById("useOnyxAsDefault");
|
||||
const statusContainer = document.getElementById("statusContainer");
|
||||
const statusElement = document.getElementById("status");
|
||||
const newTabButton = document.getElementById("newTab");
|
||||
const themeToggle = document.getElementById("themeToggle");
|
||||
const themeIcon = document.getElementById("themeIcon");
|
||||
|
||||
let currentTheme = "dark";
|
||||
|
||||
function updateThemeIcon(theme) {
|
||||
if (!themeIcon) return;
|
||||
|
||||
if (theme === "light") {
|
||||
themeIcon.innerHTML = `
|
||||
<circle cx="12" cy="12" r="4"></circle>
|
||||
<path d="M12 2v2m0 16v2M4.93 4.93l1.41 1.41m11.32 11.32l1.41 1.41M2 12h2m16 0h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"></path>
|
||||
`;
|
||||
} else {
|
||||
themeIcon.innerHTML = `
|
||||
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
function loadStoredValues() {
|
||||
chrome.storage.local.get(
|
||||
{
|
||||
[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN]: DEFAULT_ONYX_DOMAIN,
|
||||
[CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB]: false,
|
||||
[CHROME_SPECIFIC_STORAGE_KEYS.THEME]: "dark",
|
||||
},
|
||||
(result) => {
|
||||
if (domainInput)
|
||||
domainInput.value = result[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN];
|
||||
if (useOnyxAsDefaultToggle)
|
||||
useOnyxAsDefaultToggle.checked =
|
||||
result[CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB];
|
||||
|
||||
currentTheme = result[CHROME_SPECIFIC_STORAGE_KEYS.THEME] || "dark";
|
||||
updateThemeIcon(currentTheme);
|
||||
|
||||
document.body.className = currentTheme === "light" ? "light-theme" : "";
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function saveSettings() {
|
||||
const domain = domainInput.value.trim();
|
||||
const useOnyxAsDefault = useOnyxAsDefaultToggle
|
||||
? useOnyxAsDefaultToggle.checked
|
||||
: false;
|
||||
|
||||
chrome.storage.local.set(
|
||||
{
|
||||
[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN]: domain,
|
||||
[CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB]:
|
||||
useOnyxAsDefault,
|
||||
[CHROME_SPECIFIC_STORAGE_KEYS.THEME]: currentTheme,
|
||||
},
|
||||
() => {
|
||||
showStatusMessage(
|
||||
useOnyxAsDefault
|
||||
? "Settings updated. Open a new tab to test it out. Click on the extension icon to bring up Onyx from any page."
|
||||
: "Settings updated.",
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function showStatusMessage(message) {
|
||||
if (statusElement) {
|
||||
const useOnyxAsDefault = useOnyxAsDefaultToggle
|
||||
? useOnyxAsDefaultToggle.checked
|
||||
: false;
|
||||
|
||||
statusElement.textContent =
|
||||
message ||
|
||||
(useOnyxAsDefault
|
||||
? "Settings updated. Open a new tab to test it out. Click on the extension icon to bring up Onyx from any page."
|
||||
: "Settings updated.");
|
||||
|
||||
if (newTabButton) {
|
||||
newTabButton.style.display = useOnyxAsDefault ? "block" : "none";
|
||||
}
|
||||
}
|
||||
|
||||
if (statusContainer) {
|
||||
statusContainer.classList.add("show");
|
||||
}
|
||||
|
||||
setTimeout(hideStatusMessage, 5000);
|
||||
}
|
||||
|
||||
function hideStatusMessage() {
|
||||
if (statusContainer) {
|
||||
statusContainer.classList.remove("show");
|
||||
}
|
||||
}
|
||||
|
||||
function toggleTheme() {
|
||||
currentTheme = currentTheme === "light" ? "dark" : "light";
|
||||
updateThemeIcon(currentTheme);
|
||||
|
||||
document.body.className = currentTheme === "light" ? "light-theme" : "";
|
||||
|
||||
chrome.storage.local.set({
|
||||
[CHROME_SPECIFIC_STORAGE_KEYS.THEME]: currentTheme,
|
||||
});
|
||||
}
|
||||
|
||||
function openNewTab() {
|
||||
chrome.tabs.create({});
|
||||
}
|
||||
|
||||
if (domainInput) {
|
||||
domainInput.addEventListener("input", () => {
|
||||
clearTimeout(domainInput.saveTimeout);
|
||||
domainInput.saveTimeout = setTimeout(saveSettings, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
if (useOnyxAsDefaultToggle) {
|
||||
useOnyxAsDefaultToggle.addEventListener("change", saveSettings);
|
||||
}
|
||||
|
||||
if (themeToggle) {
|
||||
themeToggle.addEventListener("click", toggleTheme);
|
||||
}
|
||||
|
||||
if (newTabButton) {
|
||||
newTabButton.addEventListener("click", openNewTab);
|
||||
}
|
||||
|
||||
loadStoredValues();
|
||||
});
|
||||
91
extensions/chrome/src/pages/panel.html
Normal file
91
extensions/chrome/src/pages/panel.html
Normal file
@@ -0,0 +1,91 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="Permissions-Policy" content="clipboard-write=(self)" />
|
||||
<title>Onyx Panel</title>
|
||||
<link rel="stylesheet" href="../styles/shared.css" />
|
||||
<style>
|
||||
body,
|
||||
html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#loading-screen {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #f5f5f5;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
transition: opacity 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
#logo {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-image: url("/public/logo.png");
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
#loading-text {
|
||||
color: #0a0a0a;
|
||||
margin-top: 20px;
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
iframe {
|
||||
border: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
transition: opacity 0.5s ease-in-out;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="loading-screen">
|
||||
<div id="logo"></div>
|
||||
<div id="loading-text">Loading Onyx...</div>
|
||||
</div>
|
||||
<iframe
|
||||
id="onyx-panel-iframe"
|
||||
allow="clipboard-read; clipboard-write"
|
||||
></iframe>
|
||||
<script src="../utils/error-modal.js" type="module"></script>
|
||||
<script src="panel.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
127
extensions/chrome/src/pages/panel.js
Normal file
127
extensions/chrome/src/pages/panel.js
Normal file
@@ -0,0 +1,127 @@
|
||||
import { showErrorModal, showAuthModal } from "../utils/error-modal.js";
|
||||
import {
|
||||
ACTIONS,
|
||||
CHROME_MESSAGE,
|
||||
WEB_MESSAGE,
|
||||
CHROME_SPECIFIC_STORAGE_KEYS,
|
||||
SIDE_PANEL_PATH,
|
||||
} from "../utils/constants.js";
|
||||
(function () {
|
||||
const iframe = document.getElementById("onyx-panel-iframe");
|
||||
const loadingScreen = document.getElementById("loading-screen");
|
||||
|
||||
let currentUrl = "";
|
||||
let iframeLoaded = false;
|
||||
let iframeLoadTimeout;
|
||||
let authRequired = false;
|
||||
|
||||
async function checkPendingInput() {
|
||||
try {
|
||||
const result = await chrome.storage.session.get("pendingInput");
|
||||
if (result.pendingInput) {
|
||||
const { url, pageUrl, timestamp } = result.pendingInput;
|
||||
if (Date.now() - timestamp < 5000) {
|
||||
setIframeSrc(url, pageUrl);
|
||||
await chrome.storage.session.remove("pendingInput");
|
||||
return true;
|
||||
}
|
||||
await chrome.storage.session.remove("pendingInput");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("[Onyx Panel] Error checking pending input:", error);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async function initializePanel() {
|
||||
loadingScreen.style.display = "flex";
|
||||
loadingScreen.style.opacity = "1";
|
||||
iframe.style.opacity = "0";
|
||||
|
||||
// Check for pending input first (from selection icon click)
|
||||
const hasPendingInput = await checkPendingInput();
|
||||
if (!hasPendingInput) {
|
||||
loadOnyxDomain();
|
||||
}
|
||||
}
|
||||
|
||||
function setIframeSrc(url, pageUrl) {
|
||||
iframe.src = url;
|
||||
currentUrl = pageUrl;
|
||||
}
|
||||
|
||||
function sendWebsiteToIframe(pageUrl) {
|
||||
if (iframe.contentWindow && pageUrl !== currentUrl) {
|
||||
iframe.contentWindow.postMessage(
|
||||
{
|
||||
type: WEB_MESSAGE.PAGE_CHANGE,
|
||||
url: pageUrl,
|
||||
},
|
||||
"*",
|
||||
);
|
||||
currentUrl = pageUrl;
|
||||
}
|
||||
}
|
||||
|
||||
function startIframeLoadTimeout() {
|
||||
iframeLoadTimeout = setTimeout(() => {
|
||||
if (!iframeLoaded) {
|
||||
if (authRequired) {
|
||||
showAuthModal();
|
||||
} else {
|
||||
showErrorModal(iframe.src);
|
||||
}
|
||||
}
|
||||
}, 2500);
|
||||
}
|
||||
|
||||
function handleMessage(event) {
|
||||
if (event.data.type === CHROME_MESSAGE.ONYX_APP_LOADED) {
|
||||
clearTimeout(iframeLoadTimeout);
|
||||
iframeLoaded = true;
|
||||
showIframe();
|
||||
if (iframe.contentWindow) {
|
||||
iframe.contentWindow.postMessage({ type: "PANEL_READY" }, "*");
|
||||
}
|
||||
} else if (event.data.type === CHROME_MESSAGE.AUTH_REQUIRED) {
|
||||
authRequired = true;
|
||||
}
|
||||
}
|
||||
|
||||
function showIframe() {
|
||||
iframe.style.opacity = "1";
|
||||
loadingScreen.style.opacity = "0";
|
||||
setTimeout(() => {
|
||||
loadingScreen.style.display = "none";
|
||||
}, 500);
|
||||
}
|
||||
|
||||
async function loadOnyxDomain() {
|
||||
const response = await chrome.runtime.sendMessage({
|
||||
action: ACTIONS.GET_CURRENT_ONYX_DOMAIN,
|
||||
});
|
||||
if (response && response[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN]) {
|
||||
setIframeSrc(
|
||||
response[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN] + SIDE_PANEL_PATH,
|
||||
"",
|
||||
);
|
||||
} else {
|
||||
console.warn("Onyx domain not found, using default");
|
||||
const domain = await getOnyxDomain();
|
||||
setIframeSrc(domain + SIDE_PANEL_PATH, "");
|
||||
}
|
||||
}
|
||||
|
||||
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
||||
if (request.action === ACTIONS.OPEN_ONYX_WITH_INPUT) {
|
||||
setIframeSrc(request.url, request.pageUrl);
|
||||
} else if (request.action === ACTIONS.UPDATE_PAGE_URL) {
|
||||
sendWebsiteToIframe(request.pageUrl);
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener("message", handleMessage);
|
||||
|
||||
initializePanel();
|
||||
startIframeLoadTimeout();
|
||||
})();
|
||||
252
extensions/chrome/src/pages/popup.html
Normal file
252
extensions/chrome/src/pages/popup.html
Normal file
@@ -0,0 +1,252 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="Permissions-Policy" content="clipboard-write=(self)" />
|
||||
<title>Onyx</title>
|
||||
<link rel="stylesheet" href="../styles/shared.css" />
|
||||
<style>
|
||||
:root {
|
||||
--background-900: #0a0a0a;
|
||||
--background-800: #1a1a1a;
|
||||
--text-light-05: rgba(255, 255, 255, 0.95);
|
||||
--text-light-03: rgba(255, 255, 255, 0.6);
|
||||
--white-10: rgba(255, 255, 255, 0.1);
|
||||
--white-15: rgba(255, 255, 255, 0.15);
|
||||
--white-20: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
width: 300px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: var(--font-hanken-grotesk);
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--background-900) 0%,
|
||||
var(--background-800) 100%
|
||||
);
|
||||
color: var(--text-light-05);
|
||||
}
|
||||
|
||||
.popup-container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.popup-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid var(--white-10);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.popup-icon {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 10px;
|
||||
background: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.popup-icon img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.popup-title {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--text-light-05);
|
||||
}
|
||||
|
||||
.menu-button-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.menu-button-text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.menu-button-shortcut {
|
||||
font-size: 11px;
|
||||
color: var(--text-light-03);
|
||||
font-weight: 400;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.settings-group {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 12px;
|
||||
padding: 4px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.setting-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.setting-label {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: var(--text-light-05);
|
||||
}
|
||||
|
||||
.setting-divider {
|
||||
height: 1px;
|
||||
background: var(--white-10);
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
.menu-button {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: none;
|
||||
padding: 12px;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
color: var(--text-light-05);
|
||||
font-weight: 400;
|
||||
transition: background 0.2s;
|
||||
border-radius: 12px;
|
||||
font-family: var(--font-hanken-grotesk);
|
||||
}
|
||||
|
||||
.menu-button:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.menu-button svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
stroke: var(--text-light-05);
|
||||
fill: none;
|
||||
stroke-width: 2;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.toggle-switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 44px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.toggle-switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.toggle-slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
transition: 0.3s;
|
||||
border-radius: 24px;
|
||||
}
|
||||
|
||||
.toggle-slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
left: 3px;
|
||||
bottom: 3px;
|
||||
background-color: white;
|
||||
transition: 0.3s;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
input:checked + .toggle-slider {
|
||||
background-color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
input:checked + .toggle-slider:before {
|
||||
transform: translateX(20px);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="popup-container">
|
||||
<div class="popup-header">
|
||||
<div class="popup-icon">
|
||||
<img src="../../public/icon48.png" alt="Onyx" />
|
||||
</div>
|
||||
<h2 class="popup-title">Onyx</h2>
|
||||
</div>
|
||||
|
||||
<div class="settings-group">
|
||||
<div class="setting-row">
|
||||
<label class="setting-label" for="defaultNewTabToggle">
|
||||
Use Onyx as new tab page
|
||||
</label>
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" id="defaultNewTabToggle" />
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
<button class="menu-button" id="openSidePanel">
|
||||
<div class="menu-button-content">
|
||||
<div class="menu-button-text">
|
||||
<svg viewBox="0 0 24 24">
|
||||
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
|
||||
<line x1="15" y1="3" x2="15" y2="21"></line>
|
||||
</svg>
|
||||
Open Onyx Panel
|
||||
</div>
|
||||
<span class="menu-button-shortcut">Ctrl+O</span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button class="menu-button" id="openOptions">
|
||||
<div class="menu-button-text">
|
||||
<svg viewBox="0 0 24 24">
|
||||
<circle cx="12" cy="12" r="3"></circle>
|
||||
<path
|
||||
d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"
|
||||
></path>
|
||||
</svg>
|
||||
Extension Settings
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="popup.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
58
extensions/chrome/src/pages/popup.js
Normal file
58
extensions/chrome/src/pages/popup.js
Normal file
@@ -0,0 +1,58 @@
|
||||
import { CHROME_SPECIFIC_STORAGE_KEYS } from "../utils/constants.js";
|
||||
|
||||
document.addEventListener("DOMContentLoaded", async function () {
|
||||
const defaultNewTabToggle = document.getElementById("defaultNewTabToggle");
|
||||
const openSidePanelButton = document.getElementById("openSidePanel");
|
||||
const openOptionsButton = document.getElementById("openOptions");
|
||||
|
||||
async function loadSetting() {
|
||||
const result = await chrome.storage.local.get({
|
||||
[CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB]: false,
|
||||
});
|
||||
if (defaultNewTabToggle) {
|
||||
defaultNewTabToggle.checked =
|
||||
result[CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB];
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleSetting() {
|
||||
const currentValue = defaultNewTabToggle.checked;
|
||||
await chrome.storage.local.set({
|
||||
[CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB]: currentValue,
|
||||
});
|
||||
}
|
||||
|
||||
async function openSidePanel() {
|
||||
try {
|
||||
const [tab] = await chrome.tabs.query({
|
||||
active: true,
|
||||
currentWindow: true,
|
||||
});
|
||||
if (tab && chrome.sidePanel) {
|
||||
await chrome.sidePanel.open({ tabId: tab.id });
|
||||
window.close();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error opening side panel:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function openOptions() {
|
||||
chrome.runtime.openOptionsPage();
|
||||
window.close();
|
||||
}
|
||||
|
||||
await loadSetting();
|
||||
|
||||
if (defaultNewTabToggle) {
|
||||
defaultNewTabToggle.addEventListener("change", toggleSetting);
|
||||
}
|
||||
|
||||
if (openSidePanelButton) {
|
||||
openSidePanelButton.addEventListener("click", openSidePanel);
|
||||
}
|
||||
|
||||
if (openOptionsButton) {
|
||||
openOptionsButton.addEventListener("click", openOptions);
|
||||
}
|
||||
});
|
||||
618
extensions/chrome/src/pages/welcome.html
Normal file
618
extensions/chrome/src/pages/welcome.html
Normal file
@@ -0,0 +1,618 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Welcome to Onyx</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Hanken+Grotesk:wght@300;400;500;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link rel="stylesheet" href="../styles/shared.css" />
|
||||
<style>
|
||||
:root {
|
||||
--background-900: #0a0a0a;
|
||||
--background-800: #1a1a1a;
|
||||
--text-light-05: rgba(255, 255, 255, 0.95);
|
||||
--text-light-03: rgba(255, 255, 255, 0.6);
|
||||
--white-10: rgba(255, 255, 255, 0.1);
|
||||
--white-15: rgba(255, 255, 255, 0.15);
|
||||
--white-20: rgba(255, 255, 255, 0.2);
|
||||
--white-30: rgba(255, 255, 255, 0.3);
|
||||
--white-40: rgba(255, 255, 255, 0.4);
|
||||
--white-80: rgba(255, 255, 255, 0.8);
|
||||
--black-40: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: var(--font-hanken-grotesk);
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--background-900) 0%,
|
||||
var(--background-800) 100%
|
||||
);
|
||||
min-height: 100vh;
|
||||
color: var(--text-light-05);
|
||||
transition: background 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
body.light-theme {
|
||||
--background-900: #f5f5f5;
|
||||
--background-800: #ffffff;
|
||||
--text-light-05: rgba(0, 0, 0, 0.95);
|
||||
--text-light-03: rgba(0, 0, 0, 0.6);
|
||||
background: linear-gradient(135deg, #f5f5f5 0%, #ffffff 100%);
|
||||
}
|
||||
|
||||
body.light-theme .welcome-panel {
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
rgba(255, 255, 255, 0.95),
|
||||
rgba(245, 245, 245, 0.95)
|
||||
);
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
body.light-theme .welcome-header {
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
body.light-theme .logo-container {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
body.light-theme .theme-toggle {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
body.light-theme .theme-toggle:hover {
|
||||
background: rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
body.light-theme .theme-toggle svg {
|
||||
stroke: rgba(0, 0, 0, 0.95);
|
||||
}
|
||||
|
||||
body.light-theme .input-field {
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
color: rgba(0, 0, 0, 0.95);
|
||||
}
|
||||
|
||||
body.light-theme .input-field:focus {
|
||||
outline: none;
|
||||
border-color: rgba(0, 0, 0, 0.25);
|
||||
background: rgba(0, 0, 0, 0.08);
|
||||
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
body.light-theme .input-field::placeholder {
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
body.light-theme .toggle-slider {
|
||||
background-color: rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
body.light-theme input:checked + .toggle-slider {
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
body.light-theme .toggle-slider:before {
|
||||
background-color: white;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
body.light-theme .step-dot {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
body.light-theme .step-dot.active {
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
body.light-theme .btn-primary {
|
||||
background: rgba(0, 0, 0, 0.9);
|
||||
color: white;
|
||||
}
|
||||
|
||||
body.light-theme .btn-primary:hover {
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
body.light-theme .btn-secondary {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
color: rgba(0, 0, 0, 0.95);
|
||||
}
|
||||
|
||||
body.light-theme .btn-secondary:hover {
|
||||
background: rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
body.light-theme .settings-group {
|
||||
background: rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
|
||||
body.light-theme .setting-divider {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.welcome-container {
|
||||
max-width: 480px;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
.welcome-panel {
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
rgba(10, 10, 10, 0.95),
|
||||
rgba(26, 26, 26, 0.95)
|
||||
);
|
||||
backdrop-filter: blur(24px);
|
||||
border-radius: 20px;
|
||||
border: 1px solid var(--white-10);
|
||||
overflow: hidden;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
|
||||
animation: panelFadeIn 0.5s ease-out;
|
||||
}
|
||||
|
||||
@keyframes panelFadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.welcome-header {
|
||||
padding: 24px;
|
||||
border-bottom: 1px solid var(--white-10);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.logo-container {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 14px;
|
||||
background: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.logo-container img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.welcome-title {
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
color: var(--text-light-05);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.theme-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 12px;
|
||||
border-radius: 999px;
|
||||
background: var(--white-10);
|
||||
border: 1px solid var(--white-10);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.theme-toggle:hover {
|
||||
background: var(--white-15);
|
||||
}
|
||||
|
||||
.theme-toggle svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
stroke: var(--text-light-05);
|
||||
}
|
||||
|
||||
.welcome-content {
|
||||
padding: 32px 24px;
|
||||
}
|
||||
|
||||
/* Step indicator */
|
||||
.step-indicator {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.step-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: var(--white-20);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.step-dot.active {
|
||||
background: var(--white-80);
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
/* Steps */
|
||||
.step {
|
||||
display: none;
|
||||
animation: stepFadeIn 0.4s ease-out;
|
||||
}
|
||||
|
||||
.step.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@keyframes stepFadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
.step-title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 8px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.step-description {
|
||||
font-size: 15px;
|
||||
color: var(--text-light-03);
|
||||
text-align: center;
|
||||
margin: 0 0 28px 0;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* Form elements */
|
||||
.input-group {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.input-label {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--text-light-03);
|
||||
margin-bottom: 8px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
|
||||
.input-field {
|
||||
width: 100%;
|
||||
padding: 14px 16px;
|
||||
border: 1px solid var(--white-10);
|
||||
border-radius: 12px;
|
||||
font-size: 15px;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
color: rgba(0, 0, 0, 0.9);
|
||||
font-family: var(--font-hanken-grotesk);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.input-field:focus {
|
||||
outline: none;
|
||||
border-color: var(--white-30);
|
||||
background: rgba(255, 255, 255, 1);
|
||||
box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
.input-field::placeholder {
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
/* Settings group for step 2 */
|
||||
.settings-group {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 16px;
|
||||
padding: 4px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.setting-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.setting-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
flex: 1;
|
||||
padding-right: 16px;
|
||||
}
|
||||
|
||||
.setting-label {
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
color: var(--text-light-05);
|
||||
}
|
||||
|
||||
.setting-description {
|
||||
font-size: 13px;
|
||||
color: var(--text-light-03);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.setting-divider {
|
||||
height: 1px;
|
||||
background: var(--white-10);
|
||||
margin: 0 8px;
|
||||
}
|
||||
|
||||
/* Toggle switch */
|
||||
.toggle-switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 52px;
|
||||
height: 28px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.toggle-switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.toggle-slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
transition: 0.3s;
|
||||
border-radius: 28px;
|
||||
}
|
||||
|
||||
.toggle-slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 22px;
|
||||
width: 22px;
|
||||
left: 3px;
|
||||
bottom: 3px;
|
||||
background-color: white;
|
||||
transition: 0.3s;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
input:checked + .toggle-slider {
|
||||
background-color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
input:checked + .toggle-slider:before {
|
||||
transform: translateX(24px);
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.button-group {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
flex: 1;
|
||||
padding: 14px 24px;
|
||||
border-radius: 12px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
font-family: var(--font-hanken-grotesk);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
color: #0a0a0a;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: var(--white-10);
|
||||
color: var(--text-light-05);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: var(--white-15);
|
||||
}
|
||||
|
||||
.btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Success animation for completion */
|
||||
.success-icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
margin: 0 auto 24px;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
animation: successPop 0.5s ease-out;
|
||||
}
|
||||
|
||||
.success-icon svg {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
stroke: var(--text-light-05);
|
||||
stroke-width: 2.5;
|
||||
}
|
||||
|
||||
@keyframes successPop {
|
||||
0% {
|
||||
transform: scale(0);
|
||||
opacity: 0;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 500px) {
|
||||
.welcome-container {
|
||||
padding: 20px 16px;
|
||||
}
|
||||
|
||||
.welcome-content {
|
||||
padding: 24px 20px;
|
||||
}
|
||||
|
||||
.step-title {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="welcome-container">
|
||||
<div class="welcome-panel">
|
||||
<div class="welcome-header">
|
||||
<div class="header-left">
|
||||
<div class="logo-container">
|
||||
<img src="../../public/icon48.png" alt="Onyx" />
|
||||
</div>
|
||||
<h1 class="welcome-title">Onyx</h1>
|
||||
</div>
|
||||
<button
|
||||
class="theme-toggle"
|
||||
id="themeToggle"
|
||||
aria-label="Toggle theme"
|
||||
>
|
||||
<svg
|
||||
id="themeIcon"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="welcome-content">
|
||||
<div class="step-indicator">
|
||||
<div class="step-dot active" data-step="1"></div>
|
||||
<div class="step-dot" data-step="2"></div>
|
||||
</div>
|
||||
|
||||
<!-- Step 1: Root Domain -->
|
||||
<div class="step active" id="step1">
|
||||
<h2 class="step-title">Welcome to Onyx</h2>
|
||||
<p class="step-description">
|
||||
Enter your Onyx instance URL to get started. This is where your
|
||||
Onyx deployment is hosted.
|
||||
</p>
|
||||
|
||||
<div class="input-group">
|
||||
<label class="input-label" for="onyxDomain">Root Domain</label>
|
||||
<input
|
||||
type="text"
|
||||
id="onyxDomain"
|
||||
class="input-field"
|
||||
placeholder="https://cloud.onyx.app"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
<button class="btn btn-primary" id="continueBtn">Continue</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Step 2: New Tab Setting -->
|
||||
<div class="step" id="step2">
|
||||
<h2 class="step-title">Customize Your Experience</h2>
|
||||
<p class="step-description">
|
||||
Set Onyx as your new tab page for quick access to your AI
|
||||
assistant.
|
||||
</p>
|
||||
|
||||
<div class="settings-group">
|
||||
<div class="setting-row">
|
||||
<div class="setting-content">
|
||||
<span class="setting-label">Use Onyx as new tab page</span>
|
||||
<span class="setting-description"
|
||||
>Open Onyx every time you create a new tab</span
|
||||
>
|
||||
</div>
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" id="useOnyxAsDefault" checked />
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
<button class="btn btn-secondary" id="backBtn">Back</button>
|
||||
<button class="btn btn-primary" id="finishBtn">
|
||||
Get Started
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="welcome.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
192
extensions/chrome/src/pages/welcome.js
Normal file
192
extensions/chrome/src/pages/welcome.js
Normal file
@@ -0,0 +1,192 @@
|
||||
import {
|
||||
CHROME_SPECIFIC_STORAGE_KEYS,
|
||||
DEFAULT_ONYX_DOMAIN,
|
||||
} from "../utils/constants.js";
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const domainInput = document.getElementById("onyxDomain");
|
||||
const useOnyxAsDefaultToggle = document.getElementById("useOnyxAsDefault");
|
||||
const continueBtn = document.getElementById("continueBtn");
|
||||
const backBtn = document.getElementById("backBtn");
|
||||
const finishBtn = document.getElementById("finishBtn");
|
||||
const themeToggle = document.getElementById("themeToggle");
|
||||
const themeIcon = document.getElementById("themeIcon");
|
||||
|
||||
const step1 = document.getElementById("step1");
|
||||
const step2 = document.getElementById("step2");
|
||||
const stepDots = document.querySelectorAll(".step-dot");
|
||||
|
||||
let currentStep = 1;
|
||||
let currentTheme = "dark";
|
||||
|
||||
// Initialize theme based on system preference or stored value
|
||||
function initTheme() {
|
||||
chrome.storage.local.get(
|
||||
{ [CHROME_SPECIFIC_STORAGE_KEYS.THEME]: null },
|
||||
(result) => {
|
||||
const storedTheme = result[CHROME_SPECIFIC_STORAGE_KEYS.THEME];
|
||||
if (storedTheme) {
|
||||
currentTheme = storedTheme;
|
||||
} else {
|
||||
// Check system preference
|
||||
currentTheme = window.matchMedia("(prefers-color-scheme: light)")
|
||||
.matches
|
||||
? "light"
|
||||
: "dark";
|
||||
}
|
||||
applyTheme();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function applyTheme() {
|
||||
document.body.className = currentTheme === "light" ? "light-theme" : "";
|
||||
updateThemeIcon();
|
||||
}
|
||||
|
||||
function updateThemeIcon() {
|
||||
if (!themeIcon) return;
|
||||
|
||||
if (currentTheme === "light") {
|
||||
themeIcon.innerHTML = `
|
||||
<circle cx="12" cy="12" r="4"></circle>
|
||||
<path d="M12 2v2m0 16v2M4.93 4.93l1.41 1.41m11.32 11.32l1.41 1.41M2 12h2m16 0h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"></path>
|
||||
`;
|
||||
} else {
|
||||
themeIcon.innerHTML = `
|
||||
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
function toggleTheme() {
|
||||
currentTheme = currentTheme === "light" ? "dark" : "light";
|
||||
applyTheme();
|
||||
chrome.storage.local.set({
|
||||
[CHROME_SPECIFIC_STORAGE_KEYS.THEME]: currentTheme,
|
||||
});
|
||||
}
|
||||
|
||||
function goToStep(step) {
|
||||
if (step === 1) {
|
||||
step2.classList.remove("active");
|
||||
setTimeout(() => {
|
||||
step1.classList.add("active");
|
||||
}, 50);
|
||||
} else if (step === 2) {
|
||||
step1.classList.remove("active");
|
||||
setTimeout(() => {
|
||||
step2.classList.add("active");
|
||||
}, 50);
|
||||
}
|
||||
|
||||
stepDots.forEach((dot) => {
|
||||
const dotStep = parseInt(dot.dataset.step);
|
||||
if (dotStep === step) {
|
||||
dot.classList.add("active");
|
||||
} else {
|
||||
dot.classList.remove("active");
|
||||
}
|
||||
});
|
||||
|
||||
currentStep = step;
|
||||
}
|
||||
|
||||
// Validate domain input
|
||||
function validateDomain(domain) {
|
||||
if (!domain) return false;
|
||||
try {
|
||||
new URL(domain);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleContinue() {
|
||||
const domain = domainInput.value.trim();
|
||||
|
||||
if (domain && !validateDomain(domain)) {
|
||||
domainInput.style.borderColor = "rgba(255, 100, 100, 0.5)";
|
||||
domainInput.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
domainInput.style.borderColor = "";
|
||||
goToStep(2);
|
||||
}
|
||||
|
||||
function handleBack() {
|
||||
goToStep(1);
|
||||
}
|
||||
|
||||
function handleFinish() {
|
||||
const domain = domainInput.value.trim() || DEFAULT_ONYX_DOMAIN;
|
||||
const useOnyxAsDefault = useOnyxAsDefaultToggle.checked;
|
||||
|
||||
chrome.storage.local.set(
|
||||
{
|
||||
[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN]: domain,
|
||||
[CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB]:
|
||||
useOnyxAsDefault,
|
||||
[CHROME_SPECIFIC_STORAGE_KEYS.THEME]: currentTheme,
|
||||
[CHROME_SPECIFIC_STORAGE_KEYS.ONBOARDING_COMPLETE]: true,
|
||||
},
|
||||
() => {
|
||||
// Open a new tab if they enabled the new tab feature, otherwise just close
|
||||
if (useOnyxAsDefault) {
|
||||
chrome.tabs.create({}, () => {
|
||||
window.close();
|
||||
});
|
||||
} else {
|
||||
window.close();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Load any existing values (in case user returns to this page)
|
||||
function loadStoredValues() {
|
||||
chrome.storage.local.get(
|
||||
{
|
||||
[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN]: "",
|
||||
[CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB]: true,
|
||||
},
|
||||
(result) => {
|
||||
if (result[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN]) {
|
||||
domainInput.value = result[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN];
|
||||
}
|
||||
useOnyxAsDefaultToggle.checked =
|
||||
result[CHROME_SPECIFIC_STORAGE_KEYS.USE_ONYX_AS_DEFAULT_NEW_TAB];
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (themeToggle) {
|
||||
themeToggle.addEventListener("click", toggleTheme);
|
||||
}
|
||||
|
||||
if (continueBtn) {
|
||||
continueBtn.addEventListener("click", handleContinue);
|
||||
}
|
||||
|
||||
if (backBtn) {
|
||||
backBtn.addEventListener("click", handleBack);
|
||||
}
|
||||
|
||||
if (finishBtn) {
|
||||
finishBtn.addEventListener("click", handleFinish);
|
||||
}
|
||||
|
||||
// Allow Enter key to proceed
|
||||
if (domainInput) {
|
||||
domainInput.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Enter") {
|
||||
handleContinue();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
initTheme();
|
||||
loadStoredValues();
|
||||
});
|
||||
42
extensions/chrome/src/styles/selection-icon.css
Normal file
42
extensions/chrome/src/styles/selection-icon.css
Normal file
@@ -0,0 +1,42 @@
|
||||
#onyx-selection-icon {
|
||||
position: fixed;
|
||||
z-index: 2147483647;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #e0e0e0;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
transform: scale(0.8);
|
||||
transition:
|
||||
opacity 0.15s ease,
|
||||
transform 0.15s ease,
|
||||
box-shadow 0.15s ease;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#onyx-selection-icon.visible {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
#onyx-selection-icon:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
#onyx-selection-icon:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
#onyx-selection-icon img {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
pointer-events: none;
|
||||
}
|
||||
169
extensions/chrome/src/styles/shared.css
Normal file
169
extensions/chrome/src/styles/shared.css
Normal file
@@ -0,0 +1,169 @@
|
||||
/* Import Hanken Grotesk font */
|
||||
@import url("https://fonts.googleapis.com/css2?family=Hanken+Grotesk:wght@300;400;500;600;700&display=swap");
|
||||
|
||||
:root {
|
||||
--primary-color: #4285f4;
|
||||
--primary-hover-color: #3367d6;
|
||||
--secondary-color: #f1f3f4;
|
||||
--secondary-hover-color: #e8eaed;
|
||||
--text-color: #333;
|
||||
--text-light-color: #666;
|
||||
--background-color: #f1f3f4;
|
||||
--card-background-color: #fff;
|
||||
--border-color: #ccc;
|
||||
--font-family: Arial, sans-serif;
|
||||
--font-hanken-grotesk: "Hanken Grotesk", sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-hanken-grotesk);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 500px;
|
||||
width: 90%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: var(--card-background-color);
|
||||
padding: 25px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 3px 5px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--text-color);
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
margin-top: 0;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.option-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
color: var(--text-light-color);
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
background-color: var(--card-background-color);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.button {
|
||||
width: 100%;
|
||||
padding: 10px 20px;
|
||||
border-radius: 5px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.button.primary {
|
||||
background-color: var(--primary-color);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.button.primary:hover {
|
||||
background-color: var(--primary-hover-color);
|
||||
}
|
||||
|
||||
.button.secondary {
|
||||
background-color: var(--secondary-color);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.button.secondary:hover {
|
||||
background-color: var(--secondary-hover-color);
|
||||
}
|
||||
|
||||
.status-container {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.status-message {
|
||||
margin: 0 0 10px 0;
|
||||
color: var(--text-color);
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
transition: opacity 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
kbd {
|
||||
background-color: var(--secondary-color);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 3px;
|
||||
padding: 2px 5px;
|
||||
font-family: monospace;
|
||||
font-weight: 500;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.toggle-label {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.toggle-switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 50px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.toggle-switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: var(--secondary-color);
|
||||
transition: 0.4s;
|
||||
border-radius: 24px;
|
||||
}
|
||||
|
||||
.slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
left: 2px;
|
||||
bottom: 2px;
|
||||
background-color: white;
|
||||
transition: 0.4s;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
input:checked + .slider {
|
||||
background-color: var(--primary-color);
|
||||
}
|
||||
|
||||
input:checked + .slider:before {
|
||||
transform: translateX(26px);
|
||||
}
|
||||
43
extensions/chrome/src/utils/constants.js
Normal file
43
extensions/chrome/src/utils/constants.js
Normal file
@@ -0,0 +1,43 @@
|
||||
export const THEMES = {
|
||||
LIGHT: "light",
|
||||
DARK: "dark",
|
||||
};
|
||||
|
||||
export const DEFAULT_ONYX_DOMAIN = "http://localhost:3000";
|
||||
|
||||
export const SIDE_PANEL_PATH = "/chat/nrf/side-panel";
|
||||
|
||||
export const ACTIONS = {
|
||||
GET_SELECTED_TEXT: "getSelectedText",
|
||||
GET_CURRENT_ONYX_DOMAIN: "getCurrentOnyxDomain",
|
||||
UPDATE_PAGE_URL: "updatePageUrl",
|
||||
SEND_TO_ONYX: "sendToOnyx",
|
||||
OPEN_SIDE_PANEL: "openSidePanel",
|
||||
TOGGLE_NEW_TAB_OVERRIDE: "toggleNewTabOverride",
|
||||
OPEN_SIDE_PANEL_WITH_INPUT: "openSidePanelWithInput",
|
||||
OPEN_ONYX_WITH_INPUT: "openOnyxWithInput",
|
||||
CLOSE_SIDE_PANEL: "closeSidePanel",
|
||||
};
|
||||
|
||||
export const CHROME_SPECIFIC_STORAGE_KEYS = {
|
||||
ONYX_DOMAIN: "onyxExtensionDomain",
|
||||
USE_ONYX_AS_DEFAULT_NEW_TAB: "onyxExtensionDefaultNewTab",
|
||||
THEME: "onyxExtensionTheme",
|
||||
BACKGROUND_IMAGE: "onyxExtensionBackgroundImage",
|
||||
DARK_BG_URL: "onyxExtensionDarkBgUrl",
|
||||
LIGHT_BG_URL: "onyxExtensionLightBgUrl",
|
||||
ONBOARDING_COMPLETE: "onyxExtensionOnboardingComplete",
|
||||
};
|
||||
|
||||
export const CHROME_MESSAGE = {
|
||||
PREFERENCES_UPDATED: "PREFERENCES_UPDATED",
|
||||
ONYX_APP_LOADED: "ONYX_APP_LOADED",
|
||||
SET_DEFAULT_NEW_TAB: "SET_DEFAULT_NEW_TAB",
|
||||
LOAD_NEW_CHAT_PAGE: "LOAD_NEW_CHAT_PAGE",
|
||||
LOAD_NEW_PAGE: "LOAD_NEW_PAGE",
|
||||
AUTH_REQUIRED: "AUTH_REQUIRED",
|
||||
};
|
||||
|
||||
export const WEB_MESSAGE = {
|
||||
PAGE_CHANGE: "PAGE_CHANGE",
|
||||
};
|
||||
34
extensions/chrome/src/utils/content.js
Normal file
34
extensions/chrome/src/utils/content.js
Normal file
@@ -0,0 +1,34 @@
|
||||
let sidePanel = null;
|
||||
|
||||
function createSidePanel() {
|
||||
sidePanel = document.createElement("div");
|
||||
sidePanel.id = "onyx-side-panel";
|
||||
sidePanel.style.cssText = `
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: -400px;
|
||||
width: 400px;
|
||||
height: 100%;
|
||||
background-color: white;
|
||||
box-shadow: -2px 0 5px rgba(0,0,0,0.2);
|
||||
transition: right 0.3s ease-in-out;
|
||||
z-index: 9999;
|
||||
`;
|
||||
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.style.cssText = `
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
`;
|
||||
|
||||
chrome.runtime.sendMessage(
|
||||
{ action: ACTIONS.GET_CURRENT_ONYX_DOMAIN },
|
||||
function (response) {
|
||||
iframe.src = response[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN];
|
||||
},
|
||||
);
|
||||
|
||||
sidePanel.appendChild(iframe);
|
||||
document.body.appendChild(sidePanel);
|
||||
}
|
||||
379
extensions/chrome/src/utils/error-modal.js
Normal file
379
extensions/chrome/src/utils/error-modal.js
Normal file
@@ -0,0 +1,379 @@
|
||||
import {
|
||||
CHROME_SPECIFIC_STORAGE_KEYS,
|
||||
DEFAULT_ONYX_DOMAIN,
|
||||
ACTIONS,
|
||||
} from "./constants.js";
|
||||
|
||||
const errorModalHTML = `
|
||||
<div id="error-modal">
|
||||
<div class="modal-backdrop"></div>
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<div class="modal-icon">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<line x1="12" y1="8" x2="12" y2="12"></line>
|
||||
<line x1="12" y1="16" x2="12.01" y2="16"></line>
|
||||
</svg>
|
||||
</div>
|
||||
<h2>Configuration Error</h2>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p class="modal-description">The Onyx configuration needs to be updated. Please check your settings or contact your Onyx administrator.</p>
|
||||
<div class="url-display">
|
||||
<span class="url-label">Attempted to load:</span>
|
||||
<span id="attempted-url" class="url-value"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="button-container">
|
||||
<button id="open-options" class="button primary">Open Extension Options</button>
|
||||
<button id="disable-override" class="button secondary">Disable New Tab Override</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const style = document.createElement("style");
|
||||
style.textContent = `
|
||||
:root {
|
||||
--background-900: #0a0a0a;
|
||||
--background-800: #1a1a1a;
|
||||
--text-light-05: rgba(255, 255, 255, 0.95);
|
||||
--text-light-03: rgba(255, 255, 255, 0.6);
|
||||
--white-10: rgba(255, 255, 255, 0.1);
|
||||
--white-15: rgba(255, 255, 255, 0.15);
|
||||
--white-20: rgba(255, 255, 255, 0.2);
|
||||
--white-30: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
#error-modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 2000;
|
||||
font-family: var(--font-hanken-grotesk), 'Hanken Grotesk', sans-serif;
|
||||
}
|
||||
|
||||
#error-modal .modal-backdrop {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
#error-modal .modal-content {
|
||||
position: relative;
|
||||
background: linear-gradient(to bottom, rgba(10, 10, 10, 0.95), rgba(26, 26, 26, 0.95));
|
||||
backdrop-filter: blur(24px);
|
||||
border-radius: 16px;
|
||||
border: 1px solid var(--white-10);
|
||||
max-width: 95%;
|
||||
width: 500px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#error-modal .modal-header {
|
||||
padding: 24px;
|
||||
border-bottom: 1px solid var(--white-10);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
#error-modal .modal-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 12px;
|
||||
background: rgba(255, 87, 87, 0.15);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
#error-modal .modal-icon svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
stroke: #ff5757;
|
||||
}
|
||||
|
||||
#error-modal .modal-icon.auth-icon {
|
||||
background: rgba(66, 133, 244, 0.15);
|
||||
}
|
||||
|
||||
#error-modal .modal-icon.auth-icon svg {
|
||||
stroke: #4285f4;
|
||||
}
|
||||
|
||||
#error-modal h2 {
|
||||
margin: 0;
|
||||
color: var(--text-light-05);
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
#error-modal .modal-body {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
#error-modal .modal-description {
|
||||
color: var(--text-light-05);
|
||||
margin: 0 0 20px 0;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
#error-modal .url-display {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--white-10);
|
||||
}
|
||||
|
||||
#error-modal .url-label {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: var(--text-light-03);
|
||||
margin-bottom: 6px;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
#error-modal .url-value {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
color: var(--text-light-05);
|
||||
word-break: break-all;
|
||||
font-family: monospace;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
#error-modal .modal-footer {
|
||||
padding: 0 24px 24px 24px;
|
||||
}
|
||||
|
||||
#error-modal .button-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
#error-modal .button {
|
||||
padding: 12px 20px;
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s;
|
||||
font-family: var(--font-hanken-grotesk), 'Hanken Grotesk', sans-serif;
|
||||
}
|
||||
|
||||
#error-modal .button.primary {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
color: var(--text-light-05);
|
||||
border: 1px solid var(--white-10);
|
||||
}
|
||||
|
||||
#error-modal .button.primary:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-color: var(--white-20);
|
||||
}
|
||||
|
||||
#error-modal .button.secondary {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
color: var(--text-light-05);
|
||||
border: 1px solid var(--white-10);
|
||||
}
|
||||
|
||||
#error-modal .button.secondary:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-color: var(--white-15);
|
||||
}
|
||||
|
||||
#error-modal kbd {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border: 1px solid var(--white-10);
|
||||
border-radius: 4px;
|
||||
padding: 2px 6px;
|
||||
font-family: monospace;
|
||||
font-weight: 500;
|
||||
color: var(--text-light-05);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
#error-modal .button-container {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
#error-modal .button {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const authModalHTML = `
|
||||
<div id="error-modal">
|
||||
<div class="modal-backdrop"></div>
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<div class="modal-icon auth-icon">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
|
||||
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h2>Authentication Required</h2>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p class="modal-description">You need to log in to access Onyx. Click the button below to authenticate.</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="button-container">
|
||||
<button id="open-auth" class="button primary">Log In to Onyx</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
let errorModal, attemptedUrlSpan, openOptionsButton, disableOverrideButton;
|
||||
|
||||
let authModal, openAuthButton;
|
||||
|
||||
export function initErrorModal() {
|
||||
if (!document.getElementById("error-modal")) {
|
||||
const link = document.createElement("link");
|
||||
link.rel = "stylesheet";
|
||||
link.href = "../styles/shared.css";
|
||||
document.head.appendChild(link);
|
||||
|
||||
document.body.insertAdjacentHTML("beforeend", errorModalHTML);
|
||||
document.head.appendChild(style);
|
||||
|
||||
errorModal = document.getElementById("error-modal");
|
||||
authModal = document.getElementById("error-modal");
|
||||
attemptedUrlSpan = document.getElementById("attempted-url");
|
||||
openOptionsButton = document.getElementById("open-options");
|
||||
disableOverrideButton = document.getElementById("disable-override");
|
||||
|
||||
openOptionsButton.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
chrome.runtime.openOptionsPage();
|
||||
});
|
||||
|
||||
disableOverrideButton.addEventListener("click", () => {
|
||||
chrome.storage.local.set({ useOnyxAsDefaultNewTab: false }, () => {
|
||||
chrome.tabs.update({ url: "chrome://new-tab-page" });
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function showErrorModal(url) {
|
||||
if (!errorModal) {
|
||||
initErrorModal();
|
||||
}
|
||||
if (errorModal) {
|
||||
errorModal.style.display = "flex";
|
||||
errorModal.style.zIndex = "9999";
|
||||
attemptedUrlSpan.textContent = url;
|
||||
document.body.style.overflow = "hidden";
|
||||
}
|
||||
}
|
||||
|
||||
export function hideErrorModal() {
|
||||
if (errorModal) {
|
||||
errorModal.style.display = "none";
|
||||
document.body.style.overflow = "auto";
|
||||
}
|
||||
}
|
||||
|
||||
export function checkModalVisibility() {
|
||||
return errorModal
|
||||
? window.getComputedStyle(errorModal).display !== "none"
|
||||
: false;
|
||||
}
|
||||
|
||||
export function initAuthModal() {
|
||||
if (!document.getElementById("error-modal")) {
|
||||
const link = document.createElement("link");
|
||||
link.rel = "stylesheet";
|
||||
link.href = "../styles/shared.css";
|
||||
document.head.appendChild(link);
|
||||
|
||||
document.body.insertAdjacentHTML("beforeend", authModalHTML);
|
||||
document.head.appendChild(style);
|
||||
|
||||
authModal = document.getElementById("error-modal");
|
||||
openAuthButton = document.getElementById("open-auth");
|
||||
|
||||
openAuthButton.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
chrome.storage.local.get(
|
||||
{ [CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN]: DEFAULT_ONYX_DOMAIN },
|
||||
(result) => {
|
||||
const onyxDomain = result[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN];
|
||||
chrome.runtime.sendMessage(
|
||||
{ action: ACTIONS.CLOSE_SIDE_PANEL },
|
||||
() => {
|
||||
if (chrome.runtime.lastError) {
|
||||
console.error(
|
||||
"Error closing side panel:",
|
||||
chrome.runtime.lastError,
|
||||
);
|
||||
}
|
||||
chrome.tabs.create(
|
||||
{
|
||||
url: `${onyxDomain}/auth/login`,
|
||||
active: true,
|
||||
},
|
||||
(_) => {
|
||||
if (chrome.runtime.lastError) {
|
||||
console.error(
|
||||
"Error opening auth tab:",
|
||||
chrome.runtime.lastError,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function showAuthModal() {
|
||||
if (!authModal) {
|
||||
initAuthModal();
|
||||
}
|
||||
if (authModal) {
|
||||
authModal.style.display = "flex";
|
||||
authModal.style.zIndex = "9999";
|
||||
document.body.style.overflow = "hidden";
|
||||
}
|
||||
}
|
||||
|
||||
export function hideAuthModal() {
|
||||
if (authModal) {
|
||||
authModal.style.display = "none";
|
||||
document.body.style.overflow = "auto";
|
||||
}
|
||||
}
|
||||
152
extensions/chrome/src/utils/selection-icon.js
Normal file
152
extensions/chrome/src/utils/selection-icon.js
Normal file
@@ -0,0 +1,152 @@
|
||||
(function () {
|
||||
const OPEN_SIDE_PANEL_WITH_INPUT = "openSidePanelWithInput";
|
||||
|
||||
let selectionIcon = null;
|
||||
let currentSelectedText = "";
|
||||
|
||||
function createSelectionIcon() {
|
||||
if (selectionIcon) return;
|
||||
|
||||
selectionIcon = document.createElement("div");
|
||||
selectionIcon.id = "onyx-selection-icon";
|
||||
|
||||
const img = document.createElement("img");
|
||||
img.src = chrome.runtime.getURL("public/icon32.png");
|
||||
img.alt = "Search with Onyx";
|
||||
|
||||
selectionIcon.appendChild(img);
|
||||
document.body.appendChild(selectionIcon);
|
||||
|
||||
selectionIcon.addEventListener("mousedown", handleIconClick);
|
||||
}
|
||||
|
||||
function showIcon(text) {
|
||||
if (!selectionIcon) {
|
||||
createSelectionIcon();
|
||||
}
|
||||
|
||||
currentSelectedText = text;
|
||||
|
||||
const selection = window.getSelection();
|
||||
if (!selection.rangeCount) return;
|
||||
|
||||
const range = selection.getRangeAt(0);
|
||||
const rect = range.getBoundingClientRect();
|
||||
|
||||
const iconSize = 32;
|
||||
const offset = 4;
|
||||
|
||||
let posX = rect.right + offset;
|
||||
let posY = rect.bottom + offset;
|
||||
|
||||
if (posX + iconSize > window.innerWidth) {
|
||||
posX = rect.left - iconSize - offset;
|
||||
}
|
||||
if (posY + iconSize > window.innerHeight) {
|
||||
posY = rect.top - iconSize - offset;
|
||||
}
|
||||
|
||||
posX = Math.max(
|
||||
offset,
|
||||
Math.min(posX, window.innerWidth - iconSize - offset),
|
||||
);
|
||||
posY = Math.max(
|
||||
offset,
|
||||
Math.min(posY, window.innerHeight - iconSize - offset),
|
||||
);
|
||||
|
||||
selectionIcon.style.left = `${posX}px`;
|
||||
selectionIcon.style.top = `${posY}px`;
|
||||
selectionIcon.classList.add("visible");
|
||||
}
|
||||
|
||||
function hideIcon() {
|
||||
if (selectionIcon) {
|
||||
selectionIcon.classList.remove("visible");
|
||||
}
|
||||
currentSelectedText = "";
|
||||
}
|
||||
|
||||
function handleIconClick(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const textToSend = currentSelectedText;
|
||||
|
||||
if (textToSend) {
|
||||
chrome.runtime.sendMessage(
|
||||
{
|
||||
action: OPEN_SIDE_PANEL_WITH_INPUT,
|
||||
selectedText: textToSend,
|
||||
pageUrl: window.location.href,
|
||||
},
|
||||
(response) => {
|
||||
if (chrome.runtime.lastError) {
|
||||
console.error(
|
||||
"[Onyx] Error sending message:",
|
||||
chrome.runtime.lastError.message,
|
||||
);
|
||||
} else {
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
hideIcon();
|
||||
}
|
||||
|
||||
document.addEventListener("mouseup", (e) => {
|
||||
if (
|
||||
e.target.id === "onyx-selection-icon" ||
|
||||
e.target.closest("#onyx-selection-icon")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
const selection = window.getSelection();
|
||||
const selectedText = selection.toString().trim();
|
||||
|
||||
if (selectedText && selectedText.length > 0) {
|
||||
showIcon(selectedText);
|
||||
} else {
|
||||
hideIcon();
|
||||
}
|
||||
}, 10);
|
||||
});
|
||||
|
||||
document.addEventListener("mousedown", (e) => {
|
||||
if (
|
||||
e.target.id !== "onyx-selection-icon" &&
|
||||
!e.target.closest("#onyx-selection-icon")
|
||||
) {
|
||||
const selection = window.getSelection();
|
||||
const selectedText = selection.toString().trim();
|
||||
if (!selectedText) {
|
||||
hideIcon();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener(
|
||||
"scroll",
|
||||
() => {
|
||||
hideIcon();
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
document.addEventListener("selectionchange", () => {
|
||||
const selection = window.getSelection();
|
||||
const selectedText = selection.toString().trim();
|
||||
if (!selectedText) {
|
||||
hideIcon();
|
||||
}
|
||||
});
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", createSelectionIcon);
|
||||
} else {
|
||||
createSelectionIcon();
|
||||
}
|
||||
})();
|
||||
24
extensions/chrome/src/utils/storage.js
Normal file
24
extensions/chrome/src/utils/storage.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import {
|
||||
DEFAULT_ONYX_DOMAIN,
|
||||
CHROME_SPECIFIC_STORAGE_KEYS,
|
||||
} from "./constants.js";
|
||||
|
||||
export async function getOnyxDomain() {
|
||||
const result = await chrome.storage.local.get({
|
||||
[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN]: DEFAULT_ONYX_DOMAIN,
|
||||
});
|
||||
return result[CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN];
|
||||
}
|
||||
|
||||
export function setOnyxDomain(domain, callback) {
|
||||
chrome.storage.local.set(
|
||||
{ [CHROME_SPECIFIC_STORAGE_KEYS.ONYX_DOMAIN]: domain },
|
||||
callback,
|
||||
);
|
||||
}
|
||||
|
||||
export function getOnyxDomainSync() {
|
||||
return new Promise((resolve) => {
|
||||
getOnyxDomain(resolve);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user