mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-03-26 17:52:18 +01:00
add support for web share
This commit is contained in:
parent
fc1fa763b5
commit
92b950a0e6
5
.changeset/modern-socks-tickle.md
Normal file
5
.changeset/modern-socks-tickle.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"nostrudel": minor
|
||||
---
|
||||
|
||||
Add support for native android and ios sharing
|
@ -163,6 +163,10 @@ export default class MultiSubscription {
|
||||
}
|
||||
|
||||
destroy() {
|
||||
for (const [relay, sub] of this.subscriptions) {
|
||||
sub.destroy();
|
||||
}
|
||||
|
||||
this.process.remove();
|
||||
processManager.unregisterProcess(this.process);
|
||||
}
|
||||
|
@ -40,8 +40,8 @@ export default class TimelineLoader {
|
||||
private log: Debugger;
|
||||
private subscription: MultiSubscription;
|
||||
|
||||
private cacheChunkLoader: ChunkedRequest | null = null;
|
||||
private chunkLoaders = new Map<string, ChunkedRequest>();
|
||||
private cacheLoader: ChunkedRequest | null = null;
|
||||
private loaders = new Map<string, ChunkedRequest>();
|
||||
|
||||
constructor(name: string) {
|
||||
this.name = name;
|
||||
@ -107,10 +107,10 @@ export default class TimelineLoader {
|
||||
|
||||
// recreate all chunk loaders
|
||||
for (const relay of this.relays) {
|
||||
const loader = this.chunkLoaders.get(relay.url);
|
||||
const loader = this.loaders.get(relay.url);
|
||||
if (loader) {
|
||||
this.disconnectFromChunkLoader(loader);
|
||||
this.chunkLoaders.delete(relay.url);
|
||||
this.loaders.delete(relay.url);
|
||||
}
|
||||
|
||||
const chunkLoader = new ChunkedRequest(
|
||||
@ -118,7 +118,7 @@ export default class TimelineLoader {
|
||||
filters,
|
||||
this.log.extend(relay.url),
|
||||
);
|
||||
this.chunkLoaders.set(relay.url, chunkLoader);
|
||||
this.loaders.set(relay.url, chunkLoader);
|
||||
this.connectToChunkLoader(chunkLoader);
|
||||
}
|
||||
|
||||
@ -126,10 +126,10 @@ export default class TimelineLoader {
|
||||
this.filters = filters;
|
||||
|
||||
// recreate cache chunk loader
|
||||
if (this.cacheChunkLoader) this.disconnectFromChunkLoader(this.cacheChunkLoader);
|
||||
if (this.cacheLoader) this.disconnectFromChunkLoader(this.cacheLoader);
|
||||
if (localRelay) {
|
||||
this.cacheChunkLoader = new ChunkedRequest(localRelay, this.filters, this.log.extend("cache-relay"));
|
||||
this.connectToChunkLoader(this.cacheChunkLoader);
|
||||
this.cacheLoader = new ChunkedRequest(localRelay, this.filters, this.log.extend("cache-relay"));
|
||||
this.connectToChunkLoader(this.cacheLoader);
|
||||
}
|
||||
|
||||
// update the live subscription query map and add limit
|
||||
@ -141,20 +141,20 @@ export default class TimelineLoader {
|
||||
|
||||
// remove chunk loaders
|
||||
for (const relay of newRelays) {
|
||||
const loader = this.chunkLoaders.get(relay.url);
|
||||
const loader = this.loaders.get(relay.url);
|
||||
if (!loader) continue;
|
||||
if (!this.relays.includes(relay)) {
|
||||
this.disconnectFromChunkLoader(loader);
|
||||
this.chunkLoaders.delete(relay.url);
|
||||
this.loaders.delete(relay.url);
|
||||
}
|
||||
}
|
||||
|
||||
// create chunk loaders only if filters are set
|
||||
if (this.filters.length > 0) {
|
||||
for (const relay of newRelays) {
|
||||
if (!this.chunkLoaders.has(relay.url)) {
|
||||
if (!this.loaders.has(relay.url)) {
|
||||
const loader = new ChunkedRequest(relay, this.filters, this.log.extend(relay.url));
|
||||
this.chunkLoaders.set(relay.url, loader);
|
||||
this.loaders.set(relay.url, loader);
|
||||
this.connectToChunkLoader(loader);
|
||||
}
|
||||
}
|
||||
@ -177,9 +177,7 @@ export default class TimelineLoader {
|
||||
}
|
||||
|
||||
private getAllLoaders() {
|
||||
return this.cacheChunkLoader
|
||||
? [...this.chunkLoaders.values(), this.cacheChunkLoader]
|
||||
: Array.from(this.chunkLoaders.values());
|
||||
return this.cacheLoader ? [...this.loaders.values(), this.cacheLoader] : Array.from(this.loaders.values());
|
||||
}
|
||||
|
||||
triggerChunkLoad() {
|
||||
@ -256,8 +254,8 @@ export default class TimelineLoader {
|
||||
this.cursor = dayjs().unix();
|
||||
const loaders = this.getAllLoaders();
|
||||
for (const loader of loaders) this.disconnectFromChunkLoader(loader);
|
||||
this.chunkLoaders.clear();
|
||||
this.cacheChunkLoader = null;
|
||||
this.loaders.clear();
|
||||
this.cacheLoader = null;
|
||||
this.forgetEvents();
|
||||
}
|
||||
|
||||
@ -267,8 +265,8 @@ export default class TimelineLoader {
|
||||
|
||||
const loaders = this.getAllLoaders();
|
||||
for (const loader of loaders) this.disconnectFromChunkLoader(loader);
|
||||
this.chunkLoaders.clear();
|
||||
this.cacheChunkLoader = null;
|
||||
this.loaders.clear();
|
||||
this.cacheLoader = null;
|
||||
|
||||
this.subscription.destroy();
|
||||
|
||||
|
@ -1,25 +0,0 @@
|
||||
import { MenuItem, useToast } from "@chakra-ui/react";
|
||||
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import { getSharableEventAddress } from "../../helpers/nip19";
|
||||
import { ShareIcon } from "../icons";
|
||||
|
||||
export default function CopyShareLinkMenuItem({ event }: { event: NostrEvent }) {
|
||||
const toast = useToast();
|
||||
const address = getSharableEventAddress(event);
|
||||
|
||||
return (
|
||||
address && (
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
const text = "https://njump.me/" + address;
|
||||
if (navigator.clipboard) navigator.clipboard.writeText(text);
|
||||
else toast({ description: text, isClosable: true, duration: null });
|
||||
}}
|
||||
icon={<ShareIcon />}
|
||||
>
|
||||
Copy share link
|
||||
</MenuItem>
|
||||
)
|
||||
);
|
||||
}
|
48
src/components/common-menu-items/share-link.tsx
Normal file
48
src/components/common-menu-items/share-link.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
import { MenuItem, useToast } from "@chakra-ui/react";
|
||||
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import { getSharableEventAddress } from "../../helpers/nip19";
|
||||
import { ShareIcon } from "../icons";
|
||||
import { Signature } from "@noble/secp256k1";
|
||||
import { descriptors } from "chart.js/dist/core/core.defaults";
|
||||
import useUserMetadata from "../../hooks/use-user-metadata";
|
||||
import { useCallback } from "react";
|
||||
import { getDisplayName } from "../../helpers/nostr/user-metadata";
|
||||
|
||||
let urlShareFailed = false;
|
||||
|
||||
export default function ShareLinkMenuItem({ event }: { event: NostrEvent }) {
|
||||
const toast = useToast();
|
||||
const address = getSharableEventAddress(event);
|
||||
const metadata = useUserMetadata(event.pubkey);
|
||||
|
||||
const share = useCallback(async () => {
|
||||
const data: ShareData = {
|
||||
url: "https://njump.me/" + address,
|
||||
title: event.tags.find((t) => t[0] === "title")?.[1] || "Nostr note by " + getDisplayName(metadata, event.pubkey),
|
||||
};
|
||||
|
||||
if (event.content.length <= 256) data.text = event.content;
|
||||
|
||||
try {
|
||||
if (navigator.canShare?.(data)) {
|
||||
await navigator.share(data);
|
||||
} else {
|
||||
if (navigator.clipboard) {
|
||||
await navigator.clipboard.writeText(data.url!);
|
||||
toast({ status: "success", description: "Copied" });
|
||||
} else toast({ description: data.url, isClosable: true, duration: null });
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof Error) toast({ status: "error", description: err.message });
|
||||
}
|
||||
}, [metadata, event, toast]);
|
||||
|
||||
return (
|
||||
address && (
|
||||
<MenuItem onClick={share} icon={<ShareIcon />}>
|
||||
Share Link
|
||||
</MenuItem>
|
||||
)
|
||||
);
|
||||
}
|
@ -8,7 +8,7 @@ import { DotsMenuButton, MenuIconButtonProps } from "../dots-menu-button";
|
||||
import NoteTranslationModal from "../../views/tools/transform-note/translation";
|
||||
import Translate01 from "../icons/translate-01";
|
||||
import PinNoteMenuItem from "../common-menu-items/pin-note";
|
||||
import CopyShareLinkMenuItem from "../common-menu-items/copy-share-link";
|
||||
import ShareLinkMenuItem from "../common-menu-items/share-link";
|
||||
import OpenInAppMenuItem from "../common-menu-items/open-in-app";
|
||||
import MuteUserMenuItem from "../common-menu-items/mute-user";
|
||||
import DeleteEventMenuItem from "../common-menu-items/delete-event";
|
||||
@ -30,7 +30,7 @@ export default function NoteMenu({ event, ...props }: { event: NostrEvent } & Om
|
||||
<>
|
||||
<DotsMenuButton {...props}>
|
||||
<OpenInAppMenuItem event={event} />
|
||||
<CopyShareLinkMenuItem event={event} />
|
||||
<ShareLinkMenuItem event={event} />
|
||||
<CopyEmbedCodeMenuItem event={event} />
|
||||
<MuteUserMenuItem event={event} />
|
||||
<DeleteEventMenuItem event={event} />
|
||||
|
@ -20,8 +20,6 @@ export type RequestOptions = {
|
||||
alwaysRequest?: boolean;
|
||||
/** ignore the cache on initial load */
|
||||
ignoreCache?: boolean;
|
||||
// TODO: figure out a clean way for useReplaceableEvent hook to "unset" or "unsubscribe"
|
||||
// keepAlive?: boolean;
|
||||
};
|
||||
|
||||
export function getHumanReadableCoordinate(kind: number, pubkey: string, d?: string) {
|
||||
@ -36,8 +34,8 @@ class ReplaceableEventsService {
|
||||
private subjects = new SuperMap<string, Subject<NostrEvent>>(() => new Subject<NostrEvent>());
|
||||
|
||||
cacheLoader: BatchKindLoader | null = null;
|
||||
loaders = new SuperMap<string, BatchKindLoader>((relay) => {
|
||||
const loader = new BatchKindLoader(relayPoolService.requestRelay(relay), this.log.extend(relay));
|
||||
loaders = new SuperMap<AbstractRelay, BatchKindLoader>((relay) => {
|
||||
const loader = new BatchKindLoader(relay, this.log.extend(relay.url));
|
||||
loader.events.onEvent.subscribe((e) => this.handleEvent(e));
|
||||
this.process.addChild(loader.process);
|
||||
return loader;
|
||||
@ -93,8 +91,14 @@ class ReplaceableEventsService {
|
||||
this.writeToCacheThrottle();
|
||||
}
|
||||
|
||||
private requestEventFromRelays(relays: Iterable<string>, kind: number, pubkey: string, d?: string) {
|
||||
private requestEventFromRelays(
|
||||
urls: Iterable<string | URL | AbstractRelay>,
|
||||
kind: number,
|
||||
pubkey: string,
|
||||
d?: string,
|
||||
) {
|
||||
const cord = createCoordinate(kind, pubkey, d);
|
||||
const relays = relayPoolService.getRelays(urls);
|
||||
const sub = this.subjects.get(cord);
|
||||
|
||||
for (const relay of relays) this.loaders.get(relay).requestEvent(kind, pubkey, d);
|
||||
@ -102,8 +106,15 @@ class ReplaceableEventsService {
|
||||
return sub;
|
||||
}
|
||||
|
||||
requestEvent(relays: Iterable<string>, kind: number, pubkey: string, d?: string, opts: RequestOptions = {}) {
|
||||
requestEvent(
|
||||
urls: Iterable<string | URL | AbstractRelay>,
|
||||
kind: number,
|
||||
pubkey: string,
|
||||
d?: string,
|
||||
opts: RequestOptions = {},
|
||||
) {
|
||||
const key = createCoordinate(kind, pubkey, d);
|
||||
const relays = relayPoolService.getRelays(urls);
|
||||
const sub = this.subjects.get(key);
|
||||
|
||||
if (!sub.value && this.cacheLoader) {
|
||||
|
@ -4,7 +4,7 @@ import { nip19 } from "nostr-tools";
|
||||
import { DotsMenuButton, MenuIconButtonProps } from "../../../components/dots-menu-button";
|
||||
import { NostrEvent } from "../../../types/nostr-event";
|
||||
import { CopyToClipboardIcon } from "../../../components/icons";
|
||||
import CopyShareLinkMenuItem from "../../../components/common-menu-items/copy-share-link";
|
||||
import ShareLinkMenuItem from "../../../components/common-menu-items/share-link";
|
||||
import OpenInAppMenuItem from "../../../components/common-menu-items/open-in-app";
|
||||
import DeleteEventMenuItem from "../../../components/common-menu-items/delete-event";
|
||||
import DebugEventMenuItem from "../../../components/debug-modal/debug-event-menu-item";
|
||||
@ -20,7 +20,7 @@ export default function CommunityPostMenu({
|
||||
<>
|
||||
<DotsMenuButton {...props}>
|
||||
<OpenInAppMenuItem event={event} />
|
||||
<CopyShareLinkMenuItem event={event} />
|
||||
<ShareLinkMenuItem event={event} />
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
const text = nip19.noteEncode(event.id);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { DotsMenuButton, MenuIconButtonProps } from "../../../components/dots-menu-button";
|
||||
import OpenInAppMenuItem from "../../../components/common-menu-items/open-in-app";
|
||||
import CopyShareLinkMenuItem from "../../../components/common-menu-items/copy-share-link";
|
||||
import ShareLinkMenuItem from "../../../components/common-menu-items/share-link";
|
||||
import CopyEmbedCodeMenuItem from "../../../components/common-menu-items/copy-embed-code";
|
||||
import MuteUserMenuItem from "../../../components/common-menu-items/mute-user";
|
||||
import DeleteEventMenuItem from "../../../components/common-menu-items/delete-event";
|
||||
@ -15,7 +15,7 @@ export default function TorrentCommentMenu({
|
||||
<>
|
||||
<DotsMenuButton {...props}>
|
||||
<OpenInAppMenuItem event={comment} />
|
||||
<CopyShareLinkMenuItem event={comment} />
|
||||
<ShareLinkMenuItem event={comment} />
|
||||
<CopyEmbedCodeMenuItem event={comment} />
|
||||
<MuteUserMenuItem event={comment} />
|
||||
<DeleteEventMenuItem event={comment} />
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { NostrEvent } from "../../../types/nostr-event";
|
||||
import { DotsMenuButton, MenuIconButtonProps } from "../../../components/dots-menu-button";
|
||||
import OpenInAppMenuItem from "../../../components/common-menu-items/open-in-app";
|
||||
import CopyShareLinkMenuItem from "../../../components/common-menu-items/copy-share-link";
|
||||
import ShareLinkMenuItem from "../../../components/common-menu-items/share-link";
|
||||
import CopyEmbedCodeMenuItem from "../../../components/common-menu-items/copy-embed-code";
|
||||
import MuteUserMenuItem from "../../../components/common-menu-items/mute-user";
|
||||
import DebugEventMenuItem from "../../../components/debug-modal/debug-event-menu-item";
|
||||
@ -11,7 +11,7 @@ export default function TrackMenu({ track, ...props }: { track: NostrEvent } & O
|
||||
<>
|
||||
<DotsMenuButton {...props}>
|
||||
<OpenInAppMenuItem event={track} />
|
||||
<CopyShareLinkMenuItem event={track} />
|
||||
<ShareLinkMenuItem event={track} />
|
||||
<CopyEmbedCodeMenuItem event={track} />
|
||||
<MuteUserMenuItem event={track} />
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { DotsMenuButton, MenuIconButtonProps } from "../../../components/dots-menu-button";
|
||||
import OpenInAppMenuItem from "../../../components/common-menu-items/open-in-app";
|
||||
import CopyShareLinkMenuItem from "../../../components/common-menu-items/copy-share-link";
|
||||
import ShareLinkMenuItem from "../../../components/common-menu-items/share-link";
|
||||
import CopyEmbedCodeMenuItem from "../../../components/common-menu-items/copy-embed-code";
|
||||
import MuteUserMenuItem from "../../../components/common-menu-items/mute-user";
|
||||
import { NostrEvent } from "../../../types/nostr-event";
|
||||
@ -11,7 +11,7 @@ export default function VideoMenu({ video, ...props }: { video: NostrEvent } & O
|
||||
<>
|
||||
<DotsMenuButton {...props}>
|
||||
<OpenInAppMenuItem event={video} />
|
||||
<CopyShareLinkMenuItem event={video} />
|
||||
<ShareLinkMenuItem event={video} />
|
||||
<CopyEmbedCodeMenuItem event={video} />
|
||||
<MuteUserMenuItem event={video} />
|
||||
<DebugEventMenuItem event={video} />
|
||||
|
@ -4,7 +4,7 @@ import { MenuItem } from "@chakra-ui/react";
|
||||
|
||||
import { DotsMenuButton, MenuIconButtonProps } from "../../../components/dots-menu-button";
|
||||
import OpenInAppMenuItem from "../../../components/common-menu-items/open-in-app";
|
||||
import CopyShareLinkMenuItem from "../../../components/common-menu-items/copy-share-link";
|
||||
import ShareLinkMenuItem from "../../../components/common-menu-items/share-link";
|
||||
import CopyEmbedCodeMenuItem from "../../../components/common-menu-items/copy-embed-code";
|
||||
import DebugEventMenuItem from "../../../components/debug-modal/debug-event-menu-item";
|
||||
import useCurrentAccount from "../../../hooks/use-current-account";
|
||||
@ -24,7 +24,7 @@ export default function WikiPageMenu({ page, ...props }: { page: NostrEvent } &
|
||||
</MenuItem>
|
||||
)}
|
||||
|
||||
<CopyShareLinkMenuItem event={page} />
|
||||
<ShareLinkMenuItem event={page} />
|
||||
<CopyEmbedCodeMenuItem event={page} />
|
||||
|
||||
<DebugEventMenuItem event={page} />
|
||||
|
Loading…
x
Reference in New Issue
Block a user