Fix threads fallback incorrectly targets root event (#9229)

* Use RelationType enum instead of hardcoded value

* Fix threads replies fallback to target last reply

* Only unsubscribe from threads events if needed

* fix strict null check

* fix strict null checks

* strict null checks

* fix typing

* Unsubscribe listeners if new thread is `null`

Co-authored-by: Faye Duxovni <fayed@element.io>

* Update strict null checks

* Type HTMLElement as nullable

* Add thread fallback integration test

* lint fix

* Update snapshots

* Add test after changing thread

* Remove test comment

* update snapshot

* fix room context test utility

* Add ThreadListContextMenu test

* lint fix

* fix thread rendering

Co-authored-by: Faye Duxovni <fayed@element.io>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Germain 2022-10-20 12:48:25 +01:00 committed by GitHub
parent d898af820b
commit be281fd735
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 453 additions and 103 deletions

View file

@ -29,9 +29,9 @@ import { WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
interface IProps {
export interface ThreadListContextMenuProps {
mxEvent: MatrixEvent;
permalinkCreator: RoomPermalinkCreator;
permalinkCreator?: RoomPermalinkCreator;
onMenuToggle?: (open: boolean) => void;
}
@ -43,7 +43,7 @@ const contextMenuBelow = (elementRect: DOMRect) => {
return { left, top, chevronFace };
};
const ThreadListContextMenu: React.FC<IProps> = ({
const ThreadListContextMenu: React.FC<ThreadListContextMenuProps> = ({
mxEvent,
permalinkCreator,
onMenuToggle,
@ -64,12 +64,14 @@ const ThreadListContextMenu: React.FC<IProps> = ({
closeThreadOptions();
}, [mxEvent, closeThreadOptions]);
const copyLinkToThread = useCallback(async (evt: ButtonEvent) => {
evt.preventDefault();
evt.stopPropagation();
const matrixToUrl = permalinkCreator.forEvent(mxEvent.getId());
await copyPlaintext(matrixToUrl);
closeThreadOptions();
const copyLinkToThread = useCallback(async (evt: ButtonEvent | undefined) => {
if (permalinkCreator) {
evt?.preventDefault();
evt?.stopPropagation();
const matrixToUrl = permalinkCreator.forEvent(mxEvent.getId());
await copyPlaintext(matrixToUrl);
closeThreadOptions();
}
}, [mxEvent, closeThreadOptions, permalinkCreator]);
useEffect(() => {
@ -87,6 +89,7 @@ const ThreadListContextMenu: React.FC<IProps> = ({
title={_t("Thread options")}
isExpanded={menuDisplayed}
inputRef={button}
data-testid="threadlist-dropdown-button"
/>
{ menuDisplayed && (<IconizedContextMenu
onFinished={closeThreadOptions}
@ -102,11 +105,14 @@ const ThreadListContextMenu: React.FC<IProps> = ({
label={_t("View in room")}
iconClassName="mx_ThreadPanel_viewInRoom"
/> }
<IconizedContextMenuOption
onClick={(e) => copyLinkToThread(e)}
label={_t("Copy link to thread")}
iconClassName="mx_ThreadPanel_copyLinkToThread"
/>
{ permalinkCreator &&
<IconizedContextMenuOption
data-testid="copy-thread-link"
onClick={(e) => copyLinkToThread(e)}
label={_t("Copy link to thread")}
iconClassName="mx_ThreadPanel_copyLinkToThread"
/>
}
</IconizedContextMenuOptionList>
</IconizedContextMenu>) }
</React.Fragment>;

View file

@ -40,6 +40,7 @@ export default class Spinner extends React.PureComponent<IProps> {
style={{ width: w, height: h }}
aria-label={_t("Loading...")}
role="progressbar"
data-testid="spinner"
/>
</div>
);

View file

@ -57,7 +57,7 @@ type State = Partial<Pick<CSSProperties, "display" | "right" | "top" | "transfor
export default class Tooltip extends React.PureComponent<ITooltipProps, State> {
private static container: HTMLElement;
private parent: Element;
private parent: Element | null = null;
// XXX: This is because some components (Field) are unable to `import` the Tooltip class,
// so we expose the Alignment options off of us statically.
@ -87,7 +87,7 @@ export default class Tooltip extends React.PureComponent<ITooltipProps, State> {
capture: true,
});
this.parent = ReactDOM.findDOMNode(this).parentNode as Element;
this.parent = ReactDOM.findDOMNode(this)?.parentNode as Element ?? null;
this.updatePosition();
}
@ -109,7 +109,7 @@ export default class Tooltip extends React.PureComponent<ITooltipProps, State> {
// positioned, also taking into account any window zoom
private updatePosition = (): void => {
// When the tooltip is hidden, no need to thrash the DOM with `style` attribute updates (performance)
if (!this.props.visible) return;
if (!this.props.visible || !this.parent) return;
const parentBox = this.parent.getBoundingClientRect();
const width = UIStore.instance.windowWidth;

View file

@ -789,6 +789,7 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
aria-activedescendant={activeDescendant}
dir="auto"
aria-disabled={this.props.disabled}
data-testid="basicmessagecomposer"
/>
</div>);
}

View file

@ -73,6 +73,7 @@ function SendButton(props: ISendButtonProps) {
className="mx_MessageComposer_sendMessage"
onClick={props.onClick}
title={props.title ?? _t('Send message')}
data-testid="sendmessagebtn"
/>
);
}