From d9e90e9ea842c313eab009b04c78d8e655930690 Mon Sep 17 00:00:00 2001 From: Josh Perez <60019601+josh-signal@users.noreply.github.com> Date: Wed, 21 Jul 2021 16:45:41 -0400 Subject: [PATCH] Improvements to shared library components --- ts/background.ts | 9 +- ts/components/GroupDescriptionInput.tsx | 2 +- ts/components/GroupTitleInput.tsx | 2 +- ts/components/Input.stories.tsx | 8 +- ts/components/Input.tsx | 38 +-- ts/components/ProfileEditor.tsx | 43 ++- .../EditConversationAttributesModal.tsx | 12 +- ts/models/conversations.ts | 276 +----------------- ts/routineProfileRefresh.ts | 4 +- ts/services/writeProfile.ts | 20 +- .../routineProfileRefresh_test.ts | 114 ++++++-- ts/textsecure/SendMessage.ts | 25 ++ ts/types/SealedSender.ts | 9 + ts/util/getProfile.ts | 269 +++++++++++++++++ ts/views/conversation_view.ts | 2 +- 15 files changed, 485 insertions(+), 348 deletions(-) create mode 100644 ts/types/SealedSender.ts create mode 100644 ts/util/getProfile.ts diff --git a/ts/background.ts b/ts/background.ts index 590272add4d9..68f496b162a7 100644 --- a/ts/background.ts +++ b/ts/background.ts @@ -38,6 +38,7 @@ import { ourProfileKeyService } from './services/ourProfileKey'; import { shouldRespondWithProfileKey } from './util/shouldRespondWithProfileKey'; import { LatestQueue } from './util/LatestQueue'; import { parseIntOrThrow } from './util/parseIntOrThrow'; +import { getProfile } from './util/getProfile'; import { TypingEvent, ErrorEvent, @@ -3655,10 +3656,12 @@ export async function startApp(): Promise { const FETCH_LATEST_ENUM = Proto.SyncMessage.FetchLatest.Type; switch (eventType) { - case FETCH_LATEST_ENUM.LOCAL_PROFILE: - // Intentionally do nothing since we'll be receiving the - // window.storage manifest request and will update local profile along with that. + case FETCH_LATEST_ENUM.LOCAL_PROFILE: { + const ourUuid = window.textsecure.storage.user.getUuid(); + const ourE164 = window.textsecure.storage.user.getNumber(); + await getProfile(ourUuid, ourE164); break; + } case FETCH_LATEST_ENUM.STORAGE_MANIFEST: window.log.info('onFetchLatestSync: fetching latest manifest'); await window.Signal.Services.runStorageServiceSyncJob(); diff --git a/ts/components/GroupDescriptionInput.tsx b/ts/components/GroupDescriptionInput.tsx index afa955268a94..ec467e3d321e 100644 --- a/ts/components/GroupDescriptionInput.tsx +++ b/ts/components/GroupDescriptionInput.tsx @@ -22,7 +22,7 @@ export const GroupDescriptionInput = forwardRef( i18n={i18n} onChange={onChangeValue} placeholder={i18n('setGroupMetadata__group-description-placeholder')} - maxGraphemeCount={256} + maxLengthCount={256} ref={ref} value={value} whenToShowRemainingCount={150} diff --git a/ts/components/GroupTitleInput.tsx b/ts/components/GroupTitleInput.tsx index e885d1e8803e..0043066f9ebf 100644 --- a/ts/components/GroupTitleInput.tsx +++ b/ts/components/GroupTitleInput.tsx @@ -21,7 +21,7 @@ export const GroupTitleInput = forwardRef( i18n={i18n} onChange={onChangeValue} placeholder={i18n('setGroupMetadata__group-name-placeholder')} - maxGraphemeCount={32} + maxLengthCount={32} ref={ref} value={value} /> diff --git a/ts/components/Input.stories.tsx b/ts/components/Input.stories.tsx index 45544492394f..bf42fbc30c5c 100644 --- a/ts/components/Input.stories.tsx +++ b/ts/components/Input.stories.tsx @@ -21,7 +21,7 @@ const createProps = (overrideProps: Partial = {}): PropsType => ({ hasClearButton: Boolean(overrideProps.hasClearButton), i18n, icon: overrideProps.icon, - maxGraphemeCount: overrideProps.maxGraphemeCount, + maxLengthCount: overrideProps.maxLengthCount, onChange: action('onChange'), placeholder: text( 'placeholder', @@ -51,7 +51,7 @@ stories.add('hasClearButton', () => ( stories.add('character count', () => ( )); @@ -59,7 +59,7 @@ stories.add('character count', () => ( stories.add('character count (customizable show)', () => ( @@ -78,7 +78,7 @@ stories.add('expandable w/count', () => ( {...createProps({ expandable: true, hasClearButton: true, - maxGraphemeCount: 140, + maxLengthCount: 140, whenToShowRemainingCount: 0, })} /> diff --git a/ts/components/Input.tsx b/ts/components/Input.tsx index 5932468fd296..d5aed0a8c1f4 100644 --- a/ts/components/Input.tsx +++ b/ts/components/Input.tsx @@ -15,12 +15,13 @@ import { getClassNamesFor } from '../util/getClassNamesFor'; import { multiRef } from '../util/multiRef'; export type PropsType = { + countLength?: (value: string) => number; disabled?: boolean; expandable?: boolean; hasClearButton?: boolean; i18n: LocalizerType; icon?: ReactNode; - maxGraphemeCount?: number; + maxLengthCount?: number; moduleClassName?: string; onChange: (value: string) => unknown; placeholder: string; @@ -29,7 +30,7 @@ export type PropsType = { }; /** - * Some inputs must have fewer than maxGraphemeCount glyphs. Ideally, we'd use the + * Some inputs must have fewer than maxLengthCount glyphs. Ideally, we'd use the * `maxLength` property on inputs, but that doesn't account for glyphs that are more than * one UTF-16 code units. For example: `'💩💩'.length === 4`. * @@ -51,12 +52,13 @@ export const Input = forwardRef< >( ( { + countLength = grapheme.count, disabled, expandable, hasClearButton, i18n, icon, - maxGraphemeCount = 0, + maxLengthCount = 0, moduleClassName, onChange, placeholder, @@ -108,9 +110,9 @@ export const Input = forwardRef< const newValue = inputEl.value; - const newGraphemeCount = maxGraphemeCount ? grapheme.count(newValue) : 0; + const newLengthCount = maxLengthCount ? countLength(newValue) : 0; - if (newGraphemeCount <= maxGraphemeCount) { + if (newLengthCount <= maxLengthCount) { onChange(newValue); } else { inputEl.value = valueOnKeydownRef.current; @@ -119,12 +121,12 @@ export const Input = forwardRef< } maybeSetLarge(); - }, [maxGraphemeCount, maybeSetLarge, onChange]); + }, [countLength, maxLengthCount, maybeSetLarge, onChange]); const handlePaste = useCallback( (event: ClipboardEvent) => { const inputEl = innerRef.current; - if (!inputEl || !maxGraphemeCount) { + if (!inputEl || !maxLengthCount) { return; } @@ -136,25 +138,25 @@ export const Input = forwardRef< const pastedText = event.clipboardData.getData('Text'); - const newGraphemeCount = - grapheme.count(textBeforeSelection) + - grapheme.count(pastedText) + - grapheme.count(textAfterSelection); + const newLengthCount = + countLength(textBeforeSelection) + + countLength(pastedText) + + countLength(textAfterSelection); - if (newGraphemeCount > maxGraphemeCount) { + if (newLengthCount > maxLengthCount) { event.preventDefault(); } maybeSetLarge(); }, - [maxGraphemeCount, maybeSetLarge, value] + [countLength, maxLengthCount, maybeSetLarge, value] ); useEffect(() => { maybeSetLarge(); }, [maybeSetLarge]); - const graphemeCount = maxGraphemeCount ? grapheme.count(value) : -1; + const lengthCount = maxLengthCount ? countLength(value) : -1; const getClassName = getClassNamesFor('Input', moduleClassName); const inputProps = { @@ -187,9 +189,9 @@ export const Input = forwardRef< /> ) : null; - const graphemeCountElement = graphemeCount >= whenToShowRemainingCount && ( + const lengthCountElement = lengthCount >= whenToShowRemainingCount && (
- {maxGraphemeCount - graphemeCount} + {maxLengthCount - lengthCount}
); @@ -208,12 +210,12 @@ export const Input = forwardRef< {clearButtonElement}
- {graphemeCountElement} + {lengthCountElement}
) : (
- {graphemeCountElement} + {lengthCountElement} {clearButtonElement}
)} diff --git a/ts/components/ProfileEditor.tsx b/ts/components/ProfileEditor.tsx index e6db766cafa7..a8d9cadfed12 100644 --- a/ts/components/ProfileEditor.tsx +++ b/ts/components/ProfileEditor.tsx @@ -2,7 +2,6 @@ // SPDX-License-Identifier: AGPL-3.0-only import React, { useCallback, useEffect, useRef, useState } from 'react'; -import * as grapheme from '../util/grapheme'; import { AvatarInputContainer } from './AvatarInputContainer'; import { AvatarInputType } from './AvatarInput'; @@ -141,9 +140,19 @@ export const ProfileEditor = ({ [setAvatarData] ); - const calculateGraphemeCount = useCallback((name = '') => { - return 256 - grapheme.count(name); - }, []); + const getTextEncoder = useCallback(() => new TextEncoder(), []); + + const countByteLength = useCallback( + (str: string) => getTextEncoder().encode(str).byteLength, + [getTextEncoder] + ); + + const calculateLengthCount = useCallback( + (name = '') => { + return 256 - countByteLength(name); + }, + [countByteLength] + ); useEffect(() => { const focusNode = focusInputRef.current; @@ -155,11 +164,18 @@ export const ProfileEditor = ({ }, [editState]); if (editState === EditState.ProfileName) { + const shouldDisableSave = + !stagedProfile.firstName || + (stagedProfile.firstName === fullName.firstName && + stagedProfile.familyName === fullName.familyName); + content = ( <> { setStagedProfile(profileData => ({ ...profileData, @@ -171,8 +187,10 @@ export const ProfileEditor = ({ value={stagedProfile.firstName} /> { setStagedProfile(profileData => ({ ...profileData, @@ -208,14 +226,14 @@ export const ProfileEditor = ({ {i18n('cancel')}