diff --git a/package.json b/package.json index cb8bf8ed..b2f96b44 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,6 @@ "@fortawesome/fontawesome-svg-core": "^6.2.1", "@fortawesome/free-solid-svg-icons": "^6.2.1", "@fortawesome/react-fontawesome": "^0.2.0", - "@jukben/emoji-search": "^2.0.1", "@noble/secp256k1": "^1.7.0", "@protobufjs/base64": "^1.1.2", "@reduxjs/toolkit": "^1.9.1", @@ -14,9 +13,9 @@ "@types/node": "^18.11.18", "@types/react": "^18.0.26", "@types/react-dom": "^18.0.10", - "@types/uuid": "^9.0.0", "@types/webscopeio__react-textarea-autocomplete": "^4.7.2", "@webscopeio/react-textarea-autocomplete": "^4.9.2", + "@types/uuid": "^9.0.0", "bech32": "^2.0.0", "dexie": "^3.2.2", "dexie-react-hooks": "^1.1.1", diff --git a/public/favicon.ico b/public/favicon.ico deleted file mode 100644 index 006109f6..00000000 Binary files a/public/favicon.ico and /dev/null differ diff --git a/src/db/User.ts b/src/db/User.ts index a8aec51f..a8fc7de4 100644 --- a/src/db/User.ts +++ b/src/db/User.ts @@ -1,5 +1,4 @@ import { HexKey, TaggedRawEvent, UserMetadata } from "../nostr"; -import { hexToBech32 } from "../Util"; export interface MetadataCache extends UserMetadata { /** @@ -23,7 +22,6 @@ export function mapEventToProfile(ev: TaggedRawEvent) { let data: UserMetadata = JSON.parse(ev.content); return { pubkey: ev.pubkey, - npub: hexToBech32("npub", ev.pubkey), created: ev.created_at, loaded: new Date().getTime(), ...data diff --git a/src/db/index.ts b/src/db/index.ts index 8af9d9a8..d5734ccb 100644 --- a/src/db/index.ts +++ b/src/db/index.ts @@ -1,6 +1,5 @@ -import Dexie, { Table } from "dexie"; -import { MetadataCache } from "./User"; -import { hexToBech32 } from "../Util"; +import Dexie, { Table } from 'dexie'; +import { MetadataCache } from './User'; export class SnortDB extends Dexie { @@ -8,12 +7,8 @@ export class SnortDB extends Dexie { constructor() { super('snortDB'); - this.version(2).stores({ - users: '++pubkey, name, display_name, picture, nip05, npub' - }).upgrade(tx => { - return tx.table("users").toCollection().modify(user => { - user.npub = hexToBech32("npub", user.pubkey) - }) + this.version(1).stores({ + users: '++pubkey, name, display_name, picture, nip05' // Primary key and indexed props }); } } diff --git a/src/element/Avatar.css b/src/element/Avatar.css index a474f224..45ca77dd 100644 --- a/src/element/Avatar.css +++ b/src/element/Avatar.css @@ -33,7 +33,3 @@ .avatar[data-domain="nostriches.net"] { background-image: var(--img-url), var(--nostrplebs-gradient); } - -.avatar[data-domain="strike.army"] { - background-image: var(--img-url), var(--strike-army-gradient); -} diff --git a/src/element/DM.tsx b/src/element/DM.tsx index f80ea471..96d45ee6 100644 --- a/src/element/DM.tsx +++ b/src/element/DM.tsx @@ -1,38 +1,33 @@ import "./DM.css"; import { useEffect, useState } from "react"; -import { useDispatch, useSelector } from "react-redux"; +import { useSelector } from "react-redux"; import { useInView } from 'react-intersection-observer'; import useEventPublisher from "../feed/EventPublisher"; import Event from "../nostr/Event"; import NoteTime from "./NoteTime"; import Text from "./Text"; -import { setLastReadDm } from "../pages/MessagesPage"; -import { RootState } from "../state/Store"; -import { HexKey, TaggedRawEvent } from "../nostr"; -import { incDmInteraction } from "../state/Login"; +import { lastReadDm, setLastReadDm } from "../pages/MessagesPage"; export type DMProps = { - data: TaggedRawEvent + data: any } export default function DM(props: DMProps) { - const dispatch = useDispatch(); - const pubKey = useSelector(s => s.login.publicKey); + const pubKey = useSelector(s => s.login.publicKey); const publisher = useEventPublisher(); const [content, setContent] = useState("Loading..."); const [decrypted, setDecrypted] = useState(false); - const { ref, inView } = useInView(); + const { ref, inView, entry } = useInView(); const isMe = props.data.pubkey === pubKey; async function decrypt() { let e = new Event(props.data); - let decrypted = await publisher.decryptDm(e); - setContent(decrypted || ""); if (!isMe) { setLastReadDm(e.PubKey); - dispatch(incDmInteraction()); } + let decrypted = await publisher.decryptDm(e); + setContent(decrypted || ""); } useEffect(() => { diff --git a/src/element/FollowsList.tsx b/src/element/FollowsList.tsx index 53c53788..d2bb2afb 100644 --- a/src/element/FollowsList.tsx +++ b/src/element/FollowsList.tsx @@ -1,8 +1,8 @@ import { useMemo } from "react"; import useFollowsFeed from "../feed/FollowsFeed"; import { HexKey } from "../nostr"; +import EventKind from "../nostr/EventKind"; import FollowListBase from "./FollowListBase"; -import { getFollowers} from "../feed/FollowsFeed"; export interface FollowsListProps { pubkey: HexKey @@ -12,7 +12,9 @@ export default function FollowsList({ pubkey }: FollowsListProps) { const feed = useFollowsFeed(pubkey); const pubkeys = useMemo(() => { - return getFollowers(feed, pubkey); + let contactLists = feed?.notes.filter(a => a.kind === EventKind.ContactList && a.pubkey === pubkey); + let pTags = contactLists?.map(a => a.tags.filter(b => b[0] === "p").map(c => c[1])); + return [...new Set(pTags?.flat())]; }, [feed]); return diff --git a/src/element/FollowsYou.tsx b/src/element/FollowsYou.tsx deleted file mode 100644 index 88280e27..00000000 --- a/src/element/FollowsYou.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { useMemo } from "react"; -import { useSelector } from "react-redux"; -import { HexKey } from "../nostr"; -import { RootState } from "../state/Store"; -import useFollowsFeed from "../feed/FollowsFeed"; -import { getFollowers } from "../feed/FollowsFeed"; - -export interface FollowsYouProps { - pubkey: HexKey -} - -export default function FollowsYou({ pubkey }: FollowsYouProps ) { - const feed = useFollowsFeed(pubkey); - const loginPubKey = useSelector(s => s.login.publicKey); - - const pubkeys = useMemo(() => { - return getFollowers(feed, pubkey); - }, [feed]); - - const followsMe = pubkeys.includes(loginPubKey!) ?? false ; - - return ( - <> - { followsMe ? follows you : null } - - ) -} diff --git a/src/element/Nip05.css b/src/element/Nip05.css index 2cc3ef46..c71ebe0f 100644 --- a/src/element/Nip05.css +++ b/src/element/Nip05.css @@ -52,10 +52,6 @@ background-image: var(--nostrplebs-gradient); } -.nip05 .domain[data-domain="strike.army"] { - background-image: var(--strike-army-gradient); -} - .nip05 .badge { margin: .1em .2em; } diff --git a/src/element/Note.css b/src/element/Note.css index 85c87c43..d6156dc2 100644 --- a/src/element/Note.css +++ b/src/element/Note.css @@ -1,9 +1,7 @@ .note { margin-bottom: 10px; - border-radius: 10px; - background-color: var(--note-bg); - padding: 10px 10px 8px 10px; - min-height: 110px; + border-bottom: 1px solid var(--gray); + min-height: 140px; } .note.thread { @@ -38,6 +36,7 @@ } .note > .footer { + padding: 10px 0; text-align: right; } @@ -50,4 +49,9 @@ background-color: var(--gray-tertiary); margin-left: -5px; border-left: 3px solid var(--highlight); -} \ No newline at end of file +} + +.indented .note { + border-bottom: none; + padding: 4px; +} diff --git a/src/element/Note.tsx b/src/element/Note.tsx index 3e85bea7..40bd568e 100644 --- a/src/element/Note.tsx +++ b/src/element/Note.tsx @@ -33,7 +33,7 @@ export default function Note(props: NoteProps) { const pubKeys = useMemo(() => ev.Thread?.PubKeys || [], [ev]); const users = useProfile(pubKeys); const deletions = useMemo(() => getReactions(related, ev.Id, EventKind.Deletion), [related]); - const { ref, inView } = useInView({ triggerOnce: true }); + const { ref, inView } = useInView({triggerOnce: true}); const options = { showHeader: true, diff --git a/src/element/Text.tsx b/src/element/Text.tsx index ee28313e..add90348 100644 --- a/src/element/Text.tsx +++ b/src/element/Text.tsx @@ -156,10 +156,6 @@ function transformParagraph({ body, tags, users }: TextFragment) { } function transformText({ body, tags, users }: TextFragment) { - if(body === undefined) - { - debugger; - } let fragments = extractMentions(body, tags, users); fragments = extractLinks(fragments); fragments = extractInvoices(fragments); @@ -184,9 +180,9 @@ export interface TextProps { export default function Text({ content, tags, users }: TextProps) { const components = useMemo(() => { return { - p: (x: any) => transformParagraph({ body: x.children ?? [], tags, users }), + p: (x: any) => transformParagraph({ body: x.children, tags, users }), a: (x: any) => transformHttpLink(x.href), - li: (x: any) => transformLi({ body: x.children ?? [], tags, users }), + li: (x: any) => transformLi({ body: x.children, tags, users }), }; }, [content]); return {content} diff --git a/src/element/Textarea.css b/src/element/Textarea.css index a3ccd992..fece461f 100644 --- a/src/element/Textarea.css +++ b/src/element/Textarea.css @@ -1,4 +1,4 @@ -.user-item, .emoji-item { +.user-item { background: var(--gray); display: flex; flex-direction: row; @@ -7,7 +7,7 @@ padding: 10px; } -.user-item:hover, .emoji-item:hover { +.user-item:hover { background: var(--gray-tertiary); } @@ -39,16 +39,3 @@ .nip05 { font-size: 12px; } - -.emoji-item { - font-size: 12px; -} - -.emoji-item .emoji { - margin-right: .2em; - min-width: 20px; -} - -.emoji-item .emoji-name { - font-weight: bold; -} diff --git a/src/element/Textarea.tsx b/src/element/Textarea.tsx index 94c783d3..21ac610d 100644 --- a/src/element/Textarea.tsx +++ b/src/element/Textarea.tsx @@ -4,7 +4,6 @@ import "./Textarea.css"; import { useState } from "react"; import { useLiveQuery } from "dexie-react-hooks"; import ReactTextareaAutocomplete from "@webscopeio/react-textarea-autocomplete"; -import emoji from "@jukben/emoji-search"; import TextareaAutosize from "react-textarea-autosize"; import Avatar from "./Avatar"; @@ -13,20 +12,6 @@ import { hexToBech32 } from "../Util"; import { db } from "../db"; import { MetadataCache } from "../db/User"; -interface EmojiItemProps { - name: string - char: string -} - -const EmojiItem = ({ entity: { name, char } }: { entity: EmojiItemProps }) => { - return ( -
-
{char}
-
{name}
-
- ) -} - const UserItem = (metadata: MetadataCache) => { const { pubkey, display_name, picture, nip05, ...rest } = metadata return ( @@ -47,8 +32,7 @@ const Textarea = ({ users, onChange, ...rest }: any) => { const allUsers = useLiveQuery( () => db.users - .where("npub").startsWithIgnoreCase(query) - .or("name").startsWithIgnoreCase(query) + .where("name").startsWithIgnoreCase(query) .or("display_name").startsWithIgnoreCase(query) .or("nip05").startsWithIgnoreCase(query) .limit(5) @@ -61,12 +45,6 @@ const Textarea = ({ users, onChange, ...rest }: any) => { return allUsers } - const emojiDataProvider = (token: string) => { - return emoji(token) - .slice(0, 10) - .map(({ name, char }) => ({ name, char })); - } - return ( { onChange={onChange} textAreaComponent={TextareaAutosize} trigger={{ - ":": { - dataProvider: emojiDataProvider, - component: EmojiItem, - output: (item: EmojiItemProps, trigger) => item.char - }, "@": { afterWhitespace: true, dataProvider: userDataProvider, diff --git a/src/feed/FollowsFeed.ts b/src/feed/FollowsFeed.ts index 7dd61b5a..657f3f0b 100644 --- a/src/feed/FollowsFeed.ts +++ b/src/feed/FollowsFeed.ts @@ -1,9 +1,8 @@ import { useMemo } from "react"; import { HexKey } from "../nostr"; import EventKind from "../nostr/EventKind"; -import { Subscriptions} from "../nostr/Subscriptions"; +import { Subscriptions } from "../nostr/Subscriptions"; import useSubscription from "./Subscription"; -import { NoteStore } from "./Subscription" export default function useFollowsFeed(pubkey: HexKey) { const sub = useMemo(() => { @@ -16,10 +15,4 @@ export default function useFollowsFeed(pubkey: HexKey) { }, [pubkey]); return useSubscription(sub); -} - -export function getFollowers(feed: NoteStore, pubkey: HexKey) { - let contactLists = feed?.notes.filter(a => a.kind === EventKind.ContactList && a.pubkey === pubkey); - let pTags = contactLists?.map(a => a.tags.filter(b => b[0] === "p").map(c => c[1])); - return [...new Set(pTags?.flat())]; -} +} \ No newline at end of file diff --git a/src/feed/LoginFeed.ts b/src/feed/LoginFeed.ts index 46051d90..d9373cd2 100644 --- a/src/feed/LoginFeed.ts +++ b/src/feed/LoginFeed.ts @@ -27,16 +27,11 @@ export default function useLoginFeed() { sub.Kinds = new Set([EventKind.ContactList, EventKind.SetMetadata, EventKind.DirectMessage]); let notifications = new Subscriptions(); - notifications.Kinds = new Set([EventKind.TextNote]); + notifications.Kinds = new Set([EventKind.TextNote, EventKind.DirectMessage]); notifications.PTags = new Set([pubKey]); notifications.Limit = 100; sub.AddSubscription(notifications); - let dms = new Subscriptions(); - dms.Kinds = new Set([EventKind.DirectMessage]); - dms.PTags = new Set([pubKey]); - sub.AddSubscription(dms); - return sub; }, [pubKey]); diff --git a/src/index.css b/src/index.css index b1b58efd..5ab6b287 100644 --- a/src/index.css +++ b/src/index.css @@ -4,22 +4,20 @@ --font-color: #FFF; --bg-color: #000; --modal-bg-color: rgba(0,0,0, 0.8); - --note-bg: #111; --gray-superlight: #EEE; --gray-light: #999; --gray-medium: #666; --gray: #333; --gray-secondary: #222; --gray-tertiary: #444; - --highlight-light: #ffd342; - --highlight: #ffc400; - --highlight-dark: #dba800; + --highlight-light: #E8FF52; + --highlight: #CCFF00; + --highlight-dark: #709900; --error: #FF6053; --success: #2AD544; --gray-gradient: linear-gradient(to bottom right, var(--gray-superlight), var(--gray), var(--gray-light)); --snort-gradient: linear-gradient(to bottom right, var(--highlight-light), var(--highlight), var(--highlight-dark)); --nostrplebs-gradient: linear-gradient(to bottom right, #ff3cac, #2b86c5); - --strike-army-gradient: linear-gradient(to bottom right, #CCFF00, #a1c900); } @media (prefers-color-scheme: light) { @@ -30,7 +28,6 @@ --highlight: #FF9B00; --highlight-dark: #944F05; --modal-bg-color: rgba(240, 240, 240, 0.8); - --note-bg: #eee; --gray: #CCC; --gray-secondary: #DDD; --gray-tertiary: #EEE; @@ -58,6 +55,7 @@ code { width: 720px; margin-left: auto; margin-right: auto; + overflow: hidden; } .page > .header { @@ -348,7 +346,8 @@ body.scroll-lock { @media(max-width: 720px) { .page { - width: calc(100vw - 8px); + width: calc(100vw - 20px); + margin: 0 10px; } div.form-group { flex-direction: column; diff --git a/src/index.tsx b/src/index.js similarity index 90% rename from src/index.tsx rename to src/index.js index e3ad1112..7474800f 100644 --- a/src/index.tsx +++ b/src/index.js @@ -1,14 +1,15 @@ import './index.css'; -import { StrictMode } from 'react'; +import React from 'react'; import { QueryClient, QueryClientProvider } from 'react-query'; -import * as ReactDOM from 'react-dom/client'; +import ReactDOM from 'react-dom/client'; import { Provider } from 'react-redux' import { createBrowserRouter, RouterProvider, } from "react-router-dom"; +import { NostrSystem } from './nostr/System'; import EventPage from './pages/EventPage'; import Layout from './pages/Layout'; import LoginPage from './pages/Login'; @@ -77,13 +78,13 @@ const router = createBrowserRouter([ } ]); -const root = ReactDOM.createRoot(document.getElementById('root')!); +const root = ReactDOM.createRoot(document.getElementById('root')); root.render( - + - + - + ); diff --git a/src/pages/ChatPage.tsx b/src/pages/ChatPage.tsx index 658fddf7..ad26d15b 100644 --- a/src/pages/ChatPage.tsx +++ b/src/pages/ChatPage.tsx @@ -10,7 +10,7 @@ import useEventPublisher from "../feed/EventPublisher"; import DM from "../element/DM"; import { RawEvent } from "../nostr"; -import { dmsInChat } from "./MessagesPage"; +import { dmsInChat, isToSelf } from "./MessagesPage"; type RouterParams = { id: string @@ -20,13 +20,14 @@ export default function ChatPage() { const params = useParams(); const publisher = useEventPublisher(); const id = bech32ToHex(params.id ?? ""); + const pubKey = useSelector(s => s.login.publicKey); const dms = useSelector(s => filterDms(s.login.dms)); const [content, setContent] = useState(); const { ref, inView, entry } = useInView(); const dmListRef = useRef(null); function filterDms(dms: RawEvent[]) { - return dmsInChat(dms, id); + return dmsInChat(id === pubKey ? dms.filter(d => isToSelf(d, pubKey)) : dms, id); } const sortedDms = useMemo(() => { diff --git a/src/pages/MessagesPage.tsx b/src/pages/MessagesPage.tsx index 1be9daa0..bcfa146e 100644 --- a/src/pages/MessagesPage.tsx +++ b/src/pages/MessagesPage.tsx @@ -1,11 +1,9 @@ import { useMemo } from "react"; -import { useDispatch, useSelector } from "react-redux" +import { useSelector } from "react-redux" import { HexKey, RawEvent } from "../nostr"; import ProfileImage from "../element/ProfileImage"; import { hexToBech32 } from "../Util"; -import { incDmInteraction } from "../state/Login"; -import { RootState } from "../state/Store"; type DmChat = { pubkey: HexKey, @@ -14,14 +12,12 @@ type DmChat = { } export default function MessagesPage() { - const dispatch = useDispatch(); - const myPubKey = useSelector(s => s.login.publicKey); - const dms = useSelector(s => s.login.dms); - const dmInteraction = useSelector(s => s.login.dmInteraction); + const myPubKey = useSelector(s => s.login.publicKey); + const dms = useSelector(s => s.login.dms); const chats = useMemo(() => { - return extractChats(dms, myPubKey!); - }, [dms, myPubKey, dmInteraction]); + return extractChats(dms, myPubKey); + }, [dms]); function person(chat: DmChat) { return ( @@ -34,19 +30,9 @@ export default function MessagesPage() { ) } - function markAllRead() { - for (let c of chats) { - setLastReadDm(c.pubkey); - } - dispatch(incDmInteraction()); - } - return ( <> -
-

Messages

-
markAllRead()}>Mark All Read
-
+

Messages

{chats.sort((a, b) => b.newestMessage - a.newestMessage).map(person)} ) @@ -68,17 +54,12 @@ export function setLastReadDm(pk: HexKey) { window.localStorage.setItem(k, now.toString()); } -export function dmTo(e: RawEvent) { - let firstP = e.tags.find(b => b[0] === "p"); - return firstP ? firstP[1] : ""; -} - export function isToSelf(e: RawEvent, pk: HexKey) { - return e.pubkey === pk && dmTo(e) === pk; + return e.pubkey === pk && e.tags.some(a => a[0] === "p" && a[1] === pk); } export function dmsInChat(dms: RawEvent[], pk: HexKey) { - return dms.filter(a => a.pubkey === pk || dmTo(a) == pk); + return dms.filter(a => a.pubkey === pk || a.tags.some(b => b[0] === "p" && b[1] === pk)); } export function totalUnread(dms: RawEvent[], myPubKey: HexKey) { @@ -92,11 +73,16 @@ function unreadDms(dms: RawEvent[], myPubKey: HexKey, pk: HexKey) { } function newestMessage(dms: RawEvent[], myPubKey: HexKey, pk: HexKey) { + if(pk === myPubKey) { + return dmsInChat(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); } + export function extractChats(dms: RawEvent[], myPubKey: HexKey) { - const keys = dms.map(a => [a.pubkey, dmTo(a)]).flat(); + const keys = dms.map(a => [a.pubkey, ...a.tags.filter(b => b[0] === "p").map(b => b[1])]).flat(); const filteredKeys = Array.from(new Set(keys)); return filteredKeys.map(a => { return { diff --git a/src/pages/ProfilePage.tsx b/src/pages/ProfilePage.tsx index 7efd2217..7802601f 100644 --- a/src/pages/ProfilePage.tsx +++ b/src/pages/ProfilePage.tsx @@ -20,7 +20,6 @@ import FollowersList from "../element/FollowersList"; import FollowsList from "../element/FollowsList"; import { RootState } from "../state/Store"; import { HexKey } from "../nostr"; -import FollowsYou from "../element/FollowsYou" enum ProfileTab { Notes = "Notes", @@ -51,11 +50,10 @@ export default function ProfilePage() {

{user?.display_name || user?.name || 'Nostrich'}

{user?.nip05 && } - ) } - + function bio() { const lnurl = extractLnAddress(user?.lud16 || user?.lud06 || ""); return ( @@ -86,7 +84,7 @@ export default function ProfilePage() { function tabContent() { switch (tab) { case ProfileTab.Notes: - return ; + return ; case ProfileTab.Follows: { if (isMe) { return ( @@ -108,7 +106,7 @@ export default function ProfilePage() { function avatar() { return (
- +
) } diff --git a/src/state/Login.ts b/src/state/Login.ts index c06ad0b5..df88cc01 100644 --- a/src/state/Login.ts +++ b/src/state/Login.ts @@ -52,27 +52,9 @@ interface LoginStore { /** * Encrypted DM's */ - dms: TaggedRawEvent[], - - /** - * Counter to trigger refresh of unread dms - */ - dmInteraction: 0 + dms: TaggedRawEvent[] }; -const InitState = { - loggedOut: undefined, - publicKey: undefined, - privateKey: undefined, - relays: {}, - latestRelays: 0, - follows: [], - notifications: [], - readNotifications: 0, - dms: [], - dmInteraction: 0 -} as LoginStore; - export interface SetRelaysPayload { relays: Record, createdAt: number @@ -80,7 +62,14 @@ export interface SetRelaysPayload { const LoginSlice = createSlice({ name: "Login", - initialState: InitState, + initialState: { + relays: {}, + latestRelays: 0, + follows: [], + notifications: [], + readNotifications: 0, + dms: [] + }, reducers: { init: (state) => { state.privateKey = window.localStorage.getItem(PrivateKeyItem) ?? undefined; @@ -193,14 +182,17 @@ const LoginSlice = createSlice({ ]; } }, - incDmInteraction: (state) => { - state.dmInteraction += 1; - }, logout: (state) => { - window.localStorage.clear(); - Object.assign(state, InitState); + window.localStorage.removeItem(PrivateKeyItem); + window.localStorage.removeItem(PublicKeyItem); + window.localStorage.removeItem(NotificationsReadItem); + state.privateKey = undefined; + state.publicKey = undefined; + state.follows = []; + state.notifications = []; state.loggedOut = true; - state.relays = Object.fromEntries(DefaultRelays.entries()); + state.readNotifications = 0; + state.dms = []; }, markNotificationsRead: (state) => { state.readNotifications = new Date().getTime(); @@ -218,7 +210,6 @@ export const { setFollows, addNotifications, addDirectMessage, - incDmInteraction, logout, markNotificationsRead } = LoginSlice.actions; diff --git a/src/useScroll.js b/src/useScroll.js new file mode 100644 index 00000000..e6d7277f --- /dev/null +++ b/src/useScroll.js @@ -0,0 +1,26 @@ +import { useEffect, useState } from "react"; + +export default function useScroll() { + const [eop, setEop] = useState(false); + + function handleScroll(e) { + let target = e.path[1]; + let y = target.scrollY + target.innerHeight; + let h = e.target.scrollingElement.offsetHeight; + let padding = 10; + let atEnd = y + padding >= h; + setEop((s) => { + if (s !== atEnd) { + return atEnd; + } + return s; + }); + } + + useEffect(() => { + window.addEventListener("scroll", handleScroll, { passive: true }); + return () => window.removeEventListener("scroll", handleScroll); + }, []); + + return [eop]; +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 2e1c57c1..29bdfd81 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1024,13 +1024,6 @@ "@babel/helper-validator-option" "^7.18.6" "@babel/plugin-transform-typescript" "^7.18.6" -"@babel/runtime@7.5.5": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.5.5.tgz#74fba56d35efbeca444091c7850ccd494fd2f132" - integrity sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ== - dependencies: - regenerator-runtime "^0.13.2" - "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.20.7", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": version "7.20.7" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.7.tgz#fcb41a5a70550e04a7b708037c7c32f7f356d8fd" @@ -1548,15 +1541,6 @@ "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" -"@jukben/emoji-search@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@jukben/emoji-search/-/emoji-search-2.0.1.tgz#128ff80e3efaf00430cf1e235e5af81445f6f991" - integrity sha512-jXVcJGTBl+uOsGld+6J+hcHlRt3Vhm9ffvkrb1IeSVXuFCuyklY2XPI2wvSHG1uMGXfgmKbuUe1MCh1ZV3CXNg== - dependencies: - "@babel/runtime" "7.5.5" - emojilib "2.4.0" - match-sorter "4.0.0" - "@leichtgewicht/ip-codec@^2.0.1": version "2.0.4" resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b" @@ -2137,17 +2121,16 @@ resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz#b6725d5f4af24ace33b36fafd295136e75509f43" integrity sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA== -"@types/uuid@^9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.0.tgz#53ef263e5239728b56096b0a869595135b7952d2" - integrity sha512-kr90f+ERiQtKWMz5rP32ltJ/BtULDI5RVO0uavn1HQUOwjx0R1h0rnDYNL0CepF1zL5bSY6FISAfd9tOdDhU5Q== - "@types/webscopeio__react-textarea-autocomplete@^4.7.2": version "4.7.2" resolved "https://registry.yarnpkg.com/@types/webscopeio__react-textarea-autocomplete/-/webscopeio__react-textarea-autocomplete-4.7.2.tgz#605e8a6b4194fb4b6e55df8a19bc8fcd56319cfa" integrity sha512-e1DZGD+eH19BnllTWCGXAdrMa2kI53wEMuhn/d+wUmnu8//ZI6BiuK/EPdw07fI4+tlyo5qdPZdXdpkoXHJVOw== dependencies: "@types/react" "*" +"@types/uuid@^9.0.0": + version "9.0.0" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.0.tgz#53ef263e5239728b56096b0a869595135b7952d2" + integrity sha512-kr90f+ERiQtKWMz5rP32ltJ/BtULDI5RVO0uavn1HQUOwjx0R1h0rnDYNL0CepF1zL5bSY6FISAfd9tOdDhU5Q== "@types/ws@^8.5.1": version "8.5.4" @@ -3937,11 +3920,6 @@ emoji-regex@^9.2.2: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== -emojilib@2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/emojilib/-/emojilib-2.4.0.tgz#ac518a8bb0d5f76dda57289ccb2fdf9d39ae721e" - integrity sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw== - emojis-list@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" @@ -6348,13 +6326,6 @@ makeerror@1.0.12: dependencies: tmpl "1.0.5" -match-sorter@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/match-sorter/-/match-sorter-4.0.0.tgz#16f1a52ce51b01e462df3e8a9c16b8c1efac2584" - integrity sha512-E4DWje5l7+VvDUlqnACXy1iecuD6ZNiqUFw/DUYdFQljRIskZVHoT+76lLv5zz2BQOTxF2CUEgP1/Xu9Xn3MyQ== - dependencies: - remove-accents "0.4.2" - match-sorter@^6.0.2: version "6.3.1" resolved "https://registry.yarnpkg.com/match-sorter/-/match-sorter-6.3.1.tgz#98cc37fda756093424ddf3cbc62bfe9c75b92bda" @@ -8171,7 +8142,7 @@ regenerate@^1.4.2: resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== -regenerator-runtime@^0.13.11, regenerator-runtime@^0.13.2, regenerator-runtime@^0.13.9: +regenerator-runtime@^0.13.11, regenerator-runtime@^0.13.9: version "0.13.11" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==