From 7542a42fa6b6a0b4f783c28b349e742eb1af69a9 Mon Sep 17 00:00:00 2001 From: William Grant Date: Mon, 26 Jun 2023 17:30:35 +1000 Subject: [PATCH 01/10] feat: started adding rtl support to composition input updated buttons, emoji panel, @mentions --- ts/components/basic/Flex.tsx | 2 +- .../conversation/SessionEmojiPanel.tsx | 6 +++- .../composition/CompositionBox.tsx | 30 +++++++++++++------ ts/state/selectors/user.ts | 5 ++++ 4 files changed, 32 insertions(+), 11 deletions(-) diff --git a/ts/components/basic/Flex.tsx b/ts/components/basic/Flex.tsx index 18386c20c..11b7db2c3 100644 --- a/ts/components/basic/Flex.tsx +++ b/ts/components/basic/Flex.tsx @@ -6,7 +6,7 @@ export interface FlexProps { container?: boolean; dataTestId?: string; /****** Container Props ********/ - flexDirection?: 'row' | 'column'; + flexDirection?: 'row' | 'row-reverse' | 'column' | 'column-reverse'; justifyContent?: | 'flex-start' | 'flex-end' diff --git a/ts/components/conversation/SessionEmojiPanel.tsx b/ts/components/conversation/SessionEmojiPanel.tsx index 315776103..109a1c8ba 100644 --- a/ts/components/conversation/SessionEmojiPanel.tsx +++ b/ts/components/conversation/SessionEmojiPanel.tsx @@ -16,8 +16,10 @@ import { import { hexColorToRGB } from '../../util/hexColorToRGB'; import { getPrimaryColor } from '../../state/selectors/primaryColor'; import { i18nEmojiData } from '../../util/emoji'; +import { getWritingDirection } from '../../state/selectors/user'; export const StyledEmojiPanel = styled.div<{ + dir: string; isModal: boolean; primaryColor: PrimaryColorStateType; theme: ThemeStateType; @@ -68,7 +70,7 @@ export const StyledEmojiPanel = styled.div<{ content: ''; position: absolute; top: calc(100% - 40px); - left: calc(100% - 79px); + left: ${props.dir === 'rtl' ? '75px' : 'calc(100% - 106px)'}; width: 22px; height: 22px; transform: rotate(45deg); @@ -102,6 +104,7 @@ export const SessionEmojiPanel = forwardRef((props: Props const primaryColor = useSelector(getPrimaryColor); const theme = useSelector(getTheme); const isDarkMode = useSelector(isDarkTheme); + const writingDirection = useSelector(getWritingDirection); let panelBackgroundRGB = hexColorToRGB(THEMES.CLASSIC_DARK.COLOR1); let panelTextRGB = hexColorToRGB(THEMES.CLASSIC_DARK.COLOR6); @@ -134,6 +137,7 @@ export const SessionEmojiPanel = forwardRef((props: Props theme={theme} panelBackgroundRGB={panelBackgroundRGB} panelTextRGB={panelTextRGB} + dir={writingDirection} className={classNames(show && 'show')} ref={ref} > diff --git a/ts/components/conversation/composition/CompositionBox.tsx b/ts/components/conversation/composition/CompositionBox.tsx index 7a0547e5e..c4488bda8 100644 --- a/ts/components/conversation/composition/CompositionBox.tsx +++ b/ts/components/conversation/composition/CompositionBox.tsx @@ -57,6 +57,7 @@ import { getSelectedConversationKey, } from '../../../state/selectors/selectedConversation'; import { SettingsKey } from '../../../data/settings-key'; +import { isRtlBody } from '../../menu/Menu'; export interface ReplyingToMessageProps { convoId: string; @@ -205,21 +206,22 @@ const getSelectionBasedOnMentions = (draft: string, index: number) => { return Number.MAX_SAFE_INTEGER; }; -const StyledEmojiPanelContainer = styled.div` +const StyledEmojiPanelContainer = styled.div<{ dir: string }>` ${StyledEmojiPanel} { position: absolute; bottom: 68px; - right: 0px; + ${props => (props.dir === 'rtl' ? 'left: 0px' : 'right: 0px;')} } `; -const StyledSendMessageInput = styled.div` +const StyledSendMessageInput = styled.div<{ dir: string }>` cursor: text; display: flex; align-items: center; flex-grow: 1; min-height: var(--composition-container-height); padding: var(--margins-xs) 0; + ${props => (props.dir = 'rtl' && 'margin-right: var(--margins-sm);')} z-index: 1; background-color: inherit; @@ -409,8 +411,16 @@ class CompositionBoxInner extends React.Component { const { showEmojiPanel } = this.state; const { typingEnabled } = this.props; + const rtl = isRtlBody(); + const writingDirection = rtl ? 'rtl' : 'ltr'; + return ( - <> + {typingEnabled && } { { this.container = el; }} data-testid="message-input" > - {this.renderTextArea()} + {this.renderTextArea(writingDirection)} {typingEnabled && ( @@ -441,7 +452,7 @@ class CompositionBoxInner extends React.Component { {typingEnabled && showEmojiPanel && ( - + { /> )} - + ); } - private renderTextArea() { + private renderTextArea(dir: string) { const { i18n } = window; const { draft } = this.state; @@ -491,6 +502,7 @@ class CompositionBoxInner extends React.Component { onKeyUp={this.onKeyUp} placeholder={messagePlaceHolder} spellCheck={true} + dir={dir} inputRef={this.textarea} disabled={!typingEnabled} rows={1} @@ -505,7 +517,7 @@ class CompositionBoxInner extends React.Component { markup="@ᅭ__id__ᅲ__display__ᅭ" // ᅭ = \uFFD2 is one of the forbidden char for a display name (check displayNameRegex) trigger="@" // this is only for the composition box visible content. The real stuff on the backend box is the @markup - displayTransform={(_id, display) => `@${display}`} + displayTransform={(_id, display) => (dir === 'rtl' ? `${display}@` : `@${display}`)} data={this.fetchUsersForGroup} renderSuggestion={renderUserMentionRow} /> diff --git a/ts/state/selectors/user.ts b/ts/state/selectors/user.ts index 69535cfda..400144cbc 100644 --- a/ts/state/selectors/user.ts +++ b/ts/state/selectors/user.ts @@ -4,6 +4,7 @@ import { LocalizerType } from '../../types/Util'; import { StateType } from '../reducer'; import { UserStateType } from '../ducks/user'; +import { isRtlBody } from '../../components/menu/Menu'; export const getUser = (state: StateType): UserStateType => state.user; @@ -13,3 +14,7 @@ export const getOurNumber = createSelector( ); export const getIntl = createSelector(getUser, (): LocalizerType => window.i18n); + +export const getWritingDirection = createSelector(getUser, (): string => + isRtlBody() ? 'rtl' : 'ltr' +); From 31c79f9eeabe885008384b1897eda6ec95a540d6 Mon Sep 17 00:00:00 2001 From: William Grant Date: Mon, 26 Jun 2023 17:30:52 +1000 Subject: [PATCH 02/10] feat: use LANGUAGE flag to change UI lang needs more testing --- ts/mains/main_node.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ts/mains/main_node.ts b/ts/mains/main_node.ts index c19373d52..3956cd127 100644 --- a/ts/mains/main_node.ts +++ b/ts/mains/main_node.ts @@ -745,8 +745,9 @@ app.on('ready', async () => { assertLogger().info('app ready'); assertLogger().info(`starting version ${packageJson.version}`); if (!locale) { - const appLocale = app.getLocale() || 'en'; + const appLocale = process.env.LANGUAGE || app.getLocale() || 'en'; locale = loadLocale({ appLocale, logger }); + assertLogger().info(`locale is ${appLocale}`); } const key = getDefaultSQLKey(); From 7be11cd9734ccd03065603d2909046e9c9640104 Mon Sep 17 00:00:00 2001 From: William Grant Date: Tue, 27 Jun 2023 11:06:39 +1000 Subject: [PATCH 03/10] feat: moved html direction logic into i18n util updated Flex component with RTL support, create getWritingDirection selector --- ts/components/basic/Flex.tsx | 4 ++++ ts/components/menu/Menu.tsx | 8 +------- ts/state/selectors/user.ts | 6 ++---- ts/util/i18n.ts | 14 ++++++++++++++ 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/ts/components/basic/Flex.tsx b/ts/components/basic/Flex.tsx index 11b7db2c3..b1c0ed51e 100644 --- a/ts/components/basic/Flex.tsx +++ b/ts/components/basic/Flex.tsx @@ -1,4 +1,5 @@ import styled from 'styled-components'; +import { HTMLDirection } from '../../util/i18n'; export interface FlexProps { children?: any; @@ -36,6 +37,8 @@ export interface FlexProps { maxWidth?: string; minWidth?: string; maxHeight?: string; + /****** RTL support ********/ + dir?: HTMLDirection; } export const Flex = styled.div` @@ -53,4 +56,5 @@ export const Flex = styled.div` height: ${props => props.height || 'auto'}; max-width: ${props => props.maxWidth || 'none'}; min-width: ${props => props.minWidth || 'none'}; + dir: ${props => props.dir || undefined}; `; diff --git a/ts/components/menu/Menu.tsx b/ts/components/menu/Menu.tsx index d1010df93..605e1c374 100644 --- a/ts/components/menu/Menu.tsx +++ b/ts/components/menu/Menu.tsx @@ -373,12 +373,6 @@ export const MarkAllReadMenuItem = (): JSX.Element | null => { } }; -export function isRtlBody(): boolean { - const body = document.getElementsByTagName('body').item(0); - - return body?.classList.contains('rtl') || false; -} - export const BlockMenuItem = (): JSX.Element | null => { const convoId = useConvoIdFromContext(); const isMe = useIsMe(convoId); @@ -577,7 +571,7 @@ export const NotificationForConvoMenuItem = (): JSX.Element | null => { return null; } - // const isRtlMode = isRtlBody();' + // const isRtlMode = isRtlBody(); // exclude mentions_only settings for private chats as this does not make much sense const notificationForConvoOptions = ConversationNotificationSetting.filter(n => diff --git a/ts/state/selectors/user.ts b/ts/state/selectors/user.ts index 400144cbc..91acbacfd 100644 --- a/ts/state/selectors/user.ts +++ b/ts/state/selectors/user.ts @@ -4,7 +4,7 @@ import { LocalizerType } from '../../types/Util'; import { StateType } from '../reducer'; import { UserStateType } from '../ducks/user'; -import { isRtlBody } from '../../components/menu/Menu'; +import { HTMLDirection, getHTMLDirection } from '../../util/i18n'; export const getUser = (state: StateType): UserStateType => state.user; @@ -15,6 +15,4 @@ export const getOurNumber = createSelector( export const getIntl = createSelector(getUser, (): LocalizerType => window.i18n); -export const getWritingDirection = createSelector(getUser, (): string => - isRtlBody() ? 'rtl' : 'ltr' -); +export const getWritingDirection = createSelector(getUser, (): HTMLDirection => getHTMLDirection()); diff --git a/ts/util/i18n.ts b/ts/util/i18n.ts index 90ccf33f2..1ad307250 100644 --- a/ts/util/i18n.ts +++ b/ts/util/i18n.ts @@ -63,3 +63,17 @@ export const loadEmojiPanelI18n = async () => { } } }; + +// RTL Support + +export type HTMLDirection = 'ltr' | 'rtl'; + +export function isRtlBody(): boolean { + const body = document.getElementsByTagName('body').item(0); + + return body?.classList.contains('rtl') || false; +} + +export function getHTMLDirection(): HTMLDirection { + return isRtlBody() ? 'rtl' : 'ltr'; +} From 0996c917f299297674d7d13e971c9fe57e0b8ec7 Mon Sep 17 00:00:00 2001 From: William Grant Date: Tue, 27 Jun 2023 11:44:16 +1000 Subject: [PATCH 04/10] feat: use new methods in composition box for html direction use dir in flexbox instead of row-reverse --- ts/components/conversation/SessionEmojiPanel.tsx | 10 +++++----- .../conversation/composition/CompositionBox.tsx | 14 +++++++------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/ts/components/conversation/SessionEmojiPanel.tsx b/ts/components/conversation/SessionEmojiPanel.tsx index 109a1c8ba..22c0fd1be 100644 --- a/ts/components/conversation/SessionEmojiPanel.tsx +++ b/ts/components/conversation/SessionEmojiPanel.tsx @@ -16,10 +16,8 @@ import { import { hexColorToRGB } from '../../util/hexColorToRGB'; import { getPrimaryColor } from '../../state/selectors/primaryColor'; import { i18nEmojiData } from '../../util/emoji'; -import { getWritingDirection } from '../../state/selectors/user'; export const StyledEmojiPanel = styled.div<{ - dir: string; isModal: boolean; primaryColor: PrimaryColorStateType; theme: ThemeStateType; @@ -70,7 +68,7 @@ export const StyledEmojiPanel = styled.div<{ content: ''; position: absolute; top: calc(100% - 40px); - left: ${props.dir === 'rtl' ? '75px' : 'calc(100% - 106px)'}; + left: calc(100% - 106px); width: 22px; height: 22px; transform: rotate(45deg); @@ -79,6 +77,10 @@ export const StyledEmojiPanel = styled.div<{ border: 0.7px solid var(--border-color); clip-path: polygon(100% 100%, 7.2px 100%, 100% 7.2px); ${props.panelBackgroundRGB && `background-color: rgb(${props.panelBackgroundRGB})`}; + + [dir='rtl'] & { + left: 75px; + } } `}; } @@ -104,7 +106,6 @@ export const SessionEmojiPanel = forwardRef((props: Props const primaryColor = useSelector(getPrimaryColor); const theme = useSelector(getTheme); const isDarkMode = useSelector(isDarkTheme); - const writingDirection = useSelector(getWritingDirection); let panelBackgroundRGB = hexColorToRGB(THEMES.CLASSIC_DARK.COLOR1); let panelTextRGB = hexColorToRGB(THEMES.CLASSIC_DARK.COLOR6); @@ -137,7 +138,6 @@ export const SessionEmojiPanel = forwardRef((props: Props theme={theme} panelBackgroundRGB={panelBackgroundRGB} panelTextRGB={panelTextRGB} - dir={writingDirection} className={classNames(show && 'show')} ref={ref} > diff --git a/ts/components/conversation/composition/CompositionBox.tsx b/ts/components/conversation/composition/CompositionBox.tsx index c4488bda8..efb222935 100644 --- a/ts/components/conversation/composition/CompositionBox.tsx +++ b/ts/components/conversation/composition/CompositionBox.tsx @@ -57,7 +57,7 @@ import { getSelectedConversationKey, } from '../../../state/selectors/selectedConversation'; import { SettingsKey } from '../../../data/settings-key'; -import { isRtlBody } from '../../menu/Menu'; +import { getHTMLDirection } from '../../../util/i18n'; export interface ReplyingToMessageProps { convoId: string; @@ -411,13 +411,13 @@ class CompositionBoxInner extends React.Component { const { showEmojiPanel } = this.state; const { typingEnabled } = this.props; - const rtl = isRtlBody(); - const writingDirection = rtl ? 'rtl' : 'ltr'; + const htmlDirection = getHTMLDirection(); return ( @@ -436,14 +436,14 @@ class CompositionBoxInner extends React.Component { { this.container = el; }} data-testid="message-input" > - {this.renderTextArea(writingDirection)} + {this.renderTextArea(htmlDirection)} {typingEnabled && ( @@ -452,7 +452,7 @@ class CompositionBoxInner extends React.Component { {typingEnabled && showEmojiPanel && ( - + Date: Tue, 27 Jun 2023 14:17:01 +1000 Subject: [PATCH 05/10] fix: use margin-inline-start instead of margin-right --- ts/components/conversation/composition/CompositionBox.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/components/conversation/composition/CompositionBox.tsx b/ts/components/conversation/composition/CompositionBox.tsx index efb222935..95e09dbd2 100644 --- a/ts/components/conversation/composition/CompositionBox.tsx +++ b/ts/components/conversation/composition/CompositionBox.tsx @@ -221,7 +221,7 @@ const StyledSendMessageInput = styled.div<{ dir: string }>` flex-grow: 1; min-height: var(--composition-container-height); padding: var(--margins-xs) 0; - ${props => (props.dir = 'rtl' && 'margin-right: var(--margins-sm);')} + ${props => props.dir === 'rtl' && 'margin-inline-start: var(--margins-sm);'} z-index: 1; background-color: inherit; From 1f1bb702c3211971be84fd28d06444c03a288ecd Mon Sep 17 00:00:00 2001 From: William Grant Date: Tue, 27 Jun 2023 16:49:08 +1000 Subject: [PATCH 06/10] feat: made htmlDirection part of the compositionBox state for easier referencing user mentions and emoji quick results now support RTL --- .../composition/CompositionBox.tsx | 77 ++++++++++--------- .../composition/EmojiQuickResult.tsx | 1 + .../conversation/composition/UserMentions.tsx | 48 +++++++----- 3 files changed, 73 insertions(+), 53 deletions(-) diff --git a/ts/components/conversation/composition/CompositionBox.tsx b/ts/components/conversation/composition/CompositionBox.tsx index 95e09dbd2..1e4ee6c39 100644 --- a/ts/components/conversation/composition/CompositionBox.tsx +++ b/ts/components/conversation/composition/CompositionBox.tsx @@ -57,7 +57,7 @@ import { getSelectedConversationKey, } from '../../../state/selectors/selectedConversation'; import { SettingsKey } from '../../../data/settings-key'; -import { getHTMLDirection } from '../../../util/i18n'; +import { getHTMLDirection, HTMLDirection } from '../../../util/i18n'; export interface ReplyingToMessageProps { convoId: string; @@ -114,28 +114,31 @@ interface State { ignoredLink?: string; // set the ignored url when users closed the link preview stagedLinkPreview?: StagedLinkPreviewData; showCaptionEditor?: AttachmentType; + htmlDirection?: HTMLDirection; } -const sendMessageStyle = { - control: { - wordBreak: 'break-all', - }, - input: { - overflow: 'auto', - maxHeight: '50vh', - wordBreak: 'break-word', - padding: '0px', - margin: '0px', - }, - highlighter: { - boxSizing: 'border-box', - overflow: 'hidden', - maxHeight: '50vh', - }, - flexGrow: 1, - minHeight: '24px', - width: '100%', - ...styleForCompositionBoxSuggestions, +const sendMessageStyle = (dir: HTMLDirection) => { + return { + control: { + wordBreak: 'break-all', + }, + input: { + overflow: 'auto', + maxHeight: '50vh', + wordBreak: 'break-word', + padding: '0px', + margin: '0px', + }, + highlighter: { + boxSizing: 'border-box', + overflow: 'hidden', + maxHeight: '50vh', + }, + flexGrow: 1, + minHeight: '24px', + width: '100%', + ...styleForCompositionBoxSuggestions(dir), + }; }; const getDefaultState = (newConvoId?: string) => { @@ -146,6 +149,7 @@ const getDefaultState = (newConvoId?: string) => { ignoredLink: undefined, stagedLinkPreview: undefined, showCaptionEditor: undefined, + htmlDirection: getHTMLDirection(), }; }; @@ -206,7 +210,7 @@ const getSelectionBasedOnMentions = (draft: string, index: number) => { return Number.MAX_SAFE_INTEGER; }; -const StyledEmojiPanelContainer = styled.div<{ dir: string }>` +const StyledEmojiPanelContainer = styled.div<{ dir?: HTMLDirection }>` ${StyledEmojiPanel} { position: absolute; bottom: 68px; @@ -214,7 +218,8 @@ const StyledEmojiPanelContainer = styled.div<{ dir: string }>` } `; -const StyledSendMessageInput = styled.div<{ dir: string }>` +const StyledSendMessageInput = styled.div<{ dir?: HTMLDirection }>` + position: relative; cursor: text; display: flex; align-items: center; @@ -233,7 +238,7 @@ const StyledSendMessageInput = styled.div<{ dir: string }>` textarea { font-family: var(--font-default); min-height: calc(var(--composition-container-height) / 3); - max-height: 3 * var(--composition-container-height); + max-height: calc(3 * var(--composition-container-height)); margin-right: var(--margins-md); color: var(--text-color-primary); @@ -411,11 +416,9 @@ class CompositionBoxInner extends React.Component { const { showEmojiPanel } = this.state; const { typingEnabled } = this.props; - const htmlDirection = getHTMLDirection(); - return ( { { this.container = el; }} data-testid="message-input" > - {this.renderTextArea(htmlDirection)} + {this.renderTextArea()} {typingEnabled && ( @@ -452,7 +455,7 @@ class CompositionBoxInner extends React.Component { {typingEnabled && showEmojiPanel && ( - + { ); } - private renderTextArea(dir: string) { + private renderTextArea() { const { i18n } = window; - const { draft } = this.state; + const { draft, htmlDirection } = this.state; if (!this.props.selectedConversation) { return null; @@ -494,6 +497,8 @@ class CompositionBoxInner extends React.Component { const { typingEnabled } = this.props; const neverMatchingRegex = /($a)/; + const style = sendMessageStyle(htmlDirection || 'ltr'); + return ( { onKeyUp={this.onKeyUp} placeholder={messagePlaceHolder} spellCheck={true} - dir={dir} + dir={htmlDirection} inputRef={this.textarea} disabled={!typingEnabled} rows={1} data-testid="message-input-text-area" - style={sendMessageStyle} + style={style} suggestionsPortalHost={this.container as any} forceSuggestionsAboveCursor={true} // force mentions to be rendered on top of the cursor, this is working with a fork of react-mentions for now > @@ -517,7 +522,9 @@ class CompositionBoxInner extends React.Component { markup="@ᅭ__id__ᅲ__display__ᅭ" // ᅭ = \uFFD2 is one of the forbidden char for a display name (check displayNameRegex) trigger="@" // this is only for the composition box visible content. The real stuff on the backend box is the @markup - displayTransform={(_id, display) => (dir === 'rtl' ? `${display}@` : `@${display}`)} + displayTransform={(_id, display) => + htmlDirection === 'rtl' ? `${display}@` : `@${display}` + } data={this.fetchUsersForGroup} renderSuggestion={renderUserMentionRow} /> diff --git a/ts/components/conversation/composition/EmojiQuickResult.tsx b/ts/components/conversation/composition/EmojiQuickResult.tsx index c1c1e3b51..5f359b2d5 100644 --- a/ts/components/conversation/composition/EmojiQuickResult.tsx +++ b/ts/components/conversation/composition/EmojiQuickResult.tsx @@ -8,6 +8,7 @@ import { searchSync } from '../../../util/emoji.js'; const EmojiQuickResult = styled.span` display: flex; align-items: center; + min-width: 250px; width: 100%; padding-inline-end: 20px; padding-inline-start: 10px; diff --git a/ts/components/conversation/composition/UserMentions.tsx b/ts/components/conversation/composition/UserMentions.tsx index 09f18ab0a..badc3bf76 100644 --- a/ts/components/conversation/composition/UserMentions.tsx +++ b/ts/components/conversation/composition/UserMentions.tsx @@ -1,28 +1,40 @@ import React from 'react'; import { SuggestionDataItem } from 'react-mentions'; import { MemberListItem } from '../../MemberListItem'; +import { HTMLDirection } from '../../../util/i18n'; -export const styleForCompositionBoxSuggestions = { - suggestions: { - list: { - fontSize: 14, - boxShadow: 'var(--suggestions-shadow)', - backgroundColor: 'var(--suggestions-background-color)', - color: 'var(--suggestions-text-color)', - }, - item: { - height: '100%', - paddingTop: '5px', - paddingBottom: '5px', - backgroundColor: 'var(--suggestions-background-color)', - color: 'var(--suggestions-text-color)', - transition: '0.25s', +const listRTLStyle = { position: 'absolute', bottom: '0px', right: '100%' }; - '&focused': { - backgroundColor: 'var(--suggestions-background-hover-color)', +export const styleForCompositionBoxSuggestions = (dir: HTMLDirection) => { + const styles = { + suggestions: { + list: { + fontSize: 14, + boxShadow: 'var(--suggestions-shadow)', + backgroundColor: 'var(--suggestions-background-color)', + color: 'var(--suggestions-text-color)', + dir, + }, + item: { + height: '100%', + paddingTop: '5px', + paddingBottom: '5px', + backgroundColor: 'var(--suggestions-background-color)', + color: 'var(--suggestions-text-color)', + transition: '0.25s', + + '&focused': { + backgroundColor: 'var(--suggestions-background-hover-color)', + }, }, }, - }, + }; + + if (dir === 'rtl') { + styles.suggestions.list = { ...styles.suggestions.list, ...listRTLStyle }; + } + + return styles; }; export const renderUserMentionRow = (suggestion: SuggestionDataItem) => { From 266a0d696456351b3f5d7d38b56fa8cedae964a8 Mon Sep 17 00:00:00 2001 From: William Grant Date: Tue, 27 Jun 2023 17:10:51 +1000 Subject: [PATCH 07/10] fix: cleaned up sendMessageStyle arguments --- ts/components/conversation/composition/CompositionBox.tsx | 4 ++-- ts/components/conversation/composition/UserMentions.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ts/components/conversation/composition/CompositionBox.tsx b/ts/components/conversation/composition/CompositionBox.tsx index 1e4ee6c39..c117a109c 100644 --- a/ts/components/conversation/composition/CompositionBox.tsx +++ b/ts/components/conversation/composition/CompositionBox.tsx @@ -117,7 +117,7 @@ interface State { htmlDirection?: HTMLDirection; } -const sendMessageStyle = (dir: HTMLDirection) => { +const sendMessageStyle = (dir?: HTMLDirection) => { return { control: { wordBreak: 'break-all', @@ -497,7 +497,7 @@ class CompositionBoxInner extends React.Component { const { typingEnabled } = this.props; const neverMatchingRegex = /($a)/; - const style = sendMessageStyle(htmlDirection || 'ltr'); + const style = sendMessageStyle(htmlDirection); return ( { +export const styleForCompositionBoxSuggestions = (dir: HTMLDirection = 'ltr') => { const styles = { suggestions: { list: { From ac4a00d41506c0e1cb3158c03dd6c523a593512c Mon Sep 17 00:00:00 2001 From: William Grant Date: Tue, 27 Jun 2023 17:17:24 +1000 Subject: [PATCH 08/10] fix: remove unused selector --- ts/state/selectors/user.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/ts/state/selectors/user.ts b/ts/state/selectors/user.ts index 91acbacfd..69535cfda 100644 --- a/ts/state/selectors/user.ts +++ b/ts/state/selectors/user.ts @@ -4,7 +4,6 @@ import { LocalizerType } from '../../types/Util'; import { StateType } from '../reducer'; import { UserStateType } from '../ducks/user'; -import { HTMLDirection, getHTMLDirection } from '../../util/i18n'; export const getUser = (state: StateType): UserStateType => state.user; @@ -14,5 +13,3 @@ export const getOurNumber = createSelector( ); export const getIntl = createSelector(getUser, (): LocalizerType => window.i18n); - -export const getWritingDirection = createSelector(getUser, (): HTMLDirection => getHTMLDirection()); From 61149a5ca343dc02506fe5075fda209a1ab7f579 Mon Sep 17 00:00:00 2001 From: William Grant Date: Wed, 28 Jun 2023 16:21:06 +1000 Subject: [PATCH 09/10] feat: use a selector for htmlDirection and pass it down as props instead of using state this is more inline with class component conventions --- ts/components/conversation/SessionConversation.tsx | 3 +++ .../conversation/composition/CompositionBox.tsx | 14 +++++++------- ts/state/selectors/user.ts | 6 ++++++ ts/state/smart/SessionConversation.ts | 3 ++- ts/util/i18n.ts | 4 ---- 5 files changed, 18 insertions(+), 12 deletions(-) diff --git a/ts/components/conversation/SessionConversation.tsx b/ts/components/conversation/SessionConversation.tsx index e8db1d73a..55b97678a 100644 --- a/ts/components/conversation/SessionConversation.tsx +++ b/ts/components/conversation/SessionConversation.tsx @@ -57,6 +57,7 @@ import { MessageDetail } from './message/message-item/MessageDetail'; import styled from 'styled-components'; import { SessionSpinner } from '../basic/SessionSpinner'; +import { HTMLDirection } from '../../util/i18n'; // tslint:disable: jsx-curly-spacing interface State { @@ -76,6 +77,7 @@ interface Props { showMessageDetails: boolean; isRightPanelShowing: boolean; hasOngoingCallWithFocusedConvo: boolean; + htmlDirection: HTMLDirection; // lightbox options lightBoxOptions?: LightBoxOptions; @@ -290,6 +292,7 @@ export class SessionConversation extends React.Component { sendMessage={this.sendMessageFn} stagedAttachments={this.props.stagedAttachments} onChoseAttachments={this.onChoseAttachments} + htmlDirection={this.props.htmlDirection} />
; onChoseAttachments: (newAttachments: Array) => void; + htmlDirection: HTMLDirection; } interface State { @@ -114,7 +115,6 @@ interface State { ignoredLink?: string; // set the ignored url when users closed the link preview stagedLinkPreview?: StagedLinkPreviewData; showCaptionEditor?: AttachmentType; - htmlDirection?: HTMLDirection; } const sendMessageStyle = (dir?: HTMLDirection) => { @@ -149,7 +149,6 @@ const getDefaultState = (newConvoId?: string) => { ignoredLink: undefined, stagedLinkPreview: undefined, showCaptionEditor: undefined, - htmlDirection: getHTMLDirection(), }; }; @@ -418,7 +417,7 @@ class CompositionBoxInner extends React.Component { return ( { { this.container = el; @@ -455,7 +454,7 @@ class CompositionBoxInner extends React.Component { {typingEnabled && showEmojiPanel && ( - + { private renderTextArea() { const { i18n } = window; - const { draft, htmlDirection } = this.state; + const { draft } = this.state; + const { htmlDirection } = this.props; if (!this.props.selectedConversation) { return null; diff --git a/ts/state/selectors/user.ts b/ts/state/selectors/user.ts index 69535cfda..c4a75a5de 100644 --- a/ts/state/selectors/user.ts +++ b/ts/state/selectors/user.ts @@ -4,6 +4,7 @@ import { LocalizerType } from '../../types/Util'; import { StateType } from '../reducer'; import { UserStateType } from '../ducks/user'; +import { HTMLDirection, isRtlBody } from '../../util/i18n'; export const getUser = (state: StateType): UserStateType => state.user; @@ -13,3 +14,8 @@ export const getOurNumber = createSelector( ); export const getIntl = createSelector(getUser, (): LocalizerType => window.i18n); + +export const getHTMLDirection = createSelector( + getUser, + (): HTMLDirection => (isRtlBody() ? 'rtl' : 'ltr') +); diff --git a/ts/state/smart/SessionConversation.ts b/ts/state/smart/SessionConversation.ts index c5850f2f1..09d1b5174 100644 --- a/ts/state/smart/SessionConversation.ts +++ b/ts/state/smart/SessionConversation.ts @@ -17,7 +17,7 @@ import { } from '../selectors/selectedConversation'; import { getStagedAttachmentsForCurrentConversation } from '../selectors/stagedAttachments'; import { getTheme } from '../selectors/theme'; -import { getOurNumber } from '../selectors/user'; +import { getHTMLDirection, getOurNumber } from '../selectors/user'; const mapStateToProps = (state: StateType) => { return { @@ -33,6 +33,7 @@ const mapStateToProps = (state: StateType) => { stagedAttachments: getStagedAttachmentsForCurrentConversation(state), hasOngoingCallWithFocusedConvo: getHasOngoingCallWithFocusedConvo(state), isSelectedConvoInitialLoadingInProgress: getIsSelectedConvoInitialLoadingInProgress(state), + htmlDirection: getHTMLDirection(state), }; }; diff --git a/ts/util/i18n.ts b/ts/util/i18n.ts index 1ad307250..135f1d48e 100644 --- a/ts/util/i18n.ts +++ b/ts/util/i18n.ts @@ -73,7 +73,3 @@ export function isRtlBody(): boolean { return body?.classList.contains('rtl') || false; } - -export function getHTMLDirection(): HTMLDirection { - return isRtlBody() ? 'rtl' : 'ltr'; -} From 83f84c26e7da00e7f27f04ba021f02b7aeeb1e33 Mon Sep 17 00:00:00 2001 From: William Grant Date: Thu, 29 Jun 2023 11:41:59 +1000 Subject: [PATCH 10/10] feat: changed getHTMLDirection into a util hook useHTMLDirection --- ts/components/SessionMainPanel.tsx | 4 +++- ts/state/selectors/user.ts | 6 ------ ts/state/smart/SessionConversation.ts | 11 ++++++++--- ts/util/i18n.ts | 2 ++ 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/ts/components/SessionMainPanel.tsx b/ts/components/SessionMainPanel.tsx index 035275712..d5d383331 100644 --- a/ts/components/SessionMainPanel.tsx +++ b/ts/components/SessionMainPanel.tsx @@ -5,12 +5,14 @@ import { getFocusedSettingsSection } from '../state/selectors/section'; import { SmartSessionConversation } from '../state/smart/SessionConversation'; import { SessionSettingsView } from './settings/SessionSettings'; +import { useHTMLDirection } from '../util/i18n'; const FilteredSettingsView = SessionSettingsView as any; export const SessionMainPanel = () => { const focusedSettingsSection = useSelector(getFocusedSettingsSection); const isSettingsView = focusedSettingsSection !== undefined; + const htmlDirection = useHTMLDirection(); // even if it looks like this does nothing, this does update the redux store. useAppIsFocused(); @@ -20,7 +22,7 @@ export const SessionMainPanel = () => { } return (
- +
); }; diff --git a/ts/state/selectors/user.ts b/ts/state/selectors/user.ts index c4a75a5de..69535cfda 100644 --- a/ts/state/selectors/user.ts +++ b/ts/state/selectors/user.ts @@ -4,7 +4,6 @@ import { LocalizerType } from '../../types/Util'; import { StateType } from '../reducer'; import { UserStateType } from '../ducks/user'; -import { HTMLDirection, isRtlBody } from '../../util/i18n'; export const getUser = (state: StateType): UserStateType => state.user; @@ -14,8 +13,3 @@ export const getOurNumber = createSelector( ); export const getIntl = createSelector(getUser, (): LocalizerType => window.i18n); - -export const getHTMLDirection = createSelector( - getUser, - (): HTMLDirection => (isRtlBody() ? 'rtl' : 'ltr') -); diff --git a/ts/state/smart/SessionConversation.ts b/ts/state/smart/SessionConversation.ts index 09d1b5174..39809f652 100644 --- a/ts/state/smart/SessionConversation.ts +++ b/ts/state/smart/SessionConversation.ts @@ -17,9 +17,14 @@ import { } from '../selectors/selectedConversation'; import { getStagedAttachmentsForCurrentConversation } from '../selectors/stagedAttachments'; import { getTheme } from '../selectors/theme'; -import { getHTMLDirection, getOurNumber } from '../selectors/user'; +import { getOurNumber } from '../selectors/user'; +import { HTMLDirection } from '../../util/i18n'; -const mapStateToProps = (state: StateType) => { +type SmartSessionConversationOwnProps = { + htmlDirection: HTMLDirection; +}; + +const mapStateToProps = (state: StateType, ownProps: SmartSessionConversationOwnProps) => { return { selectedConversation: getSelectedConversation(state), selectedConversationKey: getSelectedConversationKey(state), @@ -33,7 +38,7 @@ const mapStateToProps = (state: StateType) => { stagedAttachments: getStagedAttachmentsForCurrentConversation(state), hasOngoingCallWithFocusedConvo: getHasOngoingCallWithFocusedConvo(state), isSelectedConvoInitialLoadingInProgress: getIsSelectedConvoInitialLoadingInProgress(state), - htmlDirection: getHTMLDirection(state), + htmlDirection: ownProps.htmlDirection, }; }; diff --git a/ts/util/i18n.ts b/ts/util/i18n.ts index 135f1d48e..7b77826c4 100644 --- a/ts/util/i18n.ts +++ b/ts/util/i18n.ts @@ -73,3 +73,5 @@ export function isRtlBody(): boolean { return body?.classList.contains('rtl') || false; } + +export const useHTMLDirection = (): HTMLDirection => (isRtlBody() ? 'rtl' : 'ltr');