Merge branch 'next'

This commit is contained in:
hzrd149
2023-05-15 16:55:50 -05:00
15 changed files with 223 additions and 46 deletions

View File

@@ -0,0 +1,5 @@
---
"nostrudel": minor
---
Make photo flush with edge of note

View File

@@ -0,0 +1,5 @@
---
"nostrudel": minor
---
Add content warning for NIP-36 notes

View File

@@ -0,0 +1,5 @@
---
"nostrudel": minor
---
Replace laggy photo lightbox

View File

@@ -0,0 +1,5 @@
---
"nostrudel": patch
---
Fix subscription id too long

View File

@@ -19,7 +19,6 @@
"idb": "^7.1.1",
"identicon.js": "^2.3.3",
"light-bolt11-decoder": "^2.1.0",
"lightgallery": "^2.7.1",
"moment": "^2.29.4",
"noble-secp256k1": "^1.2.14",
"nostr-tools": "^1.8.3",

View File

@@ -1,10 +1,7 @@
import { Box, Image, ImageProps, Link, useDisclosure } from "@chakra-ui/react";
import { EmbedableContent, embedJSX } from "../../helpers/embeds";
import appSettings from "../../services/app-settings";
import LightGallery from "lightgallery/react";
import lgThumbnail from "lightgallery/plugins/thumbnail";
import lgZoom from "lightgallery/plugins/zoom";
import { ImageGalleryLink } from "../image-gallery";
const BlurredImage = (props: ImageProps) => {
const { isOpen, onOpen } = useDisclosure();
@@ -28,11 +25,9 @@ export function embedImages(content: EmbedableContent, trusted = false) {
const src = match[0];
return (
<LightGallery plugins={[lgThumbnail, lgZoom]} licenseKey="1234-5678-9101-1121">
<Link href={src} target="_blank" display="inline-block">
<ImageComponent src={thumbnail} cursor="pointer" maxW="30rem" />
</Link>
</LightGallery>
<ImageGalleryLink href={src} target="_blank" display="block" mx="-2">
<ImageComponent src={thumbnail} cursor="pointer" maxW="30rem" w="full" />
</ImageGalleryLink>
);
},
name: "Image",

View File

@@ -0,0 +1,76 @@
import { DownloadIcon } from "@chakra-ui/icons";
import {
LinkProps,
Link,
useDisclosure,
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalCloseButton,
ModalBody,
Image,
ModalFooter,
Button,
} from "@chakra-ui/react";
import { PropsWithChildren, createContext, useContext, useState } from "react";
const GalleryContext = createContext({
isOpen: false,
openImage(url: string) {},
});
export function useGalleryContext() {
return useContext(GalleryContext);
}
export function ImageGalleryLink({ children, href, ...props }: Omit<LinkProps, "onClick">) {
const { openImage } = useGalleryContext();
return (
<Link
{...props}
href={href}
onClick={(e) => {
if (href) {
e.preventDefault();
openImage(href);
}
}}
>
{children}
</Link>
);
}
export function ImageGalleryProvider({ children }: PropsWithChildren) {
const { isOpen, onOpen, onClose } = useDisclosure();
const [image, setImage] = useState("");
const openImage = (url: string) => {
setImage(url);
onOpen();
};
const context = { isOpen, openImage };
return (
<GalleryContext.Provider value={context}>
{children}
<Modal isOpen={isOpen} onClose={onClose} size="6xl">
<ModalOverlay />
<ModalContent>
<ModalHeader>Image</ModalHeader>
<ModalCloseButton />
<ModalBody p="0">
<Image src={image} w="full" />
</ModalBody>
<ModalFooter>
<Button colorScheme="brand" onClick={onClose}>
Close
</Button>
</ModalFooter>
</ModalContent>
</Modal>
</GalleryContext.Provider>
);
}

View File

@@ -2,7 +2,12 @@ import React, { useMemo } from "react";
import { Link as RouterLink } from "react-router-dom";
import moment from "moment";
import {
Alert,
AlertDescription,
AlertIcon,
AlertTitle,
Box,
Button,
ButtonGroup,
Card,
CardBody,
@@ -13,6 +18,7 @@ import {
Heading,
IconButton,
Link,
Spacer,
} from "@chakra-ui/react";
import { NostrEvent } from "../../types/nostr-event";
import { UserAvatarLink } from "../user-avatar-link";
@@ -29,7 +35,7 @@ import { convertTimestampToDate } from "../../helpers/date";
import { useCurrentAccount } from "../../hooks/use-current-account";
import ReactionButton from "./buttons/reaction-button";
import NoteZapButton from "./note-zap-button";
import { ExpandProvider } from "./expanded";
import { ExpandProvider, useExpand } from "./expanded";
import useSubject from "../../hooks/use-subject";
import appSettings from "../../services/app-settings";
import EventVerificationIcon from "../event-verification-icon";
@@ -38,6 +44,31 @@ import { RepostButton } from "./buttons/repost-button";
import { QuoteRepostButton } from "./buttons/quote-repost-button";
import { useReadRelayUrls } from "../../hooks/use-client-relays";
import { ExternalLinkIcon } from "../icons";
import SensitiveContentWarning from "../sensitive-content-warning";
import useAppSettings from "../../hooks/use-app-settings";
function NoteContentWithWarning({ event, maxHeight }: { event: NostrEvent; maxHeight?: number }) {
const account = useCurrentAccount();
const expand = useExpand();
const settings = useAppSettings();
const readRelays = useReadRelayUrls();
const contacts = useUserContacts(account.pubkey, readRelays);
const following = contacts?.contacts || [];
const contentWarning = event.tags.find((t) => t[0] === "content-warning")?.[1];
const showContentWarning = settings.showContentWarning && contentWarning && !expand?.expanded;
return showContentWarning ? (
<SensitiveContentWarning description={contentWarning} />
) : (
<NoteContents
event={event}
trusted={event.pubkey === account.pubkey || following.includes(event.pubkey)}
maxHeight={maxHeight}
/>
);
}
export type NoteProps = {
event: NostrEvent;
@@ -46,13 +77,8 @@ export type NoteProps = {
};
export const Note = React.memo(({ event, maxHeight, variant = "outline" }: NoteProps) => {
const isMobile = useIsMobile();
const account = useCurrentAccount();
const { showReactions, showSignatureVerification } = useSubject(appSettings);
const readRelays = useReadRelayUrls();
const contacts = useUserContacts(account.pubkey, readRelays);
const following = contacts?.contacts || [];
// find mostr external link
const externalLink = useMemo(() => event.tags.find((t) => t[0] === "mostr"), [event]);
@@ -74,12 +100,8 @@ export const Note = React.memo(({ event, maxHeight, variant = "outline" }: NoteP
</Link>
</Flex>
</CardHeader>
<CardBody px="2" py="0">
<NoteContents
event={event}
trusted={event.pubkey === account.pubkey || following.includes(event.pubkey)}
maxHeight={maxHeight}
/>
<CardBody p="0">
<NoteContentWithWarning event={event} maxHeight={maxHeight} />
</CardBody>
<CardFooter padding="2" display="flex" gap="2">
<ButtonGroup size="sm" variant="link">

View File

@@ -1,5 +1,5 @@
import React, { useCallback, useEffect, useRef, useState } from "react";
import { Box } from "@chakra-ui/react";
import { Box, Text } from "@chakra-ui/react";
import { DraftNostrEvent, NostrEvent } from "../../types/nostr-event";
import styled from "@emotion/styled";
import { useExpand } from "./expanded";
@@ -20,6 +20,7 @@ import {
embedAppleMusic,
embedNostrHashtags,
} from "../embed-types";
import { ImageGalleryProvider } from "../image-gallery";
function buildContents(event: NostrEvent | DraftNostrEvent, trusted: boolean = false) {
let content: EmbedableContent = [event.content];
@@ -82,19 +83,28 @@ export const NoteContents = React.memo(({ event, trusted, maxHeight }: NoteConte
const showOverlay = !!maxHeight && !expand?.expanded && innerHeight > maxHeight;
return (
<Box
whiteSpace="pre-wrap"
maxHeight={!expand?.expanded ? maxHeight : undefined}
position="relative"
overflow={maxHeight && !expand?.expanded ? "hidden" : "initial"}
onLoad={() => testHeight()}
>
<div ref={ref}>
{content.map((part, i) => (
<span key={"part-" + i}>{part}</span>
))}
</div>
{showOverlay && <GradientOverlay onClick={expand?.onExpand} />}
</Box>
<ImageGalleryProvider>
<Box
whiteSpace="pre-wrap"
maxHeight={!expand?.expanded ? maxHeight : undefined}
position="relative"
overflow={maxHeight && !expand?.expanded ? "hidden" : "initial"}
onLoad={() => testHeight()}
px="2"
>
<div ref={ref}>
{content.map((part, i) =>
typeof part === "string" ? (
<Text as="span" key={"part-" + i}>
{part}
</Text>
) : (
React.cloneElement(part, { key: "part-" + i })
)
)}
</div>
{showOverlay && <GradientOverlay onClick={expand?.onExpand} />}
</Box>
</ImageGalleryProvider>
);
});

View File

@@ -0,0 +1,42 @@
import { Alert, AlertDescription, AlertIcon, AlertProps, AlertTitle, Button, Spacer, useModal } from "@chakra-ui/react";
import { useIsMobile } from "../hooks/use-is-mobile";
import { useExpand } from "./note/expanded";
export default function SensitiveContentWarning({ description }: { description: string } & AlertProps) {
const isMobile = useIsMobile();
const expand = useExpand();
if (isMobile) {
return (
<Alert
status="warning"
flexDirection="column"
alignItems="center"
justifyContent="center"
textAlign="center"
height="200px"
>
<AlertIcon boxSize="40px" mr={0} />
<AlertTitle mt={4} mb={1} fontSize="lg">
Sensitive Content
</AlertTitle>
<AlertDescription maxWidth="sm">{description}</AlertDescription>
<Button mt="2" onClick={expand?.onExpand} colorScheme="red">
Show
</Button>
</Alert>
);
}
return (
<Alert status="warning">
<AlertIcon boxSize="30px" mr="4" />
<AlertTitle fontSize="lg">Sensitive Content</AlertTitle>
<AlertDescription maxWidth="sm">{description}</AlertDescription>
<Spacer />
<Button mt="2" onClick={expand?.onExpand} colorScheme="red">
Show
</Button>
</Alert>
);
}

View File

@@ -3,10 +3,6 @@ import { createRoot } from "react-dom/client";
import { App } from "./app";
import { Providers } from "./providers";
import "lightgallery/css/lightgallery.css";
import "lightgallery/css/lg-zoom.css";
import "lightgallery/css/lg-thumbnail.css";
// register nostr: protocol handler
try {
navigator.registerProtocolHandler("web+nostr", new URL("/l/%s", location.origin).toString());

View File

@@ -26,6 +26,7 @@ export type AppSettings = {
zapAmounts: number[];
primaryColor: string;
imageProxy: string;
showContentWarning: boolean;
};
export const defaultSettings: AppSettings = {
@@ -39,6 +40,7 @@ export const defaultSettings: AppSettings = {
zapAmounts: [50, 200, 500, 1000],
primaryColor: "#8DB600",
imageProxy: "",
showContentWarning: true,
};
function parseAppSettings(event: NostrEvent): AppSettings {

View File

@@ -3,7 +3,7 @@ import { Button, Flex, Spinner } from "@chakra-ui/react";
import moment from "moment";
import { Note } from "../../components/note";
import { useTimelineLoader } from "../../hooks/use-timeline-loader";
import { isReply } from "../../helpers/nostr-event";
import { isReply, truncatedId } from "../../helpers/nostr-event";
import { useAppTitle } from "../../hooks/use-app-title";
import { useReadRelayUrls } from "../../hooks/use-client-relays";
import { useCurrentAccount } from "../../hooks/use-current-account";
@@ -69,7 +69,7 @@ export default function DiscoverTab() {
const throttledPubkeys = useThrottle(pubkeys, 1000);
const { events, loading, loadMore } = useTimelineLoader(
`${account.pubkey}-discover`,
`${truncatedId(account.pubkey)}-discover`,
relays,
{ authors: throttledPubkeys, kinds: [1], since: moment().subtract(1, "hour").unix() },
{ pageSize: moment.duration(1, "hour").asSeconds(), enabled: throttledPubkeys.length > 0 }

View File

@@ -2,7 +2,7 @@ import { Button, Flex, FormControl, FormLabel, Spinner, Switch } from "@chakra-u
import { useSearchParams } from "react-router-dom";
import moment from "moment";
import { Note } from "../../components/note";
import { isReply } from "../../helpers/nostr-event";
import { isReply, truncatedId } from "../../helpers/nostr-event";
import { useTimelineLoader } from "../../hooks/use-timeline-loader";
import { useUserContacts } from "../../hooks/use-user-contacts";
import { AddIcon } from "@chakra-ui/icons";
@@ -25,7 +25,7 @@ export default function FollowingTab() {
const following = contacts?.contacts || [];
const { events, loading, loadMore } = useTimelineLoader(
`${account.pubkey}-following-posts`,
`${truncatedId(account.pubkey)}-following-posts`,
readRelays,
{ authors: following, kinds: [1, 6], since: moment().subtract(2, "hour").unix() },
{ pageSize: moment.duration(2, "hour").asSeconds(), enabled: following.length > 0 }

View File

@@ -44,7 +44,7 @@ function ColorPicker({ value, onPickColor, ...props }: { onPickColor?: (color: s
}
export default function DisplaySettings() {
const { blurImages, colorMode, primaryColor, updateSettings } = useAppSettings();
const { blurImages, colorMode, primaryColor, updateSettings, showContentWarning } = useAppSettings();
return (
<AccordionItem>
@@ -106,6 +106,21 @@ export default function DisplaySettings() {
<span>Enabled: blur images for people you aren't following</span>
</FormHelperText>
</FormControl>
<FormControl>
<Flex alignItems="center">
<FormLabel htmlFor="show-content-warning" mb="0">
Show content warning
</FormLabel>
<Switch
id="show-content-warning"
isChecked={showContentWarning}
onChange={(v) => updateSettings({ showContentWarning: v.target.checked })}
/>
</Flex>
<FormHelperText>
<span>Enabled: shows a warning for notes with NIP-36 Content Warning</span>
</FormHelperText>
</FormControl>
<FormControl>
<Flex alignItems="center">
<FormLabel htmlFor="show-ads" mb="0">