mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-09-19 03:51:34 +02:00
add option to hide media
This commit is contained in:
@@ -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
|
||||
|
||||
|
@@ -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,
|
||||
});
|
||||
|
@@ -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) => <InlineInvoiceCard paymentRequest={match[2]} />,
|
||||
isMedia: false,
|
||||
},
|
||||
// Twitter tweet
|
||||
{
|
||||
regexp: /^https?:\/\/twitter\.com\/(?:\#!\/)?(\w+)\/status(es)?\/(\d+)[^\s]+/im,
|
||||
render: (match) => <TweetEmbed href={match[0]} conversation={false} />,
|
||||
isMedia: true,
|
||||
},
|
||||
// Youtube Video
|
||||
{
|
||||
@@ -43,6 +57,7 @@ const embeds: {
|
||||
></iframe>
|
||||
</AspectRatio>
|
||||
),
|
||||
isMedia: true,
|
||||
},
|
||||
// Youtube Music
|
||||
{
|
||||
@@ -58,6 +73,7 @@ const embeds: {
|
||||
></iframe>
|
||||
</AspectRatio>
|
||||
),
|
||||
isMedia: true,
|
||||
},
|
||||
// Tidal
|
||||
{
|
||||
@@ -69,6 +85,7 @@ const embeds: {
|
||||
height="96"
|
||||
></iframe>
|
||||
),
|
||||
isMedia: true,
|
||||
},
|
||||
// Spotify
|
||||
{
|
||||
@@ -86,6 +103,7 @@ const embeds: {
|
||||
src={`https://open.spotify.com/embed/${match[1]}/${match[2]}`}
|
||||
></iframe>
|
||||
),
|
||||
isMedia: true,
|
||||
},
|
||||
// apple music
|
||||
{
|
||||
@@ -100,6 +118,7 @@ const embeds: {
|
||||
src={match[0].replace("music.apple.com", "embed.music.apple.com")}
|
||||
></iframe>
|
||||
),
|
||||
isMedia: true,
|
||||
},
|
||||
// Image
|
||||
{
|
||||
@@ -108,6 +127,7 @@ const embeds: {
|
||||
const ImageComponent = trusted || !settings.blurImages.value ? Image : BlurredImage;
|
||||
return <ImageComponent src={match[0]} width="100%" maxWidth="30rem" />;
|
||||
},
|
||||
isMedia: true,
|
||||
},
|
||||
// Video
|
||||
{
|
||||
@@ -117,6 +137,7 @@ const embeds: {
|
||||
<video src={match[0]} controls />
|
||||
</AspectRatio>
|
||||
),
|
||||
isMedia: true,
|
||||
},
|
||||
// Link
|
||||
{
|
||||
@@ -126,6 +147,7 @@ const embeds: {
|
||||
{match[0]}
|
||||
</Link>
|
||||
),
|
||||
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) => <span style={{ fontWeight: "bold" }}>{match[1]}</span>,
|
||||
isMedia: false,
|
||||
},
|
||||
];
|
||||
|
||||
const MediaEmbed = ({ children }: { children: JSX.Element | string }) => {
|
||||
const [show, setShow] = useState(settings.autoShowMedia.value);
|
||||
|
||||
return show ? (
|
||||
<>{children}</>
|
||||
) : (
|
||||
<ButtonGroup size="sm" isAttached variant="outline">
|
||||
<Button onClick={() => setShow(true)}>Show Embed</Button>
|
||||
{/* TODO: add external link for embed */}
|
||||
{/* <IconButton as="a" aria-label="Add to friends" icon={<ExternalLinkIcon />} href={}/> */}
|
||||
</ButtonGroup>
|
||||
);
|
||||
};
|
||||
|
||||
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 ? <MediaEmbed>{embedRender}</MediaEmbed> : embedRender;
|
||||
|
||||
return [...embedContent(before, event, trusted), embed, ...embedContent(after, event, trusted)];
|
||||
}
|
||||
}
|
||||
return [content];
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -6,6 +6,7 @@ const settings = {
|
||||
relays: new BehaviorSubject<string[]>([]),
|
||||
identity: new BehaviorSubject<SavedIdentity | null>(null),
|
||||
blurImages: new BehaviorSubject(true),
|
||||
autoShowMedia: new BehaviorSubject(true),
|
||||
};
|
||||
|
||||
async function loadSettings() {
|
||||
|
@@ -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)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl display="flex" alignItems="center">
|
||||
<FormLabel htmlFor="auto-show-embeds" mb="0">
|
||||
Automatically show media
|
||||
</FormLabel>
|
||||
<Switch
|
||||
id="auto-show-embeds"
|
||||
isChecked={autoShowMedia}
|
||||
onChange={(v) => settings.autoShowMedia.next(v.target.checked)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl display="flex" alignItems="center">
|
||||
<FormLabel htmlFor="show-ads" mb="0">
|
||||
Show Ads
|
||||
|
Reference in New Issue
Block a user