typeahead: Pass input element to updater instead of using hacky this.

This commit is contained in:
evykassirer 2024-03-29 17:22:55 -07:00 committed by Tim Abbott
parent 9f83d14fb7
commit bdf4449ebf
3 changed files with 46 additions and 51 deletions

View File

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

View File

@ -872,11 +872,11 @@ export function content_highlighter_html(item) {
}
}
export function content_typeahead_selected(item, query, event) {
const pieces = split_at_cursor(query, this.input_element.$element);
export function content_typeahead_selected(item, query, input_element, event) {
const pieces = split_at_cursor(query, input_element.$element);
let beginning = pieces[0];
let rest = pieces[1];
const $textbox = this.input_element.$element;
const $textbox = input_element.$element;
// Accepting some typeahead selections, like polls, will generate
// placeholder text that is selected, in order to clarify for the
// user what a given parameter is for. This object stores the
@ -1018,11 +1018,7 @@ export function content_typeahead_selected(item, query, event) {
$textbox.caret(beginning.length, beginning.length);
compose_ui.autosize_textarea($textbox);
};
flatpickr.show_flatpickr(
this.input_element.$element[0],
on_timestamp_selection,
timestamp,
);
flatpickr.show_flatpickr(input_element.$element[0], on_timestamp_selection, timestamp);
return beginning + rest;
}
}

View File

@ -429,17 +429,14 @@ test("topics_seen_for", ({override, override_rewire}) => {
});
test("content_typeahead_selected", ({override}) => {
const fake_this = {
query: "",
input_element: {
const input_element = {
$element: {},
type: "input",
},
};
let caret_called1 = false;
let caret_called2 = false;
let query;
fake_this.input_element.$element.caret = function (...args) {
input_element.$element.caret = function (...args) {
if (args.length === 0) {
// .caret() used in split_at_cursor
caret_called1 = true;
@ -452,7 +449,7 @@ test("content_typeahead_selected", ({override}) => {
return this;
};
let range_called = false;
fake_this.input_element.$element.range = function (...args) {
input_element.$element.range = function (...args) {
const [arg1, arg2] = args;
// .range() used in setTimeout
assert.ok(arg2 > arg1);
@ -470,19 +467,19 @@ test("content_typeahead_selected", ({override}) => {
emoji_name: "octopus",
};
let actual_value = ct.content_typeahead_selected.call(fake_this, item, query);
let actual_value = ct.content_typeahead_selected(item, query, input_element);
let expected_value = ":octopus: ";
assert.equal(actual_value, expected_value);
query = " :octo";
ct.get_or_set_token_for_testing("octo");
actual_value = ct.content_typeahead_selected.call(fake_this, item, query);
actual_value = ct.content_typeahead_selected(item, query, input_element);
expected_value = " :octopus: ";
assert.equal(actual_value, expected_value);
query = "{:octo";
ct.get_or_set_token_for_testing("octo");
actual_value = ct.content_typeahead_selected.call(fake_this, item, query);
actual_value = ct.content_typeahead_selected(item, query, input_element);
expected_value = "{ :octopus: ";
assert.equal(actual_value, expected_value);
@ -498,7 +495,7 @@ test("content_typeahead_selected", ({override}) => {
query = "@**Mark Tw";
ct.get_or_set_token_for_testing("Mark Tw");
actual_value = ct.content_typeahead_selected.call(fake_this, twin1, query);
actual_value = ct.content_typeahead_selected(twin1, query, input_element);
expected_value = "@**Mark Twin|105** ";
assert.equal(actual_value, expected_value);
@ -510,26 +507,26 @@ test("content_typeahead_selected", ({override}) => {
query = "@oth";
ct.get_or_set_token_for_testing("oth");
actual_value = ct.content_typeahead_selected.call(fake_this, othello, query);
actual_value = ct.content_typeahead_selected(othello, query, input_element);
expected_value = "@**Othello, the Moor of Venice** ";
assert.equal(actual_value, expected_value);
assert.ok(warned_for_mention);
query = "Hello @oth";
ct.get_or_set_token_for_testing("oth");
actual_value = ct.content_typeahead_selected.call(fake_this, othello, query);
actual_value = ct.content_typeahead_selected(othello, query, input_element);
expected_value = "Hello @**Othello, the Moor of Venice** ";
assert.equal(actual_value, expected_value);
query = "@**oth";
ct.get_or_set_token_for_testing("oth");
actual_value = ct.content_typeahead_selected.call(fake_this, othello, query);
actual_value = ct.content_typeahead_selected(othello, query, input_element);
expected_value = "@**Othello, the Moor of Venice** ";
assert.equal(actual_value, expected_value);
query = "@*oth";
ct.get_or_set_token_for_testing("oth");
actual_value = ct.content_typeahead_selected.call(fake_this, othello, query);
actual_value = ct.content_typeahead_selected(othello, query, input_element);
expected_value = "@**Othello, the Moor of Venice** ";
assert.equal(actual_value, expected_value);
@ -537,14 +534,14 @@ test("content_typeahead_selected", ({override}) => {
ct.get_or_set_token_for_testing("back");
with_overrides(({disallow}) => {
disallow(compose_validate, "warn_if_mentioning_unsubscribed_user");
actual_value = ct.content_typeahead_selected.call(fake_this, backend, query);
actual_value = ct.content_typeahead_selected(backend, query, input_element);
});
expected_value = "@*Backend* ";
assert.equal(actual_value, expected_value);
query = "@*back";
ct.get_or_set_token_for_testing("back");
actual_value = ct.content_typeahead_selected.call(fake_this, backend, query);
actual_value = ct.content_typeahead_selected(backend, query, input_element);
expected_value = "@*Backend* ";
assert.equal(actual_value, expected_value);
@ -554,7 +551,7 @@ test("content_typeahead_selected", ({override}) => {
ct.get_or_set_token_for_testing("kin");
with_overrides(({disallow}) => {
disallow(compose_validate, "warn_if_mentioning_unsubscribed_user");
actual_value = ct.content_typeahead_selected.call(fake_this, hamlet, query);
actual_value = ct.content_typeahead_selected(hamlet, query, input_element);
});
expected_value = "@_**King Hamlet** ";
@ -562,19 +559,19 @@ test("content_typeahead_selected", ({override}) => {
query = "Hello @_kin";
ct.get_or_set_token_for_testing("kin");
actual_value = ct.content_typeahead_selected.call(fake_this, hamlet, query);
actual_value = ct.content_typeahead_selected(hamlet, query, input_element);
expected_value = "Hello @_**King Hamlet** ";
assert.equal(actual_value, expected_value);
query = "@_*kin";
ct.get_or_set_token_for_testing("kin");
actual_value = ct.content_typeahead_selected.call(fake_this, hamlet, query);
actual_value = ct.content_typeahead_selected(hamlet, query, input_element);
expected_value = "@_**King Hamlet** ";
assert.equal(actual_value, expected_value);
query = "@_**kin";
ct.get_or_set_token_for_testing("kin");
actual_value = ct.content_typeahead_selected.call(fake_this, hamlet, query);
actual_value = ct.content_typeahead_selected(hamlet, query, input_element);
expected_value = "@_**King Hamlet** ";
assert.equal(actual_value, expected_value);
@ -582,44 +579,44 @@ test("content_typeahead_selected", ({override}) => {
ct.get_or_set_token_for_testing("back");
with_overrides(({disallow}) => {
disallow(compose_validate, "warn_if_mentioning_unsubscribed_user");
actual_value = ct.content_typeahead_selected.call(fake_this, backend, query);
actual_value = ct.content_typeahead_selected(backend, query, input_element);
});
expected_value = "@_*Backend* ";
assert.equal(actual_value, expected_value);
query = "@_*back";
ct.get_or_set_token_for_testing("back");
actual_value = ct.content_typeahead_selected.call(fake_this, backend, query);
actual_value = ct.content_typeahead_selected(backend, query, input_element);
expected_value = "@_*Backend* ";
assert.equal(actual_value, expected_value);
query = "/m";
ct.get_or_set_completing_for_tests("slash");
actual_value = ct.content_typeahead_selected.call(fake_this, me_slash, query);
actual_value = ct.content_typeahead_selected(me_slash, query, input_element);
expected_value = "/me translated: is …";
assert.equal(actual_value, expected_value);
query = "/da";
ct.get_or_set_completing_for_tests("slash");
actual_value = ct.content_typeahead_selected.call(fake_this, dark_slash, query);
actual_value = ct.content_typeahead_selected(dark_slash, query, input_element);
expected_value = "/dark ";
assert.equal(actual_value, expected_value);
query = "/ni";
ct.get_or_set_completing_for_tests("slash");
actual_value = ct.content_typeahead_selected.call(fake_this, dark_slash, query);
actual_value = ct.content_typeahead_selected(dark_slash, query, input_element);
expected_value = "/dark ";
assert.equal(actual_value, expected_value);
query = "/li";
ct.get_or_set_completing_for_tests("slash");
actual_value = ct.content_typeahead_selected.call(fake_this, light_slash, query);
actual_value = ct.content_typeahead_selected(light_slash, query, input_element);
expected_value = "/light ";
assert.equal(actual_value, expected_value);
query = "/da";
ct.get_or_set_completing_for_tests("slash");
actual_value = ct.content_typeahead_selected.call(fake_this, light_slash, query);
actual_value = ct.content_typeahead_selected(light_slash, query, input_element);
expected_value = "/light ";
assert.equal(actual_value, expected_value);
@ -633,19 +630,19 @@ test("content_typeahead_selected", ({override}) => {
query = "#swed";
ct.get_or_set_token_for_testing("swed");
actual_value = ct.content_typeahead_selected.call(fake_this, sweden_stream, query);
actual_value = ct.content_typeahead_selected(sweden_stream, query, input_element);
expected_value = "#**Sweden** ";
assert.equal(actual_value, expected_value);
query = "Hello #swed";
ct.get_or_set_token_for_testing("swed");
actual_value = ct.content_typeahead_selected.call(fake_this, sweden_stream, query);
actual_value = ct.content_typeahead_selected(sweden_stream, query, input_element);
expected_value = "Hello #**Sweden** ";
assert.equal(actual_value, expected_value);
query = "#**swed";
ct.get_or_set_token_for_testing("swed");
actual_value = ct.content_typeahead_selected.call(fake_this, sweden_stream, query);
actual_value = ct.content_typeahead_selected(sweden_stream, query, input_element);
expected_value = "#**Sweden** ";
assert.equal(actual_value, expected_value);
@ -654,13 +651,13 @@ test("content_typeahead_selected", ({override}) => {
query = "Hello #**Sweden>test";
ct.get_or_set_token_for_testing("test");
actual_value = ct.content_typeahead_selected.call(fake_this, "testing", query);
actual_value = ct.content_typeahead_selected("testing", query, input_element);
expected_value = "Hello #**Sweden>testing** ";
assert.equal(actual_value, expected_value);
query = "Hello #**Sweden>";
ct.get_or_set_token_for_testing("");
actual_value = ct.content_typeahead_selected.call(fake_this, "testing", query);
actual_value = ct.content_typeahead_selected("testing", query, input_element);
expected_value = "Hello #**Sweden>testing** ";
assert.equal(actual_value, expected_value);
@ -669,40 +666,40 @@ test("content_typeahead_selected", ({override}) => {
query = "~~~p";
ct.get_or_set_token_for_testing("p");
actual_value = ct.content_typeahead_selected.call(fake_this, "python", query);
actual_value = ct.content_typeahead_selected("python", query, input_element);
expected_value = "~~~python\n\n~~~";
assert.equal(actual_value, expected_value);
query = "Hello ~~~p";
ct.get_or_set_token_for_testing("p");
actual_value = ct.content_typeahead_selected.call(fake_this, "python", query);
actual_value = ct.content_typeahead_selected("python", query, input_element);
expected_value = "Hello ~~~python\n\n~~~";
assert.equal(actual_value, expected_value);
query = "```p";
ct.get_or_set_token_for_testing("p");
actual_value = ct.content_typeahead_selected.call(fake_this, "python", query);
actual_value = ct.content_typeahead_selected("python", query, input_element);
expected_value = "```python\n\n```";
assert.equal(actual_value, expected_value);
query = "```spo";
ct.get_or_set_token_for_testing("spo");
actual_value = ct.content_typeahead_selected.call(fake_this, "spoiler", query);
actual_value = ct.content_typeahead_selected("spoiler", query, input_element);
expected_value = "```spoiler translated: Header\n\n```";
assert.equal(actual_value, expected_value);
// Test special case to not close code blocks if there is text afterward
query = "```p\nsome existing code";
ct.get_or_set_token_for_testing("p");
fake_this.input_element.$element.caret = () => 4; // Put cursor right after ```p
actual_value = ct.content_typeahead_selected.call(fake_this, "python", query);
input_element.$element.caret = () => 4; // Put cursor right after ```p
actual_value = ct.content_typeahead_selected("python", query, input_element);
expected_value = "```python\nsome existing code";
assert.equal(actual_value, expected_value);
ct.get_or_set_completing_for_tests("something-else");
query = "foo";
actual_value = ct.content_typeahead_selected.call(fake_this, {}, query);
actual_value = ct.content_typeahead_selected({}, query, input_element);
expected_value = query;
assert.equal(actual_value, expected_value);