Page MenuHomePhabricator

D8027.id28356.diff
No OneTemporary

D8027.id28356.diff

diff --git a/web/chat/chat-input-bar.css b/web/chat/chat-input-bar.css
--- a/web/chat/chat-input-bar.css
+++ b/web/chat/chat-input-bar.css
@@ -13,6 +13,7 @@
display: flex;
background: var(--text-input-bg);
border-radius: 8px;
+ /* Related to editBoxHeight in the `edit-text-message` component */
padding: 8px;
align-items: center;
flex-grow: 1;
diff --git a/web/chat/chat-input-text-area.react.js b/web/chat/chat-input-text-area.react.js
--- a/web/chat/chat-input-text-area.react.js
+++ b/web/chat/chat-input-text-area.react.js
@@ -12,8 +12,11 @@
+currentText: string,
+setCurrentText: (text: string) => void,
+onChangePosition: () => void,
+ +maxHeight?: number,
};
+export const defaultMaxHeight = 150;
+
const ChatInputTextArea: React.ComponentType<Props> = React.memo<Props>(
function ChatInputTextArea(props: Props) {
const {
@@ -23,6 +26,7 @@
send,
setCurrentText,
onChangePosition,
+ maxHeight = defaultMaxHeight,
} = props;
const textareaRef = React.useRef(null);
@@ -53,11 +57,11 @@
const textarea = textareaRef.current;
if (textarea) {
textarea.style.height = 'auto';
- const newHeight = Math.min(textarea.scrollHeight, 150);
+ const newHeight = Math.min(textarea.scrollHeight, maxHeight);
textarea.style.height = `${newHeight}px`;
}
onChangePosition();
- }, [onChangePosition]);
+ }, [maxHeight, onChangePosition]);
React.useEffect(() => {
focusAndUpdateText();
diff --git a/web/chat/chat-message-list.css b/web/chat/chat-message-list.css
--- a/web/chat/chat-message-list.css
+++ b/web/chat/chat-message-list.css
@@ -18,6 +18,9 @@
div.mirroredMessageContainer > div {
transform: scaleY(-1);
}
+div.disableAnchor {
+ overflow-anchor: none;
+}
div.message {
display: flex;
diff --git a/web/chat/chat-message-list.react.js b/web/chat/chat-message-list.react.js
--- a/web/chat/chat-message-list.react.js
+++ b/web/chat/chat-message-list.react.js
@@ -3,6 +3,7 @@
import classNames from 'classnames';
import { detect as detectBrowser } from 'detect-browser';
import invariant from 'invariant';
+import _ from 'lodash';
import * as React from 'react';
import {
@@ -28,8 +29,11 @@
useDispatchActionPromise,
} from 'lib/utils/action-utils.js';
+import { defaultMaxHeight } from './chat-input-text-area.react.js';
import css from './chat-message-list.css';
+import type { ScrollToMessageCallback } from './edit-message-provider.js';
import { useEditModalContext } from './edit-message-provider.js';
+import { editBoxHeight, editBoxTopMargin } from './edit-text-message.react.js';
import { MessageListContext } from './message-list-types.js';
import Message from './message.react.js';
import RelationshipPrompt from './relationship-prompt/relationship-prompt.js';
@@ -64,18 +68,37 @@
+clearTooltip: () => mixed,
+oldestMessageServerID: ?string,
+isEditState: boolean,
+ +addScrollToMessageListener: ScrollToMessageCallback => mixed,
+ +removeScrollToMessageListener: ScrollToMessageCallback => mixed,
};
type Snapshot = {
+scrollTop: number,
+scrollHeight: number,
};
-class ChatMessageList extends React.PureComponent<Props> {
+
+type State = {
+ +scrollingEndCallback: ?() => mixed,
+};
+
+class ChatMessageList extends React.PureComponent<Props, State> {
container: ?HTMLDivElement;
messageContainer: ?HTMLDivElement;
loadingFromScroll = false;
+ constructor(props: Props) {
+ super(props);
+ this.state = {
+ scrollingEndCallback: null,
+ };
+ }
+
componentDidMount() {
this.scrollToBottom();
+ this.props.addScrollToMessageListener(this.scrollToMessage);
+ }
+
+ componentWillUnmount() {
+ this.props.removeScrollToMessageListener(this.scrollToMessage);
}
getSnapshotBeforeUpdate(prevProps: Props) {
@@ -178,8 +201,106 @@
);
};
+ scrollingEndCallbackWrapper = (
+ composedMessageID: string,
+ callback: (maxHeight: number) => mixed,
+ ): (() => mixed) => {
+ return () => {
+ const maxHeight = this.getMaxEditTextAreaHeight(composedMessageID);
+ callback(maxHeight);
+ };
+ };
+
+ scrollToMessage = (
+ composedMessageID: string,
+ callback: (maxHeight: number) => mixed,
+ ) => {
+ const element = document.getElementById(composedMessageID);
+ if (!element) {
+ return;
+ }
+ const scrollingEndCallback = this.scrollingEndCallbackWrapper(
+ composedMessageID,
+ callback,
+ );
+ if (!this.willMessageEditWindowOverflow(composedMessageID)) {
+ scrollingEndCallback();
+ return;
+ }
+ this.setState(
+ {
+ scrollingEndCallback,
+ },
+ () => {
+ element.scrollIntoView({ behavior: 'smooth', block: 'center' });
+ // It covers the case when browser decide not to scroll to the message
+ // because it's already in the view.
+ // In this case, the 'scroll' event won't be triggered,
+ // so we need to call the callback manually.
+ this.debounceEditModeAfterScrollToMessage();
+ },
+ );
+ };
+
+ getMaxEditTextAreaHeight = (composedMessageID: string): number => {
+ const { messageContainer } = this;
+ if (!messageContainer) {
+ return defaultMaxHeight;
+ }
+ const messageElement = document.getElementById(composedMessageID);
+ if (!messageElement) {
+ console.log(`couldn't find the message element`);
+ return defaultMaxHeight;
+ }
+
+ const msgPos = messageElement.getBoundingClientRect();
+ const containerPos = messageContainer.getBoundingClientRect();
+
+ const messageBottom = msgPos.bottom;
+ const containerTop = containerPos.top;
+
+ const maxHeight =
+ messageBottom - containerTop - editBoxHeight - editBoxTopMargin;
+
+ return maxHeight;
+ };
+
+ willMessageEditWindowOverflow(composedMessageID: string) {
+ const { messageContainer } = this;
+ if (!messageContainer) {
+ return false;
+ }
+ const messageElement = document.getElementById(composedMessageID);
+ if (!messageElement) {
+ console.log(`couldn't find the message element`);
+ return false;
+ }
+
+ const msgPos = messageElement.getBoundingClientRect();
+ const containerPos = messageContainer.getBoundingClientRect();
+ const containerTop = containerPos.top;
+ const containerBottom = containerPos.bottom;
+
+ const availableTextAreaHeight =
+ (containerBottom - containerTop) / 2 - editBoxHeight;
+ const messageHeight = msgPos.height;
+ const expectedMinimumHeight = Math.min(
+ defaultMaxHeight,
+ availableTextAreaHeight,
+ );
+ const offset = Math.max(
+ 0,
+ expectedMinimumHeight + editBoxHeight + editBoxTopMargin - messageHeight,
+ );
+
+ const messageTop = msgPos.top - offset;
+ const messageBottom = msgPos.bottom;
+
+ return messageBottom > containerBottom || messageTop < containerTop;
+ }
+
render() {
- const { messageListData, threadInfo, inputState } = this.props;
+ const { messageListData, threadInfo, inputState, isEditState } = this.props;
if (!messageListData) {
return <div className={css.container} />;
}
@@ -192,6 +313,8 @@
}
const messageContainerStyle = classNames({
+ [css.disableAnchor]:
+ this.state.scrollingEndCallback !== null || isEditState,
[css.messageContainer]: true,
[css.mirroredMessageContainer]: !supportsReverseFlex,
});
@@ -221,8 +344,16 @@
}
this.props.clearTooltip();
this.possiblyLoadMoreMessages();
+ this.debounceEditModeAfterScrollToMessage();
};
+ debounceEditModeAfterScrollToMessage = _.debounce(() => {
+ if (this.state.scrollingEndCallback) {
+ this.state.scrollingEndCallback();
+ }
+ this.setState({ scrollingEndCallback: null });
+ }, 100);
+
async possiblyLoadMoreMessages() {
if (!this.messageContainer) {
return;
@@ -313,7 +444,11 @@
const oldestMessageServerID = useOldestMessageServerID(threadInfo.id);
- const { editState } = useEditModalContext();
+ const {
+ editState,
+ addScrollToMessageListener,
+ removeScrollToMessageListener,
+ } = useEditModalContext();
const isEditState = editState !== null;
return (
@@ -330,6 +465,8 @@
clearTooltip={clearTooltip}
oldestMessageServerID={oldestMessageServerID}
isEditState={isEditState}
+ addScrollToMessageListener={addScrollToMessageListener}
+ removeScrollToMessageListener={removeScrollToMessageListener}
/>
</MessageListContext.Provider>
);
diff --git a/web/chat/composed-message.react.js b/web/chat/composed-message.react.js
--- a/web/chat/composed-message.react.js
+++ b/web/chat/composed-message.react.js
@@ -11,7 +11,9 @@
import { useStringForUser } from 'lib/hooks/ens-cache.js';
import { type ChatMessageInfoItem } from 'lib/selectors/chat-selectors.js';
import { getMessageLabel } from 'lib/shared/edit-messages-utils.js';
+import { messageKey } from 'lib/shared/message-utils.js';
import { assertComposableMessageType } from 'lib/types/message-types.js';
+import type { MessageInfo } from 'lib/types/message-types.js';
import { type ThreadInfo } from 'lib/types/thread-types.js';
import css from './chat-message-list.css';
@@ -23,6 +25,14 @@
import { useMessageTooltip } from '../utils/tooltip-action-utils.js';
import { tooltipPositions } from '../utils/tooltip-utils.js';
+export type ComposedMessageID = string;
+
+export const getComposedMessageID = (
+ messageInfo: MessageInfo,
+): ComposedMessageID => {
+ return `ComposedMessageBox-${messageKey(messageInfo)}`;
+};
+
const availableTooltipPositionsForViewerMessage = [
tooltipPositions.LEFT,
tooltipPositions.LEFT_BOTTOM,
@@ -188,7 +198,11 @@
onMouseLeave={this.props.onMouseLeave}
>
{pinIcon}
- <div className={messageBoxClassName} style={messageBoxStyle}>
+ <div
+ className={messageBoxClassName}
+ style={messageBoxStyle}
+ id={getComposedMessageID(item.messageInfo)}
+ >
{this.props.children}
</div>
</div>
diff --git a/web/chat/edit-message-provider.js b/web/chat/edit-message-provider.js
--- a/web/chat/edit-message-provider.js
+++ b/web/chat/edit-message-provider.js
@@ -22,8 +22,14 @@
+editedMessageDraft: ?string,
+isError: boolean,
+position?: ModalPosition,
+ +maxHeight: number,
};
+export type ScrollToMessageCallback = (
+ composedMessageID: string,
+ callback: (maxHeight: number) => void,
+) => void;
+
type EditModalContextType = {
+renderEditModal: (params: EditState) => void,
+clearEditModal: () => void,
@@ -31,6 +37,9 @@
+setDraft: string => void,
+setError: boolean => void,
+updatePosition: ModalPosition => void,
+ +scrollToMessage: ScrollToMessageCallback,
+ +addScrollToMessageListener: ScrollToMessageCallback => void,
+ +removeScrollToMessageListener: ScrollToMessageCallback => void,
};
const EditModalContext: React.Context<EditModalContextType> =
@@ -41,6 +50,9 @@
setDraft: () => {},
setError: () => {},
updatePosition: () => {},
+ scrollToMessage: () => {},
+ addScrollToMessageListener: () => {},
+ removeScrollToMessageListener: () => {},
});
type Props = {
@@ -51,6 +63,9 @@
const [editState, setEditState] = React.useState<?EditState>(null);
+ const [scrollToMessageCallbacks, setScrollToMessageCallbacks] =
+ React.useState<Array<ScrollToMessageCallback>>([]);
+
const clearEditModal = React.useCallback(() => {
setEditState(null);
}, []);
@@ -118,6 +133,36 @@
[editState, setEditState],
);
+ const scrollToMessage: ScrollToMessageCallback = React.useCallback(
+ (messageKey: string, callback: (maxHeight: number) => void) => {
+ scrollToMessageCallbacks.forEach((callback2: ScrollToMessageCallback) =>
+ callback2(messageKey, callback),
+ );
+ },
+ [scrollToMessageCallbacks],
+ );
+
+ const addScrollToMessageListener = React.useCallback(
+ (callback: ScrollToMessageCallback): void => {
+ setScrollToMessageCallbacks(prevScrollToMessageCallbacks => [
+ ...prevScrollToMessageCallbacks,
+ callback,
+ ]);
+ },
+ [],
+ );
+
+ const removeScrollToMessageListener = React.useCallback(
+ (callback: ScrollToMessageCallback) => {
+ setScrollToMessageCallbacks(prevScrollToMessageCallbacks =>
+ prevScrollToMessageCallbacks.filter(
+ candidate => candidate !== callback,
+ ),
+ );
+ },
+ [],
+ );
+
const value = React.useMemo(
() => ({
renderEditModal,
@@ -126,6 +171,9 @@
setDraft,
setError,
updatePosition,
+ scrollToMessage,
+ addScrollToMessageListener,
+ removeScrollToMessageListener,
}),
[
renderEditModal,
@@ -134,6 +182,9 @@
setDraft,
setError,
updatePosition,
+ scrollToMessage,
+ addScrollToMessageListener,
+ removeScrollToMessageListener,
],
);
diff --git a/web/chat/edit-text-message.css b/web/chat/edit-text-message.css
--- a/web/chat/edit-text-message.css
+++ b/web/chat/edit-text-message.css
@@ -1,4 +1,5 @@
.editMessage {
+ /* Related to editBoxHeight in the `edit-text-message` component */
padding: 10px;
background-color: var(--modal-bg);
border-radius: 8px;
@@ -9,6 +10,7 @@
}
.bottomRow {
+ /* Related to editBoxHeight in the `edit-text-message` component */
padding-top: 10px;
display: flex;
align-items: center;
diff --git a/web/chat/edit-text-message.react.js b/web/chat/edit-text-message.react.js
--- a/web/chat/edit-text-message.react.js
+++ b/web/chat/edit-text-message.react.js
@@ -10,7 +10,6 @@
import { trimMessage } from 'lib/shared/message-utils.js';
import { type ThreadInfo } from 'lib/types/thread-types.js';
-import cssInputBar from './chat-input-bar.css';
import ChatInputTextArea from './chat-input-text-area.react.js';
import ComposedMessage from './composed-message.react.js';
import { useEditModalContext } from './edit-message-provider.js';
@@ -28,6 +27,11 @@
backgroundColor: 'transparent',
};
+const bottomRowHeight = 22;
+
+export const editBoxTopMargin = 10;
+export const editBoxHeight: number = 3 * 10 + 2 * 8 + bottomRowHeight;
+
function EditTextMessage(props: Props): React.Node {
const { background, threadInfo, item } = props;
const { editState, clearEditModal, setDraft, setError, updatePosition } =
@@ -138,18 +142,19 @@
[css.backgroundEditMessage]: background,
});
+ const maxTextAreaHeight = editState?.maxHeight;
+
return (
<div className={containerStyle} ref={myRef}>
- <div className={cssInputBar.inputBarTextInput}>
- <ChatInputTextArea
- focus={!background}
- currentText={editedMessageDraft}
- setCurrentText={setDraft}
- onChangePosition={updateDimensions}
- send={checkAndEdit}
- />
- </div>
- <div className={css.bottomRow}>
+ <ChatInputTextArea
+ focus={!background}
+ currentText={editedMessageDraft}
+ setCurrentText={setDraft}
+ onChangePosition={updateDimensions}
+ send={checkAndEdit}
+ maxHeight={maxTextAreaHeight}
+ />
+ <div className={css.bottomRow} style={{ height: bottomRowHeight }}>
{editFailed}
<div className={css.buttons}>
<Button
diff --git a/web/utils/tooltip-action-utils.js b/web/utils/tooltip-action-utils.js
--- a/web/utils/tooltip-action-utils.js
+++ b/web/utils/tooltip-action-utils.js
@@ -27,6 +27,7 @@
type TooltipSize,
type TooltipPosition,
} from './tooltip-utils.js';
+import { getComposedMessageID } from '../chat/composed-message.react.js';
import { useEditModalContext } from '../chat/edit-message-provider.js';
import MessageTooltip from '../chat/message-tooltip.react.js';
import type { PositionInfo } from '../chat/position-types.js';
@@ -226,7 +227,7 @@
const { messageInfo } = item;
const canEditMessage = useCanEditMessage(threadInfo, messageInfo);
- const { renderEditModal } = useEditModalContext();
+ const { renderEditModal, scrollToMessage } = useEditModalContext();
const { clearTooltip } = useTooltipContext();
return React.useMemo(() => {
@@ -235,13 +236,16 @@
}
const buttonContent = <CommIcon icon="edit-filled" size={18} />;
const onClickEdit = () => {
+ const callback = (maxHeight: number) =>
+ renderEditModal({
+ messageInfo: item,
+ threadInfo,
+ isError: false,
+ editedMessageDraft: messageInfo.text,
+ maxHeight: maxHeight,
+ });
clearTooltip();
- renderEditModal({
- messageInfo: item,
- threadInfo,
- isError: false,
- editedMessageDraft: messageInfo.text,
- });
+ scrollToMessage(getComposedMessageID(messageInfo), callback);
};
return {
actionButtonContent: buttonContent,
@@ -252,8 +256,9 @@
canEditMessage,
clearTooltip,
item,
- messageInfo.text,
+ messageInfo,
renderEditModal,
+ scrollToMessage,
threadInfo,
]);
}

File Metadata

Mime Type
text/plain
Expires
Fri, Dec 20, 1:32 PM (16 h, 15 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2681843
Default Alt Text
D8027.id28356.diff (16 KB)

Event Timeline