typeahead: Pass query to updater instead of using hacky this.

This commit is contained in:
evykassirer 2024-03-24 20:42:57 -07:00 committed by Tim Abbott
parent f0578c318c
commit b4299d99fd
5 changed files with 80 additions and 75 deletions

View File

@ -196,6 +196,7 @@ class Typeahead<ItemType extends string | object> {
highlighter_html: (item: ItemType, query: string) => string | undefined; highlighter_html: (item: ItemType, query: string) => string | undefined;
updater: ( updater: (
item: ItemType, item: ItemType,
query: string,
event?: JQuery.ClickEvent | JQuery.KeyUpEvent | JQuery.KeyDownEvent, event?: JQuery.ClickEvent | JQuery.KeyUpEvent | JQuery.KeyDownEvent,
) => string | undefined; ) => string | undefined;
$container: JQuery; $container: JQuery;
@ -270,12 +271,14 @@ class Typeahead<ItemType extends string | object> {
select(e?: JQuery.ClickEvent | JQuery.KeyUpEvent | JQuery.KeyDownEvent): this { select(e?: JQuery.ClickEvent | JQuery.KeyUpEvent | JQuery.KeyDownEvent): this {
const val = this.$menu.find(".active").data("typeahead-value"); const val = this.$menu.find(".active").data("typeahead-value");
if (this.input_element.type === "contenteditable") { if (this.input_element.type === "contenteditable") {
this.input_element.$element.text(this.updater(val, e) ?? "").trigger("change"); this.input_element.$element
.text(this.updater(val, this.query, e) ?? "")
.trigger("change");
// Empty text after the change event handler // Empty text after the change event handler
// converts the input text to html elements. // converts the input text to html elements.
this.input_element.$element.text(""); this.input_element.$element.text("");
} else { } else {
const after_text = this.updater(val, e) ?? ""; const after_text = this.updater(val, this.query, e) ?? "";
const element_val = this.input_element.$element.val(); const element_val = this.input_element.$element.val();
assert(element_val !== undefined); assert(element_val !== undefined);
const [from, to_before, to_after] = get_string_diff(element_val, after_text); const [from, to_before, to_after] = get_string_diff(element_val, after_text);
@ -711,6 +714,7 @@ type TypeaheadOptions<ItemType> = {
trigger_selection?: (event: JQuery.KeyDownEvent) => boolean; trigger_selection?: (event: JQuery.KeyDownEvent) => boolean;
updater: ( updater: (
item: ItemType, item: ItemType,
query: string,
event?: JQuery.ClickEvent | JQuery.KeyUpEvent | JQuery.KeyDownEvent, event?: JQuery.ClickEvent | JQuery.KeyUpEvent | JQuery.KeyDownEvent,
) => string | undefined; ) => string | undefined;
}; };

View File

@ -872,8 +872,8 @@ export function content_highlighter_html(item) {
} }
} }
export function content_typeahead_selected(item, event) { export function content_typeahead_selected(item, query, event) {
const pieces = split_at_cursor(this.query, this.input_element.$element); const pieces = split_at_cursor(query, this.input_element.$element);
let beginning = pieces[0]; let beginning = pieces[0];
let rest = pieces[1]; let rest = pieces[1];
const $textbox = this.input_element.$element; const $textbox = this.input_element.$element;

View File

@ -122,8 +122,8 @@ export function set_up($input, pills, opts) {
max_num_items: undefined, max_num_items: undefined,
}); });
}, },
updater(item) { updater(item, query) {
if (include_streams(this.query)) { if (include_streams(query)) {
stream_pill.append_stream(item, pills); stream_pill.append_stream(item, pills);
} else if (include_user_groups && user_groups.is_user_group(item)) { } else if (include_user_groups && user_groups.is_user_group(item)) {
user_group_pill.append_user_group(item, pills); user_group_pill.append_user_group(item, pills);

View File

@ -438,11 +438,12 @@ test("content_typeahead_selected", ({override}) => {
}; };
let caret_called1 = false; let caret_called1 = false;
let caret_called2 = false; let caret_called2 = false;
let query;
fake_this.input_element.$element.caret = function (...args) { fake_this.input_element.$element.caret = function (...args) {
if (args.length === 0) { if (args.length === 0) {
// .caret() used in split_at_cursor // .caret() used in split_at_cursor
caret_called1 = true; caret_called1 = true;
return fake_this.query.length; return query.length;
} }
const [arg1, arg2] = args; const [arg1, arg2] = args;
// .caret() used in setTimeout // .caret() used in setTimeout
@ -463,25 +464,25 @@ test("content_typeahead_selected", ({override}) => {
// emoji // emoji
ct.get_or_set_completing_for_tests("emoji"); ct.get_or_set_completing_for_tests("emoji");
fake_this.query = ":octo"; query = ":octo";
ct.get_or_set_token_for_testing("octo"); ct.get_or_set_token_for_testing("octo");
const item = { const item = {
emoji_name: "octopus", emoji_name: "octopus",
}; };
let actual_value = ct.content_typeahead_selected.call(fake_this, item); let actual_value = ct.content_typeahead_selected.call(fake_this, item, query);
let expected_value = ":octopus: "; let expected_value = ":octopus: ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
fake_this.query = " :octo"; query = " :octo";
ct.get_or_set_token_for_testing("octo"); ct.get_or_set_token_for_testing("octo");
actual_value = ct.content_typeahead_selected.call(fake_this, item); actual_value = ct.content_typeahead_selected.call(fake_this, item, query);
expected_value = " :octopus: "; expected_value = " :octopus: ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
fake_this.query = "{:octo"; query = "{:octo";
ct.get_or_set_token_for_testing("octo"); ct.get_or_set_token_for_testing("octo");
actual_value = ct.content_typeahead_selected.call(fake_this, item); actual_value = ct.content_typeahead_selected.call(fake_this, item, query);
expected_value = "{ :octopus: "; expected_value = "{ :octopus: ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
@ -495,9 +496,9 @@ test("content_typeahead_selected", ({override}) => {
(mention_text) => mention_text, (mention_text) => mention_text,
); );
fake_this.query = "@**Mark Tw"; query = "@**Mark Tw";
ct.get_or_set_token_for_testing("Mark Tw"); ct.get_or_set_token_for_testing("Mark Tw");
actual_value = ct.content_typeahead_selected.call(fake_this, twin1); actual_value = ct.content_typeahead_selected.call(fake_this, twin1, query);
expected_value = "@**Mark Twin|105** "; expected_value = "@**Mark Twin|105** ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
@ -507,118 +508,118 @@ test("content_typeahead_selected", ({override}) => {
warned_for_mention = true; warned_for_mention = true;
}); });
fake_this.query = "@oth"; query = "@oth";
ct.get_or_set_token_for_testing("oth"); ct.get_or_set_token_for_testing("oth");
actual_value = ct.content_typeahead_selected.call(fake_this, othello); actual_value = ct.content_typeahead_selected.call(fake_this, othello, query);
expected_value = "@**Othello, the Moor of Venice** "; expected_value = "@**Othello, the Moor of Venice** ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
assert.ok(warned_for_mention); assert.ok(warned_for_mention);
fake_this.query = "Hello @oth"; query = "Hello @oth";
ct.get_or_set_token_for_testing("oth"); ct.get_or_set_token_for_testing("oth");
actual_value = ct.content_typeahead_selected.call(fake_this, othello); actual_value = ct.content_typeahead_selected.call(fake_this, othello, query);
expected_value = "Hello @**Othello, the Moor of Venice** "; expected_value = "Hello @**Othello, the Moor of Venice** ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
fake_this.query = "@**oth"; query = "@**oth";
ct.get_or_set_token_for_testing("oth"); ct.get_or_set_token_for_testing("oth");
actual_value = ct.content_typeahead_selected.call(fake_this, othello); actual_value = ct.content_typeahead_selected.call(fake_this, othello, query);
expected_value = "@**Othello, the Moor of Venice** "; expected_value = "@**Othello, the Moor of Venice** ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
fake_this.query = "@*oth"; query = "@*oth";
ct.get_or_set_token_for_testing("oth"); ct.get_or_set_token_for_testing("oth");
actual_value = ct.content_typeahead_selected.call(fake_this, othello); actual_value = ct.content_typeahead_selected.call(fake_this, othello, query);
expected_value = "@**Othello, the Moor of Venice** "; expected_value = "@**Othello, the Moor of Venice** ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
fake_this.query = "@back"; query = "@back";
ct.get_or_set_token_for_testing("back"); ct.get_or_set_token_for_testing("back");
with_overrides(({disallow}) => { with_overrides(({disallow}) => {
disallow(compose_validate, "warn_if_mentioning_unsubscribed_user"); disallow(compose_validate, "warn_if_mentioning_unsubscribed_user");
actual_value = ct.content_typeahead_selected.call(fake_this, backend); actual_value = ct.content_typeahead_selected.call(fake_this, backend, query);
}); });
expected_value = "@*Backend* "; expected_value = "@*Backend* ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
fake_this.query = "@*back"; query = "@*back";
ct.get_or_set_token_for_testing("back"); ct.get_or_set_token_for_testing("back");
actual_value = ct.content_typeahead_selected.call(fake_this, backend); actual_value = ct.content_typeahead_selected.call(fake_this, backend, query);
expected_value = "@*Backend* "; expected_value = "@*Backend* ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
// silent mention // silent mention
ct.get_or_set_completing_for_tests("silent_mention"); ct.get_or_set_completing_for_tests("silent_mention");
fake_this.query = "@_kin"; query = "@_kin";
ct.get_or_set_token_for_testing("kin"); ct.get_or_set_token_for_testing("kin");
with_overrides(({disallow}) => { with_overrides(({disallow}) => {
disallow(compose_validate, "warn_if_mentioning_unsubscribed_user"); disallow(compose_validate, "warn_if_mentioning_unsubscribed_user");
actual_value = ct.content_typeahead_selected.call(fake_this, hamlet); actual_value = ct.content_typeahead_selected.call(fake_this, hamlet, query);
}); });
expected_value = "@_**King Hamlet** "; expected_value = "@_**King Hamlet** ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
fake_this.query = "Hello @_kin"; query = "Hello @_kin";
ct.get_or_set_token_for_testing("kin"); ct.get_or_set_token_for_testing("kin");
actual_value = ct.content_typeahead_selected.call(fake_this, hamlet); actual_value = ct.content_typeahead_selected.call(fake_this, hamlet, query);
expected_value = "Hello @_**King Hamlet** "; expected_value = "Hello @_**King Hamlet** ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
fake_this.query = "@_*kin"; query = "@_*kin";
ct.get_or_set_token_for_testing("kin"); ct.get_or_set_token_for_testing("kin");
actual_value = ct.content_typeahead_selected.call(fake_this, hamlet); actual_value = ct.content_typeahead_selected.call(fake_this, hamlet, query);
expected_value = "@_**King Hamlet** "; expected_value = "@_**King Hamlet** ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
fake_this.query = "@_**kin"; query = "@_**kin";
ct.get_or_set_token_for_testing("kin"); ct.get_or_set_token_for_testing("kin");
actual_value = ct.content_typeahead_selected.call(fake_this, hamlet); actual_value = ct.content_typeahead_selected.call(fake_this, hamlet, query);
expected_value = "@_**King Hamlet** "; expected_value = "@_**King Hamlet** ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
fake_this.query = "@_back"; query = "@_back";
ct.get_or_set_token_for_testing("back"); ct.get_or_set_token_for_testing("back");
with_overrides(({disallow}) => { with_overrides(({disallow}) => {
disallow(compose_validate, "warn_if_mentioning_unsubscribed_user"); disallow(compose_validate, "warn_if_mentioning_unsubscribed_user");
actual_value = ct.content_typeahead_selected.call(fake_this, backend); actual_value = ct.content_typeahead_selected.call(fake_this, backend, query);
}); });
expected_value = "@_*Backend* "; expected_value = "@_*Backend* ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
fake_this.query = "@_*back"; query = "@_*back";
ct.get_or_set_token_for_testing("back"); ct.get_or_set_token_for_testing("back");
actual_value = ct.content_typeahead_selected.call(fake_this, backend); actual_value = ct.content_typeahead_selected.call(fake_this, backend, query);
expected_value = "@_*Backend* "; expected_value = "@_*Backend* ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
fake_this.query = "/m"; query = "/m";
ct.get_or_set_completing_for_tests("slash"); ct.get_or_set_completing_for_tests("slash");
actual_value = ct.content_typeahead_selected.call(fake_this, me_slash); actual_value = ct.content_typeahead_selected.call(fake_this, me_slash, query);
expected_value = "/me translated: is …"; expected_value = "/me translated: is …";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
fake_this.query = "/da"; query = "/da";
ct.get_or_set_completing_for_tests("slash"); ct.get_or_set_completing_for_tests("slash");
actual_value = ct.content_typeahead_selected.call(fake_this, dark_slash); actual_value = ct.content_typeahead_selected.call(fake_this, dark_slash, query);
expected_value = "/dark "; expected_value = "/dark ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
fake_this.query = "/ni"; query = "/ni";
ct.get_or_set_completing_for_tests("slash"); ct.get_or_set_completing_for_tests("slash");
actual_value = ct.content_typeahead_selected.call(fake_this, dark_slash); actual_value = ct.content_typeahead_selected.call(fake_this, dark_slash, query);
expected_value = "/dark "; expected_value = "/dark ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
fake_this.query = "/li"; query = "/li";
ct.get_or_set_completing_for_tests("slash"); ct.get_or_set_completing_for_tests("slash");
actual_value = ct.content_typeahead_selected.call(fake_this, light_slash); actual_value = ct.content_typeahead_selected.call(fake_this, light_slash, query);
expected_value = "/light "; expected_value = "/light ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
fake_this.query = "/da"; query = "/da";
ct.get_or_set_completing_for_tests("slash"); ct.get_or_set_completing_for_tests("slash");
actual_value = ct.content_typeahead_selected.call(fake_this, light_slash); actual_value = ct.content_typeahead_selected.call(fake_this, light_slash, query);
expected_value = "/light "; expected_value = "/light ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
@ -630,79 +631,79 @@ test("content_typeahead_selected", ({override}) => {
warned_for_stream_link = true; warned_for_stream_link = true;
}); });
fake_this.query = "#swed"; query = "#swed";
ct.get_or_set_token_for_testing("swed"); ct.get_or_set_token_for_testing("swed");
actual_value = ct.content_typeahead_selected.call(fake_this, sweden_stream); actual_value = ct.content_typeahead_selected.call(fake_this, sweden_stream, query);
expected_value = "#**Sweden** "; expected_value = "#**Sweden** ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
fake_this.query = "Hello #swed"; query = "Hello #swed";
ct.get_or_set_token_for_testing("swed"); ct.get_or_set_token_for_testing("swed");
actual_value = ct.content_typeahead_selected.call(fake_this, sweden_stream); actual_value = ct.content_typeahead_selected.call(fake_this, sweden_stream, query);
expected_value = "Hello #**Sweden** "; expected_value = "Hello #**Sweden** ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
fake_this.query = "#**swed"; query = "#**swed";
ct.get_or_set_token_for_testing("swed"); ct.get_or_set_token_for_testing("swed");
actual_value = ct.content_typeahead_selected.call(fake_this, sweden_stream); actual_value = ct.content_typeahead_selected.call(fake_this, sweden_stream, query);
expected_value = "#**Sweden** "; expected_value = "#**Sweden** ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
// topic_list // topic_list
ct.get_or_set_completing_for_tests("topic_list"); ct.get_or_set_completing_for_tests("topic_list");
fake_this.query = "Hello #**Sweden>test"; query = "Hello #**Sweden>test";
ct.get_or_set_token_for_testing("test"); ct.get_or_set_token_for_testing("test");
actual_value = ct.content_typeahead_selected.call(fake_this, "testing"); actual_value = ct.content_typeahead_selected.call(fake_this, "testing", query);
expected_value = "Hello #**Sweden>testing** "; expected_value = "Hello #**Sweden>testing** ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
fake_this.query = "Hello #**Sweden>"; query = "Hello #**Sweden>";
ct.get_or_set_token_for_testing(""); ct.get_or_set_token_for_testing("");
actual_value = ct.content_typeahead_selected.call(fake_this, "testing"); actual_value = ct.content_typeahead_selected.call(fake_this, "testing", query);
expected_value = "Hello #**Sweden>testing** "; expected_value = "Hello #**Sweden>testing** ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
// syntax // syntax
ct.get_or_set_completing_for_tests("syntax"); ct.get_or_set_completing_for_tests("syntax");
fake_this.query = "~~~p"; query = "~~~p";
ct.get_or_set_token_for_testing("p"); ct.get_or_set_token_for_testing("p");
actual_value = ct.content_typeahead_selected.call(fake_this, "python"); actual_value = ct.content_typeahead_selected.call(fake_this, "python", query);
expected_value = "~~~python\n\n~~~"; expected_value = "~~~python\n\n~~~";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
fake_this.query = "Hello ~~~p"; query = "Hello ~~~p";
ct.get_or_set_token_for_testing("p"); ct.get_or_set_token_for_testing("p");
actual_value = ct.content_typeahead_selected.call(fake_this, "python"); actual_value = ct.content_typeahead_selected.call(fake_this, "python", query);
expected_value = "Hello ~~~python\n\n~~~"; expected_value = "Hello ~~~python\n\n~~~";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
fake_this.query = "```p"; query = "```p";
ct.get_or_set_token_for_testing("p"); ct.get_or_set_token_for_testing("p");
actual_value = ct.content_typeahead_selected.call(fake_this, "python"); actual_value = ct.content_typeahead_selected.call(fake_this, "python", query);
expected_value = "```python\n\n```"; expected_value = "```python\n\n```";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
fake_this.query = "```spo"; query = "```spo";
ct.get_or_set_token_for_testing("spo"); ct.get_or_set_token_for_testing("spo");
actual_value = ct.content_typeahead_selected.call(fake_this, "spoiler"); actual_value = ct.content_typeahead_selected.call(fake_this, "spoiler", query);
expected_value = "```spoiler translated: Header\n\n```"; expected_value = "```spoiler translated: Header\n\n```";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
// Test special case to not close code blocks if there is text afterward // Test special case to not close code blocks if there is text afterward
fake_this.query = "```p\nsome existing code"; query = "```p\nsome existing code";
ct.get_or_set_token_for_testing("p"); ct.get_or_set_token_for_testing("p");
fake_this.input_element.$element.caret = () => 4; // Put cursor right after ```p fake_this.input_element.$element.caret = () => 4; // Put cursor right after ```p
actual_value = ct.content_typeahead_selected.call(fake_this, "python"); actual_value = ct.content_typeahead_selected.call(fake_this, "python", query);
expected_value = "```python\nsome existing code"; expected_value = "```python\nsome existing code";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
ct.get_or_set_completing_for_tests("something-else"); ct.get_or_set_completing_for_tests("something-else");
fake_this.query = "foo"; query = "foo";
actual_value = ct.content_typeahead_selected.call(fake_this, {}); actual_value = ct.content_typeahead_selected.call(fake_this, {}, query);
expected_value = fake_this.query; expected_value = query;
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
assert.ok(caret_called1); assert.ok(caret_called1);

View File

@ -305,11 +305,11 @@ run_test("set_up", ({mock_template, override}) => {
return pills.length; return pills.length;
} }
assert.equal(number_of_pills(), 0); assert.equal(number_of_pills(), 0);
config.updater.call(fake_stream_this, denmark); config.updater(denmark, fake_stream_this.query);
assert.equal(number_of_pills(), 1); assert.equal(number_of_pills(), 1);
config.updater.call(fake_person_this, me); config.updater(me, fake_person_this.query);
assert.equal(number_of_pills(), 2); assert.equal(number_of_pills(), 2);
config.updater.call(fake_group_this, testers); config.updater(testers, fake_group_this.query);
assert.equal(number_of_pills(), 3); assert.equal(number_of_pills(), 3);
assert.ok(update_func_called); assert.ok(update_func_called);