Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -2865,6 +2865,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"
Comment thread
AlexProgrammerDE marked this conversation as resolved.
},
"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,
messageCompositionId: '456',
sendEditedMessage: action('sendEditedMessage'),
sendMultiMediaMessage: action('sendMultiMediaMessage'),
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 @@ -123,6 +123,7 @@ export type OwnProps = Readonly<{
isDisabled: boolean;
isFetchingUUID: boolean | null;
isFormattingEnabled: boolean;
isTypingAutoFocusEnabled: boolean;
isGroupV1AndDisabled: boolean | null;
isMissingMandatoryProfileSharing: boolean | null;
isSignalConversation: boolean | null;
Expand Down Expand Up @@ -290,6 +291,7 @@ export const CompositionArea = memo(function CompositionArea({
draftText,
getPreferredBadge,
isFormattingEnabled,
isTypingAutoFocusEnabled,
onEditorStateChange,
onTextTooLong,
ourConversationId,
Expand Down Expand Up @@ -496,6 +498,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;
}

Comment thread
AlexProgrammerDE marked this conversation as resolved.
// 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();
Comment thread
AlexProgrammerDE marked this conversation as resolved.
},
[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 @@ -156,6 +156,7 @@ export type PropsDataType = {
hasSpellCheck: boolean | undefined;
hasStoriesDisabled: boolean;
hasTextFormatting: boolean;
hasTypingAutoFocus: boolean;
hasTypingIndicators: boolean;
hasKeepMutedChatsArchived: boolean;
settingsLocation: SettingsLocation;
Expand Down Expand Up @@ -326,6 +327,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 @@ -433,6 +435,7 @@ export function Preferences({
hasSpellCheck,
hasStoriesDisabled,
hasTextFormatting,
hasTypingAutoFocus,
hasTypingIndicators,
hasKeepMutedChatsArchived,
i18n,
Expand Down Expand Up @@ -490,6 +493,7 @@ export function Preferences({
onSentMediaQualityChange,
onSpellCheckChange,
onTextFormattingChange,
onTypingAutoFocusChange,
onThemeChange,
onToggleNavTabsCollapse,
onUniversalExpireTimerChange,
Expand Down Expand Up @@ -1149,6 +1153,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 @@ -37,6 +37,7 @@ import {
getDefaultConversationColor,
getEmojiSkinToneDefault,
getTextFormattingEnabled,
getTypingAutoFocus,
} from '../selectors/items.dom.js';
import { canForward, getPropsForQuote } from '../selectors/message.preload.js';
import {
Expand Down Expand Up @@ -85,6 +86,7 @@ export const SmartCompositionArea = memo(function SmartCompositionArea({
const selectedMessageIds = useSelector(getSelectedMessageIds);
const messageLookup = useSelector(getMessages);
const isFormattingEnabled = useSelector(getTextFormattingEnabled);
const isTypingAutoFocusEnabled = useSelector(getTypingAutoFocus);
const lastEditableMessageId = useSelector(getLastEditableMessageId);
const platform = useSelector(getPlatform);
const shouldHidePopovers = useSelector(getHasPanelOpen);
Expand Down Expand Up @@ -242,6 +244,7 @@ export const SmartCompositionArea = memo(function SmartCompositionArea({
i18n={i18n}
isDisabled={isDisabled}
isFormattingEnabled={isFormattingEnabled}
isTypingAutoFocusEnabled={isTypingAutoFocusEnabled}
isActive={isActive}
lastEditableMessageId={lastEditableMessageId ?? null}
messageCompositionId={messageCompositionId}
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 @@ -707,6 +707,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 @@ -866,6 +870,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 @@ -930,6 +935,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 @@ -142,6 +142,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 @@ -39,6 +39,7 @@ export const STORAGE_UI_KEYS: ReadonlyArray<keyof StorageAccessType> = [
'showStickersIntroduction',
'emojiSkinToneDefault',
'textFormatting',
'typingAutoFocus',
'version',
'zoomFactor',
];
Loading