FIX: clicking "more..." in emoji autocomplete (#26160)

Should open the emoji picker. But it wasn't 😅

The `handleOutsideClick` event was listening too early and would catch the click on the "more..." option in the autocomplete as a click outside the emoji picker and would immediately close it 🤦

The fix was to defer registering to this event.
This commit is contained in:
Régis Hanol 2024-03-14 09:31:49 +01:00 committed by GitHub
parent 139e21e37d
commit 44f6b24e34
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 48 additions and 45 deletions

View File

@ -19,14 +19,10 @@ import discourseComputed, {
} from "discourse-common/utils/decorators";
function customEmojis() {
const list = extendedEmojiList();
const groups = [];
for (const [code, emoji] of list.entries()) {
groups[emoji.group] = groups[emoji.group] || [];
groups[emoji.group].push({
code,
src: emojiUrlFor(code),
});
for (const [code, emoji] of extendedEmojiList()) {
groups[emoji.group] ||= [];
groups[emoji.group].push({ code, src: emojiUrlFor(code) });
}
return groups;
}
@ -48,9 +44,7 @@ export default Component.extend({
init() {
this._super(...arguments);
this.set("customEmojis", customEmojis());
if ("IntersectionObserver" in window) {
this._sectionObserver = this._setupSectionObserver();
}
@ -58,7 +52,6 @@ export default Component.extend({
didInsertElement() {
this._super(...arguments);
this.appEvents.on("emoji-picker:close", this, "onClose");
},
@ -83,9 +76,7 @@ export default Component.extend({
willDestroyElement() {
this._super(...arguments);
this._sectionObserver && this._sectionObserver.disconnect();
this._sectionObserver?.disconnect();
this.appEvents.off("emoji-picker:close", this, "onClose");
},
@ -95,7 +86,6 @@ export default Component.extend({
schedule("afterRender", () => {
this._applyFilter(this.initialFilter);
document.addEventListener("click", this.handleOutsideClick);
const emojiPicker = document.querySelector(".emoji-picker");
if (!emojiPicker) {
@ -147,9 +137,10 @@ export default Component.extend({
// of blocking the rendering of the picker
discourseLater(() => {
schedule("afterRender", () => {
document.addEventListener("click", this.handleOutsideClick);
if (!this.site.isMobileDevice || this.isEditorFocused) {
const filter = emojiPicker.querySelector("input.filter");
filter && filter.focus();
emojiPicker.querySelector("input.filter")?.focus();
if (this._sectionObserver) {
emojiPicker
@ -170,7 +161,7 @@ export default Component.extend({
onClose(event) {
event?.stopPropagation();
document.removeEventListener("click", this.handleOutsideClick);
this.onEmojiPickerClose && this.onEmojiPickerClose(event);
this.onEmojiPickerClose?.(event);
},
diversityScales: computed("selectedDiversity", function () {
@ -239,10 +230,11 @@ export default Component.extend({
@action
onCategorySelection(sectionName, event) {
event?.preventDefault();
const section = document.querySelector(
`.emoji-picker-emoji-area .section[data-section="${sectionName}"]`
);
section && section.scrollIntoView();
document
.querySelector(
`.emoji-picker-emoji-area .section[data-section="${sectionName}"]`
)
?.scrollIntoView();
},
@action
@ -264,7 +256,7 @@ export default Component.extend({
if (event.key === "Escape") {
this.onClose(event);
const path = event.path || (event.composedPath && event.composedPath());
const path = event.path || event.composedPath?.();
const fromChatComposer = path.find((e) =>
e?.classList?.contains("chat-composer-container")
@ -325,8 +317,10 @@ export default Component.extend({
const emojiBelow = [...emojis]
.filter((c) => c.offsetTop > active.offsetTop)
.find((c) => c.offsetLeft === active.offsetLeft);
this._updateEmojiPreview(emojiBelow.title);
emojiBelow?.focus();
if (emojiBelow) {
this._updateEmojiPreview(emojiBelow.title);
emojiBelow.focus();
}
}
if (event.key === "ArrowUp") {
@ -420,12 +414,9 @@ export default Component.extend({
_applyDiversity(diversity) {
const emojiPickerArea = document.querySelector(".emoji-picker-emoji-area");
emojiPickerArea &&
emojiPickerArea.querySelectorAll(".emoji.diversity").forEach((img) => {
const code = this._codeWithDiversity(img.title, diversity);
img.src = emojiUrlFor(code);
});
emojiPickerArea?.querySelectorAll(".emoji.diversity").forEach((img) => {
img.src = emojiUrlFor(this._codeWithDiversity(img.title, diversity));
});
},
_setupSectionObserver() {
@ -442,14 +433,13 @@ export default Component.extend({
return;
}
const button = categoryButtons.querySelector(
`.category-button[data-section="${sectionName}"]`
);
categoryButtons
.querySelectorAll(".category-button")
.forEach((b) => b.classList.remove("current"));
button && button.classList.add("current");
categoryButtons
.querySelector(`.category-button[data-section="${sectionName}"]`)
?.classList?.add("current");
}
});
},
@ -475,8 +465,7 @@ export default Component.extend({
@bind
handleOutsideClick(event) {
const emojiPicker = document.querySelector(".emoji-picker");
if (emojiPicker && !emojiPicker.contains(event.target)) {
if (!document.querySelector(".emoji-picker")?.contains(event.target)) {
this.onClose(event);
}
},

View File

@ -1,4 +1,4 @@
import { click, visit } from "@ember/test-helpers";
import { click, fillIn, visit } from "@ember/test-helpers";
import { IMAGE_VERSION as v } from "pretty-text/emoji/version";
import { test } from "qunit";
import {
@ -8,7 +8,6 @@ import {
query,
simulateKey,
simulateKeys,
visible,
} from "discourse/tests/helpers/qunit-helpers";
acceptance("Emoji", function (needs) {
@ -20,7 +19,6 @@ acceptance("Emoji", function (needs) {
await simulateKeys(query(".d-editor-input"), "a :blonde_wo\t");
assert.ok(visible(".d-editor-preview"));
assert.strictEqual(
normalizeHtml(query(".d-editor-preview").innerHTML.trim()),
normalizeHtml(
@ -29,13 +27,31 @@ acceptance("Emoji", function (needs) {
);
});
test("emoji can be picked from the emoji-picker using the mouse", async function (assert) {
await visit("/t/internationalization-localization/280");
await click("#topic-footer-buttons .btn.create");
await simulateKeys(query(".d-editor-input"), "an :arrow");
// the 6th item in the list is the "more..."
await click(".autocomplete.ac-emoji ul li:nth-of-type(6)");
assert.dom(".emoji-picker.opened.has-filter").exists();
await click(".emoji-picker .results img:first-of-type");
assert.strictEqual(
normalizeHtml(query(".d-editor-preview").innerHTML.trim()),
normalizeHtml(
`<p>an <img src="/images/emoji/twitter/arrow_backward.png?v=${v}" title=":arrow_backward:" class="emoji" alt=":arrow_backward:" loading="lazy" width="20" height="20" style="aspect-ratio: 20 / 20;"></p>`
)
);
});
test("skin toned emoji is cooked properly", async function (assert) {
await visit("/t/internationalization-localization/280");
await click("#topic-footer-buttons .btn.create");
await simulateKeys(query(".d-editor-input"), "a :blonde_woman:t5:");
await fillIn(query(".d-editor-input"), "a :blonde_woman:t5:");
assert.ok(visible(".d-editor-preview"));
assert.strictEqual(
normalizeHtml(query(".d-editor-preview").innerHTML.trim()),
normalizeHtml(
@ -44,9 +60,7 @@ acceptance("Emoji", function (needs) {
);
});
needs.settings({
emoji_autocomplete_min_chars: 2,
});
needs.settings({ emoji_autocomplete_min_chars: 2 });
test("siteSetting:emoji_autocomplete_min_chars", async function (assert) {
await visit("/t/internationalization-localization/280");