diff --git a/src/Element/Nip5Service.tsx b/src/Element/Nip5Service.tsx index f437b301..0ab9dfca 100644 --- a/src/Element/Nip5Service.tsx +++ b/src/Element/Nip5Service.tsx @@ -33,7 +33,7 @@ export default function Nip5Service(props: Nip05ServiceProps) { const pubkey = useSelector(s => s.login.publicKey); const user = useProfile(pubkey); const publisher = useEventPublisher(); - const svc = new ServiceProvider(props.service); + const svc = useMemo(() => new ServiceProvider(props.service), [props.service]); const [serviceConfig, setServiceConfig] = useState(); const [error, setError] = useState(); const [handle, setHandle] = useState(""); @@ -43,7 +43,7 @@ export default function Nip5Service(props: Nip05ServiceProps) { const [showInvoice, setShowInvoice] = useState(false); const [registerStatus, setRegisterStatus] = useState(); - const domainConfig = useMemo(() => serviceConfig?.domains.find(a => a.name === domain), [domain]); + const domainConfig = useMemo(() => serviceConfig?.domains.find(a => a.name === domain), [domain, serviceConfig]); useEffect(() => { svc.GetConfig() @@ -58,7 +58,7 @@ export default function Nip5Service(props: Nip05ServiceProps) { } }) .catch(console.error) - }, [props]); + }, [props, svc]); useEffect(() => { setError(undefined); @@ -89,7 +89,7 @@ export default function Nip5Service(props: Nip05ServiceProps) { .catch(console.error); }); } - }, [handle, domain]); + }, [handle, domain, domainConfig, svc]); useEffect(() => { if (registerResponse && showInvoice) { @@ -111,7 +111,7 @@ export default function Nip5Service(props: Nip05ServiceProps) { }, 2_000); return () => clearInterval(t); } - }, [registerResponse, showInvoice]) + }, [registerResponse, showInvoice, svc]) function mapError(e: ServiceErrorCode, t: string | null): string | undefined { let whyMap = new Map([ diff --git a/src/Element/NoteCreator.css b/src/Element/NoteCreator.css index c415376d..9dce9434 100644 --- a/src/Element/NoteCreator.css +++ b/src/Element/NoteCreator.css @@ -16,13 +16,8 @@ min-height: 40px; background-color: var(--note-bg); border-radius: 10px 10px 0 0; - max-width: -webkit-fill-available; - max-width: -moz-available; - max-width: fill-available; - min-width: 100%; - min-width: -webkit-fill-available; - min-width: -moz-available; - min-width: fill-available; + max-width: stretch; + min-width: stretch; } .note-creator .actions { diff --git a/src/Element/NoteFooter.tsx b/src/Element/NoteFooter.tsx index b8ddf1ac..37a1e89d 100644 --- a/src/Element/NoteFooter.tsx +++ b/src/Element/NoteFooter.tsx @@ -31,8 +31,8 @@ export default function NoteFooter(props: NoteFooterProps) { const [reply, setReply] = useState(false); const [tip, setTip] = useState(false); const isMine = ev.RootPubKey === login; - const reactions = useMemo(() => getReactions(related, ev.Id, EventKind.Reaction), [related]); - const reposts = useMemo(() => getReactions(related, ev.Id, EventKind.Repost), [related]); + const reactions = useMemo(() => getReactions(related, ev.Id, EventKind.Reaction), [related, ev]); + const reposts = useMemo(() => getReactions(related, ev.Id, EventKind.Repost), [related, ev]); const groupReactions = useMemo(() => { return reactions?.reduce((acc, { content }) => { let r = normalizeReaction(content); diff --git a/src/Element/NoteReaction.tsx b/src/Element/NoteReaction.tsx index f7d75122..dc5bd62f 100644 --- a/src/Element/NoteReaction.tsx +++ b/src/Element/NoteReaction.tsx @@ -16,7 +16,8 @@ export interface NoteReactionProps { root?: TaggedRawEvent } export default function NoteReaction(props: NoteReactionProps) { - const ev = useMemo(() => props["data-ev"] || new NEvent(props.data), [props.data, props["data-ev"]]) + const { ["data-ev"]: dataEv, data } = props; + const ev = useMemo(() => dataEv || new NEvent(data), [data, dataEv]) const refEvent = useMemo(() => { if (ev) { @@ -32,19 +33,6 @@ export default function NoteReaction(props: NoteReactionProps) { return null; } - function mapReaction(c: string) { - switch (c) { - case "+": return "❤️"; - case "-": return "👎"; - default: { - if (c.length === 0) { - return "❤️"; - } - return c; - } - } - } - /** * Some clients embed the reposted note in the content */ diff --git a/src/Element/Relay.tsx b/src/Element/Relay.tsx index 92b63fc4..4a00c1db 100644 --- a/src/Element/Relay.tsx +++ b/src/Element/Relay.tsx @@ -1,11 +1,11 @@ import "./Relay.css" -import { faPlug, faTrash, faSquareCheck, faSquareXmark, faWifi, faPlugCircleXmark, faGear } from "@fortawesome/free-solid-svg-icons"; +import { faPlug, faSquareCheck, faSquareXmark, faWifi, faPlugCircleXmark, faGear } from "@fortawesome/free-solid-svg-icons"; import useRelayState from "Feed/RelayState"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { useMemo, useState } from "react"; +import { useMemo } from "react"; import { useDispatch, useSelector } from "react-redux"; -import { removeRelay, setRelays } from "State/Login"; +import { setRelays } from "State/Login"; import { RootState } from "State/Store"; import { RelaySettings } from "Nostr/Connection"; import { useNavigate } from "react-router-dom"; diff --git a/src/Element/Timeline.tsx b/src/Element/Timeline.tsx index a907ca23..c837c399 100644 --- a/src/Element/Timeline.tsx +++ b/src/Element/Timeline.tsx @@ -1,5 +1,5 @@ import "./Timeline.css"; -import { useMemo } from "react"; +import { useCallback, useMemo } from "react"; import useTimelineFeed, { TimelineSubject } from "Feed/TimelineFeed"; import { TaggedRawEvent } from "Nostr"; import EventKind from "Nostr/EventKind"; @@ -23,17 +23,17 @@ export default function Timeline({ subject, postsOnly = false, method }: Timelin method }); - const filterPosts = (notes: TaggedRawEvent[]) => { - return [...notes].sort((a, b) => b.created_at - a.created_at)?.filter(a => postsOnly ? !a.tags.some(b => b[0] === "e") : true); - } + const filterPosts = useCallback((nts: TaggedRawEvent[]) => { + return [...nts].sort((a, b) => b.created_at - a.created_at)?.filter(a => postsOnly ? !a.tags.some(b => b[0] === "e") : true); + }, [postsOnly]); const mainFeed = useMemo(() => { return filterPosts(main.notes); - }, [main]); + }, [main, filterPosts]); const latestFeed = useMemo(() => { return filterPosts(latest.notes).filter(a => !mainFeed.some(b => b.id === a.id)); - }, [latest]); + }, [latest, mainFeed, filterPosts]); function eventElement(e: TaggedRawEvent) { switch (e.kind) { diff --git a/src/Feed/ProfileFeed.ts b/src/Feed/ProfileFeed.ts index a4c4028d..b439f814 100644 --- a/src/Feed/ProfileFeed.ts +++ b/src/Feed/ProfileFeed.ts @@ -1,5 +1,5 @@ import { useLiveQuery } from "dexie-react-hooks"; -import { useEffect, useMemo } from "react"; +import { useEffect } from "react"; import { db } from "Db"; import { MetadataCache } from "Db/User"; import { HexKey } from "Nostr"; diff --git a/src/Feed/Subscription.ts b/src/Feed/Subscription.ts index 052201e2..08966653 100644 --- a/src/Feed/Subscription.ts +++ b/src/Feed/Subscription.ts @@ -38,7 +38,8 @@ function notesReducer(state: NoteStore, arg: ReducerArg) { if (!Array.isArray(evs)) { evs = [evs]; } - evs = evs.filter(a => !state.notes.some(b => b.id === a.id)); + let existingIds = new Set(state.notes.map(a => a.id)); + evs = evs.filter(a => !existingIds.has(a.id)); if (evs.length === 0) { return state; } @@ -83,7 +84,7 @@ export default function useSubscription(sub: Subscriptions | null, options?: Use setSubDebounced(sub); }); } - }, [sub]); + }, [sub, options]); useEffect(() => { if (sub) { @@ -115,6 +116,7 @@ export default function useSubscription(sub: Subscriptions | null, options?: Use }; } }, [subDebounce]); + useEffect(() => { return debounce(DebounceMs, () => { setDebounceOutput(s => s += 1); diff --git a/src/Feed/ThreadFeed.ts b/src/Feed/ThreadFeed.ts index 81921197..aef2c024 100644 --- a/src/Feed/ThreadFeed.ts +++ b/src/Feed/ThreadFeed.ts @@ -36,7 +36,7 @@ export default function useThreadFeed(id: u256) { thisSub.AddSubscription(subRelated); return thisSub; - }, [trackingEvents]); + }, [trackingEvents, pref, id]); const main = useSubscription(sub, { leaveOpen: true }); diff --git a/src/Feed/TimelineFeed.ts b/src/Feed/TimelineFeed.ts index abfd05d1..fe259421 100644 --- a/src/Feed/TimelineFeed.ts +++ b/src/Feed/TimelineFeed.ts @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { u256 } from "Nostr"; import EventKind from "Nostr/EventKind"; import { Subscriptions } from "Nostr/Subscriptions"; @@ -19,15 +19,15 @@ export interface TimelineSubject { export default function useTimelineFeed(subject: TimelineSubject, options: TimelineFeedOptions) { const now = unixNow(); - const [window, setWindow] = useState(60 * 60); + const [window] = useState(60 * 60); const [until, setUntil] = useState(now); const [since, setSince] = useState(now - window); const [trackingEvents, setTrackingEvent] = useState([]); const [trackingParentEvents, setTrackingParentEvents] = useState([]); const pref = useSelector(s => s.login.preferences); - function createSub() { - if (subject.type !== "global" && subject.items.length == 0) { + const createSub = useCallback(() => { + if (subject.type !== "global" && subject.items.length === 0) { return null; } @@ -49,7 +49,7 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel } } return sub; - } + }, [subject.type, subject.items]); const sub = useMemo(() => { let sub = createSub(); @@ -78,7 +78,7 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel } } return sub; - }, [subject.type, subject.items, until, since, window]); + }, [until, since, options.method, pref, createSub]); const main = useSubscription(sub, { leaveOpen: true }); @@ -90,7 +90,7 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel subLatest.Since = Math.floor(new Date().getTime() / 1000); } return subLatest; - }, [subject.type, subject.items]); + }, [pref, createSub]); const latest = useSubscription(subRealtime, { leaveOpen: true }); @@ -103,7 +103,7 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel sub.ETags = new Set(trackingEvents); } return sub ?? null; - }, [trackingEvents]); + }, [trackingEvents, pref, subject.type]); const others = useSubscription(subNext, { leaveOpen: true }); @@ -115,7 +115,7 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel return parents; } return null; - }, [trackingParentEvents]); + }, [trackingParentEvents, subject.type]); const parent = useSubscription(subParents); @@ -123,8 +123,10 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel if (main.store.notes.length > 0) { setTrackingEvent(s => { let ids = main.store.notes.map(a => a.id); - let temp = new Set([...s, ...ids]); - return Array.from(temp); + if(ids.some(a => !s.includes(a))) { + return Array.from(new Set([...s, ...ids])); + } + return s; }); let reposts = main.store.notes .filter(a => a.kind === EventKind.Repost && a.content === "") diff --git a/src/Nostr/Connection.ts b/src/Nostr/Connection.ts index 49819622..092b49f0 100644 --- a/src/Nostr/Connection.ts +++ b/src/Nostr/Connection.ts @@ -59,7 +59,7 @@ export default class Connection { this.Stats = new ConnectionStats(); this.StateHooks = new Map(); this.HasStateChange = true; - this.CurrentState = { + this.CurrentState = { connected: false, disconnects: 0, avgLatency: 0, @@ -67,7 +67,7 @@ export default class Connection { received: 0, send: 0 } - }; + } as StateSnapshot; this.LastState = Object.freeze({ ...this.CurrentState }); this.IsClosed = false; this.ReconnectTimer = null; diff --git a/src/Pages/NewUserPage.tsx b/src/Pages/NewUserPage.tsx index b3792b57..f268856f 100644 --- a/src/Pages/NewUserPage.tsx +++ b/src/Pages/NewUserPage.tsx @@ -24,7 +24,7 @@ export default function NewUserPage() { const sortedTwitterFollows = useMemo(() => { return follows.map(a => bech32ToHex(a)) .sort((a, b) => currentFollows.includes(a) ? 1 : -1); - }, [follows]); + }, [follows, currentFollows]); async function loadFollows() { setFollows([]); diff --git a/src/Pages/settings/Profile.tsx b/src/Pages/settings/Profile.tsx index f8ab59e0..482340da 100644 --- a/src/Pages/settings/Profile.tsx +++ b/src/Pages/settings/Profile.tsx @@ -31,7 +31,6 @@ export default function ProfileSettings() { const [about, setAbout] = useState(); const [website, setWebsite] = useState(); const [nip05, setNip05] = useState(); - const [lud06, setLud06] = useState(); const [lud16, setLud16] = useState(); const avatarPicture = (picture?.length ?? 0) === 0 ? Nostrich : picture @@ -45,7 +44,6 @@ export default function ProfileSettings() { setAbout(user.about); setWebsite(user.website); setNip05(user.nip05); - setLud06(user.lud06); setLud16(user.lud16); } }, [user]); diff --git a/src/State/Login.ts b/src/State/Login.ts index 23c0a4b6..ca009db4 100644 --- a/src/State/Login.ts +++ b/src/State/Login.ts @@ -1,9 +1,8 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit' import * as secp from '@noble/secp256k1'; import { DefaultRelays } from 'Const'; -import { HexKey, RawEvent, TaggedRawEvent } from 'Nostr'; +import { HexKey, TaggedRawEvent } from 'Nostr'; import { RelaySettings } from 'Nostr/Connection'; -import { useDispatch } from 'react-redux'; const PrivateKeyItem = "secret"; const PublicKeyItem = "pubkey"; @@ -182,7 +181,7 @@ const LoginSlice = createSlice({ let filtered = new Map(); for (let [k, v] of Object.entries(relays)) { if (k.startsWith("wss://") || k.startsWith("ws://")) { - filtered.set(k, v); + filtered.set(k, v as RelaySettings); } } diff --git a/src/Util.ts b/src/Util.ts index 2e68e50b..abd73dde 100644 --- a/src/Util.ts +++ b/src/Util.ts @@ -1,6 +1,6 @@ import * as secp from "@noble/secp256k1"; import { bech32 } from "bech32"; -import { HexKey, RawEvent, TaggedRawEvent, u256 } from "Nostr"; +import { HexKey, TaggedRawEvent, u256 } from "Nostr"; import EventKind from "Nostr/EventKind"; export async function openFile(): Promise { @@ -65,7 +65,7 @@ export function eventLink(hex: u256) { * @param {string} hex */ export function hexToBech32(hrp: string, hex: string) { - if (typeof hex !== "string" || hex.length === 0 || hex.length % 2 != 0) { + if (typeof hex !== "string" || hex.length === 0 || hex.length % 2 !== 0) { return ""; } diff --git a/src/index.css b/src/index.css index d4328f8a..8556f0db 100644 --- a/src/index.css +++ b/src/index.css @@ -228,16 +228,12 @@ textarea:placeholder { .w-max { width: 100%; - width: -moz-available; - width: -webkit-fill-available; - width: fill-available; + width: stretch; } .w-max-w { max-width: 100%; - max-width: -moz-available; - max-width: -webkit-fill-available; - max-width: fill-available; + max-width: stretch; } a { @@ -267,7 +263,7 @@ div.form-group>div:nth-child(1) { div.form-group>div:nth-child(2) { display: flex; flex-grow: 1; - justify-content: end; + justify-content: flex-end; } div.form-group>div:nth-child(2) input {