mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-10-08 20:09:17 +02:00
fixes for web of trust
This commit is contained in:
@@ -103,6 +103,7 @@ export default class AccountNotifications {
|
|||||||
for (const event of sorted) {
|
for (const event of sorted) {
|
||||||
if (!Object.hasOwn(event, typeSymbol)) continue;
|
if (!Object.hasOwn(event, typeSymbol)) continue;
|
||||||
if (mutedPubkeys.includes(event.pubkey)) continue;
|
if (mutedPubkeys.includes(event.pubkey)) continue;
|
||||||
|
if (event.pubkey === this.pubkey) continue;
|
||||||
const e = event as CategorizedEvent;
|
const e = event as CategorizedEvent;
|
||||||
|
|
||||||
switch (e[typeSymbol]) {
|
switch (e[typeSymbol]) {
|
||||||
|
@@ -1,8 +1,11 @@
|
|||||||
import { NostrEvent } from "nostr-tools";
|
import { NostrEvent } from "nostr-tools";
|
||||||
|
import _throttle from "lodash.throttle";
|
||||||
|
import { logger } from "../helpers/debug";
|
||||||
|
|
||||||
export class PubkeyGraph {
|
export class PubkeyGraph {
|
||||||
/** the pubkey at the center of it all */
|
/** the pubkey at the center of it all */
|
||||||
root: string;
|
root: string;
|
||||||
|
log = logger.extend("PubkeyGraph");
|
||||||
|
|
||||||
connections = new Map<string, string[]>();
|
connections = new Map<string, string[]>();
|
||||||
distance = new Map<string, number>();
|
distance = new Map<string, number>();
|
||||||
@@ -87,6 +90,8 @@ export class PubkeyGraph {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throttleCompute = _throttle(this.compute.bind(this), 5_000, { leading: false });
|
||||||
|
|
||||||
changed = new Set<string>();
|
changed = new Set<string>();
|
||||||
compute() {
|
compute() {
|
||||||
this.distance.clear();
|
this.distance.clear();
|
||||||
|
@@ -14,7 +14,7 @@ import { Emoji, useContextEmojis } from "../providers/global/emoji-provider";
|
|||||||
import { useUserSearchDirectoryContext } from "../providers/global/user-directory-provider";
|
import { useUserSearchDirectoryContext } from "../providers/global/user-directory-provider";
|
||||||
import UserAvatar from "./user/user-avatar";
|
import UserAvatar from "./user/user-avatar";
|
||||||
import UserDnsIdentity from "./user/user-dns-identity";
|
import UserDnsIdentity from "./user/user-dns-identity";
|
||||||
import { getWebOfTrust } from "../services/web-of-trust";
|
import { useWebOfTrust } from "../providers/global/web-of-trust-provider";
|
||||||
|
|
||||||
export type PeopleToken = { pubkey: string; names: string[] };
|
export type PeopleToken = { pubkey: string; names: string[] };
|
||||||
type Token = Emoji | PeopleToken;
|
type Token = Emoji | PeopleToken;
|
||||||
@@ -60,6 +60,7 @@ const Loading: ReactTextareaAutocompleteProps<
|
|||||||
>["loadingComponent"] = ({ data }) => <div>Loading</div>;
|
>["loadingComponent"] = ({ data }) => <div>Loading</div>;
|
||||||
|
|
||||||
function useAutocompleteTriggers() {
|
function useAutocompleteTriggers() {
|
||||||
|
const webOfTrust = useWebOfTrust();
|
||||||
const emojis = useContextEmojis();
|
const emojis = useContextEmojis();
|
||||||
const getDirectory = useUserSearchDirectoryContext();
|
const getDirectory = useUserSearchDirectoryContext();
|
||||||
|
|
||||||
@@ -77,10 +78,12 @@ function useAutocompleteTriggers() {
|
|||||||
return matchSorter(dir, token.trim(), {
|
return matchSorter(dir, token.trim(), {
|
||||||
keys: ["names"],
|
keys: ["names"],
|
||||||
sorter: (items) =>
|
sorter: (items) =>
|
||||||
getWebOfTrust().sortByDistanceAndConnections(
|
webOfTrust
|
||||||
items.sort((a, b) => b.rank - a.rank),
|
? webOfTrust.sortByDistanceAndConnections(
|
||||||
(i) => i.item.pubkey,
|
items.sort((a, b) => b.rank - a.rank),
|
||||||
),
|
(i) => i.item.pubkey,
|
||||||
|
)
|
||||||
|
: items,
|
||||||
}).slice(0, 10);
|
}).slice(0, 10);
|
||||||
},
|
},
|
||||||
component: Item,
|
component: Item,
|
||||||
|
@@ -4,7 +4,6 @@ import { App } from "./app";
|
|||||||
import { GlobalProviders } from "./providers/global";
|
import { GlobalProviders } from "./providers/global";
|
||||||
import "./services/user-event-sync";
|
import "./services/user-event-sync";
|
||||||
import "./services/username-search";
|
import "./services/username-search";
|
||||||
import "./services/web-of-trust";
|
|
||||||
|
|
||||||
// setup bitcoin connect
|
// setup bitcoin connect
|
||||||
import { init, onConnected } from "@getalby/bitcoin-connect-react";
|
import { init, onConnected } from "@getalby/bitcoin-connect-react";
|
||||||
|
@@ -11,6 +11,7 @@ import BreakpointProvider from "./breakpoint-provider";
|
|||||||
import DecryptionProvider from "./dycryption-provider";
|
import DecryptionProvider from "./dycryption-provider";
|
||||||
import DMTimelineProvider from "./dms-provider";
|
import DMTimelineProvider from "./dms-provider";
|
||||||
import PublishProvider from "./publish-provider";
|
import PublishProvider from "./publish-provider";
|
||||||
|
import WebOfTrustProvider from "./web-of-trust-provider";
|
||||||
|
|
||||||
// Top level providers, should be render as close to the root as possible
|
// Top level providers, should be render as close to the root as possible
|
||||||
export const GlobalProviders = ({ children }: { children: React.ReactNode }) => {
|
export const GlobalProviders = ({ children }: { children: React.ReactNode }) => {
|
||||||
@@ -30,7 +31,9 @@ export const GlobalProviders = ({ children }: { children: React.ReactNode }) =>
|
|||||||
<DMTimelineProvider>
|
<DMTimelineProvider>
|
||||||
<DefaultEmojiProvider>
|
<DefaultEmojiProvider>
|
||||||
<UserEmojiProvider>
|
<UserEmojiProvider>
|
||||||
<AllUserSearchDirectoryProvider>{children}</AllUserSearchDirectoryProvider>
|
<AllUserSearchDirectoryProvider>
|
||||||
|
<WebOfTrustProvider>{children}</WebOfTrustProvider>
|
||||||
|
</AllUserSearchDirectoryProvider>
|
||||||
</UserEmojiProvider>
|
</UserEmojiProvider>
|
||||||
</DefaultEmojiProvider>
|
</DefaultEmojiProvider>
|
||||||
</DMTimelineProvider>
|
</DMTimelineProvider>
|
||||||
|
77
src/providers/global/web-of-trust-provider.tsx
Normal file
77
src/providers/global/web-of-trust-provider.tsx
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import { PropsWithChildren, createContext, useContext, useEffect, useMemo } from "react";
|
||||||
|
import { NostrEvent, kinds } from "nostr-tools";
|
||||||
|
import _throttle from "lodash.throttle";
|
||||||
|
|
||||||
|
import { getPubkeysFromList } from "../../helpers/nostr/lists";
|
||||||
|
import useCurrentAccount from "../../hooks/use-current-account";
|
||||||
|
import { PubkeyGraph } from "../../classes/pubkey-graph";
|
||||||
|
import replaceableEventsService from "../../services/replaceable-events";
|
||||||
|
import { COMMON_CONTACT_RELAY } from "../../const";
|
||||||
|
|
||||||
|
export function loadSocialGraph(
|
||||||
|
graph: PubkeyGraph,
|
||||||
|
kind: number,
|
||||||
|
pubkey: string,
|
||||||
|
relay?: string,
|
||||||
|
maxLvl = 0,
|
||||||
|
walked: Set<string> = new Set(),
|
||||||
|
) {
|
||||||
|
let newEvents = 0;
|
||||||
|
|
||||||
|
const contacts = replaceableEventsService.requestEvent(
|
||||||
|
relay ? [relay, COMMON_CONTACT_RELAY] : [COMMON_CONTACT_RELAY],
|
||||||
|
kind,
|
||||||
|
pubkey,
|
||||||
|
);
|
||||||
|
|
||||||
|
walked.add(pubkey);
|
||||||
|
|
||||||
|
const handleEvent = (event: NostrEvent) => {
|
||||||
|
graph.handleEvent(event);
|
||||||
|
newEvents++;
|
||||||
|
graph.throttleCompute();
|
||||||
|
|
||||||
|
if (maxLvl > 0) {
|
||||||
|
for (const person of getPubkeysFromList(event)) {
|
||||||
|
if (walked.has(person.pubkey)) continue;
|
||||||
|
|
||||||
|
loadSocialGraph(graph, kind, person.pubkey, person.relay, maxLvl - 1, walked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (contacts.value) {
|
||||||
|
handleEvent(contacts.value);
|
||||||
|
} else {
|
||||||
|
contacts.once((event) => handleEvent(event));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const WebOfTrustContext = createContext<PubkeyGraph | null>(null);
|
||||||
|
|
||||||
|
export function useWebOfTrust() {
|
||||||
|
return useContext(WebOfTrustContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function WebOfTrustProvider({ pubkey, children }: PropsWithChildren<{ pubkey?: string }>) {
|
||||||
|
const account = useCurrentAccount();
|
||||||
|
if (account && !pubkey) pubkey = account.pubkey;
|
||||||
|
|
||||||
|
const graph = useMemo(() => {
|
||||||
|
return pubkey ? new PubkeyGraph(pubkey) : null;
|
||||||
|
}, [pubkey]);
|
||||||
|
|
||||||
|
// load the graph when it changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (!graph) return;
|
||||||
|
|
||||||
|
if (import.meta.env.DEV) {
|
||||||
|
//@ts-expect-error
|
||||||
|
window.webOfTrust = graph;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadSocialGraph(graph, kinds.Contacts, graph.root, undefined, 1);
|
||||||
|
}, [graph]);
|
||||||
|
|
||||||
|
return <WebOfTrustContext.Provider value={graph}>{children}</WebOfTrustContext.Provider>;
|
||||||
|
}
|
@@ -1,73 +0,0 @@
|
|||||||
import { NostrEvent, kinds } from "nostr-tools";
|
|
||||||
import _throttle from "lodash.throttle";
|
|
||||||
|
|
||||||
import { PubkeyGraph } from "../classes/pubkey-graph";
|
|
||||||
import { COMMON_CONTACT_RELAY } from "../const";
|
|
||||||
import { logger } from "../helpers/debug";
|
|
||||||
import accountService from "./account";
|
|
||||||
import replaceableEventsService from "./replaceable-events";
|
|
||||||
import { getPubkeysFromList } from "../helpers/nostr/lists";
|
|
||||||
|
|
||||||
const log = logger.extend("web-of-trust");
|
|
||||||
let webOfTrust = new PubkeyGraph("");
|
|
||||||
|
|
||||||
let newEvents = 0;
|
|
||||||
const throttleUpdateWebOfTrust = _throttle(() => {
|
|
||||||
log("Computing web-of-trust with", newEvents, "new events");
|
|
||||||
webOfTrust.compute();
|
|
||||||
newEvents = 0;
|
|
||||||
}, 5_000);
|
|
||||||
|
|
||||||
export function loadSocialGraph(
|
|
||||||
web: PubkeyGraph,
|
|
||||||
kind: number,
|
|
||||||
pubkey: string,
|
|
||||||
relay?: string,
|
|
||||||
maxLvl = 0,
|
|
||||||
walked: Set<string> = new Set(),
|
|
||||||
) {
|
|
||||||
const contacts = replaceableEventsService.requestEvent(
|
|
||||||
relay ? [relay, COMMON_CONTACT_RELAY] : [COMMON_CONTACT_RELAY],
|
|
||||||
kind,
|
|
||||||
pubkey,
|
|
||||||
);
|
|
||||||
|
|
||||||
walked.add(pubkey);
|
|
||||||
|
|
||||||
const handleEvent = (event: NostrEvent) => {
|
|
||||||
web.handleEvent(event);
|
|
||||||
newEvents++;
|
|
||||||
throttleUpdateWebOfTrust();
|
|
||||||
|
|
||||||
if (maxLvl > 0) {
|
|
||||||
for (const person of getPubkeysFromList(event)) {
|
|
||||||
if (walked.has(person.pubkey)) continue;
|
|
||||||
|
|
||||||
loadSocialGraph(web, kind, person.pubkey, person.relay, maxLvl - 1, walked);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (contacts.value) {
|
|
||||||
handleEvent(contacts.value);
|
|
||||||
} else {
|
|
||||||
contacts.once((event) => handleEvent(event));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
accountService.current.subscribe((account) => {
|
|
||||||
if (!account) return;
|
|
||||||
|
|
||||||
webOfTrust = new PubkeyGraph(account.pubkey);
|
|
||||||
|
|
||||||
if (import.meta.env.DEV) {
|
|
||||||
//@ts-expect-error
|
|
||||||
window.webOfTrust = webOfTrust;
|
|
||||||
}
|
|
||||||
|
|
||||||
loadSocialGraph(webOfTrust, kinds.Contacts, account.pubkey, undefined, 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
export function getWebOfTrust() {
|
|
||||||
return webOfTrust;
|
|
||||||
}
|
|
@@ -10,7 +10,7 @@ import { useUserSearchDirectoryContext } from "../../../providers/global/user-di
|
|||||||
import UserAvatar from "../../../components/user/user-avatar";
|
import UserAvatar from "../../../components/user/user-avatar";
|
||||||
import UserName from "../../../components/user/user-name";
|
import UserName from "../../../components/user/user-name";
|
||||||
import KeyboardShortcut from "../../../components/keyboard-shortcut";
|
import KeyboardShortcut from "../../../components/keyboard-shortcut";
|
||||||
import { getWebOfTrust } from "../../../services/web-of-trust";
|
import { useWebOfTrust } from "../../../providers/global/web-of-trust-provider";
|
||||||
|
|
||||||
function UserOption({ pubkey }: { pubkey: string }) {
|
function UserOption({ pubkey }: { pubkey: string }) {
|
||||||
return (
|
return (
|
||||||
@@ -22,6 +22,7 @@ function UserOption({ pubkey }: { pubkey: string }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function SearchForm({ ...props }: Omit<FlexProps, "children">) {
|
export default function SearchForm({ ...props }: Omit<FlexProps, "children">) {
|
||||||
|
const webOfTrust = useWebOfTrust();
|
||||||
const getDirectory = useUserSearchDirectoryContext();
|
const getDirectory = useUserSearchDirectoryContext();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const autoComplete = useDisclosure();
|
const autoComplete = useDisclosure();
|
||||||
@@ -32,17 +33,18 @@ export default function SearchForm({ ...props }: Omit<FlexProps, "children">) {
|
|||||||
const { value: localUsers = [] } = useAsync(async () => {
|
const { value: localUsers = [] } = useAsync(async () => {
|
||||||
if (queryThrottle.trim().length < 2) return [];
|
if (queryThrottle.trim().length < 2) return [];
|
||||||
|
|
||||||
const webOfTrust = getWebOfTrust();
|
|
||||||
const dir = await getDirectory();
|
const dir = await getDirectory();
|
||||||
return matchSorter(dir, queryThrottle.trim(), {
|
return matchSorter(dir, queryThrottle.trim(), {
|
||||||
keys: ["names"],
|
keys: ["names"],
|
||||||
sorter: (items) =>
|
sorter: (items) =>
|
||||||
webOfTrust.sortByDistanceAndConnections(
|
webOfTrust
|
||||||
items.sort((a, b) => b.rank - a.rank),
|
? webOfTrust.sortByDistanceAndConnections(
|
||||||
(i) => i.item.pubkey,
|
items.sort((a, b) => b.rank - a.rank),
|
||||||
),
|
(i) => i.item.pubkey,
|
||||||
|
)
|
||||||
|
: items,
|
||||||
}).slice(0, 10);
|
}).slice(0, 10);
|
||||||
}, [queryThrottle]);
|
}, [queryThrottle, webOfTrust]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (localUsers.length > 0 && !autoComplete.isOpen) autoComplete.onOpen();
|
if (localUsers.length > 0 && !autoComplete.isOpen) autoComplete.onOpen();
|
||||||
}, [localUsers, autoComplete.isOpen]);
|
}, [localUsers, autoComplete.isOpen]);
|
||||||
|
@@ -5,16 +5,17 @@ import { UserCard } from "./components/user-card";
|
|||||||
import { useAdditionalRelayContext } from "../../providers/local/additional-relay-context";
|
import { useAdditionalRelayContext } from "../../providers/local/additional-relay-context";
|
||||||
import useUserContactList from "../../hooks/use-user-contact-list";
|
import useUserContactList from "../../hooks/use-user-contact-list";
|
||||||
import { getPubkeysFromList } from "../../helpers/nostr/lists";
|
import { getPubkeysFromList } from "../../helpers/nostr/lists";
|
||||||
import { getWebOfTrust } from "../../services/web-of-trust";
|
import { useWebOfTrust } from "../../providers/global/web-of-trust-provider";
|
||||||
|
|
||||||
export default function UserFollowingTab() {
|
export default function UserFollowingTab() {
|
||||||
|
const webOfTrust = useWebOfTrust();
|
||||||
const { pubkey } = useOutletContext() as { pubkey: string };
|
const { pubkey } = useOutletContext() as { pubkey: string };
|
||||||
const contextRelays = useAdditionalRelayContext();
|
const contextRelays = useAdditionalRelayContext();
|
||||||
|
|
||||||
const contactsList = useUserContactList(pubkey, contextRelays, { alwaysRequest: true });
|
const contactsList = useUserContactList(pubkey, contextRelays, { alwaysRequest: true });
|
||||||
|
|
||||||
const people = contactsList ? getPubkeysFromList(contactsList) : [];
|
const people = contactsList ? getPubkeysFromList(contactsList) : [];
|
||||||
const sorted = getWebOfTrust().sortByDistanceAndConnections(people, (p) => p.pubkey);
|
const sorted = webOfTrust ? webOfTrust.sortByDistanceAndConnections(people, (p) => p.pubkey) : people;
|
||||||
|
|
||||||
if (!contactsList) return <Spinner />;
|
if (!contactsList) return <Spinner />;
|
||||||
|
|
||||||
|
@@ -22,10 +22,9 @@ import { Link as RouterLink } from "react-router-dom";
|
|||||||
import { useReadRelays } from "../../../hooks/use-client-relays";
|
import { useReadRelays } from "../../../hooks/use-client-relays";
|
||||||
import { getPageSummary } from "../../../helpers/nostr/wiki";
|
import { getPageSummary } from "../../../helpers/nostr/wiki";
|
||||||
import UserName from "../../../components/user/user-name";
|
import UserName from "../../../components/user/user-name";
|
||||||
import { getWebOfTrust } from "../../../services/web-of-trust";
|
|
||||||
import { getSharableEventAddress } from "../../../helpers/nip19";
|
|
||||||
import dictionaryService from "../../../services/dictionary";
|
import dictionaryService from "../../../services/dictionary";
|
||||||
import useSubject from "../../../hooks/use-subject";
|
import useSubject from "../../../hooks/use-subject";
|
||||||
|
import { useWebOfTrust } from "../../../providers/global/web-of-trust-provider";
|
||||||
|
|
||||||
export default function WikiLink({
|
export default function WikiLink({
|
||||||
children,
|
children,
|
||||||
@@ -35,6 +34,7 @@ export default function WikiLink({
|
|||||||
topic,
|
topic,
|
||||||
...props
|
...props
|
||||||
}: LinkProps & ExtraProps & { maxVersions?: number; topic?: string }) {
|
}: LinkProps & ExtraProps & { maxVersions?: number; topic?: string }) {
|
||||||
|
const webOfTrust = useWebOfTrust();
|
||||||
const { isOpen, onClose, onOpen } = useDisclosure();
|
const { isOpen, onClose, onOpen } = useDisclosure();
|
||||||
const readRelays = useReadRelays();
|
const readRelays = useReadRelays();
|
||||||
|
|
||||||
@@ -51,7 +51,10 @@ export default function WikiLink({
|
|||||||
|
|
||||||
const sorted = useMemo(() => {
|
const sorted = useMemo(() => {
|
||||||
if (!events) return [];
|
if (!events) return [];
|
||||||
const arr = getWebOfTrust().sortByDistanceAndConnections(Array.from(events.values()), (e) => e.pubkey);
|
|
||||||
|
let arr = Array.from(events.values());
|
||||||
|
if (webOfTrust) arr = webOfTrust.sortByDistanceAndConnections(arr, (e) => e.pubkey);
|
||||||
|
|
||||||
const seen = new Set<string>();
|
const seen = new Set<string>();
|
||||||
const unique: NostrEvent[] = [];
|
const unique: NostrEvent[] = [];
|
||||||
|
|
||||||
@@ -65,15 +68,19 @@ export default function WikiLink({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return unique;
|
return unique;
|
||||||
}, [events]);
|
}, [events, maxVersions, webOfTrust]);
|
||||||
|
|
||||||
// if there is only one result, redirect to it
|
|
||||||
const to = sorted?.length === 1 ? "/wiki/page/" + getSharableEventAddress(sorted[0]) : "/wiki/topic/" + topic;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover returnFocusOnClose={false} isOpen={isOpen} onClose={onClose} placement="top" closeOnBlur={true}>
|
<Popover returnFocusOnClose={false} isOpen={isOpen} onClose={onClose} placement="top" closeOnBlur={true}>
|
||||||
<PopoverTrigger>
|
<PopoverTrigger>
|
||||||
<Link as={RouterLink} color="blue.500" {...props} to={to} onMouseEnter={onOpen} onMouseLeave={onClose}>
|
<Link
|
||||||
|
as={RouterLink}
|
||||||
|
color="blue.500"
|
||||||
|
{...props}
|
||||||
|
to={"/wiki/topic/" + topic}
|
||||||
|
onMouseEnter={onOpen}
|
||||||
|
onMouseLeave={onClose}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</Link>
|
</Link>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
|
@@ -1,35 +1,32 @@
|
|||||||
import { useMemo, useRef, useState } from "react";
|
import {
|
||||||
import { Button, Flex, FormControl, FormLabel, Heading, Input, VisuallyHidden, useToast } from "@chakra-ui/react";
|
Button,
|
||||||
import SimpleMDE, { SimpleMDEReactProps } from "react-simplemde-editor";
|
Flex,
|
||||||
|
FormControl,
|
||||||
|
FormHelperText,
|
||||||
|
FormLabel,
|
||||||
|
Heading,
|
||||||
|
Input,
|
||||||
|
Textarea,
|
||||||
|
useToast,
|
||||||
|
} from "@chakra-ui/react";
|
||||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||||
import ReactDOMServer from "react-dom/server";
|
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { EventTemplate } from "nostr-tools";
|
import { EventTemplate } from "nostr-tools";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
import EasyMDE from "easymde";
|
|
||||||
import "easymde/dist/easymde.min.css";
|
import "easymde/dist/easymde.min.css";
|
||||||
|
|
||||||
|
import { WIKI_RELAYS } from "../../const";
|
||||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||||
import { removeNonASCIIChar } from "../../helpers/string";
|
import { removeNonASCIIChar } from "../../helpers/string";
|
||||||
import { usePublishEvent } from "../../providers/global/publish-provider";
|
import { usePublishEvent } from "../../providers/global/publish-provider";
|
||||||
import { WIKI_PAGE_KIND } from "../../helpers/nostr/wiki";
|
import { WIKI_PAGE_KIND } from "../../helpers/nostr/wiki";
|
||||||
import { getSharableEventAddress } from "../../helpers/nip19";
|
import { getSharableEventAddress } from "../../helpers/nip19";
|
||||||
import { WIKI_RELAYS } from "../../const";
|
|
||||||
import useAppSettings from "../../hooks/use-app-settings";
|
|
||||||
import { uploadFileToServers } from "../../helpers/media-upload/blossom";
|
|
||||||
import useUsersMediaServers from "../../hooks/use-user-media-servers";
|
|
||||||
import { useSigningContext } from "../../providers/global/signing-provider";
|
|
||||||
import useCurrentAccount from "../../hooks/use-current-account";
|
|
||||||
import useCacheForm from "../../hooks/use-cache-form";
|
import useCacheForm from "../../hooks/use-cache-form";
|
||||||
import MarkdownEditor from "./components/markdown-editor";
|
import MarkdownEditor from "./components/markdown-editor";
|
||||||
|
|
||||||
export default function CreateWikiPageView() {
|
export default function CreateWikiPageView() {
|
||||||
const account = useCurrentAccount();
|
|
||||||
const { mediaUploadService } = useAppSettings();
|
|
||||||
const { servers } = useUsersMediaServers(account?.pubkey);
|
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const { requestSignature } = useSigningContext();
|
|
||||||
const publish = usePublishEvent();
|
const publish = usePublishEvent();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [search] = useSearchParams();
|
const [search] = useSearchParams();
|
||||||
@@ -37,7 +34,7 @@ export default function CreateWikiPageView() {
|
|||||||
const presetTitle = search.get("title");
|
const presetTitle = search.get("title");
|
||||||
|
|
||||||
const { register, setValue, getValues, handleSubmit, watch, formState, reset } = useForm({
|
const { register, setValue, getValues, handleSubmit, watch, formState, reset } = useForm({
|
||||||
defaultValues: { content: "", title: presetTitle || presetTopic || "", topic: presetTopic || "" },
|
defaultValues: { content: "", title: presetTitle || presetTopic || "", topic: presetTopic || "", summary: "" },
|
||||||
mode: "all",
|
mode: "all",
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -99,6 +96,11 @@ export default function CreateWikiPageView() {
|
|||||||
<Input {...register("title", { required: true })} autoComplete="off" />
|
<Input {...register("title", { required: true })} autoComplete="off" />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
<FormControl>
|
||||||
|
<FormLabel>Summary</FormLabel>
|
||||||
|
<Textarea {...register("summary", { required: true })} isRequired />
|
||||||
|
<FormHelperText>We'll never share your email.</FormHelperText>
|
||||||
|
</FormControl>
|
||||||
<MarkdownEditor value={getValues().content} onChange={(v) => setValue("content", v)} />
|
<MarkdownEditor value={getValues().content} onChange={(v) => setValue("content", v)} />
|
||||||
<Flex gap="2" justifyContent="flex-end">
|
<Flex gap="2" justifyContent="flex-end">
|
||||||
<Button onClick={() => navigate(-1)}>Cancel</Button>
|
<Button onClick={() => navigate(-1)}>Cancel</Button>
|
||||||
|
@@ -21,7 +21,6 @@ import VerticalPageLayout from "../../components/vertical-page-layout";
|
|||||||
import { getPageDefer, getPageForks, getPageTitle, getPageTopic } from "../../helpers/nostr/wiki";
|
import { getPageDefer, getPageForks, getPageTitle, getPageTopic } from "../../helpers/nostr/wiki";
|
||||||
import MarkdownContent from "./components/markdown";
|
import MarkdownContent from "./components/markdown";
|
||||||
import UserLink from "../../components/user/user-link";
|
import UserLink from "../../components/user/user-link";
|
||||||
import { getWebOfTrust } from "../../services/web-of-trust";
|
|
||||||
import useSubject from "../../hooks/use-subject";
|
import useSubject from "../../hooks/use-subject";
|
||||||
import WikiPageResult from "./components/wiki-page-result";
|
import WikiPageResult from "./components/wiki-page-result";
|
||||||
import Timestamp from "../../components/timestamp";
|
import Timestamp from "../../components/timestamp";
|
||||||
@@ -38,6 +37,7 @@ import EventVoteButtons from "../../components/reactions/event-vote-buttions";
|
|||||||
import useCurrentAccount from "../../hooks/use-current-account";
|
import useCurrentAccount from "../../hooks/use-current-account";
|
||||||
import dictionaryService from "../../services/dictionary";
|
import dictionaryService from "../../services/dictionary";
|
||||||
import { useReadRelays } from "../../hooks/use-client-relays";
|
import { useReadRelays } from "../../hooks/use-client-relays";
|
||||||
|
import { useWebOfTrust } from "../../providers/global/web-of-trust-provider";
|
||||||
|
|
||||||
function ForkAlert({ page, address }: { page: NostrEvent; address: nip19.AddressPointer }) {
|
function ForkAlert({ page, address }: { page: NostrEvent; address: nip19.AddressPointer }) {
|
||||||
const topic = getPageTopic(page);
|
const topic = getPageTopic(page);
|
||||||
@@ -91,8 +91,6 @@ export function WikiPagePage({ page }: { page: NostrEvent }) {
|
|||||||
const topic = getPageTopic(page);
|
const topic = getPageTopic(page);
|
||||||
|
|
||||||
const readRelays = useReadRelays();
|
const readRelays = useReadRelays();
|
||||||
const subject = useMemo(() => dictionaryService.requestTopic(topic, readRelays), [topic, readRelays]);
|
|
||||||
const pages = useSubject(subject);
|
|
||||||
const { address } = getPageForks(page);
|
const { address } = getPageForks(page);
|
||||||
const defer = getPageDefer(page);
|
const defer = getPageDefer(page);
|
||||||
|
|
||||||
@@ -133,24 +131,18 @@ export function WikiPagePage({ page }: { page: NostrEvent }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function WikiPageFooter({ page }: { page: NostrEvent }) {
|
function WikiPageFooter({ page }: { page: NostrEvent }) {
|
||||||
|
const webOfTrust = useWebOfTrust();
|
||||||
const topic = getPageTopic(page);
|
const topic = getPageTopic(page);
|
||||||
|
|
||||||
const readRelays = useReadRelays();
|
const readRelays = useReadRelays();
|
||||||
const subject = useMemo(() => dictionaryService.requestTopic(topic, readRelays), [topic, readRelays]);
|
const subject = useMemo(() => dictionaryService.requestTopic(topic, readRelays), [topic, readRelays]);
|
||||||
const pages = useSubject(subject);
|
const pages = useSubject(subject);
|
||||||
|
|
||||||
const forks = pages
|
let forks = pages ? Array.from(pages.values()).filter((p) => getPageForks(p).address?.pubkey === page.pubkey) : [];
|
||||||
? getWebOfTrust().sortByDistanceAndConnections(
|
if (webOfTrust) forks = webOfTrust.sortByDistanceAndConnections(forks, (p) => p.pubkey);
|
||||||
Array.from(pages.values()).filter((p) => getPageForks(p).address?.pubkey === page.pubkey),
|
|
||||||
(p) => p.pubkey,
|
let other = pages ? Array.from(pages.values()).filter((p) => !forks.includes(p) && p.pubkey !== page.pubkey) : [];
|
||||||
)
|
if (webOfTrust) other = webOfTrust.sortByDistanceAndConnections(other, (p) => p.pubkey);
|
||||||
: [];
|
|
||||||
const other = pages
|
|
||||||
? getWebOfTrust().sortByDistanceAndConnections(
|
|
||||||
Array.from(pages.values()).filter((p) => !forks.includes(p) && p.pubkey !== page.pubkey),
|
|
||||||
(p) => p.pubkey,
|
|
||||||
)
|
|
||||||
: [];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@@ -12,11 +12,12 @@ import { subscribeMany } from "../../helpers/relay";
|
|||||||
import { SEARCH_RELAYS, WIKI_RELAYS } from "../../const";
|
import { SEARCH_RELAYS, WIKI_RELAYS } from "../../const";
|
||||||
import { WIKI_PAGE_KIND } from "../../helpers/nostr/wiki";
|
import { WIKI_PAGE_KIND } from "../../helpers/nostr/wiki";
|
||||||
import { localRelay } from "../../services/local-relay";
|
import { localRelay } from "../../services/local-relay";
|
||||||
import { getWebOfTrust } from "../../services/web-of-trust";
|
|
||||||
import WikiPageResult from "./components/wiki-page-result";
|
import WikiPageResult from "./components/wiki-page-result";
|
||||||
import dictionaryService from "../../services/dictionary";
|
import dictionaryService from "../../services/dictionary";
|
||||||
|
import { useWebOfTrust } from "../../providers/global/web-of-trust-provider";
|
||||||
|
|
||||||
export default function WikiSearchView() {
|
export default function WikiSearchView() {
|
||||||
|
const webOfTrust = useWebOfTrust();
|
||||||
const { value: query, setValue: setQuery } = useRouteSearchValue("q");
|
const { value: query, setValue: setQuery } = useRouteSearchValue("q");
|
||||||
if (!query) return <Navigate to="/wiki" />;
|
if (!query) return <Navigate to="/wiki" />;
|
||||||
|
|
||||||
@@ -53,7 +54,7 @@ export default function WikiSearchView() {
|
|||||||
}
|
}
|
||||||
}, [query, setResults]);
|
}, [query, setResults]);
|
||||||
|
|
||||||
const sorted = getWebOfTrust().sortByDistanceAndConnections(results, (p) => p.pubkey);
|
const sorted = webOfTrust ? webOfTrust.sortByDistanceAndConnections(results, (p) => p.pubkey) : results;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VerticalPageLayout>
|
<VerticalPageLayout>
|
||||||
|
@@ -4,7 +4,6 @@ import { NostrEvent } from "nostr-tools";
|
|||||||
|
|
||||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||||
import useSubject from "../../hooks/use-subject";
|
import useSubject from "../../hooks/use-subject";
|
||||||
import { getWebOfTrust } from "../../services/web-of-trust";
|
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import dictionaryService from "../../services/dictionary";
|
import dictionaryService from "../../services/dictionary";
|
||||||
import { useReadRelays } from "../../hooks/use-client-relays";
|
import { useReadRelays } from "../../hooks/use-client-relays";
|
||||||
@@ -12,18 +11,20 @@ import WikiPageHeader from "./components/wiki-page-header";
|
|||||||
import UserAvatar from "../../components/user/user-avatar";
|
import UserAvatar from "../../components/user/user-avatar";
|
||||||
import UserName from "../../components/user/user-name";
|
import UserName from "../../components/user/user-name";
|
||||||
import { WikiPagePage } from "./page";
|
import { WikiPagePage } from "./page";
|
||||||
|
import { useWebOfTrust } from "../../providers/global/web-of-trust-provider";
|
||||||
|
|
||||||
export default function WikiTopicView() {
|
export default function WikiTopicView() {
|
||||||
const { topic } = useParams();
|
const { topic } = useParams();
|
||||||
if (!topic) return <Navigate to="/wiki" />;
|
if (!topic) return <Navigate to="/wiki" />;
|
||||||
|
|
||||||
|
const webOfTrust = useWebOfTrust();
|
||||||
const readRelays = useReadRelays();
|
const readRelays = useReadRelays();
|
||||||
const subject = useMemo(() => dictionaryService.requestTopic(topic, readRelays), [topic, readRelays]);
|
const subject = useMemo(() => dictionaryService.requestTopic(topic, readRelays), [topic, readRelays]);
|
||||||
|
|
||||||
const pages = useSubject(subject);
|
const pages = useSubject(subject);
|
||||||
const sorted = pages
|
|
||||||
? getWebOfTrust().sortByDistanceAndConnections(Array.from(pages?.values()), (p) => p.pubkey)
|
let sorted = pages ? Array.from(pages.values()) : [];
|
||||||
: [];
|
if (webOfTrust) sorted = webOfTrust.sortByDistanceAndConnections(sorted, (p) => p.pubkey);
|
||||||
|
|
||||||
const [selected, setSelected] = useState<NostrEvent>();
|
const [selected, setSelected] = useState<NostrEvent>();
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user