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; 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 // should be removed, or replaced with helper functions similar
// to util.is_topic_synonym. // to util.is_topic_synonym.
const CHANNEL_SYNONYM = "stream"; const CHANNEL_SYNONYM = "stream";
const CHANNELS_SYNONYM = "streams";
function zephyr_stream_name_match(message: Message & {type: "stream"}, operand: string): boolean { 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)*$/ // 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) { if (operator === CHANNEL_SYNONYM) {
return "channel"; return "channel";
} }
if (operator === CHANNELS_SYNONYM) {
return "channels";
}
return operator; return operator;
} }
@@ -454,7 +459,7 @@ export class Filter {
if (term.operator === "") { if (term.operator === "") {
return term.operand; 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 sign + operator + ":" + Filter.encodeOperand(term.operand.toString());
}); });
return term_strings.join(" "); return term_strings.join(" ");
@@ -469,7 +474,7 @@ export class Filter {
result += operator; result += operator;
if (["is", "has", "in", "streams"].includes(operator)) { if (["is", "has", "in", "channels"].includes(operator)) {
result += "-" + operand; result += "-" + operand;
} }
@@ -479,7 +484,7 @@ export class Filter {
static sorted_term_types(term_types: string[]): string[] { static sorted_term_types(term_types: string[]): string[] {
const levels = [ const levels = [
"in", "in",
"streams-public", "channels-public",
"channel", "channel",
"topic", "topic",
"dm", "dm",
@@ -530,8 +535,8 @@ export class Filter {
switch (operator) { switch (operator) {
case "channel": case "channel":
return verb + CHANNEL_SYNONYM; return verb + CHANNEL_SYNONYM;
case "streams": case "channels":
return verb + "streams"; return verb + CHANNELS_SYNONYM;
case "near": case "near":
return verb + "messages around"; return verb + "messages around";
@@ -743,10 +748,10 @@ export class Filter {
"not-is-resolved", "not-is-resolved",
"in-home", "in-home",
"in-all", "in-all",
"streams-public", "channels-public",
"not-streams-public", "not-channels-public",
"streams-web-public", "channels-web-public",
"not-streams-web-public", "not-channels-web-public",
"near", "near",
]); ]);
@@ -835,7 +840,7 @@ export class Filter {
if (_.isEqual(term_types, ["is-starred"])) { if (_.isEqual(term_types, ["is-starred"])) {
return true; return true;
} }
if (_.isEqual(term_types, ["streams-public"])) { if (_.isEqual(term_types, ["channels-public"])) {
return true; return true;
} }
if (_.isEqual(term_types, ["sender"])) { if (_.isEqual(term_types, ["sender"])) {
@@ -894,8 +899,8 @@ export class Filter {
return "/#narrow/is/starred"; return "/#narrow/is/starred";
case "is-mentioned": case "is-mentioned":
return "/#narrow/is/mentioned"; return "/#narrow/is/mentioned";
case "streams-public": case "channels-public":
return "/#narrow/streams/public"; return "/#narrow/" + CHANNELS_SYNONYM + "/public";
case "dm": case "dm":
return "/#narrow/dm/" + people.emails_to_slug(this.operands("dm").join(",")); return "/#narrow/dm/" + people.emails_to_slug(this.operands("dm").join(","));
case "is-resolved": case "is-resolved":
@@ -1021,7 +1026,7 @@ export class Filter {
return $t({defaultMessage: "All messages"}); return $t({defaultMessage: "All messages"});
case "in-all": case "in-all":
return $t({defaultMessage: "All messages including muted streams"}); return $t({defaultMessage: "All messages including muted streams"});
case "streams-public": case "channels-public":
return $t({defaultMessage: "Messages in all public streams"}); return $t({defaultMessage: "Messages in all public streams"});
case "is-starred": case "is-starred":
return $t({defaultMessage: "Starred messages"}); return $t({defaultMessage: "Starred messages"});
@@ -1057,14 +1062,14 @@ export class Filter {
} }
includes_full_stream_history(): boolean { 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 { is_personal_filter(): boolean {
// Whether the filter filters for user-specific data in the // Whether the filter filters for user-specific data in the
// UserMessage table, such as stars or mentions. // 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. // will never add additional results.
return this.has_operand("is", "mentioned") || this.has_operand("is", "starred"); return this.has_operand("is", "mentioned") || this.has_operand("is", "starred");
} }
@@ -1089,9 +1094,9 @@ export class Filter {
return false; 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. // 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; return false;
} }

View File

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

View File

@@ -94,7 +94,7 @@ export function search_terms_to_hash(terms?: NarrowTerm[]): string {
for (const term of terms) { for (const term of terms) {
// Support legacy tuples. // Support legacy tuples.
const operator = util.canonicalize_stream_synonym(term.operator); const operator = util.canonicalize_stream_synonyms(term.operator);
const operand = term.operand; const operand = term.operand;
const sign = term.negated ? "-" : ""; 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 // 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 // some way with the above logic, and not need to do JSON
// parsing/stringifying here. // 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) { if (!data.narrow) {
/* For the "All messages" feed, this will be the only operator. */ /* 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); const wildcard_match = full_name_matches_wildcard_mention(full_name);
if (wildcard_match && user_id === undefined) { if (wildcard_match && user_id === undefined) {
mention += util.canonicalize_stream_synonym(full_name); mention += util.canonicalize_stream_synonyms(full_name);
} else { } else {
mention += full_name; mention += full_name;
} }

View File

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

View File

@@ -281,10 +281,20 @@ export function is_stream_synonym(text: string): boolean {
return text === "channel"; 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())) { if (is_stream_synonym(text.toLowerCase())) {
return "stream"; return "stream";
} }
if (is_streams_synonym(text.toLowerCase())) {
return "streams";
}
return text; return text;
} }

View File

@@ -214,10 +214,10 @@ test("basics", () => {
terms = [{operator: "streams", operand: "public", negated: true}]; terms = [{operator: "streams", operand: "public", negated: true}];
filter = new Filter(terms); filter = new Filter(terms);
assert.ok(!filter.contains_only_private_messages()); 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.can_mark_messages_read());
assert.ok(filter.supports_collapsing_recipients()); 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.can_apply_locally());
assert.ok(!filter.is_personal_filter()); assert.ok(!filter.is_personal_filter());
assert.ok(!filter.is_conversation_view()); assert.ok(!filter.is_conversation_view());
@@ -225,10 +225,22 @@ test("basics", () => {
terms = [{operator: "streams", operand: "public"}]; terms = [{operator: "streams", operand: "public"}];
filter = new Filter(terms); filter = new Filter(terms);
assert.ok(!filter.contains_only_private_messages()); 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.can_mark_messages_read());
assert.ok(filter.supports_collapsing_recipients()); 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.can_apply_locally());
assert.ok(filter.includes_full_stream_history()); assert.ok(filter.includes_full_stream_history());
assert.ok(!filter.is_personal_filter()); 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("channel", "whatever"), "channel");
assert_term_type(term("dm", "whomever"), "dm"); assert_term_type(term("dm", "whomever"), "dm");
assert_term_type(term("dm", "whomever", true), "not-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(["bogus", "channel", "topic"], ["channel", "topic", "bogus"]);
assert_term_sort(["channel", "topic", "channel"], ["channel", "channel", "topic"]); 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 = [ const terms = [
{operator: "topic", operand: "lunch"}, {operator: "topic", operand: "lunch"},

View File

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