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

Commit 06dca23

Browse files
committed
Use a pill to highlight the keyword that triggered a notification
1 parent 753d467 commit 06dca23

File tree

4 files changed

+108
-6
lines changed

4 files changed

+108
-6
lines changed

res/css/views/elements/_Pill.pcss

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ Please see LICENSE files in the repository root for full details.
2626
}
2727

2828
&.mx_UserPill_me,
29-
&.mx_AtRoomPill {
29+
&.mx_AtRoomPill,
30+
&.mx_KeywordPill {
3031
background-color: var(--cpd-color-bg-critical-primary) !important; /* To override .markdown-body */
3132
}
3233

@@ -45,7 +46,8 @@ Please see LICENSE files in the repository root for full details.
4546
}
4647

4748
/* We don't want to indicate clickability */
48-
&.mx_AtRoomPill:hover {
49+
&.mx_AtRoomPill:hover,
50+
&.mx_KeywordPill:hover {
4951
background-color: var(--cpd-color-bg-critical-primary) !important; /* To override .markdown-body */
5052
cursor: unset;
5153
}

src/components/views/elements/Pill.tsx

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export enum PillType {
2626
AtRoomMention = "TYPE_AT_ROOM_MENTION", // '@room' mention
2727
EventInSameRoom = "TYPE_EVENT_IN_SAME_ROOM",
2828
EventInOtherRoom = "TYPE_EVENT_IN_OTHER_ROOM",
29+
Keyword = "TYPE_KEYWORD", // Used to highlight keywords that triggered a notification rule
2930
}
3031

3132
export const pillRoomNotifPos = (text: string | null): number => {
@@ -77,14 +78,32 @@ export interface PillProps {
7778
room?: Room;
7879
// Whether to include an avatar in the pill
7980
shouldShowPillAvatar?: boolean;
81+
// Explicitly-provided text to display in the pill
82+
text?: string;
8083
}
8184

82-
export const Pill: React.FC<PillProps> = ({ type: propType, url, inMessage, room, shouldShowPillAvatar = true }) => {
83-
const { event, member, onClick, resourceId, targetRoom, text, type } = usePermalink({
85+
export const Pill: React.FC<PillProps> = ({
86+
type: propType,
87+
url,
88+
inMessage,
89+
room,
90+
shouldShowPillAvatar = true,
91+
text: customPillText,
92+
}) => {
93+
const {
94+
event,
95+
member,
96+
onClick,
97+
resourceId,
98+
targetRoom,
99+
text: linkText,
100+
type,
101+
} = usePermalink({
84102
room,
85103
type: propType,
86104
url,
87105
});
106+
const text = customPillText ?? linkText;
88107

89108
if (!type || !text) {
90109
return null;
@@ -97,6 +116,7 @@ export const Pill: React.FC<PillProps> = ({ type: propType, url, inMessage, room
97116
mx_UserPill: type === PillType.UserMention,
98117
mx_UserPill_me: resourceId === MatrixClientPeg.safeGet().getUserId(),
99118
mx_EventPill: type === PillType.EventInOtherRoom || type === PillType.EventInSameRoom,
119+
mx_KeywordPill: type === PillType.Keyword,
100120
});
101121

102122
let avatar: ReactElement | null = null;
@@ -132,6 +152,8 @@ export const Pill: React.FC<PillProps> = ({ type: propType, url, inMessage, room
132152
case PillType.UserMention:
133153
avatar = <PillMemberAvatar shouldShowPillAvatar={shouldShowPillAvatar} member={member} />;
134154
break;
155+
case PillType.Keyword:
156+
break;
135157
default:
136158
return null;
137159
}

src/components/views/messages/TextualBody.tsx

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ Please see LICENSE files in the repository root for full details.
88

99
import React, { createRef, SyntheticEvent, MouseEvent } from "react";
1010
import ReactDOM from "react-dom";
11-
import { MsgType } from "matrix-js-sdk/src/matrix";
11+
import { MsgType, PushRuleKind } from "matrix-js-sdk/src/matrix";
12+
import { globToRegexp } from "matrix-js-sdk/src/utils";
1213

1314
import * as HtmlUtils from "../../../HtmlUtils";
1415
import { formatDate } from "../../../DateUtils";
@@ -39,6 +40,7 @@ import { getParentEventId } from "../../../utils/Reply";
3940
import { EditWysiwygComposer } from "../rooms/wysiwyg_composer";
4041
import { IEventTileOps } from "../rooms/EventTile";
4142
import { MatrixClientPeg } from "../../../MatrixClientPeg";
43+
import { Pill, PillType } from "../elements/Pill";
4244

4345
const MAX_HIGHLIGHT_LENGTH = 4096;
4446

@@ -124,6 +126,16 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
124126
}, 10);
125127
}
126128
}
129+
130+
// Highlight notification keywords using pills
131+
const pushDetails = this.props.mxEvent.getPushDetails();
132+
if (
133+
pushDetails.rule?.enabled &&
134+
pushDetails.rule.kind === PushRuleKind.ContentSpecific &&
135+
pushDetails.rule.pattern
136+
) {
137+
this.pillifyNotificationKeywords([content], this.regExpForKeywordPattern(pushDetails.rule.pattern));
138+
}
127139
}
128140

129141
private addCodeElement(pre: HTMLPreElement): void {
@@ -351,6 +363,55 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
351363
}
352364
}
353365

366+
/**
367+
* Marks the text that activated a push-notification keyword pattern.
368+
*/
369+
private pillifyNotificationKeywords(nodes: ArrayLike<Element>, exp: RegExp): void {
370+
let node: Node | null = nodes[0];
371+
while (node) {
372+
if (node.nodeType === Node.TEXT_NODE) {
373+
const text = node.nodeValue;
374+
if (!text) {
375+
node = node.nextSibling;
376+
continue;
377+
}
378+
const match = text.match(exp);
379+
if (!match || match.length < 3) {
380+
node = node.nextSibling;
381+
continue;
382+
}
383+
const keywordText = match[2];
384+
const idx = match.index! + match[1].length;
385+
const before = text.substring(0, idx);
386+
const after = text.substring(idx + keywordText.length);
387+
388+
const container = document.createElement("span");
389+
const newContent = (
390+
<>
391+
{before}
392+
<Pill text={keywordText} type={PillType.Keyword}>
393+
{keywordText}
394+
</Pill>
395+
{after}
396+
</>
397+
);
398+
ReactDOM.render(newContent, container);
399+
400+
node.parentNode?.replaceChild(container, node);
401+
} else if (node.childNodes && node.childNodes.length) {
402+
this.pillifyNotificationKeywords(node.childNodes as NodeListOf<Element>, exp);
403+
}
404+
405+
node = node.nextSibling;
406+
}
407+
}
408+
409+
private regExpForKeywordPattern(pattern: string): RegExp {
410+
// Reflects the push notification pattern-matching implementation at
411+
// https://github.com/matrix-org/matrix-js-sdk/blob/dbd7d26968b94700827bac525c39afff2c198e61/src/pushprocessor.ts#L570
412+
return new RegExp("(^|\\W)(" + globToRegexp(pattern) + ")(\\W|$)", "i");
413+
}
414+
354415
private findLinks(nodes: ArrayLike<Element>): string[] {
355416
let links: string[] = [];
356417

test/components/views/messages/TextualBody-test.tsx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
77
*/
88

99
import React from "react";
10-
import { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
10+
import { MatrixClient, MatrixEvent, PushRuleKind } from "matrix-js-sdk/src/matrix";
1111
import { mocked, MockedObject } from "jest-mock";
1212
import { render } from "@testing-library/react";
1313

@@ -228,6 +228,23 @@ describe("<TextualBody />", () => {
228228
const content = container.querySelector(".mx_EventTile_body");
229229
expect(content.innerHTML.replace(defaultEvent.getId(), "%event_id%")).toMatchSnapshot();
230230
});
231+
232+
it("should pillify a keyword responsible for triggering a notification", () => {
233+
const ev = mkRoomTextMessage("foo bar baz");
234+
ev.setPushDetails(undefined, {
235+
actions: [],
236+
pattern: "bar",
237+
rule_id: "bar",
238+
default: false,
239+
enabled: true,
240+
kind: PushRuleKind.ContentSpecific,
241+
});
242+
const { container } = getComponent({ mxEvent: ev });
243+
const content = container.querySelector(".mx_EventTile_body");
244+
expect(content.innerHTML).toMatchInlineSnapshot(
245+
`"<span>foo <bdi><span tabindex="0"><span class="mx_Pill mx_KeywordPill"><span class="mx_Pill_text">bar</span></span></span></bdi> baz</span>"`,
246+
);
247+
});
231248
});
232249

233250
describe("renders formatted m.text correctly", () => {

0 commit comments

Comments
 (0)