mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-03-28 18:53:47 +01:00
Fallback to users blossom servers on broken image links with sha256
This commit is contained in:
parent
10ea8c9531
commit
91c9ad1cd0
.changeset
package.jsonsrc
components
embed-event/event-types
external-embeds/types
note/timeline-note
post-modal
timeline-page
helpers
hooks
providers/local
views
channels/components
dms/components
files
lists/list
notifications/components
relays/media-servers
streams/stream/stream-chat
thread/components
tools/transform-note/translation
torrents/components
user
wiki
5
.changeset/shaggy-pianos-invent.md
Normal file
5
.changeset/shaggy-pianos-invent.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"nostrudel": minor
|
||||
---
|
||||
|
||||
Fallback to users blossom servers on broken image links with sha256
|
@ -42,7 +42,7 @@
|
||||
"@uiw/react-codemirror": "^4.21.21",
|
||||
"@webscopeio/react-textarea-autocomplete": "^4.9.2",
|
||||
"bech32": "^2.0.0",
|
||||
"blossom-client-sdk": "^0.5.1",
|
||||
"blossom-client-sdk": "^0.6.0",
|
||||
"blossom-drive-sdk": "^0.3.0",
|
||||
"blurhash": "^2.0.5",
|
||||
"chart.js": "^4.4.1",
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Card, CardBody, CardHeader, CardProps, IconButton, LinkBox, Text, useDisclosure } from "@chakra-ui/react";
|
||||
|
||||
import { NostrEvent } from "../../../types/nostr-event";
|
||||
import { TrustProvider } from "../../../providers/local/trust";
|
||||
import { TrustProvider } from "../../../providers/local/trust-provider";
|
||||
import UserAvatarLink from "../../user/user-avatar-link";
|
||||
import UserLink from "../../user/user-link";
|
||||
import Timestamp from "../../timestamp";
|
||||
|
@ -8,7 +8,7 @@ import UserLink from "../../user/user-link";
|
||||
import useSubject from "../../../hooks/use-subject";
|
||||
import appSettings from "../../../services/settings/app-settings";
|
||||
import EventVerificationIcon from "../../common-event/event-verification-icon";
|
||||
import { TrustProvider } from "../../../providers/local/trust";
|
||||
import { TrustProvider } from "../../../providers/local/trust-provider";
|
||||
import { NoteLink } from "../../note/note-link";
|
||||
import Timestamp from "../../timestamp";
|
||||
import { getSharableEventAddress } from "../../../helpers/nip19";
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Card, CardProps, Flex, LinkBox, Spacer, Text } from "@chakra-ui/react";
|
||||
|
||||
import { NostrEvent } from "../../../types/nostr-event";
|
||||
import { TrustProvider } from "../../../providers/local/trust";
|
||||
import { TrustProvider } from "../../../providers/local/trust-provider";
|
||||
import UserAvatarLink from "../../user/user-avatar-link";
|
||||
import UserLink from "../../user/user-link";
|
||||
import Timestamp from "../../timestamp";
|
||||
|
@ -7,7 +7,7 @@ import UserLink from "../../user/user-link";
|
||||
import useSubject from "../../../hooks/use-subject";
|
||||
import appSettings from "../../../services/settings/app-settings";
|
||||
import EventVerificationIcon from "../../common-event/event-verification-icon";
|
||||
import { TrustProvider } from "../../../providers/local/trust";
|
||||
import { TrustProvider } from "../../../providers/local/trust-provider";
|
||||
import Timestamp from "../../timestamp";
|
||||
import { CompactNoteContent } from "../../compact-note-content";
|
||||
import HoverLinkOverlay from "../../hover-link-overlay";
|
||||
|
@ -4,6 +4,7 @@ import {
|
||||
MutableRefObject,
|
||||
forwardRef,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
@ -21,24 +22,23 @@ import {
|
||||
ModalContent,
|
||||
ModalHeader,
|
||||
ModalOverlay,
|
||||
Popover,
|
||||
PopoverArrow,
|
||||
PopoverBody,
|
||||
PopoverCloseButton,
|
||||
PopoverContent,
|
||||
PopoverHeader,
|
||||
PopoverTrigger,
|
||||
Text,
|
||||
Tooltip,
|
||||
useDisclosure,
|
||||
useToast,
|
||||
} from "@chakra-ui/react";
|
||||
import { sha256 } from "@noble/hashes/sha256";
|
||||
import { bytesToHex } from "@noble/hashes/utils";
|
||||
import {
|
||||
getHashFromURL,
|
||||
handleImageFallbacks,
|
||||
getServersFromServerListEvent,
|
||||
USER_BLOSSOM_SERVER_LIST_KIND,
|
||||
} from "blossom-client-sdk";
|
||||
|
||||
import { EmbedableContent, defaultGetLocation } from "../../../helpers/embeds";
|
||||
import { getMatchLink } from "../../../helpers/regexp";
|
||||
import { useRegisterSlide } from "../../lightbox-provider";
|
||||
import { getMediaHashFromURL, isImageURL } from "../../../helpers/url";
|
||||
import { isImageURL } from "../../../helpers/url";
|
||||
import PhotoGallery, { PhotoWithoutSize } from "../../photo-gallery";
|
||||
import { NostrEvent } from "../../../types/nostr-event";
|
||||
import useAppSettings from "../../../hooks/use-app-settings";
|
||||
@ -46,7 +46,9 @@ import { useBreakpointValue } from "../../../providers/global/breakpoint-provide
|
||||
import useElementTrustBlur from "../../../hooks/use-element-trust-blur";
|
||||
import { buildImageProxyURL } from "../../../helpers/image";
|
||||
import ExpandableEmbed from "../expandable-embed";
|
||||
import { bytesToHex } from "@noble/hashes/utils";
|
||||
import { useMediaOwnerContext } from "../../../providers/local/media-owner-provider";
|
||||
import replaceableEventsService from "../../../services/replaceable-events";
|
||||
import clientRelaysService from "../../../services/client-relays";
|
||||
|
||||
export type TrustImageProps = ImageProps;
|
||||
|
||||
@ -74,38 +76,62 @@ export type EmbeddedImageProps = Omit<LinkProps, "children" | "href" | "onClick"
|
||||
imageProps?: TrustImageProps;
|
||||
};
|
||||
|
||||
function getPubkeyMediaServers(pubkey?: string) {
|
||||
if (!pubkey) return;
|
||||
|
||||
return new Promise<URL[] | undefined>((res) => {
|
||||
const sub = replaceableEventsService.requestEvent(
|
||||
clientRelaysService.readRelays.value,
|
||||
USER_BLOSSOM_SERVER_LIST_KIND,
|
||||
pubkey,
|
||||
);
|
||||
|
||||
if (sub.value) res(getServersFromServerListEvent(sub.value));
|
||||
else {
|
||||
sub.once((event) => res(getServersFromServerListEvent(event)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function useImageThumbnail(src?: string) {
|
||||
return (src && buildImageProxyURL(src, "512,fit")) ?? src;
|
||||
}
|
||||
|
||||
export const EmbeddedImage = forwardRef<HTMLImageElement, EmbeddedImageProps>(
|
||||
({ src, event, imageProps, ...props }, ref) => {
|
||||
const thumbnail = useImageThumbnail(src);
|
||||
export function EmbeddedImage({ src, event, imageProps, ...props }: EmbeddedImageProps) {
|
||||
const owner = useMediaOwnerContext();
|
||||
const thumbnail = useImageThumbnail(src);
|
||||
|
||||
ref = ref || useRef<HTMLImageElement | null>(null);
|
||||
const { show } = useRegisterSlide(
|
||||
ref as MutableRefObject<HTMLImageElement | null>,
|
||||
src ? { type: "image", src, event } : undefined,
|
||||
);
|
||||
const handleClick = useCallback<MouseEventHandler<HTMLElement>>(
|
||||
(e) => {
|
||||
!e.isPropagationStopped() && show();
|
||||
e.preventDefault();
|
||||
},
|
||||
[show],
|
||||
);
|
||||
const ref = useRef<HTMLImageElement | null>(null);
|
||||
const { show } = useRegisterSlide(ref, src ? { type: "image", src, event } : undefined);
|
||||
const handleClick = useCallback<MouseEventHandler<HTMLElement>>(
|
||||
(e) => {
|
||||
!e.isPropagationStopped() && show();
|
||||
e.preventDefault();
|
||||
},
|
||||
[show],
|
||||
);
|
||||
|
||||
// NOTE: the parent <div> has display=block and and <a> has inline-block
|
||||
// this is so that the <a> element can act like a block without being full width
|
||||
return (
|
||||
<div>
|
||||
<Link href={src} isExternal onClick={handleClick} display="inline-block" {...props}>
|
||||
<TrustImage {...imageProps} src={thumbnail} cursor="pointer" ref={ref} onClick={handleClick} />
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
useEffect(() => {
|
||||
if (ref.current) handleImageFallbacks(ref.current, getPubkeyMediaServers);
|
||||
}, []);
|
||||
|
||||
// NOTE: the parent <div> has display=block and and <a> has inline-block
|
||||
// this is so that the <a> element can act like a block without being full width
|
||||
return (
|
||||
<div>
|
||||
<Link href={src} isExternal onClick={handleClick} display="inline-block" {...props}>
|
||||
<TrustImage
|
||||
{...imageProps}
|
||||
src={thumbnail}
|
||||
cursor="pointer"
|
||||
ref={ref}
|
||||
onClick={handleClick}
|
||||
data-pubkey={owner}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const GalleryImage = forwardRef<HTMLImageElement, EmbeddedImageProps>(
|
||||
({ src, event, imageProps, ...props }, ref) => {
|
||||
@ -124,6 +150,11 @@ export const GalleryImage = forwardRef<HTMLImageElement, EmbeddedImageProps>(
|
||||
[show],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const el = (ref as MutableRefObject<HTMLImageElement | null>).current;
|
||||
if (el) handleImageFallbacks(el, getPubkeyMediaServers);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Link href={src} isExternal onClick={handleClick} {...props}>
|
||||
<TrustImage src={thumbnail} cursor="pointer" ref={ref} onClick={handleClick} w="full" {...imageProps} />
|
||||
@ -277,7 +308,7 @@ function VerifyImageButton({ src, original }: { src: URL; original: string }) {
|
||||
export function renderImageUrl(match: URL) {
|
||||
if (!isImageURL(match)) return null;
|
||||
|
||||
const hash = getMediaHashFromURL(match);
|
||||
const hash = getHashFromURL(match);
|
||||
|
||||
return (
|
||||
<ExpandableEmbed
|
||||
|
@ -28,7 +28,7 @@ import RepostButton from "./components/repost-button";
|
||||
import QuoteRepostButton from "../quote-repost-button";
|
||||
import { ReplyIcon } from "../../icons";
|
||||
import NoteContentWithWarning from "./note-content-with-warning";
|
||||
import { TrustProvider } from "../../../providers/local/trust";
|
||||
import { TrustProvider } from "../../../providers/local/trust-provider";
|
||||
import { useRegisterIntersectionEntity } from "../../../providers/local/intersection-observer";
|
||||
import BookmarkButton from "../bookmark-button";
|
||||
import useCurrentAccount from "../../../hooks/use-current-account";
|
||||
|
@ -33,6 +33,7 @@ import {
|
||||
renderArchiveOrgURL,
|
||||
} from "../../external-embeds";
|
||||
import { LightboxProvider } from "../../lightbox-provider";
|
||||
import { MediaOwnerProvider } from "../../../providers/local/media-owner-provider";
|
||||
|
||||
function buildContents(event: NostrEvent | EventTemplate, simpleLinks = false) {
|
||||
let content: EmbedableContent = [event.content.trim()];
|
||||
@ -93,13 +94,15 @@ export const TextNoteContents = React.memo(
|
||||
}
|
||||
|
||||
return (
|
||||
<LightboxProvider>
|
||||
<Suspense fallback={<Spinner />}>
|
||||
<Box whiteSpace="pre-wrap" {...props}>
|
||||
{content}
|
||||
</Box>
|
||||
</Suspense>
|
||||
</LightboxProvider>
|
||||
<MediaOwnerProvider owner={(event as NostrEvent).pubkey as string | undefined}>
|
||||
<LightboxProvider>
|
||||
<Suspense fallback={<Spinner />}>
|
||||
<Box whiteSpace="pre-wrap" {...props}>
|
||||
{content}
|
||||
</Box>
|
||||
</Suspense>
|
||||
</LightboxProvider>
|
||||
</MediaOwnerProvider>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -31,7 +31,7 @@ import { kinds } from "nostr-tools";
|
||||
import { ChevronDownIcon, ChevronUpIcon, UploadImageIcon } from "../icons";
|
||||
import PublishAction from "../../classes/nostr-publish-action";
|
||||
import { PublishDetails } from "../../views/task-manager/publish-log/publish-details";
|
||||
import { TrustProvider } from "../../providers/local/trust";
|
||||
import { TrustProvider } from "../../providers/local/trust-provider";
|
||||
import {
|
||||
correctContentMentions,
|
||||
createEmojiTags,
|
||||
|
@ -8,7 +8,7 @@ import TimelineNote from "../../note/timeline-note";
|
||||
import UserAvatar from "../../user/user-avatar";
|
||||
import UserDnsIdentity from "../../user/user-dns-identity";
|
||||
import UserLink from "../../user/user-link";
|
||||
import { TrustProvider } from "../../../providers/local/trust";
|
||||
import { TrustProvider } from "../../../providers/local/trust-provider";
|
||||
import { useRegisterIntersectionEntity } from "../../../providers/local/intersection-observer";
|
||||
import useSingleEvent from "../../../hooks/use-single-event";
|
||||
import { EmbedEvent } from "../../embed-event";
|
||||
|
@ -8,7 +8,7 @@ import { getMatchLink } from "../../../helpers/regexp";
|
||||
import { LightboxProvider } from "../../lightbox-provider";
|
||||
import { isImageURL } from "../../../helpers/url";
|
||||
import { EmbeddedImageProps, GalleryImage } from "../../external-embeds";
|
||||
import { TrustProvider } from "../../../providers/local/trust";
|
||||
import { TrustProvider } from "../../../providers/local/trust-provider";
|
||||
import PhotoGallery, { PhotoWithoutSize } from "../../photo-gallery";
|
||||
import { useRegisterIntersectionEntity } from "../../../providers/local/intersection-observer";
|
||||
import { NostrEvent } from "../../../types/nostr-event";
|
||||
|
@ -1,19 +1,4 @@
|
||||
import { NostrEvent } from "nostr-tools";
|
||||
import { safeUrl } from "../parse";
|
||||
|
||||
export const USER_MEDIA_SERVERS_KIND = 10063;
|
||||
|
||||
/** @deprecated */
|
||||
export function isServerTag(tag: string[]) {
|
||||
return (tag[0] === "r" || tag[0] === "server") && tag[1];
|
||||
}
|
||||
|
||||
export function serversEqual(a: string, b: string) {
|
||||
return new URL(a).hostname === new URL(b).hostname;
|
||||
}
|
||||
|
||||
export function getServersFromEvent(event: NostrEvent) {
|
||||
return event.tags
|
||||
.filter(isServerTag)
|
||||
.map((t) => safeUrl(t[1]))
|
||||
.filter(Boolean) as string[];
|
||||
}
|
||||
|
@ -20,13 +20,6 @@ export function isAudioURL(url: string | URL) {
|
||||
return AUDIO_EXT.some((ext) => u.pathname.endsWith(ext));
|
||||
}
|
||||
|
||||
export function getMediaHashFromURL(url: string | URL) {
|
||||
const u = url instanceof URL ? url : new URL(url);
|
||||
const matches = Array.from(u.pathname.matchAll(/[0-9a-f]{64}/gi));
|
||||
|
||||
if (matches.length > 0) return matches[matches.length - 1][0] as string | undefined;
|
||||
}
|
||||
|
||||
export function replaceDomain(url: string | URL, replacementUrl: string | URL) {
|
||||
const newUrl = new URL(url);
|
||||
replacementUrl = convertToUrl(replacementUrl);
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { CSSProperties, MouseEventHandler, ReactEventHandler, useCallback } from "react";
|
||||
import { useTrustContext } from "../providers/local/trust";
|
||||
import { useTrustContext } from "../providers/local/trust-provider";
|
||||
|
||||
export default function useElementTrustBlur(): {
|
||||
style: CSSProperties;
|
||||
|
@ -69,7 +69,11 @@ export default function useTextAreaUploadFile(
|
||||
const imageUrl = response.url;
|
||||
insertURL(imageUrl);
|
||||
} else if (mediaUploadService === "blossom" && mediaServers.length) {
|
||||
const blob = await uploadFileToServers(mediaServers, file, requestSignature);
|
||||
const blob = await uploadFileToServers(
|
||||
mediaServers.map((s) => s.toString()),
|
||||
file,
|
||||
requestSignature,
|
||||
);
|
||||
insertURL(blob.url);
|
||||
}
|
||||
} catch (e) {
|
||||
|
@ -1,16 +1,16 @@
|
||||
import { useMemo } from "react";
|
||||
import { USER_MEDIA_SERVERS_KIND, getServersFromEvent } from "../helpers/nostr/blossom";
|
||||
import replaceableEventsService, { RequestOptions } from "../services/replaceable-events";
|
||||
import { useReadRelays } from "./use-client-relays";
|
||||
import useSubject from "./use-subject";
|
||||
import { USER_BLOSSOM_SERVER_LIST_KIND, getServersFromServerListEvent } from "blossom-client-sdk";
|
||||
|
||||
export default function useUsersMediaServers(pubkey?: string, additionalRelays?: string[], opts?: RequestOptions) {
|
||||
const readRelays = useReadRelays(additionalRelays);
|
||||
const sub = pubkey
|
||||
? replaceableEventsService.requestEvent(readRelays, USER_MEDIA_SERVERS_KIND, pubkey, undefined, opts)
|
||||
? replaceableEventsService.requestEvent(readRelays, USER_BLOSSOM_SERVER_LIST_KIND, pubkey, undefined, opts)
|
||||
: undefined;
|
||||
const event = useSubject(sub);
|
||||
const servers = useMemo(() => (event ? getServersFromEvent(event) : []), [event?.id]);
|
||||
const servers = useMemo(() => (event ? getServersFromServerListEvent(event) : []), [event?.id]);
|
||||
|
||||
return { event, servers };
|
||||
}
|
||||
|
12
src/providers/local/media-owner-provider.tsx
Normal file
12
src/providers/local/media-owner-provider.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
import React, { PropsWithChildren, useContext } from "react";
|
||||
|
||||
const MediaOwnerContext = React.createContext<string | undefined>(undefined);
|
||||
|
||||
export function useMediaOwnerContext() {
|
||||
return useContext(MediaOwnerContext);
|
||||
}
|
||||
|
||||
export function MediaOwnerProvider({ children, owner }: PropsWithChildren & { owner?: string }) {
|
||||
if (owner) return <MediaOwnerContext.Provider value={owner}>{children}</MediaOwnerContext.Provider>;
|
||||
else <>{children}</>;
|
||||
}
|
@ -2,7 +2,7 @@ import { memo, useMemo } from "react";
|
||||
import { Box, BoxProps } from "@chakra-ui/react";
|
||||
|
||||
import { NostrEvent } from "../../../types/nostr-event";
|
||||
import { TrustProvider } from "../../../providers/local/trust";
|
||||
import { TrustProvider } from "../../../providers/local/trust-provider";
|
||||
import { EmbedableContent, embedUrls } from "../../../helpers/embeds";
|
||||
import {
|
||||
embedCashuTokens,
|
||||
|
@ -19,7 +19,7 @@ import {
|
||||
renderWavlakeUrl,
|
||||
renderYoutubeURL,
|
||||
} from "../../../components/external-embeds";
|
||||
import { TrustProvider } from "../../../providers/local/trust";
|
||||
import { TrustProvider } from "../../../providers/local/trust-provider";
|
||||
import { LightboxProvider } from "../../../components/lightbox-provider";
|
||||
import { renderAudioUrl } from "../../../components/external-embeds/types/audio";
|
||||
|
||||
|
@ -7,7 +7,7 @@ import { NostrEvent } from "../../types/nostr-event";
|
||||
import { FILE_KIND, IMAGE_TYPES, VIDEO_TYPES, getFileUrl, parseImageFile } from "../../helpers/nostr/files";
|
||||
import { ErrorBoundary } from "../../components/error-boundary";
|
||||
import useAppSettings from "../../hooks/use-app-settings";
|
||||
import { TrustProvider, useTrustContext } from "../../providers/local/trust";
|
||||
import { TrustProvider, useTrustContext } from "../../providers/local/trust-provider";
|
||||
import BlurredImage from "../../components/blured-image";
|
||||
import PeopleListProvider, { usePeopleListContext } from "../../providers/local/people-list-provider";
|
||||
import PeopleListSelection from "../../components/people-list-selection/people-list-selection";
|
||||
|
@ -19,7 +19,7 @@ import {
|
||||
import useReplaceableEvent from "../../../hooks/use-replaceable-event";
|
||||
import UserCard from "../components/user-card";
|
||||
import OpenGraphCard from "../../../components/open-graph/open-graph-card";
|
||||
import { TrustProvider } from "../../../providers/local/trust";
|
||||
import { TrustProvider } from "../../../providers/local/trust-provider";
|
||||
import ListMenu from "../components/list-menu";
|
||||
import ListFavoriteButton from "../components/list-favorite-button";
|
||||
import ListFeedButton from "../components/list-feed-button";
|
||||
|
@ -11,7 +11,7 @@ import { getEventUID, parseCoordinate } from "../../../helpers/nostr/event";
|
||||
import { EmbedEvent, EmbedEventPointer } from "../../../components/embed-event";
|
||||
import EmbeddedUnknown from "../../../components/embed-event/event-types/embedded-unknown";
|
||||
import { ErrorBoundary } from "../../../components/error-boundary";
|
||||
import { TrustProvider } from "../../../providers/local/trust";
|
||||
import { TrustProvider } from "../../../providers/local/trust-provider";
|
||||
import Heart from "../../../components/icons/heart";
|
||||
import UserAvatarLink from "../../../components/user/user-avatar-link";
|
||||
import {
|
||||
|
@ -34,7 +34,8 @@ import DebugEventButton from "../../../components/debug-modal/debug-event-button
|
||||
import { cloneEvent } from "../../../helpers/nostr/event";
|
||||
import useAppSettings from "../../../hooks/use-app-settings";
|
||||
import useAsyncErrorHandler from "../../../hooks/use-async-error-handler";
|
||||
import { USER_MEDIA_SERVERS_KIND, isServerTag, serversEqual } from "../../../helpers/nostr/blossom";
|
||||
import { isServerTag } from "../../../helpers/nostr/blossom";
|
||||
import { USER_BLOSSOM_SERVER_LIST_KIND, areServersEqual } from "blossom-client-sdk";
|
||||
|
||||
function MediaServersPage() {
|
||||
const toast = useToast();
|
||||
@ -47,28 +48,28 @@ function MediaServersPage() {
|
||||
});
|
||||
|
||||
const addServer = async (server: string) => {
|
||||
const draft = cloneEvent(USER_MEDIA_SERVERS_KIND, event);
|
||||
const draft = cloneEvent(USER_BLOSSOM_SERVER_LIST_KIND, event);
|
||||
draft.tags = [
|
||||
...draft.tags.filter((t) => !isServerTag(t)),
|
||||
...servers.map((server) => ["server", server]),
|
||||
...servers.map((server) => ["server", server.toString()]),
|
||||
["server", server],
|
||||
];
|
||||
await publish("Add media server", draft);
|
||||
};
|
||||
const removeServer = async (server: string) => {
|
||||
const draft = cloneEvent(USER_MEDIA_SERVERS_KIND, event);
|
||||
const draft = cloneEvent(USER_BLOSSOM_SERVER_LIST_KIND, event);
|
||||
draft.tags = [
|
||||
...draft.tags.filter((t) => !isServerTag(t)),
|
||||
...servers.filter((s) => !serversEqual(s, server)).map((server) => ["server", server]),
|
||||
...servers.filter((s) => !areServersEqual(s, server)).map((server) => ["server", server.toString()]),
|
||||
];
|
||||
await publish("Remove media server", draft);
|
||||
};
|
||||
const makeDefault = async (server: string) => {
|
||||
const draft = cloneEvent(USER_MEDIA_SERVERS_KIND, event);
|
||||
const draft = cloneEvent(USER_BLOSSOM_SERVER_LIST_KIND, event);
|
||||
draft.tags = [
|
||||
...draft.tags.filter((t) => !isServerTag(t)),
|
||||
["server", server],
|
||||
...servers.filter((s) => !serversEqual(s, server)).map((server) => ["server", server]),
|
||||
["server", server.toString()],
|
||||
...servers.filter((s) => !areServersEqual(s, server)).map((server) => ["server", server.toString()]),
|
||||
];
|
||||
await publish("Remove media server", draft);
|
||||
};
|
||||
@ -83,7 +84,7 @@ function MediaServersPage() {
|
||||
const submit = handleSubmit(async (values) => {
|
||||
let url = new URL(values.server.startsWith("http") ? values.server : "https://" + values.server).toString();
|
||||
|
||||
if (event?.tags.some((t) => isServerTag(t) && serversEqual(t[1], url)))
|
||||
if (event?.tags.some((t) => isServerTag(t) && areServersEqual(t[1], url)))
|
||||
return toast({ status: "error", description: "Server already in list" });
|
||||
|
||||
try {
|
||||
@ -165,11 +166,11 @@ function MediaServersPage() {
|
||||
alignItems="center"
|
||||
borderWidth="1px"
|
||||
borderRadius="lg"
|
||||
key={server}
|
||||
key={server.toString()}
|
||||
borderColor={i === 0 ? "primary.500" : undefined}
|
||||
>
|
||||
<MediaServerFavicon server={server} size="sm" />
|
||||
<Link href={server} target="_blank" color="blue.500" fontSize="lg">
|
||||
<MediaServerFavicon server={server.toString()} size="sm" />
|
||||
<Link href={server.toString()} target="_blank" color="blue.500" fontSize="lg">
|
||||
{new URL(server).hostname}
|
||||
</Link>
|
||||
|
||||
@ -178,12 +179,12 @@ function MediaServersPage() {
|
||||
variant={i === 0 ? "solid" : "outline"}
|
||||
colorScheme={i === 0 ? "primary" : undefined}
|
||||
size="sm"
|
||||
onClick={() => makeDefault(server)}
|
||||
onClick={() => makeDefault(server.toString())}
|
||||
isDisabled={i === 0}
|
||||
>
|
||||
Default
|
||||
</Button>
|
||||
<CloseButton onClick={() => removeServer(server)} />
|
||||
<CloseButton onClick={() => removeServer(server.toString())} />
|
||||
</Flex>
|
||||
))}
|
||||
</Flex>
|
||||
|
@ -6,7 +6,7 @@ import UserAvatar from "../../../../components/user/user-avatar";
|
||||
import UserLink from "../../../../components/user/user-link";
|
||||
import { NostrEvent } from "../../../../types/nostr-event";
|
||||
import { useRegisterIntersectionEntity } from "../../../../providers/local/intersection-observer";
|
||||
import { TrustProvider } from "../../../../providers/local/trust";
|
||||
import { TrustProvider } from "../../../../providers/local/trust-provider";
|
||||
import ChatMessageContent from "./chat-message-content";
|
||||
import NoteZapButton from "../../../../components/note/note-zap-button";
|
||||
|
||||
|
@ -9,7 +9,7 @@ import { useRegisterIntersectionEntity } from "../../../../providers/local/inter
|
||||
import { LightningIcon } from "../../../../components/icons";
|
||||
import { getParsedZap } from "../../../../helpers/nostr/zaps";
|
||||
import { readablizeSats } from "../../../../helpers/bolt11";
|
||||
import { TrustProvider } from "../../../../providers/local/trust";
|
||||
import { TrustProvider } from "../../../../providers/local/trust-provider";
|
||||
import ChatMessageContent from "./chat-message-content";
|
||||
import useClientSideMuteFilter from "../../../../hooks/use-client-side-mute-filter";
|
||||
|
||||
|
@ -19,7 +19,7 @@ import useCurrentAccount from "../../../hooks/use-current-account";
|
||||
import { useSigningContext } from "../../../providers/global/signing-provider";
|
||||
import MagicTextArea, { RefType } from "../../../components/magic-textarea";
|
||||
import { useContextEmojis } from "../../../providers/global/emoji-provider";
|
||||
import { TrustProvider } from "../../../providers/local/trust";
|
||||
import { TrustProvider } from "../../../providers/local/trust-provider";
|
||||
import { nostrBuildUploadImage } from "../../../helpers/media-upload/nostr-build";
|
||||
import { UploadImageIcon } from "../../../components/icons";
|
||||
import { unique } from "../../../helpers/array";
|
||||
|
@ -9,7 +9,7 @@ import Timestamp from "../../../../components/timestamp";
|
||||
import { LightningIcon } from "../../../../components/icons";
|
||||
import { readablizeSats } from "../../../../helpers/bolt11";
|
||||
import TextNoteContents from "../../../../components/note/timeline-note/text-note-contents";
|
||||
import { TrustProvider } from "../../../../providers/local/trust";
|
||||
import { TrustProvider } from "../../../../providers/local/trust-provider";
|
||||
|
||||
const ZapEvent = memo(({ zap }: { zap: ParsedZap }) => {
|
||||
if (!zap.payment.amount) return null;
|
||||
|
@ -5,7 +5,7 @@ import { Link as RouterLink } from "react-router-dom";
|
||||
import ReplyForm from "./reply-form";
|
||||
import { ReplyIcon } from "../../../components/icons";
|
||||
import { countReplies, ThreadItem } from "../../../helpers/thread";
|
||||
import { TrustProvider } from "../../../providers/local/trust";
|
||||
import { TrustProvider } from "../../../providers/local/trust-provider";
|
||||
import useClientSideMuteFilter from "../../../hooks/use-client-side-mute-filter";
|
||||
import UserAvatarLink from "../../../components/user/user-avatar-link";
|
||||
import UserLink from "../../../components/user/user-link";
|
||||
|
@ -4,7 +4,7 @@ import UserAvatarLink from "../../../../components/user/user-avatar-link";
|
||||
import UserLink from "../../../../components/user/user-link";
|
||||
import { NostrEvent } from "../../../../types/nostr-event";
|
||||
import TextNoteContents from "../../../../components/note/timeline-note/text-note-contents";
|
||||
import { TrustProvider } from "../../../../providers/local/trust";
|
||||
import { TrustProvider } from "../../../../providers/local/trust-provider";
|
||||
|
||||
export default function TranslationResult({ result }: { result: NostrEvent }) {
|
||||
const content = useDisclosure();
|
||||
|
@ -28,7 +28,7 @@ import UserDnsIdentity from "../../../components/user/user-dns-identity";
|
||||
import Timestamp from "../../../components/timestamp";
|
||||
import Minus from "../../../components/icons/minus";
|
||||
import Expand01 from "../../../components/icons/expand-01";
|
||||
import { TrustProvider } from "../../../providers/local/trust";
|
||||
import { TrustProvider } from "../../../providers/local/trust-provider";
|
||||
import { ReplyIcon } from "../../../components/icons";
|
||||
import ReplyForm from "../../thread/components/reply-form";
|
||||
import useThreadColorLevelProps from "../../../hooks/use-thread-color-level-props";
|
||||
|
@ -13,7 +13,7 @@ import IntersectionObserverProvider, {
|
||||
useRegisterIntersectionEntity,
|
||||
} from "../../providers/local/intersection-observer";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import { TrustProvider } from "../../providers/local/trust";
|
||||
import { TrustProvider } from "../../providers/local/trust-provider";
|
||||
import UserAvatar from "../../components/user/user-avatar";
|
||||
import UserLink from "../../components/user/user-link";
|
||||
import { EmbedEventPointer } from "../../components/embed-event";
|
||||
|
@ -27,7 +27,11 @@ export default function MarkdownEditor({ options, ...props }: SimpleMDEReactProp
|
||||
async function imageUploadFunction(file: File, onSuccess: (url: string) => void, onError: (error: string) => void) {
|
||||
if (!servers) return onError("No media servers set");
|
||||
try {
|
||||
const blob = await uploadFileToServers(servers, file, requestSignature);
|
||||
const blob = await uploadFileToServers(
|
||||
servers.map((s) => s.toString()),
|
||||
file,
|
||||
requestSignature,
|
||||
);
|
||||
if (blob) onSuccess(blob.url);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) onError(error.message);
|
||||
|
@ -84,7 +84,11 @@ export default function CreateWikiPageView() {
|
||||
async function imageUploadFunction(file: File, onSuccess: (url: string) => void, onError: (error: string) => void) {
|
||||
if (!servers) return onError("No media servers set");
|
||||
try {
|
||||
const blob = await uploadFileToServers(servers, file, requestSignature);
|
||||
const blob = await uploadFileToServers(
|
||||
servers.map((s) => s.toString()),
|
||||
file,
|
||||
requestSignature,
|
||||
);
|
||||
if (blob) onSuccess(blob.url);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) onError(error.message);
|
||||
|
@ -3426,6 +3426,13 @@ blossom-client-sdk@^0.5.1:
|
||||
dependencies:
|
||||
cross-fetch "^4.0.0"
|
||||
|
||||
blossom-client-sdk@^0.6.0:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/blossom-client-sdk/-/blossom-client-sdk-0.6.0.tgz#e5fc2b17f3d76e3b8bb3d31a535db294ebce5a07"
|
||||
integrity sha512-m1dAIQF0WceEvpxbl349IUeNTay6w/QF7RAUlELIFxES7fbRQB0dkbVhT58y+SW+tTQvgMfuPmMAAsghrGwGfg==
|
||||
dependencies:
|
||||
cross-fetch "^4.0.0"
|
||||
|
||||
blossom-drive-sdk@^0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/blossom-drive-sdk/-/blossom-drive-sdk-0.3.0.tgz#35ab1afebbc8c79bd86af10628291b3e06935f35"
|
||||
|
Loading…
x
Reference in New Issue
Block a user