Skip to content

Commit 2488cf2

Browse files
committed
fixing #7748 -- refactoring the message filtering code; first steps
1 parent bed1221 commit 2488cf2

File tree

4 files changed

+151
-116
lines changed

4 files changed

+151
-116
lines changed

src/packages/frontend/chat/chat-log.tsx

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,11 @@ Render all the messages in the chat.
88
*/
99

1010
import { Alert } from "antd";
11-
import { List, Set as immutableSet } from "immutable";
11+
import { Set as immutableSet } from "immutable";
1212
import { MutableRefObject, useEffect, useMemo, useRef } from "react";
1313
import { Virtuoso, VirtuosoHandle } from "react-virtuoso";
1414
import { chatBotName, isChatBot } from "@cocalc/frontend/account/chatbot";
1515
import {
16-
TypedMap,
1716
useActions,
1817
useRedux,
1918
useTypedRedux,
@@ -27,15 +26,14 @@ import {
2726
hoursToTimeIntervalHuman,
2827
parse_hashtags,
2928
plural,
30-
search_match,
31-
search_split,
3229
} from "@cocalc/util/misc";
3330
import { ChatActions, getRootMessage } from "./actions";
3431
import Composing from "./composing";
3532
import Message from "./message";
36-
import { ChatMessageTyped, ChatMessages, MessageHistory, Mode } from "./types";
33+
import type { ChatMessageTyped, ChatMessages, Mode } from "./types";
3734
import { getSelectedHashtagsSearch, newest_content } from "./utils";
3835
import { DivTempHeight } from "@cocalc/frontend/jupyter/cell-list";
36+
import { filterMessages } from "./filter-messages";
3937

4038
interface Props {
4139
project_id: string; // used to render links more effectively
@@ -231,16 +229,6 @@ function isPrevMessageSender(
231229
);
232230
}
233231

234-
// NOTE: I removed search including send name, since that would
235-
// be slower and of questionable value.
236-
function searchMatches(message: ChatMessageTyped, searchTerms): boolean {
237-
const first = message.get("history", List()).first() as
238-
| TypedMap<MessageHistory>
239-
| undefined;
240-
if (first == null) return false;
241-
return search_match(first.get("content", ""), searchTerms);
242-
}
243-
244232
function isThread(messages: ChatMessages, message: ChatMessageTyped) {
245233
if (message.get("reply_to") != null) {
246234
return true;
@@ -282,13 +270,12 @@ export function getSortedDates(
282270
): { dates: string[]; numFolded: number } {
283271
let numFolded = 0;
284272
let m = messages;
285-
if (m == null) return { dates: [], numFolded: 0 };
286-
287-
if (search) {
288-
const searchTerms = search_split(search);
289-
m = m.filter((message) => searchMatches(message, searchTerms));
273+
if (m == null) {
274+
return { dates: [], numFolded: 0 };
290275
}
291276

277+
m = filterMessages({ messages: m, filter: search });
278+
292279
if (typeof filterRecentH === "number" && filterRecentH > 0) {
293280
const now = webapp_client.server_time().getTime();
294281
const cutoff = now - filterRecentH * 1000 * 60 * 60;
@@ -383,7 +370,7 @@ function NotShowing({ num, search, filterRecentH }: NotShowingProps) {
383370
key="not_showing"
384371
message={
385372
<b>
386-
WARNING: Hiding {num} {plural(num, "message")}
373+
WARNING: Hiding {num} {plural(num, "thread")}
387374
{search.trim()
388375
? ` that ${
389376
num != 1 ? "do" : "does"

src/packages/frontend/chat/chatroom.tsx

Lines changed: 78 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -306,83 +306,85 @@ export const ChatRoom: React.FC<Props> = ({ project_id, path, is_visible }) => {
306306

307307
function renderFilterRecent() {
308308
return (
309-
<Select
310-
open={filterRecentOpen}
311-
onDropdownVisibleChange={(v) => setFilterRecentOpen(v)}
312-
value={filterRecentH}
313-
status={filterRecentH > 0 ? "warning" : undefined}
314-
allowClear
315-
onClear={() => {
316-
actions.setState({ filterRecentH: 0 });
317-
setFilterRecentHCustom("");
318-
}}
319-
style={{ width: 120 }}
320-
popupMatchSelectWidth={false}
321-
onSelect={(val: number) => actions.setState({ filterRecentH: val })}
322-
options={[
323-
FILTER_RECENT_NONE,
324-
...[1, 6, 12, 24, 48, 24 * 7, 14 * 24, 28 * 24].map((value) => {
325-
const label = hoursToTimeIntervalHuman(value);
326-
return { value, label };
327-
}),
328-
]}
329-
labelRender={({ label, value }) => {
330-
if (!label) {
331-
if (isValidFilterRecentCustom()) {
332-
value = parseFloat(filterRecentHCustom);
333-
label = hoursToTimeIntervalHuman(value);
334-
} else {
335-
({ label, value } = FILTER_RECENT_NONE);
336-
}
337-
}
338-
return (
339-
<Tooltip
340-
title={
341-
value === 0
342-
? `All messages.`
343-
: `Only messages sent in the past ${label}.`
344-
}
345-
>
346-
{label}
347-
</Tooltip>
348-
);
349-
}}
350-
dropdownRender={(menu) => (
351-
<>
352-
{menu}
353-
<Divider style={{ margin: "8px 0" }} />
354-
<Input
355-
placeholder="Number of hours"
356-
allowClear
357-
value={filterRecentHCustom}
358-
status={
359-
filterRecentHCustom == "" || isValidFilterRecentCustom()
360-
? undefined
361-
: "error"
309+
<Tooltip title="Only show recent threads.">
310+
<Select
311+
open={filterRecentOpen}
312+
onDropdownVisibleChange={(v) => setFilterRecentOpen(v)}
313+
value={filterRecentH}
314+
status={filterRecentH > 0 ? "warning" : undefined}
315+
allowClear
316+
onClear={() => {
317+
actions.setState({ filterRecentH: 0 });
318+
setFilterRecentHCustom("");
319+
}}
320+
style={{ width: 120 }}
321+
popupMatchSelectWidth={false}
322+
onSelect={(val: number) => actions.setState({ filterRecentH: val })}
323+
options={[
324+
FILTER_RECENT_NONE,
325+
...[1, 6, 12, 24, 48, 24 * 7, 14 * 24, 28 * 24].map((value) => {
326+
const label = hoursToTimeIntervalHuman(value);
327+
return { value, label };
328+
}),
329+
]}
330+
labelRender={({ label, value }) => {
331+
if (!label) {
332+
if (isValidFilterRecentCustom()) {
333+
value = parseFloat(filterRecentHCustom);
334+
label = hoursToTimeIntervalHuman(value);
335+
} else {
336+
({ label, value } = FILTER_RECENT_NONE);
362337
}
363-
onChange={debounce(
364-
(e: React.ChangeEvent<HTMLInputElement>) => {
365-
const v = e.target.value;
366-
setFilterRecentHCustom(v);
367-
const val = parseFloat(v);
368-
if (isFinite(val) && val >= 0) {
369-
actions.setState({ filterRecentH: val });
370-
} else if (v == "") {
371-
actions.setState({
372-
filterRecentH: FILTER_RECENT_NONE.value,
373-
});
374-
}
375-
},
376-
150,
377-
{ leading: true, trailing: true },
378-
)}
379-
onKeyDown={(e) => e.stopPropagation()}
380-
onPressEnter={() => setFilterRecentOpen(false)}
381-
addonAfter={<span style={{ paddingLeft: "5px" }}>hours</span>}
382-
/>
383-
</>
384-
)}
385-
/>
338+
}
339+
return (
340+
<Tooltip
341+
title={
342+
value === 0
343+
? `All messages.`
344+
: `Only messages sent in the past ${label}.`
345+
}
346+
>
347+
{label}
348+
</Tooltip>
349+
);
350+
}}
351+
dropdownRender={(menu) => (
352+
<>
353+
{menu}
354+
<Divider style={{ margin: "8px 0" }} />
355+
<Input
356+
placeholder="Number of hours"
357+
allowClear
358+
value={filterRecentHCustom}
359+
status={
360+
filterRecentHCustom == "" || isValidFilterRecentCustom()
361+
? undefined
362+
: "error"
363+
}
364+
onChange={debounce(
365+
(e: React.ChangeEvent<HTMLInputElement>) => {
366+
const v = e.target.value;
367+
setFilterRecentHCustom(v);
368+
const val = parseFloat(v);
369+
if (isFinite(val) && val >= 0) {
370+
actions.setState({ filterRecentH: val });
371+
} else if (v == "") {
372+
actions.setState({
373+
filterRecentH: FILTER_RECENT_NONE.value,
374+
});
375+
}
376+
},
377+
150,
378+
{ leading: true, trailing: true },
379+
)}
380+
onKeyDown={(e) => e.stopPropagation()}
381+
onPressEnter={() => setFilterRecentOpen(false)}
382+
addonAfter={<span style={{ paddingLeft: "5px" }}>hours</span>}
383+
/>
384+
</>
385+
)}
386+
/>
387+
</Tooltip>
386388
);
387389
}
388390

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
3+
Find all messages that match a given collection of filters.
4+
5+
*/
6+
7+
import type { ChatMessages, ChatMessageTyped, MessageHistory } from "./types";
8+
import { search_match, search_split } from "@cocalc/util/misc";
9+
import { List } from "immutable";
10+
import type { TypedMap } from "@cocalc/frontend/app-framework";
11+
12+
export function filterMessages({
13+
messages,
14+
filter,
15+
}: {
16+
// the messages to filter down
17+
messages: ChatMessages;
18+
filter?: string;
19+
}) {
20+
if (filter) {
21+
const searchTerms = search_split(filter);
22+
messages = messages.filter((message) =>
23+
searchMatches(message, searchTerms),
24+
);
25+
}
26+
return messages;
27+
}
28+
29+
// NOTE: I removed search including send name, since that would
30+
// be slower and of questionable value.
31+
export function searchMatches(message: ChatMessageTyped, searchTerms): boolean {
32+
const first = message.get("history", List()).first() as
33+
| TypedMap<MessageHistory>
34+
| undefined;
35+
if (first == null) return false;
36+
return search_match(first.get("content", ""), searchTerms);
37+
}

src/packages/frontend/chat/filter.tsx

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Input } from "antd";
1+
import { Input, Tooltip } from "antd";
22
import { useCallback, useEffect, useMemo, useState } from "react";
33
import { debounce } from "lodash";
44

@@ -23,23 +23,32 @@ export default function Filter({ actions, search, style }) {
2323
);
2424

2525
return (
26-
<Input.Search
27-
style={style}
28-
allowClear
29-
placeholder={"Filter messages (use /re/ for regexp)..."}
30-
value={value}
31-
onChange={(e) => {
32-
setValue(e.target.value ?? "");
33-
debouncedSearch(e.target.value ?? "");
34-
}}
35-
onPressEnter={() => {
36-
debouncedSearch.cancel();
37-
doSearch(value);
38-
}}
39-
onSearch={() => {
40-
debouncedSearch.cancel();
41-
doSearch(value);
42-
}}
43-
/>
26+
<Tooltip
27+
title={
28+
!value
29+
? "Show only message threads matching this search. Use /re/ for a regular expression, quotes and dash to negate."
30+
: undefined
31+
}
32+
>
33+
<Input.Search
34+
style={style}
35+
allowClear
36+
placeholder={"Filter threads..."}
37+
value={value}
38+
onChange={(e) => {
39+
setValue(e.target.value ?? "");
40+
debouncedSearch(e.target.value ?? "");
41+
}}
42+
onPressEnter={() => {
43+
debouncedSearch.cancel();
44+
doSearch(value);
45+
}}
46+
onSearch={() => {
47+
debouncedSearch.cancel();
48+
doSearch(value);
49+
}}
50+
/>
51+
</Tooltip>
4452
);
4553
}
54+

0 commit comments

Comments
 (0)