From b96c1969df690a4144da3c9905c61777daaf11fe Mon Sep 17 00:00:00 2001 From: "Abhijith Vijayan [FLUXON]" Date: Sat, 3 Jan 2026 19:18:17 +0530 Subject: [PATCH] chore: basic extension support --- package-lock.json | 2 +- package.json | 2 +- source/Background/index.ts | 11 +- source/ContentScript/index.ts | 27 ++++- source/Options/Options.tsx | 78 +++++++++++--- source/Options/styles.scss | 151 ++++++++++++++++++++++++++- source/Popup/Popup.tsx | 131 ++++++++++++++++------- source/Popup/styles.scss | 189 +++++++++++++++++++++++++++------- source/styles/_reset.scss | 17 +-- source/types/messages.ts | 10 ++ source/types/storage.ts | 9 ++ source/utils/storage.ts | 30 ++++++ 12 files changed, 556 insertions(+), 101 deletions(-) create mode 100644 source/types/messages.ts create mode 100644 source/types/storage.ts create mode 100644 source/utils/storage.ts diff --git a/package-lock.json b/package-lock.json index 80cae02..0997728 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "3.0.0", "license": "MIT", "dependencies": { - "advanced-css-reset": "2.1.3", + "advanced-css-reset": "^2.1.3", "react": "^19.2.3", "react-dom": "^19.2.3", "webextension-polyfill": "^0.12.0" diff --git a/package.json b/package.json index 1e236c2..584ed36 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "lint:fix": "eslint . --ext .ts,.tsx --fix" }, "dependencies": { - "advanced-css-reset": "2.1.3", + "advanced-css-reset": "^2.1.3", "react": "^19.2.3", "react-dom": "^19.2.3", "webextension-polyfill": "^0.12.0" diff --git a/source/Background/index.ts b/source/Background/index.ts index 50518a4..0e40e96 100644 --- a/source/Background/index.ts +++ b/source/Background/index.ts @@ -1,5 +1,12 @@ -import browser from "webextension-polyfill"; +import browser from 'webextension-polyfill'; +import {ExtensionMessage} from '../types/messages'; browser.runtime.onInstalled.addListener((): void => { - console.log('🦄', 'extension installed'); + console.log('extension installed'); +}); + +// Listen for messages from popup or content scripts +browser.runtime.onMessage.addListener((message: unknown): void => { + const msg = message as ExtensionMessage; + console.log('Background received message:', msg.type); }); diff --git a/source/ContentScript/index.ts b/source/ContentScript/index.ts index 214ca4a..78e3622 100644 --- a/source/ContentScript/index.ts +++ b/source/ContentScript/index.ts @@ -1,3 +1,26 @@ -console.log('helloworld from content script'); +import browser from 'webextension-polyfill'; +import {ExtensionMessage, PongMessage} from '../types/messages'; +import {getStorage} from '../utils/storage'; -export {}; +// Listen for messages from popup or background +browser.runtime.onMessage.addListener( + (message: unknown): Promise | undefined => { + const msg = message as ExtensionMessage; + + if (msg.type === 'PING') { + return Promise.resolve({ + type: 'PONG', + timestamp: Date.now(), + }); + } + + return undefined; + } +); + +// Log when content script loads (if logging is enabled) +getStorage(['enableLogging']).then(({enableLogging}) => { + if (enableLogging) { + console.log('[Web Extension Starter] Content script loaded on:', window.location.href); + } +}); diff --git a/source/Options/Options.tsx b/source/Options/Options.tsx index e4122f7..1ed01b1 100644 --- a/source/Options/Options.tsx +++ b/source/Options/Options.tsx @@ -1,27 +1,81 @@ import * as React from 'react'; +import {useEffect, useState} from 'react'; +import {getStorage, setStorage} from '../utils/storage'; const Options: React.FC = () => { + const [username, setUsername] = useState(''); + const [enableLogging, setEnableLogging] = useState(false); + const [saved, setSaved] = useState(false); + + useEffect(() => { + getStorage(['username', 'enableLogging']).then((result) => { + setUsername(result.username); + setEnableLogging(result.enableLogging); + }); + }, []); + + const handleSave = async (e: React.FormEvent): Promise => { + e.preventDefault(); + await setStorage({username, enableLogging}); + setSaved(true); + setTimeout(() => setSaved(false), 2000); + }; + return ( -
-
-

- -
+

+
+

Extension Settings

+

Configure your extension preferences

+
+ + +
+ setUsername(e.target.value)} /> -

-

-

+ +
+ -

+
+ +
+ + {saved && Settings saved} +
); diff --git a/source/Options/styles.scss b/source/Options/styles.scss index e1adc43..3c457c5 100644 --- a/source/Options/styles.scss +++ b/source/Options/styles.scss @@ -5,4 +5,153 @@ body { color: variables.$black; background-color: variables.$greyWhite; -} \ No newline at end of file + min-height: 100vh; + display: flex; + justify-content: center; + padding: 40px 20px; +} + +.options { + width: 100%; + max-width: 500px; + + &__header { + margin-bottom: 30px; + text-align: center; + + h1 { + font-size: 24px; + font-weight: 700; + margin-bottom: 8px; + } + + p { + color: variables.$skyBlue; + font-size: 14px; + } + } + + &__card { + background: white; + border-radius: 12px; + padding: 24px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + } + + &__section { + margin-bottom: 24px; + + &:last-child { + margin-bottom: 0; + } + } + + &__label { + display: block; + font-size: 14px; + font-weight: 600; + margin-bottom: 8px; + color: variables.$black; + } + + &__input { + width: 100%; + padding: 12px 14px; + font-size: 14px; + background-color: white; + color: variables.$black; + border: 1px solid #e0e0e0; + border-radius: 8px; + transition: border-color 0.2s, box-shadow 0.2s; + box-sizing: border-box; + + &:focus { + outline: none; + border-color: #4a90d9; + box-shadow: 0 0 0 3px rgba(74, 144, 217, 0.15); + } + + &::placeholder { + color: #a0a0a0; + } + } + + &__checkbox-wrapper { + display: flex; + align-items: flex-start; + gap: 12px; + padding: 14px; + background: #f8f9fa; + border-radius: 8px; + cursor: pointer; + transition: background 0.2s; + + &:hover { + background: #f0f1f3; + } + } + + &__checkbox { + width: 18px; + height: 18px; + margin-top: 2px; + cursor: pointer; + accent-color: #4a90d9; + } + + &__checkbox-text { + font-size: 14px; + line-height: 1.5; + color: #444; + } + + &__actions { + display: flex; + align-items: center; + gap: 16px; + margin-top: 24px; + padding-top: 24px; + border-top: 1px solid #eee; + } + + &__button { + padding: 12px 24px; + font-size: 14px; + font-weight: 600; + border: none; + border-radius: 8px; + cursor: pointer; + transition: opacity 0.2s, transform 0.1s; + + &:hover { + opacity: 0.9; + } + + &:active { + transform: scale(0.98); + } + + &--primary { + background: #4a90d9; + color: white; + } + } + + &__status { + font-size: 14px; + color: #2d8a2d; + font-weight: 500; + display: flex; + align-items: center; + gap: 6px; + + &::before { + content: ""; + display: inline-block; + width: 8px; + height: 8px; + background: #2d8a2d; + border-radius: 50%; + } + } +} diff --git a/source/Popup/Popup.tsx b/source/Popup/Popup.tsx index 95e5994..289dd6d 100644 --- a/source/Popup/Popup.tsx +++ b/source/Popup/Popup.tsx @@ -1,50 +1,103 @@ import * as React from 'react'; -import browser, {Tabs} from "webextension-polyfill"; +import {useEffect, useState} from 'react'; +import browser, {Tabs} from 'webextension-polyfill'; function openWebPage(url: string): Promise { return browser.tabs.create({url}); } +interface TabInfo { + title: string; + url: string; + favIconUrl?: string; +} + const Popup: React.FC = () => { + const [tabInfo, setTabInfo] = useState(null); + + useEffect(() => { + browser.tabs.query({active: true, currentWindow: true}).then((tabs) => { + const tab = tabs[0]; + if (tab) { + setTabInfo({ + title: tab.title || 'Unknown', + url: tab.url || 'Unknown', + favIconUrl: tab.favIconUrl, + }); + } + }); + }, []); + + const handleReloadTab = async (): Promise => { + const tabs = await browser.tabs.query({active: true, currentWindow: true}); + const tab = tabs[0]; + if (tab?.id) { + await browser.tabs.reload(tab.id); + } + }; + + const getInitial = (title: string): string => { + return title.charAt(0).toUpperCase(); + }; + return ( -