From 9569281b6e422224d7daa265aaeff3f83622e9f7 Mon Sep 17 00:00:00 2001 From: hzrd149 Date: Tue, 17 Oct 2023 06:08:57 -0500 Subject: [PATCH] add quick reaction option --- .changeset/shaggy-carrots-destroy.md | 5 + .../note/components/reaction-button.tsx | 5 +- src/components/reaction-picker.tsx | 33 +++---- src/providers/emoji-provider.tsx | 1 + src/services/settings/migrations.ts | 23 +++-- src/views/settings/display-settings.tsx | 91 ++++++++++++++++++- 6 files changed, 123 insertions(+), 35 deletions(-) create mode 100644 .changeset/shaggy-carrots-destroy.md diff --git a/.changeset/shaggy-carrots-destroy.md b/.changeset/shaggy-carrots-destroy.md new file mode 100644 index 000000000..9cdf0d7da --- /dev/null +++ b/.changeset/shaggy-carrots-destroy.md @@ -0,0 +1,5 @@ +--- +"nostrudel": minor +--- + +Add option to customize quick reactions diff --git a/src/components/note/components/reaction-button.tsx b/src/components/note/components/reaction-button.tsx index 006796686..98b83ed55 100644 --- a/src/components/note/components/reaction-button.tsx +++ b/src/components/note/components/reaction-button.tsx @@ -7,15 +7,12 @@ import { PopoverContent, PopoverTrigger, } from "@chakra-ui/react"; -import dayjs from "dayjs"; -import { Kind } from "nostr-tools"; -import { useCurrentAccount } from "../../../hooks/use-current-account"; import useEventReactions from "../../../hooks/use-event-reactions"; import { useSigningContext } from "../../../providers/signing-provider"; import clientRelaysService from "../../../services/client-relays"; import eventReactionsService from "../../../services/event-reactions"; -import { DraftNostrEvent, NostrEvent } from "../../../types/nostr-event"; +import { NostrEvent } from "../../../types/nostr-event"; import NostrPublishAction from "../../../classes/nostr-publish-action"; import { AddReactionIcon } from "../../icons"; import ReactionPicker from "../../reaction-picker"; diff --git a/src/components/reaction-picker.tsx b/src/components/reaction-picker.tsx index e70b2e89d..9218f709f 100644 --- a/src/components/reaction-picker.tsx +++ b/src/components/reaction-picker.tsx @@ -1,10 +1,11 @@ -import { Button, Divider, Flex, IconButton, Image, Input, Text } from "@chakra-ui/react"; +import { Divider, Flex, IconButton, Image, Text } from "@chakra-ui/react"; import { DislikeIcon, LikeIcon } from "./icons"; import { useCurrentAccount } from "../hooks/use-current-account"; import useReplaceableEvent from "../hooks/use-replaceable-event"; import { getEmojisFromPack, getPackCordsFromFavorites, getPackName } from "../helpers/nostr/emoji-packs"; import useFavoriteEmojiPacks from "../hooks/use-favorite-emoji-packs"; +import useAppSettings from "../hooks/use-app-settings"; export type ReactionPickerProps = { onSelect: (emoji: string, url?: string) => void; @@ -40,6 +41,7 @@ function EmojiPack({ cord, onSelect }: { cord: string; onSelect: ReactionPickerP export default function ReactionPicker({ onSelect }: ReactionPickerProps) { const account = useCurrentAccount(); const favoritePacks = useFavoriteEmojiPacks(account?.pubkey); + const { quickReactions } = useAppSettings(); return ( @@ -52,26 +54,15 @@ export default function ReactionPicker({ onSelect }: ReactionPickerProps) { size="sm" onClick={() => onSelect("-")} /> - 🤙} - aria-label="Shaka" - variant="outline" - size="sm" - onClick={() => onSelect("🤙")} - /> - 🫂} - aria-label="Hug" - variant="outline" - size="sm" - onClick={() => onSelect("🫂")} - /> - - - - + {quickReactions.map((emoji) => ( + {emoji}} + aria-label="Shaka" + variant="outline" + size="sm" + onClick={() => onSelect(emoji)} + /> + ))} {favoritePacks && getPackCordsFromFavorites(favoritePacks).map((cord) => ( diff --git a/src/providers/emoji-provider.tsx b/src/providers/emoji-provider.tsx index 8eed4bd63..0f51988b1 100644 --- a/src/providers/emoji-provider.tsx +++ b/src/providers/emoji-provider.tsx @@ -1,5 +1,6 @@ import { PropsWithChildren, createContext, useContext } from "react"; import { lib } from "emojilib"; + import useReplaceableEvents from "../hooks/use-replaceable-events"; import { useCurrentAccount } from "../hooks/use-current-account"; import { isEmojiTag } from "../types/nostr-event"; diff --git a/src/services/settings/migrations.ts b/src/services/settings/migrations.ts index d09e55eda..97a6880d1 100644 --- a/src/services/settings/migrations.ts +++ b/src/services/settings/migrations.ts @@ -31,6 +31,10 @@ export type AppSettingsV2 = Omit & { version: 2; theme: string; }; +export type AppSettingsV3 = Omit & { + version: 3; + quickReactions: string[]; +}; export function isV0(settings: { version: number }): settings is AppSettingsV0 { return settings.version === undefined || settings.version === 0; @@ -41,11 +45,14 @@ export function isV1(settings: { version: number }): settings is AppSettingsV1 { export function isV2(settings: { version: number }): settings is AppSettingsV2 { return settings.version === 2; } +export function isV3(settings: { version: number }): settings is AppSettingsV3 { + return settings.version === 3; +} -export type AppSettings = AppSettingsV2; +export type AppSettings = AppSettingsV3; export const defaultSettings: AppSettings = { - version: 2, + version: 3, theme: "default", colorMode: "system", maxPageWidth: "none", @@ -55,6 +62,8 @@ export const defaultSettings: AppSettings = { showReactions: true, showSignatureVerification: false, + quickReactions: ["🤙", "❤️", "🤣", "😍", "🔥"], + autoPayWithWebLN: true, customZapAmounts: "50,200,500,1000,2000,5000", @@ -67,11 +76,11 @@ export const defaultSettings: AppSettings = { youtubeRedirect: undefined, }; -export function upgradeSettings(settings: { version: number }): AppSettings | null { - if (isV0(settings)) return { ...settings, version: 2, maxPageWidth: "none", theme: "default" }; - if (isV1(settings)) return { ...settings, version: 2, theme: "default" }; - if (isV2(settings)) return settings; - return null; +export function upgradeSettings(settings: { version: number }): AppSettings { + if (isV0(settings)) return { ...defaultSettings, ...settings, version: 3 }; + if (isV1(settings)) return { ...defaultSettings, ...settings, version: 3 }; + if (isV2(settings)) return { ...defaultSettings, ...settings, version: 3 }; + return settings as AppSettings; } export function parseAppSettings(event: NostrEvent): AppSettings { diff --git a/src/views/settings/display-settings.tsx b/src/views/settings/display-settings.tsx index 2f834ea60..33b166bd6 100644 --- a/src/views/settings/display-settings.tsx +++ b/src/views/settings/display-settings.tsx @@ -1,4 +1,5 @@ -import { useFormContext } from "react-hook-form"; +import { useMemo, useState } from "react"; +import { UseControllerProps, useController, useFormContext } from "react-hook-form"; import { Flex, FormControl, @@ -13,12 +14,51 @@ import { Input, Select, Textarea, + Divider, + Tag, + TagLabel, + TagCloseButton, + useDisclosure, + IconButton, + Button, } from "@chakra-ui/react"; +import { matchSorter } from "match-sorter"; + import { AppSettings } from "../../services/settings/migrations"; -import { AppearanceIcon } from "../../components/icons"; +import { AppearanceIcon, EditIcon } from "../../components/icons"; +import { useContextEmojis } from "../../providers/emoji-provider"; export default function DisplaySettings() { - const { register } = useFormContext(); + const { register, setValue, getValues, watch } = useFormContext(); + const emojiPicker = useDisclosure(); + + const emojis = useContextEmojis(); + const [emojiSearch, setEmojiSearch] = useState(""); + + watch("quickReactions"); + const filteredEmojis = useMemo(() => { + const values = getValues(); + if (emojiSearch.trim()) { + const noCustom = emojis.filter((e) => e.char && !e.url && !values.quickReactions.includes(e.char)); + return matchSorter(noCustom, emojiSearch.trim(), { keys: ["keywords"] }).slice(0, 10); + } + return []; + }, [emojiSearch, getValues().quickReactions]); + + const addEmoji = (char: string) => { + const values = getValues(); + if (values.quickReactions.includes(char)) return; + setValue("quickReactions", values.quickReactions.concat(char), { shouldTouch: true, shouldDirty: true }); + }; + const removeEmoji = (char: string) => { + const values = getValues(); + if (!values.quickReactions.includes(char)) return; + setValue( + "quickReactions", + values.quickReactions.filter((e) => e !== char), + { shouldTouch: true, shouldDirty: true }, + ); + }; return ( @@ -63,6 +103,51 @@ export default function DisplaySettings() { The primary color of the theme + + + Quick Reactions + + + {getValues().quickReactions.map((char, i) => ( + + {char} + {emojiPicker.isOpen && removeEmoji(char)} />} + + ))} + {!emojiPicker.isOpen && ( + + )} + + {emojiPicker.isOpen && ( + <> + + setEmojiSearch(e.target.value)} + mb="2" + /> + + {filteredEmojis.map((emoji) => ( + {emoji.char}} + aria-label={`Add ${emoji.name}`} + title={`Add ${emoji.name}`} + variant="outline" + size="sm" + fontSize="lg" + onClick={() => addEmoji(emoji.char)} + /> + ))} + + + )} + Max Page width