This commit is contained in:
parent
e6a42db658
commit
5baffd00b9
@ -2,5 +2,5 @@ import {ParsedFragment} from "@snort/system";
|
|||||||
import { LRUCache } from "typescript-lru-cache";
|
import { LRUCache } from "typescript-lru-cache";
|
||||||
|
|
||||||
export const TextCache = new LRUCache<string, Array<ParsedFragment>>({
|
export const TextCache = new LRUCache<string, Array<ParsedFragment>>({
|
||||||
maxSize: 1000
|
maxSize: 1000,
|
||||||
});
|
});
|
@ -8,7 +8,7 @@ import IconButton from "@/Components/Button/IconButton";
|
|||||||
import Icon from "@/Components/Icons/Icon";
|
import Icon from "@/Components/Icons/Icon";
|
||||||
import useEventPublisher from "@/Hooks/useEventPublisher";
|
import useEventPublisher from "@/Hooks/useEventPublisher";
|
||||||
import useLogin from "@/Hooks/useLogin";
|
import useLogin from "@/Hooks/useLogin";
|
||||||
import { saveRelays } from "@/Pages/settings/Relays";
|
import { saveRelays } from "@/Pages/settings/saveRelays";
|
||||||
import { getRelayName } from "@/Utils";
|
import { getRelayName } from "@/Utils";
|
||||||
import { removeRelay } from "@/Utils/Login";
|
import { removeRelay } from "@/Utils/Login";
|
||||||
|
|
||||||
|
@ -9,8 +9,8 @@ import NoteHeader from "@/Components/Event/Note/NoteHeader";
|
|||||||
import { NoteText } from "@/Components/Event/Note/NoteText";
|
import { NoteText } from "@/Components/Event/Note/NoteText";
|
||||||
import { TranslationInfo } from "@/Components/Event/Note/TranslationInfo";
|
import { TranslationInfo } from "@/Components/Event/Note/TranslationInfo";
|
||||||
import useModeration from "@/Hooks/useModeration";
|
import useModeration from "@/Hooks/useModeration";
|
||||||
import { chainKey } from "@/Hooks/useThreadContext";
|
|
||||||
import { findTag } from "@/Utils";
|
import { findTag } from "@/Utils";
|
||||||
|
import { chainKey } from "@/Utils/Thread/ChainKey";
|
||||||
|
|
||||||
import messages from "../../messages";
|
import messages from "../../messages";
|
||||||
import Text from "../../Text/Text";
|
import Text from "../../Text/Text";
|
||||||
|
@ -10,7 +10,9 @@ import BackButton from "@/Components/Button/BackButton";
|
|||||||
import Collapsed from "@/Components/Collapsed";
|
import Collapsed from "@/Components/Collapsed";
|
||||||
import Note from "@/Components/Event/EventComponent";
|
import Note from "@/Components/Event/EventComponent";
|
||||||
import NoteGhost from "@/Components/Event/Note/NoteGhost";
|
import NoteGhost from "@/Components/Event/Note/NoteGhost";
|
||||||
import { chainKey, ThreadContext, ThreadContextWrapper } from "@/Hooks/useThreadContext";
|
import { chainKey } from "@/Utils/Thread/ChainKey";
|
||||||
|
import { ThreadContext } from "@/Utils/Thread/ThreadContext";
|
||||||
|
import { ThreadContextWrapper } from "@/Utils/Thread/ThreadContextWrapper";
|
||||||
|
|
||||||
import messages from "../messages";
|
import messages from "../messages";
|
||||||
|
|
||||||
|
@ -3,8 +3,8 @@ import { NostrLink, TaggedNostrEvent } from "@snort/system";
|
|||||||
import { Thread } from "@/Components/Event/Thread";
|
import { Thread } from "@/Components/Event/Thread";
|
||||||
import Modal from "@/Components/Modal/Modal";
|
import Modal from "@/Components/Modal/Modal";
|
||||||
import { SpotlightMedia } from "@/Components/Spotlight/SpotlightMedia";
|
import { SpotlightMedia } from "@/Components/Spotlight/SpotlightMedia";
|
||||||
import { ThreadContextWrapper } from "@/Hooks/useThreadContext";
|
|
||||||
import getEventMedia from "@/Utils/getEventMedia";
|
import getEventMedia from "@/Utils/getEventMedia";
|
||||||
|
import { ThreadContextWrapper } from "@/Utils/Thread/ThreadContextWrapper";
|
||||||
|
|
||||||
interface SpotlightThreadModalProps {
|
interface SpotlightThreadModalProps {
|
||||||
thread?: NostrLink;
|
thread?: NostrLink;
|
||||||
|
@ -1,273 +0,0 @@
|
|||||||
import { unixNow } from "@snort/shared";
|
|
||||||
import { NostrLink } from "@snort/system";
|
|
||||||
import { SnortContext } from "@snort/system-react";
|
|
||||||
import { lazy, useContext, useEffect, useMemo, useState } from "react";
|
|
||||||
import { FormattedMessage } from "react-intl";
|
|
||||||
import { Link, Outlet, RouteObject, useParams } from "react-router-dom";
|
|
||||||
|
|
||||||
import Timeline from "@/Components/Feed/Timeline";
|
|
||||||
import TimelineFollows from "@/Components/Feed/TimelineFollows";
|
|
||||||
import SuggestedProfiles from "@/Components/SuggestedProfiles";
|
|
||||||
import { TaskList } from "@/Components/Tasks/TaskList";
|
|
||||||
import TrendingHashtags from "@/Components/Trending/TrendingHashtags";
|
|
||||||
import TrendingNotes from "@/Components/Trending/TrendingPosts";
|
|
||||||
import { TimelineSubject } from "@/Feed/TimelineFeed";
|
|
||||||
import useLogin from "@/Hooks/useLogin";
|
|
||||||
import { DeckContext } from "@/Pages/DeckLayout";
|
|
||||||
import Discover from "@/Pages/Discover";
|
|
||||||
import HashTagsPage from "@/Pages/HashTagsPage";
|
|
||||||
import { debounce, getCurrentRefCode, getRelayName, sha256 } from "@/Utils";
|
|
||||||
|
|
||||||
import { TopicsPage } from "./TopicsPage";
|
|
||||||
const InviteModal = lazy(() => import("@/Components/Invite"));
|
|
||||||
|
|
||||||
import useHistoryState from "@/Hooks/useHistoryState";
|
|
||||||
|
|
||||||
import messages from "./messages";
|
|
||||||
|
|
||||||
interface RelayOption {
|
|
||||||
url: string;
|
|
||||||
paid: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function RootPage() {
|
|
||||||
const code = getCurrentRefCode();
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="main-content">
|
|
||||||
<Outlet />
|
|
||||||
</div>
|
|
||||||
{code && <InviteModal />}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const FollowsHint = () => {
|
|
||||||
const { publicKey: pubKey, follows } = useLogin();
|
|
||||||
if (follows.item?.length === 0 && pubKey) {
|
|
||||||
return (
|
|
||||||
<FormattedMessage
|
|
||||||
{...messages.NoFollows}
|
|
||||||
values={{
|
|
||||||
newUsersPage: (
|
|
||||||
<Link to={"/discover"}>
|
|
||||||
<FormattedMessage {...messages.NewUsers} />
|
|
||||||
</Link>
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const GlobalTab = () => {
|
|
||||||
const { relays } = useLogin();
|
|
||||||
const [relay, setRelay] = useHistoryState<RelayOption>(undefined, "global-relay");
|
|
||||||
const [allRelays, setAllRelays] = useHistoryState<RelayOption[]>(undefined, "global-relay-options");
|
|
||||||
const [now] = useState(unixNow());
|
|
||||||
const system = useContext(SnortContext);
|
|
||||||
|
|
||||||
function globalRelaySelector() {
|
|
||||||
if (!allRelays || allRelays.length === 0) return null;
|
|
||||||
|
|
||||||
const paidRelays = allRelays.filter(a => a.paid);
|
|
||||||
const publicRelays = allRelays.filter(a => !a.paid);
|
|
||||||
return (
|
|
||||||
<div className="flex items-center g8 justify-end nowrap">
|
|
||||||
<h3>
|
|
||||||
<FormattedMessage
|
|
||||||
defaultMessage="Relay"
|
|
||||||
id="KHK8B9"
|
|
||||||
description="Label for reading global feed from specific relays"
|
|
||||||
/>
|
|
||||||
</h3>
|
|
||||||
<select
|
|
||||||
className="f-ellipsis"
|
|
||||||
onChange={e => setRelay(allRelays.find(a => a.url === e.target.value))}
|
|
||||||
value={relay?.url}>
|
|
||||||
{paidRelays.length > 0 && (
|
|
||||||
<optgroup label="Paid Relays">
|
|
||||||
{paidRelays.map(a => (
|
|
||||||
<option key={a.url} value={a.url}>
|
|
||||||
{getRelayName(a.url)}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</optgroup>
|
|
||||||
)}
|
|
||||||
<optgroup label="Public Relays">
|
|
||||||
{publicRelays.map(a => (
|
|
||||||
<option key={a.url} value={a.url}>
|
|
||||||
{getRelayName(a.url)}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</optgroup>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
return debounce(500, () => {
|
|
||||||
const ret: RelayOption[] = [];
|
|
||||||
system.Sockets.forEach(v => {
|
|
||||||
if (v.connected) {
|
|
||||||
ret.push({
|
|
||||||
url: v.address,
|
|
||||||
paid: v.info?.limitation?.payment_required ?? false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
ret.sort(a => (a.paid ? -1 : 1));
|
|
||||||
|
|
||||||
if (ret.length > 0 && !relay) {
|
|
||||||
setRelay(ret[0]);
|
|
||||||
}
|
|
||||||
setAllRelays(ret);
|
|
||||||
});
|
|
||||||
}, [relays, relay]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{globalRelaySelector()}
|
|
||||||
{relay && (
|
|
||||||
<Timeline
|
|
||||||
subject={{
|
|
||||||
type: "global",
|
|
||||||
items: [],
|
|
||||||
relay: [relay.url],
|
|
||||||
discriminator: `all-${sha256(relay.url)}`,
|
|
||||||
}}
|
|
||||||
postsOnly={false}
|
|
||||||
method={"TIME_RANGE"}
|
|
||||||
window={600}
|
|
||||||
now={now}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const FollowedByFriendsTab = () => {
|
|
||||||
const { publicKey } = useLogin();
|
|
||||||
const subject: TimelineSubject = {
|
|
||||||
type: "global",
|
|
||||||
items: [],
|
|
||||||
discriminator: `followed-by-friends-${publicKey}`,
|
|
||||||
streams: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
return <Timeline followDistance={2} subject={subject} postsOnly={true} method={"TIME_RANGE"} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const NotesTab = () => {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
const deckContext = useContext(DeckContext);
|
|
||||||
|
|
||||||
const noteOnClick = useMemo(() => {
|
|
||||||
if (deckContext) {
|
|
||||||
return ev => {
|
|
||||||
deckContext.setThread(NostrLink.fromEvent(ev));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}, [deckContext]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<FollowsHint />
|
|
||||||
<TaskList />
|
|
||||||
<TimelineFollows postsOnly={true} noteOnClick={noteOnClick} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ConversationsTab = () => {
|
|
||||||
return <TimelineFollows postsOnly={false} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const TagsTab = (params: { tag?: string }) => {
|
|
||||||
const { tag } = useParams();
|
|
||||||
const t = params.tag ?? tag ?? "";
|
|
||||||
const subject: TimelineSubject = {
|
|
||||||
type: "hashtag",
|
|
||||||
items: [t],
|
|
||||||
discriminator: `tags-${t}`,
|
|
||||||
streams: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
return <Timeline subject={subject} postsOnly={false} method={"TIME_RANGE"} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
const DefaultTab = () => {
|
|
||||||
const { preferences, publicKey } = useLogin(s => ({
|
|
||||||
preferences: s.appData.item.preferences,
|
|
||||||
publicKey: s.publicKey,
|
|
||||||
}));
|
|
||||||
const tab = publicKey ? preferences.defaultRootTab ?? `notes` : `trending/notes`;
|
|
||||||
const elm = RootTabRoutes.find(a => a.path === tab)?.element;
|
|
||||||
return elm;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const RootTabRoutes = [
|
|
||||||
{
|
|
||||||
path: "",
|
|
||||||
element: <DefaultTab />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "global",
|
|
||||||
element: <GlobalTab />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "notes",
|
|
||||||
element: <NotesTab />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "followed-by-friends",
|
|
||||||
element: <FollowedByFriendsTab />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "conversations",
|
|
||||||
element: <ConversationsTab />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "discover",
|
|
||||||
element: <Discover />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "tag/:tag",
|
|
||||||
element: <TagsTab />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "trending/notes",
|
|
||||||
element: <TrendingNotes />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "trending/hashtags",
|
|
||||||
element: <TrendingHashtags />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "suggested",
|
|
||||||
element: (
|
|
||||||
<div className="p">
|
|
||||||
<SuggestedProfiles />
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "t/:tag",
|
|
||||||
element: <HashTagsPage />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "topics",
|
|
||||||
element: <TopicsPage />,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const RootRoutes = [
|
|
||||||
{
|
|
||||||
path: "/",
|
|
||||||
element: <RootPage />,
|
|
||||||
children: RootTabRoutes,
|
|
||||||
},
|
|
||||||
] as RouteObject[];
|
|
5
packages/app/src/Pages/Root/ConversationsTab.tsx
Normal file
5
packages/app/src/Pages/Root/ConversationsTab.tsx
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import TimelineFollows from "@/Components/Feed/TimelineFollows";
|
||||||
|
|
||||||
|
export const ConversationsTab = () => {
|
||||||
|
return <TimelineFollows postsOnly={false} />;
|
||||||
|
};
|
12
packages/app/src/Pages/Root/DefaultTab.tsx
Normal file
12
packages/app/src/Pages/Root/DefaultTab.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import useLogin from "@/Hooks/useLogin";
|
||||||
|
import { RootTabRoutes } from "@/Pages/Root/RootTabRoutes";
|
||||||
|
|
||||||
|
export const DefaultTab = () => {
|
||||||
|
const { preferences, publicKey } = useLogin(s => ({
|
||||||
|
preferences: s.appData.item.preferences,
|
||||||
|
publicKey: s.publicKey,
|
||||||
|
}));
|
||||||
|
const tab = publicKey ? preferences.defaultRootTab ?? `notes` : `trending/notes`;
|
||||||
|
const elm = RootTabRoutes.find(a => a.path === tab)?.element;
|
||||||
|
return elm;
|
||||||
|
};
|
15
packages/app/src/Pages/Root/FollowedByFriendsTab.tsx
Normal file
15
packages/app/src/Pages/Root/FollowedByFriendsTab.tsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import Timeline from "@/Components/Feed/Timeline";
|
||||||
|
import { TimelineSubject } from "@/Feed/TimelineFeed";
|
||||||
|
import useLogin from "@/Hooks/useLogin";
|
||||||
|
|
||||||
|
export const FollowedByFriendsTab = () => {
|
||||||
|
const { publicKey } = useLogin();
|
||||||
|
const subject: TimelineSubject = {
|
||||||
|
type: "global",
|
||||||
|
items: [],
|
||||||
|
discriminator: `followed-by-friends-${publicKey}`,
|
||||||
|
streams: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
return <Timeline followDistance={2} subject={subject} postsOnly={true} method={"TIME_RANGE"} />;
|
||||||
|
};
|
101
packages/app/src/Pages/Root/GlobalTab.tsx
Normal file
101
packages/app/src/Pages/Root/GlobalTab.tsx
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
import { unixNow } from "@snort/shared";
|
||||||
|
import { SnortContext } from "@snort/system-react";
|
||||||
|
import { useContext, useEffect, useState } from "react";
|
||||||
|
import { FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
|
import Timeline from "@/Components/Feed/Timeline";
|
||||||
|
import useHistoryState from "@/Hooks/useHistoryState";
|
||||||
|
import useLogin from "@/Hooks/useLogin";
|
||||||
|
import { debounce, getRelayName, sha256 } from "@/Utils";
|
||||||
|
|
||||||
|
interface RelayOption {
|
||||||
|
url: string;
|
||||||
|
paid: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GlobalTab = () => {
|
||||||
|
const { relays } = useLogin();
|
||||||
|
const [relay, setRelay] = useHistoryState<RelayOption>(undefined, "global-relay");
|
||||||
|
const [allRelays, setAllRelays] = useHistoryState<RelayOption[]>(undefined, "global-relay-options");
|
||||||
|
const [now] = useState(unixNow());
|
||||||
|
const system = useContext(SnortContext);
|
||||||
|
|
||||||
|
function globalRelaySelector() {
|
||||||
|
if (!allRelays || allRelays.length === 0) return null;
|
||||||
|
|
||||||
|
const paidRelays = allRelays.filter(a => a.paid);
|
||||||
|
const publicRelays = allRelays.filter(a => !a.paid);
|
||||||
|
return (
|
||||||
|
<div className="flex items-center g8 justify-end nowrap">
|
||||||
|
<h3>
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="Relay"
|
||||||
|
id="KHK8B9"
|
||||||
|
description="Label for reading global feed from specific relays"
|
||||||
|
/>
|
||||||
|
</h3>
|
||||||
|
<select
|
||||||
|
className="f-ellipsis"
|
||||||
|
onChange={e => setRelay(allRelays.find(a => a.url === e.target.value))}
|
||||||
|
value={relay?.url}>
|
||||||
|
{paidRelays.length > 0 && (
|
||||||
|
<optgroup label="Paid Relays">
|
||||||
|
{paidRelays.map(a => (
|
||||||
|
<option key={a.url} value={a.url}>
|
||||||
|
{getRelayName(a.url)}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</optgroup>
|
||||||
|
)}
|
||||||
|
<optgroup label="Public Relays">
|
||||||
|
{publicRelays.map(a => (
|
||||||
|
<option key={a.url} value={a.url}>
|
||||||
|
{getRelayName(a.url)}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</optgroup>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return debounce(500, () => {
|
||||||
|
const ret: RelayOption[] = [];
|
||||||
|
system.Sockets.forEach(v => {
|
||||||
|
if (v.connected) {
|
||||||
|
ret.push({
|
||||||
|
url: v.address,
|
||||||
|
paid: v.info?.limitation?.payment_required ?? false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ret.sort(a => (a.paid ? -1 : 1));
|
||||||
|
|
||||||
|
if (ret.length > 0 && !relay) {
|
||||||
|
setRelay(ret[0]);
|
||||||
|
}
|
||||||
|
setAllRelays(ret);
|
||||||
|
});
|
||||||
|
}, [relays, relay]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{globalRelaySelector()}
|
||||||
|
{relay && (
|
||||||
|
<Timeline
|
||||||
|
subject={{
|
||||||
|
type: "global",
|
||||||
|
items: [],
|
||||||
|
relay: [relay.url],
|
||||||
|
discriminator: `all-${sha256(relay.url)}`,
|
||||||
|
}}
|
||||||
|
postsOnly={false}
|
||||||
|
method={"TIME_RANGE"}
|
||||||
|
window={600}
|
||||||
|
now={now}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
50
packages/app/src/Pages/Root/NotesTab.tsx
Normal file
50
packages/app/src/Pages/Root/NotesTab.tsx
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { NostrLink } from "@snort/system";
|
||||||
|
import { useContext, useMemo } from "react";
|
||||||
|
import { FormattedMessage } from "react-intl";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
|
import TimelineFollows from "@/Components/Feed/TimelineFollows";
|
||||||
|
import { TaskList } from "@/Components/Tasks/TaskList";
|
||||||
|
import useLogin from "@/Hooks/useLogin";
|
||||||
|
import { DeckContext } from "@/Pages/DeckLayout";
|
||||||
|
import messages from "@/Pages/messages";
|
||||||
|
|
||||||
|
const FollowsHint = () => {
|
||||||
|
const { publicKey: pubKey, follows } = useLogin();
|
||||||
|
if (follows.item?.length === 0 && pubKey) {
|
||||||
|
return (
|
||||||
|
<FormattedMessage
|
||||||
|
{...messages.NoFollows}
|
||||||
|
values={{
|
||||||
|
newUsersPage: (
|
||||||
|
<Link to={"/discover"}>
|
||||||
|
<FormattedMessage {...messages.NewUsers} />
|
||||||
|
</Link>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
export const NotesTab = () => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const deckContext = useContext(DeckContext);
|
||||||
|
|
||||||
|
const noteOnClick = useMemo(() => {
|
||||||
|
if (deckContext) {
|
||||||
|
return ev => {
|
||||||
|
deckContext.setThread(NostrLink.fromEvent(ev));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}, [deckContext]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<FollowsHint />
|
||||||
|
<TaskList />
|
||||||
|
<TimelineFollows postsOnly={true} noteOnClick={noteOnClick} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
25
packages/app/src/Pages/Root/RootRoutes.tsx
Normal file
25
packages/app/src/Pages/Root/RootRoutes.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { lazy } from "react";
|
||||||
|
import { Outlet, RouteObject } from "react-router-dom";
|
||||||
|
|
||||||
|
import { RootTabRoutes } from "@/Pages/Root/RootTabRoutes";
|
||||||
|
import { getCurrentRefCode } from "@/Utils";
|
||||||
|
|
||||||
|
const InviteModal = lazy(() => import("@/Components/Invite"));
|
||||||
|
export default function RootPage() {
|
||||||
|
const code = getCurrentRefCode();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="main-content">
|
||||||
|
<Outlet />
|
||||||
|
</div>
|
||||||
|
{code && <InviteModal />}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export const RootRoutes = [
|
||||||
|
{
|
||||||
|
path: "/",
|
||||||
|
element: <RootPage />,
|
||||||
|
children: RootTabRoutes,
|
||||||
|
},
|
||||||
|
] as RouteObject[];
|
67
packages/app/src/Pages/Root/RootTabRoutes.tsx
Normal file
67
packages/app/src/Pages/Root/RootTabRoutes.tsx
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import SuggestedProfiles from "@/Components/SuggestedProfiles";
|
||||||
|
import TrendingHashtags from "@/Components/Trending/TrendingHashtags";
|
||||||
|
import TrendingNotes from "@/Components/Trending/TrendingPosts";
|
||||||
|
import Discover from "@/Pages/Discover";
|
||||||
|
import HashTagsPage from "@/Pages/HashTagsPage";
|
||||||
|
import { ConversationsTab } from "@/Pages/Root/ConversationsTab";
|
||||||
|
import { DefaultTab } from "@/Pages/Root/DefaultTab";
|
||||||
|
import { FollowedByFriendsTab } from "@/Pages/Root/FollowedByFriendsTab";
|
||||||
|
import { GlobalTab } from "@/Pages/Root/GlobalTab";
|
||||||
|
import { NotesTab } from "@/Pages/Root/NotesTab";
|
||||||
|
import { TagsTab } from "@/Pages/Root/TagsTab";
|
||||||
|
import { TopicsPage } from "@/Pages/TopicsPage";
|
||||||
|
|
||||||
|
export const RootTabRoutes = [
|
||||||
|
{
|
||||||
|
path: "",
|
||||||
|
element: <DefaultTab />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "global",
|
||||||
|
element: <GlobalTab />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "notes",
|
||||||
|
element: <NotesTab />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "followed-by-friends",
|
||||||
|
element: <FollowedByFriendsTab />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "conversations",
|
||||||
|
element: <ConversationsTab />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "discover",
|
||||||
|
element: <Discover />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "tag/:tag",
|
||||||
|
element: <TagsTab />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "trending/notes",
|
||||||
|
element: <TrendingNotes />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "trending/hashtags",
|
||||||
|
element: <TrendingHashtags />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "suggested",
|
||||||
|
element: (
|
||||||
|
<div className="p">
|
||||||
|
<SuggestedProfiles />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "t/:tag",
|
||||||
|
element: <HashTagsPage />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "topics",
|
||||||
|
element: <TopicsPage />,
|
||||||
|
},
|
||||||
|
];
|
17
packages/app/src/Pages/Root/TagsTab.tsx
Normal file
17
packages/app/src/Pages/Root/TagsTab.tsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
|
||||||
|
import Timeline from "@/Components/Feed/Timeline";
|
||||||
|
import { TimelineSubject } from "@/Feed/TimelineFeed";
|
||||||
|
|
||||||
|
export const TagsTab = (params: { tag?: string }) => {
|
||||||
|
const { tag } = useParams();
|
||||||
|
const t = params.tag ?? tag ?? "";
|
||||||
|
const subject: TimelineSubject = {
|
||||||
|
type: "hashtag",
|
||||||
|
items: [t],
|
||||||
|
discriminator: `tags-${t}`,
|
||||||
|
streams: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
return <Timeline subject={subject} postsOnly={false} method={"TIME_RANGE"} />;
|
||||||
|
};
|
@ -1,5 +1,4 @@
|
|||||||
import { unixNowMs, unwrap } from "@snort/shared";
|
import { unixNowMs, unwrap } from "@snort/shared";
|
||||||
import { EventPublisher, FullRelaySettings, RelaySettings, SystemInterface } from "@snort/system";
|
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
@ -8,25 +7,13 @@ import Relay from "@/Components/Relay/Relay";
|
|||||||
import SnortApi, { RelayDistance } from "@/External/SnortApi";
|
import SnortApi, { RelayDistance } from "@/External/SnortApi";
|
||||||
import useEventPublisher from "@/Hooks/useEventPublisher";
|
import useEventPublisher from "@/Hooks/useEventPublisher";
|
||||||
import useLogin from "@/Hooks/useLogin";
|
import useLogin from "@/Hooks/useLogin";
|
||||||
|
import { saveRelays } from "@/Pages/settings/saveRelays";
|
||||||
import { getCountry, getRelayName, sanitizeRelayUrl } from "@/Utils";
|
import { getCountry, getRelayName, sanitizeRelayUrl } from "@/Utils";
|
||||||
import { Blasters } from "@/Utils/Const";
|
|
||||||
import { setRelays } from "@/Utils/Login";
|
import { setRelays } from "@/Utils/Login";
|
||||||
import { formatShort } from "@/Utils/Number";
|
import { formatShort } from "@/Utils/Number";
|
||||||
|
|
||||||
import messages from "./messages";
|
import messages from "./messages";
|
||||||
|
|
||||||
export async function saveRelays(
|
|
||||||
system: SystemInterface,
|
|
||||||
publisher: EventPublisher | undefined,
|
|
||||||
relays: Array<FullRelaySettings> | Record<string, RelaySettings>,
|
|
||||||
) {
|
|
||||||
if (publisher) {
|
|
||||||
const ev = await publisher.relayList(relays);
|
|
||||||
await system.BroadcastEvent(ev);
|
|
||||||
await Promise.all(Blasters.map(a => system.WriteOnceToRelay(a, ev)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const RelaySettingsPage = () => {
|
const RelaySettingsPage = () => {
|
||||||
const { publisher, system } = useEventPublisher();
|
const { publisher, system } = useEventPublisher();
|
||||||
const login = useLogin();
|
const login = useLogin();
|
||||||
|
@ -16,18 +16,14 @@ import Relay from "@/Pages/settings/Relays";
|
|||||||
import { ToolsPage, ToolsPages } from "./tools";
|
import { ToolsPage, ToolsPages } from "./tools";
|
||||||
import { WalletSettingsRoutes } from "./wallet";
|
import { WalletSettingsRoutes } from "./wallet";
|
||||||
|
|
||||||
const SettingsPage = () => {
|
|
||||||
return (
|
|
||||||
<div className="px-3">
|
|
||||||
<Outlet />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
path: "/settings",
|
path: "/settings",
|
||||||
element: <SettingsPage />,
|
element: (
|
||||||
|
<div className="px-3">
|
||||||
|
<Outlet />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: "",
|
path: "",
|
||||||
|
15
packages/app/src/Pages/settings/saveRelays.tsx
Normal file
15
packages/app/src/Pages/settings/saveRelays.tsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { EventPublisher, FullRelaySettings, RelaySettings, SystemInterface } from "@snort/system";
|
||||||
|
|
||||||
|
import { Blasters } from "@/Utils/Const";
|
||||||
|
|
||||||
|
export async function saveRelays(
|
||||||
|
system: SystemInterface,
|
||||||
|
publisher: EventPublisher | undefined,
|
||||||
|
relays: Array<FullRelaySettings> | Record<string, RelaySettings>,
|
||||||
|
) {
|
||||||
|
if (publisher) {
|
||||||
|
const ev = await publisher.relayList(relays);
|
||||||
|
await system.BroadcastEvent(ev);
|
||||||
|
await Promise.all(Blasters.map(a => system.WriteOnceToRelay(a, ev)));
|
||||||
|
}
|
||||||
|
}
|
18
packages/app/src/Utils/Thread/ChainKey.tsx
Normal file
18
packages/app/src/Utils/Thread/ChainKey.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { unwrap } from "@snort/shared";
|
||||||
|
import { EventExt, NostrLink, TaggedNostrEvent } from "@snort/system";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the chain key as a reply event
|
||||||
|
*/
|
||||||
|
export function replyChainKey(ev: TaggedNostrEvent) {
|
||||||
|
const t = EventExt.extractThread(ev);
|
||||||
|
return t?.replyTo?.value ?? t?.root?.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the chain key of this event
|
||||||
|
*/
|
||||||
|
export function chainKey(ev: TaggedNostrEvent) {
|
||||||
|
const link = NostrLink.fromEvent(ev);
|
||||||
|
return unwrap(link.toEventTag())[1];
|
||||||
|
}
|
14
packages/app/src/Utils/Thread/ThreadContext.tsx
Normal file
14
packages/app/src/Utils/Thread/ThreadContext.tsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/* eslint-disable no-debugger */
|
||||||
|
import { TaggedNostrEvent } from "@snort/system";
|
||||||
|
import { createContext } from "react";
|
||||||
|
|
||||||
|
interface ThreadContext {
|
||||||
|
current: string;
|
||||||
|
root?: TaggedNostrEvent;
|
||||||
|
chains: Map<string, Array<TaggedNostrEvent>>;
|
||||||
|
data: Array<TaggedNostrEvent>;
|
||||||
|
reactions: Array<TaggedNostrEvent>;
|
||||||
|
setCurrent: (i: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ThreadContext = createContext({} as ThreadContext);
|
@ -1,39 +1,12 @@
|
|||||||
/* eslint-disable no-debugger */
|
|
||||||
import { unwrap } from "@snort/shared";
|
import { unwrap } from "@snort/shared";
|
||||||
import { EventExt, NostrLink, TaggedNostrEvent, u256 } from "@snort/system";
|
import { NostrLink, TaggedNostrEvent, u256 } from "@snort/system";
|
||||||
import { createContext, ReactNode, useMemo, useState } from "react";
|
import { ReactNode, useMemo, useState } from "react";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
|
|
||||||
import useThreadFeed from "@/Feed/ThreadFeed";
|
import useThreadFeed from "@/Feed/ThreadFeed";
|
||||||
|
import useModeration from "@/Hooks/useModeration";
|
||||||
import useModeration from "./useModeration";
|
import { chainKey, replyChainKey } from "@/Utils/Thread/ChainKey";
|
||||||
|
import { ThreadContext } from "@/Utils/Thread/ThreadContext";
|
||||||
export interface ThreadContext {
|
|
||||||
current: string;
|
|
||||||
root?: TaggedNostrEvent;
|
|
||||||
chains: Map<string, Array<TaggedNostrEvent>>;
|
|
||||||
data: Array<TaggedNostrEvent>;
|
|
||||||
reactions: Array<TaggedNostrEvent>;
|
|
||||||
setCurrent: (i: string) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ThreadContext = createContext({} as ThreadContext);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the chain key as a reply event
|
|
||||||
*/
|
|
||||||
export function replyChainKey(ev: TaggedNostrEvent) {
|
|
||||||
const t = EventExt.extractThread(ev);
|
|
||||||
return t?.replyTo?.value ?? t?.root?.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the chain key of this event
|
|
||||||
*/
|
|
||||||
export function chainKey(ev: TaggedNostrEvent) {
|
|
||||||
const link = NostrLink.fromEvent(ev);
|
|
||||||
return unwrap(link.toEventTag())[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ThreadContextWrapper({ link, children }: { link: NostrLink; children?: ReactNode }) {
|
export function ThreadContextWrapper({ link, children }: { link: NostrLink; children?: ReactNode }) {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
@ -29,7 +29,8 @@ import NostrLinkHandler from "@/Pages/NostrLinkHandler";
|
|||||||
import NotificationsPage from "@/Pages/Notifications/Notifications";
|
import NotificationsPage from "@/Pages/Notifications/Notifications";
|
||||||
import { OnboardingRoutes } from "@/Pages/onboarding";
|
import { OnboardingRoutes } from "@/Pages/onboarding";
|
||||||
import ProfilePage from "@/Pages/Profile/ProfilePage";
|
import ProfilePage from "@/Pages/Profile/ProfilePage";
|
||||||
import { RootRoutes, RootTabRoutes } from "@/Pages/Root";
|
import { RootRoutes } from "@/Pages/Root/RootRoutes";
|
||||||
|
import { RootTabRoutes } from "@/Pages/Root/RootTabRoutes";
|
||||||
import SearchPage from "@/Pages/SearchPage";
|
import SearchPage from "@/Pages/SearchPage";
|
||||||
import SettingsRoutes from "@/Pages/settings/Routes";
|
import SettingsRoutes from "@/Pages/settings/Routes";
|
||||||
import { SubscribeRoutes } from "@/Pages/subscribe";
|
import { SubscribeRoutes } from "@/Pages/subscribe";
|
||||||
|
Loading…
Reference in New Issue
Block a user