Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions _locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -2861,6 +2861,14 @@
"messageformat": "Show text formatting popover when text is selected",
"description": "Description of the text-formatting popover menu setting"
},
"icu:Preferences__typing-auto-focus--title": {
"messageformat": "Auto-focus message input when typing",
"description": "Title for the setting that automatically focuses the message composition area when a key is pressed"
},
"icu:Preferences__typing-auto-focus--description": {
"messageformat": "Automatically focus the message input field when you start typing, even if it isn't currently focused.",
"description": "Description for the auto-focus typing setting"
},
"icu:spellCheckWillBeEnabled": {
"messageformat": "Spell check will be enabled the next time Signal starts.",
"description": "Shown when the user enables spellcheck to indicate that they must restart Signal."
Expand Down
1 change: 1 addition & 0 deletions ts/components/CompositionArea.dom.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export default {
i18n,
isDisabled: false,
isFormattingEnabled: true,
isTypingAutoFocusEnabled: true,
isPollSend1to1Enabled: true,
messageCompositionId: '456',
sendEditedMessage: action('sendEditedMessage'),
Expand Down
49 changes: 49 additions & 0 deletions ts/components/CompositionArea.dom.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ export type OwnProps = Readonly<{
isDisabled: boolean;
isFetchingUUID: boolean | null;
isFormattingEnabled: boolean;
isTypingAutoFocusEnabled: boolean;
isGroupV1AndDisabled: boolean | null;
isMissingMandatoryProfileSharing: boolean | null;
isPollSend1to1Enabled: boolean;
Expand Down Expand Up @@ -309,6 +310,7 @@ export const CompositionArea = memo(function CompositionArea({
draftText,
getPreferredBadge,
isFormattingEnabled,
isTypingAutoFocusEnabled,
onEditorStateChange,
onTextTooLong,
ourConversationId,
Expand Down Expand Up @@ -517,6 +519,53 @@ export const CompositionArea = memo(function CompositionArea({
}
});

// Auto-focus composer when user presses a printable key
useDocumentKeyDown(
useCallback(
(event: KeyboardEvent) => {
if (!isTypingAutoFocusEnabled) {
return;
}

if (inputApiRef.current?.hasFocus()) {
return;
}

// Printable keys have a single-char `key` value
if (event.key.length !== 1) {
return;
}

// Don't hijack keyboard shortcuts
if (event.ctrlKey || event.metaKey || event.altKey) {
return;
}

// Don't steal focus from other input elements
const active = document.activeElement;
if (active) {
const tag = active.tagName.toLowerCase();
if (
tag === 'input' ||
tag === 'textarea' ||
tag === 'select'
) {
return;
}
if (
active.getAttribute('contenteditable') === 'true' ||
active.getAttribute('role') === 'textbox'
) {
return;
}
}

inputApiRef.current?.focus();
},
[isTypingAutoFocusEnabled]
)
);

// Focus input on first mount
const previousFocusCounter = usePrevious<number | undefined>(
focusCounter,
Expand Down
2 changes: 2 additions & 0 deletions ts/components/Preferences.dom.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,7 @@ export default {
hasSpellCheck: true,
hasStoriesDisabled: false,
hasTextFormatting: true,
hasTypingAutoFocus: true,
hasTypingIndicators: true,
hasKeepMutedChatsArchived: false,
initialSpellCheckSetting: true,
Expand Down Expand Up @@ -588,6 +589,7 @@ export default {
onSpellCheckChange: action('onSpellCheckChange'),
onStartUpdate: action('onStartUpdate'),
onTextFormattingChange: action('onTextFormattingChange'),
onTypingAutoFocusChange: action('onTypingAutoFocusChange'),
onThemeChange: action('onThemeChange'),
onToggleNavTabsCollapse: action('onToggleNavTabsCollapse'),
onUniversalExpireTimerChange: action('onUniversalExpireTimerChange'),
Expand Down
14 changes: 14 additions & 0 deletions ts/components/Preferences.dom.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ export type PropsDataType = {
hasSpellCheck: boolean | undefined;
hasStoriesDisabled: boolean;
hasTextFormatting: boolean;
hasTypingAutoFocus: boolean;
hasTypingIndicators: boolean;
hasKeepMutedChatsArchived: boolean;
settingsLocation: SettingsLocation;
Expand Down Expand Up @@ -328,6 +329,7 @@ type PropsFunctionType = {
onSentMediaQualityChange: SelectChangeHandlerType<SentMediaQualityType>;
onSpellCheckChange: CheckboxChangeHandlerType;
onTextFormattingChange: CheckboxChangeHandlerType;
onTypingAutoFocusChange: CheckboxChangeHandlerType;
onThemeChange: SelectChangeHandlerType<ThemeType>;
onToggleNavTabsCollapse: (navTabsCollapsed: boolean) => void;
onUniversalExpireTimerChange: SelectChangeHandlerType<number>;
Expand Down Expand Up @@ -435,6 +437,7 @@ export function Preferences({
hasSpellCheck,
hasStoriesDisabled,
hasTextFormatting,
hasTypingAutoFocus,
hasTypingIndicators,
hasKeepMutedChatsArchived,
i18n,
Expand Down Expand Up @@ -492,6 +495,7 @@ export function Preferences({
onSentMediaQualityChange,
onSpellCheckChange,
onTextFormattingChange,
onTypingAutoFocusChange,
onThemeChange,
onToggleNavTabsCollapse,
onUniversalExpireTimerChange,
Expand Down Expand Up @@ -1152,6 +1156,16 @@ export function Preferences({
name="textFormatting"
onChange={onTextFormattingChange}
/>
<Checkbox
checked={hasTypingAutoFocus}
description={i18n(
'icu:Preferences__typing-auto-focus--description'
)}
label={i18n('icu:Preferences__typing-auto-focus--title')}
moduleClassName="Preferences__checkbox"
name="typingAutoFocus"
onChange={onTypingAutoFocusChange}
/>
<Checkbox
checked={hasLinkPreviews}
description={i18n('icu:Preferences__link-previews--description')}
Expand Down
5 changes: 5 additions & 0 deletions ts/state/selectors/items.dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,11 @@ export const getTextFormattingEnabled = createSelector(
(state: ItemsStateType): boolean => Boolean(state.textFormatting ?? true)
);

export const getTypingAutoFocus = createSelector(
getItems,
(state: ItemsStateType): boolean => Boolean(state.typingAutoFocus ?? true)
);

export const getNavTabsCollapsed = createSelector(
getItems,
(state: ItemsStateType): boolean => Boolean(state.navTabsCollapsed ?? false)
Expand Down
3 changes: 3 additions & 0 deletions ts/state/smart/CompositionArea.preload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
getEmojiSkinToneDefault,
getItems,
getTextFormattingEnabled,
getTypingAutoFocus,
} from '../selectors/items.dom.js';
import { canForward, getPropsForQuote } from '../selectors/message.preload.js';
import {
Expand Down Expand Up @@ -89,6 +90,7 @@ export const SmartCompositionArea = memo(function SmartCompositionArea({
const selectedMessageIds = useSelector(getSelectedMessageIds);
const messageLookup = useSelector(getMessages);
const isFormattingEnabled = useSelector(getTextFormattingEnabled);
const isTypingAutoFocusEnabled = useSelector(getTypingAutoFocus);
const items = useSelector(getItems);
const version = useSelector(getVersion);
const lastEditableMessageId = useSelector(getLastEditableMessageId);
Expand Down Expand Up @@ -250,6 +252,7 @@ export const SmartCompositionArea = memo(function SmartCompositionArea({
i18n={i18n}
isDisabled={isDisabled}
isFormattingEnabled={isFormattingEnabled}
isTypingAutoFocusEnabled={isTypingAutoFocusEnabled}
isPollSend1to1Enabled={isFeaturedEnabledSelector({
betaKey: 'desktop.pollSend1to1.beta',
prodKey: 'desktop.pollSend1to1.prod',
Expand Down
6 changes: 6 additions & 0 deletions ts/state/smart/Preferences.preload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -709,6 +709,10 @@ export function SmartPreferences(): React.JSX.Element | null {
'textFormatting',
true
);
const [hasTypingAutoFocus, onTypingAutoFocusChange] = createItemsAccess(
'typingAutoFocus',
true
);
const [lastSyncTime, onLastSyncTimeChange] = createItemsAccess(
'synced_at',
undefined
Expand Down Expand Up @@ -868,6 +872,7 @@ export function SmartPreferences(): React.JSX.Element | null {
hasSpellCheck={hasSpellCheck}
hasStoriesDisabled={hasStoriesDisabled}
hasTextFormatting={hasTextFormatting}
hasTypingAutoFocus={hasTypingAutoFocus}
hasTypingIndicators={hasTypingIndicators}
i18n={i18n}
initialSpellCheckSetting={initialSpellCheckSetting}
Expand Down Expand Up @@ -932,6 +937,7 @@ export function SmartPreferences(): React.JSX.Element | null {
onSentMediaQualityChange={onSentMediaQualityChange}
onSpellCheckChange={onSpellCheckChange}
onTextFormattingChange={onTextFormattingChange}
onTypingAutoFocusChange={onTypingAutoFocusChange}
onThemeChange={onThemeChange}
onToggleNavTabsCollapse={toggleNavTabsCollapse}
onUniversalExpireTimerChange={onUniversalExpireTimerChange}
Expand Down
1 change: 1 addition & 0 deletions ts/types/Storage.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ export type StorageAccessType = {
pinnedConversationIds: ReadonlyArray<string>;
preferContactAvatars: boolean;
textFormatting: boolean;
typingAutoFocus: boolean;
typingIndicators: boolean;
sealedSenderIndicators: boolean;
storageFetchComplete: boolean;
Expand Down
1 change: 1 addition & 0 deletions ts/types/StorageUIKeys.std.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const STORAGE_UI_KEYS: ReadonlyArray<keyof StorageAccessType> = [
'showStickersIntroduction',
'emojiSkinToneDefault',
'textFormatting',
'typingAutoFocus',
'version',
'zoomFactor',
];