Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F32199228
D8027.1765106306.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
14 KB
Referenced Files
None
Subscribers
None
D8027.1765106306.diff
View Options
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
@@ -28,8 +28,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 +67,39 @@
+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 = {
+ +scrollTimeoutID: ?TimeoutID,
+ +scrollingEndCallback: ?() => mixed,
+};
+
+class ChatMessageList extends React.PureComponent<Props, State> {
container: ?HTMLDivElement;
messageContainer: ?HTMLDivElement;
loadingFromScroll = false;
+ constructor(props: Props) {
+ super(props);
+ this.state = {
+ scrollTimeoutID: null,
+ scrollingEndCallback: null,
+ };
+ }
+
componentDidMount() {
this.scrollToBottom();
+ this.props.addScrollToMessageListener(this.scrollToMessage);
+ }
+
+ componentWillUnmount() {
+ this.props.removeScrollToMessageListener(this.scrollToMessage);
}
getSnapshotBeforeUpdate(prevProps: Props) {
@@ -178,8 +202,106 @@
);
};
+ scrollingEndCallbackWrapper = (
+ messageID: string,
+ callback: (maxHeight: number) => mixed,
+ ): (() => mixed) => {
+ return () => {
+ const maxHeight = this.getMaxEditTextAreaHeight(messageID);
+ callback(maxHeight);
+ };
+ };
+
+ scrollToMessage = (
+ messageID: string,
+ callback: (maxHeight: number) => mixed,
+ ) => {
+ const element = document.getElementById(messageID);
+ if (!element) {
+ return;
+ }
+ const scrollingEndCallback = this.scrollingEndCallbackWrapper(
+ messageID,
+ callback,
+ );
+ if (!this.willMessageEditWindowOverflow(messageID)) {
+ 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.handleScrollEnd();
+ },
+ );
+ };
+
+ getMaxEditTextAreaHeight = (messageID: string): number => {
+ const { messageContainer } = this;
+ if (!messageContainer) {
+ return defaultMaxHeight;
+ }
+ const messageElement = document.getElementById(messageID);
+ 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(messageID: string) {
+ const { messageContainer } = this;
+ if (!messageContainer) {
+ return false;
+ }
+ const messageElement = document.getElementById(messageID);
+ 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 +314,8 @@
}
const messageContainerStyle = classNames({
+ [css.disableAnchor]:
+ this.state.scrollingEndCallback !== null || isEditState,
[css.messageContainer]: true,
[css.mirroredMessageContainer]: !supportsReverseFlex,
});
@@ -221,6 +345,20 @@
}
this.props.clearTooltip();
this.possiblyLoadMoreMessages();
+ this.handleScrollEnd();
+ };
+
+ handleScrollEnd = () => {
+ if (this.state.scrollTimeoutID) {
+ clearTimeout(this.state.scrollTimeoutID);
+ }
+ const scrollTimeoutID = setTimeout(() => {
+ if (this.state.scrollingEndCallback) {
+ this.state.scrollingEndCallback();
+ }
+ this.setState({ scrollingEndCallback: null });
+ }, 100);
+ this.setState({ scrollTimeoutID });
};
async possiblyLoadMoreMessages() {
@@ -313,7 +451,11 @@
const oldestMessageServerID = useOldestMessageServerID(threadInfo.id);
- const { editState } = useEditModalContext();
+ const {
+ editState,
+ addScrollToMessageListener,
+ removeScrollToMessageListener,
+ } = useEditModalContext();
const isEditState = editState !== null;
return (
@@ -330,6 +472,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,6 +11,7 @@
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 ThreadInfo } from 'lib/types/thread-types.js';
@@ -188,7 +189,11 @@
onMouseLeave={this.props.onMouseLeave}
>
{pinIcon}
- <div className={messageBoxClassName} style={messageBoxStyle}>
+ <div
+ className={messageBoxClassName}
+ style={messageBoxStyle}
+ id={messageKey(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 = (
+ messageKey: 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,31 @@
[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([...scrollToMessageCallbacks, callback]);
+ },
+ [scrollToMessageCallbacks],
+ );
+
+ const removeScrollToMessageListener = React.useCallback(
+ (callback: ScrollToMessageCallback) => {
+ setScrollToMessageCallbacks(
+ scrollToMessageCallbacks.filter(candidate => candidate !== callback),
+ );
+ },
+ [scrollToMessageCallbacks],
+ );
+
const value = React.useMemo(
() => ({
renderEditModal,
@@ -126,6 +166,9 @@
setDraft,
setError,
updatePosition,
+ scrollToMessage,
+ addScrollToMessageListener,
+ removeScrollToMessageListener,
}),
[
renderEditModal,
@@ -134,6 +177,9 @@
setDraft,
setError,
updatePosition,
+ scrollToMessage,
+ addScrollToMessageListener,
+ removeScrollToMessageListener,
],
);
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
@@ -28,6 +28,9 @@
backgroundColor: 'transparent',
};
+export const editBoxTopMargin = 10;
+export const editBoxHeight = 84;
+
function EditTextMessage(props: Props): React.Node {
const { background, threadInfo, item } = props;
const { editState, clearEditModal, setDraft, setError, updatePosition } =
@@ -138,6 +141,8 @@
[css.backgroundEditMessage]: background,
});
+ const maxTextAreaHeight = editState?.maxHeight;
+
return (
<div className={containerStyle} ref={myRef}>
<div className={cssInputBar.inputBarTextInput}>
@@ -147,6 +152,7 @@
setCurrentText={setDraft}
onChangePosition={updateDimensions}
send={checkAndEdit}
+ maxHeight={maxTextAreaHeight}
/>
</div>
<div className={css.bottomRow}>
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
@@ -7,7 +7,7 @@
import { useModalContext } from 'lib/components/modal-provider.react.js';
import type { ChatMessageInfoItem } from 'lib/selectors/chat-selectors.js';
import { useCanEditMessage } from 'lib/shared/edit-messages-utils.js';
-import { createMessageReply } from 'lib/shared/message-utils.js';
+import { createMessageReply, messageKey } from 'lib/shared/message-utils.js';
import { useCanCreateReactionFromMessage } from 'lib/shared/reaction-utils.js';
import {
threadHasPermission,
@@ -233,7 +233,7 @@
const { messageInfo } = item;
const canEditMessage = useCanEditMessage(threadInfo, messageInfo);
- const { renderEditModal } = useEditModalContext();
+ const { renderEditModal, scrollToMessage } = useEditModalContext();
const { clearTooltip } = useTooltipContext();
return React.useMemo(() => {
@@ -242,13 +242,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(messageKey(messageInfo), callback);
};
return {
actionButtonContent: buttonContent,
@@ -259,8 +262,9 @@
canEditMessage,
clearTooltip,
item,
- messageInfo.text,
+ messageInfo,
renderEditModal,
+ scrollToMessage,
threadInfo,
]);
}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sun, Dec 7, 11:18 AM (13 h, 16 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5844018
Default Alt Text
D8027.1765106306.diff (14 KB)
Attached To
Mode
D8027: [web] Scrolling to the edited message when it overflows
Attached
Detach File
Event Timeline
Log In to Comment