Fallback to users blossom servers on broken image links with sha256

This commit is contained in:
hzrd149 2024-05-13 15:36:58 -05:00
parent 10ea8c9531
commit 91c9ad1cd0
36 changed files with 159 additions and 110 deletions

@ -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 };
}

@ -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"