diff --git a/src/view/com/composer/TagInput/index.tsx b/src/view/com/composer/TagInput/index.tsx index a01b94152c..d3d1f50826 100644 --- a/src/view/com/composer/TagInput/index.tsx +++ b/src/view/com/composer/TagInput/index.tsx @@ -23,7 +23,7 @@ import {useStores} from 'state/index' import {ActivityIndicator} from 'react-native' import {TagInputEntryButton} from './TagInputEntryButton' import {uniq} from 'lib/strings/helpers' -import {sanitizeHashtag} from './util' +import {sanitizeHashtag, sanitizeHashtagOnChange} from './util' export function TagInput({ max = 8, @@ -103,12 +103,14 @@ export function TagInput({ ) const onChangeText = React.useCallback( - async (v: string) => { - setValue(v) + async (value: string) => { + const tag = sanitizeHashtagOnChange(value) - if (v.length > 0) { + setValue(tag) + + if (tag.length > 0) { model.setActive(true) - await model.search(v) + await model.search(tag) setSuggestions(model.suggestions) } else { diff --git a/src/view/com/composer/TagInput/index.web.tsx b/src/view/com/composer/TagInput/index.web.tsx index d0ac8d2c75..2a9a95f1f7 100644 --- a/src/view/com/composer/TagInput/index.web.tsx +++ b/src/view/com/composer/TagInput/index.web.tsx @@ -23,7 +23,7 @@ import {useStores} from 'state/index' import {TagInputEntryButton} from './TagInputEntryButton' import {TextInputFocusEventData} from 'react-native' import {uniq} from 'lib/strings/helpers' -import {sanitizeHashtag} from './util' +import {sanitizeHashtag, sanitizeHashtagOnChange} from './util' export function TagInput({ max = 8, @@ -153,12 +153,14 @@ export function TagInput({ ) const onChangeText = React.useCallback( - async (v: string) => { - setValue(v) + async (value: string) => { + const tag = sanitizeHashtagOnChange(value) - if (v.length > 0) { + setValue(tag) + + if (tag.length > 0) { model.setActive(true) - await model.search(v) + await model.search(tag) setDropdownItems( model.suggestions.map(item => ({ @@ -199,9 +201,10 @@ export function TagInput({ (!target || !target.id.includes('tag_autocomplete_option')) ) { setOpen(false) + setValue('') } }, - [tags, setOpen], + [tags, setOpen, setValue], ) React.useEffect(() => { diff --git a/src/view/com/composer/TagInput/util.ts b/src/view/com/composer/TagInput/util.ts index b085054853..7abab04f0e 100644 --- a/src/view/com/composer/TagInput/util.ts +++ b/src/view/com/composer/TagInput/util.ts @@ -1,8 +1,28 @@ -import {TRAILING_PUNCTUATION_REGEX, LEADING_HASH_REGEX} from '@atproto/api' +import { + HASHTAG_INVALID_CHARACTER_REGEX, + TRAILING_PUNCTUATION_REGEX, + LEADING_PUNCTUATION_REGEX, + LEADING_NUMBER_REGEX, +} from '@atproto/api' -export function sanitizeHashtag(tagString: string) { - return tagString - .trim() - .replace(LEADING_HASH_REGEX, '') +/** + * Trims leading numbers, all invalid characters, and any trailing punctuation. + */ +export function sanitizeHashtag(hashtag: string) { + return hashtag + .replace(LEADING_PUNCTUATION_REGEX, '') + .replace(LEADING_NUMBER_REGEX, '') + .replace(HASHTAG_INVALID_CHARACTER_REGEX, '') .replace(TRAILING_PUNCTUATION_REGEX, '') } + +/** + * Trims leading numbers and all invalid charactes, but ignores trailing + * punctuation in case the user intends to use `_` or `-`. + */ +export function sanitizeHashtagOnChange(hashtag: string) { + return hashtag + .replace(LEADING_PUNCTUATION_REGEX, '') + .replace(LEADING_NUMBER_REGEX, '') + .replace(HASHTAG_INVALID_CHARACTER_REGEX, '') +} diff --git a/src/view/com/composer/text-input/mobile/TagsAutocomplete.tsx b/src/view/com/composer/text-input/mobile/TagsAutocomplete.tsx index 816e9509ea..a5d6756da5 100644 --- a/src/view/com/composer/text-input/mobile/TagsAutocomplete.tsx +++ b/src/view/com/composer/text-input/mobile/TagsAutocomplete.tsx @@ -29,7 +29,9 @@ export function getHashtagAt(text: string, position: number) { * show autocomplete after a single # is typed * AND the cursor is next to the # */ - for (const match of Array.from(text.matchAll(LEADING_HASH_REGEX))) { + for (const match of Array.from( + text.matchAll(new RegExp(LEADING_HASH_REGEX, 'g')), + )) { const {index} = match if (index === undefined) continue if (position >= index && position <= index + 1) {