about summary refs log tree commit diff
diff options
context:
space:
mode:
authorPaul Frazee <pfrazee@gmail.com>2023-08-17 13:39:59 -0700
committerGitHub <noreply@github.com>2023-08-17 13:39:59 -0700
commit4a59178cd2d7028e99a977e0ed10193d135e30dd (patch)
treead02636ac1987214843a737df94b48d240427819
parent5e63d3164b58552f81b597eff83cba991bea958e (diff)
downloadvoidsky-4a59178cd2d7028e99a977e0ed10193d135e30dd.tar.zst
Update the web composer textinput to an emitter (close #1193) (#1205)
The tiptap useEditor() hook creates an awkward challenge for passing
event handlers into its plugins and native events. By introducing a
memoized editor, we should be able to shuttle events out of tiptap
without retriggering the useEditor hook. The emitter can then change
its registered handlers with each state update.
-rw-r--r--package.json1
-rw-r--r--src/view/com/composer/Composer.tsx10
-rw-r--r--src/view/com/composer/text-input/TextInput.web.tsx31
-rw-r--r--yarn.lock5
4 files changed, 32 insertions, 15 deletions
diff --git a/package.json b/package.json
index 02492aceb..c897ead5f 100644
--- a/package.json
+++ b/package.json
@@ -70,6 +70,7 @@
     "base64-js": "^1.5.1",
     "bcp-47-match": "^2.0.3",
     "email-validator": "^2.0.4",
+    "eventemitter3": "^5.0.1",
     "expo": "~48.0.18",
     "expo-application": "~5.1.1",
     "expo-build-properties": "~0.5.1",
diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx
index fe9cc834f..ecfef3ecd 100644
--- a/src/view/com/composer/Composer.tsx
+++ b/src/view/com/composer/Composer.tsx
@@ -151,7 +151,7 @@ export const ComposePost = observer(function ComposePost({
     [gallery, track],
   )
 
-  const onPressPublish = async (rt: RichText) => {
+  const onPressPublish = async () => {
     if (isProcessing || graphemeLength > MAX_GRAPHEME_LENGTH) {
       return
     }
@@ -161,7 +161,7 @@ export const ComposePost = observer(function ComposePost({
 
     setError('')
 
-    if (rt.text.trim().length === 0 && gallery.isEmpty) {
+    if (richtext.text.trim().length === 0 && gallery.isEmpty) {
       setError('Did you want to say anything?')
       return
     }
@@ -170,7 +170,7 @@ export const ComposePost = observer(function ComposePost({
 
     try {
       await apilib.post(store, {
-        rawText: rt.text,
+        rawText: richtext.text,
         replyTo: replyTo?.uri,
         images: gallery.images,
         quote,
@@ -245,9 +245,7 @@ export const ComposePost = observer(function ComposePost({
           ) : canPost ? (
             <TouchableOpacity
               testID="composerPublishBtn"
-              onPress={() => {
-                onPressPublish(richtext)
-              }}
+              onPress={onPressPublish}
               accessibilityRole="button"
               accessibilityLabel={replyTo ? 'Publish reply' : 'Publish post'}
               accessibilityHint={
diff --git a/src/view/com/composer/text-input/TextInput.web.tsx b/src/view/com/composer/text-input/TextInput.web.tsx
index 152bb50ea..f64880e15 100644
--- a/src/view/com/composer/text-input/TextInput.web.tsx
+++ b/src/view/com/composer/text-input/TextInput.web.tsx
@@ -1,6 +1,7 @@
 import React from 'react'
 import {StyleSheet, View} from 'react-native'
 import {RichText} from '@atproto/api'
+import EventEmitter from 'eventemitter3'
 import {useEditor, EditorContent, JSONContent} from '@tiptap/react'
 import {Document} from '@tiptap/extension-document'
 import History from '@tiptap/extension-history'
@@ -53,6 +54,22 @@ export const TextInput = React.forwardRef(
       'ProseMirror-dark',
     )
 
+    // we use a memoized emitter to propagate events out of tiptap
+    // without triggering re-runs of the useEditor hook
+    const emitter = React.useMemo(() => new EventEmitter(), [])
+    React.useEffect(() => {
+      emitter.addListener('publish', onPressPublish)
+      return () => {
+        emitter.removeListener('publish', onPressPublish)
+      }
+    }, [emitter, onPressPublish])
+    React.useEffect(() => {
+      emitter.addListener('photo-pasted', onPhotoPasted)
+      return () => {
+        emitter.removeListener('photo-pasted', onPhotoPasted)
+      }
+    }, [emitter, onPhotoPasted])
+
     const editor = useEditor(
       {
         extensions: [
@@ -87,17 +104,13 @@ export const TextInput = React.forwardRef(
               return
             }
 
-            getImageFromUri(items, onPhotoPasted)
+            getImageFromUri(items, (uri: string) => {
+              emitter.emit('photo-pasted', uri)
+            })
           },
           handleKeyDown: (_, event) => {
             if ((event.metaKey || event.ctrlKey) && event.code === 'Enter') {
-              // Workaround relying on previous state from `setRichText` to
-              // get the updated text content during editor initialization
-              setRichText((state: RichText) => {
-                onPressPublish(state)
-                return state
-              })
-              return true
+              emitter.emit('publish')
             }
           },
         },
@@ -118,7 +131,7 @@ export const TextInput = React.forwardRef(
           }
         },
       },
-      [modeClass],
+      [modeClass, emitter],
     )
 
     React.useImperativeHandle(ref, () => ({
diff --git a/yarn.lock b/yarn.lock
index 8295df224..f49b9a12d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -10150,6 +10150,11 @@ eventemitter3@^4.0.0, eventemitter3@^4.0.4:
   resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
   integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
 
+eventemitter3@^5.0.1:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4"
+  integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==
+
 events@3.3.0, events@^3.2.0, events@^3.3.0:
   version "3.3.0"
   resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"