filter: Use "channels" operator for the Filter class.

Update the Filter class to use "channels" as the canoncial operator
for public stream searches and web-public message fetch narrows,
but keep using "streams" for user-facing text and URLs.

When searching, "channels" does not create any suggestions until
a colon is added and then it shows suggestions with "streams". And
when a search string with channel as an operator is entered, then
it is replaced by "streams" as well.

Part of stream to channel rename project.
This commit is contained in:
Lauryn Menard 2024-04-12 13:45:46 +02:00 committed by Tim Abbott
parent 1e7c5b38f8
commit a446033f32
9 changed files with 82 additions and 30 deletions

View File

@ -56,10 +56,11 @@ type Part =
operand: string;
};
// TODO: When "stream" is renamed to "channel", this placeholder
// TODO: When "stream" is renamed to "channel", these placeholders
// should be removed, or replaced with helper functions similar
// to util.is_topic_synonym.
const CHANNEL_SYNONYM = "stream";
const CHANNELS_SYNONYM = "streams";
function zephyr_stream_name_match(message: Message & {type: "stream"}, operand: string): boolean {
// Zephyr users expect narrowing to "social" to also show messages to /^(un)*social(.d)*$/
@ -278,6 +279,10 @@ export class Filter {
if (operator === CHANNEL_SYNONYM) {
return "channel";
}
if (operator === CHANNELS_SYNONYM) {
return "channels";
}
return operator;
}
@ -454,7 +459,7 @@ export class Filter {
if (term.operator === "") {
return term.operand;
}
const operator = util.canonicalize_stream_synonym(term.operator);
const operator = util.canonicalize_stream_synonyms(term.operator);
return sign + operator + ":" + Filter.encodeOperand(term.operand.toString());
});
return term_strings.join(" ");
@ -469,7 +474,7 @@ export class Filter {
result += operator;
if (["is", "has", "in", "streams"].includes(operator)) {
if (["is", "has", "in", "channels"].includes(operator)) {
result += "-" + operand;
}
@ -479,7 +484,7 @@ export class Filter {
static sorted_term_types(term_types: string[]): string[] {
const levels = [
"in",
"streams-public",
"channels-public",
"channel",
"topic",
"dm",
@ -530,8 +535,8 @@ export class Filter {
switch (operator) {
case "channel":
return verb + CHANNEL_SYNONYM;
case "streams":
return verb + "streams";
case "channels":
return verb + CHANNELS_SYNONYM;
case "near":
return verb + "messages around";
@ -743,10 +748,10 @@ export class Filter {
"not-is-resolved",
"in-home",
"in-all",
"streams-public",
"not-streams-public",
"streams-web-public",
"not-streams-web-public",
"channels-public",
"not-channels-public",
"channels-web-public",
"not-channels-web-public",
"near",
]);
@ -835,7 +840,7 @@ export class Filter {
if (_.isEqual(term_types, ["is-starred"])) {
return true;
}
if (_.isEqual(term_types, ["streams-public"])) {
if (_.isEqual(term_types, ["channels-public"])) {
return true;
}
if (_.isEqual(term_types, ["sender"])) {
@ -894,8 +899,8 @@ export class Filter {
return "/#narrow/is/starred";
case "is-mentioned":
return "/#narrow/is/mentioned";
case "streams-public":
return "/#narrow/streams/public";
case "channels-public":
return "/#narrow/" + CHANNELS_SYNONYM + "/public";
case "dm":
return "/#narrow/dm/" + people.emails_to_slug(this.operands("dm").join(","));
case "is-resolved":
@ -1021,7 +1026,7 @@ export class Filter {
return $t({defaultMessage: "All messages"});
case "in-all":
return $t({defaultMessage: "All messages including muted streams"});
case "streams-public":
case "channels-public":
return $t({defaultMessage: "Messages in all public streams"});
case "is-starred":
return $t({defaultMessage: "Starred messages"});
@ -1057,14 +1062,14 @@ export class Filter {
}
includes_full_stream_history(): boolean {
return this.has_operator("channel") || this.has_operator("streams");
return this.has_operator("channel") || this.has_operator("channels");
}
is_personal_filter(): boolean {
// Whether the filter filters for user-specific data in the
// UserMessage table, such as stars or mentions.
//
// Such filters should not advertise "streams:public" as it
// Such filters should not advertise "channels:public" as it
// will never add additional results.
return this.has_operand("is", "mentioned") || this.has_operand("is", "starred");
}
@ -1089,9 +1094,9 @@ export class Filter {
return false;
}
// TODO: It's not clear why `streams:` filters would not be
// TODO: It's not clear why `channels:` filters would not be
// applicable locally.
if (this.has_operator("streams") || this.has_negated_operand("streams", "public")) {
if (this.has_operator("channels") || this.has_negated_operand("channels", "public")) {
return false;
}

View File

@ -92,6 +92,7 @@ export function is_create_new_stream_narrow(): boolean {
}
export const allowed_web_public_narrows = [
"channels",
"channel",
"streams",
"stream",

View File

@ -94,7 +94,7 @@ export function search_terms_to_hash(terms?: NarrowTerm[]): string {
for (const term of terms) {
// Support legacy tuples.
const operator = util.canonicalize_stream_synonym(term.operator);
const operator = util.canonicalize_stream_synonyms(term.operator);
const operand = term.operand;
const sign = term.negated ? "-" : "";

View File

@ -301,7 +301,7 @@ export function load_messages(opts, attempt = 1) {
// This is a bit of a hack; ideally we'd unify this logic in
// some way with the above logic, and not need to do JSON
// parsing/stringifying here.
const web_public_narrow = {negated: false, operator: "streams", operand: "web-public"};
const web_public_narrow = {negated: false, operator: "channels", operand: "web-public"};
if (!data.narrow) {
/* For the "All messages" feed, this will be the only operator. */

View File

@ -1362,7 +1362,7 @@ export function get_mention_syntax(full_name: string, user_id?: number, silent =
}
const wildcard_match = full_name_matches_wildcard_mention(full_name);
if (wildcard_match && user_id === undefined) {
mention += util.canonicalize_stream_synonym(full_name);
mention += util.canonicalize_stream_synonyms(full_name);
} else {
mention += full_name;
}

View File

@ -123,7 +123,7 @@ function get_stream_suggestions(last: NarrowTerm, terms: NarrowTerm[]): Suggesti
const valid = ["channel", "search", ""];
const incompatible_patterns = [
{operator: "channel"},
{operator: "streams"},
{operator: "channels"},
{operator: "is", operand: "dm"},
{operator: "dm"},
{operator: "dm-including"},
@ -557,7 +557,7 @@ function get_streams_filter_suggestions(last: NarrowTerm, terms: NarrowTerm[]):
{operator: "dm-including"},
{operator: "dm"},
{operator: "in"},
{operator: "streams"},
{operator: "channels"},
],
},
];

View File

@ -281,10 +281,20 @@ export function is_stream_synonym(text: string): boolean {
return text === "channel";
}
export function canonicalize_stream_synonym(text: string): string {
export function is_streams_synonym(text: string): boolean {
return text === "channels";
}
// For parts of the codebase that have been converted to use
// channel/channels internally, this is used to convert those
// back into stream/streams for external presentation.
export function canonicalize_stream_synonyms(text: string): string {
if (is_stream_synonym(text.toLowerCase())) {
return "stream";
}
if (is_streams_synonym(text.toLowerCase())) {
return "streams";
}
return text;
}

View File

@ -214,10 +214,10 @@ test("basics", () => {
terms = [{operator: "streams", operand: "public", negated: true}];
filter = new Filter(terms);
assert.ok(!filter.contains_only_private_messages());
assert.ok(!filter.has_operator("streams"));
assert.ok(!filter.has_operator("channels"));
assert.ok(!filter.can_mark_messages_read());
assert.ok(filter.supports_collapsing_recipients());
assert.ok(filter.has_negated_operand("streams", "public"));
assert.ok(filter.has_negated_operand("channels", "public"));
assert.ok(!filter.can_apply_locally());
assert.ok(!filter.is_personal_filter());
assert.ok(!filter.is_conversation_view());
@ -225,10 +225,22 @@ test("basics", () => {
terms = [{operator: "streams", operand: "public"}];
filter = new Filter(terms);
assert.ok(!filter.contains_only_private_messages());
assert.ok(filter.has_operator("streams"));
assert.ok(filter.has_operator("channels"));
assert.ok(!filter.can_mark_messages_read());
assert.ok(filter.supports_collapsing_recipients());
assert.ok(!filter.has_negated_operand("streams", "public"));
assert.ok(!filter.has_negated_operand("channels", "public"));
assert.ok(!filter.can_apply_locally());
assert.ok(filter.includes_full_stream_history());
assert.ok(!filter.is_personal_filter());
assert.ok(!filter.is_conversation_view());
terms = [{operator: "channels", operand: "public"}];
filter = new Filter(terms);
assert.ok(!filter.contains_only_private_messages());
assert.ok(filter.has_operator("channels"));
assert.ok(!filter.can_mark_messages_read());
assert.ok(filter.supports_collapsing_recipients());
assert.ok(!filter.has_negated_operand("channels", "public"));
assert.ok(!filter.can_apply_locally());
assert.ok(filter.includes_full_stream_history());
assert.ok(!filter.is_personal_filter());
@ -1442,7 +1454,7 @@ test("term_type", () => {
};
}
assert_term_type(term("streams", "public"), "streams-public");
assert_term_type(term("channels", "public"), "channels-public");
assert_term_type(term("channel", "whatever"), "channel");
assert_term_type(term("dm", "whomever"), "dm");
assert_term_type(term("dm", "whomever", true), "not-dm");
@ -1466,7 +1478,7 @@ test("term_type", () => {
assert_term_sort(["bogus", "channel", "topic"], ["channel", "topic", "bogus"]);
assert_term_sort(["channel", "topic", "channel"], ["channel", "channel", "topic"]);
assert_term_sort(["search", "streams-public"], ["streams-public", "search"]);
assert_term_sort(["search", "channels-public"], ["channels-public", "search"]);
const terms = [
{operator: "topic", operand: "lunch"},

View File

@ -33,6 +33,7 @@ const hash_util = zrequire("hash_util");
const hashchange = zrequire("hashchange");
const narrow = zrequire("../src/narrow");
const stream_data = zrequire("stream_data");
const {Filter} = zrequire("../src/filter");
run_test("terms_round_trip", () => {
let terms;
@ -78,6 +79,29 @@ run_test("terms_round_trip", () => {
assert.deepEqual(narrow, [{operator: "stream", operand: "Florida, USA", negated: false}]);
});
run_test("stream_to_channel_rename", () => {
let terms;
let hash;
let narrow;
let filter;
terms = [{operator: "channel", operand: "devel"}];
hash = hash_util.search_terms_to_hash(terms);
assert.equal(hash, "#narrow/stream/devel");
narrow = hash_util.parse_narrow(hash.split("/"));
assert.deepEqual(narrow, [{operator: "stream", operand: "devel", negated: false}]);
filter = new Filter(narrow);
assert.deepEqual(filter.terms(), [{operator: "channel", operand: "devel", negated: false}]);
terms = [{operator: "channels", operand: "public"}];
hash = hash_util.search_terms_to_hash(terms);
assert.equal(hash, "#narrow/streams/public");
narrow = hash_util.parse_narrow(hash.split("/"));
assert.deepEqual(narrow, [{operator: "streams", operand: "public", negated: false}]);
filter = new Filter(narrow);
assert.deepEqual(filter.terms(), [{operator: "channels", operand: "public", negated: false}]);
});
run_test("terms_trailing_slash", () => {
const hash = "#narrow/stream/devel/topic/algol/";
const narrow = hash_util.parse_narrow(hash.split("/"));