mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-09-27 20:17:05 +02:00
make app settings use query store
This commit is contained in:
37
pnpm-lock.yaml
generated
37
pnpm-lock.yaml
generated
@@ -94,13 +94,13 @@ importers:
|
|||||||
version: 0.6.0(typescript@5.6.2)
|
version: 0.6.0(typescript@5.6.2)
|
||||||
applesauce-core:
|
applesauce-core:
|
||||||
specifier: ^0.6.0
|
specifier: ^0.6.0
|
||||||
version: 0.6.0(typescript@5.6.2)
|
version: link:../applesauce/packages/core
|
||||||
applesauce-react:
|
applesauce-react:
|
||||||
specifier: ^0.6.0
|
specifier: ^0.6.0
|
||||||
version: 0.6.0(typescript@5.6.2)
|
version: link:../applesauce/packages/react
|
||||||
applesauce-signer:
|
applesauce-signer:
|
||||||
specifier: ^0.6.0
|
specifier: ^0.6.0
|
||||||
version: 0.6.0(typescript@5.6.2)
|
version: link:../applesauce/packages/signer
|
||||||
bech32:
|
bech32:
|
||||||
specifier: ^2.0.0
|
specifier: ^2.0.0
|
||||||
version: 2.0.0
|
version: 2.0.0
|
||||||
@@ -2703,14 +2703,6 @@ packages:
|
|||||||
resolution:
|
resolution:
|
||||||
{ integrity: sha512-23W/7P0hzjVGIp51Yp4ppJgbDFNtrJrF3HO3/M36/z+Msdb3HKiSXmt3bMxEVMY6nJTT+zWneq7mAd7VvwhWaA== }
|
{ integrity: sha512-23W/7P0hzjVGIp51Yp4ppJgbDFNtrJrF3HO3/M36/z+Msdb3HKiSXmt3bMxEVMY6nJTT+zWneq7mAd7VvwhWaA== }
|
||||||
|
|
||||||
applesauce-react@0.6.0:
|
|
||||||
resolution:
|
|
||||||
{ integrity: sha512-T8fd7ImkrSe0o+FjC5AoLsYeqlLWq5bE5evwavR2+NTLC9CW185qKPPIzbTDrakq4hPADV8by3AOCmD+MY1Riw== }
|
|
||||||
|
|
||||||
applesauce-signer@0.6.0:
|
|
||||||
resolution:
|
|
||||||
{ integrity: sha512-BunnObvSqIBJ04MMQnpXIflfYEAwqWROCJqQrFZXHy+yXmZjHzPVMkXQLcJ7oXoNVc4CaxgJwp7w8eSmohJKFg== }
|
|
||||||
|
|
||||||
argparse@1.0.10:
|
argparse@1.0.10:
|
||||||
resolution:
|
resolution:
|
||||||
{ integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== }
|
{ integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== }
|
||||||
@@ -8301,29 +8293,6 @@ snapshots:
|
|||||||
- supports-color
|
- supports-color
|
||||||
- typescript
|
- typescript
|
||||||
|
|
||||||
applesauce-react@0.6.0(typescript@5.6.2):
|
|
||||||
dependencies:
|
|
||||||
applesauce-core: 0.6.0(typescript@5.6.2)
|
|
||||||
nostr-tools: 2.7.2(typescript@5.6.2)
|
|
||||||
react: 18.3.1
|
|
||||||
zen-observable: 0.10.0
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- supports-color
|
|
||||||
- typescript
|
|
||||||
|
|
||||||
applesauce-signer@0.6.0(typescript@5.6.2):
|
|
||||||
dependencies:
|
|
||||||
"@noble/hashes": 1.5.0
|
|
||||||
"@noble/secp256k1": 1.7.1
|
|
||||||
"@scure/base": 1.1.9
|
|
||||||
"@types/dom-serial": 1.0.6
|
|
||||||
applesauce-core: 0.6.0(typescript@5.6.2)
|
|
||||||
debug: 4.3.7
|
|
||||||
nostr-tools: 2.7.2(typescript@5.6.2)
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- supports-color
|
|
||||||
- typescript
|
|
||||||
|
|
||||||
argparse@1.0.10:
|
argparse@1.0.10:
|
||||||
dependencies:
|
dependencies:
|
||||||
sprintf-js: 1.0.3
|
sprintf-js: 1.0.3
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
import { AppSettings } from "../../services/settings/migrations";
|
import { AppSettings } from "../../helpers/app-settings";
|
||||||
import { Nip07Interface } from "applesauce-signer";
|
import { Nip07Interface } from "applesauce-signer";
|
||||||
|
|
||||||
export class Account {
|
export class Account {
|
||||||
readonly type: string = "unknown";
|
readonly type: string = "unknown";
|
||||||
pubkey: string;
|
pubkey: string;
|
||||||
localSettings?: AppSettings;
|
localSettings?: Partial<AppSettings>;
|
||||||
|
|
||||||
protected _signer?: Nip07Interface | undefined;
|
protected _signer?: Nip07Interface | undefined;
|
||||||
public get signer(): Nip07Interface | undefined {
|
public get signer(): Nip07Interface | undefined {
|
||||||
|
@@ -6,7 +6,6 @@ import { NostrEvent } from "../../../types/nostr-event";
|
|||||||
import UserAvatarLink from "../../user/user-avatar-link";
|
import UserAvatarLink from "../../user/user-avatar-link";
|
||||||
import UserLink from "../../user/user-link";
|
import UserLink from "../../user/user-link";
|
||||||
import useSubject from "../../../hooks/use-subject";
|
import useSubject from "../../../hooks/use-subject";
|
||||||
import appSettings from "../../../services/settings/app-settings";
|
|
||||||
import EventVerificationIcon from "../../common-event/event-verification-icon";
|
import EventVerificationIcon from "../../common-event/event-verification-icon";
|
||||||
import { TrustProvider } from "../../../providers/local/trust-provider";
|
import { TrustProvider } from "../../../providers/local/trust-provider";
|
||||||
import { NoteLink } from "../../note/note-link";
|
import { NoteLink } from "../../note/note-link";
|
||||||
@@ -17,9 +16,10 @@ import HoverLinkOverlay from "../../hover-link-overlay";
|
|||||||
import singleEventService from "../../../services/single-event";
|
import singleEventService from "../../../services/single-event";
|
||||||
import relayHintService from "../../../services/event-relay-hint";
|
import relayHintService from "../../../services/event-relay-hint";
|
||||||
import localSettings from "../../../services/local-settings";
|
import localSettings from "../../../services/local-settings";
|
||||||
|
import useAppSettings from "../../../hooks/use-app-settings";
|
||||||
|
|
||||||
export default function EmbeddedNote({ event, ...props }: Omit<CardProps, "children"> & { event: NostrEvent }) {
|
export default function EmbeddedNote({ event, ...props }: Omit<CardProps, "children"> & { event: NostrEvent }) {
|
||||||
const { showSignatureVerification } = useSubject(appSettings);
|
const { showSignatureVerification } = useAppSettings();
|
||||||
const enableDrawer = useSubject(localSettings.enableNoteThreadDrawer);
|
const enableDrawer = useSubject(localSettings.enableNoteThreadDrawer);
|
||||||
const navigate = enableDrawer ? useNavigateInDrawer() : useNavigate();
|
const navigate = enableDrawer ? useNavigateInDrawer() : useNavigate();
|
||||||
const to = `/n/${relayHintService.getSharableEventAddress(event)}`;
|
const to = `/n/${relayHintService.getSharableEventAddress(event)}`;
|
||||||
|
@@ -4,8 +4,6 @@ import { Link as RouterLink } from "react-router-dom";
|
|||||||
import { NostrEvent } from "../../../types/nostr-event";
|
import { NostrEvent } from "../../../types/nostr-event";
|
||||||
import UserAvatarLink from "../../user/user-avatar-link";
|
import UserAvatarLink from "../../user/user-avatar-link";
|
||||||
import UserLink from "../../user/user-link";
|
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 EventVerificationIcon from "../../common-event/event-verification-icon";
|
||||||
import { TrustProvider } from "../../../providers/local/trust-provider";
|
import { TrustProvider } from "../../../providers/local/trust-provider";
|
||||||
import Timestamp from "../../timestamp";
|
import Timestamp from "../../timestamp";
|
||||||
@@ -17,13 +15,14 @@ import { getTorrentTitle } from "../../../helpers/nostr/torrents";
|
|||||||
import { useNavigateInDrawer } from "../../../providers/drawer-sub-view-provider";
|
import { useNavigateInDrawer } from "../../../providers/drawer-sub-view-provider";
|
||||||
import { MouseEventHandler, useCallback } from "react";
|
import { MouseEventHandler, useCallback } from "react";
|
||||||
import { nip19 } from "nostr-tools";
|
import { nip19 } from "nostr-tools";
|
||||||
|
import useAppSettings from "../../../hooks/use-app-settings";
|
||||||
|
|
||||||
export default function EmbeddedTorrentComment({
|
export default function EmbeddedTorrentComment({
|
||||||
comment,
|
comment,
|
||||||
...props
|
...props
|
||||||
}: Omit<CardProps, "children"> & { comment: NostrEvent }) {
|
}: Omit<CardProps, "children"> & { comment: NostrEvent }) {
|
||||||
const navigate = useNavigateInDrawer();
|
const navigate = useNavigateInDrawer();
|
||||||
const { showSignatureVerification } = useSubject(appSettings);
|
const { showSignatureVerification } = useAppSettings();
|
||||||
const refs = getThreadReferences(comment);
|
const refs = getThreadReferences(comment);
|
||||||
const torrent = useSingleEvent(refs.root?.e?.id, refs.root?.e?.relays);
|
const torrent = useSingleEvent(refs.root?.e?.id, refs.root?.e?.relays);
|
||||||
const linkToTorrent = refs.root?.e && `/torrents/${nip19.neventEncode(refs.root.e)}`;
|
const linkToTorrent = refs.root?.e && `/torrents/${nip19.neventEncode(refs.root.e)}`;
|
||||||
|
@@ -21,13 +21,14 @@ import { EmbedProps } from "../embed-event";
|
|||||||
import userMailboxesService from "../../services/user-mailboxes";
|
import userMailboxesService from "../../services/user-mailboxes";
|
||||||
import InputStep from "./input-step";
|
import InputStep from "./input-step";
|
||||||
import lnurlMetadataService from "../../services/lnurl-metadata";
|
import lnurlMetadataService from "../../services/lnurl-metadata";
|
||||||
import userMetadataService from "../../services/user-metadata";
|
|
||||||
import signingService from "../../services/signing";
|
import signingService from "../../services/signing";
|
||||||
import accountService from "../../services/account";
|
import accountService from "../../services/account";
|
||||||
import PayStep from "./pay-step";
|
import PayStep from "./pay-step";
|
||||||
import { getInvoiceFromCallbackUrl } from "../../helpers/lnurl";
|
import { getInvoiceFromCallbackUrl } from "../../helpers/lnurl";
|
||||||
import UserLink from "../user/user-link";
|
import UserLink from "../user/user-link";
|
||||||
import relayHintService from "../../services/event-relay-hint";
|
import relayHintService from "../../services/event-relay-hint";
|
||||||
|
import { queryStore } from "../../services/event-store";
|
||||||
|
import { getValue } from "applesauce-core/observable";
|
||||||
|
|
||||||
export type PayRequest = { invoice?: string; pubkey: string; error?: any };
|
export type PayRequest = { invoice?: string; pubkey: string; error?: any };
|
||||||
|
|
||||||
@@ -38,7 +39,8 @@ async function getPayRequestForPubkey(
|
|||||||
comment?: string,
|
comment?: string,
|
||||||
additionalRelays?: Iterable<string>,
|
additionalRelays?: Iterable<string>,
|
||||||
): Promise<PayRequest> {
|
): Promise<PayRequest> {
|
||||||
const metadata = userMetadataService.getSubject(pubkey).value;
|
const metadata = await getValue(queryStore.profile(pubkey));
|
||||||
|
if (!metadata) throw new Error("Cant find user metadata");
|
||||||
const address = metadata?.lud16 || metadata?.lud06;
|
const address = metadata?.lud16 || metadata?.lud06;
|
||||||
if (!address) throw new Error("User missing lightning address");
|
if (!address) throw new Error("User missing lightning address");
|
||||||
const lnurlMetadata = await lnurlMetadataService.requestMetadata(address);
|
const lnurlMetadata = await lnurlMetadataService.requestMetadata(address);
|
||||||
|
@@ -7,7 +7,7 @@ import UserLink from "../user/user-link";
|
|||||||
import { ChevronDownIcon, ChevronUpIcon, CheckIcon, ErrorIcon, LightningIcon } from "../icons";
|
import { ChevronDownIcon, ChevronUpIcon, CheckIcon, ErrorIcon, LightningIcon } from "../icons";
|
||||||
import { InvoiceModalContent } from "../invoice-modal";
|
import { InvoiceModalContent } from "../invoice-modal";
|
||||||
import { PropsWithChildren, useEffect, useState } from "react";
|
import { PropsWithChildren, useEffect, useState } from "react";
|
||||||
import appSettings from "../../services/settings/app-settings";
|
import useAppSettings from "../../hooks/use-app-settings";
|
||||||
|
|
||||||
function UserCard({ children, pubkey }: PropsWithChildren & { pubkey: string }) {
|
function UserCard({ children, pubkey }: PropsWithChildren & { pubkey: string }) {
|
||||||
return (
|
return (
|
||||||
@@ -79,6 +79,7 @@ function ErrorCard({ pubkey, error }: { pubkey: string; error: any }) {
|
|||||||
|
|
||||||
export default function PayStep({ callbacks, onComplete }: { callbacks: PayRequest[]; onComplete: () => void }) {
|
export default function PayStep({ callbacks, onComplete }: { callbacks: PayRequest[]; onComplete: () => void }) {
|
||||||
const [paid, setPaid] = useState<string[]>([]);
|
const [paid, setPaid] = useState<string[]>([]);
|
||||||
|
const { autoPayWithWebLN } = useAppSettings();
|
||||||
|
|
||||||
const [payingAll, setPayingAll] = useState(false);
|
const [payingAll, setPayingAll] = useState(false);
|
||||||
const payAllWithWebLN = async () => {
|
const payAllWithWebLN = async () => {
|
||||||
@@ -99,14 +100,16 @@ export default function PayStep({ callbacks, onComplete }: { callbacks: PayReque
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!callbacks.filter((p) => !!p.invoice).some(({ pubkey }) => !paid.includes(pubkey))) {
|
const withInvoice = callbacks.filter((p) => !!p.invoice);
|
||||||
|
const hasUnpaid = withInvoice.some(({ pubkey }) => !paid.includes(pubkey));
|
||||||
|
if (withInvoice.length > 0 && !hasUnpaid) {
|
||||||
onComplete();
|
onComplete();
|
||||||
}
|
}
|
||||||
}, [paid]);
|
}, [paid]);
|
||||||
|
|
||||||
// if autoPayWithWebLN is enabled, try to pay all immediately
|
// if autoPayWithWebLN is enabled, try to pay all immediately
|
||||||
useMount(() => {
|
useMount(() => {
|
||||||
if (appSettings.value.autoPayWithWebLN) {
|
if (autoPayWithWebLN) {
|
||||||
payAllWithWebLN();
|
payAllWithWebLN();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
import { CSSProperties } from "react";
|
import { CSSProperties } from "react";
|
||||||
import { Box, useColorMode } from "@chakra-ui/react";
|
import { Box, useColorMode } from "@chakra-ui/react";
|
||||||
import { EmbedEventPointer } from "../../embed-event";
|
import { EmbedEventPointer } from "../../embed-event";
|
||||||
import appSettings from "../../../services/settings/app-settings";
|
|
||||||
import { STEMSTR_RELAY } from "../../../helpers/nostr/stemstr";
|
import { STEMSTR_RELAY } from "../../../helpers/nostr/stemstr";
|
||||||
import ExpandableEmbed from "../expandable-embed";
|
import ExpandableEmbed from "../expandable-embed";
|
||||||
|
import useAppSettings from "../../../hooks/use-app-settings";
|
||||||
|
|
||||||
const setZIndex: CSSProperties = { zIndex: 1, position: "relative" };
|
const setZIndex: CSSProperties = { zIndex: 1, position: "relative" };
|
||||||
|
|
||||||
@@ -137,8 +137,8 @@ export function renderStemstrUrl(match: URL) {
|
|||||||
return <EmbedEventPointer pointer={{ type: "nevent", data: { id, relays: [STEMSTR_RELAY] } }} />;
|
return <EmbedEventPointer pointer={{ type: "nevent", data: { id, relays: [STEMSTR_RELAY] } }} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function renderSoundCloudUrl(match: URL) {
|
function SoundCloudEmbed({ match }: { match: URL }) {
|
||||||
if (match.hostname !== "soundcloud.com" || match.pathname.split("/").length !== 3) return null;
|
const { primaryColor } = useAppSettings();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<iframe
|
<iframe
|
||||||
@@ -150,9 +150,14 @@ export function renderSoundCloudUrl(match: URL) {
|
|||||||
src={`https://w.soundcloud.com/player/?url=${encodeURIComponent(
|
src={`https://w.soundcloud.com/player/?url=${encodeURIComponent(
|
||||||
match.protocol + match.host + match.pathname,
|
match.protocol + match.host + match.pathname,
|
||||||
)}&color=${encodeURIComponent(
|
)}&color=${encodeURIComponent(
|
||||||
"#" + appSettings.value.primaryColor || "ff5500",
|
"#" + primaryColor || "ff5500",
|
||||||
)}&auto_play=false&hide_related=false&show_comments=true&show_user=true&show_reposts=false&show_teaser=true`}
|
)}&auto_play=false&hide_related=false&show_comments=true&show_user=true&show_reposts=false&show_teaser=true`}
|
||||||
style={setZIndex}
|
style={setZIndex}
|
||||||
></iframe>
|
></iframe>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
export function renderSoundCloudUrl(match: URL) {
|
||||||
|
if (match.hostname !== "soundcloud.com" || match.pathname.split("/").length !== 3) return null;
|
||||||
|
|
||||||
|
return <SoundCloudEmbed match={match} />;
|
||||||
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { replaceDomain } from "../../../helpers/url";
|
import { replaceDomain } from "../../../helpers/url";
|
||||||
import appSettings from "../../../services/settings/app-settings";
|
import useAppSettings from "../../../hooks/use-app-settings";
|
||||||
import { renderGenericUrl } from "./common";
|
import { renderGenericUrl } from "./common";
|
||||||
|
|
||||||
// copied from https://github.com/SimonBrazell/privacy-redirect/blob/master/src/assets/javascripts/helpers/reddit.js
|
// copied from https://github.com/SimonBrazell/privacy-redirect/blob/master/src/assets/javascripts/helpers/reddit.js
|
||||||
@@ -13,13 +13,17 @@ const REDDIT_DOMAINS = [
|
|||||||
"old.reddit.com",
|
"old.reddit.com",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
function RedditLink({ url }: { url: URL }) {
|
||||||
|
const { redditRedirect } = useAppSettings();
|
||||||
|
const fixed = redditRedirect ? replaceDomain(url, redditRedirect) : url;
|
||||||
|
|
||||||
|
return renderGenericUrl(fixed);
|
||||||
|
}
|
||||||
|
|
||||||
const bypassPaths = /\/(gallery\/poll\/rpan\/settings\/topics)/;
|
const bypassPaths = /\/(gallery\/poll\/rpan\/settings\/topics)/;
|
||||||
export function renderRedditUrl(match: URL) {
|
export function renderRedditUrl(match: URL) {
|
||||||
if (!REDDIT_DOMAINS.includes(match.hostname)) return null;
|
if (!REDDIT_DOMAINS.includes(match.hostname)) return null;
|
||||||
if (match.pathname.match(bypassPaths)) return null;
|
if (match.pathname.match(bypassPaths)) return null;
|
||||||
|
|
||||||
const { redditRedirect } = appSettings.value;
|
return <RedditLink url={match} />;
|
||||||
const fixed = redditRedirect ? replaceDomain(match, redditRedirect) : match;
|
|
||||||
|
|
||||||
return renderGenericUrl(fixed);
|
|
||||||
}
|
}
|
||||||
|
@@ -1,14 +1,18 @@
|
|||||||
import { replaceDomain } from "../../../helpers/url";
|
import { replaceDomain } from "../../../helpers/url";
|
||||||
import appSettings from "../../../services/settings/app-settings";
|
import useAppSettings from "../../../hooks/use-app-settings";
|
||||||
import { renderOpenGraphUrl } from "./common";
|
import { renderOpenGraphUrl } from "./common";
|
||||||
|
|
||||||
// copied from https://github.com/SimonBrazell/privacy-redirect/blob/master/src/assets/javascripts/helpers/twitter.js
|
// copied from https://github.com/SimonBrazell/privacy-redirect/blob/master/src/assets/javascripts/helpers/twitter.js
|
||||||
export const TWITTER_DOMAINS = ["x.com", "twitter.com", "www.twitter.com", "mobile.twitter.com", "pbs.twimg.com"];
|
export const TWITTER_DOMAINS = ["x.com", "twitter.com", "www.twitter.com", "mobile.twitter.com", "pbs.twimg.com"];
|
||||||
|
|
||||||
|
function TwitterLink({ url, isLineEnd }: { url: URL; isLineEnd?: boolean }) {
|
||||||
|
const { twitterRedirect } = useAppSettings();
|
||||||
|
if (twitterRedirect) return renderOpenGraphUrl(replaceDomain(url, twitterRedirect), !!isLineEnd);
|
||||||
|
else return renderOpenGraphUrl(url, !!isLineEnd);
|
||||||
|
}
|
||||||
|
|
||||||
export function renderTwitterUrl(match: URL, isLineEnd: boolean) {
|
export function renderTwitterUrl(match: URL, isLineEnd: boolean) {
|
||||||
if (!TWITTER_DOMAINS.includes(match.hostname)) return null;
|
if (!TWITTER_DOMAINS.includes(match.hostname)) return null;
|
||||||
|
|
||||||
const { twitterRedirect } = appSettings.value;
|
return <TwitterLink url={match} isLineEnd={isLineEnd} />;
|
||||||
if (twitterRedirect) return renderOpenGraphUrl(replaceDomain(match, twitterRedirect), isLineEnd);
|
|
||||||
else return renderOpenGraphUrl(match, isLineEnd);
|
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { AspectRatio } from "@chakra-ui/react";
|
import { AspectRatio } from "@chakra-ui/react";
|
||||||
import appSettings from "../../../services/settings/app-settings";
|
|
||||||
import ExpandableEmbed from "../expandable-embed";
|
import ExpandableEmbed from "../expandable-embed";
|
||||||
|
import useAppSettings from "../../../hooks/use-app-settings";
|
||||||
|
|
||||||
// copied from https://github.com/SimonBrazell/privacy-redirect/blob/master/src/assets/javascripts/helpers/youtube.js
|
// copied from https://github.com/SimonBrazell/privacy-redirect/blob/master/src/assets/javascripts/helpers/youtube.js
|
||||||
export const YOUTUBE_DOMAINS = [
|
export const YOUTUBE_DOMAINS = [
|
||||||
@@ -15,20 +15,15 @@ export const YOUTUBE_DOMAINS = [
|
|||||||
"music.youtube.com",
|
"music.youtube.com",
|
||||||
];
|
];
|
||||||
|
|
||||||
export function renderYoutubePlaylistURL(match: URL) {
|
function YoutubePlaylistEmbed({ url }: { url: URL }) {
|
||||||
if (!YOUTUBE_DOMAINS.includes(match.hostname)) return null;
|
const { youtubeRedirect } = useAppSettings();
|
||||||
if (!match.pathname.startsWith("/playlist")) return null;
|
const listId = url.searchParams.get("list")!;
|
||||||
|
|
||||||
const { youtubeRedirect } = appSettings.value;
|
|
||||||
|
|
||||||
const listId = match.searchParams.get("list");
|
|
||||||
if (!listId) return null;
|
|
||||||
|
|
||||||
const embedUrl = new URL(`embed/videoseries`, youtubeRedirect || "https://www.youtube-nocookie.com");
|
const embedUrl = new URL(`embed/videoseries`, youtubeRedirect || "https://www.youtube-nocookie.com");
|
||||||
embedUrl.searchParams.set("list", listId);
|
embedUrl.searchParams.set("list", listId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ExpandableEmbed label="Youtube Playlist" url={match}>
|
<ExpandableEmbed label="Youtube Playlist" url={url}>
|
||||||
<AspectRatio ratio={560 / 315} maxWidth="40rem" zIndex={1} position="relative">
|
<AspectRatio ratio={560 / 315} maxWidth="40rem" zIndex={1} position="relative">
|
||||||
<iframe
|
<iframe
|
||||||
src={embedUrl.toString()}
|
src={embedUrl.toString()}
|
||||||
@@ -42,20 +37,26 @@ export function renderYoutubePlaylistURL(match: URL) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function renderYoutubeVideoURL(match: URL) {
|
export function renderYoutubePlaylistURL(match: URL) {
|
||||||
if (!YOUTUBE_DOMAINS.includes(match.hostname)) return null;
|
if (!YOUTUBE_DOMAINS.includes(match.hostname)) return null;
|
||||||
if (match.pathname.startsWith("/live")) return null;
|
if (!match.pathname.startsWith("/playlist")) return null;
|
||||||
|
|
||||||
const { youtubeRedirect } = appSettings.value;
|
const listId = match.searchParams.get("list");
|
||||||
|
if (!listId) return null;
|
||||||
|
|
||||||
var videoId = match.searchParams.get("v");
|
return <YoutubePlaylistEmbed url={match} />;
|
||||||
if (match.hostname === "youtu.be") videoId = match.pathname.split("/")[1];
|
}
|
||||||
|
|
||||||
|
function YoutubeVideoEmbed({ url }: { url: URL }) {
|
||||||
|
const { youtubeRedirect } = useAppSettings();
|
||||||
|
var videoId = url.searchParams.get("v");
|
||||||
|
if (url.hostname === "youtu.be") videoId = url.pathname.split("/")[1];
|
||||||
if (!videoId) return null;
|
if (!videoId) return null;
|
||||||
|
|
||||||
const embedUrl = new URL(`/embed/${videoId}`, youtubeRedirect || "https://www.youtube-nocookie.com");
|
const embedUrl = new URL(`/embed/${videoId}`, youtubeRedirect || "https://www.youtube-nocookie.com");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ExpandableEmbed label="Youtube" url={match}>
|
<ExpandableEmbed label="Youtube" url={url}>
|
||||||
<AspectRatio ratio={16 / 10} maxWidth="40rem" zIndex={1} position="relative">
|
<AspectRatio ratio={16 / 10} maxWidth="40rem" zIndex={1} position="relative">
|
||||||
<iframe
|
<iframe
|
||||||
src={embedUrl.toString()}
|
src={embedUrl.toString()}
|
||||||
@@ -69,12 +70,21 @@ export function renderYoutubeVideoURL(match: URL) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function renderYoutubeVideoURL(match: URL) {
|
||||||
|
if (!YOUTUBE_DOMAINS.includes(match.hostname)) return null;
|
||||||
|
if (match.pathname.startsWith("/live")) return null;
|
||||||
|
|
||||||
|
var videoId = match.searchParams.get("v");
|
||||||
|
if (match.hostname === "youtu.be") videoId = match.pathname.split("/")[1];
|
||||||
|
if (!videoId) return null;
|
||||||
|
|
||||||
|
return <YoutubeVideoEmbed url={match} />;
|
||||||
|
}
|
||||||
|
|
||||||
// nostr:nevent1qqszwj6mk665ga4r25w5vzxmy9rsvqj42kk4gnkq2t2utljr6as948qpp4mhxue69uhkummn9ekx7mqprdmhxue69uhkvet9v3ejumn0wd68ytnzv9hxgtmdv4kk245xvyn
|
// nostr:nevent1qqszwj6mk665ga4r25w5vzxmy9rsvqj42kk4gnkq2t2utljr6as948qpp4mhxue69uhkummn9ekx7mqprdmhxue69uhkvet9v3ejumn0wd68ytnzv9hxgtmdv4kk245xvyn
|
||||||
export function renderYoutubeURL(match: URL) {
|
export function renderYoutubeURL(match: URL) {
|
||||||
if (!YOUTUBE_DOMAINS.includes(match.hostname)) return null;
|
if (!YOUTUBE_DOMAINS.includes(match.hostname)) return null;
|
||||||
if (match.pathname.startsWith("/live")) return null;
|
if (match.pathname.startsWith("/live")) return null;
|
||||||
|
|
||||||
const { youtubeRedirect } = appSettings.value;
|
|
||||||
|
|
||||||
return renderYoutubePlaylistURL(match) || renderYoutubeVideoURL(match);
|
return renderYoutubePlaylistURL(match) || renderYoutubeVideoURL(match);
|
||||||
}
|
}
|
||||||
|
@@ -22,7 +22,6 @@ import UserLink from "../../user/user-link";
|
|||||||
import NoteZapButton from "../note-zap-button";
|
import NoteZapButton from "../note-zap-button";
|
||||||
import { ExpandProvider } from "../../../providers/local/expanded";
|
import { ExpandProvider } from "../../../providers/local/expanded";
|
||||||
import useSubject from "../../../hooks/use-subject";
|
import useSubject from "../../../hooks/use-subject";
|
||||||
import appSettings from "../../../services/settings/app-settings";
|
|
||||||
import EventVerificationIcon from "../../common-event/event-verification-icon";
|
import EventVerificationIcon from "../../common-event/event-verification-icon";
|
||||||
import RepostButton from "./components/repost-button";
|
import RepostButton from "./components/repost-button";
|
||||||
import QuoteEventButton from "../quote-event-button";
|
import QuoteEventButton from "../quote-event-button";
|
||||||
@@ -48,6 +47,7 @@ import useEventIntersectionRef from "../../../hooks/use-event-intersection-ref";
|
|||||||
import relayHintService from "../../../services/event-relay-hint";
|
import relayHintService from "../../../services/event-relay-hint";
|
||||||
import localSettings from "../../../services/local-settings";
|
import localSettings from "../../../services/local-settings";
|
||||||
import NotePublishedUsing from "../note-published-using";
|
import NotePublishedUsing from "../note-published-using";
|
||||||
|
import useAppSettings from "../../../hooks/use-app-settings";
|
||||||
|
|
||||||
export type TimelineNoteProps = Omit<CardProps, "children"> & {
|
export type TimelineNoteProps = Omit<CardProps, "children"> & {
|
||||||
event: NostrEvent;
|
event: NostrEvent;
|
||||||
@@ -69,7 +69,7 @@ export function TimelineNote({
|
|||||||
...props
|
...props
|
||||||
}: TimelineNoteProps) {
|
}: TimelineNoteProps) {
|
||||||
const account = useCurrentAccount();
|
const account = useCurrentAccount();
|
||||||
const { showReactions, showSignatureVerification } = useSubject(appSettings);
|
const { showReactions, showSignatureVerification } = useAppSettings();
|
||||||
const hideZapBubbles = useSubject(localSettings.hideZapBubbles);
|
const hideZapBubbles = useSubject(localSettings.hideZapBubbles);
|
||||||
const replyForm = useDisclosure();
|
const replyForm = useDisclosure();
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { ColorModeWithSystem } from "@chakra-ui/react";
|
import { ColorModeWithSystem } from "@chakra-ui/react";
|
||||||
import { NostrEvent } from "../../types/nostr-event";
|
import { NostrEvent } from "../types/nostr-event";
|
||||||
import { safeJson } from "../../helpers/parse";
|
import { safeJson } from "./parse";
|
||||||
|
|
||||||
export type AppSettingsV0 = {
|
export type AppSettingsV0 = {
|
||||||
version: 0;
|
version: 0;
|
@@ -1,5 +1,8 @@
|
|||||||
import { fixOrientationAndStripMetadata } from "../lib/fix-image-orientation";
|
import { fixOrientationAndStripMetadata } from "../lib/fix-image-orientation";
|
||||||
import appSettings from "../services/settings/app-settings";
|
import AppSettingsQuery from "../queries/app-settings";
|
||||||
|
import accountService from "../services/account";
|
||||||
|
import { queryStore } from "../services/event-store";
|
||||||
|
import { AppSettings } from "./app-settings";
|
||||||
|
|
||||||
export type ImageSize = { width: number; height: number };
|
export type ImageSize = { width: number; height: number };
|
||||||
const imageSizeCache = new Map<string, ImageSize>();
|
const imageSizeCache = new Map<string, ImageSize>();
|
||||||
@@ -21,12 +24,23 @@ export function getImageSize(src: string): Promise<{ width: number; height: numb
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hack to get app settings
|
||||||
|
let settings: AppSettings | undefined;
|
||||||
|
let sub: ZenObservable.Subscription;
|
||||||
|
accountService.current.subscribe((account) => {
|
||||||
|
if (sub) sub.unsubscribe();
|
||||||
|
if (!account) return;
|
||||||
|
sub = queryStore
|
||||||
|
.runQuery(AppSettingsQuery)(account.pubkey)
|
||||||
|
.subscribe((v) => (settings = v));
|
||||||
|
});
|
||||||
|
|
||||||
export function buildImageProxyURL(src: string, size: string | number) {
|
export function buildImageProxyURL(src: string, size: string | number) {
|
||||||
let url: URL | null = null;
|
let url: URL | null = null;
|
||||||
if (window.IMAGE_PROXY_PATH) {
|
if (window.IMAGE_PROXY_PATH) {
|
||||||
url = new URL(location.origin);
|
url = new URL(location.origin);
|
||||||
url.pathname = window.IMAGE_PROXY_PATH;
|
url.pathname = window.IMAGE_PROXY_PATH;
|
||||||
} else if (appSettings.value.imageProxy) url = new URL(appSettings.value.imageProxy);
|
} else if (settings?.imageProxy) url = new URL(settings.imageProxy);
|
||||||
if (url === null) return;
|
if (url === null) return;
|
||||||
|
|
||||||
url.pathname = url.pathname.replace(/\/$/, "") + "/" + size + "/" + src;
|
url.pathname = url.pathname.replace(/\/$/, "") + "/" + size + "/" + src;
|
||||||
|
@@ -1,12 +1,26 @@
|
|||||||
import appSettings from "../services/settings/app-settings";
|
import AppSettingsQuery from "../queries/app-settings";
|
||||||
|
import accountService from "../services/account";
|
||||||
|
import { queryStore } from "../services/event-store";
|
||||||
|
import { AppSettings } from "./app-settings";
|
||||||
import { convertToUrl } from "./url";
|
import { convertToUrl } from "./url";
|
||||||
|
|
||||||
|
// hack to get app settings
|
||||||
|
let settings: AppSettings | undefined;
|
||||||
|
let sub: ZenObservable.Subscription;
|
||||||
|
accountService.current.subscribe((account) => {
|
||||||
|
if (sub) sub.unsubscribe();
|
||||||
|
if (!account) return;
|
||||||
|
sub = queryStore
|
||||||
|
.runQuery(AppSettingsQuery)(account.pubkey)
|
||||||
|
.subscribe((v) => (settings = v));
|
||||||
|
});
|
||||||
|
|
||||||
const clearNetFailedHosts = new Set();
|
const clearNetFailedHosts = new Set();
|
||||||
const proxyFailedHosts = new Set();
|
const proxyFailedHosts = new Set();
|
||||||
|
|
||||||
export function createRequestProxyUrl(url: URL | string, corsProxy?: string) {
|
export function createRequestProxyUrl(url: URL | string, corsProxy?: string) {
|
||||||
if (!corsProxy && window.REQUEST_PROXY) corsProxy = new URL(window.REQUEST_PROXY, location.origin).toString();
|
if (!corsProxy && window.REQUEST_PROXY) corsProxy = new URL(window.REQUEST_PROXY, location.origin).toString();
|
||||||
if (!corsProxy && appSettings.value.corsProxy) corsProxy = appSettings.value.corsProxy;
|
if (!corsProxy && settings?.corsProxy) corsProxy = settings.corsProxy;
|
||||||
if (!corsProxy) return url;
|
if (!corsProxy) return url;
|
||||||
|
|
||||||
if (corsProxy.includes("<url>")) {
|
if (corsProxy.includes("<url>")) {
|
||||||
@@ -19,7 +33,7 @@ export function createRequestProxyUrl(url: URL | string, corsProxy?: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function fetchWithProxy(url: URL | string, opts?: RequestInit) {
|
export function fetchWithProxy(url: URL | string, opts?: RequestInit) {
|
||||||
if (!appSettings.value.corsProxy && !window.REQUEST_PROXY) return fetch(url, opts);
|
if (!settings?.corsProxy && !window.REQUEST_PROXY) return fetch(url, opts);
|
||||||
|
|
||||||
const u = typeof url === "string" ? convertToUrl(url) : url;
|
const u = typeof url === "string" ? convertToUrl(url) : url;
|
||||||
|
|
||||||
|
@@ -1,35 +1,42 @@
|
|||||||
import { useCallback } from "react";
|
import { useCallback, useEffect } from "react";
|
||||||
import { useToast } from "@chakra-ui/react";
|
|
||||||
|
|
||||||
import appSettings from "../services/settings/app-settings";
|
import { AppSettings, defaultSettings } from "../helpers/app-settings";
|
||||||
import useSubject from "./use-subject";
|
|
||||||
import { AppSettings } from "../services/settings/migrations";
|
|
||||||
import useCurrentAccount from "./use-current-account";
|
import useCurrentAccount from "./use-current-account";
|
||||||
import accountService from "../services/account";
|
import accountService from "../services/account";
|
||||||
import userAppSettings from "../services/settings/user-app-settings";
|
import userAppSettings from "../services/user-app-settings";
|
||||||
import { usePublishEvent } from "../providers/global/publish-provider";
|
import { usePublishEvent } from "../providers/global/publish-provider";
|
||||||
|
import { useStoreQuery } from "applesauce-react";
|
||||||
|
import AppSettingsQuery from "../queries/app-settings";
|
||||||
|
import { useReadRelays } from "./use-client-relays";
|
||||||
|
|
||||||
export default function useAppSettings() {
|
export default function useAppSettings() {
|
||||||
const account = useCurrentAccount();
|
const account = useCurrentAccount();
|
||||||
const settings = useSubject(appSettings);
|
|
||||||
const publish = usePublishEvent();
|
const publish = usePublishEvent();
|
||||||
|
|
||||||
|
const localSettings = account?.localSettings;
|
||||||
|
const syncedSettings = useStoreQuery(AppSettingsQuery, account && [account.pubkey]);
|
||||||
|
|
||||||
|
const readRelays = useReadRelays();
|
||||||
|
useEffect(() => {
|
||||||
|
if (account?.pubkey) userAppSettings.requestAppSettings(account.pubkey, readRelays);
|
||||||
|
}, [account?.pubkey, readRelays]);
|
||||||
|
|
||||||
const updateSettings = useCallback(
|
const updateSettings = useCallback(
|
||||||
async (newSettings: Partial<AppSettings>) => {
|
async (newSettings: Partial<AppSettings>) => {
|
||||||
if (!account) return;
|
if (!account) return;
|
||||||
const full: AppSettings = { ...settings, ...newSettings };
|
const updated: Partial<AppSettings> = { ...syncedSettings, ...newSettings };
|
||||||
|
|
||||||
if (account.readonly) {
|
accountService.updateAccountLocalSettings(account.pubkey, updated);
|
||||||
accountService.updateAccountLocalSettings(account.pubkey, full);
|
if (!account.readonly) {
|
||||||
appSettings.next(full);
|
const draft = userAppSettings.buildAppSettingsEvent(updated);
|
||||||
} else {
|
|
||||||
const draft = userAppSettings.buildAppSettingsEvent(full);
|
|
||||||
await publish("Update Settings", draft);
|
await publish("Update Settings", draft);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[settings, account, publish],
|
[syncedSettings, account, publish],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const settings: AppSettings = { ...defaultSettings, ...localSettings, ...syncedSettings };
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...settings,
|
...settings,
|
||||||
updateSettings,
|
updateSettings,
|
||||||
|
@@ -1,12 +1,11 @@
|
|||||||
import { useColorMode } from "@chakra-ui/react";
|
import { useColorMode } from "@chakra-ui/react";
|
||||||
import useSubject from "./use-subject";
|
|
||||||
import appSettings from "../services/settings/app-settings";
|
|
||||||
import { useSearchParams } from "react-router-dom";
|
import { useSearchParams } from "react-router-dom";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
import useAppSettings from "./use-app-settings";
|
||||||
|
|
||||||
export default function useSetColorMode() {
|
export default function useSetColorMode() {
|
||||||
const { setColorMode } = useColorMode();
|
const { setColorMode } = useColorMode();
|
||||||
const { colorMode } = useSubject(appSettings);
|
const { colorMode } = useAppSettings();
|
||||||
const [params] = useSearchParams();
|
const [params] = useSearchParams();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@@ -14,8 +14,7 @@ import PublishProvider from "./publish-provider";
|
|||||||
import WebOfTrustProvider from "./web-of-trust-provider";
|
import WebOfTrustProvider from "./web-of-trust-provider";
|
||||||
import { queryStore } from "../../services/event-store";
|
import { queryStore } from "../../services/event-store";
|
||||||
|
|
||||||
// Top level providers, should be render as close to the root as possible
|
function ThemeProviders({ children }: { children: React.ReactNode }) {
|
||||||
export const GlobalProviders = ({ children }: { children: React.ReactNode }) => {
|
|
||||||
const { theme: themeName, primaryColor, maxPageWidth } = useAppSettings();
|
const { theme: themeName, primaryColor, maxPageWidth } = useAppSettings();
|
||||||
const theme = useMemo(
|
const theme = useMemo(
|
||||||
() => buildTheme(themeName, primaryColor, maxPageWidth !== "none" ? maxPageWidth : undefined),
|
() => buildTheme(themeName, primaryColor, maxPageWidth !== "none" ? maxPageWidth : undefined),
|
||||||
@@ -24,25 +23,32 @@ export const GlobalProviders = ({ children }: { children: React.ReactNode }) =>
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ChakraProvider theme={theme} colorModeManager={localStorageManager}>
|
<ChakraProvider theme={theme} colorModeManager={localStorageManager}>
|
||||||
<BreakpointProvider>
|
<BreakpointProvider>{children}</BreakpointProvider>
|
||||||
<QueryStoreProvider store={queryStore}>
|
|
||||||
<SigningProvider>
|
|
||||||
<PublishProvider>
|
|
||||||
<NotificationsProvider>
|
|
||||||
<DMTimelineProvider>
|
|
||||||
<DefaultEmojiProvider>
|
|
||||||
<UserEmojiProvider>
|
|
||||||
<AllUserSearchDirectoryProvider>
|
|
||||||
<WebOfTrustProvider>{children}</WebOfTrustProvider>
|
|
||||||
</AllUserSearchDirectoryProvider>
|
|
||||||
</UserEmojiProvider>
|
|
||||||
</DefaultEmojiProvider>
|
|
||||||
</DMTimelineProvider>
|
|
||||||
</NotificationsProvider>
|
|
||||||
</PublishProvider>
|
|
||||||
</SigningProvider>
|
|
||||||
</QueryStoreProvider>
|
|
||||||
</BreakpointProvider>
|
|
||||||
</ChakraProvider>
|
</ChakraProvider>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Top level providers, should be render as close to the root as possible
|
||||||
|
export const GlobalProviders = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
return (
|
||||||
|
<QueryStoreProvider store={queryStore}>
|
||||||
|
<ThemeProviders>
|
||||||
|
<SigningProvider>
|
||||||
|
<PublishProvider>
|
||||||
|
<NotificationsProvider>
|
||||||
|
<DMTimelineProvider>
|
||||||
|
<DefaultEmojiProvider>
|
||||||
|
<UserEmojiProvider>
|
||||||
|
<AllUserSearchDirectoryProvider>
|
||||||
|
<WebOfTrustProvider>{children}</WebOfTrustProvider>
|
||||||
|
</AllUserSearchDirectoryProvider>
|
||||||
|
</UserEmojiProvider>
|
||||||
|
</DefaultEmojiProvider>
|
||||||
|
</DMTimelineProvider>
|
||||||
|
</NotificationsProvider>
|
||||||
|
</PublishProvider>
|
||||||
|
</SigningProvider>
|
||||||
|
</ThemeProviders>
|
||||||
|
</QueryStoreProvider>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import AppHandlerProvider from "./app-handler-provider";
|
import AppHandlerProvider from "./app-handler-provider";
|
||||||
import DebugModalProvider from "./debug-modal-provider";
|
import DebugModalProvider from "./debug-modal-provider";
|
||||||
import DeleteEventProvider from "./delete-event-provider";
|
import DeleteEventProvider from "./delete-event-provider";
|
||||||
import InvoiceModalProvider from "./invoice-modal";
|
import InvoiceModalProvider from "./invoice-modal-provider";
|
||||||
import MuteModalProvider from "./mute-modal-provider";
|
import MuteModalProvider from "./mute-modal-provider";
|
||||||
import PostModalProvider from "./post-modal-provider";
|
import PostModalProvider from "./post-modal-provider";
|
||||||
import RequireReadRelays from "./require-read-relays";
|
import RequireReadRelays from "./require-read-relays";
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import React, { useCallback, useContext, useState } from "react";
|
import React, { useCallback, useContext, useState } from "react";
|
||||||
import InvoiceModal from "../../components/invoice-modal";
|
import InvoiceModal from "../../components/invoice-modal";
|
||||||
import createDefer, { Deferred } from "../../classes/deferred";
|
import createDefer, { Deferred } from "../../classes/deferred";
|
||||||
import appSettings from "../../services/settings/app-settings";
|
import useAppSettings from "../../hooks/use-app-settings";
|
||||||
|
|
||||||
export type InvoiceModalContext = {
|
export type InvoiceModalContext = {
|
||||||
requestPay: (invoice: string) => Promise<void>;
|
requestPay: (invoice: string) => Promise<void>;
|
||||||
@@ -20,9 +20,10 @@ export function useInvoiceModalContext() {
|
|||||||
export default function InvoiceModalProvider({ children }: { children: React.ReactNode }) {
|
export default function InvoiceModalProvider({ children }: { children: React.ReactNode }) {
|
||||||
const [invoice, setInvoice] = useState<string>();
|
const [invoice, setInvoice] = useState<string>();
|
||||||
const [defer, setDefer] = useState<Deferred<void>>();
|
const [defer, setDefer] = useState<Deferred<void>>();
|
||||||
|
const { autoPayWithWebLN } = useAppSettings();
|
||||||
|
|
||||||
const requestPay = useCallback(async (invoice: string) => {
|
const requestPay = useCallback(async (invoice: string) => {
|
||||||
if (window.webln && appSettings.value.autoPayWithWebLN) {
|
if (window.webln && autoPayWithWebLN) {
|
||||||
try {
|
try {
|
||||||
if (!window.webln.enabled) await window.webln.enable();
|
if (!window.webln.enabled) await window.webln.enable();
|
||||||
await window.webln.sendPayment(invoice);
|
await window.webln.sendPayment(invoice);
|
17
src/queries/app-settings.ts
Normal file
17
src/queries/app-settings.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { Query } from "applesauce-core";
|
||||||
|
|
||||||
|
import { AppSettings, defaultSettings } from "../helpers/app-settings";
|
||||||
|
import { APP_SETTING_IDENTIFIER, APP_SETTINGS_KIND } from "../services/user-app-settings";
|
||||||
|
import { safeJson } from "../helpers/parse";
|
||||||
|
|
||||||
|
export default function AppSettingsQuery(pubkey: string): Query<AppSettings> {
|
||||||
|
return {
|
||||||
|
key: pubkey,
|
||||||
|
run: (events) =>
|
||||||
|
events.replaceable(APP_SETTINGS_KIND, pubkey, APP_SETTING_IDENTIFIER).map((event) => {
|
||||||
|
if (!event) return defaultSettings;
|
||||||
|
const parsed = safeJson(event.content, defaultSettings) as Partial<AppSettings>;
|
||||||
|
return { ...defaultSettings, ...parsed };
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
@@ -9,7 +9,7 @@ import SerialPortAccount from "../classes/accounts/serial-port-account";
|
|||||||
import { PersistentSubject } from "../classes/subject";
|
import { PersistentSubject } from "../classes/subject";
|
||||||
import { logger } from "../helpers/debug";
|
import { logger } from "../helpers/debug";
|
||||||
import db from "./db";
|
import db from "./db";
|
||||||
import { AppSettings } from "./settings/migrations";
|
import { AppSettings } from "../helpers/app-settings";
|
||||||
|
|
||||||
type CommonAccount = {
|
type CommonAccount = {
|
||||||
pubkey: string;
|
pubkey: string;
|
||||||
@@ -119,7 +119,7 @@ class AccountService {
|
|||||||
return db.put("accounts", account.toJSON());
|
return db.put("accounts", account.toJSON());
|
||||||
}
|
}
|
||||||
|
|
||||||
updateAccountLocalSettings(pubkey: string, settings: AppSettings) {
|
updateAccountLocalSettings(pubkey: string, settings: Partial<AppSettings>) {
|
||||||
const account = this.accounts.value.find((acc) => acc.pubkey === pubkey);
|
const account = this.accounts.value.find((acc) => acc.pubkey === pubkey);
|
||||||
if (account) account.localSettings = settings;
|
if (account) account.localSettings = settings;
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,7 @@ import { DBSchema } from "idb";
|
|||||||
|
|
||||||
import { NostrEvent } from "../../types/nostr-event";
|
import { NostrEvent } from "../../types/nostr-event";
|
||||||
import { RelayInformationDocument } from "../relay-info";
|
import { RelayInformationDocument } from "../relay-info";
|
||||||
import { AppSettings } from "../settings/migrations";
|
import { AppSettings } from "../../helpers/app-settings";
|
||||||
|
|
||||||
export interface SchemaV1 {
|
export interface SchemaV1 {
|
||||||
userMetadata: {
|
userMetadata: {
|
||||||
|
@@ -1,47 +0,0 @@
|
|||||||
import { PersistentSubject } from "../../classes/subject";
|
|
||||||
import accountService from "../account";
|
|
||||||
import userAppSettings from "./user-app-settings";
|
|
||||||
import clientRelaysService from "../client-relays";
|
|
||||||
import { defaultSettings } from "./migrations";
|
|
||||||
import { logger } from "../../helpers/debug";
|
|
||||||
|
|
||||||
const log = logger.extend("AppSettings");
|
|
||||||
|
|
||||||
export let appSettings = new PersistentSubject(defaultSettings);
|
|
||||||
appSettings.subscribe((event) => {
|
|
||||||
log(`Changed`, event);
|
|
||||||
});
|
|
||||||
|
|
||||||
let accountSub: ZenObservable.Subscription;
|
|
||||||
accountService.current.subscribe(() => {
|
|
||||||
const account = accountService.current.value;
|
|
||||||
|
|
||||||
if (!account) {
|
|
||||||
appSettings.next(defaultSettings);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (accountSub) accountSub.unsubscribe();
|
|
||||||
|
|
||||||
if (account.localSettings) {
|
|
||||||
appSettings.next(account.localSettings);
|
|
||||||
log("Loaded user settings from local storage");
|
|
||||||
}
|
|
||||||
|
|
||||||
const subject = userAppSettings.requestAppSettings(account.pubkey, clientRelaysService.readRelays.value, {
|
|
||||||
alwaysRequest: true,
|
|
||||||
});
|
|
||||||
appSettings.next(subject.value || defaultSettings);
|
|
||||||
accountSub = subject.subscribe((s) => appSettings.next(s));
|
|
||||||
});
|
|
||||||
|
|
||||||
// clientRelaysService.relays.subscribe(() => {
|
|
||||||
// // relays changed, look for settings again
|
|
||||||
// const account = accountService.current.value;
|
|
||||||
|
|
||||||
// if (account) {
|
|
||||||
// userAppSettings.requestAppSettings(account.pubkey, clientRelaysService.getInboxURLs(), { alwaysRequest: true });
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
export default appSettings;
|
|
@@ -1,50 +0,0 @@
|
|||||||
import dayjs from "dayjs";
|
|
||||||
|
|
||||||
import { DraftNostrEvent } from "../../types/nostr-event";
|
|
||||||
|
|
||||||
import SuperMap from "../../classes/super-map";
|
|
||||||
import { PersistentSubject } from "../../classes/subject";
|
|
||||||
import { AppSettings, defaultSettings, parseAppSettings } from "./migrations";
|
|
||||||
import replaceableEventsService, { RequestOptions } from "../replaceable-events";
|
|
||||||
|
|
||||||
export const APP_SETTINGS_KIND = 30078;
|
|
||||||
export const SETTING_EVENT_IDENTIFIER = "nostrudel-settings";
|
|
||||||
|
|
||||||
class UserAppSettings {
|
|
||||||
private parsedSubjects = new SuperMap<string, PersistentSubject<AppSettings>>(
|
|
||||||
() => new PersistentSubject<AppSettings>(defaultSettings),
|
|
||||||
);
|
|
||||||
getSubject(pubkey: string) {
|
|
||||||
return this.parsedSubjects.get(pubkey);
|
|
||||||
}
|
|
||||||
requestAppSettings(pubkey: string, relays: Iterable<string>, opts?: RequestOptions) {
|
|
||||||
const sub = this.parsedSubjects.get(pubkey);
|
|
||||||
const requestSub = replaceableEventsService.requestEvent(
|
|
||||||
relays,
|
|
||||||
APP_SETTINGS_KIND,
|
|
||||||
pubkey,
|
|
||||||
SETTING_EVENT_IDENTIFIER,
|
|
||||||
opts,
|
|
||||||
);
|
|
||||||
sub.connectWithMapper(requestSub, (event, next) => next(parseAppSettings(event)));
|
|
||||||
return sub;
|
|
||||||
}
|
|
||||||
|
|
||||||
buildAppSettingsEvent(settings: AppSettings): DraftNostrEvent {
|
|
||||||
return {
|
|
||||||
kind: APP_SETTINGS_KIND,
|
|
||||||
tags: [["d", SETTING_EVENT_IDENTIFIER]],
|
|
||||||
content: JSON.stringify(settings),
|
|
||||||
created_at: dayjs().unix(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const userAppSettings = new UserAppSettings();
|
|
||||||
|
|
||||||
if (import.meta.env.DEV) {
|
|
||||||
// @ts-ignore
|
|
||||||
window.userAppSettings = userAppSettings;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default userAppSettings;
|
|
44
src/services/user-app-settings.ts
Normal file
44
src/services/user-app-settings.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
|
import { DraftNostrEvent } from "../types/nostr-event";
|
||||||
|
|
||||||
|
import SuperMap from "../classes/super-map";
|
||||||
|
import { AppSettings } from "../helpers/app-settings";
|
||||||
|
import replaceableEventsService, { RequestOptions } from "./replaceable-events";
|
||||||
|
import { queryStore } from "./event-store";
|
||||||
|
import Observable from "zen-observable";
|
||||||
|
import AppSettingsQuery from "../queries/app-settings";
|
||||||
|
|
||||||
|
export const APP_SETTINGS_KIND = 30078;
|
||||||
|
export const APP_SETTING_IDENTIFIER = "nostrudel-settings";
|
||||||
|
|
||||||
|
class UserAppSettings {
|
||||||
|
private parsed = new SuperMap<string, Observable<AppSettings>>((pubkey) =>
|
||||||
|
queryStore.runQuery(AppSettingsQuery)(pubkey),
|
||||||
|
);
|
||||||
|
getSubject(pubkey: string) {
|
||||||
|
return this.parsed.get(pubkey);
|
||||||
|
}
|
||||||
|
requestAppSettings(pubkey: string, relays: Iterable<string>, opts?: RequestOptions) {
|
||||||
|
replaceableEventsService.requestEvent(relays, APP_SETTINGS_KIND, pubkey, APP_SETTING_IDENTIFIER, opts);
|
||||||
|
return this.parsed.get(pubkey);
|
||||||
|
}
|
||||||
|
|
||||||
|
buildAppSettingsEvent(settings: Partial<AppSettings>): DraftNostrEvent {
|
||||||
|
return {
|
||||||
|
kind: APP_SETTINGS_KIND,
|
||||||
|
tags: [["d", APP_SETTING_IDENTIFIER]],
|
||||||
|
content: JSON.stringify(settings),
|
||||||
|
created_at: dayjs().unix(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const userAppSettings = new UserAppSettings();
|
||||||
|
|
||||||
|
if (import.meta.env.DEV) {
|
||||||
|
// @ts-ignore
|
||||||
|
window.userAppSettings = userAppSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default userAppSettings;
|
@@ -7,10 +7,9 @@ import accountService from "./account";
|
|||||||
import clientRelaysService from "./client-relays";
|
import clientRelaysService from "./client-relays";
|
||||||
import { offlineMode } from "./offline-mode";
|
import { offlineMode } from "./offline-mode";
|
||||||
import replaceableEventsService from "./replaceable-events";
|
import replaceableEventsService from "./replaceable-events";
|
||||||
import userAppSettings from "./settings/user-app-settings";
|
import userAppSettings, { APP_SETTING_IDENTIFIER, APP_SETTINGS_KIND } from "./user-app-settings";
|
||||||
import userMailboxesService from "./user-mailboxes";
|
|
||||||
import userMetadataService from "./user-metadata";
|
|
||||||
import { USER_BLOSSOM_SERVER_LIST_KIND } from "blossom-client-sdk";
|
import { USER_BLOSSOM_SERVER_LIST_KIND } from "blossom-client-sdk";
|
||||||
|
import { queryStore } from "./event-store";
|
||||||
|
|
||||||
const log = logger.extend("user-event-sync");
|
const log = logger.extend("user-event-sync");
|
||||||
|
|
||||||
@@ -18,31 +17,52 @@ function downloadEvents() {
|
|||||||
const account = accountService.current.value!;
|
const account = accountService.current.value!;
|
||||||
const relays = clientRelaysService.readRelays.value;
|
const relays = clientRelaysService.readRelays.value;
|
||||||
|
|
||||||
log("Loading user information");
|
const requestReplaceable = (relays: Iterable<string>, kind: number, d?: string) => {
|
||||||
userMetadataService.requestMetadata(account.pubkey, [...relays, COMMON_CONTACT_RELAY], { alwaysRequest: true });
|
replaceableEventsService.requestEvent(relays, kind, account.pubkey, d, {
|
||||||
userMailboxesService.requestMailboxes(account.pubkey, [...relays, COMMON_CONTACT_RELAY], { alwaysRequest: true });
|
alwaysRequest: true,
|
||||||
userAppSettings.requestAppSettings(account.pubkey, relays, { alwaysRequest: true });
|
});
|
||||||
replaceableEventsService.requestEvent(relays, USER_BLOSSOM_SERVER_LIST_KIND, account.pubkey, undefined, {
|
};
|
||||||
alwaysRequest: true,
|
|
||||||
|
log("Loading outboxes");
|
||||||
|
requestReplaceable([...relays, COMMON_CONTACT_RELAY], kinds.RelayList);
|
||||||
|
|
||||||
|
const mailboxesSub = queryStore.mailboxes(account.pubkey).subscribe((mailboxes) => {
|
||||||
|
log("Loading user information");
|
||||||
|
requestReplaceable(mailboxes?.outboxes || relays, kinds.Metadata);
|
||||||
|
requestReplaceable(mailboxes?.outboxes || relays, USER_BLOSSOM_SERVER_LIST_KIND);
|
||||||
|
requestReplaceable(mailboxes?.outboxes || relays, kinds.SearchRelaysList);
|
||||||
|
requestReplaceable(mailboxes?.outboxes || relays, APP_SETTINGS_KIND, APP_SETTING_IDENTIFIER);
|
||||||
|
userAppSettings.requestAppSettings(account.pubkey, relays, { alwaysRequest: true });
|
||||||
|
|
||||||
|
log("Loading contacts list");
|
||||||
|
replaceableEventsService.requestEvent(
|
||||||
|
[...clientRelaysService.readRelays.value, COMMON_CONTACT_RELAY],
|
||||||
|
kinds.Contacts,
|
||||||
|
account.pubkey,
|
||||||
|
undefined,
|
||||||
|
{
|
||||||
|
alwaysRequest: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
log("Loading contacts list");
|
return () => {
|
||||||
replaceableEventsService.requestEvent(
|
mailboxesSub.unsubscribe();
|
||||||
[...clientRelaysService.readRelays.value, COMMON_CONTACT_RELAY],
|
};
|
||||||
kinds.Contacts,
|
|
||||||
account.pubkey,
|
|
||||||
undefined,
|
|
||||||
{
|
|
||||||
alwaysRequest: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
accountService.current.subscribe((account) => {
|
let unsubscribe: Function | undefined;
|
||||||
if (!account) return;
|
function update() {
|
||||||
downloadEvents();
|
const account = accountService.current.value;
|
||||||
});
|
if (unsubscribe) {
|
||||||
|
unsubscribe();
|
||||||
|
unsubscribe = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
offlineMode.subscribe((offline) => {
|
if (offlineMode.value) return;
|
||||||
if (!offline && accountService.current.value) downloadEvents();
|
if (!account) return;
|
||||||
});
|
unsubscribe = downloadEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
accountService.current.subscribe(update);
|
||||||
|
offlineMode.subscribe(update);
|
||||||
|
@@ -2,7 +2,7 @@ import { IconButton, IconButtonProps, useDisclosure } from "@chakra-ui/react";
|
|||||||
import useUserProfile from "../../../hooks/use-user-profile";
|
import useUserProfile from "../../../hooks/use-user-profile";
|
||||||
import { LightningIcon } from "../../../components/icons";
|
import { LightningIcon } from "../../../components/icons";
|
||||||
import ZapModal from "../../../components/event-zap-modal";
|
import ZapModal from "../../../components/event-zap-modal";
|
||||||
import { useInvoiceModalContext } from "../../../providers/route/invoice-modal";
|
import { useInvoiceModalContext } from "../../../providers/route/invoice-modal-provider";
|
||||||
|
|
||||||
export default function UserZapButton({ pubkey, ...props }: { pubkey: string } & Omit<IconButtonProps, "aria-label">) {
|
export default function UserZapButton({ pubkey, ...props }: { pubkey: string } & Omit<IconButtonProps, "aria-label">) {
|
||||||
const metadata = useUserProfile(pubkey);
|
const metadata = useUserProfile(pubkey);
|
||||||
|
Reference in New Issue
Block a user