This commit is contained in:
reya
2023-11-20 13:50:59 +07:00
parent 8aa1741699
commit d932f129b2
10 changed files with 571 additions and 235 deletions

55
extension/icons.jsx Normal file
View File

@@ -0,0 +1,55 @@
import React from 'react'
export function Logo({props}) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="56"
height="56"
fill="none"
viewBox="0 0 56 56"
{...props}
>
<rect width="56" height="56" fill="#DCD6FF" rx="16"></rect>
<rect
width="39"
height="39"
x="8.5"
y="8.5"
fill="url(#paint0_linear_24_2370)"
rx="19.5"
></rect>
<rect
width="39"
height="39"
x="8.5"
y="8.5"
stroke="#6149F6"
rx="19.5"
></rect>
<g fill="#fff" stroke="#6149F6" clipPath="url(#clip0_24_2370)">
<path d="M23.78 20.634l.408-.235-.21-.422a4.432 4.432 0 01-.458-1.797l-.031-.78-.696.355A11.533 11.533 0 0016.5 27.998h0V28c.002.87.104 1.738.302 2.585a3.525 3.525 0 102.843-1.058A8.386 8.386 0 0119.5 28a8.523 8.523 0 014.28-7.366zM36.5 28.023v.468l.467.03c.621.042 1.227.212 1.778.5l.687.36.044-.774.005-.075c.01-.166.02-.349.02-.532v-.001a11.523 11.523 0 00-8.142-10.99 3.525 3.525 0 10-.501 2.989A8.524 8.524 0 0136.5 28s0 0 0 0v.022zM33.185 32.622a3.49 3.49 0 00.311 1.844 8.442 8.442 0 01-9.766.877l-.407-.239-.262.392c-.343.514-.79.95-1.311 1.282l-.652.414.645.425a11.39 11.39 0 0014.092-1.23c.264.069.536.107.81.113h.01a3.5 3.5 0 002.803-5.6h.556l-1.603-.932a3.491 3.491 0 00-5.226 2.654z"></path>
</g>
<defs>
<linearGradient
id="paint0_linear_24_2370"
x1="28"
x2="28"
y1="8"
y2="48"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#8E7CFF"></stop>
<stop offset="1" stopColor="#5A41F4"></stop>
</linearGradient>
<clipPath id="clip0_24_2370">
<path
fill="#fff"
d="M0 0H24V24H0z"
transform="translate(16 15)"
></path>
</clipPath>
</defs>
</svg>
)
}

View File

@@ -1,5 +1,5 @@
{
"name": "nos2x",
"name": "Nostr Connect (formerly nos2x)",
"description": "Nostr Signer Extension",
"version": "2.2.0",
"homepage_url": "https://github.com/fiatjaf/nos2x",
@@ -15,7 +15,7 @@
"service_worker": "background.build.js"
},
"action": {
"default_title": "nos2x",
"default_title": "Nostr Connect",
"default_popup": "popup.html"
},
"content_scripts": [

View File

@@ -6,9 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link href="output.css" rel="stylesheet" />
</head>
<body
class="bg-gray-50 dark:bg-gray-950 text-sm font-sans antialiased text-gray-700 dark:text-gray-300"
>
<body class="bg-background text-foreground text-sm font-sans antialiased">
<div id="main" />
<script src="options.build.js"></script>
</body>

View File

@@ -4,6 +4,7 @@ import {render} from 'react-dom'
import {generatePrivateKey, nip19} from 'nostr-tools'
import QRCode from 'react-qr-code'
import {Logo} from './icons'
import {removePermissions} from './common'
function Options() {
@@ -79,171 +80,194 @@ function Options() {
}
return (
<div className="mt-10 p-8 bg-white dark:bg-black border border-gray-100 dark:border-gray-900 rounded-2xl max-w-xl mx-auto">
<div className="flex items-center gap-4">
<div className="w-12 h-12 bg-gray-200 dark:bg-gray-800 rounded-xl" />
<div>
<h1 className="text-lg font-bold">Nostr Connect</h1>
<p className="text-base text-gray-500 font-medium">Nostr signer</p>
</div>
</div>
<div className="mt-4 flex flex-col">
<div className="mb-6 flex flex-col gap-2">
<div className="font-semibold text-base">Private key:</div>
<div className="w-screen h-screen flex flex-col items-center justify-center">
<div className="p-8 shadow-primary border border-primary rounded-2xl max-w-xl mx-auto">
<div className="flex items-center gap-4">
<Logo className="w-14 h-14" />
<div>
<div className="flex gap-2">
<input
type={hidingPrivateKey ? 'password' : 'text'}
value={privKey}
onChange={handleKeyChange}
className="flex-1 h-9 bg-transparent border px-3 py-1 border-gray-200 dark:border-gray-800 rounded-lg"
/>
<div className="shrink-0">
{!privKey && (
<button
onClick={generate}
className="px-3 h-9 font-bold border border-gray-200 shadow-sm dark:border-gray-800 rounded-lg inline-flex items-center justify-center"
>
Generate
</button>
)}
{privKey && hidingPrivateKey && (
<button onClick={() => hidePrivateKey(false)}>
Show key
</button>
)}
{privKey && !hidingPrivateKey && (
<button onClick={() => hidePrivateKey(true)}>Hide key</button>
)}
</div>
</div>
<p className="text-gray-500 text-sm mt-1">
Your key is stored locally. The developer has no way of seeing
your keys.
</p>
{privKey && !isKeyValid() && (
<div style={{color: 'red'}}>private key is invalid!</div>
)}
{!hidingPrivateKey && isKeyValid() && (
<div
style={{
height: 'auto',
maxWidth: 256,
width: '100%',
marginTop: '5px'
}}
>
<QRCode
size={256}
style={{height: 'auto', maxWidth: '100%', width: '100%'}}
value={privKey.toUpperCase()}
viewBox={`0 0 256 256`}
/>
</div>
)}
<h1 className="text-lg font-semibold">Nostr Connect</h1>
<p className="text-sm text-muted font-medium">Nostr signer</p>
</div>
</div>
<div className="mb-4 flex flex-col">
<div className="mb-4 w-full border-b border-gray-100 h-11 flex items-center gap-6">
<div className="text-indigo-600 font-medium flex gap-2 items-center h-11 border-b border-indigo-600">
Relays
<span className="px-3 h-6 inline-flex items-center justify-center bg-indigo-100 text-indigo-600 rounded-full">
10
</span>
</div>
<div className="text-gray-300 font-medium flex items-center gap-2 h-11">
Permissions
<span className="px-3 h-6 inline-flex items-center justify-center bg-gray-100 rounded-full">
0
</span>
</div>
</div>
<div className="flex flex-col gap-2">
<div className="font-semibold text-base">Preferred Relays:</div>
<div className="mt-4 flex flex-col">
<div className="mb-6 flex flex-col gap-2">
<div className="font-semibold text-base">Private key:</div>
<div>
{relays.map(({url, policy}, i) => (
<div
key={i}
style={{display: 'flex', alignItems: 'center', gap: '15px'}}
>
<input
style={{width: '400px'}}
value={url}
onChange={changeRelayURL.bind(null, i)}
/>
<div style={{display: 'flex', gap: '5px'}}>
<label style={{display: 'flex', alignItems: 'center'}}>
read
<input
type="checkbox"
checked={policy.read}
onChange={toggleRelayPolicy.bind(null, i, 'read')}
/>
</label>
<label style={{display: 'flex', alignItems: 'center'}}>
write
<input
type="checkbox"
checked={policy.write}
onChange={toggleRelayPolicy.bind(null, i, 'write')}
/>
</label>
</div>
<button onClick={removeRelay.bind(null, i)}>remove</button>
</div>
))}
<div className="flex gap-2">
<input
style={{width: '400px'}}
value={newRelayURL}
onChange={e => setNewRelayURL(e.target.value)}
onKeyDown={e => {
if (e.key === 'Enter') addNewRelay()
}}
className="flex-1 h-9 bg-transparent border px-3 py-1 border-gray-200 dark:border-gray-800 rounded-lg"
type={hidingPrivateKey ? 'password' : 'text'}
value={privKey}
onChange={handleKeyChange}
className="flex-1 h-9 bg-transparent border border-primary px-3 py-1 rounded-lg"
/>
<button
disabled={!newRelayURL}
onClick={addNewRelay}
className="shrink-0 px-3 h-9 font-bold border border-gray-200 shadow-sm dark:border-gray-800 rounded-lg inline-flex items-center justify-center disabled:text-gray-300"
>
Add Relay
</button>
<div className="shrink-0">
{!privKey && (
<button
type="button"
onClick={generate}
className="px-3 h-9 font-semibold border w-24 border-primary shadow-sm rounded-lg inline-flex items-center justify-center disabled:text-muted"
>
Generate
</button>
)}
{privKey && hidingPrivateKey && (
<button
type="button"
onClick={() => hidePrivateKey(false)}
className="px-3 h-9 font-bold border w-24 border-primary shadow-sm rounded-lg inline-flex items-center justify-center disabled:text-muted"
>
Show key
</button>
)}
{privKey && !hidingPrivateKey && (
<button
type="button"
onClick={() => hidePrivateKey(true)}
className="px-3 h-9 font-bold border w-24 border-primary shadow-sm rounded-lg inline-flex items-center justify-center disabled:text-muted"
>
Hide key
</button>
)}
</div>
</div>
<div className="mt-1 text-sm">
{privKey && !isKeyValid() ? (
<p className="text-red-500">Private key is invalid!</p>
) : (
<p className="text-gray-500">
Your key is stored locally. The developer has no way of
seeing your keys.
</p>
)}
</div>
{!hidingPrivateKey && isKeyValid() && (
<div className="mt-5 flex flex-col items-center">
<QRCode
size={256}
value={privKey.toUpperCase()}
viewBox={`0 0 256 256`}
className="w-full max-w-full"
/>
</div>
)}
</div>
</div>
<div className="mb-4 flex flex-col">
<div className="mb-4 w-full border-b border-gray-100 h-11 flex items-center gap-6">
<div className="text-primary font-medium flex gap-2 items-center h-11 border-b border-secondary">
Relays
<span className="px-3 h-6 inline-flex items-center justify-center bg-secondary text-primary rounded-full">
{relays.length}
</span>
</div>
<div className="font-medium flex items-center text-muted gap-2 h-11">
Permissions
<span className="px-3 h-6 inline-flex items-center justify-center bg-muted rounded-full">
0
</span>
</div>
</div>
<div className="flex flex-col gap-2">
<div className="font-semibold text-base">Preferred Relays:</div>
<div className="flex flex-col gap-2">
{relays.map(({url, policy}, i) => (
<div key={i} className="flex items-center gap-4">
<input
value={url}
onChange={changeRelayURL.bind(null, i)}
className="flex-1 h-9 bg-transparent border px-3 py-1 border-primary rounded-lg placeholder:text-muted"
/>
<div className="flex items-center gap-2">
<label className="inline-flex items-center gap-2 text-muted font-medium">
<input
type="checkbox"
checked={policy.read}
onChange={toggleRelayPolicy.bind(null, i, 'read')}
/>
<p>Read</p>
</label>
<label className="inline-flex items-center gap-2 text-muted font-medium">
<input
type="checkbox"
checked={policy.write}
onChange={toggleRelayPolicy.bind(null, i, 'write')}
/>
<p>Write</p>
</label>
</div>
<button
onClick={removeRelay.bind(null, i)}
className="shrink-0 px-3 w-24 h-9 font-semibold border border-primary shadow-sm rounded-lg inline-flex items-center justify-center disabled:text-muted"
>
Remove
</button>
</div>
))}
<div className="flex gap-2">
<input
value={newRelayURL}
onChange={e => setNewRelayURL(e.target.value)}
onKeyDown={e => {
if (e.key === 'Enter') addNewRelay()
}}
placeholder="wss://"
className="flex-1 h-9 bg-transparent border px-3 py-1 border-primary rounded-lg placeholder:text-muted"
/>
<button
disabled={!newRelayURL}
onClick={addNewRelay}
className="shrink-0 px-3 w-24 h-9 font-semibold border border-primary shadow-sm rounded-lg inline-flex items-center justify-center disabled:text-muted"
>
Add Relay
</button>
</div>
</div>
</div>
</div>
</div>
<div className="mb-6">
<label className="flex gap-2 items-center">
<input
type="checkbox"
checked={showNotifications}
onChange={handleNotifications}
className="w-6 h-6 rounded-md border border-gray-200 dark:border-gray-800 appearance-none"
/>
Show desktop notifications when a permissions has been used
</label>
</div>
<div className="mb-4 flex items-center justify-between">
<div className="font-semibold text-base">Advanced</div>
<div>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="w-5 h-5"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M19.5 8.25l-7.5 7.5-7.5-7.5"
<div className="mb-6">
<label className="flex gap-2 items-center">
<input
type="checkbox"
checked={showNotifications}
onChange={handleNotifications}
className="w-6 h-6 rounded-md border border-gray-200 dark:border-gray-800 appearance-none"
/>
</svg>
Show desktop notifications when a permissions has been used
</label>
</div>
</div>
{/*<div>
<details className="mb-4">
<summary className="flex items-center justify-between">
<div className="font-semibold text-base">Advanced</div>
<div>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="w-5 h-5"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M19.5 8.25l-7.5 7.5-7.5-7.5"
/>
</svg>
</div>
</summary>
<div className="mt-2">
<label className="flex gap-2 items-center">
<input
type="checkbox"
checked={handleNostrLinks}
onChange={changeHandleNostrLinks}
className="w-6 h-6 rounded-md border border-gray-200 dark:border-gray-800 appearance-none"
/>
Handle nostr links
</label>
</div>
</details>
{/*<div>
<label style={{display: 'flex', alignItems: 'center'}}>
<div>
handle{' '}
@@ -291,13 +315,13 @@ function Options() {
)}
</div>
</div>*/}
{/*<div style={{fontSize: '120%'}}>
{/*<div style={{fontSize: '120%'}}>
{messages.map((message, i) => (
<div key={i}>{message}</div>
))}
</div>*/}
</div>
{/*<div>
</div>
{/*<div>
<h2>permissions</h2>
<table>
<thead>
@@ -357,13 +381,14 @@ function Options() {
</div>
)}
</div>*/}
<button
disabled={!unsavedChanges.length}
onClick={saveChanges}
className="w-full h-10 bg-indigo-600 rounded-xl font-bold inline-flex items-center justify-center text-white"
>
Save
</button>
<button
disabled={!unsavedChanges.length}
onClick={saveChanges}
className="w-full h-10 bg-primary rounded-xl font-bold inline-flex items-center justify-center text-white"
>
Save
</button>
</div>
</div>
)

View File

@@ -625,14 +625,18 @@ video {
margin-top: 0.25rem;
}
.mt-10 {
margin-top: 2.5rem;
.mt-2 {
margin-top: 0.5rem;
}
.mt-4 {
margin-top: 1rem;
}
.mt-5 {
margin-top: 1.25rem;
}
.block {
display: block;
}
@@ -665,8 +669,8 @@ video {
height: 2.75rem;
}
.h-12 {
height: 3rem;
.h-14 {
height: 3.5rem;
}
.h-5 {
@@ -681,8 +685,16 @@ video {
height: 2.25rem;
}
.w-12 {
width: 3rem;
.h-screen {
height: 100vh;
}
.w-14 {
width: 3.5rem;
}
.w-24 {
width: 6rem;
}
.w-5 {
@@ -697,6 +709,14 @@ video {
width: 100%;
}
.w-screen {
width: 100vw;
}
.max-w-full {
max-width: 100%;
}
.max-w-xl {
max-width: 36rem;
}
@@ -793,45 +813,36 @@ video {
border-color: rgb(229 231 235 / var(--tw-border-opacity));
}
.border-indigo-600 {
.border-primary {
--tw-border-opacity: 1;
border-color: rgb(79 70 229 / var(--tw-border-opacity));
border-color: rgb(225 227 234 / var(--tw-border-opacity));
}
.bg-gray-100 {
--tw-bg-opacity: 1;
background-color: rgb(243 244 246 / var(--tw-bg-opacity));
.border-secondary {
border-color: rgba(90, 65, 244, 1);
}
.bg-gray-200 {
.bg-background {
--tw-bg-opacity: 1;
background-color: rgb(229 231 235 / var(--tw-bg-opacity));
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
}
.bg-gray-50 {
--tw-bg-opacity: 1;
background-color: rgb(249 250 251 / var(--tw-bg-opacity));
.bg-muted {
background-color: rgba(240, 240, 246, 1);
}
.bg-indigo-100 {
--tw-bg-opacity: 1;
background-color: rgb(224 231 255 / var(--tw-bg-opacity));
.bg-primary {
background-color: rgba(90, 65, 244, 1);
}
.bg-indigo-600 {
--tw-bg-opacity: 1;
background-color: rgb(79 70 229 / var(--tw-bg-opacity));
.bg-secondary {
background-color: rgba(90, 65, 244, 0.1);
}
.bg-transparent {
background-color: transparent;
}
.bg-white {
--tw-bg-opacity: 1;
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
}
.p-8 {
padding: 2rem;
}
@@ -889,9 +900,9 @@ video {
text-transform: capitalize;
}
.text-gray-300 {
.text-foreground {
--tw-text-opacity: 1;
color: rgb(209 213 219 / var(--tw-text-opacity));
color: rgb(54 54 74 / var(--tw-text-opacity));
}
.text-gray-500 {
@@ -899,14 +910,18 @@ video {
color: rgb(107 114 128 / var(--tw-text-opacity));
}
.text-gray-700 {
.text-muted {
--tw-text-opacity: 1;
color: rgb(55 65 81 / var(--tw-text-opacity));
color: rgb(129 132 152 / var(--tw-text-opacity));
}
.text-indigo-600 {
.text-primary {
color: rgba(90, 65, 244, 1);
}
.text-red-500 {
--tw-text-opacity: 1;
color: rgb(79 70 229 / var(--tw-text-opacity));
color: rgb(239 68 68 / var(--tw-text-opacity));
}
.text-white {
@@ -925,6 +940,12 @@ video {
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}
.shadow-primary {
--tw-shadow: 0px 10px 36px 0px rgba(64, 47, 132, 0.04);
--tw-shadow-colored: 0px 10px 36px 0px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}
.shadow-sm {
--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
@@ -963,9 +984,19 @@ video {
transition-duration: 150ms;
}
.disabled\:text-gray-300:disabled {
.placeholder\:text-muted::-moz-placeholder {
--tw-text-opacity: 1;
color: rgb(209 213 219 / var(--tw-text-opacity));
color: rgb(129 132 152 / var(--tw-text-opacity));
}
.placeholder\:text-muted::placeholder {
--tw-text-opacity: 1;
color: rgb(129 132 152 / var(--tw-text-opacity));
}
.disabled\:text-muted:disabled {
--tw-text-opacity: 1;
color: rgb(129 132 152 / var(--tw-text-opacity));
}
@media (prefers-color-scheme: dark) {
@@ -973,29 +1004,4 @@ video {
--tw-border-opacity: 1;
border-color: rgb(31 41 55 / var(--tw-border-opacity));
}
.dark\:border-gray-900 {
--tw-border-opacity: 1;
border-color: rgb(17 24 39 / var(--tw-border-opacity));
}
.dark\:bg-black {
--tw-bg-opacity: 1;
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
}
.dark\:bg-gray-800 {
--tw-bg-opacity: 1;
background-color: rgb(31 41 55 / var(--tw-bg-opacity));
}
.dark\:bg-gray-950 {
--tw-bg-opacity: 1;
background-color: rgb(3 7 18 / var(--tw-bg-opacity));
}
.dark\:text-gray-300 {
--tw-text-opacity: 1;
color: rgb(209 213 219 / var(--tw-text-opacity));
}
}