mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-09-19 12:00:32 +02:00
add simple corrections feed
This commit is contained in:
@@ -70,6 +70,7 @@
|
||||
"nostr-wasm": "^0.1.0",
|
||||
"react": "^18.2.0",
|
||||
"react-chartjs-2": "^5.2.0",
|
||||
"react-diff-viewer-continued": "^3.4.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-error-boundary": "^4.0.11",
|
||||
"react-force-graph-2d": "^1.25.1",
|
||||
|
10
src/app.tsx
10
src/app.tsx
@@ -77,12 +77,9 @@ import MediaServersView from "./views/relays/media-servers";
|
||||
import NIP05RelaysView from "./views/relays/nip05";
|
||||
import ContactListRelaysView from "./views/relays/contact-list";
|
||||
import UserDMsTab from "./views/user/dms";
|
||||
import DMTimelineView from "./views/tools/dm-timeline";
|
||||
import LoginNostrConnectView from "./views/signin/nostr-connect";
|
||||
import ThreadsNotificationsView from "./views/notifications/threads";
|
||||
import DVMFeedView from "./views/dvm-feed/feed";
|
||||
import TransformNoteView from "./views/tools/transform-note";
|
||||
import SatelliteCDNView from "./views/tools/satellite-cdn";
|
||||
import OtherStuffView from "./views/other-stuff";
|
||||
import { RouteProviders } from "./providers/route";
|
||||
import LaunchpadView from "./views/launchpad";
|
||||
@@ -97,13 +94,16 @@ const UserTracksTab = lazy(() => import("./views/user/tracks"));
|
||||
const UserVideosTab = lazy(() => import("./views/user/videos"));
|
||||
|
||||
const ToolsHomeView = lazy(() => import("./views/tools"));
|
||||
const WotTestView = lazy(() => import("./views/tools/wot-test"));
|
||||
const StreamModerationView = lazy(() => import("./views/streams/dashboard"));
|
||||
const NetworkMuteGraphView = lazy(() => import("./views/tools/network-mute-graph"));
|
||||
const NetworkDMGraphView = lazy(() => import("./views/tools/network-dm-graph"));
|
||||
const UnknownTimelineView = lazy(() => import("./views/tools/unknown-event-feed"));
|
||||
const EventConsoleView = lazy(() => import("./views/tools/event-console"));
|
||||
const EventPublisherView = lazy(() => import("./views/tools/event-publisher"));
|
||||
const DMTimelineView = lazy(() => import("./views/tools/dm-timeline"));
|
||||
const TransformNoteView = lazy(() => import("./views/tools/transform-note"));
|
||||
const SatelliteCDNView = lazy(() => import("./views/tools/satellite-cdn"));
|
||||
const CorrectionsFeedView = lazy(() => import("./views/tools/corrections"));
|
||||
|
||||
const UserStreamsTab = lazy(() => import("./views/user/streams"));
|
||||
const StreamsView = lazy(() => import("./views/streams"));
|
||||
@@ -339,7 +339,6 @@ const router = createHashRouter([
|
||||
path: "tools",
|
||||
children: [
|
||||
{ path: "", element: <ToolsHomeView /> },
|
||||
{ path: "wot-test", element: <WotTestView /> },
|
||||
{ path: "network-mute-graph", element: <NetworkMuteGraphView /> },
|
||||
{ path: "network-dm-graph", element: <NetworkDMGraphView /> },
|
||||
{ path: "dm-timeline", element: <DMTimelineView /> },
|
||||
@@ -348,6 +347,7 @@ const router = createHashRouter([
|
||||
{ path: "unknown", element: <UnknownTimelineView /> },
|
||||
{ path: "console", element: <EventConsoleView /> },
|
||||
{ path: "publisher", element: <EventPublisherView /> },
|
||||
{ path: "corrections", element: <CorrectionsFeedView /> },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
52
src/components/diff/diff-viewer.tsx
Normal file
52
src/components/diff/diff-viewer.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import { ColorModeContext, useColorMode } from "@chakra-ui/react";
|
||||
import ReactDiffViewer from "react-diff-viewer-continued";
|
||||
import computeStyles, { ReactDiffViewerStylesOverride } from "react-diff-viewer-continued/lib/src/styles";
|
||||
|
||||
const fixedStyles: ReactDiffViewerStylesOverride = {
|
||||
codeFold: {
|
||||
height: "auto",
|
||||
},
|
||||
contentText: {
|
||||
lineHeight: "initial !important",
|
||||
fontFamily: "inherit",
|
||||
},
|
||||
};
|
||||
|
||||
// NOTE: This is a hack to hard code the emotion styles for react-diff-viewer-continued
|
||||
// because the component was creating a new stylesheet for every instance (I guess the developer has no idea how to use @emotion/css)
|
||||
let computed: ReturnType<typeof computeStyles>;
|
||||
function getComputedStyles(dark = false) {
|
||||
if (!computed) computed = computeStyles(fixedStyles, dark, "code-better");
|
||||
return computed;
|
||||
}
|
||||
|
||||
class FixedReactDiffViewer extends ReactDiffViewer {
|
||||
static contextType = ColorModeContext;
|
||||
|
||||
// @ts-expect-error
|
||||
constructor(...args) {
|
||||
// @ts-expect-error
|
||||
super(...args);
|
||||
// @ts-expect-error
|
||||
this.computeStyles = () => {
|
||||
// @ts-expect-error
|
||||
return getComputedStyles(this.context.colorMode === "dark");
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default function DiffViewer({ oldValue, newValue }: { oldValue: string; newValue: string }) {
|
||||
const { colorMode } = useColorMode();
|
||||
|
||||
return (
|
||||
<FixedReactDiffViewer
|
||||
oldValue={oldValue}
|
||||
newValue={newValue}
|
||||
useDarkTheme={colorMode === "dark"}
|
||||
hideLineNumbers
|
||||
splitView={false}
|
||||
//@ts-expect-error
|
||||
compareMethod="diffWords"
|
||||
/>
|
||||
);
|
||||
}
|
@@ -21,6 +21,7 @@ import Users01 from "../../components/icons/users-01";
|
||||
import Film02 from "../../components/icons/film-02";
|
||||
import MessageQuestionSquare from "../../components/icons/message-question-square";
|
||||
import UploadCloud01 from "../../components/icons/upload-cloud-01";
|
||||
import Edit04 from "../../components/icons/edit-04";
|
||||
|
||||
export const internalApps: App[] = [
|
||||
{
|
||||
@@ -113,7 +114,13 @@ export const internalTools: App[] = [
|
||||
id: "publisher",
|
||||
to: "/tools/publisher ",
|
||||
},
|
||||
{ title: "WoT Test", description: "Just a test for now", icon: Users01, id: "wot-test", to: "/tools/wot-test" },
|
||||
{
|
||||
title: "Corrections Feed",
|
||||
description: "A feed of post edits",
|
||||
icon: Edit04,
|
||||
id: "corrections",
|
||||
to: "/tools/corrections ",
|
||||
},
|
||||
];
|
||||
|
||||
export const externalTools: App[] = [
|
||||
|
43
src/views/tools/corrections/correction-card.tsx
Normal file
43
src/views/tools/corrections/correction-card.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import { Suspense, lazy, useMemo, useState } from "react";
|
||||
import { NostrEvent } from "nostr-tools";
|
||||
import { Button, ButtonGroup, Spinner, useColorMode } from "@chakra-ui/react";
|
||||
|
||||
import { isETag } from "../../../types/nostr-event";
|
||||
import useSingleEvent from "../../../hooks/use-single-event";
|
||||
import TimelineItem from "../../../components/timeline-page/generic-note-timeline/timeline-item";
|
||||
import DiffViewer from "../../../components/diff/diff-viewer";
|
||||
|
||||
export default function CorrectionCard({ correction }: { correction: NostrEvent }) {
|
||||
const originalId = correction.tags.find(isETag)?.[1];
|
||||
const original = useSingleEvent(originalId);
|
||||
|
||||
// NOTE: produces an invalid event
|
||||
const modified = useMemo(() => original && { ...original, content: correction.content }, [correction, original]);
|
||||
|
||||
const [show, setShow] = useState("modified");
|
||||
const showEvent = show === "original" ? original : modified;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ButtonGroup isAttached ml="auto" size="xs">
|
||||
<Button onClick={() => setShow("original")} variant={show === "original" ? "solid" : "outline"}>
|
||||
Original
|
||||
</Button>
|
||||
<Button onClick={() => setShow("modified")} variant={show === "modified" ? "solid" : "outline"}>
|
||||
Modified
|
||||
</Button>
|
||||
<Button onClick={() => setShow("diff")} variant={show === "diff" ? "solid" : "outline"}>
|
||||
Diff
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
|
||||
{show === "diff" ? (
|
||||
<Suspense fallback={<Spinner />}>
|
||||
<DiffViewer oldValue={original?.content || ""} newValue={correction.content} />
|
||||
</Suspense>
|
||||
) : (
|
||||
showEvent && <TimelineItem event={showEvent} visible />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
44
src/views/tools/corrections/index.tsx
Normal file
44
src/views/tools/corrections/index.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import { Flex, Heading } from "@chakra-ui/react";
|
||||
|
||||
import VerticalPageLayout from "../../../components/vertical-page-layout";
|
||||
import { useReadRelays } from "../../../hooks/use-client-relays";
|
||||
import useSubject from "../../../hooks/use-subject";
|
||||
import useTimelineLoader from "../../../hooks/use-timeline-loader";
|
||||
import PeopleListProvider, { usePeopleListContext } from "../../../providers/local/people-list-provider";
|
||||
import BackButton from "../../../components/router/back-button";
|
||||
import PeopleListSelection from "../../../components/people-list-selection/people-list-selection";
|
||||
import CorrectionCard from "./correction-card";
|
||||
|
||||
function CorrectionsPage() {
|
||||
const { listId, filter } = usePeopleListContext();
|
||||
const readRelays = useReadRelays();
|
||||
const timeline = useTimelineLoader(
|
||||
`${listId}-corrections`,
|
||||
readRelays,
|
||||
filter ? [{ kinds: [1010], ...filter }] : undefined,
|
||||
);
|
||||
|
||||
const corrections = useSubject(timeline.timeline);
|
||||
|
||||
return (
|
||||
<VerticalPageLayout>
|
||||
<Flex gap="2" alignItems="center" wrap="wrap">
|
||||
<BackButton size="sm" />
|
||||
<Heading size="md">Corrections</Heading>
|
||||
<PeopleListSelection />
|
||||
</Flex>
|
||||
|
||||
{corrections.map((correction) => (
|
||||
<CorrectionCard correction={correction} key={correction.id} />
|
||||
))}
|
||||
</VerticalPageLayout>
|
||||
);
|
||||
}
|
||||
|
||||
export default function CorrectionsFeedView() {
|
||||
return (
|
||||
<PeopleListProvider>
|
||||
<CorrectionsPage />
|
||||
</PeopleListProvider>
|
||||
);
|
||||
}
|
@@ -1,66 +0,0 @@
|
||||
import { memo, useMemo, useState } from "react";
|
||||
import { Button, Flex, Select, SimpleGrid, Text } from "@chakra-ui/react";
|
||||
|
||||
import useCurrentAccount from "../../hooks/use-current-account";
|
||||
import RequireCurrentAccount from "../../providers/route/require-current-account";
|
||||
import { useNetworkConnectionCount } from "../../hooks/use-user-network";
|
||||
import UserAvatarLink from "../../components/user/user-avatar-link";
|
||||
import UserLink from "../../components/user/user-link";
|
||||
import { ChevronLeftIcon } from "../../components/icons";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
|
||||
const User = memo(({ pubkey, count }: { pubkey: string; count: number }) => (
|
||||
<Flex gap="2" overflow="hidden">
|
||||
<UserAvatarLink pubkey={pubkey} noProxy size="sm" />
|
||||
<UserLink pubkey={pubkey} isTruncated />
|
||||
<Text>({count})</Text>
|
||||
</Flex>
|
||||
));
|
||||
|
||||
function WotTestPage() {
|
||||
const navigate = useNavigate();
|
||||
const account = useCurrentAccount()!;
|
||||
const [range, setRange] = useState("50-100");
|
||||
|
||||
const network = useNetworkConnectionCount(account.pubkey);
|
||||
const filteredPubkeys = useMemo(() => {
|
||||
if (range.endsWith("+")) {
|
||||
const min = parseInt(range.replace("+", ""));
|
||||
return network.filter((p) => p.count > min);
|
||||
}
|
||||
const [min, max] = range.split("-").map((v) => parseInt(v));
|
||||
return network.filter((p) => p.count > min && p.count <= max);
|
||||
}, [range, network]);
|
||||
|
||||
return (
|
||||
<VerticalPageLayout>
|
||||
<Flex gap="2">
|
||||
<Button leftIcon={<ChevronLeftIcon />} onClick={() => navigate(-1)}>
|
||||
Back
|
||||
</Button>
|
||||
<Select value={range} onChange={(e) => setRange(e.target.value)}>
|
||||
<option value="0-1">0-1 ({network.filter((p) => p.count <= 1).length})</option>
|
||||
<option value="1-10">1-10 ({network.filter((p) => p.count > 1 && p.count < 10).length})</option>
|
||||
<option value="10-20">10-20 ({network.filter((p) => p.count > 10 && p.count < 20).length})</option>
|
||||
<option value="20-50">20-50 ({network.filter((p) => p.count > 20 && p.count < 50).length})</option>
|
||||
<option value="50-100">50-100 ({network.filter((p) => p.count > 50 && p.count < 100).length})</option>
|
||||
<option value="100+">100+ ({network.filter((p) => p.count > 100).length})</option>
|
||||
</Select>
|
||||
</Flex>
|
||||
<SimpleGrid spacing="2" columns={5}>
|
||||
{filteredPubkeys.map(({ pubkey, count }) => (
|
||||
<User key={pubkey} pubkey={pubkey} count={count} />
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</VerticalPageLayout>
|
||||
);
|
||||
}
|
||||
|
||||
export default function WotTestView() {
|
||||
return (
|
||||
<RequireCurrentAccount>
|
||||
<WotTestPage />
|
||||
</RequireCurrentAccount>
|
||||
);
|
||||
}
|
@@ -1,7 +1,12 @@
|
||||
import { NostrEvent } from "nostr-tools";
|
||||
import { WIKI_PAGE_KIND } from "../../../helpers/nostr/wiki";
|
||||
import { useReadRelays } from "../../../hooks/use-client-relays";
|
||||
import useTimelineLoader from "../../../hooks/use-timeline-loader";
|
||||
|
||||
function noEmptyEvent(event: NostrEvent) {
|
||||
return event.content.length > 0;
|
||||
}
|
||||
|
||||
export default function useWikiTopicTimeline(topic: string) {
|
||||
const relays = useReadRelays(["wss://relay.wikifreedia.xyz/"]);
|
||||
|
||||
@@ -9,6 +14,6 @@ export default function useWikiTopicTimeline(topic: string) {
|
||||
`wiki-${topic.toLocaleLowerCase()}-pages`,
|
||||
relays,
|
||||
[{ kinds: [WIKI_PAGE_KIND], "#d": [topic.toLocaleLowerCase()] }],
|
||||
{ eventFilter: (e) => e.content.length > 0 },
|
||||
{ eventFilter: noEmptyEvent },
|
||||
);
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { NostrEvent } from "nostr-tools";
|
||||
import { Box, Card, Divider, Flex, Heading, Link, Spinner, Text } from "@chakra-ui/react";
|
||||
import { Box, ButtonGroup, Divider, Flex, Heading, Link, Spinner, Text } from "@chakra-ui/react";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
|
||||
import useParamsAddressPointer from "../../hooks/use-params-address-pointer";
|
||||
@@ -14,6 +14,7 @@ import useSubject from "../../hooks/use-subject";
|
||||
import useWikiTopicTimeline from "./hooks/use-wiki-topic-timeline";
|
||||
import WikiPageResult from "./components/wiki-page-result";
|
||||
import Timestamp from "../../components/timestamp";
|
||||
import DebugEventButton from "../../components/debug-modal/debug-event-button";
|
||||
|
||||
function WikiPagePage({ page }: { page: NostrEvent }) {
|
||||
const topic = getPageTopic(page);
|
||||
@@ -34,6 +35,9 @@ function WikiPagePage({ page }: { page: NostrEvent }) {
|
||||
</Flex>
|
||||
|
||||
<Box>
|
||||
<ButtonGroup float="right">
|
||||
<DebugEventButton event={page} />
|
||||
</ButtonGroup>
|
||||
<Heading>{getPageTitle(page)}</Heading>
|
||||
<Text>
|
||||
by <UserLink pubkey={page.pubkey} /> - <Timestamp timestamp={page.created_at} />
|
||||
|
32
yarn.lock
32
yarn.lock
@@ -2145,6 +2145,17 @@
|
||||
"@emotion/weak-memoize" "^0.3.1"
|
||||
stylis "4.2.0"
|
||||
|
||||
"@emotion/css@^11.11.2":
|
||||
version "11.11.2"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/css/-/css-11.11.2.tgz#e5fa081d0c6e335352e1bc2b05953b61832dca5a"
|
||||
integrity sha512-VJxe1ucoMYMS7DkiMdC2T7PWNbrEI0a39YRiyDvK2qq4lXwjRbVP/z4lpG+odCsRzadlR+1ywwrTzhdm5HNdew==
|
||||
dependencies:
|
||||
"@emotion/babel-plugin" "^11.11.0"
|
||||
"@emotion/cache" "^11.11.0"
|
||||
"@emotion/serialize" "^1.1.2"
|
||||
"@emotion/sheet" "^1.2.2"
|
||||
"@emotion/utils" "^1.2.1"
|
||||
|
||||
"@emotion/hash@^0.9.1":
|
||||
version "0.9.1"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.1.tgz#4ffb0055f7ef676ebc3a5a91fb621393294e2f43"
|
||||
@@ -4102,6 +4113,11 @@ devlop@^1.0.0, devlop@^1.1.0:
|
||||
dependencies:
|
||||
dequal "^2.0.0"
|
||||
|
||||
diff@^5.1.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.0.tgz#26ded047cd1179b78b9537d5ef725503ce1ae531"
|
||||
integrity sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==
|
||||
|
||||
dir-glob@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f"
|
||||
@@ -5600,6 +5616,11 @@ mdn-data@2.0.14:
|
||||
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50"
|
||||
integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==
|
||||
|
||||
memoize-one@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-6.0.0.tgz#b2591b871ed82948aee4727dc6abceeeac8c1045"
|
||||
integrity sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==
|
||||
|
||||
meow@^6.0.0:
|
||||
version "6.1.1"
|
||||
resolved "https://registry.yarnpkg.com/meow/-/meow-6.1.1.tgz#1ad64c4b76b2a24dfb2f635fddcadf320d251467"
|
||||
@@ -6439,6 +6460,17 @@ react-clientside-effect@^1.2.6:
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.12.13"
|
||||
|
||||
react-diff-viewer-continued@^3.4.0:
|
||||
version "3.4.0"
|
||||
resolved "https://registry.yarnpkg.com/react-diff-viewer-continued/-/react-diff-viewer-continued-3.4.0.tgz#0501ffb2b5ab740f88b9ae5f18771aa90d3803c2"
|
||||
integrity sha512-kMZmUyb3Pv5L9vUtCfIGYsdOHs8mUojblGy1U1Sm0D7FhAOEsH9QhnngEIRo5hXWIPNGupNRJls1TJ6Eqx84eg==
|
||||
dependencies:
|
||||
"@emotion/css" "^11.11.2"
|
||||
classnames "^2.3.2"
|
||||
diff "^5.1.0"
|
||||
memoize-one "^6.0.0"
|
||||
prop-types "^15.8.1"
|
||||
|
||||
react-dnd-html5-backend@^16.0.1:
|
||||
version "16.0.1"
|
||||
resolved "https://registry.yarnpkg.com/react-dnd-html5-backend/-/react-dnd-html5-backend-16.0.1.tgz#87faef15845d512a23b3c08d29ecfd34871688b6"
|
||||
|
Reference in New Issue
Block a user