From 16b265a86164e682486a3d8fa51bfa18d51bb945 Mon Sep 17 00:00:00 2001 From: Ansh Date: Tue, 22 Aug 2023 11:01:00 -0700 Subject: [PATCH] [APP-834] Allow @ing someone in post directly from profile (#1241) * setup `initMention` for mobile * setup creating post with profile tagged on web --- src/state/models/ui/shell.ts | 1 + src/view/com/composer/Composer.tsx | 14 ++++- .../com/composer/text-input/TextInput.web.tsx | 60 ++++++++++++++++++- src/view/screens/Profile.tsx | 4 +- src/view/shell/Composer.tsx | 3 + src/view/shell/Composer.web.tsx | 3 + src/view/shell/desktop/LeftNav.tsx | 17 +++++- src/view/shell/index.tsx | 1 + src/view/shell/index.web.tsx | 1 + 9 files changed, 99 insertions(+), 5 deletions(-) diff --git a/src/state/models/ui/shell.ts b/src/state/models/ui/shell.ts index 92d028c7..d1ea4ddf 100644 --- a/src/state/models/ui/shell.ts +++ b/src/state/models/ui/shell.ts @@ -232,6 +232,7 @@ export interface ComposerOpts { replyTo?: ComposerOptsPostRef onPost?: () => void quote?: ComposerOptsQuote + mention?: string // handle of user to mention } export class ShellUiModel { diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index ecfef3ec..cb66cc90 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -45,6 +45,7 @@ import {Gallery} from './photos/Gallery' import {MAX_GRAPHEME_LENGTH} from 'lib/constants' import {LabelsBtn} from './labels/LabelsBtn' import {SelectLangBtn} from './select-language/SelectLangBtn' +import {insertMentionAt} from 'lib/strings/mention-manip' type Props = ComposerOpts & { onClose: () => void @@ -55,6 +56,7 @@ export const ComposePost = observer(function ComposePost({ onPost, onClose, quote: initQuote, + mention: initMention, }: Props) { const {track} = useAnalytics() const pal = usePalette('default') @@ -64,7 +66,17 @@ export const ComposePost = observer(function ComposePost({ const [isProcessing, setIsProcessing] = useState(false) const [processingState, setProcessingState] = 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(() => { return shortenLinks(richtext).graphemeLength }, [richtext]) diff --git a/src/view/com/composer/text-input/TextInput.web.tsx b/src/view/com/composer/text-input/TextInput.web.tsx index f64880e1..b7ebdd98 100644 --- a/src/view/com/composer/text-input/TextInput.web.tsx +++ b/src/view/com/composer/text-input/TextInput.web.tsx @@ -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, editable: true, injectCSS: true, @@ -166,6 +169,61 @@ function editorJsonToText(json: JSONContent): string { 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[] { let links: string[] = [] if (json.content?.length) { diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx index a51fbcf5..ce437d6c 100644 --- a/src/view/screens/Profile.tsx +++ b/src/view/screens/Profile.tsx @@ -92,8 +92,8 @@ export const ProfileScreen = withAuthRequired( const onPressCompose = React.useCallback(() => { track('ProfileScreen:PressCompose') - store.shell.openComposer({}) - }, [store, track]) + store.shell.openComposer({mention: uiState.profile.handle}) + }, [store, track, uiState]) const onSelectView = React.useCallback( (index: number) => { uiState.setSelectedViewIndex(index) diff --git a/src/view/shell/Composer.tsx b/src/view/shell/Composer.tsx index e87fea64..ac155887 100644 --- a/src/view/shell/Composer.tsx +++ b/src/view/shell/Composer.tsx @@ -14,6 +14,7 @@ export const Composer = observer( onPost, onClose, quote, + mention, }: { active: boolean winHeight: number @@ -21,6 +22,7 @@ export const Composer = observer( onPost?: ComposerOpts['onPost'] onClose: () => void quote?: ComposerOpts['quote'] + mention?: ComposerOpts['mention'] }) => { const pal = usePalette('default') const initInterp = useAnimatedValue(0) @@ -65,6 +67,7 @@ export const Composer = observer( onPost={onPost} onClose={onClose} quote={quote} + mention={mention} /> ) diff --git a/src/view/shell/Composer.web.tsx b/src/view/shell/Composer.web.tsx index cf850aa4..2effa0cc 100644 --- a/src/view/shell/Composer.web.tsx +++ b/src/view/shell/Composer.web.tsx @@ -15,6 +15,7 @@ export const Composer = observer( quote, onPost, onClose, + mention, }: { active: boolean winHeight: number @@ -22,6 +23,7 @@ export const Composer = observer( quote: ComposerOpts['quote'] onPost?: ComposerOpts['onPost'] onClose: () => void + mention?: ComposerOpts['mention'] }) => { const pal = usePalette('default') @@ -40,6 +42,7 @@ export const Composer = observer( quote={quote} onPost={onPost} onClose={onClose} + mention={mention} /> diff --git a/src/view/shell/desktop/LeftNav.tsx b/src/view/shell/desktop/LeftNav.tsx index 700857bb..b37befba 100644 --- a/src/view/shell/desktop/LeftNav.tsx +++ b/src/view/shell/desktop/LeftNav.tsx @@ -150,7 +150,22 @@ const NavItem = observer( function ComposeBtn() { 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 ( { replyTo={store.shell.composerOpts?.replyTo} onPost={store.shell.composerOpts?.onPost} quote={store.shell.composerOpts?.quote} + mention={store.shell.composerOpts?.mention} /> diff --git a/src/view/shell/index.web.tsx b/src/view/shell/index.web.tsx index 5e387526..16ed17a5 100644 --- a/src/view/shell/index.web.tsx +++ b/src/view/shell/index.web.tsx @@ -49,6 +49,7 @@ const ShellInner = observer(() => { replyTo={store.shell.composerOpts?.replyTo} quote={store.shell.composerOpts?.quote} onPost={store.shell.composerOpts?.onPost} + mention={store.shell.composerOpts?.mention} /> {!isDesktop && }