From 8e6837cd6ee48ea2a2272b2cb47ded9cdae957c4 Mon Sep 17 00:00:00 2001 From: Erik Handeland Date: Sat, 4 Apr 2026 03:01:31 -0700 Subject: [PATCH] rewrite --- v2/.gitignore | 4 + v2/README.md | 47 +++++ v2/icons/logo128.png | Bin 0 -> 2670 bytes v2/icons/logo192.png | Bin 0 -> 2182 bytes v2/icons/logo48.png | Bin 0 -> 774 bytes v2/index.html | 12 ++ v2/manifest.json | 33 ++++ v2/package.json | 42 ++++ v2/public/securebinlogo.svg | 20 ++ v2/public/securebinlogo_dark.svg | 20 ++ v2/src/App.tsx | 81 ++++++++ v2/src/background.ts | 54 ++++++ v2/src/components/NavBar.tsx | 49 +++++ v2/src/components/common/CopyBox.tsx | 102 ++++++++++ v2/src/components/common/PageHeader.tsx | 31 +++ v2/src/components/common/SettingsRow.tsx | 48 +++++ v2/src/components/common/StatusBadge.tsx | 35 ++++ v2/src/components/dialog/ConfirmDialog.tsx | 75 +++++++ v2/src/components/dialog/DecryptDialog.tsx | 76 ++++++++ v2/src/components/dialog/EncryptDialog.tsx | 87 +++++++++ v2/src/components/editor/ActionBar.tsx | 147 ++++++++++++++ v2/src/components/editor/TextEditor.tsx | 66 +++++++ v2/src/index.css | 100 ++++++++++ v2/src/lib/cn.ts | 6 + v2/src/lib/constants.ts | 49 +++++ v2/src/lib/crypto.ts | 194 +++++++++++++++++++ v2/src/lib/editor-utils.test.ts | 183 ++++++++++++++++++ v2/src/lib/editor-utils.ts | 54 ++++++ v2/src/lib/pastebin-detection.test.ts | 136 +++++++++++++ v2/src/lib/pastebin.e2e.test.ts | 126 ++++++++++++ v2/src/lib/pastebin.ts | 70 +++++++ v2/src/lib/storage.ts | 42 ++++ v2/src/lib/store.ts | 215 +++++++++++++++++++++ v2/src/main.tsx | 13 ++ v2/src/routes/ApiKeyConfig.tsx | 119 ++++++++++++ v2/src/routes/Editor.tsx | 189 ++++++++++++++++++ v2/src/routes/EncConfig.tsx | 69 +++++++ v2/src/routes/History.tsx | 123 ++++++++++++ v2/src/routes/Result.tsx | 104 ++++++++++ v2/src/routes/Settings.tsx | 214 ++++++++++++++++++++ v2/src/routes/Support.tsx | 68 +++++++ v2/src/vite-env.d.ts | 1 + v2/tsconfig.json | 25 +++ v2/tsconfig.tsbuildinfo | 1 + v2/vite.config.ts | 27 +++ 45 files changed, 3157 insertions(+) create mode 100644 v2/.gitignore create mode 100644 v2/README.md create mode 100755 v2/icons/logo128.png create mode 100644 v2/icons/logo192.png create mode 100755 v2/icons/logo48.png create mode 100644 v2/index.html create mode 100644 v2/manifest.json create mode 100644 v2/package.json create mode 100644 v2/public/securebinlogo.svg create mode 100644 v2/public/securebinlogo_dark.svg create mode 100644 v2/src/App.tsx create mode 100644 v2/src/background.ts create mode 100644 v2/src/components/NavBar.tsx create mode 100644 v2/src/components/common/CopyBox.tsx create mode 100644 v2/src/components/common/PageHeader.tsx create mode 100644 v2/src/components/common/SettingsRow.tsx create mode 100644 v2/src/components/common/StatusBadge.tsx create mode 100644 v2/src/components/dialog/ConfirmDialog.tsx create mode 100644 v2/src/components/dialog/DecryptDialog.tsx create mode 100644 v2/src/components/dialog/EncryptDialog.tsx create mode 100644 v2/src/components/editor/ActionBar.tsx create mode 100644 v2/src/components/editor/TextEditor.tsx create mode 100644 v2/src/index.css create mode 100644 v2/src/lib/cn.ts create mode 100644 v2/src/lib/constants.ts create mode 100644 v2/src/lib/crypto.ts create mode 100644 v2/src/lib/editor-utils.test.ts create mode 100644 v2/src/lib/editor-utils.ts create mode 100644 v2/src/lib/pastebin-detection.test.ts create mode 100644 v2/src/lib/pastebin.e2e.test.ts create mode 100644 v2/src/lib/pastebin.ts create mode 100644 v2/src/lib/storage.ts create mode 100644 v2/src/lib/store.ts create mode 100644 v2/src/main.tsx create mode 100644 v2/src/routes/ApiKeyConfig.tsx create mode 100644 v2/src/routes/Editor.tsx create mode 100644 v2/src/routes/EncConfig.tsx create mode 100644 v2/src/routes/History.tsx create mode 100644 v2/src/routes/Result.tsx create mode 100644 v2/src/routes/Settings.tsx create mode 100644 v2/src/routes/Support.tsx create mode 100644 v2/src/vite-env.d.ts create mode 100644 v2/tsconfig.json create mode 100644 v2/tsconfig.tsbuildinfo create mode 100644 v2/vite.config.ts diff --git a/v2/.gitignore b/v2/.gitignore new file mode 100644 index 0000000..dafa699 --- /dev/null +++ b/v2/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +dist/ +.env +.env.local diff --git a/v2/README.md b/v2/README.md new file mode 100644 index 0000000..715047c --- /dev/null +++ b/v2/README.md @@ -0,0 +1,47 @@ +# SecureBin v2 + +SecureBin is a Google Chrome extension for interfacing securely with PasteBin. +Users can encrypt plaintext and have it stored onto PasteBin, where they can copy the link and key to send it to another user for decryption. + +## What's New in v2 + +Version 2 is a complete rewrite of the extension using modern web technologies: +- **Framework:** React 19 with TypeScript +- **Styling:** Tailwind CSS v4 and Radix UI primitives for a modern, beautiful, and accessible UI +- **State Management:** Zustand +- **Routing:** React Router v7 +- **Bundler:** Vite with `@crxjs/vite-plugin` for optimized extension builds and HMR (Hot Module Replacement) + +## Prerequisites + +To use this extension, you will need a PasteBin Developer API Key to post your payloads securely. +1. Sign up or Log in to [PasteBin](https://pastebin.com/). +2. Navigate to the [API Documentation](https://pastebin.com/doc_api#1) to copy your developer API key. +3. Open the extension's Settings page to input and save your API key. + +## Development + +### Setup + +Ensure you have Node.js (and `npm`) installed, then install the dependencies: + +```bash +cd v2 +npm install +``` + +### Scripts + +- `npm run dev`: Start the Vite development server with Hot Module Replacement (HMR). You should load the generated `v2/dist` folder in Chrome. +- `npm run build`: Build the extension for production. +- `npm run lint`: Run ESLint to analyze the code and automatically fix format issues. +- `npm run test`: Run unit tests using Vitest. +- `npm run test:watch`: Run tests in watch mode. +- `npm run preview`: Preview the production build. + +## Installation + +1. First, build the extension using `npm run build` or start the dev server via `npm run dev`. +2. Open Chrome and navigate to your extensions page: `chrome://extensions/`. +3. Enable **Developer mode** in the top-right corner. +4. Click **Load unpacked** and select the `v2/dist` directory. diff --git a/v2/icons/logo128.png b/v2/icons/logo128.png new file mode 100755 index 0000000000000000000000000000000000000000..94a51abd2ce2f774ee08aea596114c5a6e259ff6 GIT binary patch literal 2670 zcmc&$`8V6!7XK!KBxsRFak-D?6{R&(gqUIoHC#if3Q_Y=r8QMrQi>SbQbl`FYA9FD z(h`JFx71ieQADV@H5WBh^V0R!dhaiI>;16LXYaE=YoD{uIeVX-Xl-f22N#6{0KkVc z#oDlu^h=;z?C~wet%nVuTQ(*JKxMz!3fsVXI^w)6EC5-y4h297z5wJGg)O3N0RWDC z5WvAk(670C@PE9de2)L>zlf#}A>jZ38^>Yw?IJ*Hcn2Tkz-i2Tg~V%PG8h?k)xD%Y z9j*EGmBO$c@toLd62|e5m{Mq1n@hEX5Y{NsaSNAXEYM_ZRHGxpvxn_tMVYG;Sr;!- zn{I`B4@~89WTBhTDoZ&Vcbd>>v`RKsV5rRgRc}#I(bR|a9kOAh?R5y;+{~;tPlp(W zntD*Q`5+}FrQk!D-##YSQUj921BVY?s_KUF!=nn-v}1s#UTjgzKJlwuBs&(so+78 zB@T`nhTz!(4XXV|W^FqP3 z1>2V;gH9K`jnO;WTk{=$X)#iSOK+G9-JZue)m-B|{&AqP-l$=pnhJ5O^Wu9Ts@AQN zGggT=C+@9ol}9~8N}mF~3HW3X%f?(HOe2&S(nk>>56Ng3|NzlV!9YkGBWU{=fL?1mdKS)b<&~hQ_pDB?hsC^9F4Rf zFREnp&mS*dIaCehqQjy1){kZOg@?Oe5iz4QVmRC)JIs(zTsJDmsXlPc4DVuJ&}&N{ zF0q0^PzYg z9bOTmk8cV?uD|V^TYT+5R_a2FG&uZ&{LNl3QJv(o8NBz}ci32CA3EI{%_?=Ma-oE* z*aX1ev+pdf`@KxZbKvV}1)_a+d!eV&B{CQ(s_a2dy|661D7y+%3>a7J9H&L@CVBP! zxzONT4pwk!&Oc}SvZGsQHzG?OU$Fk2DD4l<50yXT(jw3tb*#l`*oscRKA|j7RjLX1 z2cLJMbSZf$YZ0WZp0AEqF7{=k=Q=K7=`iJm$uZPN>j4B`+6l?J07`Vo>@`wn8p@<=t+8-ruGtC( zE_ZF67XGkuHr2H?N(~CKX^v)1o7;D7{W2Vl5PM8)d^J5rwolcf~E=K%RQ%BgtIb{$R- z#YAipYE{nn^eW7;*o-H+xfcvG{I#B(1^7*II3*zuy6)*VJJnue8>1&pocB4BgTozp zf~K2q&ou9?g;;FRXBg=LjD2WQbV0R*6-frMOlIS$qCKT`1Aj_GT23>HXC_lVf{4>a$aN3})_^n6=8OQz_B&C2Q!MB8=lokPqOj|a9VFe$Zqu2y zJcvs2o(6R)B^|;E>A>==|KyvBn(~~!GYhq9J|FU@FsLjcJ;AW_7JquGYs1 z!eg6E;Atj}&<1uTtPYnvN_Ek83qlk_Ne-1%D%IE(J<4m0>N|O`Im>bPXs@rvul}-D zXnDh9a5;?dd?A7w!@S4AUFzABnbCmSu(q^(sO|Z?-!&7U#QT6OfYQES^0{o_xz5!s z{kVtKMBa{P>)2{p)A~S0&Z*!FRkl}OJe@3*Xp$ohFWXhQFnk4ab$LNy5dH&+4)%xE z?mUE(hE}8K{kiDe@f-8vDqbmb%3DJ8o7 zp>Os{eIhtc97LdW@lLSO?|6DGk4|%G|jxOd4^iAteP|ewy6X+k2`Vs0-a(JW&|O zICWlCQj3q5&pt(IMru_nrX3`>;saB7r26jo?<1;a2+loJAVpmWDN-*bFzGlAzGTAR z9Ko45^bcmY#tieHb}FR}JKW>c5mH1=Nwc)J+h2 z>Ysaee8Gx5e?z*)z|s z-Q)Oo>%$AqwL^fX01ugXNY~ qq}iAF7wrO-^c{l^A*B<#KXtE6CJjr4T5bO7^>9X(*h&LJ%)bEGM90Pe literal 0 HcmV?d00001 diff --git a/v2/icons/logo192.png b/v2/icons/logo192.png new file mode 100644 index 0000000000000000000000000000000000000000..0663a2348a70f4ac29a2031d582b063409f9aa53 GIT binary patch literal 2182 zcmZuxcTm&Y68?qIf*{3mLCQms-a<{Vw4S!~%Ah@}? zjgOBX93155=WA$aJb3V6dV2cDj~_2zzSPpvQc_a#^z^i{vRYnVE-x=nNJuz7J~l8g z@bdCfS66@d@Zs$2?9tJYgM)*NjLh)x@ZR3u*=4u3w(9HaKY#uVhr=^7Gtp@D`uh6M zpFhpb&3k%!hK7dh?d|pS^b{2p&CJa1-MbebA5WoBrlzK5W@dbSedXol-QC@5YHC(j zSDl@m>+0%qb8{0D6VIN@#Kfe(zh6yFZE0yqOiU~!BxG%EO-Dz^+uIw3LbbNG8W|Y{ z1Oy-ui23<>GMOA69-fktVr*>O-QAsOTiBV%M_ zBs4U1Zf@?@ty>ip6=7jvpFVxMdGn^Ww)Vopf}Ne6uC8uuY;0LsSyEDxgoH$IZ}0Qx z&s$nrtgWqUYin^hoT;g4aB#4ys%l9|$>89iqoZS5TH5yZ_LnbTR8&;Hefwr;XlQF| z`+izq@oe(WKQc8o1LzHNhSp9_A=YzDWxW$GpVTpwF^- zQ~gC==mkdFhul1WY8p0TV@m{!Ik!fRh&e;tt<<~%g>6M8osF2xI+6Pdz|(dWHu{Ko zP|uk?W|%{^(D9o(_q$X5{0CEPr`whPREw&! zx;5=>Ifk0v{61UfI{nKwgqyl^#LdyGagJs=;#9;;!WyhIb5YL7CF z>7dbnk2ig6!$4PHj%}}bW}X0Z8sV}CH&?w*6yo{d>%z;9!7-Gra`UhaPSCDpEp?NBi2Kte8;@}#Nz_F%5LOb1SEu?vXj z-cJ+y14F}`-|aA>9>=R>SAP{H!S7F#mL-(Zo`mKTiC{@*A~2_iikhKaryhyKvoR6= zf<-)m>Sbm?(qCE0LSUF>bFC1H6sXDi1Qhb{q?+P)h;Y5@c&Uby4iQ3t2qlv-y86Jn zk4DMn8eX383dK}u#7Cf`ft-8mRpqxM?7YVyL9=9Kh@x226pfyMPH+Pd;zltbmECsGKFR2+CT)DU9sG>iAs{6gG|+f|7hdqVG62j{TsYd}=A#`W zqal=tY=H!K6f)iO>17h7EvS*}$n6FkbZ!ZR`Z0Y_kp;<(P~I;y>`2!g@mwr*6ezk1 z(>Hg65ifZXQx>v-rfkLtflU~O)g{U~1+rhsDtv)n!#)>FD*R5~H)@UHLoUYYk>0G4 z?GL(YF_xml&IBN6`IIdKC4{5%A!Ym$TSad(5ZiSZjUK%$vR;n>jWsr^Q6V^TxaMcoAJBK+5mR%I z&Cn)GqO90C17^v}uxEmT&IQA6w$dVkwaJV`)Is`d zoO3aq3mjEnm8dEZK$-t+>;7L5)Td19Am|}Qez6I9+Q-v?^!PuEO{rOq$W-h-nd$Nr zc?+4TD@XEoWhQx!%x$Eps}(K-hiqn2s9sU}uisZ-2^B2e1l@~5zq+G!jI}E@@uB|) Dqh1K; literal 0 HcmV?d00001 diff --git a/v2/icons/logo48.png b/v2/icons/logo48.png new file mode 100755 index 0000000000000000000000000000000000000000..6608db1cf8a6288a61e89c5cf7853b4a35fea387 GIT binary patch literal 774 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC#^NA%Cx&(BWL^R}E~ycoX}-P; zT0k}j17mw80}DtA5K93u0|WB{Mh0de%?J`(zyy~ySip>6gB0F2&*5QUU@Gu*aSX{| zeH*p+9iyYbePPFz4)+T`G*~-b^dEU{iJI&7TsGhVqstNH4!>r%^&dF?to`%6DBV7< zJpFOe$=ITqcW>UDc|Skx+m0tPpZ1-4`lqoN(! z4QCvlL~C^VG!#^;O;)`9c3R5j$WIkE9{cZ$-%1i&^KZh{*QJ}{)-!+XIi9>JMvwVz z2+tb99Wi=ZA8X8}dL1=4pBZ7vtoA@N;&N@=q70@8fg68UINQuVyXR!V@kQ4pc-T@b zWOkJqXiW8bQnveGi1(s%4Eohd9~w>-$`sknPkeQ1LG8MS*BS0Q#s6bkzJB`MaQoN& ztHO28`i4@pbk%DX4GpiRdpl#!vAM0}58-Bv$YeSH=<`pNtx;zeALPF%dcm@%)8)`w zKfC$I7uQ!igfj%p__oiFNnu*}B5j6q8WXa%as{VmddVb9@(^RW_UyBz;6$U`rSpXtl=f^hRBgyxc==_@)1ymn<}lgoX1XMpNbT%D z9HO;Ubb{3ODBU#w<(Csnw78U+CZBBics85+*o*i;9kH29uVx*65hCKo;?no>bl0h^ z_uory{{8nHP$-P^g85pfg$ljZHgZc}@9I9r;^NCb8EY=Rj%D)78&qol`;+0G_u%qW}N^ literal 0 HcmV?d00001 diff --git a/v2/index.html b/v2/index.html new file mode 100644 index 0000000..fa2912c --- /dev/null +++ b/v2/index.html @@ -0,0 +1,12 @@ + + + + + + SecureBin + + +
+ + + diff --git a/v2/manifest.json b/v2/manifest.json new file mode 100644 index 0000000..3d0656c --- /dev/null +++ b/v2/manifest.json @@ -0,0 +1,33 @@ +{ + "name": "SecureBin: Powerful Pastebin Tool", + "description": "Securely encrypt your text on Pastebin.com", + "version": "2.0.0", + "manifest_version": 3, + "action": { + "default_popup": "index.html", + "default_title": "Open SecureBin" + }, + "icons": { + "16": "icons/logo48.png", + "48": "icons/logo48.png", + "128": "icons/logo128.png" + }, + "background": { + "service_worker": "src/background.ts", + "type": "module" + }, + "permissions": [ + "storage", + "contextMenus", + "clipboardWrite", + "clipboardRead", + "activeTab" + ], + "optional_permissions": [ + "" + ], + "host_permissions": [ + "https://pastebin.com/*", + "https://cors.securebin.workers.dev/*" + ] +} diff --git a/v2/package.json b/v2/package.json new file mode 100644 index 0000000..1c641de --- /dev/null +++ b/v2/package.json @@ -0,0 +1,42 @@ +{ + "name": "securebin", + "version": "2.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "preview": "vite preview", + "lint": "eslint . --fix", + "test": "vitest run", + "test:watch": "vitest" + }, + "dependencies": { + "@radix-ui/react-dialog": "^1.1.6", + "@radix-ui/react-dropdown-menu": "^2.1.6", + "@radix-ui/react-select": "^2.1.6", + "@radix-ui/react-switch": "^1.1.3", + "@radix-ui/react-tooltip": "^1.1.8", + "clsx": "^2.1.1", + "date-fns": "^4.1.0", + "lucide-react": "^0.475.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-router-dom": "^7.1.0", + "tailwind-merge": "^3.0.0", + "zustand": "^5.0.0" + }, + "devDependencies": { + "@crxjs/vite-plugin": "^2.0.0-beta.30", + "@tailwindcss/vite": "^4.0.0", + "@types/chrome": "^0.0.287", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "@vitejs/plugin-react": "^4.3.0", + "autoprefixer": "^10.4.20", + "tailwindcss": "^4.0.0", + "typescript": "^5.7.0", + "vite": "^6.0.0", + "vitest": "^4.1.2" + } +} diff --git a/v2/public/securebinlogo.svg b/v2/public/securebinlogo.svg new file mode 100644 index 0000000..2eb8a46 --- /dev/null +++ b/v2/public/securebinlogo.svg @@ -0,0 +1,20 @@ + + + Group + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/v2/public/securebinlogo_dark.svg b/v2/public/securebinlogo_dark.svg new file mode 100644 index 0000000..1ac491b --- /dev/null +++ b/v2/public/securebinlogo_dark.svg @@ -0,0 +1,20 @@ + + + Group + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/v2/src/App.tsx b/v2/src/App.tsx new file mode 100644 index 0000000..45191fd --- /dev/null +++ b/v2/src/App.tsx @@ -0,0 +1,81 @@ +import { useEffect } from 'react' +import { Routes, Route, useNavigate, useLocation } from 'react-router-dom' +import { useStore } from './lib/store' +import { cn } from './lib/cn' +import NavBar from './components/NavBar' +import Editor from './routes/Editor' +import Settings from './routes/Settings' +import History from './routes/History' +import Result from './routes/Result' +import ApiKeyConfig from './routes/ApiKeyConfig' +import EncConfig from './routes/EncConfig' +import Support from './routes/Support' + +// Routes we won't try to restore (transient pages) +const NON_RESTORABLE_ROUTES = new Set(['/', '/home', '/result']) + +export default function App() { + const { settings, initialized, initialize } = useStore() + const isDark = settings.theme === 'dark' + const navigate = useNavigate() + const location = useLocation() + + useEffect(() => { + initialize() + }, [initialize]) + + useEffect(() => { + document.documentElement.classList.toggle('dark', isDark) + }, [isDark]) + + // Restore last page on popup open (after settings are loaded) + useEffect(() => { + if (!initialized) return + const { page_timeout } = settings + if (page_timeout === 0) return + + chrome.storage.session.get(['lastRoute', 'lastRouteTime'], (data) => { + const lastRoute: string = data.lastRoute ?? '' + const lastRouteTime: number = data.lastRouteTime ?? 0 + if (!lastRoute || NON_RESTORABLE_ROUTES.has(lastRoute)) return + + const ageMs = Date.now() - lastRouteTime + if (page_timeout === -1 || ageMs < page_timeout * 1000) { + navigate(lastRoute, { replace: true }) + } + }) + }, [initialized]) + + // Persist current route so we can restore it next time + useEffect(() => { + if (!initialized) return + chrome.storage.session.set({ lastRoute: location.pathname, lastRouteTime: Date.now() }) + }, [location.pathname, initialized]) + + if (!initialized) { + return ( +
+
+
+ ) + } + + return ( +
+ +
+ + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + +
+
+ ) +} diff --git a/v2/src/background.ts b/v2/src/background.ts new file mode 100644 index 0000000..4d738d4 --- /dev/null +++ b/v2/src/background.ts @@ -0,0 +1,54 @@ +import { StorageKey, EncryptionMode } from './lib/constants' + +// Initialize defaults on install +chrome.runtime.onInstalled.addListener(async () => { + const data = await chrome.storage.sync.get(StorageKey.SETTINGS) + if (!data[StorageKey.SETTINGS]) { + await chrome.storage.sync.set({ + [StorageKey.SETTINGS]: JSON.stringify({ + apiKey: 'MmU1OGNlMjcyMzllMzRhNzdjNWVmNjVkYmVhOGIyNGQ=', + encMode: EncryptionMode.AES_GCM, + keyLength: 16, + theme: 'light', + encryption: false, + default_action: 'Post to Pastebin', + page_timeout: 30, + }), + }) + } + + // Rebuild context menus on install/update to avoid duplicate registration errors + chrome.contextMenus.removeAll(() => { + chrome.contextMenus.create({ + id: 'securebinOpen', + title: 'Open in SecureBin', + contexts: ['selection'], + }) + }) +}) + +// Also register context menu on startup (service worker restart) +chrome.runtime.onStartup.addListener(() => { + chrome.contextMenus.removeAll(() => { + chrome.contextMenus.create({ + id: 'securebinOpen', + title: 'Open in SecureBin', + contexts: ['selection'], + }) + }) +}) + +chrome.contextMenus.onClicked.addListener(async (info) => { + const text = info.selectionText + if (!text || info.menuItemId !== 'securebinOpen') return + + // Store the selected text so the popup can load it into the editor + await chrome.storage.session.set({ pendingText: { text } }) + + // Open the extension popup + try { + await chrome.action.openPopup() + } catch { + // openPopup() may fail if popup is already open — safe to ignore + } +}) diff --git a/v2/src/components/NavBar.tsx b/v2/src/components/NavBar.tsx new file mode 100644 index 0000000..09cd054 --- /dev/null +++ b/v2/src/components/NavBar.tsx @@ -0,0 +1,49 @@ +import { useNavigate, useLocation } from 'react-router-dom' +import { PenLine, Clock, Settings } from 'lucide-react' +import { cn } from '@/lib/cn' +import { useStore } from '@/lib/store' + +export default function NavBar() { + const navigate = useNavigate() + const location = useLocation() + const isDark = useStore((s) => s.settings.theme === 'dark') + + const navItems = [ + { icon: PenLine, path: '/home', label: 'Editor' }, + { icon: Clock, path: '/history', label: 'History' }, + { icon: Settings, path: '/settings', label: 'Settings' }, + ] + + return ( +
+
+ SecureBin +
+ +
+ ) +} diff --git a/v2/src/components/common/CopyBox.tsx b/v2/src/components/common/CopyBox.tsx new file mode 100644 index 0000000..329b56f --- /dev/null +++ b/v2/src/components/common/CopyBox.tsx @@ -0,0 +1,102 @@ +import { useState, useCallback } from 'react' +import { Copy, Check, ExternalLink, Eye, EyeOff } from 'lucide-react' +import { cn } from '@/lib/cn' + +interface CopyBoxProps { + label?: string + value: string + multiline?: boolean + rows?: number + masked?: boolean + allowCopy?: boolean + openInNew?: boolean + className?: string +} + +export default function CopyBox({ + label, + value, + multiline = false, + rows = 4, + masked = false, + allowCopy = true, + openInNew = false, + className, +}: CopyBoxProps) { + const [copied, setCopied] = useState(false) + const [visible, setVisible] = useState(!masked) + + const handleCopy = useCallback(async () => { + await navigator.clipboard.writeText(value) + setCopied(true) + setTimeout(() => setCopied(false), 2000) + }, [value]) + + const handleOpenNew = useCallback(() => { + window.open(value, '_blank', 'noopener,noreferrer') + }, [value]) + + return ( +
+ {label && ( + + )} +
+ {multiline ? ( +