Attachment Modal: Add Symbol menu (#2525)
This commit is contained in:
parent
ecd1870fff
commit
30a36c7908
@ -175,7 +175,7 @@ const StickerSetModal: FC<OwnProps & StateProps> = ({
|
|||||||
{renderingStickerSet ? renderText(renderingStickerSet.title, ['emoji', 'links']) : lang('AccDescrStickerSet')}
|
{renderingStickerSet ? renderText(renderingStickerSet.title, ['emoji', 'links']) : lang('AccDescrStickerSet')}
|
||||||
</div>
|
</div>
|
||||||
<DropdownMenu
|
<DropdownMenu
|
||||||
className="stickers-more-menu"
|
className="stickers-more-menu with-menu-transitions"
|
||||||
trigger={MoreMenuButton}
|
trigger={MoreMenuButton}
|
||||||
positionX="right"
|
positionX="right"
|
||||||
>
|
>
|
||||||
|
|||||||
@ -226,7 +226,8 @@ const Main: FC<OwnProps & StateProps> = ({
|
|||||||
// switch back to the mobile version, you get a blank screen
|
// switch back to the mobile version, you get a blank screen
|
||||||
const { isDesktop } = useAppLayout();
|
const { isDesktop } = useAppLayout();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isMiddleColumnOpen && !isLeftColumnOpen && !isDesktop) {
|
const areColumnsConflicting = isLeftColumnOpen === isMiddleColumnOpen;
|
||||||
|
if (areColumnsConflicting && !isDesktop) {
|
||||||
toggleLeftColumn();
|
toggleLeftColumn();
|
||||||
}
|
}
|
||||||
}, [isDesktop, isLeftColumnOpen, isMiddleColumnOpen, toggleLeftColumn]);
|
}, [isDesktop, isLeftColumnOpen, isMiddleColumnOpen, toggleLeftColumn]);
|
||||||
|
|||||||
@ -355,7 +355,7 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
|||||||
</Button>
|
</Button>
|
||||||
<div className="modal-title">{bot?.firstName}</div>
|
<div className="modal-title">{bot?.firstName}</div>
|
||||||
<DropdownMenu
|
<DropdownMenu
|
||||||
className="web-app-more-menu"
|
className="web-app-more-menu with-menu-transitions"
|
||||||
trigger={MoreMenuButton}
|
trigger={MoreMenuButton}
|
||||||
positionX="right"
|
positionX="right"
|
||||||
>
|
>
|
||||||
|
|||||||
@ -25,6 +25,64 @@
|
|||||||
max-height: calc(100vh - 3.25rem - 5rem);
|
max-height: calc(100vh - 3.25rem - 5rem);
|
||||||
|
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
|
|
||||||
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
|
|
||||||
|
@supports not (padding-bottom: env(safe-area-inset-bottom)) {
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.symbol-menu-button {
|
||||||
|
flex-shrink: 0;
|
||||||
|
background: none !important;
|
||||||
|
width: 3.5rem !important;
|
||||||
|
height: 3.5rem !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
align-self: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.symbol-menu-button, .mobile-symbol-menu-button {
|
||||||
|
margin-right: -1.75rem;
|
||||||
|
margin-left: -0.5rem !important;
|
||||||
|
color: var(--color-composer-button);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-symbol-menu-button {
|
||||||
|
margin-left: 0 !important;
|
||||||
|
margin-right: -1.25rem !important;
|
||||||
|
width: 2.875rem;
|
||||||
|
height: 2.875rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
:global(body.keyboard-visible) & :global(.modal-content) {
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mobile :global {
|
||||||
|
.modal-dialog {
|
||||||
|
margin: 0;
|
||||||
|
max-width: 100% !important;
|
||||||
|
align-self: end;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
&.mobile:global(:not(.open)) :global(.modal-dialog) {
|
||||||
|
transform: translate3d(0, 8rem, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mobile.symbolMenuOpen :global(.modal-dialog) {
|
||||||
|
transition: var(--layer-transition);
|
||||||
|
|
||||||
|
transform: translate3d(0, calc((var(--symbol-menu-footer-height) + var(--symbol-menu-height) - env(safe-area-inset-bottom)) * -1), 0);
|
||||||
|
|
||||||
|
@supports not (bottom: env(safe-area-inset-bottom)) {
|
||||||
|
transform: translate3d(0, calc((var(--symbol-menu-footer-height) + var(--symbol-menu-height)) * -1), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,9 @@ import React, {
|
|||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import type { ApiAttachment, ApiChatMember, ApiSticker } from '../../../api/types';
|
import type {
|
||||||
|
ApiAttachment, ApiChatMember, ApiSticker,
|
||||||
|
} from '../../../api/types';
|
||||||
import type { GlobalState } from '../../../global/types';
|
import type { GlobalState } from '../../../global/types';
|
||||||
import type { Signal } from '../../../util/signals';
|
import type { Signal } from '../../../util/signals';
|
||||||
|
|
||||||
@ -46,6 +48,7 @@ import CustomEmojiTooltip from './CustomEmojiTooltip.async';
|
|||||||
import AttachmentModalItem from './AttachmentModalItem';
|
import AttachmentModalItem from './AttachmentModalItem';
|
||||||
import DropdownMenu from '../../ui/DropdownMenu';
|
import DropdownMenu from '../../ui/DropdownMenu';
|
||||||
import MenuItem from '../../ui/MenuItem';
|
import MenuItem from '../../ui/MenuItem';
|
||||||
|
import SymbolMenuButton from './SymbolMenuButton';
|
||||||
|
|
||||||
import styles from './AttachmentModal.module.scss';
|
import styles from './AttachmentModal.module.scss';
|
||||||
|
|
||||||
@ -66,6 +69,9 @@ export type OwnProps = {
|
|||||||
onClear: NoneToVoidFunction;
|
onClear: NoneToVoidFunction;
|
||||||
onSendSilent: (sendCompressed: boolean, sendGrouped: boolean) => void;
|
onSendSilent: (sendCompressed: boolean, sendGrouped: boolean) => void;
|
||||||
onSendScheduled: (sendCompressed: boolean, sendGrouped: boolean) => void;
|
onSendScheduled: (sendCompressed: boolean, sendGrouped: boolean) => void;
|
||||||
|
onCustomEmojiSelect: (emoji: ApiSticker) => void;
|
||||||
|
onRemoveSymbol: VoidFunction;
|
||||||
|
onEmojiSelect: (emoji: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
@ -111,6 +117,9 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
|
|||||||
onClear,
|
onClear,
|
||||||
onSendSilent,
|
onSendSilent,
|
||||||
onSendScheduled,
|
onSendScheduled,
|
||||||
|
onCustomEmojiSelect,
|
||||||
|
onRemoveSymbol,
|
||||||
|
onEmojiSelect,
|
||||||
}) => {
|
}) => {
|
||||||
const { addRecentCustomEmoji, addRecentEmoji, updateAttachmentSettings } = getActions();
|
const { addRecentCustomEmoji, addRecentEmoji, updateAttachmentSettings } = getActions();
|
||||||
|
|
||||||
@ -126,6 +135,8 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
|
|||||||
const renderingAttachments = attachments.length ? attachments : prevAttachments;
|
const renderingAttachments = attachments.length ? attachments : prevAttachments;
|
||||||
const { isMobile } = useAppLayout();
|
const { isMobile } = useAppLayout();
|
||||||
|
|
||||||
|
const [isSymbolMenuOpen, openSymbolMenu, closeSymbolMenu] = useFlag();
|
||||||
|
|
||||||
const [shouldSendCompressed, setShouldSendCompressed] = useState(
|
const [shouldSendCompressed, setShouldSendCompressed] = useState(
|
||||||
shouldSuggestCompression ?? attachmentSettings.shouldCompress,
|
shouldSuggestCompression ?? attachmentSettings.shouldCompress,
|
||||||
);
|
);
|
||||||
@ -143,6 +154,12 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
|
|||||||
const renderingIsOpen = Boolean(renderingAttachments?.length);
|
const renderingIsOpen = Boolean(renderingAttachments?.length);
|
||||||
const [isHovered, markHovered, unmarkHovered] = useFlag();
|
const [isHovered, markHovered, unmarkHovered] = useFlag();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isOpen) {
|
||||||
|
closeSymbolMenu();
|
||||||
|
}
|
||||||
|
}, [closeSymbolMenu, isOpen]);
|
||||||
|
|
||||||
const [hasMedia, hasOnlyMedia] = useMemo(() => {
|
const [hasMedia, hasOnlyMedia] = useMemo(() => {
|
||||||
const onlyMedia = Boolean(renderingAttachments?.every((a) => a.quick || a.audio));
|
const onlyMedia = Boolean(renderingAttachments?.every((a) => a.quick || a.audio));
|
||||||
if (onlyMedia) return [true, true];
|
if (onlyMedia) return [true, true];
|
||||||
@ -388,7 +405,7 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
|
|||||||
</Button>
|
</Button>
|
||||||
<div className="modal-title">{title}</div>
|
<div className="modal-title">{title}</div>
|
||||||
<DropdownMenu
|
<DropdownMenu
|
||||||
className="attachment-modal-more-menu"
|
className="attachment-modal-more-menu with-menu-transitions"
|
||||||
trigger={MoreMenuButton}
|
trigger={MoreMenuButton}
|
||||||
positionX="right"
|
positionX="right"
|
||||||
>
|
>
|
||||||
@ -453,6 +470,8 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
|
|||||||
styles.root,
|
styles.root,
|
||||||
isHovered && styles.hovered,
|
isHovered && styles.hovered,
|
||||||
!areAttachmentsNotScrolled && styles.headerBorder,
|
!areAttachmentsNotScrolled && styles.headerBorder,
|
||||||
|
isMobile && styles.mobile,
|
||||||
|
isSymbolMenuOpen && styles.symbolMenuOpen,
|
||||||
)}
|
)}
|
||||||
noBackdropClose
|
noBackdropClose
|
||||||
>
|
>
|
||||||
@ -517,6 +536,20 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
|
|||||||
onClose={closeCustomEmojiTooltip}
|
onClose={closeCustomEmojiTooltip}
|
||||||
/>
|
/>
|
||||||
<div className={styles.caption}>
|
<div className={styles.caption}>
|
||||||
|
<SymbolMenuButton
|
||||||
|
chatId={chatId}
|
||||||
|
threadId={threadId}
|
||||||
|
isMobile={isMobile}
|
||||||
|
isReady={isReady}
|
||||||
|
isSymbolMenuOpen={isSymbolMenuOpen}
|
||||||
|
openSymbolMenu={openSymbolMenu}
|
||||||
|
closeSymbolMenu={closeSymbolMenu}
|
||||||
|
onCustomEmojiSelect={onCustomEmojiSelect}
|
||||||
|
onRemoveSymbol={onRemoveSymbol}
|
||||||
|
onEmojiSelect={onEmojiSelect}
|
||||||
|
isAttachmentModal
|
||||||
|
className="attachment-modal-symbol-menu with-menu-transitions"
|
||||||
|
/>
|
||||||
<MessageInput
|
<MessageInput
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
id="caption-input-text"
|
id="caption-input-text"
|
||||||
@ -532,6 +565,8 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
|
|||||||
onScroll={handleCaptionScroll}
|
onScroll={handleCaptionScroll}
|
||||||
canAutoFocus={Boolean(isReady && isForCurrentMessageList && attachments.length)}
|
canAutoFocus={Boolean(isReady && isForCurrentMessageList && attachments.length)}
|
||||||
captionLimit={leftChars}
|
captionLimit={leftChars}
|
||||||
|
shouldSuppressFocus={isMobile && isSymbolMenuOpen}
|
||||||
|
onSuppressedFocus={closeSymbolMenu}
|
||||||
/>
|
/>
|
||||||
<div className={styles.sendWrapper}>
|
<div className={styles.sendWrapper}>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@ -163,59 +163,60 @@
|
|||||||
animation: 0.25s ease-in-out forwards show-send-as-button;
|
animation: 0.25s ease-in-out forwards show-send-as-button;
|
||||||
transform-origin: right;
|
transform-origin: right;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.mobile-symbol-menu-button {
|
.mobile-symbol-menu-button {
|
||||||
width: 2.875rem;
|
width: 2.875rem;
|
||||||
height: 2.875rem;
|
height: 2.875rem;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
.icon-smile,
|
.icon-smile,
|
||||||
.icon-keyboard,
|
.icon-keyboard,
|
||||||
|
.Spinner {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Spinner {
|
||||||
|
--spinner-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-smile {
|
||||||
|
animation: grow-icon 0.4s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-keyboard,
|
||||||
|
.Spinner {
|
||||||
|
animation: hide-icon 0.4s forwards ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.not-ready > i {
|
||||||
|
animation-duration: 0ms !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-loading {
|
||||||
.Spinner {
|
.Spinner {
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Spinner {
|
|
||||||
--spinner-size: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-smile {
|
|
||||||
animation: grow-icon 0.4s ease-out;
|
animation: grow-icon 0.4s ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-keyboard,
|
.icon-keyboard,
|
||||||
|
.icon-smile {
|
||||||
|
animation: hide-icon 0.4s forwards ease-out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.menu-opened {
|
||||||
|
.icon-keyboard {
|
||||||
|
animation: grow-icon 0.4s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-smile,
|
||||||
.Spinner {
|
.Spinner {
|
||||||
animation: hide-icon 0.4s forwards ease-out;
|
animation: hide-icon 0.4s forwards ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.not-ready > i {
|
|
||||||
animation-duration: 0ms !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-loading {
|
|
||||||
.Spinner {
|
|
||||||
animation: grow-icon 0.4s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-keyboard,
|
|
||||||
.icon-smile {
|
|
||||||
animation: hide-icon 0.4s forwards ease-out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.menu-opened {
|
|
||||||
.icon-keyboard {
|
|
||||||
animation: grow-icon 0.4s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-smile,
|
|
||||||
.Spinner {
|
|
||||||
animation: hide-icon 0.4s forwards ease-out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#message-compose {
|
#message-compose {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
max-width: calc(100% - 4rem);
|
max-width: calc(100% - 4rem);
|
||||||
@ -396,6 +397,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.symbol-menu-trigger {
|
||||||
|
left: -1rem;
|
||||||
|
bottom: 0;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
@media (min-width: 600px) {
|
@media (min-width: 600px) {
|
||||||
.symbol-menu-button {
|
.symbol-menu-button {
|
||||||
width: 2rem !important;
|
width: 2rem !important;
|
||||||
|
|||||||
@ -29,7 +29,7 @@ import {
|
|||||||
REPLIES_USER_ID,
|
REPLIES_USER_ID,
|
||||||
SEND_MESSAGE_ACTION_INTERVAL,
|
SEND_MESSAGE_ACTION_INTERVAL,
|
||||||
EDITABLE_INPUT_CSS_SELECTOR,
|
EDITABLE_INPUT_CSS_SELECTOR,
|
||||||
MAX_UPLOAD_FILEPART_SIZE,
|
MAX_UPLOAD_FILEPART_SIZE, EDITABLE_INPUT_MODAL_ID,
|
||||||
} from '../../../config';
|
} from '../../../config';
|
||||||
import { IS_VOICE_RECORDING_SUPPORTED, IS_IOS } from '../../../util/environment';
|
import { IS_VOICE_RECORDING_SUPPORTED, IS_IOS } from '../../../util/environment';
|
||||||
import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
|
import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
|
||||||
@ -93,7 +93,6 @@ import useInterval from '../../../hooks/useInterval';
|
|||||||
import useSyncEffect from '../../../hooks/useSyncEffect';
|
import useSyncEffect from '../../../hooks/useSyncEffect';
|
||||||
import useVoiceRecording from './hooks/useVoiceRecording';
|
import useVoiceRecording from './hooks/useVoiceRecording';
|
||||||
import useClipboardPaste from './hooks/useClipboardPaste';
|
import useClipboardPaste from './hooks/useClipboardPaste';
|
||||||
import useDraft from './hooks/useDraft';
|
|
||||||
import useEditing from './hooks/useEditing';
|
import useEditing from './hooks/useEditing';
|
||||||
import useEmojiTooltip from './hooks/useEmojiTooltip';
|
import useEmojiTooltip from './hooks/useEmojiTooltip';
|
||||||
import useMentionTooltip from './hooks/useMentionTooltip';
|
import useMentionTooltip from './hooks/useMentionTooltip';
|
||||||
@ -105,6 +104,7 @@ import useAttachmentModal from './hooks/useAttachmentModal';
|
|||||||
import useGetSelectionRange from '../../../hooks/useGetSelectionRange';
|
import useGetSelectionRange from '../../../hooks/useGetSelectionRange';
|
||||||
import useDerivedState from '../../../hooks/useDerivedState';
|
import useDerivedState from '../../../hooks/useDerivedState';
|
||||||
import { useStateRef } from '../../../hooks/useStateRef';
|
import { useStateRef } from '../../../hooks/useStateRef';
|
||||||
|
import useDraft from './hooks/useDraft';
|
||||||
|
|
||||||
import DeleteMessageModal from '../../common/DeleteMessageModal.async';
|
import DeleteMessageModal from '../../common/DeleteMessageModal.async';
|
||||||
import Button from '../../ui/Button';
|
import Button from '../../ui/Button';
|
||||||
@ -112,7 +112,6 @@ import ResponsiveHoverButton from '../../ui/ResponsiveHoverButton';
|
|||||||
import Spinner from '../../ui/Spinner';
|
import Spinner from '../../ui/Spinner';
|
||||||
import AttachMenu from './AttachMenu';
|
import AttachMenu from './AttachMenu';
|
||||||
import Avatar from '../../common/Avatar';
|
import Avatar from '../../common/Avatar';
|
||||||
import SymbolMenu from './SymbolMenu.async';
|
|
||||||
import InlineBotTooltip from './InlineBotTooltip.async';
|
import InlineBotTooltip from './InlineBotTooltip.async';
|
||||||
import MentionTooltip from './MentionTooltip.async';
|
import MentionTooltip from './MentionTooltip.async';
|
||||||
import CustomSendMenu from './CustomSendMenu.async';
|
import CustomSendMenu from './CustomSendMenu.async';
|
||||||
@ -130,6 +129,7 @@ import DropArea, { DropAreaState } from './DropArea.async';
|
|||||||
import WebPagePreview from './WebPagePreview';
|
import WebPagePreview from './WebPagePreview';
|
||||||
import SendAsMenu from './SendAsMenu.async';
|
import SendAsMenu from './SendAsMenu.async';
|
||||||
import BotMenuButton from './BotMenuButton';
|
import BotMenuButton from './BotMenuButton';
|
||||||
|
import SymbolMenuButton from './SymbolMenuButton';
|
||||||
|
|
||||||
import './Composer.scss';
|
import './Composer.scss';
|
||||||
|
|
||||||
@ -280,8 +280,6 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
sendMessage,
|
sendMessage,
|
||||||
clearDraft,
|
clearDraft,
|
||||||
showDialog,
|
showDialog,
|
||||||
setStickerSearchQuery,
|
|
||||||
setGifSearchQuery,
|
|
||||||
forwardMessages,
|
forwardMessages,
|
||||||
openPollModal,
|
openPollModal,
|
||||||
closePollModal,
|
closePollModal,
|
||||||
@ -374,7 +372,6 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
const [isSymbolMenuOpen, openSymbolMenu, closeSymbolMenu] = useFlag();
|
const [isSymbolMenuOpen, openSymbolMenu, closeSymbolMenu] = useFlag();
|
||||||
const [isSendAsMenuOpen, openSendAsMenu, closeSendAsMenu] = useFlag();
|
const [isSendAsMenuOpen, openSendAsMenu, closeSendAsMenu] = useFlag();
|
||||||
const [isDeleteModalOpen, openDeleteModal, closeDeleteModal] = useFlag();
|
const [isDeleteModalOpen, openDeleteModal, closeDeleteModal] = useFlag();
|
||||||
const [isSymbolMenuLoaded, onSymbolMenuLoadingComplete] = useFlag();
|
|
||||||
const [isHoverDisabled, disableHover, enableHover] = useFlag();
|
const [isHoverDisabled, disableHover, enableHover] = useFlag();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -525,31 +522,10 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
insertHtmlAndUpdateCursor(newHtml, inputId);
|
insertHtmlAndUpdateCursor(newHtml, inputId);
|
||||||
}, [insertHtmlAndUpdateCursor]);
|
}, [insertHtmlAndUpdateCursor]);
|
||||||
|
|
||||||
const insertTextAndUpdateCursor = useCallback((text: string, inputId: string = EDITABLE_INPUT_ID) => {
|
|
||||||
const newHtml = renderText(text, ['escape_html', 'emoji_html', 'br_html'])
|
|
||||||
.join('')
|
|
||||||
.replace(/\u200b+/g, '\u200b');
|
|
||||||
insertHtmlAndUpdateCursor(newHtml, inputId);
|
|
||||||
}, [insertHtmlAndUpdateCursor]);
|
|
||||||
|
|
||||||
const insertCustomEmojiAndUpdateCursor = useCallback((emoji: ApiSticker, inputId: string = EDITABLE_INPUT_ID) => {
|
const insertCustomEmojiAndUpdateCursor = useCallback((emoji: ApiSticker, inputId: string = EDITABLE_INPUT_ID) => {
|
||||||
insertHtmlAndUpdateCursor(buildCustomEmojiHtml(emoji), inputId);
|
insertHtmlAndUpdateCursor(buildCustomEmojiHtml(emoji), inputId);
|
||||||
}, [insertHtmlAndUpdateCursor]);
|
}, [insertHtmlAndUpdateCursor]);
|
||||||
|
|
||||||
const removeSymbol = useCallback(() => {
|
|
||||||
const selection = window.getSelection()!;
|
|
||||||
|
|
||||||
if (selection.rangeCount) {
|
|
||||||
const selectionRange = selection.getRangeAt(0);
|
|
||||||
if (isSelectionInsideInput(selectionRange, EDITABLE_INPUT_ID)) {
|
|
||||||
document.execCommand('delete', false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setHtml(deleteLastCharacterOutsideSelection(getHtml()));
|
|
||||||
}, [getHtml, setHtml]);
|
|
||||||
|
|
||||||
useDraft(draft, chatId, threadId, getHtml, setHtml, editingMessage, lastSyncTime);
|
useDraft(draft, chatId, threadId, getHtml, setHtml, editingMessage, lastSyncTime);
|
||||||
|
|
||||||
const resetComposer = useCallback((shouldPreserveInput = false) => {
|
const resetComposer = useCallback((shouldPreserveInput = false) => {
|
||||||
@ -867,12 +843,6 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
openBotCommandMenu();
|
openBotCommandMenu();
|
||||||
}, [closeSymbolMenu, openBotCommandMenu]);
|
}, [closeSymbolMenu, openBotCommandMenu]);
|
||||||
|
|
||||||
const handleActivateSymbolMenu = useCallback(() => {
|
|
||||||
closeBotCommandMenu();
|
|
||||||
closeSendAsMenu();
|
|
||||||
openSymbolMenu();
|
|
||||||
}, [closeBotCommandMenu, closeSendAsMenu, openSymbolMenu]);
|
|
||||||
|
|
||||||
const handleMessageSchedule = useCallback((
|
const handleMessageSchedule = useCallback((
|
||||||
args: ScheduledMessageArgs, scheduledAt: number,
|
args: ScheduledMessageArgs, scheduledAt: number,
|
||||||
) => {
|
) => {
|
||||||
@ -928,15 +898,40 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
}, [handleFileSelect, requestedDraftFiles, resetOpenChatWithDraft]);
|
}, [handleFileSelect, requestedDraftFiles, resetOpenChatWithDraft]);
|
||||||
|
|
||||||
const handleCustomEmojiSelect = useCallback((emoji: ApiSticker) => {
|
const handleCustomEmojiSelect = useCallback((emoji: ApiSticker, inputId?: string) => {
|
||||||
if (!emoji.isFree && !isCurrentUserPremium && !isChatWithSelf) {
|
if (!emoji.isFree && !isCurrentUserPremium && !isChatWithSelf) {
|
||||||
showCustomEmojiPremiumNotification();
|
showCustomEmojiPremiumNotification();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
insertCustomEmojiAndUpdateCursor(emoji);
|
insertCustomEmojiAndUpdateCursor(emoji, inputId);
|
||||||
}, [insertCustomEmojiAndUpdateCursor, isChatWithSelf, isCurrentUserPremium, showCustomEmojiPremiumNotification]);
|
}, [insertCustomEmojiAndUpdateCursor, isChatWithSelf, isCurrentUserPremium, showCustomEmojiPremiumNotification]);
|
||||||
|
|
||||||
|
const handleCustomEmojiSelectAttachmentModal = useCallback((emoji: ApiSticker) => {
|
||||||
|
handleCustomEmojiSelect(emoji, EDITABLE_INPUT_MODAL_ID);
|
||||||
|
}, [handleCustomEmojiSelect]);
|
||||||
|
|
||||||
|
const handleGifSelect = useCallback((gif: ApiVideo, isSilent?: boolean, isScheduleRequested?: boolean) => {
|
||||||
|
if (shouldSchedule || isScheduleRequested) {
|
||||||
|
forceShowSymbolMenu();
|
||||||
|
requestCalendar((scheduledAt) => {
|
||||||
|
cancelForceShowSymbolMenu();
|
||||||
|
handleMessageSchedule({ gif, isSilent }, scheduledAt);
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
resetComposer(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
sendMessage({ gif, isSilent });
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
resetComposer(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
shouldSchedule, forceShowSymbolMenu, requestCalendar, cancelForceShowSymbolMenu, handleMessageSchedule,
|
||||||
|
resetComposer, sendMessage,
|
||||||
|
]);
|
||||||
|
|
||||||
const handleStickerSelect = useCallback((
|
const handleStickerSelect = useCallback((
|
||||||
sticker: ApiSticker,
|
sticker: ApiSticker,
|
||||||
isSilent?: boolean,
|
isSilent?: boolean,
|
||||||
@ -969,27 +964,6 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
resetComposer, sendMessage,
|
resetComposer, sendMessage,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const handleGifSelect = useCallback((gif: ApiVideo, isSilent?: boolean, isScheduleRequested?: boolean) => {
|
|
||||||
if (shouldSchedule || isScheduleRequested) {
|
|
||||||
forceShowSymbolMenu();
|
|
||||||
requestCalendar((scheduledAt) => {
|
|
||||||
cancelForceShowSymbolMenu();
|
|
||||||
handleMessageSchedule({ gif, isSilent }, scheduledAt);
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
resetComposer(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
sendMessage({ gif, isSilent });
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
resetComposer(true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
shouldSchedule, forceShowSymbolMenu, requestCalendar, cancelForceShowSymbolMenu, handleMessageSchedule,
|
|
||||||
resetComposer, sendMessage,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const handleInlineBotSelect = useCallback((
|
const handleInlineBotSelect = useCallback((
|
||||||
inlineResult: ApiBotInlineResult | ApiBotInlineMediaResult, isSilent?: boolean, isScheduleRequested?: boolean,
|
inlineResult: ApiBotInlineResult | ApiBotInlineMediaResult, isSilent?: boolean, isScheduleRequested?: boolean,
|
||||||
) => {
|
) => {
|
||||||
@ -1059,31 +1033,6 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
}, [handleMessageSchedule, handleSend, handleSendAttachments, requestCalendar, shouldSchedule]);
|
}, [handleMessageSchedule, handleSend, handleSendAttachments, requestCalendar, shouldSchedule]);
|
||||||
|
|
||||||
const handleSearchOpen = useCallback((type: 'stickers' | 'gifs') => {
|
|
||||||
if (type === 'stickers') {
|
|
||||||
setStickerSearchQuery({ query: '' });
|
|
||||||
setGifSearchQuery({ query: undefined });
|
|
||||||
} else {
|
|
||||||
setGifSearchQuery({ query: '' });
|
|
||||||
setStickerSearchQuery({ query: undefined });
|
|
||||||
}
|
|
||||||
}, [setStickerSearchQuery, setGifSearchQuery]);
|
|
||||||
|
|
||||||
const handleSymbolMenuOpen = useCallback(() => {
|
|
||||||
const messageInput = document.querySelector<HTMLDivElement>(EDITABLE_INPUT_CSS_SELECTOR);
|
|
||||||
|
|
||||||
if (!isMobile || messageInput !== document.activeElement) {
|
|
||||||
openSymbolMenu();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
messageInput?.blur();
|
|
||||||
setTimeout(() => {
|
|
||||||
closeBotCommandMenu();
|
|
||||||
openSymbolMenu();
|
|
||||||
}, MOBILE_KEYBOARD_HIDE_DELAY_MS);
|
|
||||||
}, [openSymbolMenu, closeBotCommandMenu, isMobile]);
|
|
||||||
|
|
||||||
const handleSendAsMenuOpen = useCallback(() => {
|
const handleSendAsMenuOpen = useCallback(() => {
|
||||||
const messageInput = document.querySelector<HTMLDivElement>(EDITABLE_INPUT_CSS_SELECTOR);
|
const messageInput = document.querySelector<HTMLDivElement>(EDITABLE_INPUT_CSS_SELECTOR);
|
||||||
|
|
||||||
@ -1102,6 +1051,35 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
}, MOBILE_KEYBOARD_HIDE_DELAY_MS);
|
}, MOBILE_KEYBOARD_HIDE_DELAY_MS);
|
||||||
}, [closeBotCommandMenu, closeSymbolMenu, openSendAsMenu, isMobile]);
|
}, [closeBotCommandMenu, closeSymbolMenu, openSendAsMenu, isMobile]);
|
||||||
|
|
||||||
|
const insertTextAndUpdateCursor = useCallback((text: string, inputId: string = EDITABLE_INPUT_ID) => {
|
||||||
|
const newHtml = renderText(text, ['escape_html', 'emoji_html', 'br_html'])
|
||||||
|
.join('')
|
||||||
|
.replace(/\u200b+/g, '\u200b');
|
||||||
|
insertHtmlAndUpdateCursor(newHtml, inputId);
|
||||||
|
}, [insertHtmlAndUpdateCursor]);
|
||||||
|
|
||||||
|
const insertTextAndUpdateCursorAttachmentModal = useCallback((text: string) => {
|
||||||
|
insertTextAndUpdateCursor(text, EDITABLE_INPUT_MODAL_ID);
|
||||||
|
}, [insertTextAndUpdateCursor]);
|
||||||
|
|
||||||
|
const removeSymbol = useCallback((inputId = EDITABLE_INPUT_ID) => {
|
||||||
|
const selection = window.getSelection()!;
|
||||||
|
|
||||||
|
if (selection.rangeCount) {
|
||||||
|
const selectionRange = selection.getRangeAt(0);
|
||||||
|
if (isSelectionInsideInput(selectionRange, inputId)) {
|
||||||
|
document.execCommand('delete', false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setHtml(deleteLastCharacterOutsideSelection(getHtml()));
|
||||||
|
}, [getHtml, setHtml]);
|
||||||
|
|
||||||
|
const removeSymbolAttachmentModal = useCallback(() => {
|
||||||
|
removeSymbol(EDITABLE_INPUT_MODAL_ID);
|
||||||
|
}, [removeSymbol]);
|
||||||
|
|
||||||
const handleAllScheduledClick = useCallback(() => {
|
const handleAllScheduledClick = useCallback(() => {
|
||||||
openChat({
|
openChat({
|
||||||
id: chatId, threadId, type: 'scheduled', noForumTopicPanel: true,
|
id: chatId, threadId, type: 'scheduled', noForumTopicPanel: true,
|
||||||
@ -1193,14 +1171,6 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
isHoverDisabled && 'hover-disabled',
|
isHoverDisabled && 'hover-disabled',
|
||||||
);
|
);
|
||||||
|
|
||||||
const symbolMenuButtonClassName = buildClassName(
|
|
||||||
'mobile-symbol-menu-button',
|
|
||||||
!isReady && 'not-ready',
|
|
||||||
isSymbolMenuLoaded
|
|
||||||
? (isSymbolMenuOpen && 'menu-opened')
|
|
||||||
: (isSymbolMenuOpen && 'is-loading'),
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleSendScheduled = useCallback(() => {
|
const handleSendScheduled = useCallback(() => {
|
||||||
requestCalendar((scheduledAt) => {
|
requestCalendar((scheduledAt) => {
|
||||||
handleMessageSchedule({}, scheduledAt);
|
handleMessageSchedule({}, scheduledAt);
|
||||||
@ -1260,6 +1230,9 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
onFileAppend={handleAppendFiles}
|
onFileAppend={handleAppendFiles}
|
||||||
onClear={handleClearAttachments}
|
onClear={handleClearAttachments}
|
||||||
onAttachmentsUpdate={handleSetAttachments}
|
onAttachmentsUpdate={handleSetAttachments}
|
||||||
|
onCustomEmojiSelect={handleCustomEmojiSelectAttachmentModal}
|
||||||
|
onRemoveSymbol={removeSymbolAttachmentModal}
|
||||||
|
onEmojiSelect={insertTextAndUpdateCursorAttachmentModal}
|
||||||
/>
|
/>
|
||||||
<PollModal
|
<PollModal
|
||||||
isOpen={pollModal.isOpen}
|
isOpen={pollModal.isOpen}
|
||||||
@ -1359,29 +1332,25 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{isMobile ? (
|
<SymbolMenuButton
|
||||||
<Button
|
chatId={chatId}
|
||||||
className={symbolMenuButtonClassName}
|
threadId={threadId}
|
||||||
round
|
isMobile={isMobile}
|
||||||
color="translucent"
|
isReady={isReady}
|
||||||
onClick={isSymbolMenuOpen ? closeSymbolMenu : handleSymbolMenuOpen}
|
isSymbolMenuOpen={isSymbolMenuOpen}
|
||||||
ariaLabel="Choose emoji, sticker or GIF"
|
openSymbolMenu={openSymbolMenu}
|
||||||
>
|
closeSymbolMenu={closeSymbolMenu}
|
||||||
<i className="icon-smile" />
|
canSendStickers={canSendStickers}
|
||||||
<i className="icon-keyboard" />
|
canSendGifs={canSendGifs}
|
||||||
{isSymbolMenuOpen && !isSymbolMenuLoaded && <Spinner color="gray" />}
|
onGifSelect={handleGifSelect}
|
||||||
</Button>
|
onStickerSelect={handleStickerSelect}
|
||||||
) : (
|
onCustomEmojiSelect={handleCustomEmojiSelect}
|
||||||
<ResponsiveHoverButton
|
onRemoveSymbol={removeSymbol}
|
||||||
className={buildClassName('symbol-menu-button', isSymbolMenuOpen && 'activated')}
|
onEmojiSelect={insertTextAndUpdateCursor}
|
||||||
round
|
closeBotCommandMenu={closeBotCommandMenu}
|
||||||
color="translucent"
|
closeSendAsMenu={closeSendAsMenu}
|
||||||
onActivate={handleActivateSymbolMenu}
|
isSymbolMenuForced={isSymbolMenuForced}
|
||||||
ariaLabel="Choose emoji, sticker or GIF"
|
/>
|
||||||
>
|
|
||||||
<i className="icon-smile" />
|
|
||||||
</ResponsiveHoverButton>
|
|
||||||
)}
|
|
||||||
<MessageInput
|
<MessageInput
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
id="message-input-text"
|
id="message-input-text"
|
||||||
@ -1486,23 +1455,6 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
onCustomEmojiSelect={insertEmoji}
|
onCustomEmojiSelect={insertEmoji}
|
||||||
onClose={closeEmojiTooltip}
|
onClose={closeEmojiTooltip}
|
||||||
/>
|
/>
|
||||||
<SymbolMenu
|
|
||||||
chatId={chatId}
|
|
||||||
threadId={threadId}
|
|
||||||
isOpen={isSymbolMenuOpen || isSymbolMenuForced}
|
|
||||||
canSendGifs={canSendGifs}
|
|
||||||
canSendStickers={canSendStickers}
|
|
||||||
onLoad={onSymbolMenuLoadingComplete}
|
|
||||||
onClose={closeSymbolMenu}
|
|
||||||
onEmojiSelect={insertTextAndUpdateCursor}
|
|
||||||
onStickerSelect={handleStickerSelect}
|
|
||||||
onCustomEmojiSelect={handleCustomEmojiSelect}
|
|
||||||
onGifSelect={handleGifSelect}
|
|
||||||
onRemoveSymbol={removeSymbol}
|
|
||||||
onSearchOpen={handleSearchOpen}
|
|
||||||
addRecentEmoji={addRecentEmoji}
|
|
||||||
addRecentCustomEmoji={addRecentCustomEmoji}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{activeVoiceRecording && (
|
{activeVoiceRecording && (
|
||||||
|
|||||||
@ -39,7 +39,7 @@ const CustomSendMenu: FC<OwnProps> = ({
|
|||||||
autoClose
|
autoClose
|
||||||
positionX="right"
|
positionX="right"
|
||||||
positionY={isOpenToBottom ? 'top' : 'bottom'}
|
positionY={isOpenToBottom ? 'top' : 'bottom'}
|
||||||
className="CustomSendMenu"
|
className="CustomSendMenu with-menu-transitions"
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
onCloseAnimationEnd={onCloseAnimationEnd}
|
onCloseAnimationEnd={onCloseAnimationEnd}
|
||||||
onMouseEnter={!IS_TOUCH_ENV ? handleMouseEnter : undefined}
|
onMouseEnter={!IS_TOUCH_ENV ? handleMouseEnter : undefined}
|
||||||
|
|||||||
@ -164,7 +164,8 @@ const EmojiPicker: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const selectCategory = useCallback((index: number) => {
|
const selectCategory = useCallback((index: number) => {
|
||||||
setActiveCategoryIndex(index);
|
setActiveCategoryIndex(index);
|
||||||
const categoryEl = document.getElementById(`emoji-category-${index}`)!;
|
const categoryEl = containerRef.current!.closest<HTMLElement>('.SymbolMenu-main')!
|
||||||
|
.querySelector(`#emoji-category-${index}`)! as HTMLElement;
|
||||||
fastSmoothScroll(containerRef.current!, categoryEl, 'start', FOCUS_MARGIN, SMOOTH_SCROLL_DISTANCE);
|
fastSmoothScroll(containerRef.current!, categoryEl, 'start', FOCUS_MARGIN, SMOOTH_SCROLL_DISTANCE);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,17 @@
|
|||||||
@import "../../../styles/mixins";
|
@import "../../../styles/mixins";
|
||||||
|
|
||||||
.SymbolMenu {
|
.SymbolMenu {
|
||||||
|
&.attachment-modal-symbol-menu {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 10000;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.mobile-menu) {
|
||||||
|
@media (max-height: 800px) {
|
||||||
|
--symbol-menu-height: 40vh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.mobile-menu {
|
&.mobile-menu {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
left: 0;
|
left: 0;
|
||||||
@ -19,7 +30,7 @@
|
|||||||
0
|
0
|
||||||
);
|
);
|
||||||
|
|
||||||
&.open {
|
&.open:not(.in-attachment-modal) {
|
||||||
transform: translate3d(0, 0, 0);
|
transform: translate3d(0, 0, 0);
|
||||||
|
|
||||||
body.is-media-viewer-open & {
|
body.is-media-viewer-open & {
|
||||||
@ -27,6 +38,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.open.in-attachment-modal {
|
||||||
|
z-index: calc(var(--z-modal) + 1);
|
||||||
|
transform: translate3d(
|
||||||
|
0,
|
||||||
|
calc(var(--symbol-menu-height) + var(--symbol-menu-footer-height)),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Target: Old Firefox (Waterfox Classic)
|
// Target: Old Firefox (Waterfox Classic)
|
||||||
@supports not (padding-right: env(safe-area-inset-right)) {
|
@supports not (padding-right: env(safe-area-inset-right)) {
|
||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
@ -107,6 +127,11 @@
|
|||||||
width: 3.5rem;
|
width: 3.5rem;
|
||||||
height: 4.5rem;
|
height: 4.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.attachment-modal-symbol-menu > .backdrop {
|
||||||
|
bottom: 0;
|
||||||
|
top: auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Remove this monster with context menu refactor
|
// TODO Remove this monster with context menu refactor
|
||||||
|
|||||||
@ -35,24 +35,31 @@ export type OwnProps = {
|
|||||||
chatId: string;
|
chatId: string;
|
||||||
threadId?: number;
|
threadId?: number;
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
canSendStickers: boolean;
|
canSendStickers?: boolean;
|
||||||
canSendGifs: boolean;
|
canSendGifs?: boolean;
|
||||||
onLoad: () => void;
|
onLoad: () => void;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onEmojiSelect: (emoji: string) => void;
|
onEmojiSelect: (emoji: string) => void;
|
||||||
onCustomEmojiSelect: (emoji: ApiSticker) => void;
|
onCustomEmojiSelect: (emoji: ApiSticker) => void;
|
||||||
onStickerSelect: (
|
onStickerSelect?: (
|
||||||
sticker: ApiSticker,
|
sticker: ApiSticker,
|
||||||
isSilent?: boolean,
|
isSilent?: boolean,
|
||||||
shouldSchedule?: boolean,
|
shouldSchedule?: boolean,
|
||||||
shouldPreserveInput?: boolean,
|
shouldPreserveInput?: boolean,
|
||||||
shouldUpdateStickerSetsOrder?: boolean
|
shouldUpdateStickerSetsOrder?: boolean
|
||||||
) => void;
|
) => void;
|
||||||
onGifSelect: (gif: ApiVideo, isSilent?: boolean, shouldSchedule?: boolean) => void;
|
onGifSelect?: (gif: ApiVideo, isSilent?: boolean, shouldSchedule?: boolean) => void;
|
||||||
onRemoveSymbol: () => void;
|
onRemoveSymbol: () => void;
|
||||||
onSearchOpen: (type: 'stickers' | 'gifs') => void;
|
onSearchOpen: (type: 'stickers' | 'gifs') => void;
|
||||||
addRecentEmoji: GlobalActions['addRecentEmoji'];
|
addRecentEmoji: GlobalActions['addRecentEmoji'];
|
||||||
addRecentCustomEmoji: GlobalActions['addRecentCustomEmoji'];
|
addRecentCustomEmoji: GlobalActions['addRecentCustomEmoji'];
|
||||||
|
className?: string;
|
||||||
|
isAttachmentModal?: boolean;
|
||||||
|
positionX?: 'left' | 'right';
|
||||||
|
positionY?: 'top' | 'bottom';
|
||||||
|
transformOriginX?: number;
|
||||||
|
transformOriginY?: number;
|
||||||
|
style?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
@ -75,13 +82,20 @@ const SymbolMenu: FC<OwnProps & StateProps> = ({
|
|||||||
onLoad,
|
onLoad,
|
||||||
onClose,
|
onClose,
|
||||||
onEmojiSelect,
|
onEmojiSelect,
|
||||||
|
isAttachmentModal,
|
||||||
onCustomEmojiSelect,
|
onCustomEmojiSelect,
|
||||||
onStickerSelect,
|
onStickerSelect,
|
||||||
|
className,
|
||||||
onGifSelect,
|
onGifSelect,
|
||||||
onRemoveSymbol,
|
onRemoveSymbol,
|
||||||
onSearchOpen,
|
onSearchOpen,
|
||||||
addRecentEmoji,
|
addRecentEmoji,
|
||||||
addRecentCustomEmoji,
|
addRecentCustomEmoji,
|
||||||
|
positionX,
|
||||||
|
positionY,
|
||||||
|
transformOriginX,
|
||||||
|
transformOriginY,
|
||||||
|
style,
|
||||||
}) => {
|
}) => {
|
||||||
const { loadPremiumSetStickers, loadFeaturedEmojiStickers } = getActions();
|
const { loadPremiumSetStickers, loadFeaturedEmojiStickers } = getActions();
|
||||||
const [activeTab, setActiveTab] = useState<number>(0);
|
const [activeTab, setActiveTab] = useState<number>(0);
|
||||||
@ -109,7 +123,7 @@ const SymbolMenu: FC<OwnProps & StateProps> = ({
|
|||||||
}, [isCurrentUserPremium, lastSyncTime, loadFeaturedEmojiStickers, loadPremiumSetStickers]);
|
}, [isCurrentUserPremium, lastSyncTime, loadFeaturedEmojiStickers, loadPremiumSetStickers]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (!isMobile) {
|
if (!isMobile || isAttachmentModal) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,7 +142,7 @@ const SymbolMenu: FC<OwnProps & StateProps> = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [isMobile, isOpen]);
|
}, [isAttachmentModal, isMobile, isOpen]);
|
||||||
|
|
||||||
const recentEmojisRef = useRef(recentEmojis);
|
const recentEmojisRef = useRef(recentEmojis);
|
||||||
recentEmojisRef.current = recentEmojis;
|
recentEmojisRef.current = recentEmojis;
|
||||||
@ -180,7 +194,7 @@ const SymbolMenu: FC<OwnProps & StateProps> = ({
|
|||||||
const handleStickerSelect = useCallback((
|
const handleStickerSelect = useCallback((
|
||||||
sticker: ApiSticker, isSilent?: boolean, shouldSchedule?: boolean, shouldUpdateStickerSetsOrder?: boolean,
|
sticker: ApiSticker, isSilent?: boolean, shouldSchedule?: boolean, shouldUpdateStickerSetsOrder?: boolean,
|
||||||
) => {
|
) => {
|
||||||
onStickerSelect(sticker, isSilent, shouldSchedule, true, shouldUpdateStickerSetsOrder);
|
onStickerSelect?.(sticker, isSilent, shouldSchedule, true, shouldUpdateStickerSetsOrder);
|
||||||
}, [onStickerSelect]);
|
}, [onStickerSelect]);
|
||||||
|
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
@ -204,6 +218,8 @@ const SymbolMenu: FC<OwnProps & StateProps> = ({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
case SymbolMenuTabs.Stickers:
|
case SymbolMenuTabs.Stickers:
|
||||||
|
if (!canSendStickers) return undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StickerPicker
|
<StickerPicker
|
||||||
className="picker-tab"
|
className="picker-tab"
|
||||||
@ -215,6 +231,8 @@ const SymbolMenu: FC<OwnProps & StateProps> = ({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
case SymbolMenuTabs.GIFs:
|
case SymbolMenuTabs.GIFs:
|
||||||
|
if (!canSendGifs || !onGifSelect) return undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GifPicker
|
<GifPicker
|
||||||
className="picker-tab"
|
className="picker-tab"
|
||||||
@ -259,6 +277,7 @@ const SymbolMenu: FC<OwnProps & StateProps> = ({
|
|||||||
onSwitchTab={setActiveTab}
|
onSwitchTab={setActiveTab}
|
||||||
onRemoveSymbol={onRemoveSymbol}
|
onRemoveSymbol={onRemoveSymbol}
|
||||||
onSearchOpen={handleSearch}
|
onSearchOpen={handleSearch}
|
||||||
|
isAttachmentModal={isAttachmentModal}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@ -268,15 +287,24 @@ const SymbolMenu: FC<OwnProps & StateProps> = ({
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const className = buildClassName(
|
const mobileClassName = buildClassName(
|
||||||
'SymbolMenu mobile-menu',
|
'SymbolMenu mobile-menu',
|
||||||
transitionClassNames,
|
transitionClassNames,
|
||||||
isLeftColumnShown && 'left-column-open',
|
isLeftColumnShown && 'left-column-open',
|
||||||
|
isAttachmentModal && 'in-attachment-modal',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (isAttachmentModal) {
|
||||||
|
return (
|
||||||
|
<div className={mobileClassName}>
|
||||||
|
{content}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Portal>
|
<Portal>
|
||||||
<div className={className}>
|
<div className={mobileClassName}>
|
||||||
{content}
|
{content}
|
||||||
</div>
|
</div>
|
||||||
</Portal>
|
</Portal>
|
||||||
@ -286,15 +314,19 @@ const SymbolMenu: FC<OwnProps & StateProps> = ({
|
|||||||
return (
|
return (
|
||||||
<Menu
|
<Menu
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
positionX="left"
|
positionX={isAttachmentModal ? positionX : 'left'}
|
||||||
positionY="bottom"
|
positionY={isAttachmentModal ? positionY : 'bottom'}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
className="SymbolMenu"
|
withPortal={isAttachmentModal}
|
||||||
|
className={buildClassName('SymbolMenu', className)}
|
||||||
onCloseAnimationEnd={onClose}
|
onCloseAnimationEnd={onClose}
|
||||||
onMouseEnter={!IS_TOUCH_ENV ? handleMouseEnter : undefined}
|
onMouseEnter={!IS_TOUCH_ENV ? handleMouseEnter : undefined}
|
||||||
onMouseLeave={!IS_TOUCH_ENV ? handleMouseLeave : undefined}
|
onMouseLeave={!IS_TOUCH_ENV ? handleMouseLeave : undefined}
|
||||||
noCloseOnBackdrop={!IS_TOUCH_ENV}
|
noCloseOnBackdrop={!IS_TOUCH_ENV}
|
||||||
noCompact
|
noCompact
|
||||||
|
transformOriginX={transformOriginX}
|
||||||
|
transformOriginY={transformOriginY}
|
||||||
|
style={style}
|
||||||
>
|
>
|
||||||
{content}
|
{content}
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|||||||
210
src/components/middle/composer/SymbolMenuButton.tsx
Normal file
210
src/components/middle/composer/SymbolMenuButton.tsx
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
import React, {
|
||||||
|
memo, useCallback, useRef, useState,
|
||||||
|
} from '../../../lib/teact/teact';
|
||||||
|
import { getActions } from '../../../global';
|
||||||
|
|
||||||
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
|
import type { IAnchorPosition } from '../../../types';
|
||||||
|
import type { ApiVideo, ApiSticker } from '../../../api/types';
|
||||||
|
|
||||||
|
import { EDITABLE_INPUT_CSS_SELECTOR, EDITABLE_INPUT_MODAL_CSS_SELECTOR } from '../../../config';
|
||||||
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
import useFlag from '../../../hooks/useFlag';
|
||||||
|
import useContextMenuPosition from '../../../hooks/useContextMenuPosition';
|
||||||
|
|
||||||
|
import Button from '../../ui/Button';
|
||||||
|
import Spinner from '../../ui/Spinner';
|
||||||
|
import ResponsiveHoverButton from '../../ui/ResponsiveHoverButton';
|
||||||
|
import SymbolMenu from './SymbolMenu.async';
|
||||||
|
|
||||||
|
const MOBILE_KEYBOARD_HIDE_DELAY_MS = 100;
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
chatId: string;
|
||||||
|
threadId?: number;
|
||||||
|
isMobile?: boolean;
|
||||||
|
isReady?: boolean;
|
||||||
|
isSymbolMenuOpen?: boolean;
|
||||||
|
canSendGifs?: boolean;
|
||||||
|
canSendStickers?: boolean;
|
||||||
|
openSymbolMenu: VoidFunction;
|
||||||
|
closeSymbolMenu: VoidFunction;
|
||||||
|
onCustomEmojiSelect: (emoji: ApiSticker) => void;
|
||||||
|
onStickerSelect?: (
|
||||||
|
sticker: ApiSticker,
|
||||||
|
isSilent?: boolean,
|
||||||
|
shouldSchedule?: boolean,
|
||||||
|
shouldPreserveInput?: boolean,
|
||||||
|
shouldUpdateStickerSetsOrder?: boolean
|
||||||
|
) => void;
|
||||||
|
onGifSelect?: (gif: ApiVideo, isSilent?: boolean, shouldSchedule?: boolean) => void;
|
||||||
|
onRemoveSymbol: VoidFunction;
|
||||||
|
onEmojiSelect: (emoji: string) => void;
|
||||||
|
closeBotCommandMenu?: VoidFunction;
|
||||||
|
closeSendAsMenu?: VoidFunction;
|
||||||
|
isSymbolMenuForced?: boolean;
|
||||||
|
isAttachmentModal?: boolean;
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SymbolMenuButton: FC<OwnProps> = ({
|
||||||
|
chatId,
|
||||||
|
threadId,
|
||||||
|
isMobile,
|
||||||
|
canSendGifs,
|
||||||
|
canSendStickers,
|
||||||
|
isReady,
|
||||||
|
isSymbolMenuOpen,
|
||||||
|
openSymbolMenu,
|
||||||
|
closeSymbolMenu,
|
||||||
|
onCustomEmojiSelect,
|
||||||
|
onStickerSelect,
|
||||||
|
onGifSelect,
|
||||||
|
isAttachmentModal,
|
||||||
|
onRemoveSymbol,
|
||||||
|
onEmojiSelect,
|
||||||
|
closeBotCommandMenu,
|
||||||
|
closeSendAsMenu,
|
||||||
|
isSymbolMenuForced,
|
||||||
|
className,
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
setStickerSearchQuery,
|
||||||
|
setGifSearchQuery,
|
||||||
|
addRecentEmoji,
|
||||||
|
addRecentCustomEmoji,
|
||||||
|
} = getActions();
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-null/no-null
|
||||||
|
const triggerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const [isSymbolMenuLoaded, onSymbolMenuLoadingComplete] = useFlag();
|
||||||
|
const [contextMenuPosition, setContextMenuPosition] = useState<IAnchorPosition | undefined>(undefined);
|
||||||
|
|
||||||
|
const symbolMenuButtonClassName = buildClassName(
|
||||||
|
'mobile-symbol-menu-button',
|
||||||
|
!isReady && 'not-ready',
|
||||||
|
isSymbolMenuLoaded
|
||||||
|
? (isSymbolMenuOpen && 'menu-opened')
|
||||||
|
: (isSymbolMenuOpen && 'is-loading'),
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleActivateSymbolMenu = useCallback(() => {
|
||||||
|
closeBotCommandMenu?.();
|
||||||
|
closeSendAsMenu?.();
|
||||||
|
openSymbolMenu();
|
||||||
|
const triggerEl = triggerRef.current;
|
||||||
|
if (!triggerEl) return;
|
||||||
|
const { x, y } = triggerEl.getBoundingClientRect();
|
||||||
|
setContextMenuPosition({ x, y });
|
||||||
|
}, [closeBotCommandMenu, closeSendAsMenu, openSymbolMenu]);
|
||||||
|
|
||||||
|
const handleSearchOpen = useCallback((type: 'stickers' | 'gifs') => {
|
||||||
|
if (type === 'stickers') {
|
||||||
|
setStickerSearchQuery({ query: '' });
|
||||||
|
setGifSearchQuery({ query: undefined });
|
||||||
|
} else {
|
||||||
|
setGifSearchQuery({ query: '' });
|
||||||
|
setStickerSearchQuery({ query: undefined });
|
||||||
|
}
|
||||||
|
}, [setStickerSearchQuery, setGifSearchQuery]);
|
||||||
|
|
||||||
|
const handleSymbolMenuOpen = useCallback(() => {
|
||||||
|
const messageInput = document.querySelector<HTMLDivElement>(
|
||||||
|
isAttachmentModal ? EDITABLE_INPUT_MODAL_CSS_SELECTOR : EDITABLE_INPUT_CSS_SELECTOR,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isMobile || messageInput !== document.activeElement) {
|
||||||
|
openSymbolMenu();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
messageInput?.blur();
|
||||||
|
setTimeout(() => {
|
||||||
|
closeBotCommandMenu?.();
|
||||||
|
openSymbolMenu();
|
||||||
|
}, MOBILE_KEYBOARD_HIDE_DELAY_MS);
|
||||||
|
}, [isAttachmentModal, isMobile, openSymbolMenu, closeBotCommandMenu]);
|
||||||
|
|
||||||
|
const getTriggerElement = useCallback(() => triggerRef.current, []);
|
||||||
|
|
||||||
|
const getRootElement = useCallback(
|
||||||
|
() => triggerRef.current?.closest('.custom-scroll, .no-scrollbar'),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const getMenuElement = useCallback(
|
||||||
|
() => document.querySelector('#portals .SymbolMenu .bubble'),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const getLayout = useCallback(() => ({
|
||||||
|
withPortal: true,
|
||||||
|
}), []);
|
||||||
|
|
||||||
|
const {
|
||||||
|
positionX, positionY, transformOriginX, transformOriginY, style: menuStyle,
|
||||||
|
} = useContextMenuPosition(
|
||||||
|
contextMenuPosition,
|
||||||
|
getTriggerElement,
|
||||||
|
getRootElement,
|
||||||
|
getMenuElement,
|
||||||
|
getLayout,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{isMobile ? (
|
||||||
|
<Button
|
||||||
|
className={symbolMenuButtonClassName}
|
||||||
|
round
|
||||||
|
color="translucent"
|
||||||
|
onClick={isSymbolMenuOpen ? closeSymbolMenu : handleSymbolMenuOpen}
|
||||||
|
ariaLabel="Choose emoji, sticker or GIF"
|
||||||
|
>
|
||||||
|
<i className="icon-smile" />
|
||||||
|
<i className="icon-keyboard" />
|
||||||
|
{isSymbolMenuOpen && !isSymbolMenuLoaded && <Spinner color="gray" />}
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<ResponsiveHoverButton
|
||||||
|
className={buildClassName('symbol-menu-button', isSymbolMenuOpen && 'activated')}
|
||||||
|
round
|
||||||
|
color="translucent"
|
||||||
|
onActivate={handleActivateSymbolMenu}
|
||||||
|
ariaLabel="Choose emoji, sticker or GIF"
|
||||||
|
>
|
||||||
|
<div ref={triggerRef} className="symbol-menu-trigger" />
|
||||||
|
<i className="icon-smile" />
|
||||||
|
</ResponsiveHoverButton>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<SymbolMenu
|
||||||
|
chatId={chatId}
|
||||||
|
threadId={threadId}
|
||||||
|
isOpen={isSymbolMenuOpen || Boolean(isSymbolMenuForced)}
|
||||||
|
canSendGifs={canSendGifs}
|
||||||
|
canSendStickers={canSendStickers}
|
||||||
|
onLoad={onSymbolMenuLoadingComplete}
|
||||||
|
onClose={closeSymbolMenu}
|
||||||
|
onEmojiSelect={onEmojiSelect}
|
||||||
|
onStickerSelect={onStickerSelect}
|
||||||
|
onCustomEmojiSelect={onCustomEmojiSelect}
|
||||||
|
onGifSelect={onGifSelect}
|
||||||
|
onRemoveSymbol={onRemoveSymbol}
|
||||||
|
onSearchOpen={handleSearchOpen}
|
||||||
|
addRecentEmoji={addRecentEmoji}
|
||||||
|
addRecentCustomEmoji={addRecentCustomEmoji}
|
||||||
|
isAttachmentModal={isAttachmentModal}
|
||||||
|
className={className}
|
||||||
|
positionX={isAttachmentModal ? positionX : undefined}
|
||||||
|
positionY={isAttachmentModal ? positionY : undefined}
|
||||||
|
transformOriginX={isAttachmentModal ? transformOriginX : undefined}
|
||||||
|
transformOriginY={isAttachmentModal ? transformOriginY : undefined}
|
||||||
|
style={isAttachmentModal ? menuStyle : undefined}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(SymbolMenuButton);
|
||||||
@ -10,6 +10,7 @@ type OwnProps = {
|
|||||||
onSwitchTab: (tab: SymbolMenuTabs) => void;
|
onSwitchTab: (tab: SymbolMenuTabs) => void;
|
||||||
onRemoveSymbol: () => void;
|
onRemoveSymbol: () => void;
|
||||||
onSearchOpen: (type: 'stickers' | 'gifs') => void;
|
onSearchOpen: (type: 'stickers' | 'gifs') => void;
|
||||||
|
isAttachmentModal?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum SymbolMenuTabs {
|
export enum SymbolMenuTabs {
|
||||||
@ -34,7 +35,7 @@ const SYMBOL_MENU_TAB_ICONS = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const SymbolMenuFooter: FC<OwnProps> = ({
|
const SymbolMenuFooter: FC<OwnProps> = ({
|
||||||
activeTab, onSwitchTab, onRemoveSymbol, onSearchOpen,
|
activeTab, onSwitchTab, onRemoveSymbol, onSearchOpen, isAttachmentModal,
|
||||||
}) => {
|
}) => {
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
@ -79,8 +80,8 @@ const SymbolMenuFooter: FC<OwnProps> = ({
|
|||||||
|
|
||||||
{renderTabButton(SymbolMenuTabs.Emoji)}
|
{renderTabButton(SymbolMenuTabs.Emoji)}
|
||||||
{renderTabButton(SymbolMenuTabs.CustomEmoji)}
|
{renderTabButton(SymbolMenuTabs.CustomEmoji)}
|
||||||
{renderTabButton(SymbolMenuTabs.Stickers)}
|
{!isAttachmentModal && renderTabButton(SymbolMenuTabs.Stickers)}
|
||||||
{renderTabButton(SymbolMenuTabs.GIFs)}
|
{!isAttachmentModal && renderTabButton(SymbolMenuTabs.GIFs)}
|
||||||
|
|
||||||
{(activeTab === SymbolMenuTabs.Emoji || activeTab === SymbolMenuTabs.CustomEmoji) && (
|
{(activeTab === SymbolMenuTabs.Emoji || activeTab === SymbolMenuTabs.CustomEmoji) && (
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@ -67,7 +67,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body.has-open-dialog &:not(.CustomSendMenu):not(.web-app-more-menu):not(.attachment-modal-more-menu):not(.stickers-more-menu) .bubble {
|
body.has-open-dialog &:not(.with-menu-transitions) .bubble {
|
||||||
transition: none !important;
|
transition: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,13 +1,14 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, { useRef, useCallback, memo } from '../../lib/teact/teact';
|
import React, { useRef, useCallback, memo } from '../../lib/teact/teact';
|
||||||
|
|
||||||
|
import type { OwnProps as ButtonProps } from './Button';
|
||||||
|
|
||||||
import { IS_TOUCH_ENV } from '../../util/environment';
|
import { IS_TOUCH_ENV } from '../../util/environment';
|
||||||
|
|
||||||
import type { OwnProps as ButtonProps } from './Button';
|
|
||||||
import Button from './Button';
|
import Button from './Button';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
onActivate: NoneToVoidFunction;
|
onActivate: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
|
||||||
} & Omit<ButtonProps, (
|
} & Omit<ButtonProps, (
|
||||||
'onClick' | 'onMouseDown' |
|
'onClick' | 'onMouseDown' |
|
||||||
'onMouseEnter' | 'onMouseLeave' |
|
'onMouseEnter' | 'onMouseLeave' |
|
||||||
@ -21,13 +22,13 @@ let isFirstTimeActivation = true;
|
|||||||
const ResponsiveHoverButton: FC<OwnProps> = ({ onActivate, ...buttonProps }) => {
|
const ResponsiveHoverButton: FC<OwnProps> = ({ onActivate, ...buttonProps }) => {
|
||||||
const isMouseInside = useRef(false);
|
const isMouseInside = useRef(false);
|
||||||
|
|
||||||
const handleMouseEnter = useCallback(() => {
|
const handleMouseEnter = useCallback((e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||||
isMouseInside.current = true;
|
isMouseInside.current = true;
|
||||||
|
|
||||||
// This is used to counter additional delay caused by asynchronous module loading
|
// This is used to counter additional delay caused by asynchronous module loading
|
||||||
if (isFirstTimeActivation) {
|
if (isFirstTimeActivation) {
|
||||||
isFirstTimeActivation = false;
|
isFirstTimeActivation = false;
|
||||||
onActivate();
|
onActivate(e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,7 +38,7 @@ const ResponsiveHoverButton: FC<OwnProps> = ({ onActivate, ...buttonProps }) =>
|
|||||||
}
|
}
|
||||||
openTimeout = window.setTimeout(() => {
|
openTimeout = window.setTimeout(() => {
|
||||||
if (isMouseInside.current) {
|
if (isMouseInside.current) {
|
||||||
onActivate();
|
onActivate(e);
|
||||||
}
|
}
|
||||||
}, BUTTON_ACTIVATE_DELAY);
|
}, BUTTON_ACTIVATE_DELAY);
|
||||||
}, [onActivate]);
|
}, [onActivate]);
|
||||||
@ -46,9 +47,9 @@ const ResponsiveHoverButton: FC<OwnProps> = ({ onActivate, ...buttonProps }) =>
|
|||||||
isMouseInside.current = false;
|
isMouseInside.current = false;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleClick = useCallback(() => {
|
const handleClick = useCallback((e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||||
isMouseInside.current = true;
|
isMouseInside.current = true;
|
||||||
onActivate();
|
onActivate(e);
|
||||||
}, [onActivate]);
|
}, [onActivate]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -111,6 +111,7 @@ export const EDITABLE_INPUT_ID = 'editable-message-text';
|
|||||||
export const EDITABLE_INPUT_MODAL_ID = 'editable-message-text-modal';
|
export const EDITABLE_INPUT_MODAL_ID = 'editable-message-text-modal';
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
export const EDITABLE_INPUT_CSS_SELECTOR = `.messages-layout .Transition__slide--active #${EDITABLE_INPUT_ID}, .messages-layout .Transition > .to #${EDITABLE_INPUT_ID}`;
|
export const EDITABLE_INPUT_CSS_SELECTOR = `.messages-layout .Transition__slide--active #${EDITABLE_INPUT_ID}, .messages-layout .Transition > .to #${EDITABLE_INPUT_ID}`;
|
||||||
|
export const EDITABLE_INPUT_MODAL_CSS_SELECTOR = `#${EDITABLE_INPUT_MODAL_ID}`;
|
||||||
|
|
||||||
export const CUSTOM_APPENDIX_ATTRIBUTE = 'data-has-custom-appendix';
|
export const CUSTOM_APPENDIX_ATTRIBUTE = 'data-has-custom-appendix';
|
||||||
export const MESSAGE_CONTENT_CLASS_NAME = 'message-content';
|
export const MESSAGE_CONTENT_CLASS_NAME = 'message-content';
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user