review / cleanup
This commit is contained in:
@ -9,40 +9,30 @@ import { bech32ToHex } from "Util";
|
||||
import useEventPublisher from "Feed/EventPublisher";
|
||||
|
||||
import DM from "Element/DM";
|
||||
import { RawEvent, TaggedRawEvent } from "Nostr";
|
||||
import { TaggedRawEvent } from "Nostr";
|
||||
import { dmsInChat, isToSelf } from "Pages/MessagesPage";
|
||||
import NoteToSelf from "Element/NoteToSelf";
|
||||
import { RootState } from "State/Store";
|
||||
|
||||
type RouterParams = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
interface State {
|
||||
login: {
|
||||
dms: TaggedRawEvent[];
|
||||
};
|
||||
}
|
||||
|
||||
export default function ChatPage() {
|
||||
const params = useParams<RouterParams>();
|
||||
const publisher = useEventPublisher();
|
||||
const id = bech32ToHex(params.id ?? "");
|
||||
const pubKey = useSelector<{ login: { publicKey: string } }>(
|
||||
(s) => s.login.publicKey
|
||||
);
|
||||
const dms = useSelector<State, RawEvent[]>((s) => filterDms(s.login.dms));
|
||||
const pubKey = useSelector((s: RootState) => s.login.publicKey);
|
||||
const dms = useSelector((s: RootState) => filterDms(s.login.dms));
|
||||
const [content, setContent] = useState<string>();
|
||||
const { ref, inView } = useInView();
|
||||
const dmListRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
function filterDms(dms: TaggedRawEvent[]) {
|
||||
return dmsInChat(
|
||||
id === pubKey ? dms.filter((d) => isToSelf(d, pubKey)) : dms,
|
||||
id
|
||||
);
|
||||
return dmsInChat(id === pubKey ? dms.filter(d => isToSelf(d, pubKey)) : dms, id);
|
||||
}
|
||||
|
||||
const sortedDms = useMemo<RawEvent[]>(() => {
|
||||
const sortedDms = useMemo(() => {
|
||||
return [...dms].sort((a, b) => a.created_at - b.created_at);
|
||||
}, [dms]);
|
||||
|
||||
@ -70,13 +60,12 @@ export default function ChatPage() {
|
||||
|
||||
return (
|
||||
<>
|
||||
{(id === pubKey && (
|
||||
<NoteToSelf className="f-grow mb-10" pubkey={id} />
|
||||
)) || <ProfileImage pubkey={id} className="f-grow mb10" />}
|
||||
{(id === pubKey && <NoteToSelf className="f-grow mb-10" pubkey={id} />) || (
|
||||
<ProfileImage pubkey={id} className="f-grow mb10" />
|
||||
)}
|
||||
<div className="dm-list" ref={dmListRef}>
|
||||
<div>
|
||||
{/* TODO I need to look into this again, something's being bricked with the RawEvent and TaggedRawEvent */}
|
||||
{sortedDms.map((a) => (
|
||||
{sortedDms.map(a => (
|
||||
<DM data={a as TaggedRawEvent} key={a.id} />
|
||||
))}
|
||||
<div ref={ref} className="mb10"></div>
|
||||
@ -87,9 +76,8 @@ export default function ChatPage() {
|
||||
<textarea
|
||||
className="f-grow mr10"
|
||||
value={content}
|
||||
onChange={(e) => setContent(e.target.value)}
|
||||
onKeyDown={(e) => onEnter(e)}
|
||||
></textarea>
|
||||
onChange={e => setContent(e.target.value)}
|
||||
onKeyDown={e => onEnter(e)}></textarea>
|
||||
<button type="button" onClick={() => sendDm()}>
|
||||
Send
|
||||
</button>
|
||||
|
@ -7,27 +7,15 @@ import { bech32ToHex } from "Util";
|
||||
|
||||
const Developers = [
|
||||
bech32ToHex(KieranPubKey), // kieran
|
||||
bech32ToHex(
|
||||
"npub107jk7htfv243u0x5ynn43scq9wrxtaasmrwwa8lfu2ydwag6cx2quqncxg"
|
||||
), // verbiricha
|
||||
bech32ToHex(
|
||||
"npub1r0rs5q2gk0e3dk3nlc7gnu378ec6cnlenqp8a3cjhyzu6f8k5sgs4sq9ac"
|
||||
), // Karnage
|
||||
bech32ToHex("npub107jk7htfv243u0x5ynn43scq9wrxtaasmrwwa8lfu2ydwag6cx2quqncxg"), // verbiricha
|
||||
bech32ToHex("npub1r0rs5q2gk0e3dk3nlc7gnu378ec6cnlenqp8a3cjhyzu6f8k5sgs4sq9ac"), // Karnage
|
||||
];
|
||||
|
||||
const Contributors = [
|
||||
bech32ToHex(
|
||||
"npub10djxr5pvdu97rjkde7tgcsjxzpdzmdguwacfjwlchvj7t88dl7nsdl54nf"
|
||||
), // ivan
|
||||
bech32ToHex(
|
||||
"npub148jmlutaa49y5wl5mcll003ftj59v79vf7wuv3apcwpf75hx22vs7kk9ay"
|
||||
), // liran cohen
|
||||
bech32ToHex(
|
||||
"npub1xdtducdnjerex88gkg2qk2atsdlqsyxqaag4h05jmcpyspqt30wscmntxy"
|
||||
), // artur
|
||||
bech32ToHex(
|
||||
"npub1vp8fdcyejd4pqjyrjk9sgz68vuhq7pyvnzk8j0ehlljvwgp8n6eqsrnpsw"
|
||||
), // samsamskies
|
||||
bech32ToHex("npub10djxr5pvdu97rjkde7tgcsjxzpdzmdguwacfjwlchvj7t88dl7nsdl54nf"), // ivan
|
||||
bech32ToHex("npub148jmlutaa49y5wl5mcll003ftj59v79vf7wuv3apcwpf75hx22vs7kk9ay"), // liran cohen
|
||||
bech32ToHex("npub1xdtducdnjerex88gkg2qk2atsdlqsyxqaag4h05jmcpyspqt30wscmntxy"), // artur
|
||||
bech32ToHex("npub1vp8fdcyejd4pqjyrjk9sgz68vuhq7pyvnzk8j0ehlljvwgp8n6eqsrnpsw"), // samsamskies
|
||||
];
|
||||
|
||||
interface Splits {
|
||||
@ -60,7 +48,7 @@ const DonatePage = () => {
|
||||
}, []);
|
||||
|
||||
function actions(pk: HexKey) {
|
||||
const split = splits.find((a) => bech32ToHex(a.pubKey) === pk);
|
||||
const split = splits.find(a => bech32ToHex(a.pubKey) === pk);
|
||||
if (split) {
|
||||
return <>{(100 * split.split).toLocaleString()}%</>;
|
||||
}
|
||||
@ -70,44 +58,29 @@ const DonatePage = () => {
|
||||
return (
|
||||
<div className="main-content m5">
|
||||
<h2>Help fund the development of Snort</h2>
|
||||
<p>
|
||||
Snort is an open source project built by passionate people in their free
|
||||
time
|
||||
</p>
|
||||
<p>Snort is an open source project built by passionate people in their free time</p>
|
||||
<p>Your donations are greatly appreciated</p>
|
||||
<p>
|
||||
Check out the code here:{" "}
|
||||
<a
|
||||
className="highlight"
|
||||
href="https://github.com/v0l/snort"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<a className="highlight" href="https://github.com/v0l/snort" rel="noreferrer" target="_blank">
|
||||
https://github.com/v0l/snort
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
Each contributor will get paid a percentage of all donations and NIP-05
|
||||
orders, you can see the split amounts below
|
||||
Each contributor will get paid a percentage of all donations and NIP-05 orders, you can see the split amounts
|
||||
below
|
||||
</p>
|
||||
<div className="flex">
|
||||
<div className="mr10">Lightning Donation: </div>
|
||||
<ZapButton
|
||||
pubkey={bech32ToHex(SnortPubKey)}
|
||||
svc={"donate@snort.social"}
|
||||
/>
|
||||
<ZapButton pubkey={bech32ToHex(SnortPubKey)} svc={"donate@snort.social"} />
|
||||
</div>
|
||||
{today && (
|
||||
<small>
|
||||
Total today (UTC): {today.donations.toLocaleString()} sats
|
||||
</small>
|
||||
)}
|
||||
{today && <small>Total today (UTC): {today.donations.toLocaleString()} sats</small>}
|
||||
<h3>Primary Developers</h3>
|
||||
{Developers.map((a) => (
|
||||
{Developers.map(a => (
|
||||
<ProfilePreview pubkey={a} key={a} actions={actions(a)} />
|
||||
))}
|
||||
<h4>Contributors</h4>
|
||||
{Contributors.map((a) => (
|
||||
{Contributors.map(a => (
|
||||
<ProfilePreview pubkey={a} key={a} actions={actions(a)} />
|
||||
))}
|
||||
</div>
|
||||
|
@ -1,10 +1,9 @@
|
||||
import { useParams } from "react-router-dom";
|
||||
import Timeline from "Element/Timeline";
|
||||
import { unwrap } from "Util";
|
||||
|
||||
const HashTagsPage = () => {
|
||||
const params = useParams();
|
||||
const tag = unwrap(params.tag).toLowerCase();
|
||||
const tag = (params.tag ?? "").toLowerCase();
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -6,8 +6,7 @@ export default function HelpPage() {
|
||||
<>
|
||||
<h2>NIP-05</h2>
|
||||
<p>
|
||||
If you have an enquiry about your NIP-05 order please DM{" "}
|
||||
<Link to={`/messages/${KieranPubKey}`}>Kieran</Link>
|
||||
If you have an enquiry about your NIP-05 order please DM <Link to={`/messages/${KieranPubKey}`}>Kieran</Link>
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
|
@ -29,16 +29,8 @@ export default function Layout() {
|
||||
const [show, setShow] = useState(false);
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
const {
|
||||
loggedOut,
|
||||
publicKey,
|
||||
relays,
|
||||
latestNotification,
|
||||
readNotifications,
|
||||
dms,
|
||||
preferences,
|
||||
newUserKey,
|
||||
} = useSelector((s: RootState) => s.login);
|
||||
const { loggedOut, publicKey, relays, latestNotification, readNotifications, dms, preferences, newUserKey } =
|
||||
useSelector((s: RootState) => s.login);
|
||||
const { isMuted } = useModeration();
|
||||
|
||||
const usingDb = useDb();
|
||||
@ -47,7 +39,7 @@ export default function Layout() {
|
||||
|
||||
const shouldHideNoteCreator = useMemo(() => {
|
||||
const hideNoteCreator = ["/settings", "/messages", "/new"];
|
||||
return hideNoteCreator.some((a) => location.pathname.startsWith(a));
|
||||
return hideNoteCreator.some(a => location.pathname.startsWith(a));
|
||||
}, [location]);
|
||||
|
||||
const hasNotifications = useMemo(
|
||||
@ -58,7 +50,7 @@ export default function Layout() {
|
||||
() =>
|
||||
publicKey
|
||||
? totalUnread(
|
||||
dms.filter((a) => !isMuted(a.pubkey)),
|
||||
dms.filter(a => !isMuted(a.pubkey)),
|
||||
publicKey
|
||||
)
|
||||
: 0,
|
||||
@ -98,14 +90,10 @@ export default function Layout() {
|
||||
useEffect(() => {
|
||||
const osTheme = window.matchMedia("(prefers-color-scheme: light)");
|
||||
setTheme(
|
||||
preferences.theme === "system" && osTheme.matches
|
||||
? "light"
|
||||
: preferences.theme === "light"
|
||||
? "light"
|
||||
: "dark"
|
||||
preferences.theme === "system" && osTheme.matches ? "light" : preferences.theme === "light" ? "light" : "dark"
|
||||
);
|
||||
|
||||
osTheme.onchange = (e) => {
|
||||
osTheme.onchange = e => {
|
||||
if (preferences.theme === "system") {
|
||||
setTheme(e.matches ? "light" : "dark");
|
||||
}
|
||||
@ -117,7 +105,7 @@ export default function Layout() {
|
||||
|
||||
useEffect(() => {
|
||||
// check DB support then init
|
||||
IndexedUDB.isAvailable().then(async (a) => {
|
||||
IndexedUDB.isAvailable().then(async a => {
|
||||
const dbType = a ? "indexdDb" : "redux";
|
||||
|
||||
// cleanup on load
|
||||
@ -145,14 +133,9 @@ export default function Layout() {
|
||||
const rsp = await fetch("https://api.nostr.watch/v1/online");
|
||||
if (rsp.ok) {
|
||||
const online: string[] = await rsp.json();
|
||||
const pickRandom = online
|
||||
.sort(() => (Math.random() >= 0.5 ? 1 : -1))
|
||||
.slice(0, 4); // pick 4 random relays
|
||||
const pickRandom = online.sort(() => (Math.random() >= 0.5 ? 1 : -1)).slice(0, 4); // pick 4 random relays
|
||||
|
||||
const relayObjects = pickRandom.map((a) => [
|
||||
a,
|
||||
{ read: true, write: true },
|
||||
]);
|
||||
const relayObjects = pickRandom.map(a => [a, { read: true, write: true }]);
|
||||
newRelays = Object.fromEntries(relayObjects);
|
||||
dispatch(
|
||||
setRelays({
|
||||
@ -233,19 +216,10 @@ export default function Layout() {
|
||||
|
||||
{!shouldHideNoteCreator && (
|
||||
<>
|
||||
<button
|
||||
className="note-create-button"
|
||||
type="button"
|
||||
onClick={() => setShow(!show)}
|
||||
>
|
||||
<button className="note-create-button" type="button" onClick={() => setShow(!show)}>
|
||||
<Plus />
|
||||
</button>
|
||||
<NoteCreator
|
||||
replyTo={undefined}
|
||||
autoFocus={true}
|
||||
show={show}
|
||||
setShow={setShow}
|
||||
/>
|
||||
<NoteCreator replyTo={undefined} autoFocus={true} show={show} setShow={setShow} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
@ -4,12 +4,7 @@ import { useNavigate } from "react-router-dom";
|
||||
import * as secp from "@noble/secp256k1";
|
||||
|
||||
import { RootState } from "State/Store";
|
||||
import {
|
||||
setPrivateKey,
|
||||
setPublicKey,
|
||||
setRelays,
|
||||
setGeneratedPrivateKey,
|
||||
} from "State/Login";
|
||||
import { setPrivateKey, setPublicKey, setRelays, setGeneratedPrivateKey } from "State/Login";
|
||||
import { DefaultRelays, EmailRegex } from "Const";
|
||||
import { bech32ToHex } from "Util";
|
||||
import { HexKey } from "Nostr";
|
||||
@ -17,9 +12,7 @@ import { HexKey } from "Nostr";
|
||||
export default function LoginPage() {
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
const publicKey = useSelector<RootState, HexKey | undefined>(
|
||||
(s) => s.login.publicKey
|
||||
);
|
||||
const publicKey = useSelector<RootState, HexKey | undefined>(s => s.login.publicKey);
|
||||
const [key, setKey] = useState("");
|
||||
const [error, setError] = useState("");
|
||||
|
||||
@ -31,11 +24,7 @@ export default function LoginPage() {
|
||||
|
||||
async function getNip05PubKey(addr: string) {
|
||||
const [username, domain] = addr.split("@");
|
||||
const rsp = await fetch(
|
||||
`https://${domain}/.well-known/nostr.json?name=${encodeURIComponent(
|
||||
username
|
||||
)}`
|
||||
);
|
||||
const rsp = await fetch(`https://${domain}/.well-known/nostr.json?name=${encodeURIComponent(username)}`);
|
||||
if (rsp.ok) {
|
||||
const data = await rsp.json();
|
||||
const pKey = data.names[username];
|
||||
@ -124,7 +113,7 @@ export default function LoginPage() {
|
||||
type="text"
|
||||
placeholder="nsec / npub / nip-05 / hex private key..."
|
||||
className="f-grow"
|
||||
onChange={(e) => setKey(e.target.value)}
|
||||
onChange={e => setKey(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
{error.length > 0 ? <b className="error">{error}</b> : null}
|
||||
|
@ -21,18 +21,14 @@ type DmChat = {
|
||||
|
||||
export default function MessagesPage() {
|
||||
const dispatch = useDispatch();
|
||||
const myPubKey = useSelector<RootState, HexKey | undefined>(
|
||||
(s) => s.login.publicKey
|
||||
);
|
||||
const dms = useSelector<RootState, RawEvent[]>((s) => s.login.dms);
|
||||
const dmInteraction = useSelector<RootState, number>(
|
||||
(s) => s.login.dmInteraction
|
||||
);
|
||||
const myPubKey = useSelector<RootState, HexKey | undefined>(s => s.login.publicKey);
|
||||
const dms = useSelector<RootState, RawEvent[]>(s => s.login.dms);
|
||||
const dmInteraction = useSelector<RootState, number>(s => s.login.dmInteraction);
|
||||
const { isMuted } = useModeration();
|
||||
|
||||
const chats = useMemo(() => {
|
||||
return extractChats(
|
||||
dms.filter((a) => !isMuted(a.pubkey)),
|
||||
dms.filter(a => !isMuted(a.pubkey)),
|
||||
myPubKey ?? ""
|
||||
);
|
||||
}, [dms, myPubKey, dmInteraction]);
|
||||
@ -54,11 +50,7 @@ export default function MessagesPage() {
|
||||
if (chat.pubkey === myPubKey) return noteToSelf(chat);
|
||||
return (
|
||||
<div className="flex mb10" key={chat.pubkey}>
|
||||
<ProfileImage
|
||||
pubkey={chat.pubkey}
|
||||
className="f-grow"
|
||||
link={`/messages/${hexToBech32("npub", chat.pubkey)}`}
|
||||
/>
|
||||
<ProfileImage pubkey={chat.pubkey} className="f-grow" link={`/messages/${hexToBech32("npub", chat.pubkey)}`} />
|
||||
<UnreadCount unread={chat.unreadMessages} />
|
||||
</div>
|
||||
);
|
||||
@ -83,11 +75,7 @@ export default function MessagesPage() {
|
||||
</div>
|
||||
{chats
|
||||
.sort((a, b) => {
|
||||
return a.pubkey === myPubKey
|
||||
? -1
|
||||
: b.pubkey === myPubKey
|
||||
? 1
|
||||
: b.newestMessage - a.newestMessage;
|
||||
return a.pubkey === myPubKey ? -1 : b.pubkey === myPubKey ? 1 : b.newestMessage - a.newestMessage;
|
||||
})
|
||||
.map(person)}
|
||||
</div>
|
||||
@ -111,7 +99,7 @@ export function setLastReadDm(pk: HexKey) {
|
||||
}
|
||||
|
||||
export function dmTo(e: RawEvent) {
|
||||
const firstP = e.tags.find((b) => b[0] === "p");
|
||||
const firstP = e.tags.find(b => b[0] === "p");
|
||||
return firstP ? firstP[1] : "";
|
||||
}
|
||||
|
||||
@ -120,42 +108,34 @@ export function isToSelf(e: RawEvent, pk: HexKey) {
|
||||
}
|
||||
|
||||
export function dmsInChat(dms: RawEvent[], pk: HexKey) {
|
||||
return dms.filter((a) => a.pubkey === pk || dmTo(a) === pk);
|
||||
return dms.filter(a => a.pubkey === pk || dmTo(a) === pk);
|
||||
}
|
||||
|
||||
export function totalUnread(dms: RawEvent[], myPubKey: HexKey) {
|
||||
return extractChats(dms, myPubKey).reduce(
|
||||
(acc, v) => (acc += v.unreadMessages),
|
||||
0
|
||||
);
|
||||
return extractChats(dms, myPubKey).reduce((acc, v) => (acc += v.unreadMessages), 0);
|
||||
}
|
||||
|
||||
function unreadDms(dms: RawEvent[], myPubKey: HexKey, pk: HexKey) {
|
||||
if (pk === myPubKey) return 0;
|
||||
const lastRead = lastReadDm(pk);
|
||||
return dmsInChat(dms, pk).filter(
|
||||
(a) => a.created_at >= lastRead && a.pubkey !== myPubKey
|
||||
).length;
|
||||
return dmsInChat(dms, pk).filter(a => a.created_at >= lastRead && a.pubkey !== myPubKey).length;
|
||||
}
|
||||
|
||||
function newestMessage(dms: RawEvent[], myPubKey: HexKey, pk: HexKey) {
|
||||
if (pk === myPubKey) {
|
||||
return dmsInChat(
|
||||
dms.filter((d) => isToSelf(d, myPubKey)),
|
||||
dms.filter(d => isToSelf(d, myPubKey)),
|
||||
pk
|
||||
).reduce((acc, v) => (acc = v.created_at > acc ? v.created_at : acc), 0);
|
||||
}
|
||||
|
||||
return dmsInChat(dms, pk).reduce(
|
||||
(acc, v) => (acc = v.created_at > acc ? v.created_at : acc),
|
||||
0
|
||||
);
|
||||
return dmsInChat(dms, pk).reduce((acc, v) => (acc = v.created_at > acc ? v.created_at : acc), 0);
|
||||
}
|
||||
|
||||
export function extractChats(dms: RawEvent[], myPubKey: HexKey) {
|
||||
const keys = dms.map((a) => [a.pubkey, dmTo(a)]).flat();
|
||||
const keys = dms.map(a => [a.pubkey, dmTo(a)]).flat();
|
||||
const filteredKeys = Array.from(new Set<string>(keys));
|
||||
return filteredKeys.map((a) => {
|
||||
return filteredKeys.map(a => {
|
||||
return {
|
||||
pubkey: a,
|
||||
unreadMessages: unreadDms(dms, myPubKey, a),
|
||||
|
@ -7,9 +7,7 @@ import Timeline from "Element/Timeline";
|
||||
|
||||
export default function NotificationsPage() {
|
||||
const dispatch = useDispatch();
|
||||
const pubkey = useSelector<RootState, HexKey | undefined>(
|
||||
(s) => s.login.publicKey
|
||||
);
|
||||
const pubkey = useSelector<RootState, HexKey | undefined>(s => s.login.publicKey);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(markNotificationsRead());
|
||||
|
@ -53,13 +53,9 @@ export default function ProfilePage() {
|
||||
const navigate = useNavigate();
|
||||
const id = useMemo(() => parseId(params.id ?? ""), [params]);
|
||||
const user = useUserProfile(id);
|
||||
const loggedOut = useSelector<RootState, boolean | undefined>(
|
||||
(s) => s.login.loggedOut
|
||||
);
|
||||
const loginPubKey = useSelector<RootState, HexKey | undefined>(
|
||||
(s) => s.login.publicKey
|
||||
);
|
||||
const follows = useSelector<RootState, HexKey[]>((s) => s.login.follows);
|
||||
const loggedOut = useSelector<RootState, boolean | undefined>(s => s.login.loggedOut);
|
||||
const loginPubKey = useSelector<RootState, HexKey | undefined>(s => s.login.publicKey);
|
||||
const follows = useSelector<RootState, HexKey[]>(s => s.login.follows);
|
||||
const isMe = loginPubKey === id;
|
||||
const [showLnQr, setShowLnQr] = useState<boolean>(false);
|
||||
const [showProfileQr, setShowProfileQr] = useState<boolean>(false);
|
||||
@ -72,14 +68,10 @@ export default function ProfilePage() {
|
||||
});
|
||||
const lnurl = extractLnAddress(user?.lud16 || user?.lud06 || "");
|
||||
const website_url =
|
||||
user?.website && !user.website.startsWith("http")
|
||||
? "https://" + user.website
|
||||
: user?.website || "";
|
||||
user?.website && !user.website.startsWith("http") ? "https://" + user.website : user?.website || "";
|
||||
const zapFeed = useZapsFeed(id);
|
||||
const zaps = useMemo(() => {
|
||||
const profileZaps = zapFeed.store.notes
|
||||
.map(parseZap)
|
||||
.filter((z) => z.valid && z.p === id && !z.e && z.zapper !== id);
|
||||
const profileZaps = zapFeed.store.notes.map(parseZap).filter(z => z.valid && z.p === id && !z.e && z.zapper !== id);
|
||||
profileZaps.sort((a, b) => b.amount - a.amount);
|
||||
return profileZaps;
|
||||
}, [zapFeed.store, id]);
|
||||
@ -178,12 +170,9 @@ export default function ProfilePage() {
|
||||
return (
|
||||
<div className="main-content">
|
||||
<h4 className="zaps-total">
|
||||
<FormattedMessage
|
||||
{...messages.Sats}
|
||||
values={{ n: formatShort(zapsTotal) }}
|
||||
/>
|
||||
<FormattedMessage {...messages.Sats} values={{ n: formatShort(zapsTotal) }} />
|
||||
</h4>
|
||||
{zaps.map((z) => (
|
||||
{zaps.map(z => (
|
||||
<ZapElement showZapped={false} zap={z} />
|
||||
))}
|
||||
</div>
|
||||
@ -195,17 +184,10 @@ export default function ProfilePage() {
|
||||
return (
|
||||
<div className="main-content">
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
{...messages.Following}
|
||||
values={{ n: follows.length }}
|
||||
/>
|
||||
<FormattedMessage {...messages.Following} values={{ n: follows.length }} />
|
||||
</h4>
|
||||
{follows.map((a) => (
|
||||
<ProfilePreview
|
||||
key={a}
|
||||
pubkey={a.toLowerCase()}
|
||||
options={{ about: false }}
|
||||
/>
|
||||
{follows.map(a => (
|
||||
<ProfilePreview key={a} pubkey={a.toLowerCase()} options={{ about: false }} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
@ -242,11 +224,7 @@ export default function ProfilePage() {
|
||||
{showProfileQr && (
|
||||
<Modal className="qr-modal" onClose={() => setShowProfileQr(false)}>
|
||||
<ProfileImage pubkey={id} />
|
||||
<QrCode
|
||||
data={`nostr:${hexToBech32("npub", id)}`}
|
||||
link={undefined}
|
||||
className="m10"
|
||||
/>
|
||||
<QrCode data={`nostr:${hexToBech32("npub", id)}`} link={undefined} className="m10" />
|
||||
</Modal>
|
||||
)}
|
||||
{isMe ? (
|
||||
@ -265,11 +243,7 @@ export default function ProfilePage() {
|
||||
)}
|
||||
{!loggedOut && (
|
||||
<>
|
||||
<IconButton
|
||||
onClick={() =>
|
||||
navigate(`/messages/${hexToBech32("npub", id)}`)
|
||||
}
|
||||
>
|
||||
<IconButton onClick={() => navigate(`/messages/${hexToBech32("npub", id)}`)}>
|
||||
<Envelope width={16} height={13} />
|
||||
</IconButton>
|
||||
</>
|
||||
@ -301,27 +275,14 @@ export default function ProfilePage() {
|
||||
return (
|
||||
<>
|
||||
<div className="profile flex">
|
||||
{user?.banner && (
|
||||
<ProxyImg
|
||||
alt="banner"
|
||||
className="banner"
|
||||
src={user.banner}
|
||||
size={w}
|
||||
/>
|
||||
)}
|
||||
{user?.banner && <ProxyImg alt="banner" className="banner" src={user.banner} size={w} />}
|
||||
<div className="profile-wrapper flex">
|
||||
{avatar()}
|
||||
{userDetails()}
|
||||
</div>
|
||||
</div>
|
||||
<div className="tabs main-content" ref={horizontalScroll}>
|
||||
{[
|
||||
ProfileTab.Notes,
|
||||
ProfileTab.Followers,
|
||||
ProfileTab.Follows,
|
||||
ProfileTab.Zaps,
|
||||
ProfileTab.Muted,
|
||||
].map(renderTab)}
|
||||
{[ProfileTab.Notes, ProfileTab.Followers, ProfileTab.Follows, ProfileTab.Zaps, ProfileTab.Muted].map(renderTab)}
|
||||
{isMe && renderTab(ProfileTab.Blocked)}
|
||||
</div>
|
||||
{tabContent()}
|
||||
|
@ -28,10 +28,9 @@ const RootTab: Record<string, Tab> = {
|
||||
};
|
||||
|
||||
export default function RootPage() {
|
||||
const [loggedOut, pubKey, follows] = useSelector<
|
||||
RootState,
|
||||
[boolean | undefined, HexKey | undefined, HexKey[]]
|
||||
>((s) => [s.login.loggedOut, s.login.publicKey, s.login.follows]);
|
||||
const [loggedOut, pubKey, follows] = useSelector<RootState, [boolean | undefined, HexKey | undefined, HexKey[]]>(
|
||||
s => [s.login.loggedOut, s.login.publicKey, s.login.follows]
|
||||
);
|
||||
const [tab, setTab] = useState<Tab>(RootTab.Posts);
|
||||
|
||||
function followHints() {
|
||||
@ -58,13 +57,7 @@ export default function RootPage() {
|
||||
return (
|
||||
<>
|
||||
<div className="main-content">
|
||||
{pubKey && (
|
||||
<Tabs
|
||||
tabs={[RootTab.Posts, RootTab.PostsAndReplies, RootTab.Global]}
|
||||
tab={tab}
|
||||
setTab={setTab}
|
||||
/>
|
||||
)}
|
||||
{pubKey && <Tabs tabs={[RootTab.Posts, RootTab.PostsAndReplies, RootTab.Global]} tab={tab} setTab={setTab} />}
|
||||
</div>
|
||||
{followHints()}
|
||||
<Timeline
|
||||
|
@ -52,7 +52,7 @@ const SearchPage = () => {
|
||||
className="f-grow mr10"
|
||||
placeholder={formatMessage(messages.SearchPlaceholder)}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
onChange={e => setSearch(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
{keyword && (
|
||||
|
@ -48,7 +48,7 @@ export default function VerificationPage() {
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{services.map((a) => (
|
||||
{services.map(a => (
|
||||
<Nip5Service key={a.name} {...a} />
|
||||
))}
|
||||
</div>
|
||||
|
@ -7,8 +7,7 @@ const messages = defineMessages({
|
||||
Conversations: "Conversations",
|
||||
Global: "Global",
|
||||
NewUsers: "New users page",
|
||||
NoFollows:
|
||||
"Hmm nothing here.. Checkout {newUsersPage} to follow some recommended nostrich's!",
|
||||
NoFollows: "Hmm nothing here.. Checkout {newUsersPage} to follow some recommended nostrich's!",
|
||||
Notes: "Notes",
|
||||
Reactions: "Reactions",
|
||||
Followers: "Followers",
|
||||
@ -28,8 +27,7 @@ const messages = defineMessages({
|
||||
Nip05Pros: `Getting NIP-05 verified can help:`,
|
||||
AvoidImpersonators: "Prevent fake accounts from imitating you",
|
||||
EasierToFind: "Make your profile easier to find and share",
|
||||
Funding:
|
||||
"Fund developers and platforms providing NIP-05 verification services",
|
||||
Funding: "Fund developers and platforms providing NIP-05 verification services",
|
||||
SnortSocialNip: `Our very own NIP-05 verification service, help support the development of this site and get a shiny special badge on our site!`,
|
||||
NostrPlebsNip: `Nostr Plebs is one of the first NIP-05 providers in the space and offers a good collection of domains at reasonable prices`,
|
||||
});
|
||||
|
@ -14,9 +14,7 @@ export default function DiscoverFollows() {
|
||||
<>
|
||||
<h2>Follow some popular accounts</h2>
|
||||
<button onClick={() => navigate("/")}>Skip</button>
|
||||
{sortedRecommends.length > 0 && (
|
||||
<FollowListBase pubkeys={sortedRecommends} />
|
||||
)}
|
||||
{sortedRecommends.length > 0 && <FollowListBase pubkeys={sortedRecommends} />}
|
||||
<button onClick={() => navigate("/")}>Done!</button>
|
||||
</>
|
||||
);
|
||||
|
@ -18,18 +18,14 @@ export default function ImportFollows() {
|
||||
const [error, setError] = useState<string>("");
|
||||
|
||||
const sortedTwitterFollows = useMemo(() => {
|
||||
return follows
|
||||
.map((a) => bech32ToHex(a))
|
||||
.sort((a) => (currentFollows.includes(a) ? 1 : -1));
|
||||
return follows.map(a => bech32ToHex(a)).sort(a => (currentFollows.includes(a) ? 1 : -1));
|
||||
}, [follows, currentFollows]);
|
||||
|
||||
async function loadFollows() {
|
||||
setFollows([]);
|
||||
setError("");
|
||||
try {
|
||||
const rsp = await fetch(
|
||||
`${TwitterFollowsApi}?username=${twitterUsername}`
|
||||
);
|
||||
const rsp = await fetch(`${TwitterFollowsApi}?username=${twitterUsername}`);
|
||||
const data = await rsp.json();
|
||||
if (rsp.ok) {
|
||||
if (Array.isArray(data) && data.length === 0) {
|
||||
@ -64,14 +60,12 @@ export default function ImportFollows() {
|
||||
placeholder="Twitter username.."
|
||||
className="f-grow mr10"
|
||||
value={twitterUsername}
|
||||
onChange={(e) => setTwitterUsername(e.target.value)}
|
||||
onChange={e => setTwitterUsername(e.target.value)}
|
||||
/>
|
||||
<AsyncButton onClick={loadFollows}>Check</AsyncButton>
|
||||
</div>
|
||||
{error.length > 0 && <b className="error">{error}</b>}
|
||||
{sortedTwitterFollows.length > 0 && (
|
||||
<FollowListBase pubkeys={sortedTwitterFollows} />
|
||||
)}
|
||||
{sortedTwitterFollows.length > 0 && <FollowListBase pubkeys={sortedTwitterFollows} />}
|
||||
|
||||
<button onClick={() => navigate("/new/discover")}>Next</button>
|
||||
</>
|
||||
|
@ -34,44 +34,33 @@ export default function NewUserFlow() {
|
||||
return (
|
||||
<>
|
||||
<h1>Welcome to Snort!</h1>
|
||||
<p>
|
||||
Snort is a Nostr UI, nostr is a decentralised protocol for saving and
|
||||
distributing "notes".
|
||||
</p>
|
||||
<p>
|
||||
Notes hold text content, the most popular usage of these notes is to
|
||||
store "tweet like" messages.
|
||||
</p>
|
||||
<p>Snort is a Nostr UI, nostr is a decentralised protocol for saving and distributing "notes".</p>
|
||||
<p>Notes hold text content, the most popular usage of these notes is to store "tweet like" messages.</p>
|
||||
<p>Snort is designed to have a similar experience to Twitter.</p>
|
||||
|
||||
<h2>Keys</h2>
|
||||
<p>
|
||||
Nostr uses digital signature technology to provide tamper proof notes
|
||||
which can safely be replicated to many relays to provide redundant
|
||||
storage of your content.
|
||||
Nostr uses digital signature technology to provide tamper proof notes which can safely be replicated to many
|
||||
relays to provide redundant storage of your content.
|
||||
</p>
|
||||
<p>
|
||||
This means that nobody can modify notes which you have created and
|
||||
everybody can easily verify that the notes they are reading are created
|
||||
by you.
|
||||
</p>
|
||||
<p>
|
||||
This is the same technology which is used by Bitcoin and has been proven
|
||||
to be extremely secure.
|
||||
This means that nobody can modify notes which you have created and everybody can easily verify that the notes
|
||||
they are reading are created by you.
|
||||
</p>
|
||||
<p>This is the same technology which is used by Bitcoin and has been proven to be extremely secure.</p>
|
||||
|
||||
<h2>Your Key</h2>
|
||||
<p>
|
||||
When you want to author new notes you need to sign them with your
|
||||
private key, as with Bitcoin private keys these need to be kept secure.
|
||||
When you want to author new notes you need to sign them with your private key, as with Bitcoin private keys
|
||||
these need to be kept secure.
|
||||
</p>
|
||||
<p>Please now copy your private key and save it somewhere secure:</p>
|
||||
<div className="card">
|
||||
<Copy text={hexToBech32("nsec", privateKey ?? "")} />
|
||||
</div>
|
||||
<p>
|
||||
It is also recommended to use one of the following browser extensions if
|
||||
you are on a desktop computer to secure your key:
|
||||
It is also recommended to use one of the following browser extensions if you are on a desktop computer to secure
|
||||
your key:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
@ -80,11 +69,7 @@ export default function NewUserFlow() {
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://github.com/fiatjaf/nos2x"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<a href="https://github.com/fiatjaf/nos2x" target="_blank" rel="noreferrer">
|
||||
nos2x
|
||||
</a>
|
||||
</li>
|
||||
|
@ -12,9 +12,7 @@ import "./Preferences.css";
|
||||
|
||||
const PreferencesPage = () => {
|
||||
const dispatch = useDispatch();
|
||||
const perf = useSelector<RootState, UserPreferences>(
|
||||
(s) => s.login.preferences
|
||||
);
|
||||
const perf = useSelector<RootState, UserPreferences>(s => s.login.preferences);
|
||||
|
||||
return (
|
||||
<div className="preferences">
|
||||
@ -31,15 +29,14 @@ const PreferencesPage = () => {
|
||||
<div>
|
||||
<select
|
||||
value={perf.theme}
|
||||
onChange={(e) =>
|
||||
onChange={e =>
|
||||
dispatch(
|
||||
setPreferences({
|
||||
...perf,
|
||||
theme: e.target.value,
|
||||
} as UserPreferences)
|
||||
)
|
||||
}
|
||||
>
|
||||
}>
|
||||
<option value="system">
|
||||
<FormattedMessage {...messages.System} />
|
||||
</option>
|
||||
@ -64,15 +61,14 @@ const PreferencesPage = () => {
|
||||
<div>
|
||||
<select
|
||||
value={perf.autoLoadMedia}
|
||||
onChange={(e) =>
|
||||
onChange={e =>
|
||||
dispatch(
|
||||
setPreferences({
|
||||
...perf,
|
||||
autoLoadMedia: e.target.value,
|
||||
} as UserPreferences)
|
||||
)
|
||||
}
|
||||
>
|
||||
}>
|
||||
<option value="none">
|
||||
<FormattedMessage {...messages.None} />
|
||||
</option>
|
||||
@ -99,7 +95,7 @@ const PreferencesPage = () => {
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={perf.imgProxyConfig !== null}
|
||||
onChange={(e) =>
|
||||
onChange={e =>
|
||||
dispatch(
|
||||
setPreferences({
|
||||
...perf,
|
||||
@ -121,7 +117,7 @@ const PreferencesPage = () => {
|
||||
type="text"
|
||||
value={perf.imgProxyConfig?.url}
|
||||
placeholder="URL.."
|
||||
onChange={(e) =>
|
||||
onChange={e =>
|
||||
dispatch(
|
||||
setPreferences({
|
||||
...perf,
|
||||
@ -144,7 +140,7 @@ const PreferencesPage = () => {
|
||||
type="password"
|
||||
value={perf.imgProxyConfig?.key}
|
||||
placeholder="Hex key.."
|
||||
onChange={(e) =>
|
||||
onChange={e =>
|
||||
dispatch(
|
||||
setPreferences({
|
||||
...perf,
|
||||
@ -167,7 +163,7 @@ const PreferencesPage = () => {
|
||||
type="password"
|
||||
value={perf.imgProxyConfig?.salt}
|
||||
placeholder="Hex salt.."
|
||||
onChange={(e) =>
|
||||
onChange={e =>
|
||||
dispatch(
|
||||
setPreferences({
|
||||
...perf,
|
||||
@ -197,11 +193,7 @@ const PreferencesPage = () => {
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={perf.enableReactions}
|
||||
onChange={(e) =>
|
||||
dispatch(
|
||||
setPreferences({ ...perf, enableReactions: e.target.checked })
|
||||
)
|
||||
}
|
||||
onChange={e => dispatch(setPreferences({ ...perf, enableReactions: e.target.checked }))}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -218,11 +210,7 @@ const PreferencesPage = () => {
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={perf.confirmReposts}
|
||||
onChange={(e) =>
|
||||
dispatch(
|
||||
setPreferences({ ...perf, confirmReposts: e.target.checked })
|
||||
)
|
||||
}
|
||||
onChange={e => dispatch(setPreferences({ ...perf, confirmReposts: e.target.checked }))}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -239,11 +227,7 @@ const PreferencesPage = () => {
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={perf.autoShowLatest}
|
||||
onChange={(e) =>
|
||||
dispatch(
|
||||
setPreferences({ ...perf, autoShowLatest: e.target.checked })
|
||||
)
|
||||
}
|
||||
onChange={e => dispatch(setPreferences({ ...perf, autoShowLatest: e.target.checked }))}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -259,15 +243,14 @@ const PreferencesPage = () => {
|
||||
<div>
|
||||
<select
|
||||
value={perf.fileUploader}
|
||||
onChange={(e) =>
|
||||
onChange={e =>
|
||||
dispatch(
|
||||
setPreferences({
|
||||
...perf,
|
||||
fileUploader: e.target.value,
|
||||
} as UserPreferences)
|
||||
)
|
||||
}
|
||||
>
|
||||
}>
|
||||
<option value="void.cat">
|
||||
void.cat <FormattedMessage {...messages.Default} />
|
||||
</option>
|
||||
@ -289,11 +272,7 @@ const PreferencesPage = () => {
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={perf.showDebugMenus}
|
||||
onChange={(e) =>
|
||||
dispatch(
|
||||
setPreferences({ ...perf, showDebugMenus: e.target.checked })
|
||||
)
|
||||
}
|
||||
onChange={e => dispatch(setPreferences({ ...perf, showDebugMenus: e.target.checked }))}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -25,12 +25,8 @@ export interface ProfileSettingsProps {
|
||||
|
||||
export default function ProfileSettings(props: ProfileSettingsProps) {
|
||||
const navigate = useNavigate();
|
||||
const id = useSelector<RootState, HexKey | undefined>(
|
||||
(s) => s.login.publicKey
|
||||
);
|
||||
const privKey = useSelector<RootState, HexKey | undefined>(
|
||||
(s) => s.login.privateKey
|
||||
);
|
||||
const id = useSelector<RootState, HexKey | undefined>(s => s.login.publicKey);
|
||||
const privKey = useSelector<RootState, HexKey | undefined>(s => s.login.privateKey);
|
||||
const user = useUserProfile(id ?? "");
|
||||
const publisher = useEventPublisher();
|
||||
const uploader = useFileUpload();
|
||||
@ -118,11 +114,7 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
|
||||
<FormattedMessage {...messages.Name} />:
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
/>
|
||||
<input type="text" value={name} onChange={e => setName(e.target.value)} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
@ -130,11 +122,7 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
|
||||
<FormattedMessage {...messages.DisplayName} />:
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
value={displayName}
|
||||
onChange={(e) => setDisplayName(e.target.value)}
|
||||
/>
|
||||
<input type="text" value={displayName} onChange={e => setDisplayName(e.target.value)} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-group form-col">
|
||||
@ -142,11 +130,7 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
|
||||
<FormattedMessage {...messages.About} />:
|
||||
</div>
|
||||
<div className="w-max">
|
||||
<textarea
|
||||
className="w-max"
|
||||
onChange={(e) => setAbout(e.target.value)}
|
||||
value={about}
|
||||
></textarea>
|
||||
<textarea className="w-max" onChange={e => setAbout(e.target.value)} value={about}></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
@ -154,11 +138,7 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
|
||||
<FormattedMessage {...messages.Website} />:
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
value={website}
|
||||
onChange={(e) => setWebsite(e.target.value)}
|
||||
/>
|
||||
<input type="text" value={website} onChange={e => setWebsite(e.target.value)} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
@ -166,12 +146,7 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
|
||||
<FormattedMessage {...messages.Nip05} />:
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
className="mr10"
|
||||
value={nip05}
|
||||
onChange={(e) => setNip05(e.target.value)}
|
||||
/>
|
||||
<input type="text" className="mr10" value={nip05} onChange={e => setNip05(e.target.value)} />
|
||||
<button type="button" onClick={() => navigate("/verification")}>
|
||||
<FontAwesomeIcon icon={faShop} />
|
||||
<FormattedMessage {...messages.Buy} />
|
||||
@ -183,11 +158,7 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
|
||||
<FormattedMessage {...messages.LnAddress} />:
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
value={lud16}
|
||||
onChange={(e) => setLud16(e.target.value)}
|
||||
/>
|
||||
<input type="text" value={lud16} onChange={e => setLud16(e.target.value)} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
@ -212,10 +183,7 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
|
||||
<h2>
|
||||
<FormattedMessage {...messages.Avatar} />
|
||||
</h2>
|
||||
<div
|
||||
style={{ backgroundImage: `url(${avatarPicture})` }}
|
||||
className="avatar"
|
||||
>
|
||||
<div style={{ backgroundImage: `url(${avatarPicture})` }} className="avatar">
|
||||
<div className="edit" onClick={() => setNewAvatar()}>
|
||||
<FormattedMessage {...messages.Edit} />
|
||||
</div>
|
||||
@ -229,12 +197,9 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
|
||||
</h2>
|
||||
<div
|
||||
style={{
|
||||
backgroundImage: `url(${
|
||||
(banner?.length ?? 0) === 0 ? Nostrich : banner
|
||||
})`,
|
||||
backgroundImage: `url(${(banner?.length ?? 0) === 0 ? Nostrich : banner})`,
|
||||
}}
|
||||
className="banner"
|
||||
>
|
||||
className="banner">
|
||||
<div className="edit" onClick={() => setNewBanner()}>
|
||||
<FormattedMessage {...messages.Edit} />
|
||||
</div>
|
||||
|
@ -14,9 +14,7 @@ const RelayInfo = () => {
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const conn = Array.from(System.Sockets.values()).find(
|
||||
(a) => a.Id === params.id
|
||||
);
|
||||
const conn = Array.from(System.Sockets.values()).find(a => a.Id === params.id);
|
||||
console.debug(conn);
|
||||
const stats = useRelayState(conn?.Address ?? "");
|
||||
|
||||
@ -63,12 +61,9 @@ const RelayInfo = () => {
|
||||
<FormattedMessage {...messages.Contact} />
|
||||
</h4>
|
||||
<a
|
||||
href={`${
|
||||
stats.info.contact.startsWith("mailto:") ? "" : "mailto:"
|
||||
}${stats.info.contact}`}
|
||||
href={`${stats.info.contact.startsWith("mailto:") ? "" : "mailto:"}${stats.info.contact}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
rel="noreferrer">
|
||||
{stats.info.contact}
|
||||
</a>
|
||||
</div>
|
||||
@ -79,17 +74,12 @@ const RelayInfo = () => {
|
||||
<FormattedMessage {...messages.Supports} />
|
||||
</h4>
|
||||
<div className="f-grow">
|
||||
{stats.info.supported_nips.map((a) => (
|
||||
{stats.info.supported_nips.map(a => (
|
||||
<span
|
||||
className="pill"
|
||||
onClick={() =>
|
||||
navigate(
|
||||
`https://github.com/nostr-protocol/nips/blob/master/${a
|
||||
.toString()
|
||||
.padStart(2, "0")}.md`
|
||||
)
|
||||
}
|
||||
>
|
||||
navigate(`https://github.com/nostr-protocol/nips/blob/master/${a.toString().padStart(2, "0")}.md`)
|
||||
}>
|
||||
NIP-{a.toString().padStart(2, "0")}
|
||||
</span>
|
||||
))}
|
||||
@ -102,8 +92,7 @@ const RelayInfo = () => {
|
||||
onClick={() => {
|
||||
dispatch(removeRelay(unwrap(conn).Address));
|
||||
navigate("/settings/relays");
|
||||
}}
|
||||
>
|
||||
}}>
|
||||
<FormattedMessage {...messages.Remove} />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -13,9 +13,7 @@ import messages from "./messages";
|
||||
const RelaySettingsPage = () => {
|
||||
const dispatch = useDispatch();
|
||||
const publisher = useEventPublisher();
|
||||
const relays = useSelector<RootState, Record<string, RelaySettings>>(
|
||||
(s) => s.login.relays
|
||||
);
|
||||
const relays = useSelector<RootState, Record<string, RelaySettings>>(s => s.login.relays);
|
||||
const [newRelay, setNewRelay] = useState<string>();
|
||||
|
||||
async function saveRelays() {
|
||||
@ -36,7 +34,7 @@ const RelaySettingsPage = () => {
|
||||
className="f-grow"
|
||||
placeholder="wss://my-relay.com"
|
||||
value={newRelay}
|
||||
onChange={(e) => setNewRelay(e.target.value)}
|
||||
onChange={e => setNewRelay(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<button className="secondary mb10" onClick={() => addNewRelay()}>
|
||||
@ -64,7 +62,7 @@ const RelaySettingsPage = () => {
|
||||
<>
|
||||
<h3>Relays</h3>
|
||||
<div className="flex f-col mb10">
|
||||
{Object.keys(relays || {}).map((a) => (
|
||||
{Object.keys(relays || {}).map(a => (
|
||||
<Relay addr={a} key={a} />
|
||||
))}
|
||||
</div>
|
||||
|
@ -28,8 +28,7 @@ const messages = defineMessages({
|
||||
ServiceKey: "Service Key",
|
||||
ServiceSalt: "Service Salt",
|
||||
EnableReactions: "Enable reactions",
|
||||
EnableReactionsHelp:
|
||||
"Reactions will be shown on every page, if disabled no reactions will be shown",
|
||||
EnableReactionsHelp: "Reactions will be shown on every page, if disabled no reactions will be shown",
|
||||
ConfirmReposts: "Confirm Reposts",
|
||||
ConfirmRepostsHelp: "Reposts need to be manually confirmed",
|
||||
ShowLatest: "Automatically show latest notes",
|
||||
|
Reference in New Issue
Block a user