Merge branch 'main' into dbfix
This commit is contained in:
commit
beeb6e6c8b
@ -8,7 +8,7 @@
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta name="description" content="Fast nostr web ui" />
|
||||
<meta http-equiv="Content-Security-Policy"
|
||||
content="default-src 'self'; child-src 'none'; worker-src 'self'; frame-src youtube.com www.youtube.com https://platform.twitter.com https://embed.tidal.com https://w.soundcloud.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; connect-src wss://* 'self' https://*; img-src * data:; font-src https://fonts.gstatic.com; media-src *; script-src 'self' https://static.cloudflareinsights.com https://platform.twitter.com https://embed.tidal.com;" />
|
||||
content="default-src 'self'; child-src 'none'; worker-src 'self'; frame-src youtube.com www.youtube.com https://platform.twitter.com https://embed.tidal.com https://w.soundcloud.com https://www.mixcloud.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; connect-src wss://* 'self' https://*; img-src * data:; font-src https://fonts.gstatic.com; media-src *; script-src 'self' https://static.cloudflareinsights.com https://platform.twitter.com https://embed.tidal.com;" />
|
||||
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/nostrich_512.png" />
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
|
15
src/Const.ts
15
src/Const.ts
@ -5,6 +5,11 @@ import { RelaySettings } from "Nostr/Connection";
|
||||
*/
|
||||
export const ApiHost = "https://api.snort.social";
|
||||
|
||||
/**
|
||||
* Void.cat file upload service url
|
||||
*/
|
||||
export const VoidCatHost = "https://void.cat";
|
||||
|
||||
/**
|
||||
* Websocket re-connect timeout
|
||||
*/
|
||||
@ -19,9 +24,7 @@ export const ProfileCacheExpire = (1_000 * 60 * 5);
|
||||
* Default bootstrap relays
|
||||
*/
|
||||
export const DefaultRelays = new Map<string, RelaySettings>([
|
||||
["wss://relay.snort.social", { read: true, write: true }],
|
||||
["wss://relay.damus.io", { read: true, write: true }],
|
||||
["wss://nostr-pub.wellorder.net", { read: true, write: true }],
|
||||
["wss://relay.snort.social", { read: true, write: true }]
|
||||
]);
|
||||
|
||||
/**
|
||||
@ -99,3 +102,9 @@ export const TidalRegex = /tidal\.com\/(?:browse\/)?(\w+)\/([a-z0-9-]+)/i;
|
||||
* SoundCloud regex
|
||||
*/
|
||||
export const SoundCloudRegex = /soundcloud\.com\/(?!live)([a-zA-Z0-9]+)\/([a-zA-Z0-9-]+)/
|
||||
|
||||
/**
|
||||
* Mixcloud regex
|
||||
*/
|
||||
|
||||
export const MixCloudRegex = /mixcloud\.com\/(?!live)([a-zA-Z0-9]+)\/([a-zA-Z0-9-]+)/
|
@ -4,13 +4,14 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faUserMinus, faUserPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import { HexKey } from "Nostr";
|
||||
import { RootState } from "State/Store";
|
||||
import { parseId } from "Util";
|
||||
|
||||
export interface FollowButtonProps {
|
||||
pubkey: HexKey,
|
||||
className?: string,
|
||||
}
|
||||
export default function FollowButton(props: FollowButtonProps) {
|
||||
const pubkey = props.pubkey;
|
||||
const pubkey = parseId(props.pubkey);
|
||||
const publiser = useEventPublisher();
|
||||
const isFollowing = useSelector<RootState, boolean>(s => s.login.follows?.includes(pubkey) ?? false);
|
||||
const baseClassName = isFollowing ? `btn btn-warn follow-button` : `btn btn-success follow-button`
|
||||
|
27
src/Element/MixCloudEmbed.tsx
Normal file
27
src/Element/MixCloudEmbed.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import { MixCloudRegex } from "Const";
|
||||
import { useSelector } from "react-redux";
|
||||
import { RootState } from "State/Store";
|
||||
|
||||
const MixCloudEmbed = ({link}: {link: string}) => {
|
||||
|
||||
const feedPath = (MixCloudRegex.test(link) && RegExp.$1) + "%2F" + ( MixCloudRegex.test(link) && RegExp.$2)
|
||||
|
||||
const lightTheme = useSelector<RootState, boolean>(s => s.login.preferences.theme === "light");
|
||||
|
||||
const lightParams = lightTheme ? "light=1" : "light=0";
|
||||
|
||||
return(
|
||||
<>
|
||||
<br/>
|
||||
<iframe
|
||||
title="SoundCloud player"
|
||||
width="100%"
|
||||
height="120"
|
||||
frameBorder="0"
|
||||
src={`https://www.mixcloud.com/widget/iframe/?hide_cover=1&${lightParams}&feed=%2F${feedPath}%2F`}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default MixCloudEmbed;
|
@ -7,7 +7,7 @@ import "./NoteCreator.css";
|
||||
import useEventPublisher from "Feed/EventPublisher";
|
||||
import { openFile } from "Util";
|
||||
import VoidUpload from "Feed/VoidUpload";
|
||||
import { FileExtensionRegex } from "Const";
|
||||
import { FileExtensionRegex, VoidCatHost } from "Const";
|
||||
import Textarea from "Element/Textarea";
|
||||
import Event, { default as NEvent } from "Nostr/Event";
|
||||
|
||||
@ -46,7 +46,7 @@ export function NoteCreator(props: NoteCreatorProps) {
|
||||
let ext = file.name.match(FileExtensionRegex);
|
||||
|
||||
// extension tricks note parser to embed the content
|
||||
let url = rx.file.meta?.url ?? `https://void.cat/d/${rx.file.id}${ext ? `.${ext[1]}` : ""}`;
|
||||
let url = rx.file.meta?.url ?? `${VoidCatHost}/d/${rx.file.id}${ext ? `.${ext[1]}` : ""}`;
|
||||
|
||||
setNote(n => `${n}\n${url}`);
|
||||
} else if (rx?.errorMessage) {
|
||||
|
@ -63,7 +63,7 @@ export default function Relay(props: RelayProps) {
|
||||
<FontAwesomeIcon icon={faPlugCircleXmark} /> {state?.disconnects}
|
||||
</div>
|
||||
<div>
|
||||
<span className="icon-btn" onClick={() => navigate(name)}>
|
||||
<span className="icon-btn" onClick={() => navigate(state!.id)}>
|
||||
<FontAwesomeIcon icon={faGear} />
|
||||
</span>
|
||||
</div>
|
||||
|
@ -52,7 +52,7 @@
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.text img, .text video, .text iframe {
|
||||
.text img, .text video, .text iframe, .text audio {
|
||||
max-width: 100%;
|
||||
max-height: 500px;
|
||||
margin: 10px auto;
|
||||
|
@ -4,7 +4,7 @@ import { Link } from "react-router-dom";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import { TwitterTweetEmbed } from "react-twitter-embed";
|
||||
|
||||
import { UrlRegex, FileExtensionRegex, MentionRegex, InvoiceRegex, YoutubeUrlRegex, TweetUrlRegex, HashtagRegex, TidalRegex, SoundCloudRegex } from "Const";
|
||||
import { UrlRegex, FileExtensionRegex, MentionRegex, InvoiceRegex, YoutubeUrlRegex, TweetUrlRegex, HashtagRegex, TidalRegex, SoundCloudRegex, MixCloudRegex } from "Const";
|
||||
import { eventLink, hexToBech32 } from "Util";
|
||||
import Invoice from "Element/Invoice";
|
||||
import Hashtag from "Element/Hashtag";
|
||||
@ -17,6 +17,7 @@ import { useSelector } from 'react-redux';
|
||||
import { RootState } from 'State/Store';
|
||||
import { UserPreferences } from 'State/Login';
|
||||
import SoundCloudEmbed from 'Element/SoundCloudEmded'
|
||||
import MixCloudEmbed from './MixCloudEmbed';
|
||||
|
||||
function transformHttpLink(a: string, pref: UserPreferences) {
|
||||
try {
|
||||
@ -28,6 +29,7 @@ function transformHttpLink(a: string, pref: UserPreferences) {
|
||||
const tweetId = TweetUrlRegex.test(a) && RegExp.$2;
|
||||
const tidalId = TidalRegex.test(a) && RegExp.$1;
|
||||
const soundcloundId = SoundCloudRegex.test(a) && RegExp.$1;
|
||||
const mixcloudId = MixCloudRegex.test(a) && RegExp.$1;
|
||||
const extension = FileExtensionRegex.test(url.pathname.toLowerCase()) && RegExp.$1;
|
||||
if (extension) {
|
||||
switch (extension) {
|
||||
@ -39,6 +41,11 @@ function transformHttpLink(a: string, pref: UserPreferences) {
|
||||
case "webp": {
|
||||
return <img key={url.toString()} src={url.toString()} />;
|
||||
}
|
||||
case "wav":
|
||||
case "mp3":
|
||||
case "ogg": {
|
||||
return <audio key={url.toString()} src={url.toString()} controls />
|
||||
}
|
||||
case "mp4":
|
||||
case "mov":
|
||||
case "mkv":
|
||||
@ -75,6 +82,8 @@ function transformHttpLink(a: string, pref: UserPreferences) {
|
||||
return <TidalEmbed link={a} />
|
||||
} else if (soundcloundId){
|
||||
return <SoundCloudEmbed link={a} />
|
||||
} else if (mixcloudId){
|
||||
return <MixCloudEmbed link={a} />
|
||||
} else {
|
||||
return <a href={a} onClick={(e) => e.stopPropagation()} target="_blank" rel="noreferrer" className="ext">{a}</a>
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ declare global {
|
||||
nostr: {
|
||||
getPublicKey: () => Promise<HexKey>,
|
||||
signEvent: (event: RawEvent) => Promise<RawEvent>,
|
||||
getRelays: () => Promise<[[string, { read: boolean, write: boolean }]]>,
|
||||
getRelays: () => Promise<Record<string, { read: boolean, write: boolean }>>,
|
||||
nip04: {
|
||||
encrypt: (pubkey: HexKey, content: string) => Promise<string>,
|
||||
decrypt: (pubkey: HexKey, content: string) => Promise<string>
|
||||
@ -71,8 +71,8 @@ export default function useEventPublisher() {
|
||||
return match;
|
||||
}
|
||||
const content = msg.replace(/@npub[a-z0-9]+/g, replaceNpub)
|
||||
.replace(/note[a-z0-9]+/g, replaceNoteId)
|
||||
.replace(HashtagRegex, replaceHashtag);
|
||||
.replace(/note[a-z0-9]+/g, replaceNoteId)
|
||||
.replace(HashtagRegex, replaceHashtag);
|
||||
ev.Content = content;
|
||||
}
|
||||
|
||||
@ -89,8 +89,8 @@ export default function useEventPublisher() {
|
||||
* When they open the site again we wont see that updated relay list and so it will appear to reset back to the previous state
|
||||
*/
|
||||
broadcastForBootstrap: (ev: NEvent | undefined) => {
|
||||
if(ev) {
|
||||
for(let [k, _] of DefaultRelays) {
|
||||
if (ev) {
|
||||
for (let [k, _] of DefaultRelays) {
|
||||
System.WriteOnceToRelay(k, ev);
|
||||
}
|
||||
}
|
||||
@ -182,6 +182,9 @@ export default function useEventPublisher() {
|
||||
temp.add(pkAdd);
|
||||
}
|
||||
for (let pk of temp) {
|
||||
if (pk.length !== 64) {
|
||||
continue;
|
||||
}
|
||||
ev.Tags.push(new Tag(["p", pk], ev.Tags.length));
|
||||
}
|
||||
|
||||
@ -194,7 +197,7 @@ export default function useEventPublisher() {
|
||||
ev.Kind = EventKind.ContactList;
|
||||
ev.Content = JSON.stringify(relays);
|
||||
for (let pk of follows) {
|
||||
if (pk === pkRemove) {
|
||||
if (pk === pkRemove || pk.length !== 64) {
|
||||
continue;
|
||||
}
|
||||
ev.Tags.push(new Tag(["p", pk], ev.Tags.length));
|
||||
|
@ -1,4 +1,5 @@
|
||||
import * as secp from "@noble/secp256k1";
|
||||
import { VoidCatHost } from "Const";
|
||||
|
||||
/**
|
||||
* Upload file to void.cat
|
||||
@ -8,7 +9,7 @@ export default async function VoidUpload(file: File | Blob, filename: string) {
|
||||
const buf = await file.arrayBuffer();
|
||||
const digest = await crypto.subtle.digest("SHA-256", buf);
|
||||
|
||||
let req = await fetch(`https://void.cat/upload`, {
|
||||
let req = await fetch(`${VoidCatHost}/upload`, {
|
||||
mode: "cors",
|
||||
method: "POST",
|
||||
body: buf,
|
||||
@ -17,7 +18,8 @@ export default async function VoidUpload(file: File | Blob, filename: string) {
|
||||
"V-Content-Type": file.type,
|
||||
"V-Filename": filename,
|
||||
"V-Full-Digest": secp.utils.bytesToHex(new Uint8Array(digest)),
|
||||
"V-Description": "Upload from https://snort.social"
|
||||
"V-Description": "Upload from https://snort.social",
|
||||
"V-Strip-Metadata": "true"
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -29,10 +29,12 @@ export type StateSnapshot = {
|
||||
received: number,
|
||||
send: number
|
||||
},
|
||||
info?: RelayInfo
|
||||
info?: RelayInfo,
|
||||
id: string
|
||||
};
|
||||
|
||||
export default class Connection {
|
||||
Id: string;
|
||||
Address: string;
|
||||
Socket: WebSocket | null;
|
||||
Pending: Subscriptions[];
|
||||
@ -50,6 +52,7 @@ export default class Connection {
|
||||
EventsCallback: Map<u256, () => void>;
|
||||
|
||||
constructor(addr: string, options: RelaySettings) {
|
||||
this.Id = uuid();
|
||||
this.Address = addr;
|
||||
this.Socket = null;
|
||||
this.Pending = [];
|
||||
@ -285,6 +288,7 @@ export default class Connection {
|
||||
this.CurrentState.avgLatency = this.Stats.Latency.length > 0 ? (this.Stats.Latency.reduce((acc, v) => acc + v, 0) / this.Stats.Latency.length) : 0;
|
||||
this.CurrentState.disconnects = this.Stats.Disconnects;
|
||||
this.CurrentState.info = this.Info;
|
||||
this.CurrentState.id = this.Id;
|
||||
this.Stats.Latency = this.Stats.Latency.slice(-20); // trim
|
||||
this.HasStateChange = true;
|
||||
this._NotifyState();
|
||||
|
@ -21,18 +21,28 @@ interface Splits {
|
||||
split: number
|
||||
}
|
||||
|
||||
interface TotalToday {
|
||||
donations: number,
|
||||
nip5: number
|
||||
}
|
||||
|
||||
const DonatePage = () => {
|
||||
const [splits, setSplits] = useState<Splits[]>([]);
|
||||
const [today, setSumToday] = useState<TotalToday>();
|
||||
|
||||
async function loadSplits() {
|
||||
async function loadData() {
|
||||
let rsp = await fetch(`${ApiHost}/api/v1/revenue/splits`);
|
||||
if(rsp.ok) {
|
||||
setSplits(await rsp.json());
|
||||
}
|
||||
let rsp2 = await fetch(`${ApiHost}/api/v1/revenue/today`);
|
||||
if(rsp2.ok) {
|
||||
setSumToday(await rsp2.json());
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
loadSplits().catch(console.warn);
|
||||
loadData().catch(console.warn);
|
||||
}, []);
|
||||
|
||||
function actions(pk: HexKey) {
|
||||
@ -62,6 +72,7 @@ const DonatePage = () => {
|
||||
<div className="mr10">Lightning Donation: </div>
|
||||
<ZapButton svc={"donate@snort.social"} />
|
||||
</div>
|
||||
{today && (<small>Total today (UTC): {today.donations.toLocaleString()} sats</small>)}
|
||||
<h3>Primary Developers</h3>
|
||||
{Developers.map(a => <ProfilePreview pubkey={a} key={a} actions={actions(a)} />)}
|
||||
<h4>Contributors</h4>
|
||||
|
@ -4,7 +4,7 @@ import { useNavigate } from "react-router-dom";
|
||||
import * as secp from '@noble/secp256k1';
|
||||
|
||||
import { RootState } from "State/Store";
|
||||
import { setPrivateKey, setPublicKey } from "State/Login";
|
||||
import { setPrivateKey, setPublicKey, setRelays } from "State/Login";
|
||||
import { EmailRegex } from "Const";
|
||||
import { bech32ToHex } from "Util";
|
||||
import { HexKey } from "Nostr";
|
||||
@ -73,6 +73,14 @@ export default function LoginPage() {
|
||||
async function doNip07Login() {
|
||||
let pubKey = await window.nostr.getPublicKey();
|
||||
dispatch(setPublicKey(pubKey));
|
||||
|
||||
if ("getRelays" in window.nostr) {
|
||||
let relays = await window.nostr.getRelays();
|
||||
dispatch(setRelays({
|
||||
relays: relays,
|
||||
createdAt: 1
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
function altLogins() {
|
||||
|
@ -57,8 +57,9 @@ export default function MessagesPage() {
|
||||
<div className="btn" onClick={() => markAllRead()}>Mark All Read</div>
|
||||
</div>
|
||||
{chats.sort((a, b) => {
|
||||
if(b.pubkey === myPubKey) return 1
|
||||
return b.newestMessage - a.newestMessage
|
||||
return a.pubkey === myPubKey ? -1 :
|
||||
b.pubkey === myPubKey ? 1 :
|
||||
b.newestMessage - a.newestMessage
|
||||
}).map(person)}
|
||||
</>
|
||||
)
|
||||
|
@ -31,14 +31,21 @@ export default function NewUserPage() {
|
||||
setError("");
|
||||
try {
|
||||
let rsp = await fetch(`${TwitterFollowsApi}?username=${twitterUsername}`);
|
||||
let data = await rsp.json();
|
||||
if (rsp.ok) {
|
||||
setFollows(await rsp.json());
|
||||
if (Array.isArray(data) && data.length === 0) {
|
||||
setError(`No nostr users found for "${twitterUsername}"`);
|
||||
} else {
|
||||
setFollows(data);
|
||||
}
|
||||
} else if ("error" in data) {
|
||||
setError(data.error);
|
||||
} else {
|
||||
setError("Failed to load follows, is your profile public?");
|
||||
setError("Failed to load follows, please try again later");
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
setError("Failed to load follows, is your profile public?");
|
||||
setError("Failed to load follows, please try again later");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@ export const SettingsRoutes: RouteObject[] = [
|
||||
element: <Relay />,
|
||||
},
|
||||
{
|
||||
path: "relays/:addr",
|
||||
path: "relays/:id",
|
||||
element: <RelayInfo />
|
||||
},
|
||||
{
|
||||
|
@ -15,6 +15,7 @@ import { hexToBech32, openFile } from "Util";
|
||||
import Copy from "Element/Copy";
|
||||
import { RootState } from "State/Store";
|
||||
import { HexKey } from "Nostr";
|
||||
import { VoidCatHost } from "Const";
|
||||
|
||||
export default function ProfileSettings() {
|
||||
const navigate = useNavigate();
|
||||
@ -64,6 +65,7 @@ export default function ProfileSettings() {
|
||||
delete userCopy["loaded"];
|
||||
delete userCopy["created"];
|
||||
delete userCopy["pubkey"];
|
||||
delete userCopy["npub"];
|
||||
console.debug(userCopy);
|
||||
|
||||
let ev = await publisher.metadata(userCopy);
|
||||
@ -86,14 +88,14 @@ export default function ProfileSettings() {
|
||||
async function setNewAvatar() {
|
||||
const rsp = await uploadFile();
|
||||
if (rsp) {
|
||||
setPicture(rsp.meta?.url ?? `https://void.cat/d/${rsp.id}`);
|
||||
setPicture(rsp.meta?.url ?? `${VoidCatHost}/d/${rsp.id}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function setNewBanner() {
|
||||
const rsp = await uploadFile();
|
||||
if (rsp) {
|
||||
setBanner(rsp.meta?.url ?? `https://void.cat/d/${rsp.id}`);
|
||||
setBanner(rsp.meta?.url ?? `${VoidCatHost}/d/${rsp.id}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,16 +10,16 @@ const RelayInfo = () => {
|
||||
const params = useParams();
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
const addr: string = `wss://${params.addr}`;
|
||||
|
||||
const con = System.Sockets.get(addr) ?? System.Sockets.get(`${addr}/`);
|
||||
const stats = useRelayState(con?.Address ?? addr);
|
||||
const conn = Array.from(System.Sockets.values()).find(a => a.Id === params.id);
|
||||
console.debug(conn);
|
||||
const stats = useRelayState(conn?.Address ?? "");
|
||||
|
||||
return (
|
||||
<>
|
||||
<h3 className="pointer" onClick={() => navigate("/settings/relays")}>Relays</h3>
|
||||
<div className="card">
|
||||
<h3>{stats?.info?.name ?? addr}</h3>
|
||||
<h3>{stats?.info?.name}</h3>
|
||||
<p>{stats?.info?.description}</p>
|
||||
|
||||
{stats?.info?.pubkey && (<>
|
||||
@ -45,7 +45,7 @@ const RelayInfo = () => {
|
||||
</>)}
|
||||
<div className="flex mt10 f-end">
|
||||
<div className="btn error" onClick={() => {
|
||||
dispatch(removeRelay(con!.Address));
|
||||
dispatch(removeRelay(conn!.Address));
|
||||
navigate("/settings/relays")
|
||||
}}>Remove</div>
|
||||
</div>
|
||||
|
@ -8,6 +8,8 @@ const PrivateKeyItem = "secret";
|
||||
const PublicKeyItem = "pubkey";
|
||||
const NotificationsReadItem = "notifications-read";
|
||||
const UserPreferencesKey = "preferences";
|
||||
const RelayListKey = "last-relays";
|
||||
const FollowList = "last-follows";
|
||||
|
||||
export interface UserPreferences {
|
||||
/**
|
||||
@ -38,7 +40,7 @@ export interface UserPreferences {
|
||||
/**
|
||||
* Show debugging menus to help diagnose issues
|
||||
*/
|
||||
showDebugMenus: boolean
|
||||
showDebugMenus: boolean
|
||||
}
|
||||
|
||||
export interface LoginStore {
|
||||
@ -110,7 +112,7 @@ const InitState = {
|
||||
dms: [],
|
||||
dmInteraction: 0,
|
||||
preferences: {
|
||||
enableReactions: false,
|
||||
enableReactions: true,
|
||||
autoLoadMedia: true,
|
||||
theme: "system",
|
||||
confirmReposts: false,
|
||||
@ -138,8 +140,6 @@ const LoginSlice = createSlice({
|
||||
state.loggedOut = true;
|
||||
}
|
||||
|
||||
state.relays = Object.fromEntries(DefaultRelays.entries());
|
||||
|
||||
// check pub key only
|
||||
let pubKey = window.localStorage.getItem(PublicKeyItem);
|
||||
if (pubKey && !state.privateKey) {
|
||||
@ -147,6 +147,18 @@ const LoginSlice = createSlice({
|
||||
state.loggedOut = false;
|
||||
}
|
||||
|
||||
let lastRelayList = window.localStorage.getItem(RelayListKey);
|
||||
if (lastRelayList) {
|
||||
state.relays = JSON.parse(lastRelayList);
|
||||
} else {
|
||||
state.relays = Object.fromEntries(DefaultRelays.entries());
|
||||
}
|
||||
|
||||
let lastFollows = window.localStorage.getItem(FollowList);
|
||||
if (lastFollows) {
|
||||
state.follows = JSON.parse(lastFollows);
|
||||
}
|
||||
|
||||
// notifications
|
||||
let readNotif = parseInt(window.localStorage.getItem(NotificationsReadItem) ?? "0");
|
||||
if (!isNaN(readNotif)) {
|
||||
@ -187,25 +199,36 @@ const LoginSlice = createSlice({
|
||||
|
||||
state.relays = Object.fromEntries(filtered.entries());
|
||||
state.latestRelays = createdAt;
|
||||
window.localStorage.setItem(RelayListKey, JSON.stringify(state.relays));
|
||||
},
|
||||
removeRelay: (state, action: PayloadAction<string>) => {
|
||||
delete state.relays[action.payload];
|
||||
state.relays = { ...state.relays };
|
||||
window.localStorage.setItem(RelayListKey, JSON.stringify(state.relays));
|
||||
},
|
||||
setFollows: (state, action: PayloadAction<string | string[]>) => {
|
||||
setFollows: (state, action: PayloadAction<HexKey | HexKey[]>) => {
|
||||
let existing = new Set(state.follows);
|
||||
let update = Array.isArray(action.payload) ? action.payload : [action.payload];
|
||||
|
||||
let changes = false;
|
||||
for (let pk of update) {
|
||||
for (let pk of update.filter(a => a.length === 64)) {
|
||||
if (!existing.has(pk)) {
|
||||
existing.add(pk);
|
||||
changes = true;
|
||||
}
|
||||
}
|
||||
for (let pk of existing) {
|
||||
if (!update.includes(pk)) {
|
||||
existing.delete(pk);
|
||||
changes = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (changes) {
|
||||
state.follows = Array.from(existing);
|
||||
}
|
||||
|
||||
window.localStorage.setItem(FollowList, JSON.stringify(state.follows));
|
||||
},
|
||||
addNotifications: (state, action: PayloadAction<TaggedRawEvent | TaggedRawEvent[]>) => {
|
||||
let n = action.payload;
|
||||
@ -249,10 +272,10 @@ const LoginSlice = createSlice({
|
||||
state.dmInteraction += 1;
|
||||
},
|
||||
logout: (state) => {
|
||||
window.localStorage.clear();
|
||||
Object.assign(state, InitState);
|
||||
state.loggedOut = true;
|
||||
state.relays = Object.fromEntries(DefaultRelays.entries());
|
||||
window.localStorage.clear();
|
||||
},
|
||||
markNotificationsRead: (state) => {
|
||||
state.readNotifications = new Date().getTime();
|
||||
|
Loading…
Reference in New Issue
Block a user