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: ( updater: (
item: ItemType, item: ItemType,
query: string, query: string,
input_element: InputElement,
event?: JQuery.ClickEvent | JQuery.KeyUpEvent | JQuery.KeyDownEvent, event?: JQuery.ClickEvent | JQuery.KeyUpEvent | JQuery.KeyDownEvent,
) => string | undefined; ) => string | undefined;
$container: JQuery; $container: JQuery;
@ -272,13 +273,13 @@ class Typeahead<ItemType extends string | object> {
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 this.input_element.$element
.text(this.updater(val, this.query, e) ?? "") .text(this.updater(val, this.query, this.input_element, e) ?? "")
.trigger("change"); .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, this.query, e) ?? ""; const after_text = this.updater(val, this.query, this.input_element, 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);
@ -715,6 +716,7 @@ type TypeaheadOptions<ItemType> = {
updater: ( updater: (
item: ItemType, item: ItemType,
query: string, query: string,
input_element: InputElement,
event?: JQuery.ClickEvent | JQuery.KeyUpEvent | JQuery.KeyDownEvent, event?: JQuery.ClickEvent | JQuery.KeyUpEvent | JQuery.KeyDownEvent,
) => string | undefined; ) => string | undefined;
}; };

View File

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

View File

@ -429,17 +429,14 @@ test("topics_seen_for", ({override, override_rewire}) => {
}); });
test("content_typeahead_selected", ({override}) => { test("content_typeahead_selected", ({override}) => {
const fake_this = { const input_element = {
query: "", $element: {},
input_element: { type: "input",
$element: {},
type: "input",
},
}; };
let caret_called1 = false; let caret_called1 = false;
let caret_called2 = false; let caret_called2 = false;
let query; let query;
fake_this.input_element.$element.caret = function (...args) { 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;
@ -452,7 +449,7 @@ test("content_typeahead_selected", ({override}) => {
return this; return this;
}; };
let range_called = false; let range_called = false;
fake_this.input_element.$element.range = function (...args) { input_element.$element.range = function (...args) {
const [arg1, arg2] = args; const [arg1, arg2] = args;
// .range() used in setTimeout // .range() used in setTimeout
assert.ok(arg2 > arg1); assert.ok(arg2 > arg1);
@ -470,19 +467,19 @@ test("content_typeahead_selected", ({override}) => {
emoji_name: "octopus", 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: "; let expected_value = ":octopus: ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
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, query); actual_value = ct.content_typeahead_selected(item, query, input_element);
expected_value = " :octopus: "; expected_value = " :octopus: ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
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, query); actual_value = ct.content_typeahead_selected(item, query, input_element);
expected_value = "{ :octopus: "; expected_value = "{ :octopus: ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
@ -498,7 +495,7 @@ test("content_typeahead_selected", ({override}) => {
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, query); actual_value = ct.content_typeahead_selected(twin1, query, input_element);
expected_value = "@**Mark Twin|105** "; expected_value = "@**Mark Twin|105** ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
@ -510,26 +507,26 @@ test("content_typeahead_selected", ({override}) => {
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, query); actual_value = ct.content_typeahead_selected(othello, query, input_element);
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);
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, query); actual_value = ct.content_typeahead_selected(othello, query, input_element);
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);
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, query); actual_value = ct.content_typeahead_selected(othello, query, input_element);
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);
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, query); actual_value = ct.content_typeahead_selected(othello, query, input_element);
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);
@ -537,14 +534,14 @@ test("content_typeahead_selected", ({override}) => {
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, query); actual_value = ct.content_typeahead_selected(backend, query, input_element);
}); });
expected_value = "@*Backend* "; expected_value = "@*Backend* ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
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, query); actual_value = ct.content_typeahead_selected(backend, query, input_element);
expected_value = "@*Backend* "; expected_value = "@*Backend* ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
@ -554,7 +551,7 @@ test("content_typeahead_selected", ({override}) => {
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, query); actual_value = ct.content_typeahead_selected(hamlet, query, input_element);
}); });
expected_value = "@_**King Hamlet** "; expected_value = "@_**King Hamlet** ";
@ -562,19 +559,19 @@ test("content_typeahead_selected", ({override}) => {
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, query); actual_value = ct.content_typeahead_selected(hamlet, query, input_element);
expected_value = "Hello @_**King Hamlet** "; expected_value = "Hello @_**King Hamlet** ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
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, query); actual_value = ct.content_typeahead_selected(hamlet, query, input_element);
expected_value = "@_**King Hamlet** "; expected_value = "@_**King Hamlet** ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
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, query); actual_value = ct.content_typeahead_selected(hamlet, query, input_element);
expected_value = "@_**King Hamlet** "; expected_value = "@_**King Hamlet** ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
@ -582,44 +579,44 @@ test("content_typeahead_selected", ({override}) => {
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, query); actual_value = ct.content_typeahead_selected(backend, query, input_element);
}); });
expected_value = "@_*Backend* "; expected_value = "@_*Backend* ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
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, query); actual_value = ct.content_typeahead_selected(backend, query, input_element);
expected_value = "@_*Backend* "; expected_value = "@_*Backend* ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
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, query); actual_value = ct.content_typeahead_selected(me_slash, query, input_element);
expected_value = "/me translated: is …"; expected_value = "/me translated: is …";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
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, query); actual_value = ct.content_typeahead_selected(dark_slash, query, input_element);
expected_value = "/dark "; expected_value = "/dark ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
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, query); actual_value = ct.content_typeahead_selected(dark_slash, query, input_element);
expected_value = "/dark "; expected_value = "/dark ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
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, query); actual_value = ct.content_typeahead_selected(light_slash, query, input_element);
expected_value = "/light "; expected_value = "/light ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
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, query); actual_value = ct.content_typeahead_selected(light_slash, query, input_element);
expected_value = "/light "; expected_value = "/light ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
@ -633,19 +630,19 @@ test("content_typeahead_selected", ({override}) => {
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, query); actual_value = ct.content_typeahead_selected(sweden_stream, query, input_element);
expected_value = "#**Sweden** "; expected_value = "#**Sweden** ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
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, query); actual_value = ct.content_typeahead_selected(sweden_stream, query, input_element);
expected_value = "Hello #**Sweden** "; expected_value = "Hello #**Sweden** ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
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, query); actual_value = ct.content_typeahead_selected(sweden_stream, query, input_element);
expected_value = "#**Sweden** "; expected_value = "#**Sweden** ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
@ -654,13 +651,13 @@ test("content_typeahead_selected", ({override}) => {
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", query); actual_value = ct.content_typeahead_selected("testing", query, input_element);
expected_value = "Hello #**Sweden>testing** "; expected_value = "Hello #**Sweden>testing** ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
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", query); actual_value = ct.content_typeahead_selected("testing", query, input_element);
expected_value = "Hello #**Sweden>testing** "; expected_value = "Hello #**Sweden>testing** ";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
@ -669,40 +666,40 @@ test("content_typeahead_selected", ({override}) => {
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", query); actual_value = ct.content_typeahead_selected("python", query, input_element);
expected_value = "~~~python\n\n~~~"; expected_value = "~~~python\n\n~~~";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
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", query); actual_value = ct.content_typeahead_selected("python", query, input_element);
expected_value = "Hello ~~~python\n\n~~~"; expected_value = "Hello ~~~python\n\n~~~";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
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", query); actual_value = ct.content_typeahead_selected("python", query, input_element);
expected_value = "```python\n\n```"; expected_value = "```python\n\n```";
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);
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", query); actual_value = ct.content_typeahead_selected("spoiler", query, input_element);
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
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 input_element.$element.caret = () => 4; // Put cursor right after ```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\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");
query = "foo"; query = "foo";
actual_value = ct.content_typeahead_selected.call(fake_this, {}, query); actual_value = ct.content_typeahead_selected({}, query, input_element);
expected_value = query; expected_value = query;
assert.equal(actual_value, expected_value); assert.equal(actual_value, expected_value);