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