* 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
157 lines
3.7 KiB
TypeScript
157 lines
3.7 KiB
TypeScript
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>
|
|
)
|
|
},
|
|
)
|