diff --git a/.changeset/brave-mayflies-laugh.md b/.changeset/brave-mayflies-laugh.md
new file mode 100644
index 000000000..98db919db
--- /dev/null
+++ b/.changeset/brave-mayflies-laugh.md
@@ -0,0 +1,5 @@
+---
+"nostrudel": patch
+---
+
+Dont proxy main user profile image
diff --git a/.changeset/brown-lies-hide.md b/.changeset/brown-lies-hide.md
new file mode 100644
index 000000000..eaed9c36d
--- /dev/null
+++ b/.changeset/brown-lies-hide.md
@@ -0,0 +1,5 @@
+---
+"nostrudel": patch
+---
+
+Dont blur images on shared notes
diff --git a/.changeset/fuzzy-pumpkins-allow.md b/.changeset/fuzzy-pumpkins-allow.md
new file mode 100644
index 000000000..b8dd7ff6f
--- /dev/null
+++ b/.changeset/fuzzy-pumpkins-allow.md
@@ -0,0 +1,5 @@
+---
+"nostrudel": minor
+---
+
+Make all note links nevent
diff --git a/.changeset/good-mails-play.md b/.changeset/good-mails-play.md
new file mode 100644
index 000000000..6a9b76eb3
--- /dev/null
+++ b/.changeset/good-mails-play.md
@@ -0,0 +1,5 @@
+---
+"nostrudel": patch
+---
+
+Trim note content
diff --git a/.changeset/wise-gorillas-jog.md b/.changeset/wise-gorillas-jog.md
new file mode 100644
index 000000000..3eace8701
--- /dev/null
+++ b/.changeset/wise-gorillas-jog.md
@@ -0,0 +1,5 @@
+---
+"nostrudel": minor
+---
+
+Update nostr-tools dependency
diff --git a/.changeset/witty-seals-attack.md b/.changeset/witty-seals-attack.md
new file mode 100644
index 000000000..2ee19f995
--- /dev/null
+++ b/.changeset/witty-seals-attack.md
@@ -0,0 +1,5 @@
+---
+"nostrudel": patch
+---
+
+Fix link regexp
diff --git a/package.json b/package.json
index 37559a5e7..78f70219d 100644
--- a/package.json
+++ b/package.json
@@ -5,41 +5,41 @@
"license": "MIT",
"scripts": {
"start": "vite serve",
- "build": "tsc && vite build",
+ "build": "tsc --project tsconfig.json && vite build",
"format": "prettier --ignore-path .prettierignore -w ."
},
"dependencies": {
- "@chakra-ui/icons": "^2.0.14",
- "@chakra-ui/react": "^2.4.4",
- "@changesets/cli": "^2.26.1",
- "@emotion/react": "^11.10.5",
- "@emotion/styled": "^11.10.5",
+ "@chakra-ui/icons": "^2.0.19",
+ "@chakra-ui/react": "^2.6.1",
+ "@emotion/react": "^11.11.0",
+ "@emotion/styled": "^11.11.0",
"bech32": "^2.0.0",
"framer-motion": "^7.10.3",
"idb": "^7.1.1",
"identicon.js": "^2.3.3",
- "light-bolt11-decoder": "^2.1.0",
+ "light-bolt11-decoder": "^3.0.0",
"moment": "^2.29.4",
"noble-secp256k1": "^1.2.14",
- "nostr-tools": "^1.8.3",
+ "nostr-tools": "^1.11.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
- "react-error-boundary": "^3.1.4",
- "react-hook-form": "^7.43.1",
+ "react-error-boundary": "^4.0.4",
+ "react-hook-form": "^7.43.9",
"react-qr-barcode-scanner": "^1.0.6",
- "react-router-dom": "^6.5.0",
+ "react-router-dom": "^6.11.2",
"react-singleton-hook": "^4.0.1",
"react-use": "^17.4.0",
"webln": "^0.3.2"
},
"devDependencies": {
+ "@changesets/cli": "^2.26.1",
"@types/identicon.js": "^2.3.1",
- "@types/react": "^18.0.26",
- "@types/react-dom": "^18.0.9",
- "@vitejs/plugin-react": "^3.0.0",
- "prettier": "^2.8.1",
- "typescript": "^4.9.4",
- "vite": "^4.0.2",
- "vite-plugin-pwa": "^0.14.1"
+ "@types/react": "^18.2.7",
+ "@types/react-dom": "^18.2.4",
+ "@vitejs/plugin-react": "^4.0.0",
+ "prettier": "^2.8.8",
+ "typescript": "^5.0.4",
+ "vite": "^4.3.8",
+ "vite-plugin-pwa": "^0.15.1"
}
}
diff --git a/src/components/embed-types/common.tsx b/src/components/embed-types/common.tsx
index 4b082fa89..bf1b8607e 100644
--- a/src/components/embed-types/common.tsx
+++ b/src/components/embed-types/common.tsx
@@ -2,6 +2,7 @@ import { Box, Image, ImageProps, Link, useDisclosure } from "@chakra-ui/react";
import { EmbedableContent, embedJSX } from "../../helpers/embeds";
import appSettings from "../../services/app-settings";
import { ImageGalleryLink } from "../image-gallery";
+import { useIsMobile } from "../../hooks/use-is-mobile";
const BlurredImage = (props: ImageProps) => {
const { isOpen, onOpen } = useDisclosure();
@@ -12,24 +13,26 @@ const BlurredImage = (props: ImageProps) => {
);
};
+const EmbeddedImage = ({ src, blue }: { src: string; blue: boolean }) => {
+ const isMobile = useIsMobile();
+ const ImageComponent = blue || !appSettings.value.blurImages ? Image : BlurredImage;
+ const thumbnail = appSettings.value.imageProxy
+ ? new URL(`/256,fit/${src}`, appSettings.value.imageProxy).toString()
+ : src;
+
+ return (
+
+
+
+ );
+};
+
// note1n06jceulg3gukw836ghd94p0ppwaz6u3mksnnz960d8vlcp2fnqsgx3fu9
export function embedImages(content: EmbedableContent, trusted = false) {
return embedJSX(content, {
regexp:
/https?:\/\/([\dA-z\.-]+\.[A-z\.]{2,6})((?:\/[\+~%\/\.\w\-_]*)?\.(?:svg|gif|png|jpg|jpeg|webp|avif))(\??(?:[\?#\-\+=&;%@\.\w_]*)#?(?:[\-\.\!\/\\\w]*))?/i,
- render: (match) => {
- const ImageComponent = trusted || !appSettings.value.blurImages ? Image : BlurredImage;
- const thumbnail = appSettings.value.imageProxy
- ? new URL(`/256,fit/${match[0]}`, appSettings.value.imageProxy).toString()
- : match[0];
- const src = match[0];
-
- return (
-
-
-
- );
- },
+ render: (match) => ,
name: "Image",
});
}
@@ -44,12 +47,12 @@ export function embedVideos(content: EmbedableContent) {
}
// based on http://urlregex.com/
-// note1c34vht0lu2qzrgr4az3u8jn5xl3fycr2gfpahkepthg7hzlqg26sr59amt
+// nostr:nevent1qqsvg6kt4hl79qpp5p673g7ref6r0c5jvp4yys7mmvs4m50t30sy9dgpp4mhxue69uhkummn9ekx7mqpr4mhxue69uhkummnw3ez6ur4vgh8wetvd3hhyer9wghxuet59dl66z
+// nostr:nevent1qqsymds0vlpp4f5s0dckjf4qz283pdsen0rmx8lu7ct6hpnxag2hpacpremhxue69uhkummnw3ez6un9d3shjtnwda4k7arpwfhjucm0d5q3qamnwvaz7tmwdaehgu3wwa5kueghxyq76
export function embedLinks(content: EmbedableContent) {
return embedJSX(content, {
name: "Link",
- regexp:
- /https?:\/\/([\dA-z\.-]+\.[A-z\.]{2,6})((?:\/[\+~%\/\.\w\-_]*)?\??(?:[\-\+=&;%@\.\w_]*)#?(?:[\-\.\!\/\\\w]*))?/i,
+ regexp: /https?:\/\/([\dA-z\.-]+\.[A-z\.]{2,6})(\/[\+~%\/\.\w\-_]*)?([\?#][^\s]+)?/i,
render: (match) => (
{match[0]}
diff --git a/src/components/embeded-note.tsx b/src/components/embeded-note.tsx
deleted file mode 100644
index c2dd9b2bc..000000000
--- a/src/components/embeded-note.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import { Link as RouterLink } from "react-router-dom";
-import moment from "moment";
-import { Card, CardBody, CardHeader, Flex, Heading, Link } from "@chakra-ui/react";
-import { useIsMobile } from "../hooks/use-is-mobile";
-import { NoteContents } from "./note/note-contents";
-import { useUserContacts } from "../hooks/use-user-contacts";
-import { useCurrentAccount } from "../hooks/use-current-account";
-import { NostrEvent } from "../types/nostr-event";
-import { UserAvatarLink } from "./user-avatar-link";
-import { UserLink } from "./user-link";
-import { UserDnsIdentityIcon } from "./user-dns-identity";
-import { Bech32Prefix, normalizeToBech32 } from "../helpers/nip19";
-import { convertTimestampToDate } from "../helpers/date";
-import useSubject from "../hooks/use-subject";
-import appSettings from "../services/app-settings";
-import EventVerificationIcon from "./event-verification-icon";
-import { useReadRelayUrls } from "../hooks/use-client-relays";
-
-const EmbeddedNote = ({ note }: { note: NostrEvent }) => {
- const account = useCurrentAccount();
- const { showSignatureVerification } = useSubject(appSettings);
-
- const readRelays = useReadRelayUrls();
- const contacts = useUserContacts(account.pubkey, readRelays);
- const following = contacts?.contacts || [];
-
- return (
-
-
-
-
-
-
-
-
-
-
- {showSignatureVerification && }
-
- {moment(convertTimestampToDate(note.created_at)).fromNow()}
-
-
-
-
-
-
-
- );
-};
-
-export default EmbeddedNote;
diff --git a/src/components/note-link.tsx b/src/components/note-link.tsx
index 45cdf6024..828930571 100644
--- a/src/components/note-link.tsx
+++ b/src/components/note-link.tsx
@@ -1,18 +1,31 @@
import { Link, LinkProps } from "@chakra-ui/react";
import { Link as RouterLink } from "react-router-dom";
-import { Bech32Prefix, normalizeToBech32 } from "../helpers/nip19";
import { truncatedId } from "../helpers/nostr-event";
+import { nip19 } from "nostr-tools";
+import { getEventRelays } from "../services/event-relays";
+import relayScoreboardService from "../services/relay-scoreboard";
+import { useMemo } from "react";
+
+export function getSharableEncodedNoteId(eventId: string) {
+ const relays = getEventRelays(eventId).value;
+ const ranked = relayScoreboardService.getRankedRelays(relays);
+ const onlyTwo = ranked.slice(0, 2);
+
+ if (onlyTwo.length > 0) {
+ return nip19.neventEncode({ id: eventId, relays: onlyTwo });
+ } else return nip19.noteEncode(eventId);
+}
export type NoteLinkProps = LinkProps & {
noteId: string;
};
-export const NoteLink = ({ noteId, ...props }: NoteLinkProps) => {
- const note1 = normalizeToBech32(noteId, Bech32Prefix.Note) ?? noteId;
+export const NoteLink = ({ children, noteId, color = "blue.500", ...props }: NoteLinkProps) => {
+ const encoded = useMemo(() => getSharableEncodedNoteId(noteId), [noteId]);
return (
-
- {truncatedId(note1)}
+
+ {children || truncatedId(nip19.noteEncode(noteId))}
);
};
diff --git a/src/components/note/embeded-note.tsx b/src/components/note/embeded-note.tsx
new file mode 100644
index 000000000..15c8e6a7f
--- /dev/null
+++ b/src/components/note/embeded-note.tsx
@@ -0,0 +1,44 @@
+import { Link as RouterLink } from "react-router-dom";
+import moment from "moment";
+import { Card, CardBody, CardHeader, Flex, Heading, Link } from "@chakra-ui/react";
+import { NoteContents } from "./note-contents";
+import { NostrEvent } from "../../types/nostr-event";
+import { UserAvatarLink } from "../user-avatar-link";
+import { UserLink } from "../user-link";
+import { UserDnsIdentityIcon } from "../user-dns-identity";
+import { Bech32Prefix, normalizeToBech32 } from "../../helpers/nip19";
+import { convertTimestampToDate } from "../../helpers/date";
+import useSubject from "../../hooks/use-subject";
+import appSettings from "../../services/app-settings";
+import EventVerificationIcon from "../event-verification-icon";
+import { TrustProvider } from "./trust";
+import { NoteLink } from "../note-link";
+
+export default function EmbeddedNote({ note }: { note: NostrEvent }) {
+ const { showSignatureVerification } = useSubject(appSettings);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {showSignatureVerification && }
+
+ {moment(convertTimestampToDate(note.created_at)).fromNow()}
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/note/index.tsx b/src/components/note/index.tsx
index 85b0cb53a..2c91a6969 100644
--- a/src/components/note/index.tsx
+++ b/src/components/note/index.tsx
@@ -1,13 +1,7 @@
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,
@@ -18,57 +12,29 @@ import {
Heading,
IconButton,
Link,
- Spacer,
} from "@chakra-ui/react";
import { NostrEvent } from "../../types/nostr-event";
import { UserAvatarLink } from "../user-avatar-link";
-import { Bech32Prefix, normalizeToBech32 } from "../../helpers/nip19";
-import { NoteContents } from "./note-contents";
import { NoteMenu } from "./note-menu";
-import { useUserContacts } from "../../hooks/use-user-contacts";
import { NoteRelays } from "./note-relays";
import { useIsMobile } from "../../hooks/use-is-mobile";
import { UserLink } from "../user-link";
import { UserDnsIdentityIcon } from "../user-dns-identity";
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, useExpand } from "./expanded";
+import { ExpandProvider } from "./expanded";
import useSubject from "../../hooks/use-subject";
import appSettings from "../../services/app-settings";
import EventVerificationIcon from "../event-verification-icon";
import { ReplyButton } from "./buttons/reply-button";
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 ? (
-
- ) : (
-
- );
-}
+import NoteContentWithWarning from "./note-content-with-warning";
+import { TrustProvider } from "./trust";
+import { NoteLink } from "../note-link";
export type NoteProps = {
event: NostrEvent;
@@ -83,50 +49,52 @@ export const Note = React.memo(({ event, maxHeight, variant = "outline" }: NoteP
const externalLink = useMemo(() => event.tags.find((t) => t[0] === "mostr"), [event]);
return (
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
- {showSignatureVerification && }
-
- {moment(convertTimestampToDate(event.created_at)).fromNow()}
-
-
-
-
-
-
-
-
-
-
-
-
- {showReactions && }
-
-
- {externalLink && (
- }
- aria-label="Open External"
- href={externalLink[1]}
- size="sm"
- variant="link"
- target="_blank"
- />
- )}
-
-
-
-
-
+
+
+
+
+
+ {showSignatureVerification && }
+
+ {moment(convertTimestampToDate(event.created_at)).fromNow()}
+
+
+
+
+
+
+
+
+
+
+
+
+ {showReactions && }
+
+
+ {externalLink && (
+ }
+ aria-label="Open External"
+ href={externalLink[1]}
+ size="sm"
+ variant="link"
+ target="_blank"
+ />
+ )}
+
+
+
+
+
+
);
});
diff --git a/src/components/note/note-content-with-warning.tsx b/src/components/note/note-content-with-warning.tsx
new file mode 100644
index 000000000..2bca9d7b5
--- /dev/null
+++ b/src/components/note/note-content-with-warning.tsx
@@ -0,0 +1,20 @@
+import { NostrEvent } from "../../types/nostr-event";
+
+import { NoteContents } from "./note-contents";
+import { useExpand } from "./expanded";
+import SensitiveContentWarning from "../sensitive-content-warning";
+import useAppSettings from "../../hooks/use-app-settings";
+
+export default function NoteContentWithWarning({ event, maxHeight }: { event: NostrEvent; maxHeight?: number }) {
+ const expand = useExpand();
+ const settings = useAppSettings();
+
+ const contentWarning = event.tags.find((t) => t[0] === "content-warning")?.[1];
+ const showContentWarning = settings.showContentWarning && contentWarning && !expand?.expanded;
+
+ return showContentWarning ? (
+
+ ) : (
+
+ );
+}
diff --git a/src/components/note/note-contents.tsx b/src/components/note/note-contents.tsx
index 2cb493397..fd8f8e117 100644
--- a/src/components/note/note-contents.tsx
+++ b/src/components/note/note-contents.tsx
@@ -21,9 +21,10 @@ import {
embedNostrHashtags,
} from "../embed-types";
import { ImageGalleryProvider } from "../image-gallery";
+import { useTrusted } from "./trust";
-function buildContents(event: NostrEvent | DraftNostrEvent, trusted: boolean = false) {
- let content: EmbedableContent = [event.content];
+function buildContents(event: NostrEvent | DraftNostrEvent, trusted = false) {
+ let content: EmbedableContent = [event.content.trim()];
content = embedLightningInvoice(content);
content = embedTweet(content);
@@ -59,12 +60,12 @@ const GradientOverlay = styled.div`
export type NoteContentsProps = {
event: NostrEvent | DraftNostrEvent;
- trusted?: boolean;
maxHeight?: number;
};
-export const NoteContents = React.memo(({ event, trusted, maxHeight }: NoteContentsProps) => {
- const content = buildContents(event, trusted ?? false);
+export const NoteContents = React.memo(({ event, maxHeight }: NoteContentsProps) => {
+ const trusted = useTrusted();
+ const content = buildContents(event, trusted);
const expand = useExpand();
const [innerHeight, setInnerHeight] = useState(0);
const ref = useRef(null);
diff --git a/src/components/note/note-menu.tsx b/src/components/note/note-menu.tsx
index b4ae901da..1820da1ae 100644
--- a/src/components/note/note-menu.tsx
+++ b/src/components/note/note-menu.tsx
@@ -13,7 +13,6 @@ import {
useToast,
} from "@chakra-ui/react";
import { useCopyToClipboard } from "react-use";
-import { nip19 } from "nostr-tools";
import { Bech32Prefix, normalizeToBech32 } from "../../helpers/nip19";
import { NostrEvent } from "../../types/nostr-event";
@@ -21,8 +20,6 @@ import { MenuIconButton, MenuIconButtonProps } from "../menu-icon-button";
import { ClipboardIcon, CodeIcon, LikeIcon, RepostIcon, TrashIcon } from "../icons";
import NoteReactionsModal from "./note-zaps-modal";
-import { getEventRelays } from "../../services/event-relays";
-import relayScoreboardService from "../../services/relay-scoreboard";
import NoteDebugModal from "../debug-modals/note-debug-modal";
import { useCurrentAccount } from "../../hooks/use-current-account";
import { useCallback, useState } from "react";
@@ -31,16 +28,7 @@ import { buildDeleteEvent } from "../../helpers/nostr-event";
import signingService from "../../services/signing";
import { nostrPostAction } from "../../classes/nostr-post-action";
import clientRelaysService from "../../services/client-relays";
-
-function getShareLink(eventId: string) {
- const relays = getEventRelays(eventId).value;
- const ranked = relayScoreboardService.getRankedRelays(relays);
- const onlyTwo = ranked.slice(0, 2);
-
- if (onlyTwo.length > 0) {
- return nip19.neventEncode({ id: eventId, relays: onlyTwo });
- } else return nip19.noteEncode(eventId);
-}
+import { getSharableEncodedNoteId } from "../note-link";
export const NoteMenu = ({ event, ...props }: { event: NostrEvent } & Omit) => {
const account = useCurrentAccount();
@@ -80,7 +68,7 @@ export const NoteMenu = ({ event, ...props }: { event: NostrEvent } & Omit}>
Zaps/Reactions
-