diff --git a/README.md b/README.md
index 34fa10c36..3c5d56cc3 100644
--- a/README.md
+++ b/README.md
@@ -62,6 +62,8 @@
- make app handle image files
- block notes based on content
- implement NIP-56 and blocking
+- allow user to select relay or following list when fetching replies (default to my relays + following?)
+- filter list of followers by users the user has blocked/reported (stops bots/spammers from showing up at followers)
## Setup
diff --git a/src/components/icons.tsx b/src/components/icons.tsx
index da48825da..f8bd4443c 100644
--- a/src/components/icons.tsx
+++ b/src/components/icons.tsx
@@ -109,3 +109,9 @@ export const RelayIcon = createIcon({
d: "M11 14v-3h2v3h5a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1v-6a1 1 0 0 1 1-1h5zM2.51 8.837C3.835 4.864 7.584 2 12 2s8.166 2.864 9.49 6.837l-1.898.632a8.003 8.003 0 0 0-15.184 0l-1.897-.632zm3.796 1.265a6.003 6.003 0 0 1 11.388 0l-1.898.633a4.002 4.002 0 0 0-7.592 0l-1.898-.633z",
defaultProps,
});
+
+export const ExternalLinkIcon = createIcon({
+ displayName: "eternal-link-icon",
+ d: "M10 6v2H5v11h11v-5h2v6a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h6zm11-3v8h-2V6.413l-7.793 7.794-1.414-1.414L17.585 5H13V3h8z",
+ defaultProps,
+});
diff --git a/src/components/note/note-contents.tsx b/src/components/note/note-contents.tsx
index 10096bb87..e0d1039ff 100644
--- a/src/components/note/note-contents.tsx
+++ b/src/components/note/note-contents.tsx
@@ -1,5 +1,15 @@
-import React from "react";
-import { AspectRatio, Box, Image, ImageProps, Link, PinInputField, useDisclosure } from "@chakra-ui/react";
+import React, { useState } from "react";
+import {
+ AspectRatio,
+ Box,
+ Button,
+ ButtonGroup,
+ IconButton,
+ Image,
+ ImageProps,
+ Link,
+ useDisclosure,
+} from "@chakra-ui/react";
import { InlineInvoiceCard } from "../inline-invoice-card";
import { TweetEmbed } from "../tweet-embed";
import { UserLink } from "../user-link";
@@ -7,6 +17,7 @@ import { normalizeToHex } from "../../helpers/nip-19";
import { NostrEvent } from "../../types/nostr-event";
import { NoteLink } from "../note-link";
import settings from "../../services/settings";
+// import { ExternalLinkIcon } from "../icons";
const BlurredImage = (props: ImageProps) => {
const { isOpen, onToggle } = useDisclosure();
@@ -16,16 +27,19 @@ const BlurredImage = (props: ImageProps) => {
const embeds: {
regexp: RegExp;
render: (match: RegExpMatchArray, event?: NostrEvent, trusted?: boolean) => JSX.Element | string;
+ isMedia: boolean;
}[] = [
// Lightning Invoice
{
regexp: /(lightning:)?(LNBC[A-Za-z0-9]+)/im,
render: (match) => ,
+ isMedia: false,
},
// Twitter tweet
{
regexp: /^https?:\/\/twitter\.com\/(?:\#!\/)?(\w+)\/status(es)?\/(\d+)[^\s]+/im,
render: (match) => ,
+ isMedia: true,
},
// Youtube Video
{
@@ -43,6 +57,7 @@ const embeds: {
>
),
+ isMedia: true,
},
// Youtube Music
{
@@ -58,6 +73,7 @@ const embeds: {
>
),
+ isMedia: true,
},
// Tidal
{
@@ -69,6 +85,7 @@ const embeds: {
height="96"
>
),
+ isMedia: true,
},
// Spotify
{
@@ -86,6 +103,7 @@ const embeds: {
src={`https://open.spotify.com/embed/${match[1]}/${match[2]}`}
>
),
+ isMedia: true,
},
// apple music
{
@@ -100,6 +118,7 @@ const embeds: {
src={match[0].replace("music.apple.com", "embed.music.apple.com")}
>
),
+ isMedia: true,
},
// Image
{
@@ -108,6 +127,7 @@ const embeds: {
const ImageComponent = trusted || !settings.blurImages.value ? Image : BlurredImage;
return ;
},
+ isMedia: true,
},
// Video
{
@@ -117,6 +137,7 @@ const embeds: {
),
+ isMedia: true,
},
// Link
{
@@ -126,6 +147,7 @@ const embeds: {
{match[0]}
),
+ isMedia: false,
},
// npub1 and note1 links
{
@@ -139,6 +161,7 @@ const embeds: {
return match[0];
}
},
+ isMedia: false,
},
// Nostr Mention Links
{
@@ -158,26 +181,41 @@ const embeds: {
return match[0];
},
+ isMedia: false,
},
// bold text
{
regexp: /\*\*([^\n]+)\*\*/im,
render: (match) => {match[1]},
+ isMedia: false,
},
];
+const MediaEmbed = ({ children }: { children: JSX.Element | string }) => {
+ const [show, setShow] = useState(settings.autoShowMedia.value);
+
+ return show ? (
+ <>{children}>
+ ) : (
+
+
+ {/* TODO: add external link for embed */}
+ {/* } href={}/> */}
+
+ );
+};
+
function embedContent(content: string, event?: NostrEvent, trusted: boolean = false): (string | JSX.Element)[] {
- for (const { regexp, render } of embeds) {
- const match = content.match(regexp);
+ for (const embedType of embeds) {
+ const match = content.match(embedType.regexp);
if (match && match.index !== undefined) {
const before = content.slice(0, match.index);
const after = content.slice(match.index + match[0].length, content.length);
- return [
- ...embedContent(before, event, trusted),
- render(match, event, trusted),
- ...embedContent(after, event, trusted),
- ];
+ const embedRender = embedType.render(match, event, trusted);
+ const embed = embedType.isMedia ? {embedRender} : embedRender;
+
+ return [...embedContent(before, event, trusted), embed, ...embedContent(after, event, trusted)];
}
}
return [content];
diff --git a/src/helpers/user-metadata.ts b/src/helpers/user-metadata.ts
index 12e989250..1ade6e2b0 100644
--- a/src/helpers/user-metadata.ts
+++ b/src/helpers/user-metadata.ts
@@ -4,7 +4,7 @@ import { truncatedId } from "./nostr-event";
export function getUserDisplayName(metadata: Kind0ParsedContent | undefined, pubkey: string) {
if (metadata?.display_name && metadata?.name) {
- return `${metadata.display_name} (${metadata.name})`;
+ return metadata.display_name;
} else if (metadata?.name) {
return metadata.name;
}
diff --git a/src/services/settings.ts b/src/services/settings.ts
index 317d41ddb..a30902508 100644
--- a/src/services/settings.ts
+++ b/src/services/settings.ts
@@ -6,6 +6,7 @@ const settings = {
relays: new BehaviorSubject([]),
identity: new BehaviorSubject(null),
blurImages: new BehaviorSubject(true),
+ autoShowMedia: new BehaviorSubject(true),
};
async function loadSettings() {
diff --git a/src/views/settings/index.tsx b/src/views/settings/index.tsx
index 0a75faa28..9b9e6f34c 100644
--- a/src/views/settings/index.tsx
+++ b/src/views/settings/index.tsx
@@ -34,6 +34,7 @@ export const SettingsView = () => {
const navigate = useNavigate();
const relays = useSubject(settings.relays);
const blurImages = useSubject(settings.blurImages);
+ const autoShowMedia = useSubject(settings.autoShowMedia);
const [relayInputValue, setRelayInputValue] = useState("");
const { colorMode, setColorMode } = useColorMode();
@@ -155,6 +156,16 @@ export const SettingsView = () => {
onChange={(v) => settings.blurImages.next(v.target.checked)}
/>
+
+
+ Automatically show media
+
+ settings.autoShowMedia.next(v.target.checked)}
+ />
+
Show Ads