[APP-834] Allow @ing someone in post directly from profile (#1241)

* setup `initMention` for mobile

* setup creating post with profile tagged on web
zio/stable
Ansh 2023-08-22 11:01:00 -07:00 committed by GitHub
parent 3aadc43c89
commit 16b265a861
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 99 additions and 5 deletions

View File

@ -232,6 +232,7 @@ export interface ComposerOpts {
replyTo?: ComposerOptsPostRef replyTo?: ComposerOptsPostRef
onPost?: () => void onPost?: () => void
quote?: ComposerOptsQuote quote?: ComposerOptsQuote
mention?: string // handle of user to mention
} }
export class ShellUiModel { export class ShellUiModel {

View File

@ -45,6 +45,7 @@ import {Gallery} from './photos/Gallery'
import {MAX_GRAPHEME_LENGTH} from 'lib/constants' import {MAX_GRAPHEME_LENGTH} from 'lib/constants'
import {LabelsBtn} from './labels/LabelsBtn' import {LabelsBtn} from './labels/LabelsBtn'
import {SelectLangBtn} from './select-language/SelectLangBtn' import {SelectLangBtn} from './select-language/SelectLangBtn'
import {insertMentionAt} from 'lib/strings/mention-manip'
type Props = ComposerOpts & { type Props = ComposerOpts & {
onClose: () => void onClose: () => void
@ -55,6 +56,7 @@ export const ComposePost = observer(function ComposePost({
onPost, onPost,
onClose, onClose,
quote: initQuote, quote: initQuote,
mention: initMention,
}: Props) { }: Props) {
const {track} = useAnalytics() const {track} = useAnalytics()
const pal = usePalette('default') const pal = usePalette('default')
@ -64,7 +66,17 @@ export const ComposePost = observer(function ComposePost({
const [isProcessing, setIsProcessing] = useState(false) const [isProcessing, setIsProcessing] = useState(false)
const [processingState, setProcessingState] = useState('') const [processingState, setProcessingState] = useState('')
const [error, setError] = useState('') const [error, setError] = useState('')
const [richtext, setRichText] = useState(new RichText({text: ''})) const [richtext, setRichText] = useState(
new RichText({
text: initMention
? insertMentionAt(
`@${initMention}`,
initMention.length + 1,
`${initMention}`,
) // insert mention if passed in
: '',
}),
)
const graphemeLength = useMemo(() => { const graphemeLength = useMemo(() => {
return shortenLinks(richtext).graphemeLength return shortenLinks(richtext).graphemeLength
}, [richtext]) }, [richtext])

View File

@ -114,7 +114,10 @@ export const TextInput = React.forwardRef(
} }
}, },
}, },
content: richtext.text.toString(), content: textToEditorJson(richtext.text.toString()),
onFocus: ({editor: e}) => {
e.chain().focus().setTextSelection(richtext.text.length).run() // focus to the end of the text
},
autofocus: true, autofocus: true,
editable: true, editable: true,
injectCSS: true, injectCSS: true,
@ -166,6 +169,61 @@ function editorJsonToText(json: JSONContent): string {
return text return text
} }
function textToEditorJson(text: string): JSONContent {
if (text === '' || text.length === 0) {
return {
text: '',
}
}
const lines = text.split('\n')
const docContent: JSONContent[] = []
for (const line of lines) {
if (line.trim() === '') {
continue // skip empty lines
}
const paragraphContent: JSONContent[] = []
let position = 0
while (position < line.length) {
if (line[position] === '@') {
// Handle mentions
let endPosition = position + 1
while (endPosition < line.length && /\S/.test(line[endPosition])) {
endPosition++
}
const mentionId = line.substring(position + 1, endPosition)
paragraphContent.push({
type: 'mention',
attrs: {id: mentionId},
})
position = endPosition
} else {
// Handle regular text
let endPosition = line.indexOf('@', position)
if (endPosition === -1) endPosition = line.length
paragraphContent.push({
type: 'text',
text: line.substring(position, endPosition),
})
position = endPosition
}
}
docContent.push({
type: 'paragraph',
content: paragraphContent,
})
}
return {
type: 'doc',
content: docContent,
}
}
function editorJsonToLinks(json: JSONContent): string[] { function editorJsonToLinks(json: JSONContent): string[] {
let links: string[] = [] let links: string[] = []
if (json.content?.length) { if (json.content?.length) {

View File

@ -92,8 +92,8 @@ export const ProfileScreen = withAuthRequired(
const onPressCompose = React.useCallback(() => { const onPressCompose = React.useCallback(() => {
track('ProfileScreen:PressCompose') track('ProfileScreen:PressCompose')
store.shell.openComposer({}) store.shell.openComposer({mention: uiState.profile.handle})
}, [store, track]) }, [store, track, uiState])
const onSelectView = React.useCallback( const onSelectView = React.useCallback(
(index: number) => { (index: number) => {
uiState.setSelectedViewIndex(index) uiState.setSelectedViewIndex(index)

View File

@ -14,6 +14,7 @@ export const Composer = observer(
onPost, onPost,
onClose, onClose,
quote, quote,
mention,
}: { }: {
active: boolean active: boolean
winHeight: number winHeight: number
@ -21,6 +22,7 @@ export const Composer = observer(
onPost?: ComposerOpts['onPost'] onPost?: ComposerOpts['onPost']
onClose: () => void onClose: () => void
quote?: ComposerOpts['quote'] quote?: ComposerOpts['quote']
mention?: ComposerOpts['mention']
}) => { }) => {
const pal = usePalette('default') const pal = usePalette('default')
const initInterp = useAnimatedValue(0) const initInterp = useAnimatedValue(0)
@ -65,6 +67,7 @@ export const Composer = observer(
onPost={onPost} onPost={onPost}
onClose={onClose} onClose={onClose}
quote={quote} quote={quote}
mention={mention}
/> />
</Animated.View> </Animated.View>
) )

View File

@ -15,6 +15,7 @@ export const Composer = observer(
quote, quote,
onPost, onPost,
onClose, onClose,
mention,
}: { }: {
active: boolean active: boolean
winHeight: number winHeight: number
@ -22,6 +23,7 @@ export const Composer = observer(
quote: ComposerOpts['quote'] quote: ComposerOpts['quote']
onPost?: ComposerOpts['onPost'] onPost?: ComposerOpts['onPost']
onClose: () => void onClose: () => void
mention?: ComposerOpts['mention']
}) => { }) => {
const pal = usePalette('default') const pal = usePalette('default')
@ -40,6 +42,7 @@ export const Composer = observer(
quote={quote} quote={quote}
onPost={onPost} onPost={onPost}
onClose={onClose} onClose={onClose}
mention={mention}
/> />
</View> </View>
</View> </View>

View File

@ -150,7 +150,22 @@ const NavItem = observer(
function ComposeBtn() { function ComposeBtn() {
const store = useStores() const store = useStores()
const onPressCompose = () => store.shell.openComposer({}) const {getState} = useNavigation()
const getProfileHandle = () => {
const {routes} = getState()
const currentRoute = routes[routes.length - 1]
if (currentRoute.name === 'Profile') {
const {name: handle} =
currentRoute.params as CommonNavigatorParams['Profile']
if (handle === store.me.handle) return undefined
return handle
}
return undefined
}
const onPressCompose = () =>
store.shell.openComposer({mention: getProfileHandle()})
return ( return (
<TouchableOpacity <TouchableOpacity

View File

@ -68,6 +68,7 @@ const ShellInner = observer(() => {
replyTo={store.shell.composerOpts?.replyTo} replyTo={store.shell.composerOpts?.replyTo}
onPost={store.shell.composerOpts?.onPost} onPost={store.shell.composerOpts?.onPost}
quote={store.shell.composerOpts?.quote} quote={store.shell.composerOpts?.quote}
mention={store.shell.composerOpts?.mention}
/> />
<ModalsContainer /> <ModalsContainer />
<Lightbox /> <Lightbox />

View File

@ -49,6 +49,7 @@ const ShellInner = observer(() => {
replyTo={store.shell.composerOpts?.replyTo} replyTo={store.shell.composerOpts?.replyTo}
quote={store.shell.composerOpts?.quote} quote={store.shell.composerOpts?.quote}
onPost={store.shell.composerOpts?.onPost} onPost={store.shell.composerOpts?.onPost}
mention={store.shell.composerOpts?.mention}
/> />
{!isDesktop && <BottomBarWeb />} {!isDesktop && <BottomBarWeb />}
<ModalsContainer /> <ModalsContainer />