From c10a17eee79febdd1311581ef6defcf6edf0f654 Mon Sep 17 00:00:00 2001 From: hzrd149 Date: Thu, 31 Aug 2023 12:48:31 -0500 Subject: [PATCH] Add emoji autocomplete when writing notes --- .changeset/tame-masks-listen.md | 5 + package.json | 4 + src/app.tsx | 27 + src/components/embed-types/emoji.tsx | 11 +- src/components/magic-textarea.tsx | 47 + src/components/post-modal/index.tsx | 74 +- src/components/reaction-picker.tsx | 1 + src/helpers/nostr/post.ts | 18 +- src/helpers/regexp.ts | 1 + src/index.tsx | 1 + src/polyfill.ts | 1 + src/providers/emoji-provider.tsx | 46 + src/providers/index.tsx | 7 +- src/types/emojilib.d.ts | 11 + src/types/nostr-event.ts | 4 + src/views/note/components/reply-form.tsx | 63 +- stats.html | 36506 +++------------------ yarn.lock | 43 + 18 files changed, 4866 insertions(+), 32004 deletions(-) create mode 100644 .changeset/tame-masks-listen.md create mode 100644 src/components/magic-textarea.tsx create mode 100644 src/polyfill.ts create mode 100644 src/providers/emoji-provider.tsx create mode 100644 src/types/emojilib.d.ts diff --git a/.changeset/tame-masks-listen.md b/.changeset/tame-masks-listen.md new file mode 100644 index 000000000..c7ac0e44d --- /dev/null +++ b/.changeset/tame-masks-listen.md @@ -0,0 +1,5 @@ +--- +"nostrudel": minor +--- + +Add emoji autocomplete when writing notes diff --git a/package.json b/package.json index 09347c249..592757403 100644 --- a/package.json +++ b/package.json @@ -18,10 +18,12 @@ "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@getalby/bitcoin-connect-react": "^1.0.0", + "@webscopeio/react-textarea-autocomplete": "^4.9.2", "bech32": "^2.0.0", "cheerio": "^1.0.0-rc.12", "dayjs": "^1.11.9", "debug": "^4.3.4", + "emojilib": "2", "framer-motion": "^10.16.0", "hls.js": "^1.4.10", "idb": "^7.1.1", @@ -29,6 +31,7 @@ "leaflet": "^1.9.4", "leaflet.locatecontrol": "^0.79.0", "light-bolt11-decoder": "^3.0.0", + "match-sorter": "^6.3.1", "nanoid": "^4.0.2", "ngeohash": "^0.6.3", "noble-secp256k1": "^1.2.14", @@ -55,6 +58,7 @@ "@types/ngeohash": "^0.6.4", "@types/react": "^18.2.20", "@types/react-dom": "^18.2.7", + "@types/webscopeio__react-textarea-autocomplete": "^4.7.2", "@vitejs/plugin-react": "^4.0.4", "cypress": "^12.17.4", "prettier": "^3.0.2", diff --git a/src/app.tsx b/src/app.tsx index 52bdac340..b228a7024 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -40,17 +40,44 @@ import UserListsTab from "./views/user/lists"; import "./services/emoji-packs"; import BrowseListView from "./views/lists/browse"; +import { css, Global } from "@emotion/react"; const StreamsView = React.lazy(() => import("./views/streams")); const StreamView = React.lazy(() => import("./views/streams/stream")); const SearchView = React.lazy(() => import("./views/search")); const MapView = React.lazy(() => import("./views/map")); +const overrideReactTextareaAutocompleteStyles = css` + .rta__autocomplete { + z-index: var(--chakra-zIndices-popover); + font-size: var(--chakra-fontSizes-md); + } + .rta__list { + background: var(--chakra-colors-chakra-subtle-bg); + color: var(--chakra-colors-chakra-body-text); + border: var(--chakra-borders-1px) var(--chakra-colors-chakra-border-color); + border-radius: var(--chakra-sizes-1); + overflow: hidden; + } + .rta__entity { + background: none; + color: inherit; + padding: var(--chakra-sizes-1) var(--chakra-sizes-2); + } + .rta__entity--selected { + background: var(--chakra-ring-color); + } + .rta__item:not(:last-child) { + border-bottom: var(--chakra-borders-1px) var(--chakra-colors-chakra-border-color); + } +`; + const RootPage = () => { useSetColorMode(); return ( + }> diff --git a/src/components/embed-types/emoji.tsx b/src/components/embed-types/emoji.tsx index 3fba2f7b5..f8cd60553 100644 --- a/src/components/embed-types/emoji.tsx +++ b/src/components/embed-types/emoji.tsx @@ -1,17 +1,16 @@ import { Image } from "@chakra-ui/react"; import { EmbedableContent, embedJSX } from "../../helpers/embeds"; -import { DraftNostrEvent, NostrEvent } from "../../types/nostr-event"; +import { DraftNostrEvent, NostrEvent, isEmojiTag } from "../../types/nostr-event"; +import { getMatchEmoji } from "../../helpers/regexp"; export function embedEmoji(content: EmbedableContent, note: NostrEvent | DraftNostrEvent) { return embedJSX(content, { - regexp: /:([a-zA-Z0-9_]+):/gi, + regexp: getMatchEmoji(), render: (match) => { - const emojiTag = note.tags.find( - (tag) => tag[0] === "emoji" && tag[1].toLowerCase() === match[1].toLowerCase() && tag[2], - ); + const emojiTag = note.tags.filter(isEmojiTag).find((t) => t[1].toLowerCase() === match[1].toLowerCase()); if (emojiTag) { return ( - + ); } return null; diff --git a/src/components/magic-textarea.tsx b/src/components/magic-textarea.tsx new file mode 100644 index 000000000..3e724a1cf --- /dev/null +++ b/src/components/magic-textarea.tsx @@ -0,0 +1,47 @@ +import React from "react"; +import { Image, Textarea, TextareaProps } from "@chakra-ui/react"; +import ReactTextareaAutocomplete, { + ItemComponentProps, + TextareaProps as ReactTextareaAutocompleteProps, +} from "@webscopeio/react-textarea-autocomplete"; +import "@webscopeio/react-textarea-autocomplete/style.css"; + +import { matchSorter } from "match-sorter/dist/match-sorter.esm.js"; +import { Emoji, useContextEmojis } from "../providers/emoji-provider"; + +const Item = ({ entity: { name, char, url } }: ItemComponentProps) => { + if (url) + return ( + + {name}: + + ); + else return {`${name}: ${char}`}; +}; +const Loading: ReactTextareaAutocompleteProps< + Emoji, + React.TextareaHTMLAttributes +>["loadingComponent"] = ({ data }) =>
Loading
; + +export default function MagicTextArea({ ...props }: TextareaProps) { + const emojis = useContextEmojis(); + + return ( +