mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-09-19 12:00:32 +02:00
add embedded wiki page card
improve markdown formatting fix wiki pages when logged out handle wiki naddr
This commit is contained in:
@@ -4,7 +4,7 @@ import { NostrEvent } from "../types/nostr-event";
|
||||
import relayPoolService from "../services/relay-pool";
|
||||
import { isFilterEqual } from "../helpers/nostr/filter";
|
||||
import ControlledObservable from "./controlled-observable";
|
||||
import { Filter, Relay, Subscription } from "nostr-tools";
|
||||
import { AbstractRelay, Filter, Subscription } from "nostr-tools";
|
||||
import { offlineMode } from "../services/offline-mode";
|
||||
import RelaySet from "./relay-set";
|
||||
|
||||
@@ -17,8 +17,8 @@ export default class NostrMultiSubscription {
|
||||
name?: string;
|
||||
filters: Filter[] = [];
|
||||
|
||||
relays: Relay[] = [];
|
||||
subscriptions = new Map<Relay, Subscription>();
|
||||
relays: AbstractRelay[] = [];
|
||||
subscriptions = new Map<AbstractRelay, Subscription>();
|
||||
|
||||
state = NostrMultiSubscription.INIT;
|
||||
onEvent = new ControlledObservable<NostrEvent>();
|
||||
@@ -34,10 +34,10 @@ export default class NostrMultiSubscription {
|
||||
this.seenEvents.add(event.id);
|
||||
}
|
||||
|
||||
private handleAddRelay(relay: Relay) {
|
||||
private handleAddRelay(relay: AbstractRelay) {
|
||||
relayPoolService.addClaim(relay.url, this);
|
||||
}
|
||||
private handleRemoveRelay(relay: Relay) {
|
||||
private handleRemoveRelay(relay: AbstractRelay) {
|
||||
relayPoolService.removeClaim(relay.url, this);
|
||||
|
||||
// close subscription
|
||||
@@ -96,7 +96,8 @@ export default class NostrMultiSubscription {
|
||||
subscription.filters = filters;
|
||||
subscription.fire();
|
||||
} else {
|
||||
if (filters.length === 0) debugger;
|
||||
if (!relay.connected) relayPoolService.requestConnect(relay);
|
||||
|
||||
subscription = relay.subscribe(filters, {
|
||||
onevent: (event) => this.handleEvent(event),
|
||||
onclose: () => {
|
||||
@@ -112,7 +113,12 @@ export default class NostrMultiSubscription {
|
||||
}
|
||||
|
||||
publish(event: NostrEvent) {
|
||||
return Promise.allSettled(this.relays.map((r) => r.publish(event)));
|
||||
return Promise.allSettled(
|
||||
this.relays.map(async (r) => {
|
||||
if (!r.connected) await relayPoolService.requestConnect(r);
|
||||
return await r.publish(event);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
open() {
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { AbstractRelay } from "nostr-tools";
|
||||
|
||||
import { logger } from "../helpers/debug";
|
||||
import { validateRelayURL } from "../helpers/relay";
|
||||
import { offlineMode } from "../services/offline-mode";
|
||||
@@ -35,7 +36,20 @@ export default class RelayPool {
|
||||
}
|
||||
|
||||
const relay = this.relays.get(key) as AbstractRelay;
|
||||
if (connect && !relay.connected && !offlineMode.value) {
|
||||
if (connect) this.requestConnect(relay);
|
||||
return relay;
|
||||
}
|
||||
|
||||
async requestConnect(relayOrUrl: string | URL | AbstractRelay) {
|
||||
let relay: AbstractRelay | undefined = undefined;
|
||||
|
||||
if (typeof relayOrUrl === "string") relay = this.relays.get(relayOrUrl);
|
||||
else if (relayOrUrl instanceof URL) relay = this.relays.get(relayOrUrl.toString());
|
||||
else relay = relayOrUrl;
|
||||
|
||||
if (!relay) return;
|
||||
|
||||
if (!relay.connected && !offlineMode.value) {
|
||||
try {
|
||||
relay.connect();
|
||||
} catch (e) {
|
||||
@@ -43,7 +57,6 @@ export default class RelayPool {
|
||||
this.log(e);
|
||||
}
|
||||
}
|
||||
return relay;
|
||||
}
|
||||
|
||||
pruneRelays() {
|
||||
|
@@ -0,0 +1,66 @@
|
||||
import {
|
||||
Button,
|
||||
ButtonGroup,
|
||||
Card,
|
||||
CardBody,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardProps,
|
||||
Heading,
|
||||
LinkBox,
|
||||
Text,
|
||||
} from "@chakra-ui/react";
|
||||
import { useMemo } from "react";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import { nip19 } from "nostr-tools";
|
||||
|
||||
import { NostrEvent } from "../../../types/nostr-event";
|
||||
import UserLink from "../../user/user-link";
|
||||
import { getPageForks, getPageSummary, getPageTitle } from "../../../helpers/nostr/wiki";
|
||||
import HoverLinkOverlay from "../../hover-link-overlay";
|
||||
import { getSharableEventAddress } from "../../../helpers/nip19";
|
||||
import Timestamp from "../../timestamp";
|
||||
import GitBranch01 from "../../icons/git-branch-01";
|
||||
import UserName from "../../user/user-name";
|
||||
|
||||
export default function EmbeddedWikiPage({ page: page, ...props }: Omit<CardProps, "children"> & { page: NostrEvent }) {
|
||||
const { address } = useMemo(() => getPageForks(page), [page]);
|
||||
const showFooter = !!address;
|
||||
|
||||
return (
|
||||
<Card as={LinkBox} {...props}>
|
||||
<CardHeader p="2" pb="0" display="flex" gap="2" alignItems="center">
|
||||
<Heading size="md">
|
||||
<HoverLinkOverlay as={RouterLink} to={`/wiki/page/${getSharableEventAddress(page)}`}>
|
||||
{getPageTitle(page)}
|
||||
</HoverLinkOverlay>
|
||||
</Heading>
|
||||
<Text>
|
||||
by <UserLink pubkey={page.pubkey} fontWeight="bold " /> - <Timestamp timestamp={page.created_at} />
|
||||
</Text>
|
||||
</CardHeader>
|
||||
<CardBody p="2" overflow="hidden">
|
||||
<Text color="GrayText" noOfLines={2}>
|
||||
{getPageSummary(page)}
|
||||
</Text>
|
||||
</CardBody>
|
||||
{showFooter && (
|
||||
<CardFooter>
|
||||
<ButtonGroup variant="link" mt="auto">
|
||||
{address && (
|
||||
<Button
|
||||
as={RouterLink}
|
||||
to={`/wiki/page/${nip19.naddrEncode(address)}`}
|
||||
p="2"
|
||||
colorScheme="blue"
|
||||
leftIcon={<GitBranch01 />}
|
||||
>
|
||||
<UserName pubkey={address.pubkey} />
|
||||
</Button>
|
||||
)}
|
||||
</ButtonGroup>
|
||||
</CardFooter>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
@@ -41,6 +41,8 @@ import { FLARE_VIDEO_KIND } from "../../helpers/nostr/flare";
|
||||
import EmbeddedFlareVideo from "./event-types/embedded-flare-video";
|
||||
import LoadingNostrLink from "../loading-nostr-link";
|
||||
import EmbeddedRepost from "./event-types/embedded-repost";
|
||||
import { WIKI_PAGE_KIND } from "../../helpers/nostr/wiki";
|
||||
import EmbeddedWikiPage from "./event-types/embedded-wiki-page";
|
||||
const EmbeddedStemstrTrack = lazy(() => import("./event-types/embedded-stemstr-track"));
|
||||
|
||||
export type EmbedProps = {
|
||||
@@ -93,6 +95,8 @@ export function EmbedEvent({
|
||||
case kinds.Repost:
|
||||
case kinds.GenericRepost:
|
||||
return <EmbeddedRepost repost={event} {...cardProps} />;
|
||||
case WIKI_PAGE_KIND:
|
||||
return <EmbeddedWikiPage page={event} {...cardProps} />;
|
||||
}
|
||||
|
||||
return <EmbeddedUnknown event={event} {...cardProps} />;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Flex, Tag, TagLabel } from "@chakra-ui/react";
|
||||
import { Flex, FlexProps, Tag, TagLabel } from "@chakra-ui/react";
|
||||
import { NostrEvent } from "nostr-tools";
|
||||
import { getEventUID } from "nostr-idb";
|
||||
import styled from "@emotion/styled";
|
||||
@@ -16,7 +16,7 @@ const HiddenScrollbar = styled(Flex)`
|
||||
}
|
||||
`;
|
||||
|
||||
export default function ZapBubbles({ event }: { event: NostrEvent }) {
|
||||
export default function ZapBubbles({ event, ...props }: { event: NostrEvent } & Omit<FlexProps, "children">) {
|
||||
const zaps = useEventZaps(getEventUID(event));
|
||||
|
||||
if (zaps.length === 0) return null;
|
||||
@@ -24,10 +24,10 @@ export default function ZapBubbles({ event }: { event: NostrEvent }) {
|
||||
const sorted = zaps.sort((a, b) => (b.payment.amount ?? 0) - (a.payment.amount ?? 0));
|
||||
|
||||
return (
|
||||
<HiddenScrollbar overflowY="hidden" overflowX="auto" gap="2">
|
||||
<HiddenScrollbar overflowY="hidden" overflowX="auto" gap="2" {...props}>
|
||||
{sorted.map((zap) => (
|
||||
<Tag key={zap.event.id} borderRadius="full" py="1" flexShrink={0} variant="outline">
|
||||
<LightningIcon mr="1" />
|
||||
<LightningIcon mr="1" color="yellow.400" />
|
||||
<TagLabel fontWeight="bold">{readablizeSats((zap.payment.amount ?? 0) / 1000)}</TagLabel>
|
||||
<UserAvatar pubkey={zap.request.pubkey} size="xs" square={false} ml="2" />
|
||||
</Tag>
|
||||
|
@@ -19,8 +19,8 @@ export function getPageSummary(page: NostrEvent) {
|
||||
}
|
||||
|
||||
export function getPageForks(page: NostrEvent) {
|
||||
const addressFork = page.tags.find((t) => t[0] === "a" && t[1] && t[3]);
|
||||
const eventFork = page.tags.find((t) => t[0] === "a" && t[1] && t[3]);
|
||||
const addressFork = page.tags.find((t) => t[0] === "a" && t[1] && t[3] === "fork");
|
||||
const eventFork = page.tags.find((t) => t[0] === "a" && t[1] && t[3] === "fork");
|
||||
|
||||
const address = addressFork ? parseCoordinate(addressFork[1], true) ?? undefined : undefined;
|
||||
const event: nip19.EventPointer | undefined = eventFork ? { id: eventFork[1] } : undefined;
|
||||
@@ -28,6 +28,16 @@ export function getPageForks(page: NostrEvent) {
|
||||
return { event, address };
|
||||
}
|
||||
|
||||
export function getPageDefer(page: NostrEvent) {
|
||||
const addressTag = page.tags.find((t) => t[0] === "a" && t[1] && t[3] === "defer");
|
||||
const eventTag = page.tags.find((t) => t[0] === "a" && t[1] && t[3] === "defer");
|
||||
|
||||
const address = addressTag ? parseCoordinate(addressTag[1], true) ?? undefined : undefined;
|
||||
const event: nip19.EventPointer | undefined = eventTag ? { id: eventTag[1] } : undefined;
|
||||
|
||||
if (event || address) return { event, address };
|
||||
}
|
||||
|
||||
export function isPageFork(page: NostrEvent) {
|
||||
return page.tags.some((t) => (t[0] === "a" || t[0] === "e") && t[3] === "fork");
|
||||
}
|
||||
|
@@ -158,7 +158,7 @@ export function subscribeMany(relays: string[], filters: Filter[], params: Subsc
|
||||
let relay: AbstractRelay;
|
||||
try {
|
||||
relay = relayPoolService.requestRelay(url);
|
||||
await relay.connect();
|
||||
await relayPoolService.requestConnect(relay);
|
||||
// changed from nostr-tools
|
||||
// relay = await this.ensureRelay(url, {
|
||||
// connectionTimeout: params.maxWait ? Math.max(params.maxWait * 0.8, params.maxWait - 1000) : undefined,
|
||||
|
@@ -9,7 +9,7 @@ import replaceableEventsService from "./replaceable-events";
|
||||
import { getPubkeysFromList } from "../helpers/nostr/lists";
|
||||
|
||||
const log = logger.extend("web-of-trust");
|
||||
let webOfTrust: PubkeyGraph;
|
||||
let webOfTrust = new PubkeyGraph("");
|
||||
|
||||
let newEvents = 0;
|
||||
const throttleUpdateWebOfTrust = _throttle(() => {
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Alert, AlertIcon, AlertTitle } from "@chakra-ui/react";
|
||||
import { Navigate, useParams } from "react-router-dom";
|
||||
import { kinds, nip19 } from "nostr-tools";
|
||||
|
||||
import { STREAM_KIND } from "../../helpers/nostr/stream";
|
||||
import { EMOJI_PACK_KIND } from "../../helpers/nostr/emoji-packs";
|
||||
import { NOTE_LIST_KIND, PEOPLE_LIST_KIND } from "../../helpers/nostr/lists";
|
||||
@@ -8,6 +9,8 @@ import { ErrorBoundary } from "../../components/error-boundary";
|
||||
import { COMMUNITY_DEFINITION_KIND } from "../../helpers/nostr/communities";
|
||||
import { TORRENT_KIND } from "../../helpers/nostr/torrents";
|
||||
import { FLARE_VIDEO_KIND } from "../../helpers/nostr/flare";
|
||||
import { WIKI_PAGE_KIND } from "../../helpers/nostr/wiki";
|
||||
import { EmbedEventPointer } from "../../components/embed-event";
|
||||
|
||||
function NostrLinkPage() {
|
||||
const { link } = useParams() as { link?: string };
|
||||
@@ -41,15 +44,17 @@ function NostrLinkPage() {
|
||||
if (decoded.data.kind === FLARE_VIDEO_KIND) return <Navigate to={`/videos/${cleanLink}`} replace />;
|
||||
if (decoded.data.kind === kinds.ChannelCreation) return <Navigate to={`/channels/${cleanLink}`} replace />;
|
||||
if (decoded.data.kind === kinds.ShortTextNote) return <Navigate to={`/n/${cleanLink}`} replace />;
|
||||
// if there is no kind redirect to the thread view
|
||||
return <Navigate to={`/n/${cleanLink}`} replace />;
|
||||
if (decoded.data.kind === WIKI_PAGE_KIND) return <Navigate to={`/wiki/page/${cleanLink}`} replace />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Alert status="warning">
|
||||
<AlertIcon />
|
||||
<AlertTitle>Unknown type {JSON.stringify(decoded.data)}</AlertTitle>
|
||||
</Alert>
|
||||
<>
|
||||
<Alert status="warning">
|
||||
<AlertIcon />
|
||||
<AlertTitle>Unknown event kind</AlertTitle>
|
||||
</Alert>
|
||||
<EmbedEventPointer pointer={decoded} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -1,8 +1,81 @@
|
||||
import { Image, Link, LinkProps, Text, TextProps } from "@chakra-ui/react";
|
||||
import {
|
||||
Code,
|
||||
CodeProps,
|
||||
Heading,
|
||||
HeadingProps,
|
||||
Image,
|
||||
Link,
|
||||
LinkProps,
|
||||
ListItem,
|
||||
OrderedList,
|
||||
Table,
|
||||
TableContainer,
|
||||
TableProps,
|
||||
Tbody,
|
||||
Td,
|
||||
Text,
|
||||
TextProps,
|
||||
Tfoot,
|
||||
Th,
|
||||
Thead,
|
||||
Tr,
|
||||
UnorderedList,
|
||||
} from "@chakra-ui/react";
|
||||
import styled from "@emotion/styled";
|
||||
import { NostrEvent } from "nostr-tools";
|
||||
import Markdown, { Components } from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
|
||||
const StyledMarkdown = styled(Markdown)`
|
||||
pre > code {
|
||||
display: block;
|
||||
padding-block: var(--chakra-space-2);
|
||||
padding-inline: var(--chakra-space-4);
|
||||
}
|
||||
`;
|
||||
|
||||
function H1({ children, ...props }: HeadingProps) {
|
||||
return (
|
||||
<Heading as="h1" size="xl" mt="6" mb="2" {...props}>
|
||||
{children}
|
||||
</Heading>
|
||||
);
|
||||
}
|
||||
function H2({ children, ...props }: HeadingProps) {
|
||||
return (
|
||||
<Heading as="h2" size="lg" mt="6" mb="2" {...props}>
|
||||
{children}
|
||||
</Heading>
|
||||
);
|
||||
}
|
||||
function H3({ children, ...props }: HeadingProps) {
|
||||
return (
|
||||
<Heading as="h3" size="md" mt="4" mb="2" {...props}>
|
||||
{children}
|
||||
</Heading>
|
||||
);
|
||||
}
|
||||
function H4({ children, ...props }: HeadingProps) {
|
||||
return (
|
||||
<Heading as="h4" size="sm" my="2" {...props}>
|
||||
{children}
|
||||
</Heading>
|
||||
);
|
||||
}
|
||||
function H5({ children, ...props }: HeadingProps) {
|
||||
return (
|
||||
<Heading as="h5" size="xs" my="2" {...props}>
|
||||
{children}
|
||||
</Heading>
|
||||
);
|
||||
}
|
||||
function H6({ children, ...props }: HeadingProps) {
|
||||
return (
|
||||
<Heading as="h6" size="xs" my="2" {...props}>
|
||||
{children}
|
||||
</Heading>
|
||||
);
|
||||
}
|
||||
function A({ children, ...props }: LinkProps) {
|
||||
return (
|
||||
<Link color="blue.500" isExternal {...props}>
|
||||
@@ -12,22 +85,48 @@ function A({ children, ...props }: LinkProps) {
|
||||
}
|
||||
function P({ children, ...props }: TextProps) {
|
||||
return (
|
||||
<Text py="2" {...props}>
|
||||
<Text my="2" {...props}>
|
||||
{children}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
function TableWithContainer({ children, ...props }: TableProps) {
|
||||
return (
|
||||
<TableContainer>
|
||||
<Table size="sm" mb="4" {...props}>
|
||||
{children}
|
||||
</Table>
|
||||
</TableContainer>
|
||||
);
|
||||
}
|
||||
|
||||
const components: Partial<Components> = {
|
||||
h1: H1,
|
||||
h2: H2,
|
||||
h3: H3,
|
||||
h4: H4,
|
||||
h5: H5,
|
||||
h6: H6,
|
||||
a: A,
|
||||
img: Image,
|
||||
p: P,
|
||||
ul: UnorderedList,
|
||||
ol: OrderedList,
|
||||
li: ListItem,
|
||||
table: TableWithContainer,
|
||||
thead: Thead,
|
||||
tbody: Tbody,
|
||||
tfoot: Tfoot,
|
||||
tr: Tr,
|
||||
td: Td,
|
||||
th: Th,
|
||||
code: Code,
|
||||
};
|
||||
|
||||
export default function MarkdownContent({ event }: { event: NostrEvent }) {
|
||||
return (
|
||||
<Markdown remarkPlugins={[remarkGfm]} components={components}>
|
||||
<StyledMarkdown remarkPlugins={[remarkGfm]} components={components}>
|
||||
{event.content}
|
||||
</Markdown>
|
||||
</StyledMarkdown>
|
||||
);
|
||||
}
|
||||
|
@@ -19,21 +19,19 @@ export default function WikiPageResult({ page, compare }: { page: NostrEvent; co
|
||||
|
||||
return (
|
||||
<Flex as={LinkBox} py="2" px="4" direction="column">
|
||||
<Flex gap="2" alignItems="center">
|
||||
<Box overflow="hidden">
|
||||
<Heading size="md">
|
||||
<HoverLinkOverlay as={RouterLink} to={`/wiki/page/${getSharableEventAddress(page)}`}>
|
||||
{getPageTitle(page)}
|
||||
</HoverLinkOverlay>
|
||||
</Heading>
|
||||
<Text>
|
||||
by <UserLink pubkey={page.pubkey} fontWeight="bold " /> - <Timestamp timestamp={page.created_at} />
|
||||
</Text>
|
||||
<Text color="GrayText" noOfLines={2}>
|
||||
{getPageSummary(page)}
|
||||
</Text>
|
||||
</Box>
|
||||
</Flex>
|
||||
<Box overflow="hidden">
|
||||
<Heading size="md">
|
||||
<HoverLinkOverlay as={RouterLink} to={`/wiki/page/${getSharableEventAddress(page)}`}>
|
||||
{getPageTitle(page)}
|
||||
</HoverLinkOverlay>
|
||||
</Heading>
|
||||
<Text>
|
||||
by <UserLink pubkey={page.pubkey} fontWeight="bold " /> - <Timestamp timestamp={page.created_at} />
|
||||
</Text>
|
||||
<Text color="GrayText" noOfLines={2}>
|
||||
{getPageSummary(page)}
|
||||
</Text>
|
||||
</Box>
|
||||
<ButtonGroup variant="link" mt="auto">
|
||||
{address && (
|
||||
<Button
|
||||
|
@@ -7,8 +7,7 @@ export default function WikiSearchForm({ ...props }: Omit<FlexProps, "children">
|
||||
const { register, handleSubmit } = useForm({ defaultValues: { search: "" } });
|
||||
|
||||
const onSubmit = handleSubmit((values) => {
|
||||
// navigate(`/wiki/search?q=${encodeURIComponent(values.search)}`);
|
||||
navigate(`/wiki/topic/${values.search}`);
|
||||
navigate(`/wiki/search?q=${encodeURIComponent(values.search)}`);
|
||||
});
|
||||
|
||||
return (
|
||||
|
24
src/views/wiki/components/wioki-page-menu.tsx
Normal file
24
src/views/wiki/components/wioki-page-menu.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import { NostrEvent } from "nostr-tools";
|
||||
|
||||
import { DotsMenuButton, MenuIconButtonProps } from "../../../components/dots-menu-button";
|
||||
import OpenInAppMenuItem from "../../../components/common-menu-items/open-in-app";
|
||||
import CopyShareLinkMenuItem from "../../../components/common-menu-items/copy-share-link";
|
||||
import CopyEmbedCodeMenuItem from "../../../components/common-menu-items/copy-embed-code";
|
||||
import DebugEventMenuItem from "../../../components/debug-modal/debug-event-menu-item";
|
||||
|
||||
export default function WikiPageMenu({
|
||||
page,
|
||||
...props
|
||||
}: { page: NostrEvent } & Omit<MenuIconButtonProps, "children">) {
|
||||
return (
|
||||
<>
|
||||
<DotsMenuButton {...props}>
|
||||
<OpenInAppMenuItem event={page} />
|
||||
<CopyShareLinkMenuItem event={page} />
|
||||
<CopyEmbedCodeMenuItem event={page} />
|
||||
|
||||
<DebugEventMenuItem event={page} />
|
||||
</DotsMenuButton>
|
||||
</>
|
||||
);
|
||||
}
|
@@ -14,6 +14,7 @@ import WikiPageResult from "./components/wiki-page-result";
|
||||
import TimelineActionAndStatus from "../../components/timeline-page/timeline-action-and-status";
|
||||
import { ErrorBoundary } from "../../components/error-boundary";
|
||||
import { WIKI_RELAYS } from "../../const";
|
||||
import { ExternalLinkIcon } from "../../components/icons";
|
||||
|
||||
function eventFilter(event: NostrEvent) {
|
||||
if (!validatePage(event)) return false;
|
||||
@@ -29,13 +30,16 @@ export default function WikiHomeView() {
|
||||
|
||||
return (
|
||||
<VerticalPageLayout>
|
||||
<Flex mx="auto" mt="10vh" mb="10vh" direction="column" alignItems="center" maxW="full" gap="4">
|
||||
<Flex mx="auto" mt="10vh" mb="10vh" direction="column" alignItems="center" maxW="full">
|
||||
<Heading>
|
||||
<Link as={RouterLink} to="/wiki/topic/wikifreedia">
|
||||
Wikifreedia
|
||||
</Link>
|
||||
</Heading>
|
||||
<WikiSearchForm maxW="full" />
|
||||
<Link isExternal color="blue.500" href="https://wikifreedia.xyz/">
|
||||
wikifreedia.xyz <ExternalLinkIcon />
|
||||
</Link>
|
||||
<WikiSearchForm maxW="full" mt="4" />
|
||||
</Flex>
|
||||
|
||||
<Heading size="md" mt="4">
|
||||
|
@@ -16,7 +16,7 @@ import { Link as RouterLink } from "react-router-dom";
|
||||
import useParamsAddressPointer from "../../hooks/use-params-address-pointer";
|
||||
import useReplaceableEvent from "../../hooks/use-replaceable-event";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
import { getPageForks, getPageTitle, getPageTopic } from "../../helpers/nostr/wiki";
|
||||
import { getPageDefer, getPageForks, getPageTitle, getPageTopic } from "../../helpers/nostr/wiki";
|
||||
import MarkdownContent from "./components/markdown";
|
||||
import UserLink from "../../components/user/user-link";
|
||||
import { getWebOfTrust } from "../../services/web-of-trust";
|
||||
@@ -33,6 +33,54 @@ import FileSearch01 from "../../components/icons/file-search-01";
|
||||
import NoteZapButton from "../../components/note/note-zap-button";
|
||||
import ZapBubbles from "../../components/note/timeline-note/components/zap-bubbles";
|
||||
import QuoteRepostButton from "../../components/note/quote-repost-button";
|
||||
import WikiPageMenu from "./components/wioki-page-menu";
|
||||
|
||||
function ForkAlert({ page, address }: { page: NostrEvent; address: nip19.AddressPointer }) {
|
||||
const topic = getPageTopic(page);
|
||||
|
||||
return (
|
||||
<Alert status="info" display="flex" flexWrap="wrap">
|
||||
<AlertIcon>
|
||||
<GitBranch01 boxSize={5} />
|
||||
</AlertIcon>
|
||||
<Text>
|
||||
This page was forked from <UserLink pubkey={address.pubkey} fontWeight="bold" /> version
|
||||
</Text>
|
||||
<ButtonGroup variant="link" ml="auto">
|
||||
<Button leftIcon={<ExternalLinkIcon />} as={RouterLink} to={`/wiki/page/${nip19.naddrEncode(address)}`}>
|
||||
Original
|
||||
</Button>
|
||||
<Button
|
||||
leftIcon={<FileSearch01 />}
|
||||
as={RouterLink}
|
||||
to={`/wiki/compare/${topic}/${address.pubkey}/${page.pubkey}`}
|
||||
>
|
||||
Compare
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
function DeferAlert({ page, address }: { page: NostrEvent; address: nip19.AddressPointer }) {
|
||||
return (
|
||||
<Alert status="warning" display="flex" flexWrap="wrap">
|
||||
<AlertIcon />
|
||||
<Text>
|
||||
The author of this page has deferred to <UserLink pubkey={address.pubkey} fontWeight="bold" /> version
|
||||
</Text>
|
||||
<Button
|
||||
leftIcon={<ExternalLinkIcon />}
|
||||
as={RouterLink}
|
||||
to={`/wiki/page/${nip19.naddrEncode(address)}`}
|
||||
variant="link"
|
||||
ml="4"
|
||||
>
|
||||
View
|
||||
</Button>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
function WikiPagePage({ page }: { page: NostrEvent }) {
|
||||
const topic = getPageTopic(page);
|
||||
@@ -40,6 +88,7 @@ function WikiPagePage({ page }: { page: NostrEvent }) {
|
||||
|
||||
const pages = useSubject(timeline.timeline).filter((p) => p.pubkey !== page.pubkey);
|
||||
const { address } = getPageForks(page);
|
||||
const defer = getPageDefer(page);
|
||||
|
||||
const forks = getWebOfTrust().sortByDistanceAndConnections(
|
||||
pages.filter((p) => getPageForks(p).address?.pubkey === page.pubkey),
|
||||
@@ -58,37 +107,17 @@ function WikiPagePage({ page }: { page: NostrEvent }) {
|
||||
<ButtonGroup float="right">
|
||||
<QuoteRepostButton event={page} />
|
||||
<NoteZapButton event={page} showEventPreview={false} />
|
||||
<DebugEventButton event={page} />
|
||||
<WikiPageMenu page={page} aria-label="Page Options" />
|
||||
</ButtonGroup>
|
||||
<Heading>{getPageTitle(page)}</Heading>
|
||||
<Text>
|
||||
by <UserLink pubkey={page.pubkey} /> - <Timestamp timestamp={page.created_at} />
|
||||
</Text>
|
||||
{address && (
|
||||
<Alert status="info" display="flex" flexWrap="wrap">
|
||||
<AlertIcon>
|
||||
<GitBranch01 boxSize={5} />
|
||||
</AlertIcon>
|
||||
<Text>
|
||||
This page was forked from <UserLink pubkey={address.pubkey} fontWeight="bold" /> version
|
||||
</Text>
|
||||
<ButtonGroup variant="link" ml="auto">
|
||||
<Button leftIcon={<ExternalLinkIcon />} as={RouterLink} to={`/wiki/page/${nip19.naddrEncode(address)}`}>
|
||||
Original
|
||||
</Button>
|
||||
<Button
|
||||
leftIcon={<FileSearch01 />}
|
||||
as={RouterLink}
|
||||
to={`/wiki/compare/${topic}/${address.pubkey}/${page.pubkey}`}
|
||||
>
|
||||
Compare
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</Alert>
|
||||
)}
|
||||
{address && <ForkAlert page={page} address={address} />}
|
||||
{defer?.address && <DeferAlert page={page} address={defer.address} />}
|
||||
<Divider my="2" />
|
||||
<MarkdownContent event={page} />
|
||||
<ZapBubbles event={page} />
|
||||
<ZapBubbles event={page} mt="4" />
|
||||
</Box>
|
||||
|
||||
{forks.length > 0 && (
|
||||
|
@@ -2,14 +2,18 @@ import { useEffect, useState } from "react";
|
||||
import { Navigate } from "react-router-dom";
|
||||
import { Button, Flex, Heading, Input, Link } from "@chakra-ui/react";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import { NostrEvent } from "nostr-tools";
|
||||
import { Filter, NostrEvent } from "nostr-tools";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { Subscription, getEventUID } from "nostr-idb";
|
||||
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
import useRouteSearchValue from "../../hooks/use-route-search-value";
|
||||
import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
import { subscribeMany } from "../../helpers/relay";
|
||||
import { SEARCH_RELAYS } from "../../const";
|
||||
import { SEARCH_RELAYS, WIKI_RELAYS } from "../../const";
|
||||
import { WIKI_PAGE_KIND } from "../../helpers/nostr/wiki";
|
||||
import { localRelay } from "../../services/local-relay";
|
||||
import { getWebOfTrust } from "../../services/web-of-trust";
|
||||
import WikiPageResult from "./components/wiki-page-result";
|
||||
|
||||
export default function WikiSearchView() {
|
||||
const { value: query, setValue: setQuery } = useRouteSearchValue("q");
|
||||
@@ -22,9 +26,32 @@ export default function WikiSearchView() {
|
||||
|
||||
const [results, setResults] = useState<NostrEvent[]>([]);
|
||||
|
||||
// useEffect(() => {
|
||||
// const sub = subscribeMany([SEARCH_RELAYS]);
|
||||
// }, [query]);
|
||||
useEffect(() => {
|
||||
setResults([]);
|
||||
|
||||
const filter: Filter = { kinds: [WIKI_PAGE_KIND], search: query };
|
||||
|
||||
const seen = new Set<string>();
|
||||
const handleEvent = (event: NostrEvent) => {
|
||||
if (seen.has(getEventUID(event))) return;
|
||||
setResults((arr) => arr.concat(event));
|
||||
seen.add(getEventUID(event));
|
||||
};
|
||||
|
||||
const remoteSearchSub = subscribeMany([...SEARCH_RELAYS, ...WIKI_RELAYS], [filter], {
|
||||
onevent: handleEvent,
|
||||
oneose: () => remoteSearchSub.close(),
|
||||
});
|
||||
|
||||
if (localRelay) {
|
||||
const localSearchSub: Subscription = localRelay.subscribe([filter], {
|
||||
onevent: handleEvent,
|
||||
oneose: () => localSearchSub.close(),
|
||||
});
|
||||
}
|
||||
}, [query, setResults]);
|
||||
|
||||
const sorted = getWebOfTrust().sortByDistanceAndConnections(results, (p) => p.pubkey);
|
||||
|
||||
return (
|
||||
<VerticalPageLayout>
|
||||
@@ -49,6 +76,9 @@ export default function WikiSearchView() {
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
{sorted.map((page) => (
|
||||
<WikiPageResult key={page.id} page={page} />
|
||||
))}
|
||||
</VerticalPageLayout>
|
||||
);
|
||||
}
|
||||
|
Reference in New Issue
Block a user