|
// retoor <retoor@molodetz.nl>
|
|
|
|
import { Http } from "./Http.js";
|
|
import { Avatar } from "./Avatar.js";
|
|
import { TextInput } from "./TextInput.js";
|
|
import { DomUtils } from "./DomUtils.js";
|
|
import { ListNav } from "./ListNav.js";
|
|
|
|
export class MentionInput {
|
|
constructor(element) {
|
|
this.input = element;
|
|
this.dropdown = null;
|
|
this.debounceTimer = null;
|
|
this.lastMatch = null;
|
|
this.nav = null;
|
|
this.build();
|
|
}
|
|
|
|
build() {
|
|
const wrap = document.createElement("div");
|
|
wrap.className = "mention-wrapper";
|
|
this.input.parentNode.insertBefore(wrap, this.input);
|
|
wrap.appendChild(this.input);
|
|
|
|
this.dropdown = document.createElement("div");
|
|
this.dropdown.className = "mention-dropdown";
|
|
this.dropdown.setAttribute("role", "listbox");
|
|
wrap.appendChild(this.dropdown);
|
|
|
|
this.nav = new ListNav(this.input, this.dropdown, {
|
|
itemSelector: ".mention-dropdown-item",
|
|
activeClass: "active",
|
|
isOpen: () => DomUtils.isShown(this.dropdown),
|
|
onChoose: (item) => this.insert(item.dataset.username),
|
|
onEscape: () => this.hide(),
|
|
});
|
|
|
|
this.input.addEventListener("input", () => this.onInput());
|
|
this.input.addEventListener("blur", () => {
|
|
setTimeout(() => this.hide(), 200);
|
|
});
|
|
}
|
|
|
|
hide() {
|
|
DomUtils.hide(this.dropdown);
|
|
this.nav.closed();
|
|
}
|
|
|
|
onInput() {
|
|
clearTimeout(this.debounceTimer);
|
|
const pos = this.input.selectionStart;
|
|
const text = this.input.value.substring(0, pos);
|
|
let match = text.match(/(?:^|\s|\x28)@([a-zA-Z0-9_-]*)$/);
|
|
|
|
if (!match) {
|
|
this.hide();
|
|
this.lastMatch = null;
|
|
return;
|
|
}
|
|
|
|
const query = match[1];
|
|
this.lastMatch = { query, index: match.index + (text[match.index] === "@" ? 0 : 1) };
|
|
|
|
if (query.length < 1) {
|
|
this.hide();
|
|
return;
|
|
}
|
|
|
|
this.debounceTimer = setTimeout(() => this.fetch(query), 200);
|
|
}
|
|
|
|
async fetch(query) {
|
|
try {
|
|
const data = await Http.getJson("/profile/search?q=" + encodeURIComponent(query));
|
|
const results = data.results || [];
|
|
if (results.length === 0) {
|
|
this.hide();
|
|
return;
|
|
}
|
|
this.render(results);
|
|
} catch (e) {
|
|
this.hide();
|
|
}
|
|
}
|
|
|
|
render(results) {
|
|
this.dropdown.innerHTML = "";
|
|
for (const r of results) {
|
|
const item = document.createElement("button");
|
|
item.type = "button";
|
|
item.className = "mention-dropdown-item";
|
|
item.dataset.username = r.username;
|
|
const label = document.createElement("span");
|
|
label.textContent = "@" + r.username;
|
|
item.append(Avatar.imgElement(r.username), label);
|
|
item.addEventListener("mousedown", (e) => {
|
|
e.preventDefault();
|
|
this.insert(r.username);
|
|
});
|
|
this.dropdown.appendChild(item);
|
|
}
|
|
DomUtils.show(this.dropdown);
|
|
this.nav.opened();
|
|
}
|
|
|
|
insert(username) {
|
|
if (!this.lastMatch) return;
|
|
const val = this.input.value;
|
|
let before = val.substring(0, this.lastMatch.index);
|
|
let after = val.substring(this.lastMatch.index + this.lastMatch.query.length + 1);
|
|
before = before.replace(/@+$/, "");
|
|
after = after.replace(/^@+/, "");
|
|
TextInput.applyValue(this.input, before + "@" + username + " " + after, before.length + username.length + 2);
|
|
this.hide();
|
|
this.lastMatch = null;
|
|
}
|
|
}
|
|
window.MentionInput = MentionInput;
|