From 56cf890debeb9872f791ccb992a5587f2c05fd9e Mon Sep 17 00:00:00 2001
From: Paul Frazee <pfrazee@gmail.com>
Date: Mon, 13 Mar 2023 16:01:43 -0500
Subject: Move to expo and react-navigation (#288)

* WIP - adding expo

* WIP - adding expo 2

* Fix tsc

* Finish adding expo

* Disable the 'require cycle' warning

* Tweak plist

* Modify some dependency versions to make expo happy

* Fix icon fill

* Get Web compiling for expo

* 1.7

* Switch to react-navigation in expo2 (#287)

* WIP Switch to react-navigation

* WIP Switch to react-navigation 2

* WIP Switch to react-navigation 3

* Convert all screens to react navigation

* Update BottomBar for react navigation

* Update mobile menu to be react-native drawer

* Fixes to drawer and bottombar

* Factor out some helpers

* Replace the navigation model with react-navigation

* Restructure the shell folder and fix the header positioning

* Restore the error boundary

* Fix tsc

* Implement not-found page

* Remove react-native-gesture-handler (no longer used)

* Handle notifee card presses

* Handle all navigations from the state layer

* Fix drawer behaviors

* Fix two linking issues

* Switch to our react-native-progress fork to fix an svg rendering issue

* Get Web working with react-navigation

* Refactor routes and navigation for a bit more clarity

* Remove dead code

* Rework Web shell to left/right nav to make this easier

* Fix ViewHeader for desktop web

* Hide profileheader back btn on desktop web

* Move the compose button to the left nav

* Implement reply prompt in threads for desktop web

* Composer refactors

* Factor out all platform-specific text input behaviors from the composer

* Small fix

* Update the web build to use tiptap for the composer

* Tune up the mention autocomplete dropdown

* Simplify the default avatar and banner

* Fixes to link cards in web composer

* Fix dropdowns on web

* Tweak load latest on desktop

* Add web beta message and feedback link

* Fix up links in desktop web
---
 .../com/composer/text-input/web/Autocomplete.tsx   | 157 +++++++++++++++++++++
 1 file changed, 157 insertions(+)
 create mode 100644 src/view/com/composer/text-input/web/Autocomplete.tsx

(limited to 'src/view/com/composer/text-input/web/Autocomplete.tsx')

diff --git a/src/view/com/composer/text-input/web/Autocomplete.tsx b/src/view/com/composer/text-input/web/Autocomplete.tsx
new file mode 100644
index 000000000..fbe438969
--- /dev/null
+++ b/src/view/com/composer/text-input/web/Autocomplete.tsx
@@ -0,0 +1,157 @@
+import React, {
+  forwardRef,
+  useEffect,
+  useImperativeHandle,
+  useState,
+} from 'react'
+import {ReactRenderer} from '@tiptap/react'
+import tippy, {Instance as TippyInstance} from 'tippy.js'
+import {
+  SuggestionOptions,
+  SuggestionProps,
+  SuggestionKeyDownProps,
+} from '@tiptap/suggestion'
+import {UserAutocompleteViewModel} from 'state/models/user-autocomplete-view'
+
+interface MentionListRef {
+  onKeyDown: (props: SuggestionKeyDownProps) => boolean
+}
+
+export function createSuggestion({
+  autocompleteView,
+}: {
+  autocompleteView: UserAutocompleteViewModel
+}): Omit<SuggestionOptions, 'editor'> {
+  return {
+    async items({query}) {
+      autocompleteView.setActive(true)
+      await autocompleteView.setPrefix(query)
+      return autocompleteView.suggestions.slice(0, 8).map(s => s.handle)
+    },
+
+    render: () => {
+      let component: ReactRenderer<MentionListRef> | undefined
+      let popup: TippyInstance[] | undefined
+
+      return {
+        onStart: props => {
+          component = new ReactRenderer(MentionList, {
+            props,
+            editor: props.editor,
+          })
+
+          if (!props.clientRect) {
+            return
+          }
+
+          // @ts-ignore getReferenceClientRect doesnt like that clientRect can return null -prf
+          popup = tippy('body', {
+            getReferenceClientRect: props.clientRect,
+            appendTo: () => document.body,
+            content: component.element,
+            showOnCreate: true,
+            interactive: true,
+            trigger: 'manual',
+            placement: 'bottom-start',
+          })
+        },
+
+        onUpdate(props) {
+          component?.updateProps(props)
+
+          if (!props.clientRect) {
+            return
+          }
+
+          popup?.[0]?.setProps({
+            // @ts-ignore getReferenceClientRect doesnt like that clientRect can return null -prf
+            getReferenceClientRect: props.clientRect,
+          })
+        },
+
+        onKeyDown(props) {
+          if (props.event.key === 'Escape') {
+            popup?.[0]?.hide()
+
+            return true
+          }
+
+          return component?.ref?.onKeyDown(props) || false
+        },
+
+        onExit() {
+          popup?.[0]?.destroy()
+          component?.destroy()
+        },
+      }
+    },
+  }
+}
+
+const MentionList = forwardRef<MentionListRef, SuggestionProps>(
+  (props: SuggestionProps, ref) => {
+    const [selectedIndex, setSelectedIndex] = useState(0)
+
+    const selectItem = (index: number) => {
+      const item = props.items[index]
+
+      if (item) {
+        props.command({id: item})
+      }
+    }
+
+    const upHandler = () => {
+      setSelectedIndex(
+        (selectedIndex + props.items.length - 1) % props.items.length,
+      )
+    }
+
+    const downHandler = () => {
+      setSelectedIndex((selectedIndex + 1) % props.items.length)
+    }
+
+    const enterHandler = () => {
+      selectItem(selectedIndex)
+    }
+
+    useEffect(() => setSelectedIndex(0), [props.items])
+
+    useImperativeHandle(ref, () => ({
+      onKeyDown: ({event}) => {
+        if (event.key === 'ArrowUp') {
+          upHandler()
+          return true
+        }
+
+        if (event.key === 'ArrowDown') {
+          downHandler()
+          return true
+        }
+
+        if (event.key === 'Enter') {
+          enterHandler()
+          return true
+        }
+
+        return false
+      },
+    }))
+
+    return (
+      <div className="items">
+        {props.items.length ? (
+          props.items.map((item, index) => (
+            <button
+              className={`item ${index === selectedIndex ? 'is-selected' : ''}`}
+              key={index}
+              onClick={() => selectItem(index)}>
+              {item}
+            </button>
+          ))
+        ) : (
+          <div className="item">No result</div>
+        )}
+      </div>
+    )
+  },
+)
-- 
cgit 1.4.1

