2022-02-05 19:15:30 +01:00
|
|
|
|
// todo: Refactor pills subsystem to use modern javascript classes?
|
|
|
|
|
|
2021-03-11 05:43:45 +01:00
|
|
|
|
import $ from "jquery";
|
|
|
|
|
|
2021-02-28 21:29:50 +01:00
|
|
|
|
import render_input_pill from "../templates/input_pill.hbs";
|
2020-08-01 03:43:15 +02:00
|
|
|
|
|
2021-03-16 23:38:59 +01:00
|
|
|
|
import * as blueslip from "./blueslip";
|
2021-08-06 19:07:31 +02:00
|
|
|
|
import * as compose from "./compose";
|
2021-02-28 21:29:50 +01:00
|
|
|
|
import * as ui_util from "./ui_util";
|
2021-02-28 01:00:36 +01:00
|
|
|
|
|
2018-07-21 17:56:43 +02:00
|
|
|
|
// See https://zulip.readthedocs.io/en/latest/subsystems/input-pills.html
|
2021-02-28 21:29:50 +01:00
|
|
|
|
export function random_id() {
|
2018-03-07 20:46:13 +01:00
|
|
|
|
return Math.random().toString(16);
|
2021-02-28 21:29:50 +01:00
|
|
|
|
}
|
2018-03-07 20:46:13 +01:00
|
|
|
|
|
2021-02-28 21:29:50 +01:00
|
|
|
|
export function create(opts) {
|
2022-01-25 11:36:19 +01:00
|
|
|
|
if (!opts.$container) {
|
2020-07-15 01:29:15 +02:00
|
|
|
|
blueslip.error("Pill needs container.");
|
2020-09-24 07:50:36 +02:00
|
|
|
|
return undefined;
|
2018-03-06 15:03:20 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!opts.create_item_from_text) {
|
2020-07-15 01:29:15 +02:00
|
|
|
|
blueslip.error("Pill needs create_item_from_text");
|
2020-09-24 07:50:36 +02:00
|
|
|
|
return undefined;
|
2018-03-06 15:03:20 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!opts.get_text_from_item) {
|
2020-07-15 01:29:15 +02:00
|
|
|
|
blueslip.error("Pill needs get_text_from_item");
|
2020-09-24 07:50:36 +02:00
|
|
|
|
return undefined;
|
2018-03-06 15:03:20 +01:00
|
|
|
|
}
|
|
|
|
|
|
2017-10-24 20:15:35 +02:00
|
|
|
|
// a stateful object of this `pill_container` instance.
|
|
|
|
|
// all unique instance information is stored in here.
|
2019-11-02 00:06:25 +01:00
|
|
|
|
const store = {
|
2017-10-24 20:15:35 +02:00
|
|
|
|
pills: [],
|
2022-02-05 19:15:30 +01:00
|
|
|
|
pill_config: opts.pill_config,
|
2022-01-25 11:36:19 +01:00
|
|
|
|
$parent: opts.$container,
|
|
|
|
|
$input: opts.$container.find(".input").expectOne(),
|
2018-03-06 15:03:20 +01:00
|
|
|
|
create_item_from_text: opts.create_item_from_text,
|
|
|
|
|
get_text_from_item: opts.get_text_from_item,
|
2017-10-24 20:15:35 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// a dictionary of internal functions. Some of these are exposed as well,
|
|
|
|
|
// and nothing in here should be assumed to be private (due to the passing)
|
|
|
|
|
// of the `this` arg in the `Function.prototype.bind` use in the prototype.
|
2019-10-26 00:26:37 +02:00
|
|
|
|
const funcs = {
|
2017-10-24 20:15:35 +02:00
|
|
|
|
// return the value of the contenteditable input form.
|
2020-07-20 22:18:43 +02:00
|
|
|
|
value(input_elem) {
|
2020-10-07 10:15:47 +02:00
|
|
|
|
return input_elem.textContent;
|
2017-10-24 20:15:35 +02:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// clear the value of the input form.
|
2020-07-20 22:18:43 +02:00
|
|
|
|
clear(input_elem) {
|
2020-10-07 10:15:47 +02:00
|
|
|
|
input_elem.textContent = "";
|
2017-10-24 20:15:35 +02:00
|
|
|
|
},
|
|
|
|
|
|
2020-07-20 22:18:43 +02:00
|
|
|
|
clear_text() {
|
2018-03-06 15:03:20 +01:00
|
|
|
|
store.$input.text("");
|
|
|
|
|
},
|
2017-10-24 20:15:35 +02:00
|
|
|
|
|
2020-07-20 22:18:43 +02:00
|
|
|
|
is_pending() {
|
2018-09-18 01:25:38 +02:00
|
|
|
|
// This function returns true if we have text
|
|
|
|
|
// in out widget that hasn't been turned into
|
|
|
|
|
// pills. We use it to decide things like
|
|
|
|
|
// whether we're ready to send typing indicators.
|
2020-07-15 01:29:15 +02:00
|
|
|
|
return store.$input.text().trim() !== "";
|
2018-09-18 01:25:38 +02:00
|
|
|
|
},
|
|
|
|
|
|
2020-07-20 22:18:43 +02:00
|
|
|
|
create_item(text) {
|
2019-11-02 00:06:25 +01:00
|
|
|
|
const existing_items = funcs.items();
|
|
|
|
|
const item = store.create_item_from_text(text, existing_items);
|
2017-10-25 01:05:24 +02:00
|
|
|
|
|
2018-03-06 15:03:20 +01:00
|
|
|
|
if (!item || !item.display_value) {
|
2017-11-13 22:06:33 +01:00
|
|
|
|
store.$input.addClass("shake");
|
2020-09-24 07:50:36 +02:00
|
|
|
|
return undefined;
|
2017-10-24 20:15:35 +02:00
|
|
|
|
}
|
|
|
|
|
|
2018-03-06 15:03:20 +01:00
|
|
|
|
return item;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// This is generally called by typeahead logic, where we have all
|
|
|
|
|
// the data we need (as opposed to, say, just a user-typed email).
|
2020-07-20 22:18:43 +02:00
|
|
|
|
appendValidatedData(item) {
|
2021-02-28 21:29:50 +01:00
|
|
|
|
const id = random_id();
|
2017-10-24 20:15:35 +02:00
|
|
|
|
|
2018-03-06 15:03:20 +01:00
|
|
|
|
if (!item.display_value) {
|
2020-07-15 01:29:15 +02:00
|
|
|
|
blueslip.error("no display_value returned");
|
2018-03-06 15:03:20 +01:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-27 16:56:20 +02:00
|
|
|
|
if (!item.type) {
|
|
|
|
|
blueslip.error("no type defined for the item");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
|
const payload = {
|
2020-07-20 22:18:43 +02:00
|
|
|
|
id,
|
|
|
|
|
item,
|
2017-10-24 20:15:35 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
store.pills.push(payload);
|
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
|
const has_image = item.img_src !== undefined;
|
2018-06-27 20:17:04 +02:00
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
|
const opts = {
|
2018-03-31 13:22:29 +02:00
|
|
|
|
id: payload.id,
|
|
|
|
|
display_value: item.display_value,
|
2020-07-20 22:18:43 +02:00
|
|
|
|
has_image,
|
2020-07-29 03:27:36 +02:00
|
|
|
|
deactivated: item.deactivated,
|
2018-03-31 13:22:29 +02:00
|
|
|
|
};
|
|
|
|
|
|
2018-06-27 20:17:04 +02:00
|
|
|
|
if (has_image) {
|
|
|
|
|
opts.img_src = item.img_src;
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-05 19:15:30 +01:00
|
|
|
|
if (store.pill_config?.show_user_status_emoji === true) {
|
|
|
|
|
const has_status = item.status_emoji_info !== undefined;
|
|
|
|
|
if (has_status) {
|
|
|
|
|
opts.status_emoji_info = item.status_emoji_info;
|
|
|
|
|
}
|
|
|
|
|
opts.has_status = has_status;
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-29 18:49:28 +02:00
|
|
|
|
if (typeof store.onPillCreate === "function") {
|
|
|
|
|
store.onPillCreate();
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
|
const pill_html = render_input_pill(opts);
|
2018-03-31 13:22:29 +02:00
|
|
|
|
payload.$element = $(pill_html);
|
2018-03-06 15:03:20 +01:00
|
|
|
|
store.$input.before(payload.$element);
|
2017-10-24 20:15:35 +02:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// this appends a pill to the end of the container but before the
|
|
|
|
|
// input block.
|
2020-07-20 22:18:43 +02:00
|
|
|
|
appendPill(value) {
|
2018-03-06 15:03:20 +01:00
|
|
|
|
if (value.length === 0) {
|
2020-09-24 07:50:36 +02:00
|
|
|
|
return true;
|
2018-03-06 15:03:20 +01:00
|
|
|
|
}
|
2017-11-13 22:06:33 +01:00
|
|
|
|
if (value.match(",")) {
|
|
|
|
|
funcs.insertManyPills(value);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2018-03-06 15:03:20 +01:00
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
|
const payload = this.create_item(value);
|
2017-10-24 20:15:35 +02:00
|
|
|
|
// if the pill object is undefined, then it means the pill was
|
|
|
|
|
// rejected so we should return out of this.
|
|
|
|
|
if (!payload) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2018-02-16 23:20:13 +01:00
|
|
|
|
|
2018-03-06 15:03:20 +01:00
|
|
|
|
this.appendValidatedData(payload);
|
2020-09-24 07:50:36 +02:00
|
|
|
|
return true;
|
2017-10-24 20:15:35 +02:00
|
|
|
|
},
|
|
|
|
|
|
2022-02-08 00:13:33 +01:00
|
|
|
|
// this searches given a particular pill ID for it, removes the node
|
2017-10-24 20:15:35 +02:00
|
|
|
|
// from the DOM, removes it from the array and returns it.
|
|
|
|
|
// this would generally be used for DOM-provoked actions, such as a user
|
|
|
|
|
// clicking on a pill to remove it.
|
2020-07-20 22:18:43 +02:00
|
|
|
|
removePill(id) {
|
2019-11-02 00:06:25 +01:00
|
|
|
|
let idx;
|
|
|
|
|
for (let x = 0; x < store.pills.length; x += 1) {
|
2017-10-24 20:15:35 +02:00
|
|
|
|
if (store.pills[x].id === id) {
|
|
|
|
|
idx = x;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (typeof idx === "number") {
|
|
|
|
|
store.pills[idx].$element.remove();
|
2019-11-02 00:06:25 +01:00
|
|
|
|
const pill = store.pills.splice(idx, 1);
|
2017-12-18 19:36:00 +01:00
|
|
|
|
if (typeof store.removePillFunction === "function") {
|
|
|
|
|
store.removePillFunction(pill);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return pill;
|
2017-10-24 20:15:35 +02:00
|
|
|
|
}
|
2020-09-24 07:50:36 +02:00
|
|
|
|
|
|
|
|
|
/* istanbul ignore next */
|
|
|
|
|
return undefined;
|
2017-10-24 20:15:35 +02:00
|
|
|
|
},
|
|
|
|
|
|
2018-07-14 16:05:05 +02:00
|
|
|
|
// this will remove the last pill in the container -- by default tied
|
2020-08-11 02:09:14 +02:00
|
|
|
|
// to the "Backspace" key when the value of the input is empty.
|
2018-07-14 16:05:05 +02:00
|
|
|
|
// If quiet is a truthy value, the event handler associated with the
|
|
|
|
|
// pill will not be evaluated. This is useful when using clear to reset
|
|
|
|
|
// the pills.
|
2020-07-20 22:18:43 +02:00
|
|
|
|
removeLastPill(quiet) {
|
2019-11-02 00:06:25 +01:00
|
|
|
|
const pill = store.pills.pop();
|
2017-10-24 20:15:35 +02:00
|
|
|
|
|
|
|
|
|
if (pill) {
|
|
|
|
|
pill.$element.remove();
|
2018-07-14 16:05:05 +02:00
|
|
|
|
if (!quiet && typeof store.removePillFunction === "function") {
|
2017-12-18 19:36:00 +01:00
|
|
|
|
store.removePillFunction(pill);
|
|
|
|
|
}
|
2017-10-24 20:15:35 +02:00
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
2020-07-20 22:18:43 +02:00
|
|
|
|
removeAllPills(quiet) {
|
2017-11-10 22:11:01 +01:00
|
|
|
|
while (store.pills.length > 0) {
|
2018-07-14 16:05:05 +02:00
|
|
|
|
this.removeLastPill(quiet);
|
2017-11-10 22:11:01 +01:00
|
|
|
|
}
|
|
|
|
|
|
2017-11-13 22:06:33 +01:00
|
|
|
|
this.clear(store.$input[0]);
|
|
|
|
|
},
|
|
|
|
|
|
2020-07-20 22:18:43 +02:00
|
|
|
|
insertManyPills(pills) {
|
2017-11-13 22:06:33 +01:00
|
|
|
|
if (typeof pills === "string") {
|
2020-07-02 01:45:54 +02:00
|
|
|
|
pills = pills.split(/,/g).map((pill) => pill.trim());
|
2017-11-13 22:06:33 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// this is an array to push all the errored values to, so it's drafts
|
|
|
|
|
// of pills for the user to fix.
|
2021-01-22 22:29:08 +01:00
|
|
|
|
const drafts = pills.filter(
|
|
|
|
|
(pill) =>
|
2022-02-08 00:13:33 +01:00
|
|
|
|
// if this returns `false`, it errored and we should push it to
|
2021-01-22 22:29:08 +01:00
|
|
|
|
// the draft pills.
|
|
|
|
|
funcs.appendPill(pill) === false,
|
|
|
|
|
);
|
2017-11-13 22:06:33 +01:00
|
|
|
|
|
|
|
|
|
store.$input.text(drafts.join(", "));
|
|
|
|
|
// when using the `text` insertion feature with jQuery the caret is
|
|
|
|
|
// placed at the beginning of the input field, so this moves it to
|
|
|
|
|
// the end.
|
|
|
|
|
ui_util.place_caret_at_end(store.$input[0]);
|
|
|
|
|
|
2020-09-24 07:50:36 +02:00
|
|
|
|
// this sends a flag if the operation wasn't completely successful,
|
2017-11-13 22:06:33 +01:00
|
|
|
|
// which in this case is defined as some of the pills not autofilling
|
2020-03-28 01:25:56 +01:00
|
|
|
|
// correctly.
|
2020-09-24 07:50:36 +02:00
|
|
|
|
return drafts.length === 0;
|
2017-11-10 22:11:01 +01:00
|
|
|
|
},
|
|
|
|
|
|
2020-07-20 22:18:43 +02:00
|
|
|
|
getByID(id) {
|
2020-07-02 01:39:34 +02:00
|
|
|
|
return store.pills.find((pill) => pill.id === id);
|
2018-02-16 23:20:13 +01:00
|
|
|
|
},
|
|
|
|
|
|
2021-02-25 17:31:33 +01:00
|
|
|
|
_get_pills_for_testing() {
|
|
|
|
|
return store.pills;
|
|
|
|
|
},
|
|
|
|
|
|
2020-07-20 22:18:43 +02:00
|
|
|
|
items() {
|
2020-07-02 01:39:34 +02:00
|
|
|
|
return store.pills.map((pill) => pill.item);
|
2018-03-06 15:03:20 +01:00
|
|
|
|
},
|
2020-05-15 01:27:15 +02:00
|
|
|
|
|
2020-07-20 22:18:43 +02:00
|
|
|
|
createPillonPaste() {
|
2020-05-15 01:27:15 +02:00
|
|
|
|
if (typeof store.createPillonPaste === "function") {
|
|
|
|
|
return store.createPillonPaste();
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
},
|
2017-10-24 20:15:35 +02:00
|
|
|
|
};
|
|
|
|
|
|
2021-05-06 21:49:45 +02:00
|
|
|
|
{
|
2020-07-02 01:45:54 +02:00
|
|
|
|
store.$parent.on("keydown", ".input", (e) => {
|
2021-05-29 22:06:13 +02:00
|
|
|
|
if (e.key === "Enter") {
|
2017-10-24 20:15:35 +02:00
|
|
|
|
// regardless of the value of the input, the ENTER keyword
|
|
|
|
|
// should be ignored in favor of keeping content to one line
|
|
|
|
|
// always.
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
|
|
|
|
// if there is input, grab the input, make a pill from it,
|
|
|
|
|
// and append the pill, then clear the input.
|
2020-05-10 00:47:46 +02:00
|
|
|
|
const value = funcs.value(e.target).trim();
|
2018-08-24 18:06:39 +02:00
|
|
|
|
if (value.length > 0) {
|
2017-10-24 20:15:35 +02:00
|
|
|
|
// append the pill and by proxy create the pill object.
|
2019-11-02 00:06:25 +01:00
|
|
|
|
const ret = funcs.appendPill(value);
|
2017-10-24 20:15:35 +02:00
|
|
|
|
|
|
|
|
|
// if the pill to append was rejected, no need to clear the
|
|
|
|
|
// input; it may have just been a typo or something close but
|
|
|
|
|
// incorrect.
|
|
|
|
|
if (ret !== false) {
|
|
|
|
|
// clear the input.
|
|
|
|
|
funcs.clear(e.target);
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// if the user backspaces and there is input, just do normal char
|
|
|
|
|
// deletion, otherwise delete the last pill in the sequence.
|
2020-07-15 00:34:28 +02:00
|
|
|
|
if (
|
2021-05-29 22:06:13 +02:00
|
|
|
|
e.key === "Backspace" &&
|
2020-07-15 00:34:28 +02:00
|
|
|
|
(funcs.value(e.target).length === 0 || window.getSelection().anchorOffset === 0)
|
|
|
|
|
) {
|
2017-10-24 20:15:35 +02:00
|
|
|
|
e.preventDefault();
|
|
|
|
|
funcs.removeLastPill();
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// if one is on the ".input" element and back/left arrows, then it
|
|
|
|
|
// should switch to focus the last pill in the list.
|
|
|
|
|
// the rest of the events then will be taken care of in the function
|
|
|
|
|
// below that handles events on the ".pill" class.
|
2021-05-29 22:06:13 +02:00
|
|
|
|
if (e.key === "ArrowLeft" && window.getSelection().anchorOffset === 0) {
|
2020-12-22 11:26:39 +01:00
|
|
|
|
store.$parent.find(".pill").last().trigger("focus");
|
2017-10-24 20:15:35 +02:00
|
|
|
|
}
|
2017-11-13 22:07:49 +01:00
|
|
|
|
|
2020-06-01 02:42:46 +02:00
|
|
|
|
// Typing of the comma is prevented if the last field doesn't validate,
|
|
|
|
|
// as well as when the new pill is created.
|
2021-05-29 22:06:13 +02:00
|
|
|
|
if (e.key === ",") {
|
2017-11-13 22:07:49 +01:00
|
|
|
|
// if the pill is successful, it will create the pill and clear
|
|
|
|
|
// the input.
|
|
|
|
|
if (funcs.appendPill(store.$input.text().trim()) !== false) {
|
|
|
|
|
funcs.clear(store.$input[0]);
|
|
|
|
|
}
|
2020-06-01 02:42:46 +02:00
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
|
|
|
|
return;
|
2017-11-13 22:07:49 +01:00
|
|
|
|
}
|
2017-10-24 20:15:35 +02:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// handle events while hovering on ".pill" elements.
|
|
|
|
|
// the three primary events are next, previous, and delete.
|
2020-07-02 01:45:54 +02:00
|
|
|
|
store.$parent.on("keydown", ".pill", (e) => {
|
2019-11-02 00:06:25 +01:00
|
|
|
|
const $pill = store.$parent.find(".pill:focus");
|
2017-10-24 20:15:35 +02:00
|
|
|
|
|
2021-05-29 22:06:13 +02:00
|
|
|
|
switch (e.key) {
|
|
|
|
|
case "ArrowLeft":
|
2021-05-09 23:36:58 +02:00
|
|
|
|
$pill.prev().trigger("focus");
|
|
|
|
|
break;
|
2021-05-29 22:06:13 +02:00
|
|
|
|
case "ArrowRight":
|
2021-05-09 23:36:58 +02:00
|
|
|
|
$pill.next().trigger("focus");
|
|
|
|
|
break;
|
2021-05-29 22:06:13 +02:00
|
|
|
|
case "Backspace": {
|
2021-05-09 23:36:58 +02:00
|
|
|
|
const $next = $pill.next();
|
|
|
|
|
const id = $pill.data("id");
|
|
|
|
|
funcs.removePill(id);
|
|
|
|
|
$next.trigger("focus");
|
|
|
|
|
// the "Backspace" key in Firefox will go back a page if you do
|
|
|
|
|
// not prevent it.
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
break;
|
|
|
|
|
}
|
2017-10-24 20:15:35 +02:00
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// when the shake animation is applied to the ".input" on invalid input,
|
|
|
|
|
// we want to remove the class when finished automatically.
|
|
|
|
|
store.$parent.on("animationend", ".input", function () {
|
|
|
|
|
$(this).removeClass("shake");
|
|
|
|
|
});
|
|
|
|
|
|
2017-11-13 22:06:33 +01:00
|
|
|
|
// replace formatted input with plaintext to allow for sane copy-paste
|
|
|
|
|
// actions.
|
2020-07-02 01:45:54 +02:00
|
|
|
|
store.$parent.on("paste", ".input", (e) => {
|
2017-11-13 22:06:33 +01:00
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
|
|
|
|
// get text representation of clipboard
|
2020-07-15 01:29:15 +02:00
|
|
|
|
const text = (e.originalEvent || e).clipboardData.getData("text/plain");
|
2017-11-13 22:06:33 +01:00
|
|
|
|
|
|
|
|
|
// insert text manually
|
2018-03-31 13:09:50 +02:00
|
|
|
|
document.execCommand("insertText", false, text);
|
2017-11-13 22:06:33 +01:00
|
|
|
|
|
2020-05-15 01:27:15 +02:00
|
|
|
|
if (funcs.createPillonPaste()) {
|
|
|
|
|
funcs.insertManyPills(store.$input.text().trim());
|
|
|
|
|
}
|
2017-11-13 22:06:33 +01:00
|
|
|
|
});
|
|
|
|
|
|
2017-10-24 20:15:35 +02:00
|
|
|
|
// when the "×" is clicked on a pill, it should delete that pill and then
|
|
|
|
|
// select the next pill (or input).
|
2018-11-02 16:00:43 +01:00
|
|
|
|
store.$parent.on("click", ".exit", function (e) {
|
|
|
|
|
e.stopPropagation();
|
2019-11-02 00:06:25 +01:00
|
|
|
|
const $pill = $(this).closest(".pill");
|
|
|
|
|
const $next = $pill.next();
|
|
|
|
|
const id = $pill.data("id");
|
2017-10-24 20:15:35 +02:00
|
|
|
|
|
|
|
|
|
funcs.removePill(id);
|
2020-07-20 21:24:26 +02:00
|
|
|
|
$next.trigger("focus");
|
2021-08-06 19:07:31 +02:00
|
|
|
|
|
|
|
|
|
compose.update_fade();
|
2017-10-24 20:15:35 +02:00
|
|
|
|
});
|
2017-10-24 20:33:18 +02:00
|
|
|
|
|
|
|
|
|
store.$parent.on("click", function (e) {
|
|
|
|
|
if ($(e.target).is(".pill-container")) {
|
2020-07-20 21:24:26 +02:00
|
|
|
|
$(this).find(".input").trigger("focus");
|
2017-10-24 20:33:18 +02:00
|
|
|
|
}
|
|
|
|
|
});
|
2018-02-16 23:20:13 +01:00
|
|
|
|
|
2020-07-02 01:45:54 +02:00
|
|
|
|
store.$parent.on("copy", ".pill", (e) => {
|
2019-11-02 00:06:25 +01:00
|
|
|
|
const id = store.$parent.find(":focus").data("id");
|
|
|
|
|
const data = funcs.getByID(id);
|
2020-07-15 00:34:28 +02:00
|
|
|
|
e.originalEvent.clipboardData.setData(
|
|
|
|
|
"text/plain",
|
|
|
|
|
store.get_text_from_item(data.item),
|
|
|
|
|
);
|
2018-02-16 23:20:13 +01:00
|
|
|
|
e.preventDefault();
|
|
|
|
|
});
|
2021-05-06 21:49:45 +02:00
|
|
|
|
}
|
2017-10-24 20:15:35 +02:00
|
|
|
|
|
|
|
|
|
// the external, user-accessible prototype.
|
2019-11-02 00:06:25 +01:00
|
|
|
|
const prototype = {
|
2018-03-06 15:03:20 +01:00
|
|
|
|
appendValue: funcs.appendPill.bind(funcs),
|
|
|
|
|
appendValidatedData: funcs.appendValidatedData.bind(funcs),
|
2017-10-24 20:15:35 +02:00
|
|
|
|
|
2020-06-18 17:17:11 +02:00
|
|
|
|
getByID: funcs.getByID,
|
2018-03-06 15:03:20 +01:00
|
|
|
|
items: funcs.items,
|
2017-10-24 20:15:35 +02:00
|
|
|
|
|
2020-07-20 22:18:43 +02:00
|
|
|
|
onPillCreate(callback) {
|
2018-03-06 15:03:20 +01:00
|
|
|
|
store.onPillCreate = callback;
|
2017-10-24 20:15:35 +02:00
|
|
|
|
},
|
2017-10-25 01:05:24 +02:00
|
|
|
|
|
2020-07-20 22:18:43 +02:00
|
|
|
|
onPillRemove(callback) {
|
2017-12-18 19:36:00 +01:00
|
|
|
|
store.removePillFunction = callback;
|
|
|
|
|
},
|
|
|
|
|
|
2020-07-20 22:18:43 +02:00
|
|
|
|
createPillonPaste(callback) {
|
2020-05-15 01:27:15 +02:00
|
|
|
|
store.createPillonPaste = callback;
|
|
|
|
|
},
|
|
|
|
|
|
2017-11-10 22:11:01 +01:00
|
|
|
|
clear: funcs.removeAllPills.bind(funcs),
|
2018-03-06 15:03:20 +01:00
|
|
|
|
clear_text: funcs.clear_text,
|
2018-09-18 01:25:38 +02:00
|
|
|
|
is_pending: funcs.is_pending,
|
2021-02-25 17:31:33 +01:00
|
|
|
|
_get_pills_for_testing: funcs._get_pills_for_testing,
|
2017-10-24 20:15:35 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return prototype;
|
2021-02-28 21:29:50 +01:00
|
|
|
|
}
|