();
+
+ useEffect(() => {
+ if (stream && video.current && !video.current.src && Hls.isSupported()) {
+ try {
+ const hls = new Hls();
+ hls.loadSource(stream);
+ hls.attachMedia(video.current);
+ hls.on(Hls.Events.ERROR, (event, data) => {
+ const errorType = data.type;
+ if (errorType === Hls.ErrorTypes.NETWORK_ERROR && data.fatal) {
+ hls.stopLoad();
+ hls.detachMedia();
+ setStatus(VideoStatus.Offline);
+ }
+ });
+ hls.on(Hls.Events.MANIFEST_PARSED, () => {
+ setStatus(VideoStatus.Online);
+ });
+ return () => hls.destroy();
+ } catch (e) {
+ console.error(e);
+ setStatus(VideoStatus.Offline);
+ }
+ }
+ }, [video, stream]);
+
+ return (
+
+
+ {status}
+
+
+
+ );
+}
diff --git a/src/components/note/note-contents.tsx b/src/components/note/note-contents.tsx
index 96f44dba9..0c16dadd7 100644
--- a/src/components/note/note-contents.tsx
+++ b/src/components/note/note-contents.tsx
@@ -1,5 +1,5 @@
import React, { useCallback, useEffect, useRef, useState } from "react";
-import { Box, Text } from "@chakra-ui/react";
+import { Box } from "@chakra-ui/react";
import { DraftNostrEvent, NostrEvent } from "../../types/nostr-event";
import styled from "@emotion/styled";
import { useExpand } from "./expanded";
@@ -23,6 +23,7 @@ import {
import { ImageGalleryProvider } from "../image-gallery";
import { useTrusted } from "./trust";
import { renderRedditUrl } from "../embed-types/reddit";
+import EmbeddedContent from "../embeded-content";
function buildContents(event: NostrEvent | DraftNostrEvent, trusted = false) {
let content: EmbedableContent = [event.content.trim()];
@@ -99,15 +100,7 @@ export const NoteContents = React.memo(({ event, maxHeight }: NoteContentsProps)
px="2"
>
- {content.map((part, i) =>
- typeof part === "string" ? (
-
- {part}
-
- ) : (
- React.cloneElement(part, { key: "part-" + i })
- )
- )}
+
{showOverlay && }
diff --git a/src/components/open-graph-card.tsx b/src/components/open-graph-card.tsx
index 8da40c248..99347758f 100644
--- a/src/components/open-graph-card.tsx
+++ b/src/components/open-graph-card.tsx
@@ -24,7 +24,7 @@ export default function OpenGraphCard({ url, ...props }: { url: URL } & Omit
- {data.ogDescription || data.dcDescription}
+ {data.ogDescription || data.dcDescription}
{link}
diff --git a/src/components/page/account-switcher.tsx b/src/components/page/account-switcher.tsx
index 935b106e0..dce148950 100644
--- a/src/components/page/account-switcher.tsx
+++ b/src/components/page/account-switcher.tsx
@@ -15,12 +15,14 @@ import {
import { getUserDisplayName } from "../../helpers/user-metadata";
import useSubject from "../../hooks/use-subject";
import { useUserMetadata } from "../../hooks/use-user-metadata";
-import accountService from "../../services/account";
+import accountService, { Account } from "../../services/account";
import { AddIcon } from "../icons";
import { UserAvatar } from "../user-avatar";
import { useLocation, useNavigate } from "react-router-dom";
+import AccountInfoBadge from "../account-info-badge";
-function AccountItem({ pubkey }: { pubkey: string }) {
+function AccountItem({ account }: { account: Account }) {
+ const pubkey = account.pubkey;
const metadata = useUserMetadata(pubkey, []);
const accord = useAccordionContext();
@@ -32,9 +34,10 @@ function AccountItem({ pubkey }: { pubkey: string }) {
return (
-
- {getUserDisplayName(metadata, pubkey)}
-
+
+ {getUserDisplayName(metadata, pubkey)}
+
+
}
aria-label="Remove Account"
@@ -60,7 +63,7 @@ export function AccountSwitcherList() {
return (
{otherAccounts.map((account) => (
-
+
))}
diff --git a/src/views/login/start.tsx b/src/views/login/start.tsx
index 5426d193b..77a49b28a 100644
--- a/src/views/login/start.tsx
+++ b/src/views/login/start.tsx
@@ -86,7 +86,7 @@ export default function LoginStartView() {
{accounts.map((account) => (
-
+
))}
>
diff --git a/src/views/user/about.tsx b/src/views/user/about.tsx
index c86abd5c7..8b3926d86 100644
--- a/src/views/user/about.tsx
+++ b/src/views/user/about.tsx
@@ -8,6 +8,7 @@ import {
AccordionItem,
AccordionPanel,
Box,
+ Button,
Flex,
Heading,
IconButton,
@@ -40,6 +41,7 @@ import { readablizeSats } from "../../helpers/bolt11";
import { UserAvatar } from "../../components/user-avatar";
import { useIsMobile } from "../../hooks/use-is-mobile";
import { getUserDisplayName } from "../../helpers/user-metadata";
+import { useSharableProfileId } from "../../hooks/use-shareable-profile-id";
function buildDescriptionContent(description: string) {
let content: EmbedableContent = [description.trim()];
@@ -59,12 +61,10 @@ export default function UserAboutTab() {
const metadata = useUserMetadata(pubkey, contextRelays);
const contacts = useUserContacts(pubkey, contextRelays);
const npub = normalizeToBech32(pubkey, Bech32Prefix.Pubkey);
+ const nprofile = useSharableProfileId(pubkey);
const { value: stats } = useAsync(() => userTrustedStatsService.getUserStats(pubkey), [pubkey]);
- const account = useCurrentAccount();
- const isSelf = pubkey === account?.pubkey;
-
const aboutContent = metadata?.about && buildDescriptionContent(metadata?.about);
return (
@@ -259,6 +259,17 @@ export default function UserAboutTab() {
)}
+
+ }
+ rightIcon={}
+ isExternal
+ >
+ Nosta.me page
+
+
);
}
diff --git a/src/views/user/media.tsx b/src/views/user/media.tsx
index a28f68928..8aa9dfbe5 100644
--- a/src/views/user/media.tsx
+++ b/src/views/user/media.tsx
@@ -1,7 +1,6 @@
-import React, { useCallback, useEffect, useMemo, useRef } from "react";
+import React, { useCallback, useMemo, useRef } from "react";
import { Box, Flex, Grid, IconButton } from "@chakra-ui/react";
import { useNavigate, useOutletContext } from "react-router-dom";
-import { useMount, useUnmount } from "react-use";
import { useAdditionalRelayContext } from "../../providers/additional-relay-context";
import { matchImageUrls } from "../../helpers/regexp";
import { useIsMobile } from "../../hooks/use-is-mobile";
@@ -9,11 +8,12 @@ import { ImageGalleryLink, ImageGalleryProvider } from "../../components/image-g
import { ExternalLinkIcon } from "../../components/icons";
import { getSharableNoteId } from "../../helpers/nip19";
import useSubject from "../../hooks/use-subject";
-import userTimelineService from "../../services/user-timeline";
import { NostrEvent } from "../../types/nostr-event";
import TimelineActionAndStatus from "../../components/timeline-action-and-status";
import IntersectionObserverProvider, { useRegisterIntersectionEntity } from "../../providers/intersection-observer";
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
+import { useTimelineLoader } from "../../hooks/use-timeline-loader";
+import { truncatedId } from "../../helpers/nostr-event";
type ImagePreview = { eventId: string; src: string; index: number };
const matchAllImages = new RegExp(matchImageUrls, "ig");
@@ -49,22 +49,19 @@ const UserMediaTab = () => {
const { pubkey } = useOutletContext() as { pubkey: string };
const contextRelays = useAdditionalRelayContext();
- const timeline = useMemo(() => userTimelineService.getTimeline(pubkey), [pubkey]);
-
const eventFilter = useCallback((e: NostrEvent) => e.kind === 1 && !!e.content.match(matchAllImages), []);
- useEffect(() => {
- timeline.setFilter(eventFilter);
- }, [timeline, eventFilter]);
+ const timeline = useTimelineLoader(
+ truncatedId(pubkey) + "-notes",
+ contextRelays,
+ {
+ authors: [pubkey],
+ kinds: [1, 6],
+ },
+ { eventFilter }
+ );
const events = useSubject(timeline.timeline);
- useEffect(() => {
- timeline.setRelays(contextRelays);
- }, [timeline, contextRelays.join("|")]);
-
- useMount(() => timeline.open());
- useUnmount(() => timeline.close());
-
const images = useMemo(() => {
var images: { eventId: string; src: string; index: number }[] = [];
diff --git a/src/views/user/notes.tsx b/src/views/user/notes.tsx
index afecfc459..3945a7ea8 100644
--- a/src/views/user/notes.tsx
+++ b/src/views/user/notes.tsx
@@ -1,20 +1,15 @@
-import React, { useCallback, useEffect, useMemo, useRef } from "react";
+import { useCallback, useRef } from "react";
import { Flex, FormControl, FormLabel, Switch, useDisclosure } from "@chakra-ui/react";
import { useOutletContext } from "react-router-dom";
-import { Note } from "../../components/note";
-import RepostNote from "../../components/repost-note";
-import { isReply, isRepost } from "../../helpers/nostr-event";
+import { isReply, isRepost, truncatedId } from "../../helpers/nostr-event";
import { useAdditionalRelayContext } from "../../providers/additional-relay-context";
-import userTimelineService from "../../services/user-timeline";
-import useSubject from "../../hooks/use-subject";
-import { useMount, useUnmount } from "react-use";
import { RelayIconStack } from "../../components/relay-icon-stack";
import { NostrEvent } from "../../types/nostr-event";
import TimelineActionAndStatus from "../../components/timeline-action-and-status";
import IntersectionObserverProvider from "../../providers/intersection-observer";
-import { TimelineLoader } from "../../classes/timeline-loader";
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
-import GenericNoteTimeline from "../../components/generric-note-timeline";
+import GenericNoteTimeline from "../../components/generic-note-timeline";
+import { useTimelineLoader } from "../../hooks/use-timeline-loader";
const UserNotesTab = () => {
const { pubkey } = useOutletContext() as { pubkey: string };
@@ -23,9 +18,6 @@ const UserNotesTab = () => {
const { isOpen: showReplies, onToggle: toggleReplies } = useDisclosure();
const { isOpen: hideReposts, onToggle: toggleReposts } = useDisclosure();
- const scrollBox = useRef(null);
-
- const timeline = useMemo(() => userTimelineService.getTimeline(pubkey), [pubkey]);
const eventFilter = useCallback(
(event: NostrEvent) => {
if (!showReplies && isReply(event)) return false;
@@ -34,16 +26,17 @@ const UserNotesTab = () => {
},
[showReplies, hideReposts]
);
- useEffect(() => {
- timeline.setFilter(eventFilter);
- }, [timeline, eventFilter]);
- useEffect(() => {
- timeline.setRelays(readRelays);
- }, [timeline, readRelays.join("|")]);
-
- useMount(() => timeline.open());
- useUnmount(() => timeline.close());
+ const timeline = useTimelineLoader(
+ truncatedId(pubkey) + "-notes",
+ readRelays,
+ {
+ authors: [pubkey],
+ kinds: [1, 6],
+ },
+ { eventFilter }
+ );
+ const scrollBox = useRef(null);
const callback = useTimelineCurserIntersectionCallback(timeline);
return (
diff --git a/yarn.lock b/yarn.lock
index ac3d46fff..a68e9a64d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4278,6 +4278,11 @@ hey-listen@^1.0.8:
resolved "https://registry.yarnpkg.com/hey-listen/-/hey-listen-1.0.8.tgz#8e59561ff724908de1aa924ed6ecc84a56a9aa68"
integrity sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==
+hls.js@^1.4.7:
+ version "1.4.7"
+ resolved "https://registry.yarnpkg.com/hls.js/-/hls.js-1.4.7.tgz#a739d93ad74944eaa52493b6e37d08f042c31041"
+ integrity sha512-dvwJXLlYES6wb7DR42uuTrio5sUTsIoWbuNeQS4xHMqfVBZ0KAlJlBmjFAo4s20/0XRhsMjWf5bx0kq5Lgvv1w==
+
hoist-non-react-statics@^3.3.1:
version "3.3.2"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"