From b4ef6b72d0a10f36247d5196ec8937c70a4f0d47 Mon Sep 17 00:00:00 2001 From: Olaf Sulich Date: Fri, 14 Feb 2025 13:12:32 +0100 Subject: [PATCH 01/13] refactor(InputBar): split logic into hooks and components --- .../InputBar/FileInput/FileInput.tsx | 36 + src/script/components/InputBar/InputBar.tsx | 704 +++++------------- .../InputBarAvatar/InputBarAvatar.tsx | 45 ++ .../InputBarButtons/InputBarButtons.tsx | 97 +++ .../AssetUploadButton.test.tsx | 0 .../AssetUploadButton/AssetUploadButton.tsx | 0 .../AssetUploadButton/index.ts | 0 .../CancelEditButton/CancelEditButton.tsx | 0 .../InputBarControls/ControlButtons.test.tsx | 0 .../InputBarControls/ControlButtons.tsx | 13 +- .../EmojiButton/EmojiButton.tsx | 0 .../FormatTextButton/FormatTextButton.tsx | 0 .../GiphyButton/GiphyButton.tsx | 0 .../ImageUploadButton.test.tsx | 0 .../ImageUploadButton/ImageUploadButton.tsx | 0 .../ImageUploadButton/index.ts | 0 .../MessageTimerButton.test.tsx | 0 .../MessageTimerButton/MessageTimerButton.tsx | 0 .../MessageTimerButton/index.ts | 0 .../PingButton/PingButton.tsx | 0 .../InputBarEditor/InputBarEditor.tsx | 95 +++ .../PastedFileControls/PastedFileControls.tsx | 6 +- .../{components => }/ReplyBar/ReplyBar.tsx | 8 +- .../FormatButton/FormatButton.tsx | 0 .../FormatToolbar/FormatToolbar.styles.ts | 0 .../FormatToolbar/FormatToolbar.tsx | 2 +- .../LinkDialog/LinkDialog.styles.ts | 0 .../FormatToolbar/LinkDialog/LinkDialog.tsx | 0 .../isBlockquoteNode/isBlockquoteNode.test.ts | 0 .../isBlockquoteNode/isBlockquoteNode.ts | 0 .../isCodeBlockNode/isCodeBlockNode.test.ts | 0 .../common/isCodeBlockNode/isCodeBlockNode.ts | 0 .../isHeadingNode/isHeadingNode.test.ts | 0 .../common/isHeadingNode/isHeadingNode.ts | 0 .../common/isListNode/isListNode.test.ts | 0 .../common/isListNode/isListNode.ts | 0 .../useBlockquoteState/useBlockquoteState.ts | 0 .../useCodeBlockState/useCodeBlockState.ts | 0 .../useHeadingState/headingCommand.ts | 0 .../useHeadingState/useHeadingState.ts | 0 .../createNewLink/createNewLink.ts | 0 .../getSelectedNode/getSelectedNode.ts | 0 .../useLinkEditing/useLinkEditing.ts | 0 .../useLinkState/useLinkState.ts | 0 .../useModalState/useModalState.ts | 0 .../useListState/useListState.ts | 0 .../useToolbarState/useToolbarState.ts | 0 .../Placeholder/Placeholder.tsx | 0 .../RichTextEditor/RichTextEditor.tsx | 10 +- .../SendMessageButton/SendMessageButton.tsx | 0 .../SendMessageButton/index.ts | 0 .../RichTextEditor/editorConfig.ts | 0 .../{components => }/RichTextEditor/index.ts | 0 .../RichTextEditor/nodes/EmojiNode.ts | 0 .../RichTextEditor/nodes/Mention.tsx | 0 .../RichTextEditor/nodes/MentionNode.tsx | 0 .../AutoFocusPlugin/AutoFocusPlugin.tsx | 0 .../plugins/AutoLinkPlugin/AutoLinkPlugin.tsx | 0 .../BlockquotePlugin/BlockquotePlugin.tsx | 0 .../CodeHighlightPlugin.tsx | 0 .../DraftStatePlugin/DraftStatePlugin.tsx | 0 .../EditedMessagePlugin.tsx | 0 .../getMentionMarkdownTransformer.ts | 0 .../getMentionNodesFromMessage.ts | 0 .../getRawMarkdownFromMessage.ts | 0 .../wrapMentionsWithTags.ts | 0 .../EmojiPickerPlugin/EmojiItem.styles.ts | 0 .../plugins/EmojiPickerPlugin/EmojiItem.tsx | 0 .../EmojiPickerPlugin/EmojiPickerPlugin.tsx | 0 .../plugins/EmojiPickerPlugin/index.ts | 0 .../GlobalEventsPlugin/GlobalEventsPlugin.tsx | 0 .../plugins/HistoryPlugin/HistoryPlugin.tsx | 0 .../InlineEmojiReplacementPlugin.tsx | 0 .../InlineEmojiReplacementPlugin/index.ts | 0 .../inlineReplacements.ts | 0 .../plugins/LinkPlugin/LinkPlugin.tsx | 0 .../ListIndentationPlugin.ts | 0 .../ListMaxIndentLevelPlugin.tsx | 0 .../MentionsPlugin/MentionSuggestionsItem.tsx | 0 .../plugins/MentionsPlugin/MentionsPlugin.tsx | 0 .../plugins/MentionsPlugin/index.ts | 0 .../plugins/PastePlugin/PastePlugin.tsx | 0 .../ReplaceCarriageReturnPlugin.ts | 0 .../plugins/SendPlugin/SendPlugin.tsx | 0 .../TypeaheadMenuPlugin.tsx | 0 .../{components => }/RichTextEditor/theme.ts | 0 .../RichTextEditor/utils/generateNodes.ts | 0 .../RichTextEditor/utils/getDomRangeRect.ts | 0 .../RichTextEditor/utils/getSelectionInfo.ts | 0 .../utils/markdownTransformers.ts | 0 .../RichTextEditor/utils/parseMentions.ts | 0 .../RichTextEditor/utils/transformMessage.ts | 0 .../RichTextEditor/utils/url.ts | 0 .../utils/useEditorDraftState.ts | 0 .../TypingIndicator/TypingIndicator.styles.ts | 0 .../TypingIndicator/TypingIndicator.test.tsx | 2 +- .../TypingIndicator/TypingIndicator.tsx | 8 +- .../{components => }/TypingIndicator/index.ts | 2 +- .../useTypingIndicatorState.ts} | 4 +- .../FormatSeparator/FormatSeparator.tsx | 0 .../InputBar/useDraftState/useDraftState.ts | 65 ++ .../useEmojiPicker/useEmojiPicker.ts | 0 .../useFileHandling/useFileHandling.ts | 84 +++ .../useFilePaste/useFilePaste.test.ts | 0 .../{hooks => }/useFilePaste/useFilePaste.ts | 0 .../useFormatToolbar/useFormatToolbar.ts | 0 .../components/InputBar/useGiphy/useGiphy.ts | 43 ++ .../useMessageHandling/useMessageHandling.ts | 240 ++++++ .../components/InputBar/usePing/usePing.ts | 77 ++ .../useTypingIndicator.test.ts | 0 .../useTypingIndicator/useTypingIndicator.ts | 2 +- 111 files changed, 987 insertions(+), 556 deletions(-) create mode 100644 src/script/components/InputBar/FileInput/FileInput.tsx create mode 100644 src/script/components/InputBar/InputBarAvatar/InputBarAvatar.tsx create mode 100644 src/script/components/InputBar/InputBarButtons/InputBarButtons.tsx rename src/script/components/InputBar/{components => InputBarControls}/AssetUploadButton/AssetUploadButton.test.tsx (100%) rename src/script/components/InputBar/{components => InputBarControls}/AssetUploadButton/AssetUploadButton.tsx (100%) rename src/script/components/InputBar/{components => InputBarControls}/AssetUploadButton/index.ts (100%) rename src/script/components/InputBar/{components => }/InputBarControls/CancelEditButton/CancelEditButton.tsx (100%) rename src/script/components/InputBar/{components => }/InputBarControls/ControlButtons.test.tsx (100%) rename src/script/components/InputBar/{components => }/InputBarControls/ControlButtons.tsx (92%) rename src/script/components/InputBar/{components => }/InputBarControls/EmojiButton/EmojiButton.tsx (100%) rename src/script/components/InputBar/{components => }/InputBarControls/FormatTextButton/FormatTextButton.tsx (100%) rename src/script/components/InputBar/{components => }/InputBarControls/GiphyButton/GiphyButton.tsx (100%) rename src/script/components/InputBar/{components => InputBarControls}/ImageUploadButton/ImageUploadButton.test.tsx (100%) rename src/script/components/InputBar/{components => InputBarControls}/ImageUploadButton/ImageUploadButton.tsx (100%) rename src/script/components/InputBar/{components => InputBarControls}/ImageUploadButton/index.ts (100%) rename src/script/components/InputBar/{components => InputBarControls}/MessageTimerButton/MessageTimerButton.test.tsx (100%) rename src/script/components/InputBar/{components => InputBarControls}/MessageTimerButton/MessageTimerButton.tsx (100%) rename src/script/components/InputBar/{components => InputBarControls}/MessageTimerButton/index.ts (100%) rename src/script/components/InputBar/{components => }/InputBarControls/PingButton/PingButton.tsx (100%) create mode 100644 src/script/components/InputBar/InputBarEditor/InputBarEditor.tsx rename src/script/components/InputBar/{components => }/PastedFileControls/PastedFileControls.tsx (93%) rename src/script/components/InputBar/{components => }/ReplyBar/ReplyBar.tsx (95%) rename src/script/components/InputBar/{components/RichTextEditor/components => RichTextEditor}/FormatToolbar/FormatButton/FormatButton.tsx (100%) rename src/script/components/InputBar/{components/RichTextEditor/components => RichTextEditor}/FormatToolbar/FormatToolbar.styles.ts (100%) rename src/script/components/InputBar/{components/RichTextEditor/components => RichTextEditor}/FormatToolbar/FormatToolbar.tsx (98%) rename src/script/components/InputBar/{components/RichTextEditor/components => RichTextEditor}/FormatToolbar/LinkDialog/LinkDialog.styles.ts (100%) rename src/script/components/InputBar/{components/RichTextEditor/components => RichTextEditor}/FormatToolbar/LinkDialog/LinkDialog.tsx (100%) rename src/script/components/InputBar/{components/RichTextEditor/components => RichTextEditor}/FormatToolbar/common/isBlockquoteNode/isBlockquoteNode.test.ts (100%) rename src/script/components/InputBar/{components/RichTextEditor/components => RichTextEditor}/FormatToolbar/common/isBlockquoteNode/isBlockquoteNode.ts (100%) rename src/script/components/InputBar/{components/RichTextEditor/components => RichTextEditor}/FormatToolbar/common/isCodeBlockNode/isCodeBlockNode.test.ts (100%) rename src/script/components/InputBar/{components/RichTextEditor/components => RichTextEditor}/FormatToolbar/common/isCodeBlockNode/isCodeBlockNode.ts (100%) rename src/script/components/InputBar/{components/RichTextEditor/components => RichTextEditor}/FormatToolbar/common/isHeadingNode/isHeadingNode.test.ts (100%) rename src/script/components/InputBar/{components/RichTextEditor/components => RichTextEditor}/FormatToolbar/common/isHeadingNode/isHeadingNode.ts (100%) rename src/script/components/InputBar/{components/RichTextEditor/components => RichTextEditor}/FormatToolbar/common/isListNode/isListNode.test.ts (100%) rename src/script/components/InputBar/{components/RichTextEditor/components => RichTextEditor}/FormatToolbar/common/isListNode/isListNode.ts (100%) rename src/script/components/InputBar/{components/RichTextEditor/components => RichTextEditor}/FormatToolbar/useBlockquoteState/useBlockquoteState.ts (100%) rename src/script/components/InputBar/{components/RichTextEditor/components => RichTextEditor}/FormatToolbar/useCodeBlockState/useCodeBlockState.ts (100%) rename src/script/components/InputBar/{components/RichTextEditor/components => RichTextEditor}/FormatToolbar/useHeadingState/headingCommand.ts (100%) rename src/script/components/InputBar/{components/RichTextEditor/components => RichTextEditor}/FormatToolbar/useHeadingState/useHeadingState.ts (100%) rename src/script/components/InputBar/{components/RichTextEditor/components => RichTextEditor}/FormatToolbar/useLinkState/createNewLink/createNewLink.ts (100%) rename src/script/components/InputBar/{components/RichTextEditor/components => RichTextEditor}/FormatToolbar/useLinkState/getSelectedNode/getSelectedNode.ts (100%) rename src/script/components/InputBar/{components/RichTextEditor/components => RichTextEditor}/FormatToolbar/useLinkState/useLinkEditing/useLinkEditing.ts (100%) rename src/script/components/InputBar/{components/RichTextEditor/components => RichTextEditor}/FormatToolbar/useLinkState/useLinkState.ts (100%) rename src/script/components/InputBar/{components/RichTextEditor/components => RichTextEditor}/FormatToolbar/useLinkState/useModalState/useModalState.ts (100%) rename src/script/components/InputBar/{components/RichTextEditor/components => RichTextEditor}/FormatToolbar/useListState/useListState.ts (100%) rename src/script/components/InputBar/{components/RichTextEditor/components => RichTextEditor}/FormatToolbar/useToolbarState/useToolbarState.ts (100%) rename src/script/components/InputBar/{components/RichTextEditor/components => RichTextEditor}/Placeholder/Placeholder.tsx (100%) rename src/script/components/InputBar/{components => }/RichTextEditor/RichTextEditor.tsx (96%) rename src/script/components/InputBar/{components/RichTextEditor/components => RichTextEditor}/SendMessageButton/SendMessageButton.tsx (100%) rename src/script/components/InputBar/{components/RichTextEditor/components => RichTextEditor}/SendMessageButton/index.ts (100%) rename src/script/components/InputBar/{components => }/RichTextEditor/editorConfig.ts (100%) rename src/script/components/InputBar/{components => }/RichTextEditor/index.ts (100%) rename src/script/components/InputBar/{components => }/RichTextEditor/nodes/EmojiNode.ts (100%) rename src/script/components/InputBar/{components => }/RichTextEditor/nodes/Mention.tsx (100%) rename src/script/components/InputBar/{components => }/RichTextEditor/nodes/MentionNode.tsx (100%) rename src/script/components/InputBar/{components => }/RichTextEditor/plugins/AutoFocusPlugin/AutoFocusPlugin.tsx (100%) rename src/script/components/InputBar/{components => }/RichTextEditor/plugins/AutoLinkPlugin/AutoLinkPlugin.tsx (100%) rename src/script/components/InputBar/{components => }/RichTextEditor/plugins/BlockquotePlugin/BlockquotePlugin.tsx (100%) rename src/script/components/InputBar/{components => }/RichTextEditor/plugins/CodeHighlightPlugin/CodeHighlightPlugin.tsx (100%) rename src/script/components/InputBar/{components => }/RichTextEditor/plugins/DraftStatePlugin/DraftStatePlugin.tsx (100%) rename src/script/components/InputBar/{components => }/RichTextEditor/plugins/EditedMessagePlugin/EditedMessagePlugin.tsx (100%) rename src/script/components/InputBar/{components => }/RichTextEditor/plugins/EditedMessagePlugin/getMentionMarkdownTransformer/getMentionMarkdownTransformer.ts (100%) rename src/script/components/InputBar/{components => }/RichTextEditor/plugins/EditedMessagePlugin/getMentionNodesFromMessage/getMentionNodesFromMessage.ts (100%) rename src/script/components/InputBar/{components => }/RichTextEditor/plugins/EditedMessagePlugin/getRawMarkdownFromMessage/getRawMarkdownFromMessage.ts (100%) rename src/script/components/InputBar/{components => }/RichTextEditor/plugins/EditedMessagePlugin/wrapMentionsWithTags/wrapMentionsWithTags.ts (100%) rename src/script/components/InputBar/{components => }/RichTextEditor/plugins/EmojiPickerPlugin/EmojiItem.styles.ts (100%) rename src/script/components/InputBar/{components => }/RichTextEditor/plugins/EmojiPickerPlugin/EmojiItem.tsx (100%) rename src/script/components/InputBar/{components => }/RichTextEditor/plugins/EmojiPickerPlugin/EmojiPickerPlugin.tsx (100%) rename src/script/components/InputBar/{components => }/RichTextEditor/plugins/EmojiPickerPlugin/index.ts (100%) rename src/script/components/InputBar/{components => }/RichTextEditor/plugins/GlobalEventsPlugin/GlobalEventsPlugin.tsx (100%) rename src/script/components/InputBar/{components => }/RichTextEditor/plugins/HistoryPlugin/HistoryPlugin.tsx (100%) rename src/script/components/InputBar/{components => }/RichTextEditor/plugins/InlineEmojiReplacementPlugin/InlineEmojiReplacementPlugin.tsx (100%) rename src/script/components/InputBar/{components => }/RichTextEditor/plugins/InlineEmojiReplacementPlugin/index.ts (100%) rename src/script/components/InputBar/{components => }/RichTextEditor/plugins/InlineEmojiReplacementPlugin/inlineReplacements.ts (100%) rename src/script/components/InputBar/{components => }/RichTextEditor/plugins/LinkPlugin/LinkPlugin.tsx (100%) rename src/script/components/InputBar/{components => }/RichTextEditor/plugins/ListIndentationPlugin/ListIndentationPlugin.ts (100%) rename src/script/components/InputBar/{components => }/RichTextEditor/plugins/ListMaxIndentLevelPlugin/ListMaxIndentLevelPlugin.tsx (100%) rename src/script/components/InputBar/{components => }/RichTextEditor/plugins/MentionsPlugin/MentionSuggestionsItem.tsx (100%) rename src/script/components/InputBar/{components => }/RichTextEditor/plugins/MentionsPlugin/MentionsPlugin.tsx (100%) rename src/script/components/InputBar/{components => }/RichTextEditor/plugins/MentionsPlugin/index.ts (100%) rename src/script/components/InputBar/{components => }/RichTextEditor/plugins/PastePlugin/PastePlugin.tsx (100%) rename src/script/components/InputBar/{components => }/RichTextEditor/plugins/ReplaceCarriageReturnPlugin/ReplaceCarriageReturnPlugin.ts (100%) rename src/script/components/InputBar/{components => }/RichTextEditor/plugins/SendPlugin/SendPlugin.tsx (100%) rename src/script/components/InputBar/{components => }/RichTextEditor/plugins/TypeaheadMenuPlugin/TypeaheadMenuPlugin.tsx (100%) rename src/script/components/InputBar/{components => }/RichTextEditor/theme.ts (100%) rename src/script/components/InputBar/{components => }/RichTextEditor/utils/generateNodes.ts (100%) rename src/script/components/InputBar/{components => }/RichTextEditor/utils/getDomRangeRect.ts (100%) rename src/script/components/InputBar/{components => }/RichTextEditor/utils/getSelectionInfo.ts (100%) rename src/script/components/InputBar/{components => }/RichTextEditor/utils/markdownTransformers.ts (100%) rename src/script/components/InputBar/{components => }/RichTextEditor/utils/parseMentions.ts (100%) rename src/script/components/InputBar/{components => }/RichTextEditor/utils/transformMessage.ts (100%) rename src/script/components/InputBar/{components => }/RichTextEditor/utils/url.ts (100%) rename src/script/components/InputBar/{components => }/RichTextEditor/utils/useEditorDraftState.ts (100%) rename src/script/components/InputBar/{components => }/TypingIndicator/TypingIndicator.styles.ts (100%) rename src/script/components/InputBar/{components => }/TypingIndicator/TypingIndicator.test.tsx (97%) rename src/script/components/InputBar/{components => }/TypingIndicator/TypingIndicator.tsx (93%) rename src/script/components/InputBar/{components => }/TypingIndicator/index.ts (90%) rename src/script/components/InputBar/{components/TypingIndicator/TypingIndicator.state.tsx => TypingIndicator/useTypingIndicatorState/useTypingIndicatorState.ts} (95%) rename src/script/components/InputBar/{components => }/common/FormatSeparator/FormatSeparator.tsx (100%) create mode 100644 src/script/components/InputBar/useDraftState/useDraftState.ts rename src/script/components/InputBar/{hooks => }/useEmojiPicker/useEmojiPicker.ts (100%) create mode 100644 src/script/components/InputBar/useFileHandling/useFileHandling.ts rename src/script/components/InputBar/{hooks => }/useFilePaste/useFilePaste.test.ts (100%) rename src/script/components/InputBar/{hooks => }/useFilePaste/useFilePaste.ts (100%) rename src/script/components/InputBar/{hooks => }/useFormatToolbar/useFormatToolbar.ts (100%) create mode 100644 src/script/components/InputBar/useGiphy/useGiphy.ts create mode 100644 src/script/components/InputBar/useMessageHandling/useMessageHandling.ts create mode 100644 src/script/components/InputBar/usePing/usePing.ts rename src/script/components/InputBar/{hooks => }/useTypingIndicator/useTypingIndicator.test.ts (100%) rename src/script/components/InputBar/{hooks => }/useTypingIndicator/useTypingIndicator.ts (97%) diff --git a/src/script/components/InputBar/FileInput/FileInput.tsx b/src/script/components/InputBar/FileInput/FileInput.tsx new file mode 100644 index 00000000000..8ac201cd6f0 --- /dev/null +++ b/src/script/components/InputBar/FileInput/FileInput.tsx @@ -0,0 +1,36 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import {FC} from 'react'; + +import {PastedFileControls} from '../PastedFileControls/PastedFileControls'; + +interface FileInputProps { + pastedFile: File | null; + onClearPastedFile: () => void; + onSendPastedFile: () => void; +} + +export const FileInput: FC = ({pastedFile, onClearPastedFile, onSendPastedFile}) => { + if (!pastedFile) { + return null; + } + + return ; +}; diff --git a/src/script/components/InputBar/InputBar.tsx b/src/script/components/InputBar/InputBar.tsx index 4d6a013d48b..9d261f47e48 100644 --- a/src/script/components/InputBar/InputBar.tsx +++ b/src/script/components/InputBar/InputBar.tsx @@ -17,62 +17,51 @@ * */ -import {useCallback, useEffect, useRef, useState} from 'react'; +import {useRef, useState} from 'react'; -import {$convertToMarkdownString} from '@lexical/markdown'; import {amplify} from 'amplify'; import cx from 'classnames'; -import {LexicalEditor, $createTextNode, $insertNodes, CLEAR_EDITOR_COMMAND} from 'lexical'; -import {container} from 'tsyringe'; +import {LexicalEditor, $createTextNode, $insertNodes} from 'lexical'; import {WebAppEvents} from '@wireapp/webapp-events'; -import {Avatar, AVATAR_SIZE} from 'Components/Avatar'; import {ConversationClassifiedBar} from 'Components/ClassifiedBar/ClassifiedBar'; import {checkFileSharingPermission} from 'Components/Conversation/utils/checkFileSharingPermission'; import {EmojiPicker} from 'Components/EmojiPicker/EmojiPicker'; -import {markdownTransformers} from 'Components/InputBar/components/RichTextEditor/utils/markdownTransformers'; -import {transformMessage} from 'Components/InputBar/components/RichTextEditor/utils/transformMessage'; -import {PrimaryModal} from 'Components/Modals/PrimaryModal'; import {showWarningModal} from 'Components/Modals/utils/showWarningModal'; -import {ConversationRepository} from 'src/script/conversation/ConversationRepository'; import {useUserPropertyValue} from 'src/script/hooks/useUserProperty'; -import {PropertiesRepository} from 'src/script/properties/PropertiesRepository'; import {PROPERTIES_TYPE} from 'src/script/properties/PropertiesType'; import {EventName} from 'src/script/tracking/EventName'; import {CONVERSATION_TYPING_INDICATOR_MODE} from 'src/script/user/TypingIndicatorMode'; import {useKoSubscribableChildren} from 'Util/ComponentUtil'; -import {KEY} from 'Util/KeyboardUtil'; import {t} from 'Util/LocalizerUtil'; -import {sanitizeMarkdown} from 'Util/MarkdownUtil'; -import {formatLocale, TIME_IN_MILLIS} from 'Util/TimeUtil'; -import {getFileExtension} from 'Util/util'; - -import {ControlButtons} from './components/InputBarControls/ControlButtons'; -import {PastedFileControls} from './components/PastedFileControls/PastedFileControls'; -import {ReplyBar} from './components/ReplyBar/ReplyBar'; -import {RichTextContent, RichTextEditor} from './components/RichTextEditor'; -import {SendMessageButton} from './components/RichTextEditor/components/SendMessageButton'; -import {TypingIndicator} from './components/TypingIndicator/TypingIndicator'; -import {useEmojiPicker} from './hooks/useEmojiPicker/useEmojiPicker'; -import {useFilePaste} from './hooks/useFilePaste/useFilePaste'; -import {useFormatToolbar} from './hooks/useFormatToolbar/useFormatToolbar'; -import {useTypingIndicator} from './hooks/useTypingIndicator/useTypingIndicator'; -import {handleClickOutsideOfInputBar, IgnoreOutsideClickWrapper} from './util/clickHandlers'; -import {loadDraftState, saveDraftState} from './util/DraftStateUtil'; +import {TIME_IN_MILLIS} from 'Util/TimeUtil'; + +import {InputBarAvatar} from './InputBarAvatar/InputBarAvatar'; +import {InputBarButtons} from './InputBarButtons/InputBarButtons'; +import {InputBarEditor} from './InputBarEditor/InputBarEditor'; +import {PastedFileControls} from './PastedFileControls/PastedFileControls'; +import {ReplyBar} from './ReplyBar/ReplyBar'; +import {RichTextContent} from './RichTextEditor'; +import {TypingIndicator} from './TypingIndicator'; +import {useDraftState} from './useDraftState/useDraftState'; +import {useEmojiPicker} from './useEmojiPicker/useEmojiPicker'; +import {useFileHandling} from './useFileHandling/useFileHandling'; +import {useFilePaste} from './useFilePaste/useFilePaste'; +import {useFormatToolbar} from './useFormatToolbar/useFormatToolbar'; +import {useGiphy} from './useGiphy/useGiphy'; +import {useMessageHandling} from './useMessageHandling/useMessageHandling'; +import {usePing} from './usePing/usePing'; +import {useTypingIndicator} from './useTypingIndicator/useTypingIndicator'; import {Config} from '../../Config'; -import {ConversationVerificationState} from '../../conversation/ConversationVerificationState'; -import {MessageRepository, OutgoingQuote} from '../../conversation/MessageRepository'; +import {ConversationRepository} from '../../conversation/ConversationRepository'; +import {MessageRepository} from '../../conversation/MessageRepository'; import {Conversation} from '../../entity/Conversation'; -import {ContentMessage} from '../../entity/message/ContentMessage'; import {User} from '../../entity/User'; -import {ConversationError} from '../../error/ConversationError'; import {EventRepository} from '../../event/EventRepository'; -import {MentionEntity} from '../../message/MentionEntity'; -import {MessageHasher} from '../../message/MessageHasher'; -import {QuoteEntity} from '../../message/QuoteEntity'; import {useAppMainState} from '../../page/state'; +import {PropertiesRepository} from '../../properties/PropertiesRepository'; import {SearchRepository} from '../../search/SearchRepository'; import {StorageRepository} from '../../storage'; import {TeamState} from '../../team/TeamState'; @@ -100,8 +89,6 @@ interface InputBarProps { uploadFiles: (files: File[]) => void; } -const conversationInputBarClassName = 'conversation-input-bar'; - export const InputBar = ({ conversation, conversationRepository, @@ -112,7 +99,7 @@ export const InputBar = ({ searchRepository, storageRepository, selfUser, - teamState = container.resolve(TeamState), + teamState, onShiftTab, uploadDroppedFiles, uploadImages, @@ -137,40 +124,9 @@ export const InputBar = ({ ]); const wrapperRef = useRef(null); - - // Lexical const editorRef = useRef(null); - - // Typing indicator - const {typingIndicatorMode} = useKoSubscribableChildren(propertiesRepository, ['typingIndicatorMode']); - const isTypingIndicatorEnabled = typingIndicatorMode === CONVERSATION_TYPING_INDICATOR_MODE.ON; - - // Message - /** the messageContent represents the message being edited. It's directly derived from the editor state */ const [messageContent, setMessageContent] = useState({text: ''}); - const [editedMessage, setEditedMessage] = useState(); - const [replyMessageEntity, setReplyMessageEntity] = useState(null); - const textValue = messageContent.text; - - const formatToolbar = useFormatToolbar(); - - const emojiPicker = useEmojiPicker({ - wrapperRef, - onEmojiPicked: emoji => { - editorRef.current?.update(() => { - $insertNodes([$createTextNode(emoji)]); - }); - amplify.publish(WebAppEvents.ANALYTICS.EVENT, EventName.INPUT.EMOJI_MODAL.EMOJI_PICKED); - }, - }); - - // Files - const [pastedFile, setPastedFile] = useState(null); - // Common - const [pingDisabled, setIsPingDisabled] = useState(false); - - // Right sidebar const {rightSidebar} = useAppMainState.getState(); const lastItem = rightSidebar.history.length - 1; const currentState = rightSidebar.history[lastItem]; @@ -178,428 +134,133 @@ export const InputBar = ({ const inputPlaceholder = messageTimer ? t('tooltipConversationEphemeral') : t('tooltipConversationInputPlaceholder'); - const isEditing = !!editedMessage; - const isReplying = !!replyMessageEntity; const isConnectionRequest = isOutgoingRequest || isIncomingRequest; const hasLocalEphemeralTimer = isSelfDeletingMessagesEnabled && !!localMessageTimer && !hasGlobalMessageTimer; const isTypingRef = useRef(false); const isMessageFormatButtonsFlagEnabled = CONFIG.FEATURE.ENABLE_MESSAGE_FORMAT_BUTTONS; - const showGiphyButton = isMessageFormatButtonsFlagEnabled - ? textValue.length > 0 - : textValue.length > 0 && textValue.length <= CONFIG.GIPHY_TEXT_LENGTH; - const shouldReplaceEmoji = useUserPropertyValue( () => propertiesRepository.getPreference(PROPERTIES_TYPE.EMOJI.REPLACE_INLINE), WebAppEvents.PROPERTIES.UPDATE.EMOJI.REPLACE_INLINE, ); - // Mentions - const getMentionCandidates = (search?: string | null) => { - const candidates = conversation.participating_user_ets().filter(userEntity => !userEntity.isService); - return typeof search === 'string' ? searchRepository.searchUserInSet(search, candidates) : candidates; - }; - - useTypingIndicator({ - isEnabled: isTypingIndicatorEnabled, - text: textValue, - onTypingChange: useCallback( - isTyping => { - isTypingRef.current = isTyping; - if (isTyping) { - void conversationRepository.sendTypingStart(conversation); - } else { - void conversationRepository.sendTypingStop(conversation); - } - }, - [conversationRepository, conversation], - ), - }); + const {typingIndicatorMode} = useKoSubscribableChildren(propertiesRepository, ['typingIndicatorMode']); + const isTypingIndicatorEnabled = typingIndicatorMode === CONVERSATION_TYPING_INDICATOR_MODE.ON; - const handleSaveEditorDraft = (replyId = '') => { - const editor = editorRef.current; + const formatToolbar = useFormatToolbar(); - if (!editor) { - return; - } + const draftState = useDraftState({ + conversation, + storageRepository, + messageRepository, + editorRef, + }); - editor.getEditorState().read(() => { - const markdown = $convertToMarkdownString(markdownTransformers, undefined, true); - void saveDraft( - JSON.stringify(editor.getEditorState().toJSON()), - transformMessage({replaceEmojis: shouldReplaceEmoji, markdown}), + const { + editedMessage, + replyMessageEntity, + isEditing, + isReplying, + handleSendMessage, + cancelMessageEditing, + cancelMessageReply, + editMessage, + } = useMessageHandling({ + conversation, + conversationRepository, + eventRepository, + messageRepository, + editorRef, + onResetDraftState: draftState.resetDraftState, + onSaveDraft: (replyId?: string) => + draftState.saveDraftState( + JSON.stringify(editorRef.current?.getEditorState().toJSON()), + messageContent.text, replyId, - ); - }); - }; - - const resetDraftState = () => { - setReplyMessageEntity(null); - editorRef.current?.dispatchCommand(CLEAR_EDITOR_COMMAND, undefined); - }; - - const clearPastedFile = () => setPastedFile(null); - - const sendPastedFile = () => { - if (pastedFile) { - uploadDroppedFiles([pastedFile]); - clearPastedFile(); - } - }; - - const cancelMessageReply = (resetDraft = true) => { - setReplyMessageEntity(null); - handleSaveEditorDraft(); - - if (resetDraft) { - resetDraftState(); - } - }; - - useEffect(() => { - amplify.subscribe(WebAppEvents.CONVERSATION.MESSAGE.EDIT, (messageEntity: ContentMessage) => { - editMessage(messageEntity); - }); - - return () => { - amplify.unsubscribeAll(WebAppEvents.CONVERSATION.MESSAGE.EDIT); - }; + ), }); - const cancelMessageEditing = (resetDraft = true) => { - setEditedMessage(undefined); - setReplyMessageEntity(null); - - if (resetDraft) { - resetDraftState(); - } - }; - - const editMessage = (messageEntity?: ContentMessage) => { - if (messageEntity?.isEditable() && messageEntity !== editedMessage) { - cancelMessageReply(); - cancelMessageEditing(true); - setEditedMessage(messageEntity); - - const quote = messageEntity.quote(); - if (quote && conversation) { - void messageRepository - .getMessageInConversationById(conversation, quote.messageId) - .then(quotedMessage => setReplyMessageEntity(quotedMessage)); - } - } - }; - - const replyMessage = (messageEntity: ContentMessage): void => { - if (messageEntity?.isReplyable() && messageEntity !== replyMessageEntity) { - cancelMessageReply(false); - cancelMessageEditing(!!editedMessage); - setReplyMessageEntity(messageEntity); - handleSaveEditorDraft(messageEntity.id); - - editorRef.current?.focus(); - } - }; - - const generateQuote = (): Promise => { - return !replyMessageEntity - ? Promise.resolve(undefined) - : eventRepository.eventService - .loadEvent(replyMessageEntity.conversation_id, replyMessageEntity.id) - .then(MessageHasher.hashEvent) - .then((messageHash: ArrayBuffer) => { - return new QuoteEntity({ - hash: messageHash, - messageId: replyMessageEntity.id, - userId: replyMessageEntity.from, - }) as OutgoingQuote; - }); - }; - - const sendMessageEdit = (messageText: string, mentions: MentionEntity[]): void | Promise => { - const mentionEntities = mentions.slice(0); - cancelMessageEditing(true); - - if (!messageText.length && editedMessage) { - return messageRepository.deleteMessageForEveryone(conversation, editedMessage); - } - - if (editedMessage) { - messageRepository.sendMessageEdit(conversation, messageText, editedMessage, mentionEntities).catch(error => { - if (error.type !== ConversationError.TYPE.NO_MESSAGE_CHANGES) { - throw error; - } - }); - - cancelMessageReply(); - } - }; - - const sendTextMessage = (messageText: string, mentions: MentionEntity[]) => { - if (messageText.length) { - const mentionEntities = mentions.slice(0); - - void generateQuote().then(quoteEntity => { - void messageRepository.sendTextWithLinkPreview(conversation, messageText, mentionEntities, quoteEntity); - cancelMessageReply(); - }); - } - }; - - const sendMessage = (): void => { - if (pastedFile) { - return void sendPastedFile(); - } - - const messageTrimmedStart = textValue.trimStart(); - const text = messageTrimmedStart.trimEnd(); - const isMessageTextTooLong = text.length > CONFIG.MAXIMUM_MESSAGE_LENGTH; - const mentions = messageContent.mentions ?? []; - - if (isMessageTextTooLong) { - showWarningModal( - t('modalConversationMessageTooLongHeadline'), - t('modalConversationMessageTooLongMessage', {number: CONFIG.MAXIMUM_MESSAGE_LENGTH}), - ); - - return; - } - - if (isEditing) { - void sendMessageEdit(text, mentions); - } else { - sendTextMessage(text, mentions); - } - - editorRef.current?.focus(); - resetDraftState(); - }; - - const handleSendMessage = async () => { - await conversationRepository.refreshMLSConversationVerificationState(conversation); - const isE2EIDegraded = conversation.mlsVerificationState() === ConversationVerificationState.DEGRADED; - - if (isE2EIDegraded) { - PrimaryModal.show(PrimaryModal.type.CONFIRM, { - secondaryAction: { - action: () => { - conversation.mlsVerificationState(ConversationVerificationState.UNVERIFIED); - sendMessage(); - }, - text: t('conversation.E2EISendAnyway'), - }, - primaryAction: { - action: () => {}, - text: t('conversation.E2EICancel'), - }, - text: { - message: t('conversation.E2EIDegradedNewMessage'), - title: t('conversation.E2EIConversationNoLongerVerified'), - }, - }); - } else { - sendMessage(); - } - }; - - const onGifClick = () => openGiphy(textValue); - - const pingConversation = () => { - setIsPingDisabled(true); - void messageRepository.sendPing(conversation).then(() => { - window.setTimeout(() => setIsPingDisabled(false), CONFIG.PING_TIMEOUT); - }); - }; - - const onPingClick = () => { - if (pingDisabled) { - return; - } + const fileHandling = useFileHandling({ + uploadDroppedFiles, + }); - const totalConversationUsers = conversation.participating_user_ets().length; - if ( - !CONFIG.FEATURE.ENABLE_PING_CONFIRMATION || - is1to1 || - totalConversationUsers < CONFIG.FEATURE.MAX_USERS_TO_PING_WITHOUT_ALERT - ) { - pingConversation(); - } else { - PrimaryModal.show(PrimaryModal.type.CONFIRM, { - primaryAction: { - action: pingConversation, - text: t('tooltipConversationPing'), - }, - text: { - title: t('conversationPingConfirmTitle', {memberCount: totalConversationUsers.toString()}), - }, + const emojiPicker = useEmojiPicker({ + wrapperRef, + onEmojiPicked: emoji => { + editorRef.current?.update(() => { + $insertNodes([$createTextNode(emoji)]); }); - } - }; - - const handlePasteFiles = (files: FileList): void => { - const [pastedFile] = files; - - if (!pastedFile) { - return; - } - const {lastModified} = pastedFile; - - const date = formatLocale(lastModified || new Date(), 'PP, pp'); - const fileName = `${t('conversationSendPastedFile', {date})}.${getFileExtension(pastedFile.name)}`; - - const newFile = new File([pastedFile], fileName, { - type: pastedFile.type, - }); - - setPastedFile(newFile); - }; - - const sendGiphy = (gifUrl: string, tag: string): void => { - void generateQuote().then(quoteEntity => { - void messageRepository.sendGif(conversation, gifUrl, tag, quoteEntity); - cancelMessageEditing(true); - }); - }; - - useEffect(() => { - amplify.subscribe(WebAppEvents.CONVERSATION.IMAGE.SEND, uploadImages); - amplify.subscribe(WebAppEvents.CONVERSATION.MESSAGE.REPLY, replyMessage); - amplify.subscribe(WebAppEvents.EXTENSIONS.GIPHY.SEND, sendGiphy); - amplify.subscribe(WebAppEvents.SHORTCUT.PING, onPingClick); - conversation.isTextInputReady(true); - - return () => { - amplify.unsubscribeAll(WebAppEvents.SHORTCUT.PING); - amplify.unsubscribeAll(WebAppEvents.CONVERSATION.IMAGE.SEND); - amplify.unsubscribeAll(WebAppEvents.CONVERSATION.MESSAGE.REPLY); - amplify.unsubscribeAll(WebAppEvents.EXTENSIONS.GIPHY.SEND); - conversation.isTextInputReady(false); - }; - }, []); - - const saveDraft = async (editorState: string, plainMessage: string, replyId?: string) => { - await saveDraftState({ - storageRepository, - conversation, - editorState, - plainMessage: sanitizeMarkdown(plainMessage), - replyId: replyId ?? replyMessageEntity?.id, - editedMessageId: editedMessage?.id, - }); - }; - - const loadDraft = async () => { - const draftState = await loadDraftState(conversation, storageRepository, messageRepository); - - const reply = draftState.messageReply; - if (reply?.isReplyable()) { - setReplyMessageEntity(reply); - } - - const editedMessage = draftState.editedMessage; - if (editedMessage) { - setEditedMessage(editedMessage); - } - - return draftState; - }; - - const handleRepliedMessageDeleted = (messageId: string) => { - if (replyMessageEntity?.id === messageId) { - setReplyMessageEntity(null); - } - }; - - const handleRepliedMessageUpdated = (originalMessageId: string, messageEntity: ContentMessage) => { - if (replyMessageEntity?.id === originalMessageId) { - setReplyMessageEntity(messageEntity); - } - }; + amplify.publish(WebAppEvents.ANALYTICS.EVENT, EventName.INPUT.EMOJI_MODAL.EMOJI_PICKED); + }, + }); - useEffect(() => { - amplify.subscribe(WebAppEvents.CONVERSATION.MESSAGE.REMOVED, handleRepliedMessageDeleted); - amplify.subscribe(WebAppEvents.CONVERSATION.MESSAGE.UPDATED, handleRepliedMessageUpdated); - - return () => { - amplify.unsubscribe(WebAppEvents.CONVERSATION.MESSAGE.REMOVED, handleRepliedMessageDeleted); - amplify.unsubscribe(WebAppEvents.CONVERSATION.MESSAGE.UPDATED, handleRepliedMessageUpdated); - }; - }, [replyMessageEntity]); - - useEffect(() => { - const onWindowClick = (event: Event): void => - handleClickOutsideOfInputBar(event, () => { - // We want to add a timeout in case the click happens because the user switched conversation and the component is unmounting. - // In this case we want to keep the edited message for this conversation - setTimeout(() => { - cancelMessageEditing(true); - cancelMessageReply(); - }); - }); - if (isEditing) { - window.addEventListener('click', onWindowClick); + const ping = usePing({ + conversation, + messageRepository, + is1to1, + maxUsersWithoutAlert: CONFIG.FEATURE.MAX_USERS_TO_PING_WITHOUT_ALERT, + enablePingConfirmation: CONFIG.FEATURE.ENABLE_PING_CONFIRMATION, + }); - return () => { - window.removeEventListener('click', onWindowClick); - }; - } + const giphy = useGiphy({ + text: messageContent.text, + maxLength: CONFIG.GIPHY_TEXT_LENGTH, + isMessageFormatButtonsFlagEnabled, + openGiphy, + }); - return () => undefined; - }, [cancelMessageEditing, cancelMessageReply, isEditing]); + useTypingIndicator({ + isEnabled: isTypingIndicatorEnabled, + text: messageContent.text, + onTypingChange: isTyping => { + isTypingRef.current = isTyping; + if (isTyping) { + void conversationRepository.sendTypingStart(conversation); + } else { + void conversationRepository.sendTypingStop(conversation); + } + }, + }); - useFilePaste(checkFileSharingPermission(handlePasteFiles)); + useFilePaste(checkFileSharingPermission(fileHandling.handlePasteFiles)); - const sendImageOnEnterClick = (event: KeyboardEvent) => { - if (event.key === KEY.ENTER && !event.shiftKey && !event.altKey && !event.metaKey) { - sendPastedFile(); - } + const getMentionCandidates = (search?: string | null) => { + const candidates = conversation.participating_user_ets().filter(userEntity => !userEntity.isService); + return typeof search === 'string' ? searchRepository.searchUserInSet(search, candidates) : candidates; }; - useEffect(() => { - if (!pastedFile) { - return () => undefined; - } - - window.addEventListener('keydown', sendImageOnEnterClick); - - return () => { - window.removeEventListener('keydown', sendImageOnEnterClick); - }; - }, [pastedFile]); - const showMarkdownPreview = useUserPropertyValue( () => propertiesRepository.getPreference(PROPERTIES_TYPE.INTERFACE.MARKDOWN_PREVIEW), WebAppEvents.PROPERTIES.UPDATE.INTERFACE.MARKDOWN_PREVIEW, ); - const controlButtonsProps = { - conversation: conversation, - disableFilesharing: !isFileSharingSendingEnabled, - disablePing: pingDisabled, - input: textValue, - isEditing: isEditing, - onCancelEditing: () => cancelMessageEditing(true), - onClickPing: onPingClick, - onGifClick: onGifClick, - onSelectFiles: uploadFiles, - onSelectImages: uploadImages, - showGiphyButton: showGiphyButton, - showFormatButton: isMessageFormatButtonsFlagEnabled && showMarkdownPreview, - showEmojiButton: isMessageFormatButtonsFlagEnabled, - isFormatActive: formatToolbar.open, - onFormatClick: formatToolbar.handleClick, - isEmojiActive: emojiPicker.open, - onEmojiClick: emojiPicker.handleToggle, - }; + const handleSend = () => { + if (fileHandling.pastedFile) { + fileHandling.sendPastedFile(); + } else { + const messageTrimmedStart = messageContent.text.trimStart(); + const text = messageTrimmedStart.trimEnd(); + const isMessageTextTooLong = text.length > CONFIG.MAXIMUM_MESSAGE_LENGTH; + + if (isMessageTextTooLong) { + showWarningModal( + t('modalConversationMessageTooLongHeadline'), + t('modalConversationMessageTooLongMessage', {number: CONFIG.MAXIMUM_MESSAGE_LENGTH}), + ); + return; + } - const enableSending = textValue.length > 0; + void handleSendMessage(text, messageContent.mentions ?? []); + } + }; - const showAvatar = !!textValue.length; + const conversationInputBarClassName = 'conversation-input-bar'; + const showAvatar = !!messageContent.text.length; return ( -
- +
)} - {isReplying && !isEditing && ( + {isReplying && !isEditing && replyMessageEntity && ( cancelMessageReply(false)} /> )} -
- {!isOutgoingRequest && ( - <> -
- {showAvatar && ( - - )} -
- {!isSelfUserRemoved && !pastedFile && ( - { - editorRef.current = lexical; - }} - editedMessage={editedMessage} - onEscape={() => { - if (editedMessage) { - cancelMessageEditing(true); - } else if (replyMessageEntity) { - cancelMessageReply(); - } - }} - onArrowUp={() => { - if (textValue.length === 0) { - editMessage(conversation.getLastEditableMessage()); - } - }} - getMentionCandidates={getMentionCandidates} - replaceEmojis={shouldReplaceEmoji} - placeholder={inputPlaceholder} - onUpdate={setMessageContent} - hasLocalEphemeralTimer={hasLocalEphemeralTimer} - showFormatToolbar={formatToolbar.open} - showMarkdownPreview={showMarkdownPreview} - saveDraftState={saveDraft} - loadDraftState={loadDraft} - onShiftTab={onShiftTab} - onSend={handleSendMessage} - onBlur={() => isTypingRef.current && conversationRepository.sendTypingStop(conversation)} - > -
-
    - -
- -
-
- )} - +
+ + + {!isSelfUserRemoved && !fileHandling.pastedFile && ( + { + editorRef.current = editor; + }} + onEscape={() => { + if (editedMessage) { + cancelMessageEditing(true); + } else if (replyMessageEntity) { + cancelMessageReply(); + } + }} + onArrowUp={() => { + if (messageContent.text.length === 0) { + editMessage(conversation.getLastEditableMessage()); + } + }} + onShiftTab={onShiftTab} + onBlur={() => isTypingRef.current && conversationRepository.sendTypingStop(conversation)} + onUpdate={setMessageContent} + onSend={handleSend} + getMentionCandidates={getMentionCandidates} + saveDraftState={draftState.saveDraftState} + loadDraftState={draftState.loadDraftState} + replaceEmojis={shouldReplaceEmoji} + > + { + if (editedMessage) { + cancelMessageEditing(true); + } else if (replyMessageEntity) { + cancelMessageReply(); + } + }} + onClickPing={ping.handlePing} + onGifClick={giphy.handleGifClick} + onSelectFiles={uploadFiles} + onSelectImages={uploadImages} + onSend={handleSend} + /> + )} - {pastedFile && ( - + {fileHandling.pastedFile && ( + )}
- +
{emojiPicker.open ? ( = ({selfUser, showAvatar}) => { + if (!showAvatar) { + return null; + } + + return ( +
+ +
+ ); +}; diff --git a/src/script/components/InputBar/InputBarButtons/InputBarButtons.tsx b/src/script/components/InputBar/InputBarButtons/InputBarButtons.tsx new file mode 100644 index 00000000000..42856b137dc --- /dev/null +++ b/src/script/components/InputBar/InputBarButtons/InputBarButtons.tsx @@ -0,0 +1,97 @@ +/* + * Wire + * Copyright (C) 2025 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import {Conversation} from 'src/script/entity/Conversation'; + +import {ControlButtons} from '../InputBarControls/ControlButtons'; +import {RichTextContent} from '../RichTextEditor/RichTextEditor'; +import {SendMessageButton} from '../RichTextEditor/SendMessageButton'; + +interface InputBarButtonsProps { + conversation: Conversation; + isFileSharingSendingEnabled: boolean; + pingDisabled: boolean; + messageContent: RichTextContent; + isEditing: boolean; + isMessageFormatButtonsFlagEnabled: boolean; + showMarkdownPreview: boolean; + showGiphyButton: boolean; + formatToolbar: { + open: boolean; + handleClick: () => void; + }; + emojiPicker: { + open: boolean; + handleToggle: (event: React.MouseEvent) => void; + }; + onEscape: () => void; + onClickPing: () => void; + onGifClick: () => void; + onSelectFiles: (files: File[]) => void; + onSelectImages: (files: File[]) => void; + onSend: () => void; +} + +export const InputBarButtons = ({ + conversation, + isFileSharingSendingEnabled, + pingDisabled, + messageContent, + isEditing, + isMessageFormatButtonsFlagEnabled, + showMarkdownPreview, + showGiphyButton, + formatToolbar, + emojiPicker, + onEscape, + onClickPing, + onGifClick, + onSelectFiles, + onSelectImages, + onSend, +}: InputBarButtonsProps) => { + const enableSending = messageContent.text.length > 0; + + return ( +
+
    + +
+ +
+ ); +}; diff --git a/src/script/components/InputBar/components/AssetUploadButton/AssetUploadButton.test.tsx b/src/script/components/InputBar/InputBarControls/AssetUploadButton/AssetUploadButton.test.tsx similarity index 100% rename from src/script/components/InputBar/components/AssetUploadButton/AssetUploadButton.test.tsx rename to src/script/components/InputBar/InputBarControls/AssetUploadButton/AssetUploadButton.test.tsx diff --git a/src/script/components/InputBar/components/AssetUploadButton/AssetUploadButton.tsx b/src/script/components/InputBar/InputBarControls/AssetUploadButton/AssetUploadButton.tsx similarity index 100% rename from src/script/components/InputBar/components/AssetUploadButton/AssetUploadButton.tsx rename to src/script/components/InputBar/InputBarControls/AssetUploadButton/AssetUploadButton.tsx diff --git a/src/script/components/InputBar/components/AssetUploadButton/index.ts b/src/script/components/InputBar/InputBarControls/AssetUploadButton/index.ts similarity index 100% rename from src/script/components/InputBar/components/AssetUploadButton/index.ts rename to src/script/components/InputBar/InputBarControls/AssetUploadButton/index.ts diff --git a/src/script/components/InputBar/components/InputBarControls/CancelEditButton/CancelEditButton.tsx b/src/script/components/InputBar/InputBarControls/CancelEditButton/CancelEditButton.tsx similarity index 100% rename from src/script/components/InputBar/components/InputBarControls/CancelEditButton/CancelEditButton.tsx rename to src/script/components/InputBar/InputBarControls/CancelEditButton/CancelEditButton.tsx diff --git a/src/script/components/InputBar/components/InputBarControls/ControlButtons.test.tsx b/src/script/components/InputBar/InputBarControls/ControlButtons.test.tsx similarity index 100% rename from src/script/components/InputBar/components/InputBarControls/ControlButtons.test.tsx rename to src/script/components/InputBar/InputBarControls/ControlButtons.test.tsx diff --git a/src/script/components/InputBar/components/InputBarControls/ControlButtons.tsx b/src/script/components/InputBar/InputBarControls/ControlButtons.tsx similarity index 92% rename from src/script/components/InputBar/components/InputBarControls/ControlButtons.tsx rename to src/script/components/InputBar/InputBarControls/ControlButtons.tsx index 528cc411381..8090a0b80b9 100644 --- a/src/script/components/InputBar/components/InputBarControls/ControlButtons.tsx +++ b/src/script/components/InputBar/InputBarControls/ControlButtons.tsx @@ -19,20 +19,19 @@ import React, {MouseEvent} from 'react'; -import {FormatSeparator} from 'Components/InputBar/components/common/FormatSeparator/FormatSeparator'; +import {FormatSeparator} from 'Components/InputBar/common/FormatSeparator/FormatSeparator'; import {Config} from 'src/script/Config'; import {Conversation} from 'src/script/entity/Conversation'; +import {AssetUploadButton} from './AssetUploadButton'; import {CancelEditButton} from './CancelEditButton/CancelEditButton'; import {EmojiButton} from './EmojiButton/EmojiButton'; import {FormatTextButton} from './FormatTextButton/FormatTextButton'; import {GiphyButton} from './GiphyButton/GiphyButton'; +import {ImageUploadButton} from './ImageUploadButton'; +import {MessageTimerButton} from './MessageTimerButton'; import {PingButton} from './PingButton/PingButton'; -import {AssetUploadButton} from '../AssetUploadButton'; -import {ImageUploadButton} from '../ImageUploadButton'; -import {MessageTimerButton} from '../MessageTimerButton'; - export type ControlButtonsProps = { input: string; conversation: Conversation; @@ -53,7 +52,7 @@ export type ControlButtonsProps = { onEmojiClick: (event: MouseEvent) => void; }; -const ControlButtons: React.FC = ({ +const ControlButtons = ({ conversation, disablePing, disableFilesharing, @@ -71,7 +70,7 @@ const ControlButtons: React.FC = ({ onGifClick, onFormatClick, onEmojiClick, -}) => { +}: ControlButtonsProps) => { if (isEditing) { return ( <> diff --git a/src/script/components/InputBar/components/InputBarControls/EmojiButton/EmojiButton.tsx b/src/script/components/InputBar/InputBarControls/EmojiButton/EmojiButton.tsx similarity index 100% rename from src/script/components/InputBar/components/InputBarControls/EmojiButton/EmojiButton.tsx rename to src/script/components/InputBar/InputBarControls/EmojiButton/EmojiButton.tsx diff --git a/src/script/components/InputBar/components/InputBarControls/FormatTextButton/FormatTextButton.tsx b/src/script/components/InputBar/InputBarControls/FormatTextButton/FormatTextButton.tsx similarity index 100% rename from src/script/components/InputBar/components/InputBarControls/FormatTextButton/FormatTextButton.tsx rename to src/script/components/InputBar/InputBarControls/FormatTextButton/FormatTextButton.tsx diff --git a/src/script/components/InputBar/components/InputBarControls/GiphyButton/GiphyButton.tsx b/src/script/components/InputBar/InputBarControls/GiphyButton/GiphyButton.tsx similarity index 100% rename from src/script/components/InputBar/components/InputBarControls/GiphyButton/GiphyButton.tsx rename to src/script/components/InputBar/InputBarControls/GiphyButton/GiphyButton.tsx diff --git a/src/script/components/InputBar/components/ImageUploadButton/ImageUploadButton.test.tsx b/src/script/components/InputBar/InputBarControls/ImageUploadButton/ImageUploadButton.test.tsx similarity index 100% rename from src/script/components/InputBar/components/ImageUploadButton/ImageUploadButton.test.tsx rename to src/script/components/InputBar/InputBarControls/ImageUploadButton/ImageUploadButton.test.tsx diff --git a/src/script/components/InputBar/components/ImageUploadButton/ImageUploadButton.tsx b/src/script/components/InputBar/InputBarControls/ImageUploadButton/ImageUploadButton.tsx similarity index 100% rename from src/script/components/InputBar/components/ImageUploadButton/ImageUploadButton.tsx rename to src/script/components/InputBar/InputBarControls/ImageUploadButton/ImageUploadButton.tsx diff --git a/src/script/components/InputBar/components/ImageUploadButton/index.ts b/src/script/components/InputBar/InputBarControls/ImageUploadButton/index.ts similarity index 100% rename from src/script/components/InputBar/components/ImageUploadButton/index.ts rename to src/script/components/InputBar/InputBarControls/ImageUploadButton/index.ts diff --git a/src/script/components/InputBar/components/MessageTimerButton/MessageTimerButton.test.tsx b/src/script/components/InputBar/InputBarControls/MessageTimerButton/MessageTimerButton.test.tsx similarity index 100% rename from src/script/components/InputBar/components/MessageTimerButton/MessageTimerButton.test.tsx rename to src/script/components/InputBar/InputBarControls/MessageTimerButton/MessageTimerButton.test.tsx diff --git a/src/script/components/InputBar/components/MessageTimerButton/MessageTimerButton.tsx b/src/script/components/InputBar/InputBarControls/MessageTimerButton/MessageTimerButton.tsx similarity index 100% rename from src/script/components/InputBar/components/MessageTimerButton/MessageTimerButton.tsx rename to src/script/components/InputBar/InputBarControls/MessageTimerButton/MessageTimerButton.tsx diff --git a/src/script/components/InputBar/components/MessageTimerButton/index.ts b/src/script/components/InputBar/InputBarControls/MessageTimerButton/index.ts similarity index 100% rename from src/script/components/InputBar/components/MessageTimerButton/index.ts rename to src/script/components/InputBar/InputBarControls/MessageTimerButton/index.ts diff --git a/src/script/components/InputBar/components/InputBarControls/PingButton/PingButton.tsx b/src/script/components/InputBar/InputBarControls/PingButton/PingButton.tsx similarity index 100% rename from src/script/components/InputBar/components/InputBarControls/PingButton/PingButton.tsx rename to src/script/components/InputBar/InputBarControls/PingButton/PingButton.tsx diff --git a/src/script/components/InputBar/InputBarEditor/InputBarEditor.tsx b/src/script/components/InputBar/InputBarEditor/InputBarEditor.tsx new file mode 100644 index 00000000000..f6e4af110b4 --- /dev/null +++ b/src/script/components/InputBar/InputBarEditor/InputBarEditor.tsx @@ -0,0 +1,95 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import {FC} from 'react'; + +import {LexicalEditor} from 'lexical'; + +import {User} from 'src/script/entity/User'; + +import {RichTextEditor, RichTextContent} from '../RichTextEditor'; + +interface InputBarEditorProps { + editorRef: React.MutableRefObject; + inputPlaceholder: string; + hasLocalEphemeralTimer: boolean; + showMarkdownPreview: boolean; + formatToolbar: { + open: boolean; + handleClick: () => void; + }; + onSetup: (editor: LexicalEditor) => void; + onEscape: () => void; + onArrowUp: () => void; + onShiftTab: () => void; + onBlur: () => void; + onUpdate: (content: RichTextContent) => void; + onSend: () => void; + getMentionCandidates: (search?: string | null) => User[]; + saveDraftState: (editorState: string, plainMessage: string, replyId?: string) => void; + loadDraftState: () => Promise; + replaceEmojis: boolean; + children: React.ReactNode; +} + +export const InputBarEditor: FC = ({ + editorRef, + inputPlaceholder, + hasLocalEphemeralTimer, + showMarkdownPreview, + formatToolbar, + onSetup, + onEscape, + onArrowUp, + onShiftTab, + onBlur, + onUpdate, + onSend, + getMentionCandidates, + saveDraftState, + loadDraftState, + replaceEmojis, + children, +}) => { + return ( + { + editorRef.current = editor; + onSetup(editor); + }} + editedMessage={undefined} + onEscape={onEscape} + onArrowUp={onArrowUp} + getMentionCandidates={getMentionCandidates} + replaceEmojis={replaceEmojis} + placeholder={inputPlaceholder} + onUpdate={onUpdate} + hasLocalEphemeralTimer={hasLocalEphemeralTimer} + showFormatToolbar={formatToolbar.open} + showMarkdownPreview={showMarkdownPreview} + saveDraftState={saveDraftState} + loadDraftState={loadDraftState} + onShiftTab={onShiftTab} + onSend={onSend} + onBlur={onBlur} + > + {children} + + ); +}; diff --git a/src/script/components/InputBar/components/PastedFileControls/PastedFileControls.tsx b/src/script/components/InputBar/PastedFileControls/PastedFileControls.tsx similarity index 93% rename from src/script/components/InputBar/components/PastedFileControls/PastedFileControls.tsx rename to src/script/components/InputBar/PastedFileControls/PastedFileControls.tsx index cc276069f55..ec9efcc294c 100644 --- a/src/script/components/InputBar/components/PastedFileControls/PastedFileControls.tsx +++ b/src/script/components/InputBar/PastedFileControls/PastedFileControls.tsx @@ -24,7 +24,7 @@ import {TabIndex} from '@wireapp/react-ui-kit/lib/types/enums'; import * as Icon from 'Components/Icon'; import {t} from 'Util/LocalizerUtil'; -import {Config} from '../../../../Config'; +import {Config} from '../../../Config'; interface PastedFileControlsProps { pastedFile: File; @@ -32,7 +32,7 @@ interface PastedFileControlsProps { onSend: () => void; } -const PastedFileControls: FC = ({pastedFile, onClear, onSend}) => { +export const PastedFileControls: FC = ({pastedFile, onClear, onSend}) => { const isSupportedFileType = (Config.getConfig().ALLOWED_IMAGE_TYPES as ReadonlyArray).includes( pastedFile.type, ); @@ -85,5 +85,3 @@ const PastedFileControls: FC = ({pastedFile, onClear, o
); }; - -export {PastedFileControls}; diff --git a/src/script/components/InputBar/components/ReplyBar/ReplyBar.tsx b/src/script/components/InputBar/ReplyBar/ReplyBar.tsx similarity index 95% rename from src/script/components/InputBar/components/ReplyBar/ReplyBar.tsx rename to src/script/components/InputBar/ReplyBar/ReplyBar.tsx index 00cc272866d..44b4951398b 100644 --- a/src/script/components/InputBar/components/ReplyBar/ReplyBar.tsx +++ b/src/script/components/InputBar/ReplyBar/ReplyBar.tsx @@ -19,8 +19,6 @@ /* eslint-disable jsx-a11y/no-noninteractive-tabindex */ -import {FC} from 'react'; - import {TabIndex} from '@wireapp/react-ui-kit/lib/types/enums'; import {RestrictedVideo} from 'Components/asset/RestrictedVideo'; @@ -31,14 +29,14 @@ import {useKoSubscribableChildren} from 'Util/ComponentUtil'; import {t} from 'Util/LocalizerUtil'; import {renderMessage} from 'Util/messageRenderer'; -import {ContentMessage} from '../../../../entity/message/ContentMessage'; +import {ContentMessage} from '../../../entity/message/ContentMessage'; interface ReplyBarProps { replyMessageEntity: ContentMessage; onCancel: () => void; } -const ReplyBar: FC = ({replyMessageEntity, onCancel}) => { +export const ReplyBar = ({replyMessageEntity, onCancel}: ReplyBarProps) => { const { assets, senderName, @@ -131,5 +129,3 @@ const ReplyBar: FC = ({replyMessageEntity, onCancel}) => {
); }; - -export {ReplyBar}; diff --git a/src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/FormatButton/FormatButton.tsx b/src/script/components/InputBar/RichTextEditor/FormatToolbar/FormatButton/FormatButton.tsx similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/FormatButton/FormatButton.tsx rename to src/script/components/InputBar/RichTextEditor/FormatToolbar/FormatButton/FormatButton.tsx diff --git a/src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/FormatToolbar.styles.ts b/src/script/components/InputBar/RichTextEditor/FormatToolbar/FormatToolbar.styles.ts similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/FormatToolbar.styles.ts rename to src/script/components/InputBar/RichTextEditor/FormatToolbar/FormatToolbar.styles.ts diff --git a/src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/FormatToolbar.tsx b/src/script/components/InputBar/RichTextEditor/FormatToolbar/FormatToolbar.tsx similarity index 98% rename from src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/FormatToolbar.tsx rename to src/script/components/InputBar/RichTextEditor/FormatToolbar/FormatToolbar.tsx index 0fcb20bc947..a766e3b981a 100644 --- a/src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/FormatToolbar.tsx +++ b/src/script/components/InputBar/RichTextEditor/FormatToolbar/FormatToolbar.tsx @@ -45,7 +45,7 @@ import {useLinkState} from './useLinkState/useLinkState'; import {useListState} from './useListState/useListState'; import {useToolbarState} from './useToolbarState/useToolbarState'; -import {FormatSeparator} from '../../../common/FormatSeparator/FormatSeparator'; +import {FormatSeparator} from '../../../../common/FormatSeparator/FormatSeparator'; interface FormatToolbarProps { isEditing: boolean; diff --git a/src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/LinkDialog/LinkDialog.styles.ts b/src/script/components/InputBar/RichTextEditor/FormatToolbar/LinkDialog/LinkDialog.styles.ts similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/LinkDialog/LinkDialog.styles.ts rename to src/script/components/InputBar/RichTextEditor/FormatToolbar/LinkDialog/LinkDialog.styles.ts diff --git a/src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/LinkDialog/LinkDialog.tsx b/src/script/components/InputBar/RichTextEditor/FormatToolbar/LinkDialog/LinkDialog.tsx similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/LinkDialog/LinkDialog.tsx rename to src/script/components/InputBar/RichTextEditor/FormatToolbar/LinkDialog/LinkDialog.tsx diff --git a/src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/common/isBlockquoteNode/isBlockquoteNode.test.ts b/src/script/components/InputBar/RichTextEditor/FormatToolbar/common/isBlockquoteNode/isBlockquoteNode.test.ts similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/common/isBlockquoteNode/isBlockquoteNode.test.ts rename to src/script/components/InputBar/RichTextEditor/FormatToolbar/common/isBlockquoteNode/isBlockquoteNode.test.ts diff --git a/src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/common/isBlockquoteNode/isBlockquoteNode.ts b/src/script/components/InputBar/RichTextEditor/FormatToolbar/common/isBlockquoteNode/isBlockquoteNode.ts similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/common/isBlockquoteNode/isBlockquoteNode.ts rename to src/script/components/InputBar/RichTextEditor/FormatToolbar/common/isBlockquoteNode/isBlockquoteNode.ts diff --git a/src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/common/isCodeBlockNode/isCodeBlockNode.test.ts b/src/script/components/InputBar/RichTextEditor/FormatToolbar/common/isCodeBlockNode/isCodeBlockNode.test.ts similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/common/isCodeBlockNode/isCodeBlockNode.test.ts rename to src/script/components/InputBar/RichTextEditor/FormatToolbar/common/isCodeBlockNode/isCodeBlockNode.test.ts diff --git a/src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/common/isCodeBlockNode/isCodeBlockNode.ts b/src/script/components/InputBar/RichTextEditor/FormatToolbar/common/isCodeBlockNode/isCodeBlockNode.ts similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/common/isCodeBlockNode/isCodeBlockNode.ts rename to src/script/components/InputBar/RichTextEditor/FormatToolbar/common/isCodeBlockNode/isCodeBlockNode.ts diff --git a/src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/common/isHeadingNode/isHeadingNode.test.ts b/src/script/components/InputBar/RichTextEditor/FormatToolbar/common/isHeadingNode/isHeadingNode.test.ts similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/common/isHeadingNode/isHeadingNode.test.ts rename to src/script/components/InputBar/RichTextEditor/FormatToolbar/common/isHeadingNode/isHeadingNode.test.ts diff --git a/src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/common/isHeadingNode/isHeadingNode.ts b/src/script/components/InputBar/RichTextEditor/FormatToolbar/common/isHeadingNode/isHeadingNode.ts similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/common/isHeadingNode/isHeadingNode.ts rename to src/script/components/InputBar/RichTextEditor/FormatToolbar/common/isHeadingNode/isHeadingNode.ts diff --git a/src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/common/isListNode/isListNode.test.ts b/src/script/components/InputBar/RichTextEditor/FormatToolbar/common/isListNode/isListNode.test.ts similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/common/isListNode/isListNode.test.ts rename to src/script/components/InputBar/RichTextEditor/FormatToolbar/common/isListNode/isListNode.test.ts diff --git a/src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/common/isListNode/isListNode.ts b/src/script/components/InputBar/RichTextEditor/FormatToolbar/common/isListNode/isListNode.ts similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/common/isListNode/isListNode.ts rename to src/script/components/InputBar/RichTextEditor/FormatToolbar/common/isListNode/isListNode.ts diff --git a/src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/useBlockquoteState/useBlockquoteState.ts b/src/script/components/InputBar/RichTextEditor/FormatToolbar/useBlockquoteState/useBlockquoteState.ts similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/useBlockquoteState/useBlockquoteState.ts rename to src/script/components/InputBar/RichTextEditor/FormatToolbar/useBlockquoteState/useBlockquoteState.ts diff --git a/src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/useCodeBlockState/useCodeBlockState.ts b/src/script/components/InputBar/RichTextEditor/FormatToolbar/useCodeBlockState/useCodeBlockState.ts similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/useCodeBlockState/useCodeBlockState.ts rename to src/script/components/InputBar/RichTextEditor/FormatToolbar/useCodeBlockState/useCodeBlockState.ts diff --git a/src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/useHeadingState/headingCommand.ts b/src/script/components/InputBar/RichTextEditor/FormatToolbar/useHeadingState/headingCommand.ts similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/useHeadingState/headingCommand.ts rename to src/script/components/InputBar/RichTextEditor/FormatToolbar/useHeadingState/headingCommand.ts diff --git a/src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/useHeadingState/useHeadingState.ts b/src/script/components/InputBar/RichTextEditor/FormatToolbar/useHeadingState/useHeadingState.ts similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/useHeadingState/useHeadingState.ts rename to src/script/components/InputBar/RichTextEditor/FormatToolbar/useHeadingState/useHeadingState.ts diff --git a/src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/useLinkState/createNewLink/createNewLink.ts b/src/script/components/InputBar/RichTextEditor/FormatToolbar/useLinkState/createNewLink/createNewLink.ts similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/useLinkState/createNewLink/createNewLink.ts rename to src/script/components/InputBar/RichTextEditor/FormatToolbar/useLinkState/createNewLink/createNewLink.ts diff --git a/src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/useLinkState/getSelectedNode/getSelectedNode.ts b/src/script/components/InputBar/RichTextEditor/FormatToolbar/useLinkState/getSelectedNode/getSelectedNode.ts similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/useLinkState/getSelectedNode/getSelectedNode.ts rename to src/script/components/InputBar/RichTextEditor/FormatToolbar/useLinkState/getSelectedNode/getSelectedNode.ts diff --git a/src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/useLinkState/useLinkEditing/useLinkEditing.ts b/src/script/components/InputBar/RichTextEditor/FormatToolbar/useLinkState/useLinkEditing/useLinkEditing.ts similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/useLinkState/useLinkEditing/useLinkEditing.ts rename to src/script/components/InputBar/RichTextEditor/FormatToolbar/useLinkState/useLinkEditing/useLinkEditing.ts diff --git a/src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/useLinkState/useLinkState.ts b/src/script/components/InputBar/RichTextEditor/FormatToolbar/useLinkState/useLinkState.ts similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/useLinkState/useLinkState.ts rename to src/script/components/InputBar/RichTextEditor/FormatToolbar/useLinkState/useLinkState.ts diff --git a/src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/useLinkState/useModalState/useModalState.ts b/src/script/components/InputBar/RichTextEditor/FormatToolbar/useLinkState/useModalState/useModalState.ts similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/useLinkState/useModalState/useModalState.ts rename to src/script/components/InputBar/RichTextEditor/FormatToolbar/useLinkState/useModalState/useModalState.ts diff --git a/src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/useListState/useListState.ts b/src/script/components/InputBar/RichTextEditor/FormatToolbar/useListState/useListState.ts similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/useListState/useListState.ts rename to src/script/components/InputBar/RichTextEditor/FormatToolbar/useListState/useListState.ts diff --git a/src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/useToolbarState/useToolbarState.ts b/src/script/components/InputBar/RichTextEditor/FormatToolbar/useToolbarState/useToolbarState.ts similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/components/FormatToolbar/useToolbarState/useToolbarState.ts rename to src/script/components/InputBar/RichTextEditor/FormatToolbar/useToolbarState/useToolbarState.ts diff --git a/src/script/components/InputBar/components/RichTextEditor/components/Placeholder/Placeholder.tsx b/src/script/components/InputBar/RichTextEditor/Placeholder/Placeholder.tsx similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/components/Placeholder/Placeholder.tsx rename to src/script/components/InputBar/RichTextEditor/Placeholder/Placeholder.tsx diff --git a/src/script/components/InputBar/components/RichTextEditor/RichTextEditor.tsx b/src/script/components/InputBar/RichTextEditor/RichTextEditor.tsx similarity index 96% rename from src/script/components/InputBar/components/RichTextEditor/RichTextEditor.tsx rename to src/script/components/InputBar/RichTextEditor/RichTextEditor.tsx index 63255599a76..15ef0845c81 100644 --- a/src/script/components/InputBar/components/RichTextEditor/RichTextEditor.tsx +++ b/src/script/components/InputBar/RichTextEditor/RichTextEditor.tsx @@ -17,7 +17,7 @@ * */ -import {ReactElement, useRef} from 'react'; +import {ReactNode, useRef} from 'react'; import {$convertToMarkdownString} from '@lexical/markdown'; import {ClearEditorPlugin} from '@lexical/react/LexicalClearEditorPlugin'; @@ -35,9 +35,9 @@ import {DraftState} from 'Components/InputBar/util/DraftStateUtil'; import {ContentMessage} from 'src/script/entity/message/ContentMessage'; import {User} from 'src/script/entity/User'; -import {FormatToolbar} from './components/FormatToolbar/FormatToolbar'; -import {Placeholder} from './components/Placeholder/Placeholder'; import {editorConfig} from './editorConfig'; +import {FormatToolbar} from './FormatToolbar/FormatToolbar'; +import {Placeholder} from './Placeholder/Placeholder'; import {AutoFocusPlugin} from './plugins/AutoFocusPlugin/AutoFocusPlugin'; import {AutoLinkPlugin} from './plugins/AutoLinkPlugin/AutoLinkPlugin'; import {BlockquotePlugin} from './plugins/BlockquotePlugin/BlockquotePlugin'; @@ -60,7 +60,7 @@ import {parseMentions} from './utils/parseMentions'; import {transformMessage} from './utils/transformMessage'; import {useEditorDraftState} from './utils/useEditorDraftState'; -import {MentionEntity} from '../../../../message/MentionEntity'; +import {MentionEntity} from '../../../message/MentionEntity'; export type RichTextContent = { text: string; @@ -71,7 +71,7 @@ interface RichTextEditorProps { placeholder: string; replaceEmojis: boolean; editedMessage?: ContentMessage; - children: ReactElement; + children: ReactNode; hasLocalEphemeralTimer: boolean; showFormatToolbar: boolean; showMarkdownPreview: boolean; diff --git a/src/script/components/InputBar/components/RichTextEditor/components/SendMessageButton/SendMessageButton.tsx b/src/script/components/InputBar/RichTextEditor/SendMessageButton/SendMessageButton.tsx similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/components/SendMessageButton/SendMessageButton.tsx rename to src/script/components/InputBar/RichTextEditor/SendMessageButton/SendMessageButton.tsx diff --git a/src/script/components/InputBar/components/RichTextEditor/components/SendMessageButton/index.ts b/src/script/components/InputBar/RichTextEditor/SendMessageButton/index.ts similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/components/SendMessageButton/index.ts rename to src/script/components/InputBar/RichTextEditor/SendMessageButton/index.ts diff --git a/src/script/components/InputBar/components/RichTextEditor/editorConfig.ts b/src/script/components/InputBar/RichTextEditor/editorConfig.ts similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/editorConfig.ts rename to src/script/components/InputBar/RichTextEditor/editorConfig.ts diff --git a/src/script/components/InputBar/components/RichTextEditor/index.ts b/src/script/components/InputBar/RichTextEditor/index.ts similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/index.ts rename to src/script/components/InputBar/RichTextEditor/index.ts diff --git a/src/script/components/InputBar/components/RichTextEditor/nodes/EmojiNode.ts b/src/script/components/InputBar/RichTextEditor/nodes/EmojiNode.ts similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/nodes/EmojiNode.ts rename to src/script/components/InputBar/RichTextEditor/nodes/EmojiNode.ts diff --git a/src/script/components/InputBar/components/RichTextEditor/nodes/Mention.tsx b/src/script/components/InputBar/RichTextEditor/nodes/Mention.tsx similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/nodes/Mention.tsx rename to src/script/components/InputBar/RichTextEditor/nodes/Mention.tsx diff --git a/src/script/components/InputBar/components/RichTextEditor/nodes/MentionNode.tsx b/src/script/components/InputBar/RichTextEditor/nodes/MentionNode.tsx similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/nodes/MentionNode.tsx rename to src/script/components/InputBar/RichTextEditor/nodes/MentionNode.tsx diff --git a/src/script/components/InputBar/components/RichTextEditor/plugins/AutoFocusPlugin/AutoFocusPlugin.tsx b/src/script/components/InputBar/RichTextEditor/plugins/AutoFocusPlugin/AutoFocusPlugin.tsx similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/plugins/AutoFocusPlugin/AutoFocusPlugin.tsx rename to src/script/components/InputBar/RichTextEditor/plugins/AutoFocusPlugin/AutoFocusPlugin.tsx diff --git a/src/script/components/InputBar/components/RichTextEditor/plugins/AutoLinkPlugin/AutoLinkPlugin.tsx b/src/script/components/InputBar/RichTextEditor/plugins/AutoLinkPlugin/AutoLinkPlugin.tsx similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/plugins/AutoLinkPlugin/AutoLinkPlugin.tsx rename to src/script/components/InputBar/RichTextEditor/plugins/AutoLinkPlugin/AutoLinkPlugin.tsx diff --git a/src/script/components/InputBar/components/RichTextEditor/plugins/BlockquotePlugin/BlockquotePlugin.tsx b/src/script/components/InputBar/RichTextEditor/plugins/BlockquotePlugin/BlockquotePlugin.tsx similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/plugins/BlockquotePlugin/BlockquotePlugin.tsx rename to src/script/components/InputBar/RichTextEditor/plugins/BlockquotePlugin/BlockquotePlugin.tsx diff --git a/src/script/components/InputBar/components/RichTextEditor/plugins/CodeHighlightPlugin/CodeHighlightPlugin.tsx b/src/script/components/InputBar/RichTextEditor/plugins/CodeHighlightPlugin/CodeHighlightPlugin.tsx similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/plugins/CodeHighlightPlugin/CodeHighlightPlugin.tsx rename to src/script/components/InputBar/RichTextEditor/plugins/CodeHighlightPlugin/CodeHighlightPlugin.tsx diff --git a/src/script/components/InputBar/components/RichTextEditor/plugins/DraftStatePlugin/DraftStatePlugin.tsx b/src/script/components/InputBar/RichTextEditor/plugins/DraftStatePlugin/DraftStatePlugin.tsx similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/plugins/DraftStatePlugin/DraftStatePlugin.tsx rename to src/script/components/InputBar/RichTextEditor/plugins/DraftStatePlugin/DraftStatePlugin.tsx diff --git a/src/script/components/InputBar/components/RichTextEditor/plugins/EditedMessagePlugin/EditedMessagePlugin.tsx b/src/script/components/InputBar/RichTextEditor/plugins/EditedMessagePlugin/EditedMessagePlugin.tsx similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/plugins/EditedMessagePlugin/EditedMessagePlugin.tsx rename to src/script/components/InputBar/RichTextEditor/plugins/EditedMessagePlugin/EditedMessagePlugin.tsx diff --git a/src/script/components/InputBar/components/RichTextEditor/plugins/EditedMessagePlugin/getMentionMarkdownTransformer/getMentionMarkdownTransformer.ts b/src/script/components/InputBar/RichTextEditor/plugins/EditedMessagePlugin/getMentionMarkdownTransformer/getMentionMarkdownTransformer.ts similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/plugins/EditedMessagePlugin/getMentionMarkdownTransformer/getMentionMarkdownTransformer.ts rename to src/script/components/InputBar/RichTextEditor/plugins/EditedMessagePlugin/getMentionMarkdownTransformer/getMentionMarkdownTransformer.ts diff --git a/src/script/components/InputBar/components/RichTextEditor/plugins/EditedMessagePlugin/getMentionNodesFromMessage/getMentionNodesFromMessage.ts b/src/script/components/InputBar/RichTextEditor/plugins/EditedMessagePlugin/getMentionNodesFromMessage/getMentionNodesFromMessage.ts similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/plugins/EditedMessagePlugin/getMentionNodesFromMessage/getMentionNodesFromMessage.ts rename to src/script/components/InputBar/RichTextEditor/plugins/EditedMessagePlugin/getMentionNodesFromMessage/getMentionNodesFromMessage.ts diff --git a/src/script/components/InputBar/components/RichTextEditor/plugins/EditedMessagePlugin/getRawMarkdownFromMessage/getRawMarkdownFromMessage.ts b/src/script/components/InputBar/RichTextEditor/plugins/EditedMessagePlugin/getRawMarkdownFromMessage/getRawMarkdownFromMessage.ts similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/plugins/EditedMessagePlugin/getRawMarkdownFromMessage/getRawMarkdownFromMessage.ts rename to src/script/components/InputBar/RichTextEditor/plugins/EditedMessagePlugin/getRawMarkdownFromMessage/getRawMarkdownFromMessage.ts diff --git a/src/script/components/InputBar/components/RichTextEditor/plugins/EditedMessagePlugin/wrapMentionsWithTags/wrapMentionsWithTags.ts b/src/script/components/InputBar/RichTextEditor/plugins/EditedMessagePlugin/wrapMentionsWithTags/wrapMentionsWithTags.ts similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/plugins/EditedMessagePlugin/wrapMentionsWithTags/wrapMentionsWithTags.ts rename to src/script/components/InputBar/RichTextEditor/plugins/EditedMessagePlugin/wrapMentionsWithTags/wrapMentionsWithTags.ts diff --git a/src/script/components/InputBar/components/RichTextEditor/plugins/EmojiPickerPlugin/EmojiItem.styles.ts b/src/script/components/InputBar/RichTextEditor/plugins/EmojiPickerPlugin/EmojiItem.styles.ts similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/plugins/EmojiPickerPlugin/EmojiItem.styles.ts rename to src/script/components/InputBar/RichTextEditor/plugins/EmojiPickerPlugin/EmojiItem.styles.ts diff --git a/src/script/components/InputBar/components/RichTextEditor/plugins/EmojiPickerPlugin/EmojiItem.tsx b/src/script/components/InputBar/RichTextEditor/plugins/EmojiPickerPlugin/EmojiItem.tsx similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/plugins/EmojiPickerPlugin/EmojiItem.tsx rename to src/script/components/InputBar/RichTextEditor/plugins/EmojiPickerPlugin/EmojiItem.tsx diff --git a/src/script/components/InputBar/components/RichTextEditor/plugins/EmojiPickerPlugin/EmojiPickerPlugin.tsx b/src/script/components/InputBar/RichTextEditor/plugins/EmojiPickerPlugin/EmojiPickerPlugin.tsx similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/plugins/EmojiPickerPlugin/EmojiPickerPlugin.tsx rename to src/script/components/InputBar/RichTextEditor/plugins/EmojiPickerPlugin/EmojiPickerPlugin.tsx diff --git a/src/script/components/InputBar/components/RichTextEditor/plugins/EmojiPickerPlugin/index.ts b/src/script/components/InputBar/RichTextEditor/plugins/EmojiPickerPlugin/index.ts similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/plugins/EmojiPickerPlugin/index.ts rename to src/script/components/InputBar/RichTextEditor/plugins/EmojiPickerPlugin/index.ts diff --git a/src/script/components/InputBar/components/RichTextEditor/plugins/GlobalEventsPlugin/GlobalEventsPlugin.tsx b/src/script/components/InputBar/RichTextEditor/plugins/GlobalEventsPlugin/GlobalEventsPlugin.tsx similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/plugins/GlobalEventsPlugin/GlobalEventsPlugin.tsx rename to src/script/components/InputBar/RichTextEditor/plugins/GlobalEventsPlugin/GlobalEventsPlugin.tsx diff --git a/src/script/components/InputBar/components/RichTextEditor/plugins/HistoryPlugin/HistoryPlugin.tsx b/src/script/components/InputBar/RichTextEditor/plugins/HistoryPlugin/HistoryPlugin.tsx similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/plugins/HistoryPlugin/HistoryPlugin.tsx rename to src/script/components/InputBar/RichTextEditor/plugins/HistoryPlugin/HistoryPlugin.tsx diff --git a/src/script/components/InputBar/components/RichTextEditor/plugins/InlineEmojiReplacementPlugin/InlineEmojiReplacementPlugin.tsx b/src/script/components/InputBar/RichTextEditor/plugins/InlineEmojiReplacementPlugin/InlineEmojiReplacementPlugin.tsx similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/plugins/InlineEmojiReplacementPlugin/InlineEmojiReplacementPlugin.tsx rename to src/script/components/InputBar/RichTextEditor/plugins/InlineEmojiReplacementPlugin/InlineEmojiReplacementPlugin.tsx diff --git a/src/script/components/InputBar/components/RichTextEditor/plugins/InlineEmojiReplacementPlugin/index.ts b/src/script/components/InputBar/RichTextEditor/plugins/InlineEmojiReplacementPlugin/index.ts similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/plugins/InlineEmojiReplacementPlugin/index.ts rename to src/script/components/InputBar/RichTextEditor/plugins/InlineEmojiReplacementPlugin/index.ts diff --git a/src/script/components/InputBar/components/RichTextEditor/plugins/InlineEmojiReplacementPlugin/inlineReplacements.ts b/src/script/components/InputBar/RichTextEditor/plugins/InlineEmojiReplacementPlugin/inlineReplacements.ts similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/plugins/InlineEmojiReplacementPlugin/inlineReplacements.ts rename to src/script/components/InputBar/RichTextEditor/plugins/InlineEmojiReplacementPlugin/inlineReplacements.ts diff --git a/src/script/components/InputBar/components/RichTextEditor/plugins/LinkPlugin/LinkPlugin.tsx b/src/script/components/InputBar/RichTextEditor/plugins/LinkPlugin/LinkPlugin.tsx similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/plugins/LinkPlugin/LinkPlugin.tsx rename to src/script/components/InputBar/RichTextEditor/plugins/LinkPlugin/LinkPlugin.tsx diff --git a/src/script/components/InputBar/components/RichTextEditor/plugins/ListIndentationPlugin/ListIndentationPlugin.ts b/src/script/components/InputBar/RichTextEditor/plugins/ListIndentationPlugin/ListIndentationPlugin.ts similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/plugins/ListIndentationPlugin/ListIndentationPlugin.ts rename to src/script/components/InputBar/RichTextEditor/plugins/ListIndentationPlugin/ListIndentationPlugin.ts diff --git a/src/script/components/InputBar/components/RichTextEditor/plugins/ListMaxIndentLevelPlugin/ListMaxIndentLevelPlugin.tsx b/src/script/components/InputBar/RichTextEditor/plugins/ListMaxIndentLevelPlugin/ListMaxIndentLevelPlugin.tsx similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/plugins/ListMaxIndentLevelPlugin/ListMaxIndentLevelPlugin.tsx rename to src/script/components/InputBar/RichTextEditor/plugins/ListMaxIndentLevelPlugin/ListMaxIndentLevelPlugin.tsx diff --git a/src/script/components/InputBar/components/RichTextEditor/plugins/MentionsPlugin/MentionSuggestionsItem.tsx b/src/script/components/InputBar/RichTextEditor/plugins/MentionsPlugin/MentionSuggestionsItem.tsx similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/plugins/MentionsPlugin/MentionSuggestionsItem.tsx rename to src/script/components/InputBar/RichTextEditor/plugins/MentionsPlugin/MentionSuggestionsItem.tsx diff --git a/src/script/components/InputBar/components/RichTextEditor/plugins/MentionsPlugin/MentionsPlugin.tsx b/src/script/components/InputBar/RichTextEditor/plugins/MentionsPlugin/MentionsPlugin.tsx similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/plugins/MentionsPlugin/MentionsPlugin.tsx rename to src/script/components/InputBar/RichTextEditor/plugins/MentionsPlugin/MentionsPlugin.tsx diff --git a/src/script/components/InputBar/components/RichTextEditor/plugins/MentionsPlugin/index.ts b/src/script/components/InputBar/RichTextEditor/plugins/MentionsPlugin/index.ts similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/plugins/MentionsPlugin/index.ts rename to src/script/components/InputBar/RichTextEditor/plugins/MentionsPlugin/index.ts diff --git a/src/script/components/InputBar/components/RichTextEditor/plugins/PastePlugin/PastePlugin.tsx b/src/script/components/InputBar/RichTextEditor/plugins/PastePlugin/PastePlugin.tsx similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/plugins/PastePlugin/PastePlugin.tsx rename to src/script/components/InputBar/RichTextEditor/plugins/PastePlugin/PastePlugin.tsx diff --git a/src/script/components/InputBar/components/RichTextEditor/plugins/ReplaceCarriageReturnPlugin/ReplaceCarriageReturnPlugin.ts b/src/script/components/InputBar/RichTextEditor/plugins/ReplaceCarriageReturnPlugin/ReplaceCarriageReturnPlugin.ts similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/plugins/ReplaceCarriageReturnPlugin/ReplaceCarriageReturnPlugin.ts rename to src/script/components/InputBar/RichTextEditor/plugins/ReplaceCarriageReturnPlugin/ReplaceCarriageReturnPlugin.ts diff --git a/src/script/components/InputBar/components/RichTextEditor/plugins/SendPlugin/SendPlugin.tsx b/src/script/components/InputBar/RichTextEditor/plugins/SendPlugin/SendPlugin.tsx similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/plugins/SendPlugin/SendPlugin.tsx rename to src/script/components/InputBar/RichTextEditor/plugins/SendPlugin/SendPlugin.tsx diff --git a/src/script/components/InputBar/components/RichTextEditor/plugins/TypeaheadMenuPlugin/TypeaheadMenuPlugin.tsx b/src/script/components/InputBar/RichTextEditor/plugins/TypeaheadMenuPlugin/TypeaheadMenuPlugin.tsx similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/plugins/TypeaheadMenuPlugin/TypeaheadMenuPlugin.tsx rename to src/script/components/InputBar/RichTextEditor/plugins/TypeaheadMenuPlugin/TypeaheadMenuPlugin.tsx diff --git a/src/script/components/InputBar/components/RichTextEditor/theme.ts b/src/script/components/InputBar/RichTextEditor/theme.ts similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/theme.ts rename to src/script/components/InputBar/RichTextEditor/theme.ts diff --git a/src/script/components/InputBar/components/RichTextEditor/utils/generateNodes.ts b/src/script/components/InputBar/RichTextEditor/utils/generateNodes.ts similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/utils/generateNodes.ts rename to src/script/components/InputBar/RichTextEditor/utils/generateNodes.ts diff --git a/src/script/components/InputBar/components/RichTextEditor/utils/getDomRangeRect.ts b/src/script/components/InputBar/RichTextEditor/utils/getDomRangeRect.ts similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/utils/getDomRangeRect.ts rename to src/script/components/InputBar/RichTextEditor/utils/getDomRangeRect.ts diff --git a/src/script/components/InputBar/components/RichTextEditor/utils/getSelectionInfo.ts b/src/script/components/InputBar/RichTextEditor/utils/getSelectionInfo.ts similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/utils/getSelectionInfo.ts rename to src/script/components/InputBar/RichTextEditor/utils/getSelectionInfo.ts diff --git a/src/script/components/InputBar/components/RichTextEditor/utils/markdownTransformers.ts b/src/script/components/InputBar/RichTextEditor/utils/markdownTransformers.ts similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/utils/markdownTransformers.ts rename to src/script/components/InputBar/RichTextEditor/utils/markdownTransformers.ts diff --git a/src/script/components/InputBar/components/RichTextEditor/utils/parseMentions.ts b/src/script/components/InputBar/RichTextEditor/utils/parseMentions.ts similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/utils/parseMentions.ts rename to src/script/components/InputBar/RichTextEditor/utils/parseMentions.ts diff --git a/src/script/components/InputBar/components/RichTextEditor/utils/transformMessage.ts b/src/script/components/InputBar/RichTextEditor/utils/transformMessage.ts similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/utils/transformMessage.ts rename to src/script/components/InputBar/RichTextEditor/utils/transformMessage.ts diff --git a/src/script/components/InputBar/components/RichTextEditor/utils/url.ts b/src/script/components/InputBar/RichTextEditor/utils/url.ts similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/utils/url.ts rename to src/script/components/InputBar/RichTextEditor/utils/url.ts diff --git a/src/script/components/InputBar/components/RichTextEditor/utils/useEditorDraftState.ts b/src/script/components/InputBar/RichTextEditor/utils/useEditorDraftState.ts similarity index 100% rename from src/script/components/InputBar/components/RichTextEditor/utils/useEditorDraftState.ts rename to src/script/components/InputBar/RichTextEditor/utils/useEditorDraftState.ts diff --git a/src/script/components/InputBar/components/TypingIndicator/TypingIndicator.styles.ts b/src/script/components/InputBar/TypingIndicator/TypingIndicator.styles.ts similarity index 100% rename from src/script/components/InputBar/components/TypingIndicator/TypingIndicator.styles.ts rename to src/script/components/InputBar/TypingIndicator/TypingIndicator.styles.ts diff --git a/src/script/components/InputBar/components/TypingIndicator/TypingIndicator.test.tsx b/src/script/components/InputBar/TypingIndicator/TypingIndicator.test.tsx similarity index 97% rename from src/script/components/InputBar/components/TypingIndicator/TypingIndicator.test.tsx rename to src/script/components/InputBar/TypingIndicator/TypingIndicator.test.tsx index 65f4d612911..c78669facf7 100644 --- a/src/script/components/InputBar/components/TypingIndicator/TypingIndicator.test.tsx +++ b/src/script/components/InputBar/TypingIndicator/TypingIndicator.test.tsx @@ -21,7 +21,7 @@ import {render} from '@testing-library/react'; import {act} from 'react-dom/test-utils'; import {TypingIndicator, TypingIndicatorProps} from './TypingIndicator'; -import {useTypingIndicatorState} from './TypingIndicator.state'; +import {useTypingIndicatorState} from './useTypingIndicatorState/useTypingIndicatorState'; import {User} from '../../../../entity/User'; diff --git a/src/script/components/InputBar/components/TypingIndicator/TypingIndicator.tsx b/src/script/components/InputBar/TypingIndicator/TypingIndicator.tsx similarity index 93% rename from src/script/components/InputBar/components/TypingIndicator/TypingIndicator.tsx rename to src/script/components/InputBar/TypingIndicator/TypingIndicator.tsx index 7482e8cf2e7..5360488378a 100644 --- a/src/script/components/InputBar/components/TypingIndicator/TypingIndicator.tsx +++ b/src/script/components/InputBar/TypingIndicator/TypingIndicator.tsx @@ -17,13 +17,10 @@ * */ -import {FC} from 'react'; - import {Avatar, AVATAR_SIZE} from 'Components/Avatar'; import * as Icon from 'Components/Icon'; import {t} from 'Util/LocalizerUtil'; -import {useTypingIndicatorState} from './TypingIndicator.state'; import { dotOneStyles, dotThreeStyles, @@ -33,12 +30,13 @@ import { indicatorTitleStyles, wrapperStyles, } from './TypingIndicator.styles'; +import {useTypingIndicatorState} from './useTypingIndicatorState/useTypingIndicatorState'; export interface TypingIndicatorProps { conversationId: string; } -const TypingIndicator: FC = ({conversationId}) => { +export const TypingIndicator = ({conversationId}: TypingIndicatorProps) => { const users = useTypingIndicatorState(state => state.getTypingUsersInConversation(conversationId)); const usersCount = users.length; @@ -93,5 +91,3 @@ const TypingIndicator: FC = ({conversationId}) => { ); }; - -export {TypingIndicator}; diff --git a/src/script/components/InputBar/components/TypingIndicator/index.ts b/src/script/components/InputBar/TypingIndicator/index.ts similarity index 90% rename from src/script/components/InputBar/components/TypingIndicator/index.ts rename to src/script/components/InputBar/TypingIndicator/index.ts index 3154396ef02..eaf30885e62 100644 --- a/src/script/components/InputBar/components/TypingIndicator/index.ts +++ b/src/script/components/InputBar/TypingIndicator/index.ts @@ -21,6 +21,6 @@ import {TIME_IN_MILLIS} from 'Util/TimeUtil'; export * from './TypingIndicator'; -export {useTypingIndicatorState} from './TypingIndicator.state'; +export {useTypingIndicatorState} from './useTypingIndicatorState/useTypingIndicatorState'; export const TYPING_TIMEOUT = TIME_IN_MILLIS.SECOND * 10; diff --git a/src/script/components/InputBar/components/TypingIndicator/TypingIndicator.state.tsx b/src/script/components/InputBar/TypingIndicator/useTypingIndicatorState/useTypingIndicatorState.ts similarity index 95% rename from src/script/components/InputBar/components/TypingIndicator/TypingIndicator.state.tsx rename to src/script/components/InputBar/TypingIndicator/useTypingIndicatorState/useTypingIndicatorState.ts index fb9d5a8620e..acf5d3a9929 100644 --- a/src/script/components/InputBar/components/TypingIndicator/TypingIndicator.state.tsx +++ b/src/script/components/InputBar/TypingIndicator/useTypingIndicatorState/useTypingIndicatorState.ts @@ -36,7 +36,7 @@ type TypingIndicatorState = { getTypingUser: (user: User, conversationId: string) => TypingUser | undefined; }; -const useTypingIndicatorState = create((set, get) => ({ +export const useTypingIndicatorState = create((set, get) => ({ typingUsers: [], addTypingUser: ({conversationId, user, timerId}) => set(state => { @@ -65,5 +65,3 @@ const useTypingIndicatorState = create((set, get) => ({ .map(typingUser => typingUser.user), clearTypingUsers: () => set({typingUsers: []}), })); - -export {useTypingIndicatorState}; diff --git a/src/script/components/InputBar/components/common/FormatSeparator/FormatSeparator.tsx b/src/script/components/InputBar/common/FormatSeparator/FormatSeparator.tsx similarity index 100% rename from src/script/components/InputBar/components/common/FormatSeparator/FormatSeparator.tsx rename to src/script/components/InputBar/common/FormatSeparator/FormatSeparator.tsx diff --git a/src/script/components/InputBar/useDraftState/useDraftState.ts b/src/script/components/InputBar/useDraftState/useDraftState.ts new file mode 100644 index 00000000000..97dae1d7963 --- /dev/null +++ b/src/script/components/InputBar/useDraftState/useDraftState.ts @@ -0,0 +1,65 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import {useCallback} from 'react'; + +import {LexicalEditor, CLEAR_EDITOR_COMMAND} from 'lexical'; + +import {MessageRepository} from 'src/script/conversation/MessageRepository'; +import {Conversation} from 'src/script/entity/Conversation'; +import {StorageRepository} from 'src/script/storage'; +import {sanitizeMarkdown} from 'Util/MarkdownUtil'; + +import {loadDraftState as loadDraft, saveDraftState as saveDraft} from '../util/DraftStateUtil'; + +interface UseDraftStateProps { + conversation: Conversation; + storageRepository: StorageRepository; + messageRepository: MessageRepository; + editorRef: React.RefObject; +} + +export const useDraftState = ({conversation, storageRepository, messageRepository, editorRef}: UseDraftStateProps) => { + const resetDraftState = useCallback(() => { + editorRef.current?.dispatchCommand(CLEAR_EDITOR_COMMAND, undefined); + }, [editorRef]); + + const saveDraftState = useCallback( + async (editorState: string, plainMessage: string, replyId?: string) => { + await saveDraft({ + storageRepository, + conversation, + editorState, + plainMessage: sanitizeMarkdown(plainMessage), + replyId, + }); + }, + [conversation, storageRepository], + ); + + const loadDraftState = useCallback(async () => { + return loadDraft(conversation, storageRepository, messageRepository); + }, [conversation, messageRepository, storageRepository]); + + return { + resetDraftState, + saveDraftState, + loadDraftState, + }; +}; diff --git a/src/script/components/InputBar/hooks/useEmojiPicker/useEmojiPicker.ts b/src/script/components/InputBar/useEmojiPicker/useEmojiPicker.ts similarity index 100% rename from src/script/components/InputBar/hooks/useEmojiPicker/useEmojiPicker.ts rename to src/script/components/InputBar/useEmojiPicker/useEmojiPicker.ts diff --git a/src/script/components/InputBar/useFileHandling/useFileHandling.ts b/src/script/components/InputBar/useFileHandling/useFileHandling.ts new file mode 100644 index 00000000000..6f0fff9c61a --- /dev/null +++ b/src/script/components/InputBar/useFileHandling/useFileHandling.ts @@ -0,0 +1,84 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import {useEffect, useState} from 'react'; + +import {t} from 'Util/LocalizerUtil'; +import {formatLocale} from 'Util/TimeUtil'; +import {getFileExtension} from 'Util/util'; + +interface UseFileHandlingProps { + uploadDroppedFiles: (files: File[]) => void; +} + +export const useFileHandling = ({uploadDroppedFiles}: UseFileHandlingProps) => { + const [pastedFile, setPastedFile] = useState(null); + + const clearPastedFile = () => setPastedFile(null); + + const sendPastedFile = () => { + if (pastedFile) { + uploadDroppedFiles([pastedFile]); + clearPastedFile(); + } + }; + + const handlePasteFiles = (files: FileList): void => { + const [pastedFile] = files; + + if (!pastedFile) { + return; + } + const {lastModified} = pastedFile; + + const date = formatLocale(lastModified || new Date(), 'PP, pp'); + const fileName = `${t('conversationSendPastedFile', {date})}.${getFileExtension(pastedFile.name)}`; + + const newFile = new File([pastedFile], fileName, { + type: pastedFile.type, + }); + + setPastedFile(newFile); + }; + + const sendImageOnEnterClick = (event: KeyboardEvent) => { + if (event.key === 'Enter' && !event.shiftKey && !event.altKey && !event.metaKey) { + sendPastedFile(); + } + }; + + useEffect(() => { + if (!pastedFile) { + return () => undefined; + } + + window.addEventListener('keydown', sendImageOnEnterClick); + + return () => { + window.removeEventListener('keydown', sendImageOnEnterClick); + }; + }, [pastedFile]); + + return { + pastedFile, + clearPastedFile, + sendPastedFile, + handlePasteFiles, + }; +}; diff --git a/src/script/components/InputBar/hooks/useFilePaste/useFilePaste.test.ts b/src/script/components/InputBar/useFilePaste/useFilePaste.test.ts similarity index 100% rename from src/script/components/InputBar/hooks/useFilePaste/useFilePaste.test.ts rename to src/script/components/InputBar/useFilePaste/useFilePaste.test.ts diff --git a/src/script/components/InputBar/hooks/useFilePaste/useFilePaste.ts b/src/script/components/InputBar/useFilePaste/useFilePaste.ts similarity index 100% rename from src/script/components/InputBar/hooks/useFilePaste/useFilePaste.ts rename to src/script/components/InputBar/useFilePaste/useFilePaste.ts diff --git a/src/script/components/InputBar/hooks/useFormatToolbar/useFormatToolbar.ts b/src/script/components/InputBar/useFormatToolbar/useFormatToolbar.ts similarity index 100% rename from src/script/components/InputBar/hooks/useFormatToolbar/useFormatToolbar.ts rename to src/script/components/InputBar/useFormatToolbar/useFormatToolbar.ts diff --git a/src/script/components/InputBar/useGiphy/useGiphy.ts b/src/script/components/InputBar/useGiphy/useGiphy.ts new file mode 100644 index 00000000000..33c7cdbe2c8 --- /dev/null +++ b/src/script/components/InputBar/useGiphy/useGiphy.ts @@ -0,0 +1,43 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import {useMemo} from 'react'; + +interface UseGiphyProps { + text: string; + maxLength: number; + isMessageFormatButtonsFlagEnabled: boolean; + openGiphy: (inputValue: string) => void; +} + +export const useGiphy = ({text, maxLength, isMessageFormatButtonsFlagEnabled, openGiphy}: UseGiphyProps) => { + const showGiphyButton = useMemo(() => { + if (isMessageFormatButtonsFlagEnabled) { + return text.length > 0; + } + return text.length > 0 && text.length <= maxLength; + }, [text.length, maxLength, isMessageFormatButtonsFlagEnabled]); + + const handleGifClick = () => openGiphy(text); + + return { + showGiphyButton, + handleGifClick, + }; +}; diff --git a/src/script/components/InputBar/useMessageHandling/useMessageHandling.ts b/src/script/components/InputBar/useMessageHandling/useMessageHandling.ts new file mode 100644 index 00000000000..4e58531cbcc --- /dev/null +++ b/src/script/components/InputBar/useMessageHandling/useMessageHandling.ts @@ -0,0 +1,240 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import {useCallback, useEffect, useState} from 'react'; + +import {amplify} from 'amplify'; +import {LexicalEditor} from 'lexical'; + +import {WebAppEvents} from '@wireapp/webapp-events'; + +import {PrimaryModal} from 'Components/Modals/PrimaryModal'; +import {ConversationRepository} from 'src/script/conversation/ConversationRepository'; +import {ConversationVerificationState} from 'src/script/conversation/ConversationVerificationState'; +import {MessageRepository, OutgoingQuote} from 'src/script/conversation/MessageRepository'; +import {Conversation} from 'src/script/entity/Conversation'; +import {ContentMessage} from 'src/script/entity/message/ContentMessage'; +import {ConversationError} from 'src/script/error/ConversationError'; +import {EventRepository} from 'src/script/event/EventRepository'; +import {MentionEntity} from 'src/script/message/MentionEntity'; +import {MessageHasher} from 'src/script/message/MessageHasher'; +import {QuoteEntity} from 'src/script/message/QuoteEntity'; +import {t} from 'Util/LocalizerUtil'; + +interface UseMessageHandlingProps { + conversation: Conversation; + conversationRepository: ConversationRepository; + eventRepository: EventRepository; + messageRepository: MessageRepository; + editorRef: React.RefObject; + onResetDraftState: () => void; + onSaveDraft: (replyId?: string) => void; +} + +export const useMessageHandling = ({ + conversation, + conversationRepository, + eventRepository, + messageRepository, + editorRef, + onResetDraftState, + onSaveDraft, +}: UseMessageHandlingProps) => { + const [editedMessage, setEditedMessage] = useState(); + const [replyMessageEntity, setReplyMessageEntity] = useState(null); + + const generateQuote = async (): Promise => { + if (!replyMessageEntity) { + return undefined; + } + + const event = await eventRepository.eventService.loadEvent( + replyMessageEntity.conversation_id, + replyMessageEntity.id, + ); + if (!event) { + return undefined; + } + + const messageHash = await MessageHasher.hashEvent(event); + return new QuoteEntity({ + hash: messageHash, + messageId: replyMessageEntity.id, + userId: replyMessageEntity.from, + }) as OutgoingQuote; + }; + + const sendMessageEdit = (messageText: string, mentions: MentionEntity[]): void | Promise => { + const mentionEntities = mentions.slice(0); + cancelMessageEditing(true); + + if (!messageText.length && editedMessage) { + return messageRepository.deleteMessageForEveryone(conversation, editedMessage); + } + + if (editedMessage) { + messageRepository.sendMessageEdit(conversation, messageText, editedMessage, mentionEntities).catch(error => { + if (error.type !== ConversationError.TYPE.NO_MESSAGE_CHANGES) { + throw error; + } + }); + + cancelMessageReply(); + } + }; + + const sendTextMessage = (messageText: string, mentions: MentionEntity[]) => { + if (messageText.length) { + const mentionEntities = mentions.slice(0); + + void generateQuote().then(quoteEntity => { + void messageRepository.sendTextWithLinkPreview(conversation, messageText, mentionEntities, quoteEntity); + cancelMessageReply(); + }); + } + }; + + const sendMessage = (text: string, mentions: MentionEntity[]): void => { + const messageTrimmedStart = text.trimStart(); + const messageText = messageTrimmedStart.trimEnd(); + + if (editedMessage) { + void sendMessageEdit(messageText, mentions); + } else { + sendTextMessage(messageText, mentions); + } + + editorRef.current?.focus(); + onResetDraftState(); + }; + + const handleSendMessage = async (text: string, mentions: MentionEntity[]) => { + await conversationRepository.refreshMLSConversationVerificationState(conversation); + const isE2EIDegraded = conversation.mlsVerificationState() === ConversationVerificationState.DEGRADED; + + if (isE2EIDegraded) { + PrimaryModal.show(PrimaryModal.type.CONFIRM, { + secondaryAction: { + action: () => { + conversation.mlsVerificationState(ConversationVerificationState.UNVERIFIED); + sendMessage(text, mentions); + }, + text: t('conversation.E2EISendAnyway'), + }, + primaryAction: { + action: () => {}, + text: t('conversation.E2EICancel'), + }, + text: { + message: t('conversation.E2EIDegradedNewMessage'), + title: t('conversation.E2EIConversationNoLongerVerified'), + }, + }); + } else { + sendMessage(text, mentions); + } + }; + + const cancelMessageEditing = (resetDraft = true) => { + setEditedMessage(undefined); + setReplyMessageEntity(null); + + if (resetDraft) { + onResetDraftState(); + } + }; + + const cancelMessageReply = (resetDraft = true) => { + setReplyMessageEntity(null); + + if (resetDraft) { + onResetDraftState(); + } + }; + + const editMessage = (messageEntity?: ContentMessage) => { + if (messageEntity?.isEditable() && messageEntity !== editedMessage) { + cancelMessageReply(); + cancelMessageEditing(true); + setEditedMessage(messageEntity); + + const quote = messageEntity.quote(); + if (quote && conversation) { + void messageRepository + .getMessageInConversationById(conversation, quote.messageId) + .then(quotedMessage => setReplyMessageEntity(quotedMessage)); + } + } + }; + + const replyMessage = (messageEntity: ContentMessage): void => { + if (messageEntity?.isReplyable() && messageEntity !== replyMessageEntity) { + cancelMessageReply(false); + cancelMessageEditing(!!editedMessage); + setReplyMessageEntity(messageEntity); + onSaveDraft(messageEntity.id); + + editorRef.current?.focus(); + } + }; + + const handleRepliedMessageDeleted = useCallback( + (messageId: string) => { + if (replyMessageEntity?.id === messageId) { + setReplyMessageEntity(null); + } + }, + [replyMessageEntity], + ); + + const handleRepliedMessageUpdated = useCallback( + (originalMessageId: string, messageEntity: ContentMessage) => { + if (replyMessageEntity?.id === originalMessageId) { + setReplyMessageEntity(messageEntity); + } + }, + [replyMessageEntity], + ); + + useEffect(() => { + amplify.subscribe(WebAppEvents.CONVERSATION.MESSAGE.REPLY, replyMessage); + amplify.subscribe(WebAppEvents.CONVERSATION.MESSAGE.REMOVED, handleRepliedMessageDeleted); + amplify.subscribe(WebAppEvents.CONVERSATION.MESSAGE.UPDATED, handleRepliedMessageUpdated); + amplify.subscribe(WebAppEvents.CONVERSATION.MESSAGE.EDIT, editMessage); + + return () => { + amplify.unsubscribeAll(WebAppEvents.CONVERSATION.MESSAGE.REPLY); + amplify.unsubscribeAll(WebAppEvents.CONVERSATION.MESSAGE.REMOVED); + amplify.unsubscribeAll(WebAppEvents.CONVERSATION.MESSAGE.UPDATED); + amplify.unsubscribeAll(WebAppEvents.CONVERSATION.MESSAGE.EDIT); + }; + }, [handleRepliedMessageDeleted, handleRepliedMessageUpdated]); + + return { + editedMessage, + replyMessageEntity, + isEditing: !!editedMessage, + isReplying: !!replyMessageEntity, + handleSendMessage, + cancelMessageEditing, + cancelMessageReply, + editMessage, + replyMessage, + }; +}; diff --git a/src/script/components/InputBar/usePing/usePing.ts b/src/script/components/InputBar/usePing/usePing.ts new file mode 100644 index 00000000000..2d0e06bc337 --- /dev/null +++ b/src/script/components/InputBar/usePing/usePing.ts @@ -0,0 +1,77 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import {useState} from 'react'; + +import {PrimaryModal} from 'Components/Modals/PrimaryModal'; +import {MessageRepository} from 'src/script/conversation/MessageRepository'; +import {Conversation} from 'src/script/entity/Conversation'; +import {t} from 'Util/LocalizerUtil'; +import {TIME_IN_MILLIS} from 'Util/TimeUtil'; + +interface UsePingProps { + conversation: Conversation; + messageRepository: MessageRepository; + is1to1: boolean; + maxUsersWithoutAlert: number; + enablePingConfirmation: boolean; +} + +export const usePing = ({ + conversation, + messageRepository, + is1to1, + maxUsersWithoutAlert, + enablePingConfirmation, +}: UsePingProps) => { + const [isPingDisabled, setIsPingDisabled] = useState(false); + + const pingConversation = () => { + setIsPingDisabled(true); + void messageRepository.sendPing(conversation).then(() => { + window.setTimeout(() => setIsPingDisabled(false), TIME_IN_MILLIS.SECOND * 2); + }); + }; + + const handlePing = () => { + if (isPingDisabled) { + return; + } + + const totalConversationUsers = conversation.participating_user_ets().length; + if (!enablePingConfirmation || is1to1 || totalConversationUsers < maxUsersWithoutAlert) { + pingConversation(); + } else { + PrimaryModal.show(PrimaryModal.type.CONFIRM, { + primaryAction: { + action: pingConversation, + text: t('tooltipConversationPing'), + }, + text: { + title: t('conversationPingConfirmTitle', {memberCount: totalConversationUsers.toString()}), + }, + }); + } + }; + + return { + isPingDisabled, + handlePing, + }; +}; diff --git a/src/script/components/InputBar/hooks/useTypingIndicator/useTypingIndicator.test.ts b/src/script/components/InputBar/useTypingIndicator/useTypingIndicator.test.ts similarity index 100% rename from src/script/components/InputBar/hooks/useTypingIndicator/useTypingIndicator.test.ts rename to src/script/components/InputBar/useTypingIndicator/useTypingIndicator.test.ts diff --git a/src/script/components/InputBar/hooks/useTypingIndicator/useTypingIndicator.ts b/src/script/components/InputBar/useTypingIndicator/useTypingIndicator.ts similarity index 97% rename from src/script/components/InputBar/hooks/useTypingIndicator/useTypingIndicator.ts rename to src/script/components/InputBar/useTypingIndicator/useTypingIndicator.ts index 4130ff59140..0ead274b45c 100644 --- a/src/script/components/InputBar/hooks/useTypingIndicator/useTypingIndicator.ts +++ b/src/script/components/InputBar/useTypingIndicator/useTypingIndicator.ts @@ -19,7 +19,7 @@ import {useCallback, useEffect, useRef} from 'react'; -import {TYPING_TIMEOUT} from '../../components/TypingIndicator'; +import {TYPING_TIMEOUT} from '../TypingIndicator'; type TypingIndicatorProps = { text: string; From ab577ba1250ed4b0d96a21f466e1591273fd51cd Mon Sep 17 00:00:00 2001 From: Olaf Sulich Date: Mon, 17 Feb 2025 07:44:38 +0100 Subject: [PATCH 02/13] refactor(InputBar): small adjusmetns --- src/script/components/InputBar/InputBar.tsx | 10 +++++----- .../InputBarAvatar/InputBarAvatar.tsx | 9 +-------- .../AssetUploadButton/index.ts | 20 ------------------- .../InputBarControls/ControlButtons.test.tsx | 4 ++-- .../InputBarControls/ControlButtons.tsx | 8 ++++---- .../ImageUploadButton/index.ts | 20 ------------------- .../InputBarControls.tsx} | 15 +++++++------- .../MessageTimerButton/index.ts | 20 ------------------- .../SendMessageButton/SendMessageButton.tsx | 0 .../InputBarEditor/InputBarEditor.tsx | 6 ++++-- .../FormatButton/FormatButton.tsx | 0 .../FormatToolbar/FormatToolbar.styles.ts | 0 .../FormatToolbar/FormatToolbar.tsx | 3 +-- .../LinkDialog/LinkDialog.styles.ts | 0 .../FormatToolbar/LinkDialog/LinkDialog.tsx | 2 +- .../isBlockquoteNode/isBlockquoteNode.test.ts | 0 .../isBlockquoteNode/isBlockquoteNode.ts | 0 .../isCodeBlockNode/isCodeBlockNode.test.ts | 0 .../common/isCodeBlockNode/isCodeBlockNode.ts | 0 .../isHeadingNode/isHeadingNode.test.ts | 0 .../common/isHeadingNode/isHeadingNode.ts | 0 .../common/isListNode/isListNode.test.ts | 0 .../common/isListNode/isListNode.ts | 0 .../useBlockquoteState/useBlockquoteState.ts | 0 .../useCodeBlockState/useCodeBlockState.ts | 0 .../useHeadingState/headingCommand.ts | 0 .../useHeadingState/useHeadingState.ts | 0 .../createNewLink/createNewLink.ts | 2 +- .../getSelectedNode/getSelectedNode.ts | 0 .../useLinkEditing/useLinkEditing.ts | 0 .../useLinkState/useLinkState.ts | 2 +- .../useModalState/useModalState.ts | 0 .../useListState/useListState.ts | 0 .../useToolbarState/useToolbarState.ts | 0 .../Placeholder/Placeholder.tsx | 0 .../RichTextEditor/RichTextEditor.tsx | 10 ++-------- .../RichTextEditor/editorConfig.ts | 0 .../RichTextEditor/index.ts | 0 .../RichTextEditor/nodes/EmojiNode.ts | 0 .../RichTextEditor/nodes/Mention.tsx | 0 .../RichTextEditor/nodes/MentionNode.tsx | 0 .../AutoFocusPlugin/AutoFocusPlugin.tsx | 0 .../plugins/AutoLinkPlugin/AutoLinkPlugin.tsx | 0 .../BlockquotePlugin/BlockquotePlugin.tsx | 0 .../CodeHighlightPlugin.tsx | 0 .../DraftStatePlugin/DraftStatePlugin.tsx | 0 .../EditedMessagePlugin.tsx | 3 ++- .../getMentionMarkdownTransformer.ts | 0 .../getMentionNodesFromMessage.ts | 0 .../getRawMarkdownFromMessage.ts | 2 +- .../wrapMentionsWithTags.ts | 0 .../EmojiPickerPlugin/EmojiItem.styles.ts | 0 .../plugins/EmojiPickerPlugin/EmojiItem.tsx | 0 .../EmojiPickerPlugin/EmojiPickerPlugin.tsx | 0 .../plugins/EmojiPickerPlugin/index.ts | 0 .../GlobalEventsPlugin/GlobalEventsPlugin.tsx | 0 .../plugins/HistoryPlugin/HistoryPlugin.tsx | 0 .../InlineEmojiReplacementPlugin.tsx | 0 .../InlineEmojiReplacementPlugin/index.ts | 0 .../inlineReplacements.ts | 0 .../plugins/LinkPlugin/LinkPlugin.tsx | 0 .../ListIndentationPlugin.ts | 0 .../ListMaxIndentLevelPlugin.tsx | 0 .../MentionsPlugin/MentionSuggestionsItem.tsx | 0 .../plugins/MentionsPlugin/MentionsPlugin.tsx | 0 .../plugins/MentionsPlugin/index.ts | 0 .../plugins/PastePlugin/PastePlugin.tsx | 0 .../ReplaceCarriageReturnPlugin.ts | 0 .../plugins/SendPlugin/SendPlugin.tsx | 0 .../TypeaheadMenuPlugin.tsx | 0 .../RichTextEditor/theme.ts | 0 .../RichTextEditor/utils/generateNodes.ts | 0 .../RichTextEditor/utils/getDomRangeRect.ts | 0 .../RichTextEditor/utils/getSelectionInfo.ts | 0 .../utils/markdownTransformers.ts | 0 .../RichTextEditor/utils/parseMentions.ts | 0 .../RichTextEditor/utils/transformMessage.ts | 0 .../RichTextEditor/utils/url.ts | 0 .../utils/useEditorDraftState.ts | 0 .../components/InputBar/ReplyBar/ReplyBar.tsx | 2 +- .../TypingIndicator/TypingIndicator.test.tsx | 4 ++-- .../messageContent/messageContent.ts} | 9 +++++++-- 82 files changed, 43 insertions(+), 108 deletions(-) delete mode 100644 src/script/components/InputBar/InputBarControls/AssetUploadButton/index.ts delete mode 100644 src/script/components/InputBar/InputBarControls/ImageUploadButton/index.ts rename src/script/components/InputBar/{InputBarButtons/InputBarButtons.tsx => InputBarControls/InputBarControls.tsx} (89%) delete mode 100644 src/script/components/InputBar/InputBarControls/MessageTimerButton/index.ts rename src/script/components/InputBar/{RichTextEditor => InputBarControls}/SendMessageButton/SendMessageButton.tsx (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/FormatToolbar/FormatButton/FormatButton.tsx (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/FormatToolbar/FormatToolbar.styles.ts (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/FormatToolbar/FormatToolbar.tsx (98%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/FormatToolbar/LinkDialog/LinkDialog.styles.ts (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/FormatToolbar/LinkDialog/LinkDialog.tsx (99%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/FormatToolbar/common/isBlockquoteNode/isBlockquoteNode.test.ts (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/FormatToolbar/common/isBlockquoteNode/isBlockquoteNode.ts (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/FormatToolbar/common/isCodeBlockNode/isCodeBlockNode.test.ts (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/FormatToolbar/common/isCodeBlockNode/isCodeBlockNode.ts (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/FormatToolbar/common/isHeadingNode/isHeadingNode.test.ts (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/FormatToolbar/common/isHeadingNode/isHeadingNode.ts (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/FormatToolbar/common/isListNode/isListNode.test.ts (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/FormatToolbar/common/isListNode/isListNode.ts (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/FormatToolbar/useBlockquoteState/useBlockquoteState.ts (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/FormatToolbar/useCodeBlockState/useCodeBlockState.ts (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/FormatToolbar/useHeadingState/headingCommand.ts (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/FormatToolbar/useHeadingState/useHeadingState.ts (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/FormatToolbar/useLinkState/createNewLink/createNewLink.ts (95%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/FormatToolbar/useLinkState/getSelectedNode/getSelectedNode.ts (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/FormatToolbar/useLinkState/useLinkEditing/useLinkEditing.ts (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/FormatToolbar/useLinkState/useLinkState.ts (99%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/FormatToolbar/useLinkState/useModalState/useModalState.ts (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/FormatToolbar/useListState/useListState.ts (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/FormatToolbar/useToolbarState/useToolbarState.ts (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/Placeholder/Placeholder.tsx (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/RichTextEditor.tsx (97%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/editorConfig.ts (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/index.ts (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/nodes/EmojiNode.ts (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/nodes/Mention.tsx (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/nodes/MentionNode.tsx (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/plugins/AutoFocusPlugin/AutoFocusPlugin.tsx (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/plugins/AutoLinkPlugin/AutoLinkPlugin.tsx (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/plugins/BlockquotePlugin/BlockquotePlugin.tsx (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/plugins/CodeHighlightPlugin/CodeHighlightPlugin.tsx (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/plugins/DraftStatePlugin/DraftStatePlugin.tsx (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/plugins/EditedMessagePlugin/EditedMessagePlugin.tsx (97%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/plugins/EditedMessagePlugin/getMentionMarkdownTransformer/getMentionMarkdownTransformer.ts (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/plugins/EditedMessagePlugin/getMentionNodesFromMessage/getMentionNodesFromMessage.ts (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/plugins/EditedMessagePlugin/getRawMarkdownFromMessage/getRawMarkdownFromMessage.ts (93%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/plugins/EditedMessagePlugin/wrapMentionsWithTags/wrapMentionsWithTags.ts (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/plugins/EmojiPickerPlugin/EmojiItem.styles.ts (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/plugins/EmojiPickerPlugin/EmojiItem.tsx (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/plugins/EmojiPickerPlugin/EmojiPickerPlugin.tsx (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/plugins/EmojiPickerPlugin/index.ts (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/plugins/GlobalEventsPlugin/GlobalEventsPlugin.tsx (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/plugins/HistoryPlugin/HistoryPlugin.tsx (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/plugins/InlineEmojiReplacementPlugin/InlineEmojiReplacementPlugin.tsx (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/plugins/InlineEmojiReplacementPlugin/index.ts (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/plugins/InlineEmojiReplacementPlugin/inlineReplacements.ts (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/plugins/LinkPlugin/LinkPlugin.tsx (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/plugins/ListIndentationPlugin/ListIndentationPlugin.ts (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/plugins/ListMaxIndentLevelPlugin/ListMaxIndentLevelPlugin.tsx (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/plugins/MentionsPlugin/MentionSuggestionsItem.tsx (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/plugins/MentionsPlugin/MentionsPlugin.tsx (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/plugins/MentionsPlugin/index.ts (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/plugins/PastePlugin/PastePlugin.tsx (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/plugins/ReplaceCarriageReturnPlugin/ReplaceCarriageReturnPlugin.ts (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/plugins/SendPlugin/SendPlugin.tsx (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/plugins/TypeaheadMenuPlugin/TypeaheadMenuPlugin.tsx (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/theme.ts (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/utils/generateNodes.ts (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/utils/getDomRangeRect.ts (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/utils/getSelectionInfo.ts (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/utils/markdownTransformers.ts (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/utils/parseMentions.ts (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/utils/transformMessage.ts (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/utils/url.ts (100%) rename src/script/components/InputBar/{ => InputBarEditor}/RichTextEditor/utils/useEditorDraftState.ts (100%) rename src/script/components/InputBar/{RichTextEditor/SendMessageButton/index.ts => common/messageContent/messageContent.ts} (78%) diff --git a/src/script/components/InputBar/InputBar.tsx b/src/script/components/InputBar/InputBar.tsx index 9d261f47e48..bee033babf4 100644 --- a/src/script/components/InputBar/InputBar.tsx +++ b/src/script/components/InputBar/InputBar.tsx @@ -37,12 +37,12 @@ import {useKoSubscribableChildren} from 'Util/ComponentUtil'; import {t} from 'Util/LocalizerUtil'; import {TIME_IN_MILLIS} from 'Util/TimeUtil'; +import {MessageContent} from './common/messageContent/messageContent'; import {InputBarAvatar} from './InputBarAvatar/InputBarAvatar'; -import {InputBarButtons} from './InputBarButtons/InputBarButtons'; +import {InputBarControls} from './InputBarControls/InputBarControls'; import {InputBarEditor} from './InputBarEditor/InputBarEditor'; import {PastedFileControls} from './PastedFileControls/PastedFileControls'; import {ReplyBar} from './ReplyBar/ReplyBar'; -import {RichTextContent} from './RichTextEditor'; import {TypingIndicator} from './TypingIndicator'; import {useDraftState} from './useDraftState/useDraftState'; import {useEmojiPicker} from './useEmojiPicker/useEmojiPicker'; @@ -125,7 +125,7 @@ export const InputBar = ({ const wrapperRef = useRef(null); const editorRef = useRef(null); - const [messageContent, setMessageContent] = useState({text: ''}); + const [messageContent, setMessageContent] = useState({text: ''}); const {rightSidebar} = useAppMainState.getState(); const lastItem = rightSidebar.history.length - 1; @@ -276,7 +276,7 @@ export const InputBar = ({ )}
- + {showAvatar && } {!isSelfUserRemoved && !fileHandling.pastedFile && ( - = ({selfUser, showAvatar}) => { - if (!showAvatar) { - return null; - } - +export const InputBarAvatar = ({selfUser}: InputBarAvatarProps) => { return (
; const defaultParams: PropsType = { - conversation: undefined, + conversation: undefined as unknown as Conversation, input: '', - onCancelEditing: jest.fn(), onClickPing: jest.fn(), onGifClick: jest.fn(), diff --git a/src/script/components/InputBar/InputBarControls/ControlButtons.tsx b/src/script/components/InputBar/InputBarControls/ControlButtons.tsx index 8090a0b80b9..d06d5b424ca 100644 --- a/src/script/components/InputBar/InputBarControls/ControlButtons.tsx +++ b/src/script/components/InputBar/InputBarControls/ControlButtons.tsx @@ -17,19 +17,19 @@ * */ -import React, {MouseEvent} from 'react'; +import {MouseEvent} from 'react'; import {FormatSeparator} from 'Components/InputBar/common/FormatSeparator/FormatSeparator'; import {Config} from 'src/script/Config'; import {Conversation} from 'src/script/entity/Conversation'; -import {AssetUploadButton} from './AssetUploadButton'; +import {AssetUploadButton} from './AssetUploadButton/AssetUploadButton'; import {CancelEditButton} from './CancelEditButton/CancelEditButton'; import {EmojiButton} from './EmojiButton/EmojiButton'; import {FormatTextButton} from './FormatTextButton/FormatTextButton'; import {GiphyButton} from './GiphyButton/GiphyButton'; -import {ImageUploadButton} from './ImageUploadButton'; -import {MessageTimerButton} from './MessageTimerButton'; +import {ImageUploadButton} from './ImageUploadButton/ImageUploadButton'; +import {MessageTimerButton} from './MessageTimerButton/MessageTimerButton'; import {PingButton} from './PingButton/PingButton'; export type ControlButtonsProps = { diff --git a/src/script/components/InputBar/InputBarControls/ImageUploadButton/index.ts b/src/script/components/InputBar/InputBarControls/ImageUploadButton/index.ts deleted file mode 100644 index 072686c7769..00000000000 --- a/src/script/components/InputBar/InputBarControls/ImageUploadButton/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Wire - * Copyright (C) 2022 Wire Swiss GmbH - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see http://www.gnu.org/licenses/. - * - */ - -export * from './ImageUploadButton'; diff --git a/src/script/components/InputBar/InputBarButtons/InputBarButtons.tsx b/src/script/components/InputBar/InputBarControls/InputBarControls.tsx similarity index 89% rename from src/script/components/InputBar/InputBarButtons/InputBarButtons.tsx rename to src/script/components/InputBar/InputBarControls/InputBarControls.tsx index 42856b137dc..b0e5b1a1518 100644 --- a/src/script/components/InputBar/InputBarButtons/InputBarButtons.tsx +++ b/src/script/components/InputBar/InputBarControls/InputBarControls.tsx @@ -19,15 +19,16 @@ import {Conversation} from 'src/script/entity/Conversation'; -import {ControlButtons} from '../InputBarControls/ControlButtons'; -import {RichTextContent} from '../RichTextEditor/RichTextEditor'; -import {SendMessageButton} from '../RichTextEditor/SendMessageButton'; +import {ControlButtons} from './ControlButtons'; +import {SendMessageButton} from './SendMessageButton/SendMessageButton'; -interface InputBarButtonsProps { +import {MessageContent} from '../common/messageContent/messageContent'; + +interface InputBarControlsProps { conversation: Conversation; isFileSharingSendingEnabled: boolean; pingDisabled: boolean; - messageContent: RichTextContent; + messageContent: MessageContent; isEditing: boolean; isMessageFormatButtonsFlagEnabled: boolean; showMarkdownPreview: boolean; @@ -48,7 +49,7 @@ interface InputBarButtonsProps { onSend: () => void; } -export const InputBarButtons = ({ +export const InputBarControls = ({ conversation, isFileSharingSendingEnabled, pingDisabled, @@ -65,7 +66,7 @@ export const InputBarButtons = ({ onSelectFiles, onSelectImages, onSend, -}: InputBarButtonsProps) => { +}: InputBarControlsProps) => { const enableSending = messageContent.text.length > 0; return ( diff --git a/src/script/components/InputBar/InputBarControls/MessageTimerButton/index.ts b/src/script/components/InputBar/InputBarControls/MessageTimerButton/index.ts deleted file mode 100644 index 5363ab400f7..00000000000 --- a/src/script/components/InputBar/InputBarControls/MessageTimerButton/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Wire - * Copyright (C) 2022 Wire Swiss GmbH - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see http://www.gnu.org/licenses/. - * - */ - -export * from './MessageTimerButton'; diff --git a/src/script/components/InputBar/RichTextEditor/SendMessageButton/SendMessageButton.tsx b/src/script/components/InputBar/InputBarControls/SendMessageButton/SendMessageButton.tsx similarity index 100% rename from src/script/components/InputBar/RichTextEditor/SendMessageButton/SendMessageButton.tsx rename to src/script/components/InputBar/InputBarControls/SendMessageButton/SendMessageButton.tsx diff --git a/src/script/components/InputBar/InputBarEditor/InputBarEditor.tsx b/src/script/components/InputBar/InputBarEditor/InputBarEditor.tsx index f6e4af110b4..900a81b8837 100644 --- a/src/script/components/InputBar/InputBarEditor/InputBarEditor.tsx +++ b/src/script/components/InputBar/InputBarEditor/InputBarEditor.tsx @@ -23,7 +23,9 @@ import {LexicalEditor} from 'lexical'; import {User} from 'src/script/entity/User'; -import {RichTextEditor, RichTextContent} from '../RichTextEditor'; +import {RichTextEditor} from './RichTextEditor'; + +import {MessageContent} from '../common/messageContent/messageContent'; interface InputBarEditorProps { editorRef: React.MutableRefObject; @@ -39,7 +41,7 @@ interface InputBarEditorProps { onArrowUp: () => void; onShiftTab: () => void; onBlur: () => void; - onUpdate: (content: RichTextContent) => void; + onUpdate: (content: MessageContent) => void; onSend: () => void; getMentionCandidates: (search?: string | null) => User[]; saveDraftState: (editorState: string, plainMessage: string, replyId?: string) => void; diff --git a/src/script/components/InputBar/RichTextEditor/FormatToolbar/FormatButton/FormatButton.tsx b/src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/FormatButton/FormatButton.tsx similarity index 100% rename from src/script/components/InputBar/RichTextEditor/FormatToolbar/FormatButton/FormatButton.tsx rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/FormatButton/FormatButton.tsx diff --git a/src/script/components/InputBar/RichTextEditor/FormatToolbar/FormatToolbar.styles.ts b/src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/FormatToolbar.styles.ts similarity index 100% rename from src/script/components/InputBar/RichTextEditor/FormatToolbar/FormatToolbar.styles.ts rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/FormatToolbar.styles.ts diff --git a/src/script/components/InputBar/RichTextEditor/FormatToolbar/FormatToolbar.tsx b/src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/FormatToolbar.tsx similarity index 98% rename from src/script/components/InputBar/RichTextEditor/FormatToolbar/FormatToolbar.tsx rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/FormatToolbar.tsx index a766e3b981a..3d52cca4293 100644 --- a/src/script/components/InputBar/RichTextEditor/FormatToolbar/FormatToolbar.tsx +++ b/src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/FormatToolbar.tsx @@ -33,6 +33,7 @@ import { LinkIcon, } from '@wireapp/react-ui-kit'; +import {FormatSeparator} from 'Components/InputBar/common/FormatSeparator/FormatSeparator'; import {t} from 'Util/LocalizerUtil'; import {FormatButton} from './FormatButton/FormatButton'; @@ -45,8 +46,6 @@ import {useLinkState} from './useLinkState/useLinkState'; import {useListState} from './useListState/useListState'; import {useToolbarState} from './useToolbarState/useToolbarState'; -import {FormatSeparator} from '../../../../common/FormatSeparator/FormatSeparator'; - interface FormatToolbarProps { isEditing: boolean; } diff --git a/src/script/components/InputBar/RichTextEditor/FormatToolbar/LinkDialog/LinkDialog.styles.ts b/src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/LinkDialog/LinkDialog.styles.ts similarity index 100% rename from src/script/components/InputBar/RichTextEditor/FormatToolbar/LinkDialog/LinkDialog.styles.ts rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/LinkDialog/LinkDialog.styles.ts diff --git a/src/script/components/InputBar/RichTextEditor/FormatToolbar/LinkDialog/LinkDialog.tsx b/src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/LinkDialog/LinkDialog.tsx similarity index 99% rename from src/script/components/InputBar/RichTextEditor/FormatToolbar/LinkDialog/LinkDialog.tsx rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/LinkDialog/LinkDialog.tsx index db723476678..68424287f20 100644 --- a/src/script/components/InputBar/RichTextEditor/FormatToolbar/LinkDialog/LinkDialog.tsx +++ b/src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/LinkDialog/LinkDialog.tsx @@ -34,7 +34,7 @@ import { titleStyles, } from './LinkDialog.styles'; -import {validateUrl} from '../../../utils/url'; +import {validateUrl} from '../../utils/url'; interface LinkDialogProps { isOpen: boolean; diff --git a/src/script/components/InputBar/RichTextEditor/FormatToolbar/common/isBlockquoteNode/isBlockquoteNode.test.ts b/src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/common/isBlockquoteNode/isBlockquoteNode.test.ts similarity index 100% rename from src/script/components/InputBar/RichTextEditor/FormatToolbar/common/isBlockquoteNode/isBlockquoteNode.test.ts rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/common/isBlockquoteNode/isBlockquoteNode.test.ts diff --git a/src/script/components/InputBar/RichTextEditor/FormatToolbar/common/isBlockquoteNode/isBlockquoteNode.ts b/src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/common/isBlockquoteNode/isBlockquoteNode.ts similarity index 100% rename from src/script/components/InputBar/RichTextEditor/FormatToolbar/common/isBlockquoteNode/isBlockquoteNode.ts rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/common/isBlockquoteNode/isBlockquoteNode.ts diff --git a/src/script/components/InputBar/RichTextEditor/FormatToolbar/common/isCodeBlockNode/isCodeBlockNode.test.ts b/src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/common/isCodeBlockNode/isCodeBlockNode.test.ts similarity index 100% rename from src/script/components/InputBar/RichTextEditor/FormatToolbar/common/isCodeBlockNode/isCodeBlockNode.test.ts rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/common/isCodeBlockNode/isCodeBlockNode.test.ts diff --git a/src/script/components/InputBar/RichTextEditor/FormatToolbar/common/isCodeBlockNode/isCodeBlockNode.ts b/src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/common/isCodeBlockNode/isCodeBlockNode.ts similarity index 100% rename from src/script/components/InputBar/RichTextEditor/FormatToolbar/common/isCodeBlockNode/isCodeBlockNode.ts rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/common/isCodeBlockNode/isCodeBlockNode.ts diff --git a/src/script/components/InputBar/RichTextEditor/FormatToolbar/common/isHeadingNode/isHeadingNode.test.ts b/src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/common/isHeadingNode/isHeadingNode.test.ts similarity index 100% rename from src/script/components/InputBar/RichTextEditor/FormatToolbar/common/isHeadingNode/isHeadingNode.test.ts rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/common/isHeadingNode/isHeadingNode.test.ts diff --git a/src/script/components/InputBar/RichTextEditor/FormatToolbar/common/isHeadingNode/isHeadingNode.ts b/src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/common/isHeadingNode/isHeadingNode.ts similarity index 100% rename from src/script/components/InputBar/RichTextEditor/FormatToolbar/common/isHeadingNode/isHeadingNode.ts rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/common/isHeadingNode/isHeadingNode.ts diff --git a/src/script/components/InputBar/RichTextEditor/FormatToolbar/common/isListNode/isListNode.test.ts b/src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/common/isListNode/isListNode.test.ts similarity index 100% rename from src/script/components/InputBar/RichTextEditor/FormatToolbar/common/isListNode/isListNode.test.ts rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/common/isListNode/isListNode.test.ts diff --git a/src/script/components/InputBar/RichTextEditor/FormatToolbar/common/isListNode/isListNode.ts b/src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/common/isListNode/isListNode.ts similarity index 100% rename from src/script/components/InputBar/RichTextEditor/FormatToolbar/common/isListNode/isListNode.ts rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/common/isListNode/isListNode.ts diff --git a/src/script/components/InputBar/RichTextEditor/FormatToolbar/useBlockquoteState/useBlockquoteState.ts b/src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/useBlockquoteState/useBlockquoteState.ts similarity index 100% rename from src/script/components/InputBar/RichTextEditor/FormatToolbar/useBlockquoteState/useBlockquoteState.ts rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/useBlockquoteState/useBlockquoteState.ts diff --git a/src/script/components/InputBar/RichTextEditor/FormatToolbar/useCodeBlockState/useCodeBlockState.ts b/src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/useCodeBlockState/useCodeBlockState.ts similarity index 100% rename from src/script/components/InputBar/RichTextEditor/FormatToolbar/useCodeBlockState/useCodeBlockState.ts rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/useCodeBlockState/useCodeBlockState.ts diff --git a/src/script/components/InputBar/RichTextEditor/FormatToolbar/useHeadingState/headingCommand.ts b/src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/useHeadingState/headingCommand.ts similarity index 100% rename from src/script/components/InputBar/RichTextEditor/FormatToolbar/useHeadingState/headingCommand.ts rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/useHeadingState/headingCommand.ts diff --git a/src/script/components/InputBar/RichTextEditor/FormatToolbar/useHeadingState/useHeadingState.ts b/src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/useHeadingState/useHeadingState.ts similarity index 100% rename from src/script/components/InputBar/RichTextEditor/FormatToolbar/useHeadingState/useHeadingState.ts rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/useHeadingState/useHeadingState.ts diff --git a/src/script/components/InputBar/RichTextEditor/FormatToolbar/useLinkState/createNewLink/createNewLink.ts b/src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/useLinkState/createNewLink/createNewLink.ts similarity index 95% rename from src/script/components/InputBar/RichTextEditor/FormatToolbar/useLinkState/createNewLink/createNewLink.ts rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/useLinkState/createNewLink/createNewLink.ts index bdf30199a8b..28f54dbc294 100644 --- a/src/script/components/InputBar/RichTextEditor/FormatToolbar/useLinkState/createNewLink/createNewLink.ts +++ b/src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/useLinkState/createNewLink/createNewLink.ts @@ -20,7 +20,7 @@ import {$createLinkNode} from '@lexical/link'; import {RangeSelection, $createTextNode} from 'lexical'; -import {sanitizeUrl} from '../../../../utils/url'; +import {sanitizeUrl} from '../../../utils/url'; interface CreateLinkParams { selection: RangeSelection; diff --git a/src/script/components/InputBar/RichTextEditor/FormatToolbar/useLinkState/getSelectedNode/getSelectedNode.ts b/src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/useLinkState/getSelectedNode/getSelectedNode.ts similarity index 100% rename from src/script/components/InputBar/RichTextEditor/FormatToolbar/useLinkState/getSelectedNode/getSelectedNode.ts rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/useLinkState/getSelectedNode/getSelectedNode.ts diff --git a/src/script/components/InputBar/RichTextEditor/FormatToolbar/useLinkState/useLinkEditing/useLinkEditing.ts b/src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/useLinkState/useLinkEditing/useLinkEditing.ts similarity index 100% rename from src/script/components/InputBar/RichTextEditor/FormatToolbar/useLinkState/useLinkEditing/useLinkEditing.ts rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/useLinkState/useLinkEditing/useLinkEditing.ts diff --git a/src/script/components/InputBar/RichTextEditor/FormatToolbar/useLinkState/useLinkState.ts b/src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/useLinkState/useLinkState.ts similarity index 99% rename from src/script/components/InputBar/RichTextEditor/FormatToolbar/useLinkState/useLinkState.ts rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/useLinkState/useLinkState.ts index 2215a9d7621..9625d2c43f8 100644 --- a/src/script/components/InputBar/RichTextEditor/FormatToolbar/useLinkState/useLinkState.ts +++ b/src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/useLinkState/useLinkState.ts @@ -38,7 +38,7 @@ import {getSelectedNode} from './getSelectedNode/getSelectedNode'; import {useLinkEditing} from './useLinkEditing/useLinkEditing'; import {useModalState} from './useModalState/useModalState'; -import {sanitizeUrl} from '../../../utils/url'; +import {sanitizeUrl} from '../../utils/url'; export const FORMAT_LINK_COMMAND = createCommand(); diff --git a/src/script/components/InputBar/RichTextEditor/FormatToolbar/useLinkState/useModalState/useModalState.ts b/src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/useLinkState/useModalState/useModalState.ts similarity index 100% rename from src/script/components/InputBar/RichTextEditor/FormatToolbar/useLinkState/useModalState/useModalState.ts rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/useLinkState/useModalState/useModalState.ts diff --git a/src/script/components/InputBar/RichTextEditor/FormatToolbar/useListState/useListState.ts b/src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/useListState/useListState.ts similarity index 100% rename from src/script/components/InputBar/RichTextEditor/FormatToolbar/useListState/useListState.ts rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/useListState/useListState.ts diff --git a/src/script/components/InputBar/RichTextEditor/FormatToolbar/useToolbarState/useToolbarState.ts b/src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/useToolbarState/useToolbarState.ts similarity index 100% rename from src/script/components/InputBar/RichTextEditor/FormatToolbar/useToolbarState/useToolbarState.ts rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/FormatToolbar/useToolbarState/useToolbarState.ts diff --git a/src/script/components/InputBar/RichTextEditor/Placeholder/Placeholder.tsx b/src/script/components/InputBar/InputBarEditor/RichTextEditor/Placeholder/Placeholder.tsx similarity index 100% rename from src/script/components/InputBar/RichTextEditor/Placeholder/Placeholder.tsx rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/Placeholder/Placeholder.tsx diff --git a/src/script/components/InputBar/RichTextEditor/RichTextEditor.tsx b/src/script/components/InputBar/InputBarEditor/RichTextEditor/RichTextEditor.tsx similarity index 97% rename from src/script/components/InputBar/RichTextEditor/RichTextEditor.tsx rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/RichTextEditor.tsx index 15ef0845c81..5e8a95f8e23 100644 --- a/src/script/components/InputBar/RichTextEditor/RichTextEditor.tsx +++ b/src/script/components/InputBar/InputBarEditor/RichTextEditor/RichTextEditor.tsx @@ -31,6 +31,7 @@ import {OnChangePlugin} from '@lexical/react/LexicalOnChangePlugin'; import {RichTextPlugin} from '@lexical/react/LexicalRichTextPlugin'; import {LexicalEditor, EditorState} from 'lexical'; +import {MessageContent} from 'Components/InputBar/common/messageContent/messageContent'; import {DraftState} from 'Components/InputBar/util/DraftStateUtil'; import {ContentMessage} from 'src/script/entity/message/ContentMessage'; import {User} from 'src/script/entity/User'; @@ -60,13 +61,6 @@ import {parseMentions} from './utils/parseMentions'; import {transformMessage} from './utils/transformMessage'; import {useEditorDraftState} from './utils/useEditorDraftState'; -import {MentionEntity} from '../../../message/MentionEntity'; - -export type RichTextContent = { - text: string; - mentions?: MentionEntity[]; -}; - interface RichTextEditorProps { placeholder: string; replaceEmojis: boolean; @@ -78,7 +72,7 @@ interface RichTextEditorProps { getMentionCandidates: (search?: string | null) => User[]; saveDraftState: (editor: string, plainMessage: string, replyId?: string) => void; loadDraftState: () => Promise; - onUpdate: (content: RichTextContent) => void; + onUpdate: (content: MessageContent) => void; onArrowUp: () => void; onEscape: () => void; onShiftTab: () => void; diff --git a/src/script/components/InputBar/RichTextEditor/editorConfig.ts b/src/script/components/InputBar/InputBarEditor/RichTextEditor/editorConfig.ts similarity index 100% rename from src/script/components/InputBar/RichTextEditor/editorConfig.ts rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/editorConfig.ts diff --git a/src/script/components/InputBar/RichTextEditor/index.ts b/src/script/components/InputBar/InputBarEditor/RichTextEditor/index.ts similarity index 100% rename from src/script/components/InputBar/RichTextEditor/index.ts rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/index.ts diff --git a/src/script/components/InputBar/RichTextEditor/nodes/EmojiNode.ts b/src/script/components/InputBar/InputBarEditor/RichTextEditor/nodes/EmojiNode.ts similarity index 100% rename from src/script/components/InputBar/RichTextEditor/nodes/EmojiNode.ts rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/nodes/EmojiNode.ts diff --git a/src/script/components/InputBar/RichTextEditor/nodes/Mention.tsx b/src/script/components/InputBar/InputBarEditor/RichTextEditor/nodes/Mention.tsx similarity index 100% rename from src/script/components/InputBar/RichTextEditor/nodes/Mention.tsx rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/nodes/Mention.tsx diff --git a/src/script/components/InputBar/RichTextEditor/nodes/MentionNode.tsx b/src/script/components/InputBar/InputBarEditor/RichTextEditor/nodes/MentionNode.tsx similarity index 100% rename from src/script/components/InputBar/RichTextEditor/nodes/MentionNode.tsx rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/nodes/MentionNode.tsx diff --git a/src/script/components/InputBar/RichTextEditor/plugins/AutoFocusPlugin/AutoFocusPlugin.tsx b/src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/AutoFocusPlugin/AutoFocusPlugin.tsx similarity index 100% rename from src/script/components/InputBar/RichTextEditor/plugins/AutoFocusPlugin/AutoFocusPlugin.tsx rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/AutoFocusPlugin/AutoFocusPlugin.tsx diff --git a/src/script/components/InputBar/RichTextEditor/plugins/AutoLinkPlugin/AutoLinkPlugin.tsx b/src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/AutoLinkPlugin/AutoLinkPlugin.tsx similarity index 100% rename from src/script/components/InputBar/RichTextEditor/plugins/AutoLinkPlugin/AutoLinkPlugin.tsx rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/AutoLinkPlugin/AutoLinkPlugin.tsx diff --git a/src/script/components/InputBar/RichTextEditor/plugins/BlockquotePlugin/BlockquotePlugin.tsx b/src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/BlockquotePlugin/BlockquotePlugin.tsx similarity index 100% rename from src/script/components/InputBar/RichTextEditor/plugins/BlockquotePlugin/BlockquotePlugin.tsx rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/BlockquotePlugin/BlockquotePlugin.tsx diff --git a/src/script/components/InputBar/RichTextEditor/plugins/CodeHighlightPlugin/CodeHighlightPlugin.tsx b/src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/CodeHighlightPlugin/CodeHighlightPlugin.tsx similarity index 100% rename from src/script/components/InputBar/RichTextEditor/plugins/CodeHighlightPlugin/CodeHighlightPlugin.tsx rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/CodeHighlightPlugin/CodeHighlightPlugin.tsx diff --git a/src/script/components/InputBar/RichTextEditor/plugins/DraftStatePlugin/DraftStatePlugin.tsx b/src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/DraftStatePlugin/DraftStatePlugin.tsx similarity index 100% rename from src/script/components/InputBar/RichTextEditor/plugins/DraftStatePlugin/DraftStatePlugin.tsx rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/DraftStatePlugin/DraftStatePlugin.tsx diff --git a/src/script/components/InputBar/RichTextEditor/plugins/EditedMessagePlugin/EditedMessagePlugin.tsx b/src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/EditedMessagePlugin/EditedMessagePlugin.tsx similarity index 97% rename from src/script/components/InputBar/RichTextEditor/plugins/EditedMessagePlugin/EditedMessagePlugin.tsx rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/EditedMessagePlugin/EditedMessagePlugin.tsx index 3bcb0662d51..213144767f1 100644 --- a/src/script/components/InputBar/RichTextEditor/plugins/EditedMessagePlugin/EditedMessagePlugin.tsx +++ b/src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/EditedMessagePlugin/EditedMessagePlugin.tsx @@ -23,7 +23,6 @@ import {$convertFromMarkdownString} from '@lexical/markdown'; import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext'; import {$getRoot, $setSelection} from 'lexical'; -import {markdownTransformers} from 'Components/InputBar/components/RichTextEditor/utils/markdownTransformers'; import {ContentMessage} from 'src/script/entity/message/ContentMessage'; import {getMentionMarkdownTransformer} from './getMentionMarkdownTransformer/getMentionMarkdownTransformer'; @@ -31,6 +30,8 @@ import {getMentionNodesFromMessage} from './getMentionNodesFromMessage/getMentio import {getRawMarkdownNodesWithMentions} from './getRawMarkdownFromMessage/getRawMarkdownFromMessage'; import {wrapMentionsWithTags} from './wrapMentionsWithTags/wrapMentionsWithTags'; +import {markdownTransformers} from '../../utils/markdownTransformers'; + type Props = { message?: ContentMessage; showMarkdownPreview: boolean; diff --git a/src/script/components/InputBar/RichTextEditor/plugins/EditedMessagePlugin/getMentionMarkdownTransformer/getMentionMarkdownTransformer.ts b/src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/EditedMessagePlugin/getMentionMarkdownTransformer/getMentionMarkdownTransformer.ts similarity index 100% rename from src/script/components/InputBar/RichTextEditor/plugins/EditedMessagePlugin/getMentionMarkdownTransformer/getMentionMarkdownTransformer.ts rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/EditedMessagePlugin/getMentionMarkdownTransformer/getMentionMarkdownTransformer.ts diff --git a/src/script/components/InputBar/RichTextEditor/plugins/EditedMessagePlugin/getMentionNodesFromMessage/getMentionNodesFromMessage.ts b/src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/EditedMessagePlugin/getMentionNodesFromMessage/getMentionNodesFromMessage.ts similarity index 100% rename from src/script/components/InputBar/RichTextEditor/plugins/EditedMessagePlugin/getMentionNodesFromMessage/getMentionNodesFromMessage.ts rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/EditedMessagePlugin/getMentionNodesFromMessage/getMentionNodesFromMessage.ts diff --git a/src/script/components/InputBar/RichTextEditor/plugins/EditedMessagePlugin/getRawMarkdownFromMessage/getRawMarkdownFromMessage.ts b/src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/EditedMessagePlugin/getRawMarkdownFromMessage/getRawMarkdownFromMessage.ts similarity index 93% rename from src/script/components/InputBar/RichTextEditor/plugins/EditedMessagePlugin/getRawMarkdownFromMessage/getRawMarkdownFromMessage.ts rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/EditedMessagePlugin/getRawMarkdownFromMessage/getRawMarkdownFromMessage.ts index 3fb26919b68..d53295a362e 100644 --- a/src/script/components/InputBar/RichTextEditor/plugins/EditedMessagePlugin/getRawMarkdownFromMessage/getRawMarkdownFromMessage.ts +++ b/src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/EditedMessagePlugin/getRawMarkdownFromMessage/getRawMarkdownFromMessage.ts @@ -19,10 +19,10 @@ import {$createParagraphNode, $createTextNode} from 'lexical'; -import {$createMentionNode} from 'Components/InputBar/components/RichTextEditor/nodes/MentionNode'; import {ContentMessage} from 'src/script/entity/message/ContentMessage'; import {Text} from 'src/script/entity/message/Text'; +import {$createMentionNode} from '../../../nodes/MentionNode'; import {createNodes} from '../../../utils/generateNodes'; export const getRawMarkdownNodesWithMentions = (message: ContentMessage) => { diff --git a/src/script/components/InputBar/RichTextEditor/plugins/EditedMessagePlugin/wrapMentionsWithTags/wrapMentionsWithTags.ts b/src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/EditedMessagePlugin/wrapMentionsWithTags/wrapMentionsWithTags.ts similarity index 100% rename from src/script/components/InputBar/RichTextEditor/plugins/EditedMessagePlugin/wrapMentionsWithTags/wrapMentionsWithTags.ts rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/EditedMessagePlugin/wrapMentionsWithTags/wrapMentionsWithTags.ts diff --git a/src/script/components/InputBar/RichTextEditor/plugins/EmojiPickerPlugin/EmojiItem.styles.ts b/src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/EmojiPickerPlugin/EmojiItem.styles.ts similarity index 100% rename from src/script/components/InputBar/RichTextEditor/plugins/EmojiPickerPlugin/EmojiItem.styles.ts rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/EmojiPickerPlugin/EmojiItem.styles.ts diff --git a/src/script/components/InputBar/RichTextEditor/plugins/EmojiPickerPlugin/EmojiItem.tsx b/src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/EmojiPickerPlugin/EmojiItem.tsx similarity index 100% rename from src/script/components/InputBar/RichTextEditor/plugins/EmojiPickerPlugin/EmojiItem.tsx rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/EmojiPickerPlugin/EmojiItem.tsx diff --git a/src/script/components/InputBar/RichTextEditor/plugins/EmojiPickerPlugin/EmojiPickerPlugin.tsx b/src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/EmojiPickerPlugin/EmojiPickerPlugin.tsx similarity index 100% rename from src/script/components/InputBar/RichTextEditor/plugins/EmojiPickerPlugin/EmojiPickerPlugin.tsx rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/EmojiPickerPlugin/EmojiPickerPlugin.tsx diff --git a/src/script/components/InputBar/RichTextEditor/plugins/EmojiPickerPlugin/index.ts b/src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/EmojiPickerPlugin/index.ts similarity index 100% rename from src/script/components/InputBar/RichTextEditor/plugins/EmojiPickerPlugin/index.ts rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/EmojiPickerPlugin/index.ts diff --git a/src/script/components/InputBar/RichTextEditor/plugins/GlobalEventsPlugin/GlobalEventsPlugin.tsx b/src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/GlobalEventsPlugin/GlobalEventsPlugin.tsx similarity index 100% rename from src/script/components/InputBar/RichTextEditor/plugins/GlobalEventsPlugin/GlobalEventsPlugin.tsx rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/GlobalEventsPlugin/GlobalEventsPlugin.tsx diff --git a/src/script/components/InputBar/RichTextEditor/plugins/HistoryPlugin/HistoryPlugin.tsx b/src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/HistoryPlugin/HistoryPlugin.tsx similarity index 100% rename from src/script/components/InputBar/RichTextEditor/plugins/HistoryPlugin/HistoryPlugin.tsx rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/HistoryPlugin/HistoryPlugin.tsx diff --git a/src/script/components/InputBar/RichTextEditor/plugins/InlineEmojiReplacementPlugin/InlineEmojiReplacementPlugin.tsx b/src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/InlineEmojiReplacementPlugin/InlineEmojiReplacementPlugin.tsx similarity index 100% rename from src/script/components/InputBar/RichTextEditor/plugins/InlineEmojiReplacementPlugin/InlineEmojiReplacementPlugin.tsx rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/InlineEmojiReplacementPlugin/InlineEmojiReplacementPlugin.tsx diff --git a/src/script/components/InputBar/RichTextEditor/plugins/InlineEmojiReplacementPlugin/index.ts b/src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/InlineEmojiReplacementPlugin/index.ts similarity index 100% rename from src/script/components/InputBar/RichTextEditor/plugins/InlineEmojiReplacementPlugin/index.ts rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/InlineEmojiReplacementPlugin/index.ts diff --git a/src/script/components/InputBar/RichTextEditor/plugins/InlineEmojiReplacementPlugin/inlineReplacements.ts b/src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/InlineEmojiReplacementPlugin/inlineReplacements.ts similarity index 100% rename from src/script/components/InputBar/RichTextEditor/plugins/InlineEmojiReplacementPlugin/inlineReplacements.ts rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/InlineEmojiReplacementPlugin/inlineReplacements.ts diff --git a/src/script/components/InputBar/RichTextEditor/plugins/LinkPlugin/LinkPlugin.tsx b/src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/LinkPlugin/LinkPlugin.tsx similarity index 100% rename from src/script/components/InputBar/RichTextEditor/plugins/LinkPlugin/LinkPlugin.tsx rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/LinkPlugin/LinkPlugin.tsx diff --git a/src/script/components/InputBar/RichTextEditor/plugins/ListIndentationPlugin/ListIndentationPlugin.ts b/src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/ListIndentationPlugin/ListIndentationPlugin.ts similarity index 100% rename from src/script/components/InputBar/RichTextEditor/plugins/ListIndentationPlugin/ListIndentationPlugin.ts rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/ListIndentationPlugin/ListIndentationPlugin.ts diff --git a/src/script/components/InputBar/RichTextEditor/plugins/ListMaxIndentLevelPlugin/ListMaxIndentLevelPlugin.tsx b/src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/ListMaxIndentLevelPlugin/ListMaxIndentLevelPlugin.tsx similarity index 100% rename from src/script/components/InputBar/RichTextEditor/plugins/ListMaxIndentLevelPlugin/ListMaxIndentLevelPlugin.tsx rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/ListMaxIndentLevelPlugin/ListMaxIndentLevelPlugin.tsx diff --git a/src/script/components/InputBar/RichTextEditor/plugins/MentionsPlugin/MentionSuggestionsItem.tsx b/src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/MentionsPlugin/MentionSuggestionsItem.tsx similarity index 100% rename from src/script/components/InputBar/RichTextEditor/plugins/MentionsPlugin/MentionSuggestionsItem.tsx rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/MentionsPlugin/MentionSuggestionsItem.tsx diff --git a/src/script/components/InputBar/RichTextEditor/plugins/MentionsPlugin/MentionsPlugin.tsx b/src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/MentionsPlugin/MentionsPlugin.tsx similarity index 100% rename from src/script/components/InputBar/RichTextEditor/plugins/MentionsPlugin/MentionsPlugin.tsx rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/MentionsPlugin/MentionsPlugin.tsx diff --git a/src/script/components/InputBar/RichTextEditor/plugins/MentionsPlugin/index.ts b/src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/MentionsPlugin/index.ts similarity index 100% rename from src/script/components/InputBar/RichTextEditor/plugins/MentionsPlugin/index.ts rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/MentionsPlugin/index.ts diff --git a/src/script/components/InputBar/RichTextEditor/plugins/PastePlugin/PastePlugin.tsx b/src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/PastePlugin/PastePlugin.tsx similarity index 100% rename from src/script/components/InputBar/RichTextEditor/plugins/PastePlugin/PastePlugin.tsx rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/PastePlugin/PastePlugin.tsx diff --git a/src/script/components/InputBar/RichTextEditor/plugins/ReplaceCarriageReturnPlugin/ReplaceCarriageReturnPlugin.ts b/src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/ReplaceCarriageReturnPlugin/ReplaceCarriageReturnPlugin.ts similarity index 100% rename from src/script/components/InputBar/RichTextEditor/plugins/ReplaceCarriageReturnPlugin/ReplaceCarriageReturnPlugin.ts rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/ReplaceCarriageReturnPlugin/ReplaceCarriageReturnPlugin.ts diff --git a/src/script/components/InputBar/RichTextEditor/plugins/SendPlugin/SendPlugin.tsx b/src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/SendPlugin/SendPlugin.tsx similarity index 100% rename from src/script/components/InputBar/RichTextEditor/plugins/SendPlugin/SendPlugin.tsx rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/SendPlugin/SendPlugin.tsx diff --git a/src/script/components/InputBar/RichTextEditor/plugins/TypeaheadMenuPlugin/TypeaheadMenuPlugin.tsx b/src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/TypeaheadMenuPlugin/TypeaheadMenuPlugin.tsx similarity index 100% rename from src/script/components/InputBar/RichTextEditor/plugins/TypeaheadMenuPlugin/TypeaheadMenuPlugin.tsx rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/TypeaheadMenuPlugin/TypeaheadMenuPlugin.tsx diff --git a/src/script/components/InputBar/RichTextEditor/theme.ts b/src/script/components/InputBar/InputBarEditor/RichTextEditor/theme.ts similarity index 100% rename from src/script/components/InputBar/RichTextEditor/theme.ts rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/theme.ts diff --git a/src/script/components/InputBar/RichTextEditor/utils/generateNodes.ts b/src/script/components/InputBar/InputBarEditor/RichTextEditor/utils/generateNodes.ts similarity index 100% rename from src/script/components/InputBar/RichTextEditor/utils/generateNodes.ts rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/utils/generateNodes.ts diff --git a/src/script/components/InputBar/RichTextEditor/utils/getDomRangeRect.ts b/src/script/components/InputBar/InputBarEditor/RichTextEditor/utils/getDomRangeRect.ts similarity index 100% rename from src/script/components/InputBar/RichTextEditor/utils/getDomRangeRect.ts rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/utils/getDomRangeRect.ts diff --git a/src/script/components/InputBar/RichTextEditor/utils/getSelectionInfo.ts b/src/script/components/InputBar/InputBarEditor/RichTextEditor/utils/getSelectionInfo.ts similarity index 100% rename from src/script/components/InputBar/RichTextEditor/utils/getSelectionInfo.ts rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/utils/getSelectionInfo.ts diff --git a/src/script/components/InputBar/RichTextEditor/utils/markdownTransformers.ts b/src/script/components/InputBar/InputBarEditor/RichTextEditor/utils/markdownTransformers.ts similarity index 100% rename from src/script/components/InputBar/RichTextEditor/utils/markdownTransformers.ts rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/utils/markdownTransformers.ts diff --git a/src/script/components/InputBar/RichTextEditor/utils/parseMentions.ts b/src/script/components/InputBar/InputBarEditor/RichTextEditor/utils/parseMentions.ts similarity index 100% rename from src/script/components/InputBar/RichTextEditor/utils/parseMentions.ts rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/utils/parseMentions.ts diff --git a/src/script/components/InputBar/RichTextEditor/utils/transformMessage.ts b/src/script/components/InputBar/InputBarEditor/RichTextEditor/utils/transformMessage.ts similarity index 100% rename from src/script/components/InputBar/RichTextEditor/utils/transformMessage.ts rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/utils/transformMessage.ts diff --git a/src/script/components/InputBar/RichTextEditor/utils/url.ts b/src/script/components/InputBar/InputBarEditor/RichTextEditor/utils/url.ts similarity index 100% rename from src/script/components/InputBar/RichTextEditor/utils/url.ts rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/utils/url.ts diff --git a/src/script/components/InputBar/RichTextEditor/utils/useEditorDraftState.ts b/src/script/components/InputBar/InputBarEditor/RichTextEditor/utils/useEditorDraftState.ts similarity index 100% rename from src/script/components/InputBar/RichTextEditor/utils/useEditorDraftState.ts rename to src/script/components/InputBar/InputBarEditor/RichTextEditor/utils/useEditorDraftState.ts diff --git a/src/script/components/InputBar/ReplyBar/ReplyBar.tsx b/src/script/components/InputBar/ReplyBar/ReplyBar.tsx index 44b4951398b..3e1185199f4 100644 --- a/src/script/components/InputBar/ReplyBar/ReplyBar.tsx +++ b/src/script/components/InputBar/ReplyBar/ReplyBar.tsx @@ -79,7 +79,7 @@ export const ReplyBar = ({replyMessageEntity, onCancel}: ReplyBarProps) => {
diff --git a/src/script/components/InputBar/TypingIndicator/TypingIndicator.test.tsx b/src/script/components/InputBar/TypingIndicator/TypingIndicator.test.tsx index c78669facf7..0ac417e5486 100644 --- a/src/script/components/InputBar/TypingIndicator/TypingIndicator.test.tsx +++ b/src/script/components/InputBar/TypingIndicator/TypingIndicator.test.tsx @@ -20,11 +20,11 @@ import {render} from '@testing-library/react'; import {act} from 'react-dom/test-utils'; +import {User} from 'src/script/entity/User'; + import {TypingIndicator, TypingIndicatorProps} from './TypingIndicator'; import {useTypingIndicatorState} from './useTypingIndicatorState/useTypingIndicatorState'; -import {User} from '../../../../entity/User'; - function createUser(id: string, name: string): User { const user = new User(id); user.name(name); diff --git a/src/script/components/InputBar/RichTextEditor/SendMessageButton/index.ts b/src/script/components/InputBar/common/messageContent/messageContent.ts similarity index 78% rename from src/script/components/InputBar/RichTextEditor/SendMessageButton/index.ts rename to src/script/components/InputBar/common/messageContent/messageContent.ts index 131603eb4ff..eb145137a88 100644 --- a/src/script/components/InputBar/RichTextEditor/SendMessageButton/index.ts +++ b/src/script/components/InputBar/common/messageContent/messageContent.ts @@ -1,6 +1,6 @@ /* * Wire - * Copyright (C) 2023 Wire Swiss GmbH + * Copyright (C) 2025 Wire Swiss GmbH * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,4 +17,9 @@ * */ -export * from './SendMessageButton'; +import {MentionEntity} from 'src/script/message/MentionEntity'; + +export interface MessageContent { + text: string; + mentions?: MentionEntity[]; +} From 6592d96a8ad48b97e6fc5d206966728fa064b4b7 Mon Sep 17 00:00:00 2001 From: Olaf Sulich Date: Mon, 17 Feb 2025 07:52:03 +0100 Subject: [PATCH 03/13] fix: lint --- .../AssetUploadButton/AssetUploadButton.test.tsx | 2 +- .../ImageUploadButton/ImageUploadButton.test.tsx | 2 +- .../components/InputBar/InputBarEditor/InputBarEditor.tsx | 6 ++---- .../InputBar/useTypingIndicator/useTypingIndicator.test.ts | 2 +- src/script/conversation/ConversationRepository.ts | 2 +- 5 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/script/components/InputBar/InputBarControls/AssetUploadButton/AssetUploadButton.test.tsx b/src/script/components/InputBar/InputBarControls/AssetUploadButton/AssetUploadButton.test.tsx index cdaeaa049ef..49b115f6fb3 100644 --- a/src/script/components/InputBar/InputBarControls/AssetUploadButton/AssetUploadButton.test.tsx +++ b/src/script/components/InputBar/InputBarControls/AssetUploadButton/AssetUploadButton.test.tsx @@ -19,7 +19,7 @@ import {render, fireEvent} from '@testing-library/react'; -import {AssetUploadButton} from '.'; +import {AssetUploadButton} from './AssetUploadButton'; const pngFile = new File(['(⌐□_□)'], 'chucknorris.png', {type: 'image/png'}); diff --git a/src/script/components/InputBar/InputBarControls/ImageUploadButton/ImageUploadButton.test.tsx b/src/script/components/InputBar/InputBarControls/ImageUploadButton/ImageUploadButton.test.tsx index 76a73835919..d0ca99e55f2 100644 --- a/src/script/components/InputBar/InputBarControls/ImageUploadButton/ImageUploadButton.test.tsx +++ b/src/script/components/InputBar/InputBarControls/ImageUploadButton/ImageUploadButton.test.tsx @@ -19,7 +19,7 @@ import {render, fireEvent} from '@testing-library/react'; -import {ImageUploadButton} from '.'; +import {ImageUploadButton} from './ImageUploadButton'; const ALLOWED_IMAGE_TYPES = ['image/gif', 'image/avif']; diff --git a/src/script/components/InputBar/InputBarEditor/InputBarEditor.tsx b/src/script/components/InputBar/InputBarEditor/InputBarEditor.tsx index 900a81b8837..fcb56e0922c 100644 --- a/src/script/components/InputBar/InputBarEditor/InputBarEditor.tsx +++ b/src/script/components/InputBar/InputBarEditor/InputBarEditor.tsx @@ -17,8 +17,6 @@ * */ -import {FC} from 'react'; - import {LexicalEditor} from 'lexical'; import {User} from 'src/script/entity/User'; @@ -50,7 +48,7 @@ interface InputBarEditorProps { children: React.ReactNode; } -export const InputBarEditor: FC = ({ +export const InputBarEditor = ({ editorRef, inputPlaceholder, hasLocalEphemeralTimer, @@ -68,7 +66,7 @@ export const InputBarEditor: FC = ({ loadDraftState, replaceEmojis, children, -}) => { +}: InputBarEditorProps) => { return ( { diff --git a/src/script/components/InputBar/useTypingIndicator/useTypingIndicator.test.ts b/src/script/components/InputBar/useTypingIndicator/useTypingIndicator.test.ts index 99f728ed871..2f050ef277c 100644 --- a/src/script/components/InputBar/useTypingIndicator/useTypingIndicator.test.ts +++ b/src/script/components/InputBar/useTypingIndicator/useTypingIndicator.test.ts @@ -21,7 +21,7 @@ import {fireEvent, renderHook} from '@testing-library/react'; import {useTypingIndicator} from './useTypingIndicator'; -import {TYPING_TIMEOUT} from '../../components/TypingIndicator'; +import {TYPING_TIMEOUT} from '../TypingIndicator'; describe('useTypingIndicator', () => { beforeAll(() => { diff --git a/src/script/conversation/ConversationRepository.ts b/src/script/conversation/ConversationRepository.ts index f196f6cdf80..90e5a01e5e6 100644 --- a/src/script/conversation/ConversationRepository.ts +++ b/src/script/conversation/ConversationRepository.ts @@ -58,7 +58,7 @@ import {flatten} from 'underscore'; import {Asset as ProtobufAsset, Confirmation, LegalHoldStatus} from '@wireapp/protocol-messaging'; import {WebAppEvents} from '@wireapp/webapp-events'; -import {TYPING_TIMEOUT, useTypingIndicatorState} from 'Components/InputBar/components/TypingIndicator'; +import {TYPING_TIMEOUT, useTypingIndicatorState} from 'Components/InputBar/TypingIndicator'; import {getNextItem} from 'Util/ArrayUtil'; import {allowsAllFiles, getFileExtensionOrName, isAllowedFile} from 'Util/FileTypeUtil'; import {replaceLink, t} from 'Util/LocalizerUtil'; From 082ebcfdb7ecbe7cdf97c1e0f5358c2b4008ecd3 Mon Sep 17 00:00:00 2001 From: Olaf Sulich Date: Mon, 17 Feb 2025 10:12:02 +0100 Subject: [PATCH 04/13] refactor(InputBar): restructure files --- src/script/components/InputBar/InputBar.tsx | 292 +++++++++--------- .../InputBarContainer/InputBarContainer.tsx | 49 +++ .../InputBarControls/InputBarControls.tsx | 14 + .../InputBar/useDraftState/useDraftState.ts | 37 +-- .../useFileHandling/useFileHandling.ts | 44 ++- .../useFilePaste/useFilePaste.ts | 73 +++++ .../useFilePaste/useFilePaste.test.ts | 45 --- .../InputBar/useFilePaste/useFilePaste.ts | 38 --- .../components/InputBar/useGiphy/useGiphy.ts | 43 ++- .../useMessageHandling/useMessageHandling.ts | 269 ++++++++++------ .../useMessageHandlingV2.ts | 47 +++ 11 files changed, 587 insertions(+), 364 deletions(-) create mode 100644 src/script/components/InputBar/InputBarContainer/InputBarContainer.tsx create mode 100644 src/script/components/InputBar/useFileHandling/useFilePaste/useFilePaste.ts delete mode 100644 src/script/components/InputBar/useFilePaste/useFilePaste.test.ts delete mode 100644 src/script/components/InputBar/useFilePaste/useFilePaste.ts create mode 100644 src/script/components/InputBar/useMessageHandlingV2/useMessageHandlingV2.ts diff --git a/src/script/components/InputBar/InputBar.tsx b/src/script/components/InputBar/InputBar.tsx index bee033babf4..7d01ef89914 100644 --- a/src/script/components/InputBar/InputBar.tsx +++ b/src/script/components/InputBar/InputBar.tsx @@ -17,18 +17,18 @@ * */ -import {useRef, useState} from 'react'; +import {useCallback, useRef, useState} from 'react'; import {amplify} from 'amplify'; import cx from 'classnames'; import {LexicalEditor, $createTextNode, $insertNodes} from 'lexical'; +import {container} from 'tsyringe'; import {WebAppEvents} from '@wireapp/webapp-events'; +import {Avatar, AVATAR_SIZE} from 'Components/Avatar'; import {ConversationClassifiedBar} from 'Components/ClassifiedBar/ClassifiedBar'; -import {checkFileSharingPermission} from 'Components/Conversation/utils/checkFileSharingPermission'; import {EmojiPicker} from 'Components/EmojiPicker/EmojiPicker'; -import {showWarningModal} from 'Components/Modals/utils/showWarningModal'; import {useUserPropertyValue} from 'src/script/hooks/useUserProperty'; import {PROPERTIES_TYPE} from 'src/script/properties/PropertiesType'; import {EventName} from 'src/script/tracking/EventName'; @@ -38,7 +38,7 @@ import {t} from 'Util/LocalizerUtil'; import {TIME_IN_MILLIS} from 'Util/TimeUtil'; import {MessageContent} from './common/messageContent/messageContent'; -import {InputBarAvatar} from './InputBarAvatar/InputBarAvatar'; +import {InputBarContainer} from './InputBarContainer/InputBarContainer'; import {InputBarControls} from './InputBarControls/InputBarControls'; import {InputBarEditor} from './InputBarEditor/InputBarEditor'; import {PastedFileControls} from './PastedFileControls/PastedFileControls'; @@ -47,7 +47,6 @@ import {TypingIndicator} from './TypingIndicator'; import {useDraftState} from './useDraftState/useDraftState'; import {useEmojiPicker} from './useEmojiPicker/useEmojiPicker'; import {useFileHandling} from './useFileHandling/useFileHandling'; -import {useFilePaste} from './useFilePaste/useFilePaste'; import {useFormatToolbar} from './useFormatToolbar/useFormatToolbar'; import {useGiphy} from './useGiphy/useGiphy'; import {useMessageHandling} from './useMessageHandling/useMessageHandling'; @@ -60,7 +59,6 @@ import {MessageRepository} from '../../conversation/MessageRepository'; import {Conversation} from '../../entity/Conversation'; import {User} from '../../entity/User'; import {EventRepository} from '../../event/EventRepository'; -import {useAppMainState} from '../../page/state'; import {PropertiesRepository} from '../../properties/PropertiesRepository'; import {SearchRepository} from '../../search/SearchRepository'; import {StorageRepository} from '../../storage'; @@ -88,6 +86,7 @@ interface InputBarProps { uploadImages: (images: File[]) => void; uploadFiles: (files: File[]) => void; } +const conversationInputBarClassName = 'conversation-input-bar'; export const InputBar = ({ conversation, @@ -99,7 +98,7 @@ export const InputBar = ({ searchRepository, storageRepository, selfUser, - teamState, + teamState = container.resolve(TeamState), onShiftTab, uploadDroppedFiles, uploadImages, @@ -124,13 +123,28 @@ export const InputBar = ({ ]); const wrapperRef = useRef(null); + const editorRef = useRef(null); + + const {typingIndicatorMode} = useKoSubscribableChildren(propertiesRepository, ['typingIndicatorMode']); + const isTypingIndicatorEnabled = typingIndicatorMode === CONVERSATION_TYPING_INDICATOR_MODE.ON; + + /** The messageContent represents the message being edited. + * It's directly derived from the editor state + */ const [messageContent, setMessageContent] = useState({text: ''}); - const {rightSidebar} = useAppMainState.getState(); - const lastItem = rightSidebar.history.length - 1; - const currentState = rightSidebar.history[lastItem]; - const isRightSidebarOpen = !!currentState; + const formatToolbar = useFormatToolbar(); + + const emojiPicker = useEmojiPicker({ + wrapperRef, + onEmojiPicked: emoji => { + editorRef.current?.update(() => { + $insertNodes([$createTextNode(emoji)]); + }); + amplify.publish(WebAppEvents.ANALYTICS.EVENT, EventName.INPUT.EMOJI_MODAL.EMOJI_PICKED); + }, + }); const inputPlaceholder = messageTimer ? t('tooltipConversationEphemeral') : t('tooltipConversationInputPlaceholder'); @@ -145,10 +159,39 @@ export const InputBar = ({ WebAppEvents.PROPERTIES.UPDATE.EMOJI.REPLACE_INLINE, ); - const {typingIndicatorMode} = useKoSubscribableChildren(propertiesRepository, ['typingIndicatorMode']); - const isTypingIndicatorEnabled = typingIndicatorMode === CONVERSATION_TYPING_INDICATOR_MODE.ON; + const getMentionCandidates = useCallback( + (search?: string | null) => { + const candidates = conversation.participating_user_ets().filter(userEntity => !userEntity.isService); + return typeof search === 'string' ? searchRepository.searchUserInSet(search, candidates) : candidates; + }, + [conversation, searchRepository], + ); - const formatToolbar = useFormatToolbar(); + useTypingIndicator({ + isEnabled: isTypingIndicatorEnabled, + text: messageContent.text, + onTypingChange: useCallback( + isTyping => { + isTypingRef.current = isTyping; + if (isTyping) { + void conversationRepository.sendTypingStart(conversation); + } else { + void conversationRepository.sendTypingStop(conversation); + } + }, + [conversationRepository, conversation], + ), + }); + + const fileHandling = useFileHandling({ + uploadDroppedFiles, + uploadImages, + }); + + const showMarkdownPreview = useUserPropertyValue( + () => propertiesRepository.getPreference(PROPERTIES_TYPE.INTERFACE.MARKDOWN_PREVIEW), + WebAppEvents.PROPERTIES.UPDATE.INTERFACE.MARKDOWN_PREVIEW, + ); const draftState = useDraftState({ conversation, @@ -166,33 +209,19 @@ export const InputBar = ({ cancelMessageEditing, cancelMessageReply, editMessage, + generateQuote, } = useMessageHandling({ + messageContent, conversation, conversationRepository, eventRepository, messageRepository, editorRef, - onResetDraftState: draftState.resetDraftState, + pastedFile: fileHandling.pastedFile, + sendPastedFile: fileHandling.sendPastedFile, + onResetDraftState: draftState.reset, onSaveDraft: (replyId?: string) => - draftState.saveDraftState( - JSON.stringify(editorRef.current?.getEditorState().toJSON()), - messageContent.text, - replyId, - ), - }); - - const fileHandling = useFileHandling({ - uploadDroppedFiles, - }); - - const emojiPicker = useEmojiPicker({ - wrapperRef, - onEmojiPicked: emoji => { - editorRef.current?.update(() => { - $insertNodes([$createTextNode(emoji)]); - }); - amplify.publish(WebAppEvents.ANALYTICS.EVENT, EventName.INPUT.EMOJI_MODAL.EMOJI_PICKED); - }, + draftState.save(JSON.stringify(editorRef.current?.getEditorState().toJSON()), messageContent.text, replyId), }); const ping = usePing({ @@ -208,63 +237,17 @@ export const InputBar = ({ maxLength: CONFIG.GIPHY_TEXT_LENGTH, isMessageFormatButtonsFlagEnabled, openGiphy, + generateQuote, + messageRepository, + conversation, + cancelMessageEditing, }); - useTypingIndicator({ - isEnabled: isTypingIndicatorEnabled, - text: messageContent.text, - onTypingChange: isTyping => { - isTypingRef.current = isTyping; - if (isTyping) { - void conversationRepository.sendTypingStart(conversation); - } else { - void conversationRepository.sendTypingStop(conversation); - } - }, - }); - - useFilePaste(checkFileSharingPermission(fileHandling.handlePasteFiles)); - - const getMentionCandidates = (search?: string | null) => { - const candidates = conversation.participating_user_ets().filter(userEntity => !userEntity.isService); - return typeof search === 'string' ? searchRepository.searchUserInSet(search, candidates) : candidates; - }; - - const showMarkdownPreview = useUserPropertyValue( - () => propertiesRepository.getPreference(PROPERTIES_TYPE.INTERFACE.MARKDOWN_PREVIEW), - WebAppEvents.PROPERTIES.UPDATE.INTERFACE.MARKDOWN_PREVIEW, - ); - - const handleSend = () => { - if (fileHandling.pastedFile) { - fileHandling.sendPastedFile(); - } else { - const messageTrimmedStart = messageContent.text.trimStart(); - const text = messageTrimmedStart.trimEnd(); - const isMessageTextTooLong = text.length > CONFIG.MAXIMUM_MESSAGE_LENGTH; - - if (isMessageTextTooLong) { - showWarningModal( - t('modalConversationMessageTooLongHeadline'), - t('modalConversationMessageTooLongMessage', {number: CONFIG.MAXIMUM_MESSAGE_LENGTH}), - ); - return; - } - - void handleSendMessage(text, messageContent.mentions ?? []); - } - }; - - const conversationInputBarClassName = 'conversation-input-bar'; const showAvatar = !!messageContent.text.length; return ( -
-
+
+ {isTypingIndicatorEnabled && } {classifiedDomains && !isConnectionRequest && ( @@ -275,65 +258,82 @@ export const InputBar = ({ cancelMessageReply(false)} /> )} -
- {showAvatar && } - - {!isSelfUserRemoved && !fileHandling.pastedFile && ( - { - editorRef.current = editor; - }} - onEscape={() => { - if (editedMessage) { - cancelMessageEditing(true); - } else if (replyMessageEntity) { - cancelMessageReply(); - } - }} - onArrowUp={() => { - if (messageContent.text.length === 0) { - editMessage(conversation.getLastEditableMessage()); - } - }} - onShiftTab={onShiftTab} - onBlur={() => isTypingRef.current && conversationRepository.sendTypingStop(conversation)} - onUpdate={setMessageContent} - onSend={handleSend} - getMentionCandidates={getMentionCandidates} - saveDraftState={draftState.saveDraftState} - loadDraftState={draftState.loadDraftState} - replaceEmojis={shouldReplaceEmoji} - > - { - if (editedMessage) { - cancelMessageEditing(true); - } else if (replyMessageEntity) { - cancelMessageReply(); - } - }} - onClickPing={ping.handlePing} - onGifClick={giphy.handleGifClick} - onSelectFiles={uploadFiles} - onSelectImages={uploadImages} - onSend={handleSend} - /> - +
+ {!isOutgoingRequest && ( + <> +
+ {showAvatar && ( + + )} +
+ {!isSelfUserRemoved && !fileHandling.pastedFile && ( + { + editorRef.current = editor; + }} + onEscape={() => { + if (editedMessage) { + cancelMessageEditing(true); + } else if (replyMessageEntity) { + cancelMessageReply(); + } + }} + onArrowUp={() => { + if (messageContent.text.length === 0) { + editMessage(conversation.getLastEditableMessage()); + } + }} + onShiftTab={onShiftTab} + onBlur={() => isTypingRef.current && conversationRepository.sendTypingStop(conversation)} + onUpdate={setMessageContent} + onSend={handleSendMessage} + getMentionCandidates={getMentionCandidates} + saveDraftState={draftState.save} + loadDraftState={draftState.load} + replaceEmojis={shouldReplaceEmoji} + > + { + if (editedMessage) { + cancelMessageEditing(true); + } else if (replyMessageEntity) { + cancelMessageReply(); + } + }} + onClickPing={ping.handlePing} + onGifClick={giphy.handleGifClick} + onSelectFiles={uploadFiles} + onSelectImages={uploadImages} + onSend={handleSendMessage} + /> + + )} + )} {fileHandling.pastedFile && ( @@ -344,7 +344,7 @@ export const InputBar = ({ /> )}
-
+
{emojiPicker.open ? ( { + const {rightSidebar} = useAppMainState.getState(); + const lastItem = rightSidebar.history.length - 1; + const currentState = rightSidebar.history[lastItem]; + const isRightSidebarOpen = !!currentState; + + return ( + + {children} + + ); +}; diff --git a/src/script/components/InputBar/InputBarControls/InputBarControls.tsx b/src/script/components/InputBar/InputBarControls/InputBarControls.tsx index b0e5b1a1518..5fab850d652 100644 --- a/src/script/components/InputBar/InputBarControls/InputBarControls.tsx +++ b/src/script/components/InputBar/InputBarControls/InputBarControls.tsx @@ -17,6 +17,12 @@ * */ +import {useEffect} from 'react'; + +import {amplify} from 'amplify'; + +import {WebAppEvents} from '@wireapp/webapp-events'; + import {Conversation} from 'src/script/entity/Conversation'; import {ControlButtons} from './ControlButtons'; @@ -69,6 +75,14 @@ export const InputBarControls = ({ }: InputBarControlsProps) => { const enableSending = messageContent.text.length > 0; + useEffect(() => { + amplify.subscribe(WebAppEvents.SHORTCUT.PING, onClickPing); + + return () => { + amplify.unsubscribeAll(WebAppEvents.SHORTCUT.PING); + }; + }, [onClickPing]); + return (
    diff --git a/src/script/components/InputBar/useDraftState/useDraftState.ts b/src/script/components/InputBar/useDraftState/useDraftState.ts index 97dae1d7963..3e2d97c9508 100644 --- a/src/script/components/InputBar/useDraftState/useDraftState.ts +++ b/src/script/components/InputBar/useDraftState/useDraftState.ts @@ -26,7 +26,7 @@ import {Conversation} from 'src/script/entity/Conversation'; import {StorageRepository} from 'src/script/storage'; import {sanitizeMarkdown} from 'Util/MarkdownUtil'; -import {loadDraftState as loadDraft, saveDraftState as saveDraft} from '../util/DraftStateUtil'; +import {loadDraftState, saveDraftState} from '../util/DraftStateUtil'; interface UseDraftStateProps { conversation: Conversation; @@ -36,30 +36,27 @@ interface UseDraftStateProps { } export const useDraftState = ({conversation, storageRepository, messageRepository, editorRef}: UseDraftStateProps) => { - const resetDraftState = useCallback(() => { + const reset = useCallback(() => { editorRef.current?.dispatchCommand(CLEAR_EDITOR_COMMAND, undefined); }, [editorRef]); - const saveDraftState = useCallback( - async (editorState: string, plainMessage: string, replyId?: string) => { - await saveDraft({ - storageRepository, - conversation, - editorState, - plainMessage: sanitizeMarkdown(plainMessage), - replyId, - }); - }, - [conversation, storageRepository], - ); - - const loadDraftState = useCallback(async () => { - return loadDraft(conversation, storageRepository, messageRepository); + const load = useCallback(async () => { + return loadDraftState(conversation, storageRepository, messageRepository); }, [conversation, messageRepository, storageRepository]); + const save = async (editorState: string, plainMessage: string, replyId = '') => { + void saveDraftState({ + storageRepository, + conversation, + editorState, + plainMessage: sanitizeMarkdown(plainMessage), + replyId, + }); + }; + return { - resetDraftState, - saveDraftState, - loadDraftState, + reset, + load, + save, }; }; diff --git a/src/script/components/InputBar/useFileHandling/useFileHandling.ts b/src/script/components/InputBar/useFileHandling/useFileHandling.ts index 6f0fff9c61a..2c0e1fc795e 100644 --- a/src/script/components/InputBar/useFileHandling/useFileHandling.ts +++ b/src/script/components/InputBar/useFileHandling/useFileHandling.ts @@ -19,17 +19,26 @@ import {useEffect, useState} from 'react'; -import {t} from 'Util/LocalizerUtil'; -import {formatLocale} from 'Util/TimeUtil'; -import {getFileExtension} from 'Util/util'; +import {amplify} from 'amplify'; + +import {WebAppEvents} from '@wireapp/webapp-events'; + +import {useFilePaste} from './useFilePaste/useFilePaste'; interface UseFileHandlingProps { uploadDroppedFiles: (files: File[]) => void; + uploadImages: (images: File[]) => void; } -export const useFileHandling = ({uploadDroppedFiles}: UseFileHandlingProps) => { +export const useFileHandling = ({uploadDroppedFiles, uploadImages}: UseFileHandlingProps) => { const [pastedFile, setPastedFile] = useState(null); + useFilePaste({ + onFilePasted: file => { + setPastedFile(file); + }, + }); + const clearPastedFile = () => setPastedFile(null); const sendPastedFile = () => { @@ -39,24 +48,6 @@ export const useFileHandling = ({uploadDroppedFiles}: UseFileHandlingProps) => { } }; - const handlePasteFiles = (files: FileList): void => { - const [pastedFile] = files; - - if (!pastedFile) { - return; - } - const {lastModified} = pastedFile; - - const date = formatLocale(lastModified || new Date(), 'PP, pp'); - const fileName = `${t('conversationSendPastedFile', {date})}.${getFileExtension(pastedFile.name)}`; - - const newFile = new File([pastedFile], fileName, { - type: pastedFile.type, - }); - - setPastedFile(newFile); - }; - const sendImageOnEnterClick = (event: KeyboardEvent) => { if (event.key === 'Enter' && !event.shiftKey && !event.altKey && !event.metaKey) { sendPastedFile(); @@ -75,10 +66,17 @@ export const useFileHandling = ({uploadDroppedFiles}: UseFileHandlingProps) => { }; }, [pastedFile]); + useEffect(() => { + amplify.subscribe(WebAppEvents.CONVERSATION.IMAGE.SEND, uploadImages); + + return () => { + amplify.unsubscribeAll(WebAppEvents.CONVERSATION.IMAGE.SEND); + }; + }, []); + return { pastedFile, clearPastedFile, sendPastedFile, - handlePasteFiles, }; }; diff --git a/src/script/components/InputBar/useFileHandling/useFilePaste/useFilePaste.ts b/src/script/components/InputBar/useFileHandling/useFilePaste/useFilePaste.ts new file mode 100644 index 00000000000..592b84fb246 --- /dev/null +++ b/src/script/components/InputBar/useFileHandling/useFilePaste/useFilePaste.ts @@ -0,0 +1,73 @@ +/* + * Wire + * Copyright (C) 2022 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import {useCallback, useEffect} from 'react'; + +import {checkFileSharingPermission} from 'Components/Conversation/utils/checkFileSharingPermission'; +import {t} from 'Util/LocalizerUtil'; +import {formatLocale} from 'Util/TimeUtil'; +import {getFileExtension} from 'Util/util'; + +interface UseFilePasteParams { + onFilePasted: (file: File) => void; +} + +export const useFilePaste = ({onFilePasted}: UseFilePasteParams) => { + const handlePasteFiles = useCallback( + (files: FileList): void => { + const [pastedFile] = files; + + if (!pastedFile) { + return; + } + const {lastModified} = pastedFile; + + const date = formatLocale(lastModified || new Date(), 'PP, pp'); + const fileName = `${t('conversationSendPastedFile', {date})}.${getFileExtension(pastedFile.name)}`; + + const newFile = new File([pastedFile], fileName, { + type: pastedFile.type, + }); + + onFilePasted(newFile); + }, + [onFilePasted], + ); + + const handleFilePasting = useCallback( + (event: ClipboardEvent) => { + if (event.clipboardData?.types.includes('text/plain')) { + return; + } + // Avoid copying the filename into the input field + event.preventDefault(); + const files = event.clipboardData?.files; + + if (files) { + handlePasteFiles(files); + } + }, + [handlePasteFiles], + ); + + useEffect(() => { + document.addEventListener('paste', checkFileSharingPermission(handleFilePasting)); + return () => document.removeEventListener('paste', checkFileSharingPermission(handleFilePasting)); + }, [handleFilePasting]); +}; diff --git a/src/script/components/InputBar/useFilePaste/useFilePaste.test.ts b/src/script/components/InputBar/useFilePaste/useFilePaste.test.ts deleted file mode 100644 index d9fac3b410a..00000000000 --- a/src/script/components/InputBar/useFilePaste/useFilePaste.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Wire - * Copyright (C) 2022 Wire Swiss GmbH - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see http://www.gnu.org/licenses/. - * - */ - -import {renderHook, act, fireEvent} from '@testing-library/react'; - -import {useFilePaste} from './useFilePaste'; - -describe('useFilePaste', () => { - it('Calls the onFilePasted callback when a file is pasted', async () => { - const onFilePasted = jest.fn(); - renderHook(() => useFilePaste(onFilePasted)); - const files = [new File([''], 'test.jpg', {type: 'image/jpeg'})]; - - act(() => { - fireEvent.paste(document, {clipboardData: {types: ['image/jpeg'], files}}); - }); - expect(onFilePasted).toHaveBeenCalledWith(files); - }); - - it('Ignores paste that are plain text', async () => { - const onFilePasted = jest.fn(); - renderHook(() => useFilePaste(onFilePasted)); - - act(() => { - fireEvent.paste(document, {clipboardData: {types: ['text/plain']}}); - }); - expect(onFilePasted).not.toHaveBeenCalled(); - }); -}); diff --git a/src/script/components/InputBar/useFilePaste/useFilePaste.ts b/src/script/components/InputBar/useFilePaste/useFilePaste.ts deleted file mode 100644 index 5e55baefb42..00000000000 --- a/src/script/components/InputBar/useFilePaste/useFilePaste.ts +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Wire - * Copyright (C) 2022 Wire Swiss GmbH - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see http://www.gnu.org/licenses/. - * - */ - -import {useEffect} from 'react'; - -export const useFilePaste = (onFilePasted: (files: FileList) => void) => { - useEffect(() => { - const handleFilePasting = (event: ClipboardEvent) => { - if (event.clipboardData?.types.includes('text/plain')) { - return; - } - // Avoid copying the filename into the input field - event.preventDefault(); - const files = event.clipboardData?.files; - if (files) { - onFilePasted(files); - } - }; - document.addEventListener('paste', handleFilePasting); - return () => document.removeEventListener('paste', handleFilePasting); - }, [onFilePasted]); -}; diff --git a/src/script/components/InputBar/useGiphy/useGiphy.ts b/src/script/components/InputBar/useGiphy/useGiphy.ts index 33c7cdbe2c8..2feae2dd318 100644 --- a/src/script/components/InputBar/useGiphy/useGiphy.ts +++ b/src/script/components/InputBar/useGiphy/useGiphy.ts @@ -17,16 +17,36 @@ * */ -import {useMemo} from 'react'; +import {useCallback, useEffect, useMemo} from 'react'; + +import {amplify} from 'amplify'; + +import {WebAppEvents} from '@wireapp/webapp-events'; + +import {MessageRepository, OutgoingQuote} from 'src/script/conversation/MessageRepository'; +import {Conversation} from 'src/script/entity/Conversation'; interface UseGiphyProps { text: string; maxLength: number; isMessageFormatButtonsFlagEnabled: boolean; openGiphy: (inputValue: string) => void; + generateQuote: () => Promise; + messageRepository: MessageRepository; + conversation: Conversation; + cancelMessageEditing: (resetDraft?: boolean) => void; } -export const useGiphy = ({text, maxLength, isMessageFormatButtonsFlagEnabled, openGiphy}: UseGiphyProps) => { +export const useGiphy = ({ + text, + maxLength, + isMessageFormatButtonsFlagEnabled, + openGiphy, + generateQuote, + messageRepository, + conversation, + cancelMessageEditing, +}: UseGiphyProps) => { const showGiphyButton = useMemo(() => { if (isMessageFormatButtonsFlagEnabled) { return text.length > 0; @@ -36,8 +56,27 @@ export const useGiphy = ({text, maxLength, isMessageFormatButtonsFlagEnabled, op const handleGifClick = () => openGiphy(text); + const sendGiphy = useCallback( + (gifUrl: string, tag: string): void => { + void generateQuote().then(quoteEntity => { + void messageRepository.sendGif(conversation, gifUrl, tag, quoteEntity); + cancelMessageEditing(true); + }); + }, + [cancelMessageEditing, conversation, generateQuote, messageRepository], + ); + + useEffect(() => { + amplify.subscribe(WebAppEvents.EXTENSIONS.GIPHY.SEND, sendGiphy); + + return () => { + amplify.unsubscribeAll(WebAppEvents.EXTENSIONS.GIPHY.SEND); + }; + }, [sendGiphy, cancelMessageEditing]); + return { showGiphyButton, handleGifClick, + sendGiphy, }; }; diff --git a/src/script/components/InputBar/useMessageHandling/useMessageHandling.ts b/src/script/components/InputBar/useMessageHandling/useMessageHandling.ts index 4e58531cbcc..982d0efb26f 100644 --- a/src/script/components/InputBar/useMessageHandling/useMessageHandling.ts +++ b/src/script/components/InputBar/useMessageHandling/useMessageHandling.ts @@ -25,6 +25,8 @@ import {LexicalEditor} from 'lexical'; import {WebAppEvents} from '@wireapp/webapp-events'; import {PrimaryModal} from 'Components/Modals/PrimaryModal'; +import {showWarningModal} from 'Components/Modals/utils/showWarningModal'; +import {Config} from 'src/script/Config'; import {ConversationRepository} from 'src/script/conversation/ConversationRepository'; import {ConversationVerificationState} from 'src/script/conversation/ConversationVerificationState'; import {MessageRepository, OutgoingQuote} from 'src/script/conversation/MessageRepository'; @@ -37,7 +39,11 @@ import {MessageHasher} from 'src/script/message/MessageHasher'; import {QuoteEntity} from 'src/script/message/QuoteEntity'; import {t} from 'Util/LocalizerUtil'; +import {MessageContent} from '../common/messageContent/messageContent'; +import {handleClickOutsideOfInputBar} from '../util/clickHandlers'; + interface UseMessageHandlingProps { + messageContent: MessageContent; conversation: Conversation; conversationRepository: ConversationRepository; eventRepository: EventRepository; @@ -45,9 +51,12 @@ interface UseMessageHandlingProps { editorRef: React.RefObject; onResetDraftState: () => void; onSaveDraft: (replyId?: string) => void; + pastedFile: File | null; + sendPastedFile: () => void; } export const useMessageHandling = ({ + messageContent, conversation, conversationRepository, eventRepository, @@ -55,65 +64,114 @@ export const useMessageHandling = ({ editorRef, onResetDraftState, onSaveDraft, + pastedFile, + sendPastedFile, }: UseMessageHandlingProps) => { const [editedMessage, setEditedMessage] = useState(); const [replyMessageEntity, setReplyMessageEntity] = useState(null); - const generateQuote = async (): Promise => { - if (!replyMessageEntity) { - return undefined; - } + const isEditing = !!editedMessage; + const isReplying = !!replyMessageEntity; + + const generateQuote = useCallback(async (): Promise => { + return !replyMessageEntity + ? Promise.resolve(undefined) + : eventRepository.eventService + .loadEvent(replyMessageEntity.conversation_id, replyMessageEntity.id) + .then(MessageHasher.hashEvent) + .then((messageHash: ArrayBuffer) => { + return new QuoteEntity({ + hash: messageHash, + messageId: replyMessageEntity.id, + userId: replyMessageEntity.from, + }) as OutgoingQuote; + }); + }, [eventRepository.eventService, replyMessageEntity]); + + const cancelMessageReply = useCallback( + (resetDraft = true) => { + setReplyMessageEntity(null); + onSaveDraft(); + + if (resetDraft) { + onResetDraftState(); + } + }, + [onResetDraftState, onSaveDraft], + ); - const event = await eventRepository.eventService.loadEvent( - replyMessageEntity.conversation_id, - replyMessageEntity.id, - ); - if (!event) { - return undefined; - } + const cancelMessageEditing = useCallback( + (resetDraft = true) => { + setEditedMessage(undefined); + setReplyMessageEntity(null); - const messageHash = await MessageHasher.hashEvent(event); - return new QuoteEntity({ - hash: messageHash, - messageId: replyMessageEntity.id, - userId: replyMessageEntity.from, - }) as OutgoingQuote; - }; + if (resetDraft) { + onResetDraftState(); + } + }, + [onResetDraftState], + ); - const sendMessageEdit = (messageText: string, mentions: MentionEntity[]): void | Promise => { - const mentionEntities = mentions.slice(0); - cancelMessageEditing(true); + const sendMessageEdit = useCallback( + (messageText: string, mentions: MentionEntity[]): void | Promise => { + const mentionEntities = mentions.slice(0); + cancelMessageEditing(true); - if (!messageText.length && editedMessage) { - return messageRepository.deleteMessageForEveryone(conversation, editedMessage); - } + if (!messageText.length && editedMessage) { + return messageRepository.deleteMessageForEveryone(conversation, editedMessage); + } - if (editedMessage) { - messageRepository.sendMessageEdit(conversation, messageText, editedMessage, mentionEntities).catch(error => { - if (error.type !== ConversationError.TYPE.NO_MESSAGE_CHANGES) { - throw error; - } - }); + if (editedMessage) { + messageRepository.sendMessageEdit(conversation, messageText, editedMessage, mentionEntities).catch(error => { + if (error.type !== ConversationError.TYPE.NO_MESSAGE_CHANGES) { + throw error; + } + }); - cancelMessageReply(); - } - }; + cancelMessageReply(); + } + }, + [cancelMessageEditing, cancelMessageReply, conversation, editedMessage, messageRepository], + ); - const sendTextMessage = (messageText: string, mentions: MentionEntity[]) => { - if (messageText.length) { - const mentionEntities = mentions.slice(0); + const sendTextMessage = useCallback( + (messageText: string, mentions: MentionEntity[]) => { + if (messageText.length) { + const mentionEntities = mentions.slice(0); - void generateQuote().then(quoteEntity => { - void messageRepository.sendTextWithLinkPreview(conversation, messageText, mentionEntities, quoteEntity); - cancelMessageReply(); - }); + void generateQuote().then(quoteEntity => { + void messageRepository.sendTextWithLinkPreview(conversation, messageText, mentionEntities, quoteEntity); + cancelMessageReply(); + }); + } + }, + [cancelMessageReply, conversation, generateQuote, messageRepository], + ); + + const sendMessage = useCallback((): void => { + if (pastedFile) { + return void sendPastedFile(); } - }; - const sendMessage = (text: string, mentions: MentionEntity[]): void => { + const text = messageContent.text; + const mentions = messageContent.mentions ?? []; + const messageTrimmedStart = text.trimStart(); const messageText = messageTrimmedStart.trimEnd(); + const config = Config.getConfig(); + + const isMessageTextTooLong = text.length > config.MAXIMUM_MESSAGE_LENGTH; + + if (isMessageTextTooLong) { + showWarningModal( + t('modalConversationMessageTooLongHeadline'), + t('modalConversationMessageTooLongMessage', {number: config.MAXIMUM_MESSAGE_LENGTH}), + ); + + return; + } + if (editedMessage) { void sendMessageEdit(messageText, mentions); } else { @@ -122,9 +180,18 @@ export const useMessageHandling = ({ editorRef.current?.focus(); onResetDraftState(); - }; - - const handleSendMessage = async (text: string, mentions: MentionEntity[]) => { + }, [ + editedMessage, + editorRef, + onResetDraftState, + pastedFile, + sendMessageEdit, + sendPastedFile, + sendTextMessage, + messageContent, + ]); + + const handleSendMessage = useCallback(async () => { await conversationRepository.refreshMLSConversationVerificationState(conversation); const isE2EIDegraded = conversation.mlsVerificationState() === ConversationVerificationState.DEGRADED; @@ -133,7 +200,7 @@ export const useMessageHandling = ({ secondaryAction: { action: () => { conversation.mlsVerificationState(ConversationVerificationState.UNVERIFIED); - sendMessage(text, mentions); + sendMessage(); }, text: t('conversation.E2EISendAnyway'), }, @@ -147,52 +214,41 @@ export const useMessageHandling = ({ }, }); } else { - sendMessage(text, mentions); - } - }; - - const cancelMessageEditing = (resetDraft = true) => { - setEditedMessage(undefined); - setReplyMessageEntity(null); - - if (resetDraft) { - onResetDraftState(); + sendMessage(); } - }; - - const cancelMessageReply = (resetDraft = true) => { - setReplyMessageEntity(null); + }, [conversation, conversationRepository, sendMessage]); - if (resetDraft) { - onResetDraftState(); - } - }; - - const editMessage = (messageEntity?: ContentMessage) => { - if (messageEntity?.isEditable() && messageEntity !== editedMessage) { - cancelMessageReply(); - cancelMessageEditing(true); - setEditedMessage(messageEntity); - - const quote = messageEntity.quote(); - if (quote && conversation) { - void messageRepository - .getMessageInConversationById(conversation, quote.messageId) - .then(quotedMessage => setReplyMessageEntity(quotedMessage)); + const editMessage = useCallback( + (messageEntity?: ContentMessage) => { + if (messageEntity?.isEditable() && messageEntity !== editedMessage) { + cancelMessageReply(); + cancelMessageEditing(true); + setEditedMessage(messageEntity); + + const quote = messageEntity.quote(); + if (quote && conversation) { + void messageRepository + .getMessageInConversationById(conversation, quote.messageId) + .then(quotedMessage => setReplyMessageEntity(quotedMessage)); + } } - } - }; + }, + [cancelMessageEditing, cancelMessageReply, conversation, editedMessage, messageRepository], + ); - const replyMessage = (messageEntity: ContentMessage): void => { - if (messageEntity?.isReplyable() && messageEntity !== replyMessageEntity) { - cancelMessageReply(false); - cancelMessageEditing(!!editedMessage); - setReplyMessageEntity(messageEntity); - onSaveDraft(messageEntity.id); + const replyMessage = useCallback( + (messageEntity: ContentMessage) => { + if (messageEntity?.isReplyable() && messageEntity !== replyMessageEntity) { + cancelMessageReply(false); + cancelMessageEditing(!!editedMessage); + setReplyMessageEntity(messageEntity); + onSaveDraft(messageEntity.id); - editorRef.current?.focus(); - } - }; + editorRef.current?.focus(); + } + }, + [cancelMessageEditing, cancelMessageReply, editedMessage, editorRef, onSaveDraft, replyMessageEntity], + ); const handleRepliedMessageDeleted = useCallback( (messageId: string) => { @@ -224,17 +280,50 @@ export const useMessageHandling = ({ amplify.unsubscribeAll(WebAppEvents.CONVERSATION.MESSAGE.UPDATED); amplify.unsubscribeAll(WebAppEvents.CONVERSATION.MESSAGE.EDIT); }; - }, [handleRepliedMessageDeleted, handleRepliedMessageUpdated]); + }, [handleRepliedMessageDeleted, replyMessage, editMessage, handleRepliedMessageUpdated]); + + useEffect(() => { + const onWindowClick = (event: Event): void => + handleClickOutsideOfInputBar(event, () => { + // We want to add a timeout in case the click happens because the user switched conversation and the component is unmounting. + // In this case we want to keep the edited message for this conversation + setTimeout(() => { + cancelMessageEditing(true); + cancelMessageReply(); + }); + }); + if (isEditing) { + window.addEventListener('click', onWindowClick); + + return () => { + window.removeEventListener('click', onWindowClick); + }; + } + + return () => undefined; + }, [cancelMessageEditing, cancelMessageReply, isEditing]); + + useEffect(() => { + amplify.subscribe(WebAppEvents.CONVERSATION.MESSAGE.REPLY, replyMessage); + conversation.isTextInputReady(true); + + return () => { + amplify.unsubscribeAll(WebAppEvents.CONVERSATION.MESSAGE.REPLY); + conversation.isTextInputReady(false); + }; + }, [replyMessage, conversation]); return { editedMessage, replyMessageEntity, - isEditing: !!editedMessage, - isReplying: !!replyMessageEntity, + isEditing, + isReplying, + handleSendMessage2: sendMessage, handleSendMessage, cancelMessageEditing, cancelMessageReply, editMessage, replyMessage, + generateQuote, }; }; diff --git a/src/script/components/InputBar/useMessageHandlingV2/useMessageHandlingV2.ts b/src/script/components/InputBar/useMessageHandlingV2/useMessageHandlingV2.ts new file mode 100644 index 00000000000..fef3e9d1a76 --- /dev/null +++ b/src/script/components/InputBar/useMessageHandlingV2/useMessageHandlingV2.ts @@ -0,0 +1,47 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import {LexicalEditor} from 'lexical'; + +import {ConversationRepository} from 'src/script/conversation/ConversationRepository'; +import {MessageRepository} from 'src/script/conversation/MessageRepository'; +import {Conversation} from 'src/script/entity/Conversation'; +import {EventRepository} from 'src/script/event/EventRepository'; + +interface UseMessageHandlingProps { + conversation: Conversation; + conversationRepository: ConversationRepository; + eventRepository: EventRepository; + messageRepository: MessageRepository; + editorRef: React.RefObject; + onResetDraftState: () => void; + onSaveDraft: (replyId?: string) => void; +} + +export const useMessageHandlingV2 = ({ + conversation, + conversationRepository, + eventRepository, + messageRepository, + editorRef, + onResetDraftState, + onSaveDraft, +}: UseMessageHandlingProps) => { + return {}; +}; From 3fa69a2bce61cbe546dca6b1b70c2acfd4ba5025 Mon Sep 17 00:00:00 2001 From: Olaf Sulich Date: Mon, 17 Feb 2025 10:14:07 +0100 Subject: [PATCH 05/13] chore: remove unused variable --- src/script/components/InputBar/InputBar.tsx | 5 ++--- .../InputBar/InputBarContainer/InputBarContainer.tsx | 6 ++---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/script/components/InputBar/InputBar.tsx b/src/script/components/InputBar/InputBar.tsx index 7d01ef89914..2c2985fd8ac 100644 --- a/src/script/components/InputBar/InputBar.tsx +++ b/src/script/components/InputBar/InputBar.tsx @@ -86,7 +86,6 @@ interface InputBarProps { uploadImages: (images: File[]) => void; uploadFiles: (files: File[]) => void; } -const conversationInputBarClassName = 'conversation-input-bar'; export const InputBar = ({ conversation, @@ -259,8 +258,8 @@ export const InputBar = ({ )}
    diff --git a/src/script/components/InputBar/InputBarContainer/InputBarContainer.tsx b/src/script/components/InputBar/InputBarContainer/InputBarContainer.tsx index 8e785a5f7a5..78462b0a5b2 100644 --- a/src/script/components/InputBar/InputBarContainer/InputBarContainer.tsx +++ b/src/script/components/InputBar/InputBarContainer/InputBarContainer.tsx @@ -29,8 +29,6 @@ interface InputBarContainerProps { children: ReactNode; } -const conversationInputBarClassName = 'conversation-input-bar'; - export const InputBarContainer = ({children}: InputBarContainerProps) => { const {rightSidebar} = useAppMainState.getState(); const lastItem = rightSidebar.history.length - 1; @@ -39,8 +37,8 @@ export const InputBarContainer = ({children}: InputBarContainerProps) => { return ( {children} From e6850598c2fc9f0faf01457bc96d99914babec3c Mon Sep 17 00:00:00 2001 From: Olaf Sulich Date: Mon, 17 Feb 2025 10:33:06 +0100 Subject: [PATCH 06/13] feat(useFIlePaste): add tests --- .../InputBar/FileInput/FileInput.tsx | 4 +- .../useFilePaste/useFilePaste.test.ts | 169 ++++++++++++++++++ 2 files changed, 170 insertions(+), 3 deletions(-) create mode 100644 src/script/components/InputBar/useFileHandling/useFilePaste/useFilePaste.test.ts diff --git a/src/script/components/InputBar/FileInput/FileInput.tsx b/src/script/components/InputBar/FileInput/FileInput.tsx index 8ac201cd6f0..bf8225a9cc2 100644 --- a/src/script/components/InputBar/FileInput/FileInput.tsx +++ b/src/script/components/InputBar/FileInput/FileInput.tsx @@ -17,8 +17,6 @@ * */ -import {FC} from 'react'; - import {PastedFileControls} from '../PastedFileControls/PastedFileControls'; interface FileInputProps { @@ -27,7 +25,7 @@ interface FileInputProps { onSendPastedFile: () => void; } -export const FileInput: FC = ({pastedFile, onClearPastedFile, onSendPastedFile}) => { +export const FileInput = ({pastedFile, onClearPastedFile, onSendPastedFile}: FileInputProps) => { if (!pastedFile) { return null; } diff --git a/src/script/components/InputBar/useFileHandling/useFilePaste/useFilePaste.test.ts b/src/script/components/InputBar/useFileHandling/useFilePaste/useFilePaste.test.ts new file mode 100644 index 00000000000..04a09dc9891 --- /dev/null +++ b/src/script/components/InputBar/useFileHandling/useFilePaste/useFilePaste.test.ts @@ -0,0 +1,169 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import {act, renderHook} from '@testing-library/react'; + +import * as checkFileSharingPermissionModule from 'Components/Conversation/utils/checkFileSharingPermission'; +import * as LocalizerUtil from 'Util/LocalizerUtil'; +import * as TimeUtil from 'Util/TimeUtil'; + +import {useFilePaste} from './useFilePaste'; + +jest.mock('Components/Conversation/utils/checkFileSharingPermission', () => ({ + checkFileSharingPermission: jest.fn(callback => callback), +})); + +jest.mock('Util/LocalizerUtil', () => ({ + t: jest.fn(), +})); + +jest.mock('Util/TimeUtil', () => ({ + formatLocale: jest.fn(), +})); + +describe('useFilePaste', () => { + const mockOnFilePasted = jest.fn(); + const mockDate = new Date('2024-01-01'); + const mockFormattedDate = '1 Jan 2024, 12:00'; + + beforeEach(() => { + jest.clearAllMocks(); + (LocalizerUtil.t as jest.Mock).mockImplementation((key, params) => { + if (key === 'conversationSendPastedFile' && params?.date) { + return `Pasted file from ${params.date}`; + } + return key; + }); + (TimeUtil.formatLocale as jest.Mock).mockReturnValue(mockFormattedDate); + }); + + it('handles file paste event', () => { + renderHook(() => useFilePaste({onFilePasted: mockOnFilePasted})); + + const file = new File(['test content'], 'test.txt', {type: 'text/plain', lastModified: mockDate.getTime()}); + const clipboardEvent = new MockClipboardEvent([file]); + + act(() => { + document.dispatchEvent(clipboardEvent); + }); + + expect(mockOnFilePasted).toHaveBeenCalled(); + const calledWithFile = mockOnFilePasted.mock.calls[0][0]; + expect(calledWithFile instanceof File).toBe(true); + expect(calledWithFile.name).toBe(`Pasted file from ${mockFormattedDate}.txt`); + }); + + it('ignores paste events with text/plain content', () => { + renderHook(() => useFilePaste({onFilePasted: mockOnFilePasted})); + + const clipboardEvent = new MockClipboardEvent([], ['text/plain']); + + act(() => { + document.dispatchEvent(clipboardEvent); + }); + + expect(mockOnFilePasted).not.toHaveBeenCalled(); + }); + + it('does nothing when no files are pasted', () => { + renderHook(() => useFilePaste({onFilePasted: mockOnFilePasted})); + + const clipboardEvent = new MockClipboardEvent([]); + + act(() => { + document.dispatchEvent(clipboardEvent); + }); + + expect(mockOnFilePasted).not.toHaveBeenCalled(); + }); + + it('uses checkFileSharingPermission wrapper', () => { + renderHook(() => useFilePaste({onFilePasted: mockOnFilePasted})); + + expect(checkFileSharingPermissionModule.checkFileSharingPermission).toHaveBeenCalled(); + }); +}); + +/** + * Mock implementation of the browser's DataTransfer API + * + * Why do we need this? + * 1. DataTransfer is a browser API not available in Jest's test environment + * 2. When files are pasted in a real browser, they come as a ClipboardEvent + * containing a DataTransfer object with: + * - files: List of pasted files + * - types: Content types being pasted (e.g., 'text/plain', 'Files') + * 3. Our hook checks these properties to: + * - Filter out text-only pastes + * - Handle pasted files + * + * This mock allows us to simulate file paste events as they would occur in a real browser. + */ +class MockDataTransfer implements Partial { + readonly files: FileList; + readonly types: string[]; + readonly items: DataTransferItemList; + readonly dropEffect: 'none' = 'none'; + readonly effectAllowed: 'none' = 'none'; + + constructor(files: File[] = []) { + this.files = { + ...files, + length: files.length, + item: (index: number) => files[index] || null, + [Symbol.iterator]: function* () { + for (let i = 0; i < files.length; i++) { + yield files[i]; + } + }, + } as unknown as FileList; + this.types = []; + this.items = { + length: 0, + add: jest.fn(), + clear: jest.fn(), + remove: jest.fn(), + } as unknown as DataTransferItemList; + } + + clearData(): void {} + getData(): string { + return ''; + } + setData(): boolean { + return false; + } + setDragImage(): void {} +} + +class MockClipboardEvent extends Event { + readonly clipboardData: DataTransfer; + constructor(files: File[] = [], types: string[] = []) { + super('paste'); + const dataTransfer = new MockDataTransfer(files); + Object.defineProperty(dataTransfer, 'types', { + value: types, + enumerable: true, + }); + this.clipboardData = dataTransfer; + } +} + +global.DataTransfer = MockDataTransfer as unknown as typeof DataTransfer; +global.ClipboardEvent = MockClipboardEvent as unknown as typeof ClipboardEvent; From 66cdcda65128523199f43f567db5c047eba14766 Mon Sep 17 00:00:00 2001 From: Olaf Sulich Date: Mon, 17 Feb 2025 10:43:07 +0100 Subject: [PATCH 07/13] chore: cleanup --- .../InputBar/useDraftState/useDraftState.ts | 4 +- .../useFilePaste/useFilePaste.ts | 14 +++--- .../useMessageHandlingV2.ts | 47 ------------------- 3 files changed, 9 insertions(+), 56 deletions(-) delete mode 100644 src/script/components/InputBar/useMessageHandlingV2/useMessageHandlingV2.ts diff --git a/src/script/components/InputBar/useDraftState/useDraftState.ts b/src/script/components/InputBar/useDraftState/useDraftState.ts index 3e2d97c9508..444f43b100d 100644 --- a/src/script/components/InputBar/useDraftState/useDraftState.ts +++ b/src/script/components/InputBar/useDraftState/useDraftState.ts @@ -44,12 +44,12 @@ export const useDraftState = ({conversation, storageRepository, messageRepositor return loadDraftState(conversation, storageRepository, messageRepository); }, [conversation, messageRepository, storageRepository]); - const save = async (editorState: string, plainMessage: string, replyId = '') => { + const save = async (editorState: string, text: string, replyId = '') => { void saveDraftState({ storageRepository, conversation, editorState, - plainMessage: sanitizeMarkdown(plainMessage), + plainMessage: sanitizeMarkdown(text), replyId, }); }; diff --git a/src/script/components/InputBar/useFileHandling/useFilePaste/useFilePaste.ts b/src/script/components/InputBar/useFileHandling/useFilePaste/useFilePaste.ts index 592b84fb246..300425dccb7 100644 --- a/src/script/components/InputBar/useFileHandling/useFilePaste/useFilePaste.ts +++ b/src/script/components/InputBar/useFileHandling/useFilePaste/useFilePaste.ts @@ -29,7 +29,7 @@ interface UseFilePasteParams { } export const useFilePaste = ({onFilePasted}: UseFilePasteParams) => { - const handlePasteFiles = useCallback( + const processClipboardFiles = useCallback( (files: FileList): void => { const [pastedFile] = files; @@ -50,7 +50,7 @@ export const useFilePaste = ({onFilePasted}: UseFilePasteParams) => { [onFilePasted], ); - const handleFilePasting = useCallback( + const handlePasteEvent = useCallback( (event: ClipboardEvent) => { if (event.clipboardData?.types.includes('text/plain')) { return; @@ -60,14 +60,14 @@ export const useFilePaste = ({onFilePasted}: UseFilePasteParams) => { const files = event.clipboardData?.files; if (files) { - handlePasteFiles(files); + processClipboardFiles(files); } }, - [handlePasteFiles], + [processClipboardFiles], ); useEffect(() => { - document.addEventListener('paste', checkFileSharingPermission(handleFilePasting)); - return () => document.removeEventListener('paste', checkFileSharingPermission(handleFilePasting)); - }, [handleFilePasting]); + document.addEventListener('paste', checkFileSharingPermission(handlePasteEvent)); + return () => document.removeEventListener('paste', checkFileSharingPermission(handlePasteEvent)); + }, [handlePasteEvent]); }; diff --git a/src/script/components/InputBar/useMessageHandlingV2/useMessageHandlingV2.ts b/src/script/components/InputBar/useMessageHandlingV2/useMessageHandlingV2.ts deleted file mode 100644 index fef3e9d1a76..00000000000 --- a/src/script/components/InputBar/useMessageHandlingV2/useMessageHandlingV2.ts +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Wire - * Copyright (C) 2024 Wire Swiss GmbH - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see http://www.gnu.org/licenses/. - * - */ - -import {LexicalEditor} from 'lexical'; - -import {ConversationRepository} from 'src/script/conversation/ConversationRepository'; -import {MessageRepository} from 'src/script/conversation/MessageRepository'; -import {Conversation} from 'src/script/entity/Conversation'; -import {EventRepository} from 'src/script/event/EventRepository'; - -interface UseMessageHandlingProps { - conversation: Conversation; - conversationRepository: ConversationRepository; - eventRepository: EventRepository; - messageRepository: MessageRepository; - editorRef: React.RefObject; - onResetDraftState: () => void; - onSaveDraft: (replyId?: string) => void; -} - -export const useMessageHandlingV2 = ({ - conversation, - conversationRepository, - eventRepository, - messageRepository, - editorRef, - onResetDraftState, - onSaveDraft, -}: UseMessageHandlingProps) => { - return {}; -}; From 994f2d652b87b0ad93fb643b994ed6d484ed760e Mon Sep 17 00:00:00 2001 From: Olaf Sulich Date: Mon, 17 Feb 2025 11:55:55 +0100 Subject: [PATCH 08/13] refactor(InputBar): don't reuse isMessageFormatButtonsFlagEnabled flag --- src/script/components/InputBar/InputBar.tsx | 4 ---- .../InputBar/InputBarControls/InputBarControls.tsx | 5 +++-- src/script/components/InputBar/useGiphy/useGiphy.ts | 5 +++-- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/script/components/InputBar/InputBar.tsx b/src/script/components/InputBar/InputBar.tsx index 2c2985fd8ac..22fbb3c6e26 100644 --- a/src/script/components/InputBar/InputBar.tsx +++ b/src/script/components/InputBar/InputBar.tsx @@ -151,8 +151,6 @@ export const InputBar = ({ const hasLocalEphemeralTimer = isSelfDeletingMessagesEnabled && !!localMessageTimer && !hasGlobalMessageTimer; const isTypingRef = useRef(false); - const isMessageFormatButtonsFlagEnabled = CONFIG.FEATURE.ENABLE_MESSAGE_FORMAT_BUTTONS; - const shouldReplaceEmoji = useUserPropertyValue( () => propertiesRepository.getPreference(PROPERTIES_TYPE.EMOJI.REPLACE_INLINE), WebAppEvents.PROPERTIES.UPDATE.EMOJI.REPLACE_INLINE, @@ -234,7 +232,6 @@ export const InputBar = ({ const giphy = useGiphy({ text: messageContent.text, maxLength: CONFIG.GIPHY_TEXT_LENGTH, - isMessageFormatButtonsFlagEnabled, openGiphy, generateQuote, messageRepository, @@ -312,7 +309,6 @@ export const InputBar = ({ pingDisabled={ping.isPingDisabled} messageContent={messageContent} isEditing={isEditing} - isMessageFormatButtonsFlagEnabled={isMessageFormatButtonsFlagEnabled} showMarkdownPreview={showMarkdownPreview} showGiphyButton={giphy.showGiphyButton} formatToolbar={formatToolbar} diff --git a/src/script/components/InputBar/InputBarControls/InputBarControls.tsx b/src/script/components/InputBar/InputBarControls/InputBarControls.tsx index 5fab850d652..9c8ed2a54a5 100644 --- a/src/script/components/InputBar/InputBarControls/InputBarControls.tsx +++ b/src/script/components/InputBar/InputBarControls/InputBarControls.tsx @@ -23,6 +23,7 @@ import {amplify} from 'amplify'; import {WebAppEvents} from '@wireapp/webapp-events'; +import {Config} from 'src/script/Config'; import {Conversation} from 'src/script/entity/Conversation'; import {ControlButtons} from './ControlButtons'; @@ -36,7 +37,6 @@ interface InputBarControlsProps { pingDisabled: boolean; messageContent: MessageContent; isEditing: boolean; - isMessageFormatButtonsFlagEnabled: boolean; showMarkdownPreview: boolean; showGiphyButton: boolean; formatToolbar: { @@ -61,7 +61,6 @@ export const InputBarControls = ({ pingDisabled, messageContent, isEditing, - isMessageFormatButtonsFlagEnabled, showMarkdownPreview, showGiphyButton, formatToolbar, @@ -75,6 +74,8 @@ export const InputBarControls = ({ }: InputBarControlsProps) => { const enableSending = messageContent.text.length > 0; + const isMessageFormatButtonsFlagEnabled = Config.getConfig().FEATURE.ENABLE_MESSAGE_FORMAT_BUTTONS; + useEffect(() => { amplify.subscribe(WebAppEvents.SHORTCUT.PING, onClickPing); diff --git a/src/script/components/InputBar/useGiphy/useGiphy.ts b/src/script/components/InputBar/useGiphy/useGiphy.ts index 2feae2dd318..07aa0d2d97e 100644 --- a/src/script/components/InputBar/useGiphy/useGiphy.ts +++ b/src/script/components/InputBar/useGiphy/useGiphy.ts @@ -23,13 +23,13 @@ import {amplify} from 'amplify'; import {WebAppEvents} from '@wireapp/webapp-events'; +import {Config} from 'src/script/Config'; import {MessageRepository, OutgoingQuote} from 'src/script/conversation/MessageRepository'; import {Conversation} from 'src/script/entity/Conversation'; interface UseGiphyProps { text: string; maxLength: number; - isMessageFormatButtonsFlagEnabled: boolean; openGiphy: (inputValue: string) => void; generateQuote: () => Promise; messageRepository: MessageRepository; @@ -40,13 +40,14 @@ interface UseGiphyProps { export const useGiphy = ({ text, maxLength, - isMessageFormatButtonsFlagEnabled, openGiphy, generateQuote, messageRepository, conversation, cancelMessageEditing, }: UseGiphyProps) => { + const isMessageFormatButtonsFlagEnabled = Config.getConfig().FEATURE.ENABLE_MESSAGE_FORMAT_BUTTONS; + const showGiphyButton = useMemo(() => { if (isMessageFormatButtonsFlagEnabled) { return text.length > 0; From 9e4c5722ebfab29197227810fc68f31934d548f7 Mon Sep 17 00:00:00 2001 From: Olaf Sulich Date: Mon, 17 Feb 2025 12:04:25 +0100 Subject: [PATCH 09/13] refactor(InputBar): extract draft state management logic --- .../components/CellDescription/CellDescription.tsx | 2 +- .../components/InputBar/InputBarEditor/InputBarEditor.tsx | 7 +++++-- .../DraftStateUtil.ts => common/draftState/draftState.ts} | 8 ++++---- .../components/InputBar/useDraftState/useDraftState.ts | 2 +- 4 files changed, 11 insertions(+), 8 deletions(-) rename src/script/components/InputBar/{util/DraftStateUtil.ts => common/draftState/draftState.ts} (91%) diff --git a/src/script/components/ConversationListCell/components/CellDescription/CellDescription.tsx b/src/script/components/ConversationListCell/components/CellDescription/CellDescription.tsx index 95638d9d69c..cc6243b076f 100644 --- a/src/script/components/ConversationListCell/components/CellDescription/CellDescription.tsx +++ b/src/script/components/ConversationListCell/components/CellDescription/CellDescription.tsx @@ -22,7 +22,7 @@ import {useMemo} from 'react'; import cx from 'classnames'; import * as Icon from 'Components/Icon'; -import {DraftState, generateConversationInputStorageKey} from 'Components/InputBar/util/DraftStateUtil'; +import {DraftState, generateConversationInputStorageKey} from 'Components/InputBar/common/draftState/draftState'; import {useLocalStorage} from 'Hooks/useLocalStorage'; import {iconStyle} from './CellDescription.style'; diff --git a/src/script/components/InputBar/InputBarEditor/InputBarEditor.tsx b/src/script/components/InputBar/InputBarEditor/InputBarEditor.tsx index fcb56e0922c..4a004d47a9e 100644 --- a/src/script/components/InputBar/InputBarEditor/InputBarEditor.tsx +++ b/src/script/components/InputBar/InputBarEditor/InputBarEditor.tsx @@ -17,16 +17,19 @@ * */ +import {MutableRefObject} from 'react'; + import {LexicalEditor} from 'lexical'; import {User} from 'src/script/entity/User'; import {RichTextEditor} from './RichTextEditor'; +import {DraftState} from '../common/draftState/draftState'; import {MessageContent} from '../common/messageContent/messageContent'; interface InputBarEditorProps { - editorRef: React.MutableRefObject; + editorRef: MutableRefObject; inputPlaceholder: string; hasLocalEphemeralTimer: boolean; showMarkdownPreview: boolean; @@ -43,7 +46,7 @@ interface InputBarEditorProps { onSend: () => void; getMentionCandidates: (search?: string | null) => User[]; saveDraftState: (editorState: string, plainMessage: string, replyId?: string) => void; - loadDraftState: () => Promise; + loadDraftState: () => Promise; replaceEmojis: boolean; children: React.ReactNode; } diff --git a/src/script/components/InputBar/util/DraftStateUtil.ts b/src/script/components/InputBar/common/draftState/draftState.ts similarity index 91% rename from src/script/components/InputBar/util/DraftStateUtil.ts rename to src/script/components/InputBar/common/draftState/draftState.ts index 33ae7c8907f..ff4eb943784 100644 --- a/src/script/components/InputBar/util/DraftStateUtil.ts +++ b/src/script/components/InputBar/common/draftState/draftState.ts @@ -17,10 +17,10 @@ * */ -import {MessageRepository} from '../../../conversation/MessageRepository'; -import {Conversation} from '../../../entity/Conversation'; -import {ContentMessage} from '../../../entity/message/ContentMessage'; -import {StorageKey, StorageRepository} from '../../../storage'; +import {MessageRepository} from 'src/script/conversation/MessageRepository'; +import {Conversation} from 'src/script/entity/Conversation'; +import {ContentMessage} from 'src/script/entity/message/ContentMessage'; +import {StorageKey, StorageRepository} from 'src/script/storage'; export interface DraftState { editorState: string | null; diff --git a/src/script/components/InputBar/useDraftState/useDraftState.ts b/src/script/components/InputBar/useDraftState/useDraftState.ts index 444f43b100d..dff4f901109 100644 --- a/src/script/components/InputBar/useDraftState/useDraftState.ts +++ b/src/script/components/InputBar/useDraftState/useDraftState.ts @@ -26,7 +26,7 @@ import {Conversation} from 'src/script/entity/Conversation'; import {StorageRepository} from 'src/script/storage'; import {sanitizeMarkdown} from 'Util/MarkdownUtil'; -import {loadDraftState, saveDraftState} from '../util/DraftStateUtil'; +import {loadDraftState, saveDraftState} from '../common/draftState/draftState'; interface UseDraftStateProps { conversation: Conversation; From 633c1172bbd8ee1abc1c87cd48e8d68386025227 Mon Sep 17 00:00:00 2001 From: Olaf Sulich Date: Mon, 17 Feb 2025 12:32:46 +0100 Subject: [PATCH 10/13] fix(InputBar): edit message draft --- src/script/components/InputBar/InputBar.tsx | 14 +---- .../InputBarEditor/InputBarEditor.tsx | 5 +- .../useDraftState/useDraftState.ts | 24 +++++-- .../useMessageHandling/useMessageHandling.ts | 62 ++++++++++++++----- 4 files changed, 72 insertions(+), 33 deletions(-) rename src/script/components/InputBar/{ => useMessageHandling}/useDraftState/useDraftState.ts (71%) diff --git a/src/script/components/InputBar/InputBar.tsx b/src/script/components/InputBar/InputBar.tsx index 22fbb3c6e26..eb5bbf09f9f 100644 --- a/src/script/components/InputBar/InputBar.tsx +++ b/src/script/components/InputBar/InputBar.tsx @@ -44,7 +44,6 @@ import {InputBarEditor} from './InputBarEditor/InputBarEditor'; import {PastedFileControls} from './PastedFileControls/PastedFileControls'; import {ReplyBar} from './ReplyBar/ReplyBar'; import {TypingIndicator} from './TypingIndicator'; -import {useDraftState} from './useDraftState/useDraftState'; import {useEmojiPicker} from './useEmojiPicker/useEmojiPicker'; import {useFileHandling} from './useFileHandling/useFileHandling'; import {useFormatToolbar} from './useFormatToolbar/useFormatToolbar'; @@ -190,13 +189,6 @@ export const InputBar = ({ WebAppEvents.PROPERTIES.UPDATE.INTERFACE.MARKDOWN_PREVIEW, ); - const draftState = useDraftState({ - conversation, - storageRepository, - messageRepository, - editorRef, - }); - const { editedMessage, replyMessageEntity, @@ -206,19 +198,18 @@ export const InputBar = ({ cancelMessageEditing, cancelMessageReply, editMessage, + draftState, generateQuote, } = useMessageHandling({ messageContent, conversation, conversationRepository, + storageRepository, eventRepository, messageRepository, editorRef, pastedFile: fileHandling.pastedFile, sendPastedFile: fileHandling.sendPastedFile, - onResetDraftState: draftState.reset, - onSaveDraft: (replyId?: string) => - draftState.save(JSON.stringify(editorRef.current?.getEditorState().toJSON()), messageContent.text, replyId), }); const ping = usePing({ @@ -275,6 +266,7 @@ export const InputBar = ({ {!isSelfUserRemoved && !fileHandling.pastedFile && ( ; + editedMessage: ContentMessage | undefined; inputPlaceholder: string; hasLocalEphemeralTimer: boolean; showMarkdownPreview: boolean; @@ -53,6 +55,7 @@ interface InputBarEditorProps { export const InputBarEditor = ({ editorRef, + editedMessage, inputPlaceholder, hasLocalEphemeralTimer, showMarkdownPreview, @@ -76,7 +79,7 @@ export const InputBarEditor = ({ editorRef.current = editor; onSetup(editor); }} - editedMessage={undefined} + editedMessage={editedMessage} onEscape={onEscape} onArrowUp={onArrowUp} getMentionCandidates={getMentionCandidates} diff --git a/src/script/components/InputBar/useDraftState/useDraftState.ts b/src/script/components/InputBar/useMessageHandling/useDraftState/useDraftState.ts similarity index 71% rename from src/script/components/InputBar/useDraftState/useDraftState.ts rename to src/script/components/InputBar/useMessageHandling/useDraftState/useDraftState.ts index dff4f901109..66c1da720d9 100644 --- a/src/script/components/InputBar/useDraftState/useDraftState.ts +++ b/src/script/components/InputBar/useMessageHandling/useDraftState/useDraftState.ts @@ -26,23 +26,36 @@ import {Conversation} from 'src/script/entity/Conversation'; import {StorageRepository} from 'src/script/storage'; import {sanitizeMarkdown} from 'Util/MarkdownUtil'; -import {loadDraftState, saveDraftState} from '../common/draftState/draftState'; +import {DraftState, loadDraftState, saveDraftState} from '../../common/draftState/draftState'; interface UseDraftStateProps { conversation: Conversation; storageRepository: StorageRepository; messageRepository: MessageRepository; editorRef: React.RefObject; + onLoad?: (draftState: DraftState) => void; + editedMessageId?: string; + replyMessageEntityId?: string; } -export const useDraftState = ({conversation, storageRepository, messageRepository, editorRef}: UseDraftStateProps) => { +export const useDraftState = ({ + conversation, + storageRepository, + messageRepository, + editorRef, + onLoad, + editedMessageId, + replyMessageEntityId, +}: UseDraftStateProps) => { const reset = useCallback(() => { editorRef.current?.dispatchCommand(CLEAR_EDITOR_COMMAND, undefined); }, [editorRef]); const load = useCallback(async () => { - return loadDraftState(conversation, storageRepository, messageRepository); - }, [conversation, messageRepository, storageRepository]); + const draftState = await loadDraftState(conversation, storageRepository, messageRepository); + onLoad?.(draftState); + return draftState; + }, [conversation, messageRepository, onLoad, storageRepository]); const save = async (editorState: string, text: string, replyId = '') => { void saveDraftState({ @@ -50,7 +63,8 @@ export const useDraftState = ({conversation, storageRepository, messageRepositor conversation, editorState, plainMessage: sanitizeMarkdown(text), - replyId, + replyId: replyId ?? replyMessageEntityId, + editedMessageId, }); }; diff --git a/src/script/components/InputBar/useMessageHandling/useMessageHandling.ts b/src/script/components/InputBar/useMessageHandling/useMessageHandling.ts index 982d0efb26f..5b7e5d12b2c 100644 --- a/src/script/components/InputBar/useMessageHandling/useMessageHandling.ts +++ b/src/script/components/InputBar/useMessageHandling/useMessageHandling.ts @@ -37,8 +37,11 @@ import {EventRepository} from 'src/script/event/EventRepository'; import {MentionEntity} from 'src/script/message/MentionEntity'; import {MessageHasher} from 'src/script/message/MessageHasher'; import {QuoteEntity} from 'src/script/message/QuoteEntity'; +import {StorageRepository} from 'src/script/storage'; import {t} from 'Util/LocalizerUtil'; +import {useDraftState} from './useDraftState/useDraftState'; + import {MessageContent} from '../common/messageContent/messageContent'; import {handleClickOutsideOfInputBar} from '../util/clickHandlers'; @@ -48,9 +51,8 @@ interface UseMessageHandlingProps { conversationRepository: ConversationRepository; eventRepository: EventRepository; messageRepository: MessageRepository; + storageRepository: StorageRepository; editorRef: React.RefObject; - onResetDraftState: () => void; - onSaveDraft: (replyId?: string) => void; pastedFile: File | null; sendPastedFile: () => void; } @@ -61,9 +63,8 @@ export const useMessageHandling = ({ conversationRepository, eventRepository, messageRepository, + storageRepository, editorRef, - onResetDraftState, - onSaveDraft, pastedFile, sendPastedFile, }: UseMessageHandlingProps) => { @@ -73,6 +74,33 @@ export const useMessageHandling = ({ const isEditing = !!editedMessage; const isReplying = !!replyMessageEntity; + const draftState = useDraftState({ + conversation, + storageRepository, + messageRepository, + editorRef, + editedMessageId: editedMessage?.id, + replyMessageEntityId: replyMessageEntity?.id, + onLoad: draftState => { + const reply = draftState.messageReply; + if (reply?.isReplyable()) { + setReplyMessageEntity(reply); + } + + const editedMessage = draftState.editedMessage; + if (editedMessage) { + setEditedMessage(editedMessage); + } + }, + }); + + const handleSaveDraft = useCallback( + async (replyId?: string) => { + await draftState.save(JSON.stringify(editorRef.current?.getEditorState().toJSON()), messageContent.text, replyId); + }, + [draftState, editorRef, messageContent.text], + ); + const generateQuote = useCallback(async (): Promise => { return !replyMessageEntity ? Promise.resolve(undefined) @@ -91,13 +119,13 @@ export const useMessageHandling = ({ const cancelMessageReply = useCallback( (resetDraft = true) => { setReplyMessageEntity(null); - onSaveDraft(); + void handleSaveDraft(); if (resetDraft) { - onResetDraftState(); + draftState.reset(); } }, - [onResetDraftState, onSaveDraft], + [draftState, handleSaveDraft], ); const cancelMessageEditing = useCallback( @@ -106,10 +134,10 @@ export const useMessageHandling = ({ setReplyMessageEntity(null); if (resetDraft) { - onResetDraftState(); + draftState.reset(); } }, - [onResetDraftState], + [draftState], ); const sendMessageEdit = useCallback( @@ -179,16 +207,17 @@ export const useMessageHandling = ({ } editorRef.current?.focus(); - onResetDraftState(); + draftState.reset(); }, [ + pastedFile, + messageContent.text, + messageContent.mentions, editedMessage, editorRef, - onResetDraftState, - pastedFile, - sendMessageEdit, + draftState, sendPastedFile, + sendMessageEdit, sendTextMessage, - messageContent, ]); const handleSendMessage = useCallback(async () => { @@ -242,12 +271,12 @@ export const useMessageHandling = ({ cancelMessageReply(false); cancelMessageEditing(!!editedMessage); setReplyMessageEntity(messageEntity); - onSaveDraft(messageEntity.id); + void handleSaveDraft(messageEntity.id); editorRef.current?.focus(); } }, - [cancelMessageEditing, cancelMessageReply, editedMessage, editorRef, onSaveDraft, replyMessageEntity], + [cancelMessageEditing, cancelMessageReply, editedMessage, editorRef, handleSaveDraft, replyMessageEntity], ); const handleRepliedMessageDeleted = useCallback( @@ -314,6 +343,7 @@ export const useMessageHandling = ({ }, [replyMessage, conversation]); return { + draftState, editedMessage, replyMessageEntity, isEditing, From ff2cca59e81f99f96d244b8b0200a7a132c487e8 Mon Sep 17 00:00:00 2001 From: Olaf Sulich Date: Mon, 17 Feb 2025 13:25:39 +0100 Subject: [PATCH 11/13] refactor(useMessageHandling): split logic into hooks --- src/script/components/InputBar/InputBar.tsx | 29 +- .../components/InputBar/useGiphy/useGiphy.ts | 10 +- .../useMessageEditing/useMessageEditing.ts | 54 ++++ .../useMessageHandling/useMessageHandling.ts | 277 +++++------------- .../useMessageReply/useMessageReply.ts | 70 +++++ .../useMessageSend/useMessageSend.ts | 212 ++++++++++++++ .../components/InputBar/usePing/usePing.ts | 14 +- 7 files changed, 435 insertions(+), 231 deletions(-) create mode 100644 src/script/components/InputBar/useMessageHandling/useMessageEditing/useMessageEditing.ts create mode 100644 src/script/components/InputBar/useMessageHandling/useMessageReply/useMessageReply.ts create mode 100644 src/script/components/InputBar/useMessageHandling/useMessageSend/useMessageSend.ts diff --git a/src/script/components/InputBar/InputBar.tsx b/src/script/components/InputBar/InputBar.tsx index eb5bbf09f9f..fb9c9ea8976 100644 --- a/src/script/components/InputBar/InputBar.tsx +++ b/src/script/components/InputBar/InputBar.tsx @@ -194,8 +194,9 @@ export const InputBar = ({ replyMessageEntity, isEditing, isReplying, - handleSendMessage, - cancelMessageEditing, + sendMessage, + cancelSending, + cancelMesssageEditing, cancelMessageReply, editMessage, draftState, @@ -216,8 +217,6 @@ export const InputBar = ({ conversation, messageRepository, is1to1, - maxUsersWithoutAlert: CONFIG.FEATURE.MAX_USERS_TO_PING_WITHOUT_ALERT, - enablePingConfirmation: CONFIG.FEATURE.ENABLE_PING_CONFIRMATION, }); const giphy = useGiphy({ @@ -227,7 +226,7 @@ export const InputBar = ({ generateQuote, messageRepository, conversation, - cancelMessageEditing, + cancelMesssageEditing, }); const showAvatar = !!messageContent.text.length; @@ -274,13 +273,7 @@ export const InputBar = ({ onSetup={editor => { editorRef.current = editor; }} - onEscape={() => { - if (editedMessage) { - cancelMessageEditing(true); - } else if (replyMessageEntity) { - cancelMessageReply(); - } - }} + onEscape={cancelSending} onArrowUp={() => { if (messageContent.text.length === 0) { editMessage(conversation.getLastEditableMessage()); @@ -289,7 +282,7 @@ export const InputBar = ({ onShiftTab={onShiftTab} onBlur={() => isTypingRef.current && conversationRepository.sendTypingStop(conversation)} onUpdate={setMessageContent} - onSend={handleSendMessage} + onSend={sendMessage} getMentionCandidates={getMentionCandidates} saveDraftState={draftState.save} loadDraftState={draftState.load} @@ -305,18 +298,12 @@ export const InputBar = ({ showGiphyButton={giphy.showGiphyButton} formatToolbar={formatToolbar} emojiPicker={emojiPicker} - onEscape={() => { - if (editedMessage) { - cancelMessageEditing(true); - } else if (replyMessageEntity) { - cancelMessageReply(); - } - }} + onEscape={cancelSending} onClickPing={ping.handlePing} onGifClick={giphy.handleGifClick} onSelectFiles={uploadFiles} onSelectImages={uploadImages} - onSend={handleSendMessage} + onSend={sendMessage} /> )} diff --git a/src/script/components/InputBar/useGiphy/useGiphy.ts b/src/script/components/InputBar/useGiphy/useGiphy.ts index 07aa0d2d97e..ed41909f3ab 100644 --- a/src/script/components/InputBar/useGiphy/useGiphy.ts +++ b/src/script/components/InputBar/useGiphy/useGiphy.ts @@ -34,7 +34,7 @@ interface UseGiphyProps { generateQuote: () => Promise; messageRepository: MessageRepository; conversation: Conversation; - cancelMessageEditing: (resetDraft?: boolean) => void; + cancelMesssageEditing: () => void; } export const useGiphy = ({ @@ -44,7 +44,7 @@ export const useGiphy = ({ generateQuote, messageRepository, conversation, - cancelMessageEditing, + cancelMesssageEditing, }: UseGiphyProps) => { const isMessageFormatButtonsFlagEnabled = Config.getConfig().FEATURE.ENABLE_MESSAGE_FORMAT_BUTTONS; @@ -61,10 +61,10 @@ export const useGiphy = ({ (gifUrl: string, tag: string): void => { void generateQuote().then(quoteEntity => { void messageRepository.sendGif(conversation, gifUrl, tag, quoteEntity); - cancelMessageEditing(true); + cancelMesssageEditing(); }); }, - [cancelMessageEditing, conversation, generateQuote, messageRepository], + [cancelMesssageEditing, conversation, generateQuote, messageRepository], ); useEffect(() => { @@ -73,7 +73,7 @@ export const useGiphy = ({ return () => { amplify.unsubscribeAll(WebAppEvents.EXTENSIONS.GIPHY.SEND); }; - }, [sendGiphy, cancelMessageEditing]); + }, [sendGiphy, cancelMesssageEditing]); return { showGiphyButton, diff --git a/src/script/components/InputBar/useMessageHandling/useMessageEditing/useMessageEditing.ts b/src/script/components/InputBar/useMessageHandling/useMessageEditing/useMessageEditing.ts new file mode 100644 index 00000000000..cd10a0aa3bc --- /dev/null +++ b/src/script/components/InputBar/useMessageHandling/useMessageEditing/useMessageEditing.ts @@ -0,0 +1,54 @@ +/* + * Wire + * Copyright (C) 2025 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import {useCallback, useEffect, useState} from 'react'; + +import {amplify} from 'amplify'; + +import {WebAppEvents} from '@wireapp/webapp-events'; + +import {ContentMessage} from 'src/script/entity/message/ContentMessage'; + +export const useMessageEditing = () => { + const [editedMessage, setEditedMessage] = useState(); + + const cancelMessageEditing = useCallback((callback?: () => void) => { + setEditedMessage(undefined); + callback?.(); + }, []); + + const editMessage = useCallback((messageEntity: ContentMessage) => { + setEditedMessage(messageEntity); + }, []); + + useEffect(() => { + amplify.subscribe(WebAppEvents.CONVERSATION.MESSAGE.EDIT, editMessage); + + return () => { + amplify.unsubscribeAll(WebAppEvents.CONVERSATION.MESSAGE.EDIT); + }; + }, [editMessage]); + + return { + editedMessage, + editMessage, + isEditing: !!editedMessage, + cancelMessageEditing, + }; +}; diff --git a/src/script/components/InputBar/useMessageHandling/useMessageHandling.ts b/src/script/components/InputBar/useMessageHandling/useMessageHandling.ts index 5b7e5d12b2c..0b2cb8080b7 100644 --- a/src/script/components/InputBar/useMessageHandling/useMessageHandling.ts +++ b/src/script/components/InputBar/useMessageHandling/useMessageHandling.ts @@ -17,30 +17,21 @@ * */ -import {useCallback, useEffect, useState} from 'react'; +import {useCallback, useEffect} from 'react'; -import {amplify} from 'amplify'; import {LexicalEditor} from 'lexical'; -import {WebAppEvents} from '@wireapp/webapp-events'; - -import {PrimaryModal} from 'Components/Modals/PrimaryModal'; -import {showWarningModal} from 'Components/Modals/utils/showWarningModal'; -import {Config} from 'src/script/Config'; import {ConversationRepository} from 'src/script/conversation/ConversationRepository'; -import {ConversationVerificationState} from 'src/script/conversation/ConversationVerificationState'; -import {MessageRepository, OutgoingQuote} from 'src/script/conversation/MessageRepository'; +import {MessageRepository} from 'src/script/conversation/MessageRepository'; import {Conversation} from 'src/script/entity/Conversation'; import {ContentMessage} from 'src/script/entity/message/ContentMessage'; -import {ConversationError} from 'src/script/error/ConversationError'; import {EventRepository} from 'src/script/event/EventRepository'; -import {MentionEntity} from 'src/script/message/MentionEntity'; -import {MessageHasher} from 'src/script/message/MessageHasher'; -import {QuoteEntity} from 'src/script/message/QuoteEntity'; import {StorageRepository} from 'src/script/storage'; -import {t} from 'Util/LocalizerUtil'; import {useDraftState} from './useDraftState/useDraftState'; +import {useMessageEditing} from './useMessageEditing/useMessageEditing'; +import {useMessageReply} from './useMessageReply/useMessageReply'; +import {useMessageSend} from './useMessageSend/useMessageSend'; import {MessageContent} from '../common/messageContent/messageContent'; import {handleClickOutsideOfInputBar} from '../util/clickHandlers'; @@ -68,11 +59,9 @@ export const useMessageHandling = ({ pastedFile, sendPastedFile, }: UseMessageHandlingProps) => { - const [editedMessage, setEditedMessage] = useState(); - const [replyMessageEntity, setReplyMessageEntity] = useState(null); + const {isEditing, editedMessage, editMessage: editMessageCallback, cancelMessageEditing} = useMessageEditing(); - const isEditing = !!editedMessage; - const isReplying = !!replyMessageEntity; + const {isReplying, replyMessage: replyMessageCallback, replyMessageEntity} = useMessageReply(); const draftState = useDraftState({ conversation, @@ -83,13 +72,14 @@ export const useMessageHandling = ({ replyMessageEntityId: replyMessageEntity?.id, onLoad: draftState => { const reply = draftState.messageReply; + if (reply?.isReplyable()) { - setReplyMessageEntity(reply); + replyMessageCallback(reply); } const editedMessage = draftState.editedMessage; if (editedMessage) { - setEditedMessage(editedMessage); + editMessageCallback(editedMessage); } }, }); @@ -101,215 +91,104 @@ export const useMessageHandling = ({ [draftState, editorRef, messageContent.text], ); - const generateQuote = useCallback(async (): Promise => { - return !replyMessageEntity - ? Promise.resolve(undefined) - : eventRepository.eventService - .loadEvent(replyMessageEntity.conversation_id, replyMessageEntity.id) - .then(MessageHasher.hashEvent) - .then((messageHash: ArrayBuffer) => { - return new QuoteEntity({ - hash: messageHash, - messageId: replyMessageEntity.id, - userId: replyMessageEntity.from, - }) as OutgoingQuote; - }); - }, [eventRepository.eventService, replyMessageEntity]); - const cancelMessageReply = useCallback( (resetDraft = true) => { - setReplyMessageEntity(null); + replyMessageCallback(null); void handleSaveDraft(); if (resetDraft) { draftState.reset(); } }, - [draftState, handleSaveDraft], - ); - - const cancelMessageEditing = useCallback( - (resetDraft = true) => { - setEditedMessage(undefined); - setReplyMessageEntity(null); - - if (resetDraft) { - draftState.reset(); - } - }, - [draftState], - ); - - const sendMessageEdit = useCallback( - (messageText: string, mentions: MentionEntity[]): void | Promise => { - const mentionEntities = mentions.slice(0); - cancelMessageEditing(true); - - if (!messageText.length && editedMessage) { - return messageRepository.deleteMessageForEveryone(conversation, editedMessage); - } - - if (editedMessage) { - messageRepository.sendMessageEdit(conversation, messageText, editedMessage, mentionEntities).catch(error => { - if (error.type !== ConversationError.TYPE.NO_MESSAGE_CHANGES) { - throw error; - } - }); - - cancelMessageReply(); - } - }, - [cancelMessageEditing, cancelMessageReply, conversation, editedMessage, messageRepository], - ); - - const sendTextMessage = useCallback( - (messageText: string, mentions: MentionEntity[]) => { - if (messageText.length) { - const mentionEntities = mentions.slice(0); - - void generateQuote().then(quoteEntity => { - void messageRepository.sendTextWithLinkPreview(conversation, messageText, mentionEntities, quoteEntity); - cancelMessageReply(); - }); - } - }, - [cancelMessageReply, conversation, generateQuote, messageRepository], + [draftState, handleSaveDraft, replyMessageCallback], ); - const sendMessage = useCallback((): void => { - if (pastedFile) { - return void sendPastedFile(); - } - - const text = messageContent.text; - const mentions = messageContent.mentions ?? []; - - const messageTrimmedStart = text.trimStart(); - const messageText = messageTrimmedStart.trimEnd(); - - const config = Config.getConfig(); - - const isMessageTextTooLong = text.length > config.MAXIMUM_MESSAGE_LENGTH; - - if (isMessageTextTooLong) { - showWarningModal( - t('modalConversationMessageTooLongHeadline'), - t('modalConversationMessageTooLongMessage', {number: config.MAXIMUM_MESSAGE_LENGTH}), - ); - - return; - } - - if (editedMessage) { - void sendMessageEdit(messageText, mentions); - } else { - sendTextMessage(messageText, mentions); - } + const cancelMesssageEditingWithDraftReset = useCallback(() => { + cancelMessageEditing(() => { + replyMessageCallback(null); + draftState.reset(); + }); + }, [cancelMessageEditing, draftState, replyMessageCallback]); - editorRef.current?.focus(); - draftState.reset(); - }, [ - pastedFile, - messageContent.text, - messageContent.mentions, + const {sendMessage, generateQuote} = useMessageSend({ + replyMessageEntity, + eventRepository, + messageRepository, + conversation, + conversationRepository, + draftState, + cancelMessageEditing, + cancelMessageReply, editedMessage, + replyMessageCallback, editorRef, - draftState, + pastedFile, sendPastedFile, - sendMessageEdit, - sendTextMessage, - ]); - - const handleSendMessage = useCallback(async () => { - await conversationRepository.refreshMLSConversationVerificationState(conversation); - const isE2EIDegraded = conversation.mlsVerificationState() === ConversationVerificationState.DEGRADED; - - if (isE2EIDegraded) { - PrimaryModal.show(PrimaryModal.type.CONFIRM, { - secondaryAction: { - action: () => { - conversation.mlsVerificationState(ConversationVerificationState.UNVERIFIED); - sendMessage(); - }, - text: t('conversation.E2EISendAnyway'), - }, - primaryAction: { - action: () => {}, - text: t('conversation.E2EICancel'), - }, - text: { - message: t('conversation.E2EIDegradedNewMessage'), - title: t('conversation.E2EIConversationNoLongerVerified'), - }, - }); - } else { - sendMessage(); - } - }, [conversation, conversationRepository, sendMessage]); + messageContent, + }); const editMessage = useCallback( (messageEntity?: ContentMessage) => { if (messageEntity?.isEditable() && messageEntity !== editedMessage) { cancelMessageReply(); - cancelMessageEditing(true); - setEditedMessage(messageEntity); + cancelMesssageEditingWithDraftReset(); + editMessageCallback(messageEntity); const quote = messageEntity.quote(); if (quote && conversation) { void messageRepository .getMessageInConversationById(conversation, quote.messageId) - .then(quotedMessage => setReplyMessageEntity(quotedMessage)); + .then(quotedMessage => replyMessageCallback(quotedMessage)); } } }, - [cancelMessageEditing, cancelMessageReply, conversation, editedMessage, messageRepository], + [ + cancelMessageReply, + cancelMesssageEditingWithDraftReset, + conversation, + editMessageCallback, + editedMessage, + messageRepository, + replyMessageCallback, + ], ); const replyMessage = useCallback( (messageEntity: ContentMessage) => { if (messageEntity?.isReplyable() && messageEntity !== replyMessageEntity) { cancelMessageReply(false); - cancelMessageEditing(!!editedMessage); - setReplyMessageEntity(messageEntity); + cancelMessageEditing(() => { + replyMessageCallback(null); + if (isEditing) { + draftState.reset(); + } + }); + + replyMessageCallback(messageEntity); void handleSaveDraft(messageEntity.id); editorRef.current?.focus(); } }, - [cancelMessageEditing, cancelMessageReply, editedMessage, editorRef, handleSaveDraft, replyMessageEntity], + [ + cancelMessageEditing, + cancelMessageReply, + draftState, + editorRef, + handleSaveDraft, + isEditing, + replyMessageCallback, + replyMessageEntity, + ], ); - const handleRepliedMessageDeleted = useCallback( - (messageId: string) => { - if (replyMessageEntity?.id === messageId) { - setReplyMessageEntity(null); - } - }, - [replyMessageEntity], - ); - - const handleRepliedMessageUpdated = useCallback( - (originalMessageId: string, messageEntity: ContentMessage) => { - if (replyMessageEntity?.id === originalMessageId) { - setReplyMessageEntity(messageEntity); - } - }, - [replyMessageEntity], - ); - - useEffect(() => { - amplify.subscribe(WebAppEvents.CONVERSATION.MESSAGE.REPLY, replyMessage); - amplify.subscribe(WebAppEvents.CONVERSATION.MESSAGE.REMOVED, handleRepliedMessageDeleted); - amplify.subscribe(WebAppEvents.CONVERSATION.MESSAGE.UPDATED, handleRepliedMessageUpdated); - amplify.subscribe(WebAppEvents.CONVERSATION.MESSAGE.EDIT, editMessage); - - return () => { - amplify.unsubscribeAll(WebAppEvents.CONVERSATION.MESSAGE.REPLY); - amplify.unsubscribeAll(WebAppEvents.CONVERSATION.MESSAGE.REMOVED); - amplify.unsubscribeAll(WebAppEvents.CONVERSATION.MESSAGE.UPDATED); - amplify.unsubscribeAll(WebAppEvents.CONVERSATION.MESSAGE.EDIT); - }; - }, [handleRepliedMessageDeleted, replyMessage, editMessage, handleRepliedMessageUpdated]); + const cancelSending = useCallback(() => { + if (editedMessage) { + cancelMesssageEditingWithDraftReset(); + } else if (replyMessageEntity) { + cancelMessageReply(); + } + }, [editedMessage, replyMessageEntity, cancelMesssageEditingWithDraftReset, cancelMessageReply]); useEffect(() => { const onWindowClick = (event: Event): void => @@ -317,7 +196,7 @@ export const useMessageHandling = ({ // We want to add a timeout in case the click happens because the user switched conversation and the component is unmounting. // In this case we want to keep the edited message for this conversation setTimeout(() => { - cancelMessageEditing(true); + cancelMesssageEditingWithDraftReset(); cancelMessageReply(); }); }); @@ -330,17 +209,22 @@ export const useMessageHandling = ({ } return () => undefined; - }, [cancelMessageEditing, cancelMessageReply, isEditing]); + }, [ + cancelMessageEditing, + cancelMessageReply, + cancelMesssageEditingWithDraftReset, + draftState, + isEditing, + replyMessageCallback, + ]); useEffect(() => { - amplify.subscribe(WebAppEvents.CONVERSATION.MESSAGE.REPLY, replyMessage); conversation.isTextInputReady(true); return () => { - amplify.unsubscribeAll(WebAppEvents.CONVERSATION.MESSAGE.REPLY); conversation.isTextInputReady(false); }; - }, [replyMessage, conversation]); + }, [conversation]); return { draftState, @@ -348,10 +232,11 @@ export const useMessageHandling = ({ replyMessageEntity, isEditing, isReplying, - handleSendMessage2: sendMessage, - handleSendMessage, + sendMessage, + cancelSending, cancelMessageEditing, cancelMessageReply, + cancelMesssageEditing: cancelMesssageEditingWithDraftReset, editMessage, replyMessage, generateQuote, diff --git a/src/script/components/InputBar/useMessageHandling/useMessageReply/useMessageReply.ts b/src/script/components/InputBar/useMessageHandling/useMessageReply/useMessageReply.ts new file mode 100644 index 00000000000..1430b409145 --- /dev/null +++ b/src/script/components/InputBar/useMessageHandling/useMessageReply/useMessageReply.ts @@ -0,0 +1,70 @@ +/* + * Wire + * Copyright (C) 2025 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import {useCallback, useEffect, useState} from 'react'; + +import {amplify} from 'amplify'; + +import {WebAppEvents} from '@wireapp/webapp-events'; + +import {ContentMessage} from 'src/script/entity/message/ContentMessage'; + +export const useMessageReply = () => { + const [replyMessageEntity, setReplyMessageEntity] = useState(null); + + const replyMessage = useCallback((messageEntity: ContentMessage | null) => { + setReplyMessageEntity(messageEntity); + }, []); + + const handleRepliedMessageDeleted = useCallback( + (messageId: string) => { + if (replyMessageEntity?.id === messageId) { + replyMessage(null); + } + }, + [replyMessageEntity?.id, replyMessage], + ); + + const handleRepliedMessageUpdated = useCallback( + (originalMessageId: string, messageEntity: ContentMessage) => { + if (replyMessageEntity?.id === originalMessageId) { + replyMessage(messageEntity); + } + }, + [replyMessageEntity, replyMessage], + ); + + useEffect(() => { + amplify.subscribe(WebAppEvents.CONVERSATION.MESSAGE.REPLY, replyMessage); + amplify.subscribe(WebAppEvents.CONVERSATION.MESSAGE.REMOVED, handleRepliedMessageDeleted); + amplify.subscribe(WebAppEvents.CONVERSATION.MESSAGE.UPDATED, handleRepliedMessageUpdated); + + return () => { + amplify.unsubscribeAll(WebAppEvents.CONVERSATION.MESSAGE.REPLY); + amplify.unsubscribeAll(WebAppEvents.CONVERSATION.MESSAGE.REMOVED); + amplify.unsubscribeAll(WebAppEvents.CONVERSATION.MESSAGE.UPDATED); + }; + }, [handleRepliedMessageDeleted, handleRepliedMessageUpdated, replyMessage]); + + return { + isReplying: !!replyMessageEntity, + replyMessageEntity, + replyMessage, + }; +}; diff --git a/src/script/components/InputBar/useMessageHandling/useMessageSend/useMessageSend.ts b/src/script/components/InputBar/useMessageHandling/useMessageSend/useMessageSend.ts new file mode 100644 index 00000000000..3499016ee25 --- /dev/null +++ b/src/script/components/InputBar/useMessageHandling/useMessageSend/useMessageSend.ts @@ -0,0 +1,212 @@ +/* + * Wire + * Copyright (C) 2025 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import {useCallback} from 'react'; + +import {LexicalEditor} from 'lexical'; + +import {MessageContent} from 'Components/InputBar/common/messageContent/messageContent'; +import {PrimaryModal} from 'Components/Modals/PrimaryModal'; +import {showWarningModal} from 'Components/Modals/utils/showWarningModal'; +import {Config} from 'src/script/Config'; +import {ConversationRepository} from 'src/script/conversation/ConversationRepository'; +import {ConversationVerificationState} from 'src/script/conversation/ConversationVerificationState'; +import {MessageRepository, OutgoingQuote} from 'src/script/conversation/MessageRepository'; +import {Conversation} from 'src/script/entity/Conversation'; +import {ContentMessage} from 'src/script/entity/message/ContentMessage'; +import {ConversationError} from 'src/script/error/ConversationError'; +import {EventRepository} from 'src/script/event/EventRepository'; +import {MentionEntity} from 'src/script/message/MentionEntity'; +import {MessageHasher} from 'src/script/message/MessageHasher'; +import {QuoteEntity} from 'src/script/message/QuoteEntity'; +import {t} from 'Util/LocalizerUtil'; + +interface UseMessageSendProps { + replyMessageEntity: ContentMessage | null; + eventRepository: EventRepository; + messageRepository: MessageRepository; + conversation: Conversation; + conversationRepository: ConversationRepository; + draftState: { + reset: () => void; + }; + cancelMessageEditing: (callback?: () => void) => void; + cancelMessageReply: () => void; + editedMessage: ContentMessage | undefined; + replyMessageCallback: (messageEntity: ContentMessage | null) => void; + editorRef: React.RefObject; + pastedFile: File | null; + sendPastedFile: () => void; + messageContent: MessageContent; +} + +export const useMessageSend = ({ + replyMessageEntity, + eventRepository, + messageRepository, + conversation, + conversationRepository, + draftState, + cancelMessageEditing, + cancelMessageReply, + editedMessage, + replyMessageCallback, + editorRef, + pastedFile, + sendPastedFile, + messageContent, +}: UseMessageSendProps) => { + const generateQuote = useCallback(async (): Promise => { + return !replyMessageEntity + ? Promise.resolve(undefined) + : eventRepository.eventService + .loadEvent(replyMessageEntity.conversation_id, replyMessageEntity.id) + .then(MessageHasher.hashEvent) + .then((messageHash: ArrayBuffer) => { + return new QuoteEntity({ + hash: messageHash, + messageId: replyMessageEntity.id, + userId: replyMessageEntity.from, + }) as OutgoingQuote; + }); + }, [eventRepository.eventService, replyMessageEntity]); + + const sendMessageEdit = useCallback( + (messageText: string, mentions: MentionEntity[]): void | Promise => { + const mentionEntities = mentions.slice(0); + cancelMessageEditing(() => { + replyMessageCallback(null); + draftState.reset(); + }); + + if (!messageText.length && editedMessage) { + return messageRepository.deleteMessageForEveryone(conversation, editedMessage); + } + + if (editedMessage) { + messageRepository.sendMessageEdit(conversation, messageText, editedMessage, mentionEntities).catch(error => { + if (error.type !== ConversationError.TYPE.NO_MESSAGE_CHANGES) { + throw error; + } + }); + + cancelMessageReply(); + } + }, + [ + cancelMessageEditing, + cancelMessageReply, + conversation, + draftState, + editedMessage, + messageRepository, + replyMessageCallback, + ], + ); + + const sendTextMessage = useCallback( + (messageText: string, mentions: MentionEntity[]) => { + if (messageText.length) { + const mentionEntities = mentions.slice(0); + + void generateQuote().then(quoteEntity => { + void messageRepository.sendTextWithLinkPreview(conversation, messageText, mentionEntities, quoteEntity); + cancelMessageReply(); + }); + } + }, + [cancelMessageReply, conversation, generateQuote, messageRepository], + ); + + const sendMessage = useCallback((): void => { + if (pastedFile) { + return void sendPastedFile(); + } + + const text = messageContent.text; + const mentions = messageContent.mentions ?? []; + + const messageTrimmedStart = text.trimStart(); + const messageText = messageTrimmedStart.trimEnd(); + + const config = Config.getConfig(); + + const isMessageTextTooLong = text.length > config.MAXIMUM_MESSAGE_LENGTH; + + if (isMessageTextTooLong) { + showWarningModal( + t('modalConversationMessageTooLongHeadline'), + t('modalConversationMessageTooLongMessage', {number: config.MAXIMUM_MESSAGE_LENGTH}), + ); + + return; + } + + if (editedMessage) { + void sendMessageEdit(messageText, mentions); + } else { + sendTextMessage(messageText, mentions); + } + + editorRef.current?.focus(); + draftState.reset(); + }, [ + pastedFile, + messageContent.text, + messageContent.mentions, + editedMessage, + editorRef, + draftState, + sendPastedFile, + sendMessageEdit, + sendTextMessage, + ]); + + const handleSendMessage = useCallback(async () => { + await conversationRepository.refreshMLSConversationVerificationState(conversation); + const isE2EIDegraded = conversation.mlsVerificationState() === ConversationVerificationState.DEGRADED; + + if (isE2EIDegraded) { + PrimaryModal.show(PrimaryModal.type.CONFIRM, { + secondaryAction: { + action: () => { + conversation.mlsVerificationState(ConversationVerificationState.UNVERIFIED); + sendMessage(); + }, + text: t('conversation.E2EISendAnyway'), + }, + primaryAction: { + action: () => {}, + text: t('conversation.E2EICancel'), + }, + text: { + message: t('conversation.E2EIDegradedNewMessage'), + title: t('conversation.E2EIConversationNoLongerVerified'), + }, + }); + } else { + sendMessage(); + } + }, [conversation, conversationRepository, sendMessage]); + + return { + sendMessage: handleSendMessage, + generateQuote, + }; +}; diff --git a/src/script/components/InputBar/usePing/usePing.ts b/src/script/components/InputBar/usePing/usePing.ts index 2d0e06bc337..7fee180f3b4 100644 --- a/src/script/components/InputBar/usePing/usePing.ts +++ b/src/script/components/InputBar/usePing/usePing.ts @@ -20,6 +20,7 @@ import {useState} from 'react'; import {PrimaryModal} from 'Components/Modals/PrimaryModal'; +import {Config} from 'src/script/Config'; import {MessageRepository} from 'src/script/conversation/MessageRepository'; import {Conversation} from 'src/script/entity/Conversation'; import {t} from 'Util/LocalizerUtil'; @@ -29,19 +30,14 @@ interface UsePingProps { conversation: Conversation; messageRepository: MessageRepository; is1to1: boolean; - maxUsersWithoutAlert: number; - enablePingConfirmation: boolean; } -export const usePing = ({ - conversation, - messageRepository, - is1to1, - maxUsersWithoutAlert, - enablePingConfirmation, -}: UsePingProps) => { +export const usePing = ({conversation, messageRepository, is1to1}: UsePingProps) => { const [isPingDisabled, setIsPingDisabled] = useState(false); + const maxUsersWithoutAlert = Config.getConfig().FEATURE.MAX_USERS_TO_PING_WITHOUT_ALERT; + const enablePingConfirmation = Config.getConfig().FEATURE.ENABLE_PING_CONFIRMATION; + const pingConversation = () => { setIsPingDisabled(true); void messageRepository.sendPing(conversation).then(() => { From cc777b15d7a0237a32da70dcdf309de1f829fbe3 Mon Sep 17 00:00:00 2001 From: Olaf Sulich Date: Mon, 17 Feb 2025 13:37:27 +0100 Subject: [PATCH 12/13] refaactor(useMessageHandling): split login into useOutsideInputClick --- .../useMessageHandling/useMessageHandling.ts | 40 ++++++--------- .../useMessageReply/useMessageReply.ts | 2 - .../useOutsideInputClick.ts | 49 +++++++++++++++++++ 3 files changed, 64 insertions(+), 27 deletions(-) create mode 100644 src/script/components/InputBar/useMessageHandling/useOutsideInputClick/useOutsideInputClick.ts diff --git a/src/script/components/InputBar/useMessageHandling/useMessageHandling.ts b/src/script/components/InputBar/useMessageHandling/useMessageHandling.ts index 0b2cb8080b7..5cb24f45af5 100644 --- a/src/script/components/InputBar/useMessageHandling/useMessageHandling.ts +++ b/src/script/components/InputBar/useMessageHandling/useMessageHandling.ts @@ -19,8 +19,11 @@ import {useCallback, useEffect} from 'react'; +import {amplify} from 'amplify'; import {LexicalEditor} from 'lexical'; +import {WebAppEvents} from '@wireapp/webapp-events'; + import {ConversationRepository} from 'src/script/conversation/ConversationRepository'; import {MessageRepository} from 'src/script/conversation/MessageRepository'; import {Conversation} from 'src/script/entity/Conversation'; @@ -32,9 +35,9 @@ import {useDraftState} from './useDraftState/useDraftState'; import {useMessageEditing} from './useMessageEditing/useMessageEditing'; import {useMessageReply} from './useMessageReply/useMessageReply'; import {useMessageSend} from './useMessageSend/useMessageSend'; +import {useOutsideInputClick} from './useOutsideInputClick/useOutsideInputClick'; import {MessageContent} from '../common/messageContent/messageContent'; -import {handleClickOutsideOfInputBar} from '../util/clickHandlers'; interface UseMessageHandlingProps { messageContent: MessageContent; @@ -158,7 +161,6 @@ export const useMessageHandling = ({ if (messageEntity?.isReplyable() && messageEntity !== replyMessageEntity) { cancelMessageReply(false); cancelMessageEditing(() => { - replyMessageCallback(null); if (isEditing) { draftState.reset(); } @@ -191,32 +193,20 @@ export const useMessageHandling = ({ }, [editedMessage, replyMessageEntity, cancelMesssageEditingWithDraftReset, cancelMessageReply]); useEffect(() => { - const onWindowClick = (event: Event): void => - handleClickOutsideOfInputBar(event, () => { - // We want to add a timeout in case the click happens because the user switched conversation and the component is unmounting. - // In this case we want to keep the edited message for this conversation - setTimeout(() => { - cancelMesssageEditingWithDraftReset(); - cancelMessageReply(); - }); - }); - if (isEditing) { - window.addEventListener('click', onWindowClick); + amplify.subscribe(WebAppEvents.CONVERSATION.MESSAGE.REPLY, replyMessage); - return () => { - window.removeEventListener('click', onWindowClick); - }; - } + return () => { + amplify.unsubscribeAll(WebAppEvents.CONVERSATION.MESSAGE.REPLY); + }; + }, [replyMessage]); - return () => undefined; - }, [ - cancelMessageEditing, - cancelMessageReply, - cancelMesssageEditingWithDraftReset, - draftState, + useOutsideInputClick({ isEditing, - replyMessageCallback, - ]); + callback: () => { + cancelMesssageEditingWithDraftReset(); + cancelMessageReply(); + }, + }); useEffect(() => { conversation.isTextInputReady(true); diff --git a/src/script/components/InputBar/useMessageHandling/useMessageReply/useMessageReply.ts b/src/script/components/InputBar/useMessageHandling/useMessageReply/useMessageReply.ts index 1430b409145..1cdc6a7b86b 100644 --- a/src/script/components/InputBar/useMessageHandling/useMessageReply/useMessageReply.ts +++ b/src/script/components/InputBar/useMessageHandling/useMessageReply/useMessageReply.ts @@ -51,12 +51,10 @@ export const useMessageReply = () => { ); useEffect(() => { - amplify.subscribe(WebAppEvents.CONVERSATION.MESSAGE.REPLY, replyMessage); amplify.subscribe(WebAppEvents.CONVERSATION.MESSAGE.REMOVED, handleRepliedMessageDeleted); amplify.subscribe(WebAppEvents.CONVERSATION.MESSAGE.UPDATED, handleRepliedMessageUpdated); return () => { - amplify.unsubscribeAll(WebAppEvents.CONVERSATION.MESSAGE.REPLY); amplify.unsubscribeAll(WebAppEvents.CONVERSATION.MESSAGE.REMOVED); amplify.unsubscribeAll(WebAppEvents.CONVERSATION.MESSAGE.UPDATED); }; diff --git a/src/script/components/InputBar/useMessageHandling/useOutsideInputClick/useOutsideInputClick.ts b/src/script/components/InputBar/useMessageHandling/useOutsideInputClick/useOutsideInputClick.ts new file mode 100644 index 00000000000..5c34ec8f363 --- /dev/null +++ b/src/script/components/InputBar/useMessageHandling/useOutsideInputClick/useOutsideInputClick.ts @@ -0,0 +1,49 @@ +/* + * Wire + * Copyright (C) 2025 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import {useEffect} from 'react'; + +import {handleClickOutsideOfInputBar} from 'Components/InputBar/util/clickHandlers'; + +interface UseOutsideInputClickParams { + isEditing: boolean; + callback: () => void; +} + +export const useOutsideInputClick = ({isEditing, callback}: UseOutsideInputClickParams) => { + useEffect(() => { + const onWindowClick = (event: Event): void => + handleClickOutsideOfInputBar(event, () => { + // We want to add a timeout in case the click happens because the user switched conversation and the component is unmounting. + // In this case we want to keep the edited message for this conversation + setTimeout(() => { + callback(); + }); + }); + if (isEditing) { + window.addEventListener('click', onWindowClick); + + return () => { + window.removeEventListener('click', onWindowClick); + }; + } + + return () => undefined; + }, [callback, isEditing]); +}; From eecae85e23ad7d2025141be408e8c02a422e689f Mon Sep 17 00:00:00 2001 From: Olaf Sulich Date: Mon, 17 Feb 2025 13:43:04 +0100 Subject: [PATCH 13/13] fix: import paths --- .../InputBar/InputBarEditor/RichTextEditor/RichTextEditor.tsx | 2 +- .../plugins/DraftStatePlugin/DraftStatePlugin.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/script/components/InputBar/InputBarEditor/RichTextEditor/RichTextEditor.tsx b/src/script/components/InputBar/InputBarEditor/RichTextEditor/RichTextEditor.tsx index 5e8a95f8e23..a8f7b054eee 100644 --- a/src/script/components/InputBar/InputBarEditor/RichTextEditor/RichTextEditor.tsx +++ b/src/script/components/InputBar/InputBarEditor/RichTextEditor/RichTextEditor.tsx @@ -31,8 +31,8 @@ import {OnChangePlugin} from '@lexical/react/LexicalOnChangePlugin'; import {RichTextPlugin} from '@lexical/react/LexicalRichTextPlugin'; import {LexicalEditor, EditorState} from 'lexical'; +import {DraftState} from 'Components/InputBar/common/draftState/draftState'; import {MessageContent} from 'Components/InputBar/common/messageContent/messageContent'; -import {DraftState} from 'Components/InputBar/util/DraftStateUtil'; import {ContentMessage} from 'src/script/entity/message/ContentMessage'; import {User} from 'src/script/entity/User'; diff --git a/src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/DraftStatePlugin/DraftStatePlugin.tsx b/src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/DraftStatePlugin/DraftStatePlugin.tsx index 6ddbcf056ad..7adc147097a 100644 --- a/src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/DraftStatePlugin/DraftStatePlugin.tsx +++ b/src/script/components/InputBar/InputBarEditor/RichTextEditor/plugins/DraftStatePlugin/DraftStatePlugin.tsx @@ -21,7 +21,7 @@ import {useCallback, useEffect} from 'react'; import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext'; -import {DraftState} from 'Components/InputBar/util/DraftStateUtil'; +import {DraftState} from 'Components/InputBar/common/draftState/draftState'; interface DraftStatePluginProps { loadDraftState: () => Promise;