Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit fb30b67

Browse files
Fix issues with the new topic dialog (#8608)
1 parent e1d11db commit fb30b67

File tree

8 files changed

+112
-51
lines changed

8 files changed

+112
-51
lines changed

res/css/views/rooms/_RoomHeader.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ limitations under the License.
142142

143143
.mx_RoomTopic {
144144
position: relative;
145+
cursor: pointer;
145146
}
146147

147148
.mx_RoomHeader_topic {

src/components/views/elements/Linkify.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,26 +14,31 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
import React, { useEffect, useRef } from "react";
18-
import linkifyElement from "linkify-element";
17+
import React, { useLayoutEffect, useRef } from "react";
18+
19+
import { linkifyElement } from "../../../HtmlUtils";
1920

2021
interface Props {
2122
as?: string;
2223
children: React.ReactNode;
24+
onClick?: (ev: MouseEvent) => void;
2325
}
2426

2527
export function Linkify({
2628
as = "div",
2729
children,
30+
onClick,
2831
}: Props): JSX.Element {
2932
const ref = useRef();
3033

31-
useEffect(() => {
34+
useLayoutEffect(() => {
3235
linkifyElement(ref.current);
3336
}, [children]);
3437

3538
return React.createElement(as, {
3639
children,
3740
ref,
41+
onClick,
3842
});
3943
}
44+

src/components/views/elements/RoomTopic.tsx

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,13 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
import React, { useCallback, useContext, useEffect, useRef } from "react";
17+
import React, { useCallback, useContext, useRef } from "react";
1818
import { Room } from "matrix-js-sdk/src/models/room";
1919
import classNames from "classnames";
2020
import { EventType } from "matrix-js-sdk/src/@types/event";
2121

22-
import { linkifyElement } from "../../../HtmlUtils";
2322
import { useTopic } from "../../../hooks/room/useTopic";
24-
import useHover from "../../../hooks/useHover";
25-
import Tooltip, { Alignment } from "./Tooltip";
23+
import { Alignment } from "./Tooltip";
2624
import { _t } from "../../../languageHandler";
2725
import dis from "../../../dispatcher/dispatcher";
2826
import { Action } from "../../../dispatcher/actions";
@@ -32,6 +30,7 @@ import { useDispatcher } from "../../../hooks/useDispatcher";
3230
import MatrixClientContext from "../../../contexts/MatrixClientContext";
3331
import AccessibleButton from "./AccessibleButton";
3432
import { Linkify } from "./Linkify";
33+
import TooltipTarget from "./TooltipTarget";
3534

3635
interface IProps extends React.HTMLProps<HTMLDivElement> {
3736
room?: Room;
@@ -43,7 +42,6 @@ export default function RoomTopic({
4342
}: IProps) {
4443
const client = useContext(MatrixClientContext);
4544
const ref = useRef<HTMLDivElement>();
46-
const hovered = useHover(ref);
4745

4846
const topic = useTopic(room);
4947

@@ -57,14 +55,27 @@ export default function RoomTopic({
5755
dis.fire(Action.ShowRoomTopic);
5856
}, [props]);
5957

58+
const ignoreHover = (ev: React.MouseEvent): boolean => {
59+
return (ev.target as HTMLElement).tagName.toUpperCase() === "A";
60+
};
61+
6062
useDispatcher(dis, (payload) => {
6163
if (payload.action === Action.ShowRoomTopic) {
6264
const canSetTopic = room.currentState.maySendStateEvent(EventType.RoomTopic, client.getUserId());
6365

6466
const modal = Modal.createDialog(InfoDialog, {
6567
title: room.name,
6668
description: <div>
67-
<Linkify as="p">{ topic }</Linkify>
69+
<Linkify
70+
as="p"
71+
onClick={(ev: MouseEvent) => {
72+
if ((ev.target as HTMLElement).tagName.toUpperCase() === "A") {
73+
modal.close();
74+
}
75+
}}
76+
>
77+
{ topic }
78+
</Linkify>
6879
{ canSetTopic && <AccessibleButton
6980
kind="primary_outline"
7081
onClick={() => {
@@ -80,10 +91,6 @@ export default function RoomTopic({
8091
}
8192
});
8293

83-
useEffect(() => {
84-
linkifyElement(ref.current);
85-
}, [topic]);
86-
8794
const className = classNames(props.className, "mx_RoomTopic");
8895

8996
return <div {...props}
@@ -92,9 +99,10 @@ export default function RoomTopic({
9299
dir="auto"
93100
className={className}
94101
>
95-
{ topic }
96-
{ hovered && (
97-
<Tooltip label={_t("Click to read topic")} alignment={Alignment.Bottom} />
98-
) }
102+
<TooltipTarget label={_t("Click to read topic")} alignment={Alignment.Bottom} ignoreHover={ignoreHover}>
103+
<Linkify>
104+
{ topic }
105+
</Linkify>
106+
</TooltipTarget>
99107
</div>;
100108
}

src/components/views/elements/TooltipTarget.tsx

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,15 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
import React, { useState, HTMLAttributes } from 'react';
17+
import React, { HTMLAttributes } from 'react';
1818

19+
import useFocus from "../../../hooks/useFocus";
20+
import useHover from "../../../hooks/useHover";
1921
import Tooltip, { ITooltipProps } from './Tooltip';
2022

2123
interface IProps extends HTMLAttributes<HTMLSpanElement>, Omit<ITooltipProps, 'visible'> {
2224
tooltipTargetClassName?: string;
25+
ignoreHover?: (ev: React.MouseEvent) => boolean;
2326
}
2427

2528
/**
@@ -36,34 +39,31 @@ const TooltipTarget: React.FC<IProps> = ({
3639
alignment,
3740
tooltipClassName,
3841
maxParentWidth,
42+
ignoreHover,
3943
...rest
4044
}) => {
41-
const [isVisible, setIsVisible] = useState(false);
42-
43-
const show = () => setIsVisible(true);
44-
const hide = () => setIsVisible(false);
45+
const [isFocused, focusProps] = useFocus();
46+
const [isHovering, hoverProps] = useHover(ignoreHover);
4547

4648
// No need to fill up the DOM with hidden tooltip elements. Only add the
4749
// tooltip when we're hovering over the item (performance)
48-
const tooltip = isVisible && <Tooltip
50+
const tooltip = (isFocused || isHovering) && <Tooltip
4951
id={id}
5052
className={className}
5153
tooltipClassName={tooltipClassName}
5254
label={label}
5355
alignment={alignment}
54-
visible={isVisible}
56+
visible={isFocused || isHovering}
5557
maxParentWidth={maxParentWidth}
5658
/>;
5759

5860
return (
5961
<div
62+
{...hoverProps}
63+
{...focusProps}
6064
tabIndex={0}
6165
aria-describedby={id}
6266
className={tooltipTargetClassName}
63-
onMouseOver={show}
64-
onMouseLeave={hide}
65-
onFocus={show}
66-
onBlur={hide}
6767
{...rest}
6868
>
6969
{ children }

src/hooks/useFocus.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
Copyright 2022 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { useState } from "react";
18+
19+
export default function useFocus(
20+
): [boolean, {onFocus: () => void, onBlur: () => void}] {
21+
const [focused, setFocused] = useState(false);
22+
23+
const props = {
24+
onFocus: () => setFocused(true),
25+
onBlur: () => setFocused(false),
26+
};
27+
28+
return [focused, props];
29+
}

src/hooks/useHover.ts

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,29 +14,20 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
import React, { useEffect, useState } from "react";
17+
import { useState } from "react";
1818

19-
export default function useHover(ref: React.MutableRefObject<HTMLElement>) {
19+
export default function useHover(
20+
ignoreHover?: (ev: React.MouseEvent) => boolean,
21+
): [boolean, { onMouseOver: () => void, onMouseLeave: () => void, onMouseMove: (ev: React.MouseEvent) => void }] {
2022
const [hovered, setHoverState] = useState(false);
2123

22-
const handleMouseOver = () => setHoverState(true);
23-
const handleMouseOut = () => setHoverState(false);
24-
25-
useEffect(
26-
() => {
27-
const node = ref.current;
28-
if (node) {
29-
node.addEventListener("mouseover", handleMouseOver);
30-
node.addEventListener("mouseout", handleMouseOut);
31-
32-
return () => {
33-
node.removeEventListener("mouseover", handleMouseOver);
34-
node.removeEventListener("mouseout", handleMouseOut);
35-
};
36-
}
24+
const props = {
25+
onMouseOver: () => setHoverState(true),
26+
onMouseLeave: () => setHoverState(false),
27+
onMouseMove: (ev: React.MouseEvent): void => {
28+
setHoverState(!ignoreHover(ev));
3729
},
38-
[ref],
39-
);
30+
};
4031

41-
return hovered;
32+
return [hovered, props];
4233
}

test/components/views/elements/Linkify-test.tsx

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,23 @@ describe("Linkify", () => {
2222
const wrapper = mount(<Linkify>
2323
https://perdu.com
2424
</Linkify>);
25-
expect(wrapper.html()).toBe('<div><a href="https://perdu.com">https://perdu.com</a></div>');
25+
expect(wrapper.html()).toBe(
26+
"<div><a href=\"https://perdu.com\" class=\"linkified\" target=\"_blank\" rel=\"noreferrer noopener\">"+
27+
"https://perdu.com" +
28+
"</a></div>",
29+
);
30+
});
31+
32+
it("correctly linkifies a room alias", () => {
33+
const wrapper = mount(<Linkify>
34+
#element-web:matrix.org
35+
</Linkify>);
36+
expect(wrapper.html()).toBe(
37+
"<div>" +
38+
"<a href=\"https://matrix.to/#/#element-web:matrix.org\" class=\"linkified\" rel=\"noreferrer noopener\">" +
39+
"#element-web:matrix.org" +
40+
"</a></div>",
41+
);
2642
});
2743

2844
it("changes the root tag name", () => {
@@ -55,10 +71,20 @@ describe("Linkify", () => {
5571

5672
const wrapper = mount(<DummyTest />);
5773

58-
expect(wrapper.html()).toBe('<div><div><a href="https://perdu.com">https://perdu.com</a></div></div>');
74+
expect(wrapper.html()).toBe(
75+
"<div><div>" +
76+
"<a href=\"https://perdu.com\" class=\"linkified\" target=\"_blank\" rel=\"noreferrer noopener\">" +
77+
"https://perdu.com" +
78+
"</a></div></div>",
79+
);
5980

6081
wrapper.find('div').at(0).simulate('click');
6182

62-
expect(wrapper.html()).toBe('<div><div><a href="https://matrix.org">https://matrix.org</a></div></div>');
83+
expect(wrapper.html()).toBe(
84+
"<div><div>" +
85+
"<a href=\"https://matrix.org\" class=\"linkified\" target=\"_blank\" rel=\"noreferrer noopener\">" +
86+
"https://matrix.org" +
87+
"</a></div></div>",
88+
);
6389
});
6490
});

test/components/views/messages/__snapshots__/MLocationBody-test.tsx.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ exports[`MLocationBody <MLocationBody> without error renders map correctly 1`] =
9595
onBlur={[Function]}
9696
onFocus={[Function]}
9797
onMouseLeave={[Function]}
98+
onMouseMove={[Function]}
9899
onMouseOver={[Function]}
99100
tabIndex={0}
100101
>

0 commit comments

Comments
 (0)