mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-03-29 11:12:12 +01:00
Merge branch 'next'
This commit is contained in:
commit
b425631835
5
.changeset/clever-socks-chew.md
Normal file
5
.changeset/clever-socks-chew.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"nostrudel": minor
|
||||
---
|
||||
|
||||
Remove brb.io link from user profiles
|
5
.changeset/new-snakes-remember.md
Normal file
5
.changeset/new-snakes-remember.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"nostrudel": minor
|
||||
---
|
||||
|
||||
Add custom zap amounts to settings
|
5
.changeset/quiet-singers-cross.md
Normal file
5
.changeset/quiet-singers-cross.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"nostrudel": patch
|
||||
---
|
||||
|
||||
Increase min height of note before showing expandable overlay
|
5
.changeset/red-owls-juggle.md
Normal file
5
.changeset/red-owls-juggle.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"nostrudel": minor
|
||||
---
|
||||
|
||||
Support nostr: links in notes
|
5
.changeset/tiny-melons-attend.md
Normal file
5
.changeset/tiny-melons-attend.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"nostrudel": minor
|
||||
---
|
||||
|
||||
Add "Download Backup" link to profile edit view
|
@ -1,12 +1,10 @@
|
||||
import { createIcon, IconProps } from "@chakra-ui/icons";
|
||||
|
||||
import nostrGuruIcon from "./icons/nostr-guru.jpg";
|
||||
import brbIcon from "./icons/brb.png";
|
||||
import snortSocialIcon from "./icons/snort-social.png";
|
||||
|
||||
export const IMAGE_ICONS = {
|
||||
nostrGuruIcon,
|
||||
brbIcon,
|
||||
snortSocialIcon,
|
||||
};
|
||||
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 26 KiB |
@ -3,12 +3,13 @@ import { AspectRatio, Box, Button, ButtonGroup, Image, ImageProps, Link, useDisc
|
||||
import { InlineInvoiceCard } from "../inline-invoice-card";
|
||||
import { TweetEmbed } from "../tweet-embed";
|
||||
import { UserLink } from "../user-link";
|
||||
import { normalizeToHex } from "../../helpers/nip19";
|
||||
import { DraftNostrEvent, NostrEvent } from "../../types/nostr-event";
|
||||
import settings from "../../services/settings";
|
||||
import styled from "@emotion/styled";
|
||||
import QuoteNote from "./quote-note";
|
||||
import { useExpand } from "./expanded";
|
||||
import { nip19 } from "nostr-tools";
|
||||
import { EventPointer, ProfilePointer } from "nostr-tools/lib/nip19";
|
||||
|
||||
const BlurredImage = (props: ImageProps) => {
|
||||
const { isOpen, onToggle } = useDisclosure();
|
||||
@ -191,6 +192,38 @@ const embeds: EmbedType[] = [
|
||||
),
|
||||
isMedia: false,
|
||||
},
|
||||
// nostr: links
|
||||
// nostr:nevent1qqsthg2qlxp9l7egtwa92t8lusm7pjknmjwa75ctrrpcjyulr9754fqpz3mhxue69uhhyetvv9ujuerpd46hxtnfduq36amnwvaz7tmwdaehgu3dwp6kytnhv4kxcmmjv3jhytnwv46q2qg5q9
|
||||
// nostr:nevent1qqsq3wc73lqxd70lg43m5rul57d4mhcanttjat56e30yx5zla48qzlspz9mhxue69uhkummnw3e82efwvdhk6qgdwaehxw309ahx7uewd3hkcq5hsum
|
||||
{
|
||||
regexp: /(nostr:)?((npub|note|nprofile|nevent)1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{58,})/i,
|
||||
render: (match, event) => {
|
||||
try {
|
||||
const decoded = nip19.decode(match[2]);
|
||||
console.log(decoded);
|
||||
|
||||
switch (decoded.type) {
|
||||
case "npub":
|
||||
return <UserLink color="blue.500" pubkey={decoded.data as string} showAt />;
|
||||
case "nprofile": {
|
||||
const pointer = decoded.data as ProfilePointer;
|
||||
return <UserLink color="blue.500" pubkey={pointer.pubkey} showAt />;
|
||||
}
|
||||
case "note":
|
||||
return <QuoteNote noteId={decoded.data as string} />;
|
||||
case "nevent": {
|
||||
const pointer = decoded.data as EventPointer;
|
||||
return <QuoteNote noteId={pointer.id} relay={pointer.relays?.[0]} />;
|
||||
}
|
||||
default:
|
||||
return match[0];
|
||||
}
|
||||
} catch (e) {
|
||||
return match[0];
|
||||
}
|
||||
},
|
||||
isMedia: false,
|
||||
},
|
||||
// Nostr Mention Links
|
||||
{
|
||||
regexp: /#\[(\d+)\]/,
|
||||
|
@ -47,8 +47,12 @@ function finalizeNote(draft: DraftNostrEvent) {
|
||||
|
||||
const hex = normalizeToHex(match[1]);
|
||||
if (!hex) continue;
|
||||
const mentionType = match[2] === "npub1" ? "p" : "e";
|
||||
// TODO: find the best relay for this user or note
|
||||
const index = updatedDraft.tags.push([match[2] === "npub1" ? "p" : "e", hex, "", "mention"]) - 1;
|
||||
const existingMention = updatedDraft.tags.find((t) => t[0] === mentionType && t[1] === hex);
|
||||
const index = existingMention
|
||||
? updatedDraft.tags.indexOf(existingMention)
|
||||
: updatedDraft.tags.push([mentionType, hex, "", "mention"]) - 1;
|
||||
|
||||
// replace the npub1 or note1 with a mention tag #[0]
|
||||
const c = updatedDraft.content;
|
||||
|
@ -1,7 +1,5 @@
|
||||
import {
|
||||
Button,
|
||||
ButtonGroup,
|
||||
DefaultIcon,
|
||||
Flex,
|
||||
IconButton,
|
||||
Input,
|
||||
@ -35,6 +33,7 @@ import QrCodeSvg from "./qr-code-svg";
|
||||
import { CopyIconButton } from "./copy-icon-button";
|
||||
import { useIsMobile } from "../hooks/use-is-mobile";
|
||||
import settings from "../services/settings";
|
||||
import useSubject from "../hooks/use-subject";
|
||||
|
||||
type FormValues = {
|
||||
amount: number;
|
||||
@ -64,6 +63,7 @@ export default function ZapModal({
|
||||
const [promptInvoice, setPromptInvoice] = useState<string>();
|
||||
const { isOpen: showQr, onToggle: toggleQr } = useDisclosure();
|
||||
const isMobile = useIsMobile();
|
||||
const zapAmounts = useSubject(settings.zapAmounts);
|
||||
|
||||
const {
|
||||
register,
|
||||
@ -239,17 +239,18 @@ export default function ZapModal({
|
||||
<Text>{actionName}</Text>
|
||||
<UserLink pubkey={pubkey} />
|
||||
</Flex>
|
||||
<Flex gap="2" alignItems="center">
|
||||
<ButtonGroup>
|
||||
<Button onClick={() => setValue("amount", 10)}>10</Button>
|
||||
<Button onClick={() => setValue("amount", 100)}>100</Button>
|
||||
<Button onClick={() => setValue("amount", 500)}>500</Button>
|
||||
<Button onClick={() => setValue("amount", 1000)}>1K</Button>
|
||||
</ButtonGroup>
|
||||
<Flex gap="2" alignItems="center" flexWrap="wrap">
|
||||
{zapAmounts.map((amount, i) => (
|
||||
<Button key={amount + i} onClick={() => setValue("amount", amount)} size="sm" variant="outline">
|
||||
{amount}
|
||||
</Button>
|
||||
))}
|
||||
</Flex>
|
||||
<Flex gap="2">
|
||||
<InputGroup maxWidth={32}>
|
||||
{!isMobile && (
|
||||
<InputLeftElement pointerEvents="none" color="gray.300" fontSize="1.2em">
|
||||
<LightningIcon fontSize="1rem" />
|
||||
<LightningIcon fontSize="1rem" color="yellow.400" />
|
||||
</InputLeftElement>
|
||||
)}
|
||||
<Input
|
||||
@ -260,14 +261,14 @@ export default function ZapModal({
|
||||
{...register("amount", { valueAsNumber: true, min: 1, required: true })}
|
||||
/>
|
||||
</InputGroup>
|
||||
{(canZap || lnurlMetadata?.commentAllowed) && (
|
||||
<Input
|
||||
placeholder="Comment"
|
||||
{...register("comment", { maxLength: lnurlMetadata?.commentAllowed ?? 150 })}
|
||||
autoComplete="off"
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
{(canZap || lnurlMetadata?.commentAllowed) && (
|
||||
<Input
|
||||
placeholder="Comment"
|
||||
{...register("comment", { maxLength: lnurlMetadata?.commentAllowed ?? 150 })}
|
||||
autoComplete="off"
|
||||
/>
|
||||
)}
|
||||
<Button leftIcon={<LightningIcon />} type="submit" isLoading={isSubmitting} variant="solid" size="md">
|
||||
{actionName} {getUserDisplayName(metadata, pubkey)} {readablizeSats(watch("amount"))} sats
|
||||
</Button>
|
||||
|
@ -6,7 +6,7 @@ export function encodeText(prefix: string, text: string) {
|
||||
}
|
||||
|
||||
export function decodeText(encoded: string) {
|
||||
const decoded = bech32.decode(encoded, 256);
|
||||
const decoded = bech32.decode(encoded, Infinity);
|
||||
const text = new TextDecoder().decode(new Uint8Array(bech32.fromWords(decoded.words)));
|
||||
return {
|
||||
text,
|
||||
|
@ -9,12 +9,13 @@ export enum Bech32Prefix {
|
||||
Pubkey = "npub",
|
||||
SecKey = "nsec",
|
||||
Note = "note",
|
||||
Profile = "nprofile",
|
||||
}
|
||||
|
||||
export function isBech32Key(bech32String: string) {
|
||||
try {
|
||||
const { prefix } = bech32.decode(bech32String.toLowerCase());
|
||||
if (!["npub", "nsec", "note"].includes(prefix)) return false;
|
||||
if (!prefix) return false;
|
||||
if (!isHex(bech32ToHex(bech32String))) return false;
|
||||
} catch (error) {
|
||||
return false;
|
||||
|
@ -16,6 +16,7 @@ const settings = {
|
||||
showSignatureVerification: new PersistentSubject(false),
|
||||
accounts: new PersistentSubject<Account[]>([]),
|
||||
lightningPayMode: new PersistentSubject<LightningPayMode>(LightningPayMode.Prompt),
|
||||
zapAmounts: new PersistentSubject<number[]>([50, 200, 500, 1000]),
|
||||
};
|
||||
|
||||
async function loadSettings() {
|
||||
|
@ -80,7 +80,7 @@ export default function DiscoverTab() {
|
||||
return (
|
||||
<Flex direction="column" gap="2">
|
||||
{timeline.map((event) => (
|
||||
<Note key={event.id} event={event} maxHeight={300} />
|
||||
<Note key={event.id} event={event} maxHeight={600} />
|
||||
))}
|
||||
{loading ? <Spinner ml="auto" mr="auto" mt="8" mb="8" /> : <Button onClick={() => loadMore()}>Load More</Button>}
|
||||
</Flex>
|
||||
|
@ -44,7 +44,7 @@ export default function FollowingTab() {
|
||||
<Switch id="show-replies" isChecked={showReplies} onChange={onToggle} />
|
||||
</FormControl>
|
||||
{timeline.map((event) => (
|
||||
<Note key={event.id} event={event} maxHeight={300} />
|
||||
<Note key={event.id} event={event} maxHeight={600} />
|
||||
))}
|
||||
{loading ? <Spinner ml="auto" mr="auto" mt="8" mb="8" /> : <Button onClick={() => loadMore()}>Load More</Button>}
|
||||
</Flex>
|
||||
|
@ -57,7 +57,7 @@ export default function GlobalTab() {
|
||||
</FormControl>
|
||||
</Flex>
|
||||
{timeline.map((event) => (
|
||||
<Note key={event.id} event={event} maxHeight={300} />
|
||||
<Note key={event.id} event={event} maxHeight={600} />
|
||||
))}
|
||||
{loading ? <Spinner ml="auto" mr="auto" mt="8" mb="8" /> : <Button onClick={() => loadMore()}>Load More</Button>}
|
||||
</Flex>
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
FormErrorMessage,
|
||||
FormLabel,
|
||||
Input,
|
||||
Link,
|
||||
Textarea,
|
||||
useToast,
|
||||
} from "@chakra-ui/react";
|
||||
@ -13,6 +14,7 @@ import moment from "moment";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { nostrPostAction } from "../../classes/nostr-post-action";
|
||||
import { ExternalLinkIcon } from "../../components/icons";
|
||||
import { isLNURL } from "../../helpers/lnurl";
|
||||
import { Kind0ParsedContent } from "../../helpers/user-metadata";
|
||||
import { useReadRelayUrls, useWriteRelayUrls } from "../../hooks/use-client-relays";
|
||||
@ -169,6 +171,9 @@ const MetadataForm = ({ defaultValues, onSubmit }: MetadataFormProps) => {
|
||||
<FormErrorMessage>{errors.lightningAddress?.message}</FormErrorMessage>
|
||||
</FormControl>
|
||||
<Flex alignSelf="flex-end" gap="2">
|
||||
<Button as={Link} isExternal href="https://metadata.nostr.com/" rightIcon={<ExternalLinkIcon />}>
|
||||
Download Backup
|
||||
</Button>
|
||||
<Button onClick={() => reset()}>Reset</Button>
|
||||
<Button colorScheme="brand" isLoading={isSubmitting} type="submit">
|
||||
Update
|
||||
|
50
src/views/settings/database-settings.tsx
Normal file
50
src/views/settings/database-settings.tsx
Normal file
@ -0,0 +1,50 @@
|
||||
import {
|
||||
Button,
|
||||
AccordionItem,
|
||||
AccordionPanel,
|
||||
AccordionButton,
|
||||
Box,
|
||||
AccordionIcon,
|
||||
ButtonGroup,
|
||||
} from "@chakra-ui/react";
|
||||
import { useState } from "react";
|
||||
import { clearCacheData, deleteDatabase } from "../../services/db";
|
||||
|
||||
export default function DatabaseSettings() {
|
||||
const [clearing, setClearing] = useState(false);
|
||||
const handleClearData = async () => {
|
||||
setClearing(true);
|
||||
await clearCacheData();
|
||||
setClearing(false);
|
||||
};
|
||||
|
||||
const [deleting, setDeleting] = useState(false);
|
||||
const handleDeleteDatabase = async () => {
|
||||
setDeleting(true);
|
||||
await deleteDatabase();
|
||||
setDeleting(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<AccordionItem>
|
||||
<h2>
|
||||
<AccordionButton>
|
||||
<Box as="span" flex="1" textAlign="left">
|
||||
Database
|
||||
</Box>
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
</h2>
|
||||
<AccordionPanel>
|
||||
<ButtonGroup>
|
||||
<Button onClick={handleClearData} isLoading={clearing} isDisabled={clearing}>
|
||||
Clear cache data
|
||||
</Button>
|
||||
<Button colorScheme="red" onClick={handleDeleteDatabase} isLoading={deleting} isDisabled={deleting}>
|
||||
Delete database
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
);
|
||||
}
|
83
src/views/settings/display-settings.tsx
Normal file
83
src/views/settings/display-settings.tsx
Normal file
@ -0,0 +1,83 @@
|
||||
import {
|
||||
Flex,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
Switch,
|
||||
useColorMode,
|
||||
AccordionItem,
|
||||
AccordionPanel,
|
||||
AccordionButton,
|
||||
Box,
|
||||
AccordionIcon,
|
||||
FormHelperText,
|
||||
} from "@chakra-ui/react";
|
||||
import settings from "../../services/settings";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
|
||||
export default function DisplaySettings() {
|
||||
const blurImages = useSubject(settings.blurImages);
|
||||
|
||||
const { colorMode, setColorMode } = useColorMode();
|
||||
|
||||
return (
|
||||
<AccordionItem>
|
||||
<h2>
|
||||
<AccordionButton>
|
||||
<Box as="span" flex="1" textAlign="left">
|
||||
Display
|
||||
</Box>
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
</h2>
|
||||
<AccordionPanel>
|
||||
<Flex direction="column" gap="4">
|
||||
<FormControl>
|
||||
<Flex alignItems="center">
|
||||
<FormLabel htmlFor="use-dark-theme" mb="0">
|
||||
Use dark theme
|
||||
</FormLabel>
|
||||
<Switch
|
||||
id="use-dark-theme"
|
||||
isChecked={colorMode === "dark"}
|
||||
onChange={(v) => setColorMode(v.target.checked ? "dark" : "light")}
|
||||
/>
|
||||
</Flex>
|
||||
<FormHelperText>
|
||||
<span>Enabled: hacker mode</span>
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<Flex alignItems="center">
|
||||
<FormLabel htmlFor="blur-images" mb="0">
|
||||
Blur images from strangers
|
||||
</FormLabel>
|
||||
<Switch
|
||||
id="blur-images"
|
||||
isChecked={blurImages}
|
||||
onChange={(v) => settings.blurImages.next(v.target.checked)}
|
||||
/>
|
||||
</Flex>
|
||||
<FormHelperText>
|
||||
<span>Enabled: blur images for people you aren't following</span>
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<Flex alignItems="center">
|
||||
<FormLabel htmlFor="show-ads" mb="0">
|
||||
Show Ads
|
||||
</FormLabel>
|
||||
<Switch
|
||||
id="show-ads"
|
||||
isChecked={false}
|
||||
onChange={(v) => alert("Sorry, that feature will never be finished.")}
|
||||
/>
|
||||
</Flex>
|
||||
<FormHelperText>
|
||||
<span>Enabled: shows ads so I can steal your data</span>
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</Flex>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
);
|
||||
}
|
@ -1,242 +1,22 @@
|
||||
import {
|
||||
Button,
|
||||
Flex,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
Switch,
|
||||
useColorMode,
|
||||
AccordionItem,
|
||||
Accordion,
|
||||
AccordionPanel,
|
||||
AccordionButton,
|
||||
Box,
|
||||
AccordionIcon,
|
||||
ButtonGroup,
|
||||
FormHelperText,
|
||||
Select,
|
||||
Link,
|
||||
} from "@chakra-ui/react";
|
||||
import { useState } from "react";
|
||||
import settings, { LightningPayMode } from "../../services/settings";
|
||||
import { clearCacheData, deleteDatabase } from "../../services/db";
|
||||
import { Button, Flex, Accordion, Link } from "@chakra-ui/react";
|
||||
import accountService from "../../services/account";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import { GithubIcon, LightningIcon, LogoutIcon } from "../../components/icons";
|
||||
import { GithubIcon, LogoutIcon } from "../../components/icons";
|
||||
import LightningSettings from "./lightning-settings";
|
||||
import DatabaseSettings from "./database-settings";
|
||||
import DisplaySettings from "./display-settings";
|
||||
import PerformanceSettings from "./performance-settings";
|
||||
|
||||
export default function SettingsView() {
|
||||
const blurImages = useSubject(settings.blurImages);
|
||||
const autoShowMedia = useSubject(settings.autoShowMedia);
|
||||
const proxyUserMedia = useSubject(settings.proxyUserMedia);
|
||||
const showReactions = useSubject(settings.showReactions);
|
||||
const showSignatureVerification = useSubject(settings.showSignatureVerification);
|
||||
const lightningPayMode = useSubject(settings.lightningPayMode);
|
||||
|
||||
const { colorMode, setColorMode } = useColorMode();
|
||||
|
||||
const [clearing, setClearing] = useState(false);
|
||||
const handleClearData = async () => {
|
||||
setClearing(true);
|
||||
await clearCacheData();
|
||||
setClearing(false);
|
||||
};
|
||||
|
||||
const [deleting, setDeleting] = useState(false);
|
||||
const handleDeleteDatabase = async () => {
|
||||
setDeleting(true);
|
||||
await deleteDatabase();
|
||||
setDeleting(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex direction="column" pt="2" pb="2" overflow="auto">
|
||||
<Accordion defaultIndex={[0]} allowMultiple>
|
||||
<AccordionItem>
|
||||
<h2>
|
||||
<AccordionButton>
|
||||
<Box as="span" flex="1" textAlign="left">
|
||||
Display
|
||||
</Box>
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
</h2>
|
||||
<AccordionPanel>
|
||||
<Flex direction="column" gap="4">
|
||||
<FormControl>
|
||||
<Flex alignItems="center">
|
||||
<FormLabel htmlFor="use-dark-theme" mb="0">
|
||||
Use dark theme
|
||||
</FormLabel>
|
||||
<Switch
|
||||
id="use-dark-theme"
|
||||
isChecked={colorMode === "dark"}
|
||||
onChange={(v) => setColorMode(v.target.checked ? "dark" : "light")}
|
||||
/>
|
||||
</Flex>
|
||||
<FormHelperText>
|
||||
<span>Enabled: hacker mode</span>
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<Flex alignItems="center">
|
||||
<FormLabel htmlFor="blur-images" mb="0">
|
||||
Blur images from strangers
|
||||
</FormLabel>
|
||||
<Switch
|
||||
id="blur-images"
|
||||
isChecked={blurImages}
|
||||
onChange={(v) => settings.blurImages.next(v.target.checked)}
|
||||
/>
|
||||
</Flex>
|
||||
<FormHelperText>
|
||||
<span>Enabled: blur images for people you aren't following</span>
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<Flex alignItems="center">
|
||||
<FormLabel htmlFor="show-ads" mb="0">
|
||||
Show Ads
|
||||
</FormLabel>
|
||||
<Switch
|
||||
id="show-ads"
|
||||
isChecked={false}
|
||||
onChange={(v) => alert("Sorry, that feature will never be finished.")}
|
||||
/>
|
||||
</Flex>
|
||||
<FormHelperText>
|
||||
<span>Enabled: shows ads so I can steal your data</span>
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</Flex>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
<DisplaySettings />
|
||||
|
||||
<AccordionItem>
|
||||
<h2>
|
||||
<AccordionButton>
|
||||
<Box as="span" flex="1" textAlign="left">
|
||||
Performance
|
||||
</Box>
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
</h2>
|
||||
<AccordionPanel>
|
||||
<Flex direction="column" gap="4">
|
||||
<FormControl>
|
||||
<Flex alignItems="center">
|
||||
<FormLabel htmlFor="proxy-user-media" mb="0">
|
||||
Proxy user media
|
||||
</FormLabel>
|
||||
<Switch
|
||||
id="proxy-user-media"
|
||||
isChecked={proxyUserMedia}
|
||||
onChange={(v) => settings.proxyUserMedia.next(v.target.checked)}
|
||||
/>
|
||||
</Flex>
|
||||
<FormHelperText>
|
||||
<span>Enabled: Use media.nostr.band to get smaller profile pictures (saves ~50Mb of data)</span>
|
||||
<br />
|
||||
<span>Side Effect: Some user pictures may not load or may be outdated</span>
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<Flex alignItems="center">
|
||||
<FormLabel htmlFor="auto-show-embeds" mb="0">
|
||||
Automatically show media
|
||||
</FormLabel>
|
||||
<Switch
|
||||
id="auto-show-embeds"
|
||||
isChecked={autoShowMedia}
|
||||
onChange={(v) => settings.autoShowMedia.next(v.target.checked)}
|
||||
/>
|
||||
</Flex>
|
||||
<FormHelperText>Disabled: Images and videos will show expandable buttons</FormHelperText>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<Flex alignItems="center">
|
||||
<FormLabel htmlFor="show-reactions" mb="0">
|
||||
Show reactions
|
||||
</FormLabel>
|
||||
<Switch
|
||||
id="show-reactions"
|
||||
isChecked={showReactions}
|
||||
onChange={(v) => settings.showReactions.next(v.target.checked)}
|
||||
/>
|
||||
</Flex>
|
||||
<FormHelperText>Enabled: Show reactions on notes</FormHelperText>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<Flex alignItems="center">
|
||||
<FormLabel htmlFor="show-sig-verify" mb="0">
|
||||
Show signature verification
|
||||
</FormLabel>
|
||||
<Switch
|
||||
id="show-sig-verify"
|
||||
isChecked={showSignatureVerification}
|
||||
onChange={(v) => settings.showSignatureVerification.next(v.target.checked)}
|
||||
/>
|
||||
</Flex>
|
||||
<FormHelperText>Enabled: show signature verification on notes</FormHelperText>
|
||||
</FormControl>
|
||||
</Flex>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
<PerformanceSettings />
|
||||
|
||||
<AccordionItem>
|
||||
<h2>
|
||||
<AccordionButton>
|
||||
<Box as="span" flex="1" textAlign="left">
|
||||
Lightning <LightningIcon color="yellow.400" />
|
||||
</Box>
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
</h2>
|
||||
<AccordionPanel>
|
||||
<Flex direction="column" gap="4">
|
||||
<FormControl>
|
||||
<FormLabel htmlFor="lightning-payment-mode" mb="0">
|
||||
Payment mode
|
||||
</FormLabel>
|
||||
<Select
|
||||
id="lightning-payment-mode"
|
||||
value={lightningPayMode}
|
||||
onChange={(e) => settings.lightningPayMode.next(e.target.value as LightningPayMode)}
|
||||
>
|
||||
<option value="prompt">Prompt</option>
|
||||
<option value="webln">WebLN</option>
|
||||
<option value="external">External</option>
|
||||
</Select>
|
||||
<FormHelperText>
|
||||
<span>Prompt: Ask every time</span>
|
||||
<br />
|
||||
<span>WebLN: Use browser extension</span>
|
||||
<br />
|
||||
<span>External: Open an external app using "lightning:" link</span>
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</Flex>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
<LightningSettings />
|
||||
|
||||
<AccordionItem>
|
||||
<h2>
|
||||
<AccordionButton>
|
||||
<Box as="span" flex="1" textAlign="left">
|
||||
Database
|
||||
</Box>
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
</h2>
|
||||
<AccordionPanel>
|
||||
<ButtonGroup>
|
||||
<Button onClick={handleClearData} isLoading={clearing} isDisabled={clearing}>
|
||||
Clear cache data
|
||||
</Button>
|
||||
<Button colorScheme="red" onClick={handleDeleteDatabase} isLoading={deleting} isDisabled={deleting}>
|
||||
Delete database
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
<DatabaseSettings />
|
||||
</Accordion>
|
||||
<Flex gap="2" padding="4" alignItems="center" justifyContent="space-between">
|
||||
<Button leftIcon={<LogoutIcon />} onClick={() => accountService.logout()}>
|
||||
|
86
src/views/settings/lightning-settings.tsx
Normal file
86
src/views/settings/lightning-settings.tsx
Normal file
@ -0,0 +1,86 @@
|
||||
import {
|
||||
Flex,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
AccordionItem,
|
||||
AccordionPanel,
|
||||
AccordionButton,
|
||||
Box,
|
||||
AccordionIcon,
|
||||
FormHelperText,
|
||||
Input,
|
||||
Select,
|
||||
} from "@chakra-ui/react";
|
||||
import { useState } from "react";
|
||||
import settings, { LightningPayMode } from "../../services/settings";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import { LightningIcon } from "../../components/icons";
|
||||
|
||||
export default function LightningSettings() {
|
||||
const lightningPayMode = useSubject(settings.lightningPayMode);
|
||||
const zapAmounts = useSubject(settings.zapAmounts);
|
||||
|
||||
const [zapInput, setZapInput] = useState(zapAmounts.join(","));
|
||||
|
||||
return (
|
||||
<AccordionItem>
|
||||
<h2>
|
||||
<AccordionButton>
|
||||
<Box as="span" flex="1" textAlign="left">
|
||||
Lightning <LightningIcon color="yellow.400" />
|
||||
</Box>
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
</h2>
|
||||
<AccordionPanel>
|
||||
<Flex direction="column" gap="4">
|
||||
<FormControl>
|
||||
<FormLabel htmlFor="lightning-payment-mode" mb="0">
|
||||
Payment mode
|
||||
</FormLabel>
|
||||
<Select
|
||||
id="lightning-payment-mode"
|
||||
value={lightningPayMode}
|
||||
onChange={(e) => settings.lightningPayMode.next(e.target.value as LightningPayMode)}
|
||||
>
|
||||
<option value="prompt">Prompt</option>
|
||||
<option value="webln">WebLN</option>
|
||||
<option value="external">External</option>
|
||||
</Select>
|
||||
<FormHelperText>
|
||||
<span>Prompt: Ask every time</span>
|
||||
<br />
|
||||
<span>WebLN: Use browser extension</span>
|
||||
<br />
|
||||
<span>External: Open an external app using "lightning:" link</span>
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
|
||||
<FormControl>
|
||||
<FormLabel htmlFor="zap-amounts" mb="0">
|
||||
Zap Amounts
|
||||
</FormLabel>
|
||||
<Input
|
||||
id="zap-amounts"
|
||||
value={zapInput}
|
||||
onChange={(e) => setZapInput(e.target.value)}
|
||||
onBlur={() => {
|
||||
const amounts = zapInput
|
||||
.split(",")
|
||||
.map((v) => parseInt(v))
|
||||
.filter(Boolean)
|
||||
.sort((a, b) => a - b);
|
||||
|
||||
settings.zapAmounts.next(amounts);
|
||||
setZapInput(amounts.join(","));
|
||||
}}
|
||||
/>
|
||||
<FormHelperText>
|
||||
<span>Comma separated list of custom zap amounts</span>
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</Flex>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
);
|
||||
}
|
94
src/views/settings/performance-settings.tsx
Normal file
94
src/views/settings/performance-settings.tsx
Normal file
@ -0,0 +1,94 @@
|
||||
import {
|
||||
Flex,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
Switch,
|
||||
AccordionItem,
|
||||
AccordionPanel,
|
||||
AccordionButton,
|
||||
Box,
|
||||
AccordionIcon,
|
||||
FormHelperText,
|
||||
} from "@chakra-ui/react";
|
||||
import settings from "../../services/settings";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
|
||||
export default function PerformanceSettings() {
|
||||
const autoShowMedia = useSubject(settings.autoShowMedia);
|
||||
const proxyUserMedia = useSubject(settings.proxyUserMedia);
|
||||
const showReactions = useSubject(settings.showReactions);
|
||||
const showSignatureVerification = useSubject(settings.showSignatureVerification);
|
||||
|
||||
return (
|
||||
<AccordionItem>
|
||||
<h2>
|
||||
<AccordionButton>
|
||||
<Box as="span" flex="1" textAlign="left">
|
||||
Performance
|
||||
</Box>
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
</h2>
|
||||
<AccordionPanel>
|
||||
<Flex direction="column" gap="4">
|
||||
<FormControl>
|
||||
<Flex alignItems="center">
|
||||
<FormLabel htmlFor="proxy-user-media" mb="0">
|
||||
Proxy user media
|
||||
</FormLabel>
|
||||
<Switch
|
||||
id="proxy-user-media"
|
||||
isChecked={proxyUserMedia}
|
||||
onChange={(v) => settings.proxyUserMedia.next(v.target.checked)}
|
||||
/>
|
||||
</Flex>
|
||||
<FormHelperText>
|
||||
<span>Enabled: Use media.nostr.band to get smaller profile pictures (saves ~50Mb of data)</span>
|
||||
<br />
|
||||
<span>Side Effect: Some user pictures may not load or may be outdated</span>
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<Flex alignItems="center">
|
||||
<FormLabel htmlFor="auto-show-embeds" mb="0">
|
||||
Automatically show media
|
||||
</FormLabel>
|
||||
<Switch
|
||||
id="auto-show-embeds"
|
||||
isChecked={autoShowMedia}
|
||||
onChange={(v) => settings.autoShowMedia.next(v.target.checked)}
|
||||
/>
|
||||
</Flex>
|
||||
<FormHelperText>Disabled: Images and videos will show expandable buttons</FormHelperText>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<Flex alignItems="center">
|
||||
<FormLabel htmlFor="show-reactions" mb="0">
|
||||
Show reactions
|
||||
</FormLabel>
|
||||
<Switch
|
||||
id="show-reactions"
|
||||
isChecked={showReactions}
|
||||
onChange={(v) => settings.showReactions.next(v.target.checked)}
|
||||
/>
|
||||
</Flex>
|
||||
<FormHelperText>Enabled: Show reactions on notes</FormHelperText>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<Flex alignItems="center">
|
||||
<FormLabel htmlFor="show-sig-verify" mb="0">
|
||||
Show signature verification
|
||||
</FormLabel>
|
||||
<Switch
|
||||
id="show-sig-verify"
|
||||
isChecked={showSignatureVerification}
|
||||
onChange={(v) => settings.showSignatureVerification.next(v.target.checked)}
|
||||
/>
|
||||
</Flex>
|
||||
<FormHelperText>Enabled: show signature verification on notes</FormHelperText>
|
||||
</FormControl>
|
||||
</Flex>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
);
|
||||
}
|
@ -1,5 +1,8 @@
|
||||
import {
|
||||
Avatar,
|
||||
Code,
|
||||
Flex,
|
||||
Heading,
|
||||
MenuItem,
|
||||
Modal,
|
||||
ModalBody,
|
||||
@ -17,6 +20,7 @@ import { useUserMetadata } from "../../../hooks/use-user-metadata";
|
||||
import { getUserDisplayName } from "../../../helpers/user-metadata";
|
||||
import { useUserRelays } from "../../../hooks/use-user-relays";
|
||||
import { RelayMode } from "../../../classes/relay";
|
||||
import { CopyIconButton } from "../../../components/copy-icon-button";
|
||||
|
||||
export const UserProfileMenu = ({ pubkey, ...props }: { pubkey: string } & Omit<MenuIconButtonProps, "children">) => {
|
||||
const npub = normalizeToBech32(pubkey, Bech32Prefix.Pubkey);
|
||||
@ -42,6 +46,9 @@ export const UserProfileMenu = ({ pubkey, ...props }: { pubkey: string } & Omit<
|
||||
<MenuItem icon={<SpyIcon fontSize="1.5em" />} onClick={() => loginAsUser()}>
|
||||
Login as {getUserDisplayName(metadata, pubkey)}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={infoModal.onOpen} icon={<CodeIcon />}>
|
||||
View Raw
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
as="a"
|
||||
icon={<Avatar src={IMAGE_ICONS.nostrGuruIcon} size="xs" />}
|
||||
@ -50,14 +57,6 @@ export const UserProfileMenu = ({ pubkey, ...props }: { pubkey: string } & Omit<
|
||||
>
|
||||
Open in Nostr.guru
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
as="a"
|
||||
icon={<Avatar src={IMAGE_ICONS.brbIcon} size="xs" />}
|
||||
href={`https://brb.io/u/${npub}`}
|
||||
target="_blank"
|
||||
>
|
||||
Open in BRB
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
as="a"
|
||||
icon={<Avatar src={IMAGE_ICONS.snortSocialIcon} size="xs" />}
|
||||
@ -66,9 +65,6 @@ export const UserProfileMenu = ({ pubkey, ...props }: { pubkey: string } & Omit<
|
||||
>
|
||||
Open in snort.social
|
||||
</MenuItem>
|
||||
<MenuItem onClick={infoModal.onOpen} icon={<CodeIcon />}>
|
||||
View Raw
|
||||
</MenuItem>
|
||||
</MenuIconButton>
|
||||
{infoModal.isOpen && (
|
||||
<Modal isOpen={infoModal.isOpen} onClose={infoModal.onClose} size="6xl">
|
||||
@ -76,7 +72,38 @@ export const UserProfileMenu = ({ pubkey, ...props }: { pubkey: string } & Omit<
|
||||
<ModalContent>
|
||||
<ModalCloseButton />
|
||||
<ModalBody overflow="auto" fontSize="sm" padding="2">
|
||||
<pre>{JSON.stringify(metadata, null, 2)}</pre>
|
||||
<Flex gap="2" direction="column">
|
||||
<Heading size="sm" mt="2">
|
||||
Hex pubkey
|
||||
</Heading>
|
||||
<Flex gap="2">
|
||||
<Code fontSize="md" wordBreak="break-all">
|
||||
{pubkey}
|
||||
</Code>
|
||||
<CopyIconButton text={pubkey} size="xs" aria-label="copy hex" />
|
||||
</Flex>
|
||||
|
||||
{npub && (
|
||||
<>
|
||||
<Heading size="sm" mt="2">
|
||||
Encoded pubkey (NIP-19)
|
||||
</Heading>
|
||||
<Flex gap="2">
|
||||
<Code fontSize="md" wordBreak="break-all">
|
||||
{npub}
|
||||
</Code>
|
||||
<CopyIconButton text={npub} size="xs" aria-label="copy npub" />
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Heading size="sm" mt="2">
|
||||
Metadata (kind 0)
|
||||
</Heading>
|
||||
<Code whiteSpace="pre" overflowX="auto">
|
||||
{JSON.stringify(metadata, null, 2)}
|
||||
</Code>
|
||||
</Flex>
|
||||
</ModalBody>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
|
@ -76,7 +76,7 @@ const UserNotesTab = () => {
|
||||
</Popover>
|
||||
</FormControl>
|
||||
{timeline.map((event) => (
|
||||
<Note key={event.id} event={event} maxHeight={300} />
|
||||
<Note key={event.id} event={event} maxHeight={1200} />
|
||||
))}
|
||||
{loading ? <Spinner ml="auto" mr="auto" mt="8" mb="8" /> : <Button onClick={() => loadMore()}>Load More</Button>}
|
||||
</Flex>
|
||||
|
@ -17,7 +17,9 @@ const UserRelaysTab = () => {
|
||||
<Flex direction="column" gap="2">
|
||||
{ranked.map((relayConfig) => (
|
||||
<Box key={relayConfig.url} display="flex" gap="2" alignItems="center" pr="2" pl="2">
|
||||
<Text flex={1}>{relayConfig.url}</Text>
|
||||
<Text flex={1} isTruncated>
|
||||
{relayConfig.url}
|
||||
</Text>
|
||||
<RelayScoreBreakdown relay={relayConfig.url} />
|
||||
{relayConfig.mode & RelayMode.WRITE ? <Badge colorScheme="green">Write</Badge> : null}
|
||||
{relayConfig.mode & RelayMode.READ ? <Badge>Read</Badge> : null}
|
||||
|
Loading…
x
Reference in New Issue
Block a user