···55import {useLingui} from '@lingui/react'
6677import {cleanError} from '#/lib/strings/errors'
88-import {useWarnMaxGraphemeCount} from '#/lib/strings/helpers'
88+import {isOverMaxGraphemeCount} from '#/lib/strings/helpers'
99import {richTextToString} from '#/lib/strings/rich-text-helpers'
1010import {shortenLinks, stripInvalidMentions} from '#/lib/strings/rich-text-manip'
1111import {logger} from '#/logger'
···259259 _,
260260 ])
261261262262- const displayNameTooLong = useWarnMaxGraphemeCount({
262262+ const displayNameTooLong = isOverMaxGraphemeCount({
263263 text: displayName,
264264 maxCount: DISPLAY_NAME_MAX_GRAPHEMES,
265265 })
266266- const descriptionTooLong = useWarnMaxGraphemeCount({
266266+ const descriptionTooLong = isOverMaxGraphemeCount({
267267 text: descriptionRt,
268268 maxCount: DESCRIPTION_MAX_GRAPHEMES,
269269 })
+7-27
src/lib/strings/helpers.ts
···11-import {useCallback, useMemo} from 'react'
21import {type RichText} from '@atproto/api'
33-import Graphemer from 'graphemer'
22+import {countGraphemes} from 'unicode-segmenter/grapheme'
4354import {shortenLinks} from './rich-text-manip'
65···2928 return str
3029}
31303232-export function useEnforceMaxGraphemeCount() {
3333- const splitter = useMemo(() => new Graphemer(), [])
3434-3535- return useCallback(
3636- (text: string, maxCount: number) => {
3737- if (splitter.countGraphemes(text) > maxCount) {
3838- return splitter.splitGraphemes(text).slice(0, maxCount).join('')
3939- } else {
4040- return text
4141- }
4242- },
4343- [splitter],
4444- )
4545-}
4646-4747-export function useWarnMaxGraphemeCount({
3131+export function isOverMaxGraphemeCount({
4832 text,
4933 maxCount,
5034}: {
5135 text: string | RichText
5236 maxCount: number
5337}) {
5454- const splitter = useMemo(() => new Graphemer(), [])
5555-5656- return useMemo(() => {
5757- if (typeof text === 'string') {
5858- return splitter.countGraphemes(text) > maxCount
5959- } else {
6060- return shortenLinks(text).graphemeLength > maxCount
6161- }
6262- }, [splitter, maxCount, text])
3838+ if (typeof text === 'string') {
3939+ return countGraphemes(text) > maxCount
4040+ } else {
4141+ return shortenLinks(text).graphemeLength > maxCount
4242+ }
6343}
64446545export function countLines(str: string | undefined): number {
+2-2
src/screens/Messages/components/MessageInput.tsx
···1414import {useSafeAreaInsets} from 'react-native-safe-area-context'
1515import {msg} from '@lingui/macro'
1616import {useLingui} from '@lingui/react'
1717-import Graphemer from 'graphemer'
1717+import {countGraphemes} from 'unicode-segmenter/grapheme'
18181919import {HITSLOP_10, MAX_DM_GRAPHEME_LENGTH} from '#/lib/constants'
2020import {useHaptics} from '#/lib/haptics'
···7575 if (!hasEmbed && message.trim() === '') {
7676 return
7777 }
7878- if (new Graphemer().countGraphemes(message) > MAX_DM_GRAPHEME_LENGTH) {
7878+ if (countGraphemes(message) > MAX_DM_GRAPHEME_LENGTH) {
7979 Toast.show(_(msg`Message is too long`), 'xmark')
8080 return
8181 }
···22import {Pressable, View} from 'react-native'
33import {msg} from '@lingui/macro'
44import {useLingui} from '@lingui/react'
55-import Graphemer from 'graphemer'
65import {flushSync} from 'react-dom'
76import TextareaAutosize from 'react-textarea-autosize'
77+import {countGraphemes} from 'unicode-segmenter/grapheme'
8899import {isSafari, isTouchDevice} from '#/lib/browser'
1010import {MAX_DM_GRAPHEME_LENGTH} from '#/lib/constants'
···5656 if (!hasEmbed && message.trim() === '') {
5757 return
5858 }
5959- if (new Graphemer().countGraphemes(message) > MAX_DM_GRAPHEME_LENGTH) {
5959+ if (countGraphemes(message) > MAX_DM_GRAPHEME_LENGTH) {
6060 Toast.show(_(msg`Message is too long`), 'xmark')
6161 return
6262 }
+3-3
src/screens/Profile/Header/EditProfileDialog.tsx
···6677import {urls} from '#/lib/constants'
88import {cleanError} from '#/lib/strings/errors'
99-import {useWarnMaxGraphemeCount} from '#/lib/strings/helpers'
99+import {isOverMaxGraphemeCount} from '#/lib/strings/helpers'
1010import {logger} from '#/logger'
1111import {type ImageMeta} from '#/state/gallery'
1212import {useProfileUpdateMutation} from '#/state/queries/profile'
···203203 _,
204204 ])
205205206206- const displayNameTooLong = useWarnMaxGraphemeCount({
206206+ const displayNameTooLong = isOverMaxGraphemeCount({
207207 text: displayName,
208208 maxCount: DISPLAY_NAME_MAX_GRAPHEMES,
209209 })
210210- const descriptionTooLong = useWarnMaxGraphemeCount({
210210+ const descriptionTooLong = isOverMaxGraphemeCount({
211211 text: description,
212212 maxCount: DESCRIPTION_MAX_GRAPHEMES,
213213 })
+9-14
src/screens/Takendown.tsx
···11-import {useMemo, useState} from 'react'
11+import {useState} from 'react'
22import {View} from 'react-native'
33import {KeyboardAwareScrollView} from 'react-native-keyboard-controller'
44import {useSafeAreaInsets} from 'react-native-safe-area-context'
···66import {msg, Trans} from '@lingui/macro'
77import {useLingui} from '@lingui/react'
88import {useMutation} from '@tanstack/react-query'
99-import Graphemer from 'graphemer'
99+import {countGraphemes} from 'unicode-segmenter/grapheme'
10101111import {
1212 BLUESKY_MOD_SERVICE_HEADERS,
···3737 const agent = useAgent()
3838 const [isAppealling, setIsAppealling] = useState(false)
3939 const [reason, setReason] = useState('')
4040- const graphemer = useMemo(() => new Graphemer(), [])
41404242- const reasonGraphemeLength = useMemo(() => {
4343- return graphemer.countGraphemes(reason)
4444- }, [graphemer, reason])
4141+ const reasonGraphemeLength = countGraphemes(reason)
4242+ const isOverMaxLength =
4343+ reasonGraphemeLength > MAX_REPORT_REASON_GRAPHEME_LENGTH
45444645 const {
4746 mutate: submitAppeal,
···7271 const primaryBtn =
7372 isAppealling && !isSuccess ? (
7473 <Button
7575- variant="solid"
7674 color="primary"
7775 size="large"
7876 label={_(msg`Submit appeal`)}
7977 onPress={() => submitAppeal(reason)}
8080- disabled={
8181- isPending || reasonGraphemeLength > MAX_REPORT_REASON_GRAPHEME_LENGTH
8282- }>
7878+ disabled={isPending || isOverMaxLength}>
8379 <ButtonText>
8480 <Trans>Submit Appeal</Trans>
8581 </ButtonText>
···8783 </Button>
8884 ) : (
8985 <Button
9090- variant="solid"
9186 size="large"
9287 color="secondary_inverted"
9388 label={_(msg`Sign out`)}
···204199 <Text
205200 style={[
206201 a.text_md,
207207- a.leading_normal,
202202+ a.leading_snug,
208203 {color: t.palette.negative_500},
209204 a.mt_lg,
210205 ]}>
···213208 )}
214209 </View>
215210 ) : (
216216- <P style={[t.atoms.text_contrast_medium]}>
211211+ <P style={[t.atoms.text_contrast_medium, a.leading_snug]}>
217212 <Trans>
218213 Your account was found to be in violation of the{' '}
219214 <SimpleInlineLinkText
220215 label={_(msg`Bluesky Social Terms of Service`)}
221216 to="https://bsky.social/about/support/tos"
222222- style={[a.text_md, a.leading_normal]}>
217217+ style={[a.text_md, a.leading_snug]}>
223218 Bluesky Social Terms of Service
224219 </SimpleInlineLinkText>
225220 . You have been sent an email outlining the specific violation