@@ -128,12 +141,10 @@ export default class ChatMessageActionsMobile extends Component {
{{/each}}
{{/if}}
diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-message.gjs b/plugins/chat/assets/javascripts/discourse/components/chat-message.gjs
index 9f7ece30f10..b08676cc140 100644
--- a/plugins/chat/assets/javascripts/discourse/components/chat-message.gjs
+++ b/plugins/chat/assets/javascripts/discourse/components/chat-message.gjs
@@ -1,7 +1,7 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { Input } from "@ember/component";
-import { fn } from "@ember/helper";
+import { concat, fn } from "@ember/helper";
import { on } from "@ember/modifier";
import { action } from "@ember/object";
import { getOwner } from "@ember/owner";
@@ -13,6 +13,7 @@ import { service } from "@ember/service";
import { modifier } from "ember-modifier";
import { eq, lt, not } from "truth-helpers";
import DButton from "discourse/components/d-button";
+import EmojiPicker from "discourse/components/emoji-picker";
import concatClass from "discourse/helpers/concat-class";
import { applyValueTransformer } from "discourse/lib/transformer";
import { updateUserStatusOnMention } from "discourse/lib/update-user-status-on-mention";
@@ -57,14 +58,13 @@ export default class ChatMessage extends Component {
@service capabilities;
@service chat;
@service chatApi;
- @service chatEmojiReactionStore;
- @service chatEmojiPickerManager;
@service chatChannelPane;
@service chatThreadPane;
@service chatChannelsManager;
@service router;
@service toasts;
@service modal;
+ @service interactedChatMessage;
@tracked isActive = false;
@@ -305,6 +305,10 @@ export default class ChatMessage extends Component {
return;
}
+ if (this.interactedChatMessage.emojiPickerOpen) {
+ return;
+ }
+
if (!this.secondaryActionsIsExpanded) {
this._onMouseEnterMessageDebouncedHandler = discourseDebounce(
this,
@@ -324,9 +328,15 @@ export default class ChatMessage extends Component {
return;
}
- if (!this.secondaryActionsIsExpanded) {
- this._setActiveMessage();
+ if (this.secondaryActionsIsExpanded) {
+ return;
}
+
+ if (this.interactedChatMessage.emojiPickerOpen) {
+ return;
+ }
+
+ this._setActiveMessage();
}
@action
@@ -344,9 +354,16 @@ export default class ChatMessage extends Component {
) {
return;
}
- if (!this.secondaryActionsIsExpanded) {
- this.chat.activeMessage = null;
+
+ if (this.interactedChatMessage.emojiPickerOpen) {
+ return;
}
+
+ if (this.secondaryActionsIsExpanded) {
+ return;
+ }
+
+ this.chat.activeMessage = null;
}
@bind
@@ -522,6 +539,16 @@ export default class ChatMessage extends Component {
);
}
+ @action
+ onEmojiPickerClose() {
+ this.interactedChatMessage.emojiPickerOpen = false;
+ }
+
+ @action
+ onEmojiPickerShow() {
+ this.interactedChatMessage.emojiPickerOpen = true;
+ }
+
@action
stopMessageStreaming(message) {
this.chatApi.stopMessageStreaming(message.channel.id, message.id);
@@ -651,12 +678,13 @@ export default class ChatMessage extends Component {
{{/each}}
{{#if this.shouldRenderOpenEmojiPickerButton}}
-
{{/if}}
diff --git a/plugins/chat/assets/javascripts/discourse/components/chat/routes/channel-info-members.gjs b/plugins/chat/assets/javascripts/discourse/components/chat/routes/channel-info-members.gjs
index d8c2733c40b..838d423c116 100644
--- a/plugins/chat/assets/javascripts/discourse/components/chat/routes/channel-info-members.gjs
+++ b/plugins/chat/assets/javascripts/discourse/components/chat/routes/channel-info-members.gjs
@@ -6,6 +6,7 @@ import { action } from "@ember/object";
import { LinkTo } from "@ember/routing";
import { service } from "@ember/service";
import { modifier } from "ember-modifier";
+import FilterInput from "discourse/components/filter-input";
import isElementInViewport from "discourse/lib/is-element-in-viewport";
import DiscourseURL, { userPath } from "discourse/lib/url";
import autoFocus from "discourse/modifiers/auto-focus";
@@ -16,7 +17,6 @@ import { i18n } from "discourse-i18n";
import MessageCreator from "discourse/plugins/chat/discourse/components/chat/message-creator";
import { MODES } from "discourse/plugins/chat/discourse/components/chat/message-creator/constants";
import ChatUserInfo from "discourse/plugins/chat/discourse/components/chat-user-info";
-import DcFilterInput from "discourse/plugins/chat/discourse/components/dc-filter-input";
export default class ChatRouteChannelInfoMembers extends Component {
@service appEvents;
@@ -144,7 +144,7 @@ export default class ChatRouteChannelInfoMembers extends Component {
/>
{{else}}
-
\ No newline at end of file
diff --git a/plugins/chat/assets/javascripts/discourse/connectors/chat-composer-inline-buttons/emoji-picker.hbs b/plugins/chat/assets/javascripts/discourse/connectors/chat-composer-inline-buttons/emoji-picker.hbs
new file mode 100644
index 00000000000..7ec960c7750
--- /dev/null
+++ b/plugins/chat/assets/javascripts/discourse/connectors/chat-composer-inline-buttons/emoji-picker.hbs
@@ -0,0 +1,9 @@
+{{#if this.site.desktopView}}
+
+
+
+{{/if}}
\ No newline at end of file
diff --git a/plugins/chat/assets/javascripts/discourse/helpers/tonable-emoji-title.js b/plugins/chat/assets/javascripts/discourse/helpers/tonable-emoji-title.js
deleted file mode 100644
index 6c4c625e15d..00000000000
--- a/plugins/chat/assets/javascripts/discourse/helpers/tonable-emoji-title.js
+++ /dev/null
@@ -1,7 +0,0 @@
-export default function tonableEmojiTitle(emoji, diversity) {
- if (!emoji.tonable || diversity === 1) {
- return `:${emoji.name}:`;
- }
-
- return `:${emoji.name}:t${diversity}:`;
-}
diff --git a/plugins/chat/assets/javascripts/discourse/helpers/tonable-emoji-url.js b/plugins/chat/assets/javascripts/discourse/helpers/tonable-emoji-url.js
deleted file mode 100644
index 4d4aca6f301..00000000000
--- a/plugins/chat/assets/javascripts/discourse/helpers/tonable-emoji-url.js
+++ /dev/null
@@ -1,7 +0,0 @@
-export default function tonableEmojiUrl(emoji, scale) {
- if (!emoji.tonable || scale === 1) {
- return emoji.url;
- }
-
- return emoji.url.split(".png")[0] + `/${scale}.png`;
-}
diff --git a/plugins/chat/assets/javascripts/discourse/initializers/chat-setup.js b/plugins/chat/assets/javascripts/discourse/initializers/chat-setup.js
index 2f50b3aa15b..fd2a8ca6181 100644
--- a/plugins/chat/assets/javascripts/discourse/initializers/chat-setup.js
+++ b/plugins/chat/assets/javascripts/discourse/initializers/chat-setup.js
@@ -1,5 +1,6 @@
import { setOwner } from "@ember/owner";
import { service } from "@ember/service";
+import EmojiPickerDetached from "discourse/components/emoji-picker/detached";
import { number } from "discourse/lib/formatter";
import { withPluginApi } from "discourse/lib/plugin-api";
import { getOwnerWithFallback } from "discourse-common/lib/get-owner";
@@ -58,6 +59,37 @@ class ChatSetupInit {
api.registerHashtagType("channel", new ChannelHashtagType(owner));
+ if (this.siteSettings.enable_emoji) {
+ api.registerChatComposerButton({
+ label: "chat.emoji",
+ id: "emoji",
+ class: "chat-emoji-btn",
+ icon: "discourse-emojis",
+ position: "dropdown",
+ displayed: owner.lookup("service:site").mobileView,
+ action(context) {
+ const didSelectEmoji = (emoji) => {
+ const composer = owner.lookup(`service:chat-${context}-composer`);
+ composer.textarea.addText(
+ composer.textarea.getSelected(),
+ `:${emoji}:`
+ );
+ };
+
+ owner.lookup("service:menu").show(document.body, {
+ identifier: "emoji-picker",
+ groupIdentifier: "emoji-picker",
+ component: EmojiPickerDetached,
+ modalForMobile: true,
+ data: {
+ context: "chat",
+ didSelectEmoji,
+ },
+ });
+ },
+ });
+ }
+
api.registerChatComposerButton({
id: "chat-upload-btn",
icon: "far-image",
@@ -83,36 +115,6 @@ class ChatSetupInit {
});
}
- api.registerChatComposerButton({
- label: "chat.emoji",
- id: "emoji",
- class: "chat-emoji-btn",
- icon: "far-face-smile",
- position: this.site.desktopView ? "inline" : "dropdown",
- context: "channel",
- action() {
- const chatEmojiPickerManager = owner.lookup(
- "service:chat-emoji-picker-manager"
- );
- chatEmojiPickerManager.open({ context: "channel" });
- },
- });
-
- api.registerChatComposerButton({
- label: "chat.emoji",
- id: "channel-emoji",
- class: "chat-emoji-btn",
- icon: "discourse-emojis",
- position: "dropdown",
- context: "thread",
- action() {
- const chatEmojiPickerManager = owner.lookup(
- "service:chat-emoji-picker-manager"
- );
- chatEmojiPickerManager.open({ context: "thread" });
- },
- });
-
// we want to decorate the chat quote dates regardless
// of whether the current user has chat enabled
api.decorateCookedElement((elem) => {
diff --git a/plugins/chat/assets/javascripts/discourse/lib/chat-message-interactor.js b/plugins/chat/assets/javascripts/discourse/lib/chat-message-interactor.js
index f02bb0350cf..ed35de5f160 100644
--- a/plugins/chat/assets/javascripts/discourse/lib/chat-message-interactor.js
+++ b/plugins/chat/assets/javascripts/discourse/lib/chat-message-interactor.js
@@ -2,6 +2,7 @@ import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object";
import { getOwner, setOwner } from "@ember/owner";
import { service } from "@ember/service";
+import EmojiPickerDetached from "discourse/components/emoji-picker/detached";
import BookmarkModal from "discourse/components/modal/bookmark";
import FlagModal from "discourse/components/modal/flag";
import { popupAjaxError } from "discourse/lib/ajax-error";
@@ -14,9 +15,7 @@ import { i18n } from "discourse-i18n";
import { MESSAGE_CONTEXT_THREAD } from "discourse/plugins/chat/discourse/components/chat-message";
import ChatMessageFlag from "discourse/plugins/chat/discourse/lib/chat-message-flag";
import ChatMessage from "discourse/plugins/chat/discourse/models/chat-message";
-import ChatMessageReaction, {
- REACTIONS,
-} from "discourse/plugins/chat/discourse/models/chat-message-reaction";
+import { REACTIONS } from "discourse/plugins/chat/discourse/models/chat-message-reaction";
const removedSecondaryActions = new Set();
@@ -28,12 +27,10 @@ export function resetRemovedChatComposerSecondaryActions() {
removedSecondaryActions.clear();
}
-export default class ChatMessageInteractor {
+export default class ChatemojiReactions {
@service appEvents;
@service dialog;
@service chat;
- @service chatEmojiReactionStore;
- @service chatEmojiPickerManager;
@service chatChannelComposer;
@service chatThreadComposer;
@service chatChannelPane;
@@ -44,19 +41,18 @@ export default class ChatMessageInteractor {
@service router;
@service modal;
@service capabilities;
+ @service menu;
@service toasts;
+ @service interactedChatMessage;
@tracked message = null;
@tracked context = null;
- cachedFavoritesReactions = null;
-
constructor(owner, message, context) {
setOwner(this, owner);
this.message = message;
this.context = context;
- this.cachedFavoritesReactions = this.chatEmojiReactionStore.favorites;
}
get pane() {
@@ -65,22 +61,6 @@ export default class ChatMessageInteractor {
: this.chatChannelPane;
}
- get emojiReactions() {
- let favorites = this.cachedFavoritesReactions;
-
- // may be a {} if no defaults defined in some production builds
- if (!favorites || !favorites.slice) {
- return [];
- }
-
- return favorites.slice(0, 3).map((emoji) => {
- return (
- this.message.reactions.find((reaction) => reaction.emoji === emoji) ||
- ChatMessageReaction.create({ emoji })
- );
- });
- }
-
get canEdit() {
return (
!this.message.deletedAt &&
@@ -291,10 +271,6 @@ export default class ChatMessageInteractor {
this.chat.activeMessage = null;
}
- if (reactAction === REACTIONS.add) {
- this.chatEmojiReactionStore.track(`:${emoji}:`);
- }
-
this.pane.reacting = true;
this.message.react(
@@ -406,13 +382,29 @@ export default class ChatMessageInteractor {
}
@action
- openEmojiPicker(_, { target }) {
- const pickerState = {
- didSelectEmoji: this.selectReaction,
- trigger: target,
- context: "chat-channel-message",
- };
- this.chatEmojiPickerManager.open(pickerState);
+ async openEmojiPicker(trigger) {
+ this.interactedChatMessage.emojiPickerOpen = true;
+
+ await this.menu.show(trigger, {
+ identifier: "emoji-picker",
+ groupIdentifier: "emoji-picker",
+ component: EmojiPickerDetached,
+ onClose: () => {
+ this.interactedChatMessage.emojiPickerOpen = false;
+ },
+ data: {
+ context: `channel_${this.message.channel.id}`,
+ didSelectEmoji: (emoji) => {
+ this.selectReaction(emoji);
+ },
+ },
+ });
+ }
+
+ @action
+ async closeEmojiPicker() {
+ await this.menu.close("emoji-picker");
+ this.interactedChatMessage.emojiPickerOpen = false;
}
@bind
diff --git a/plugins/chat/assets/javascripts/discourse/lib/textarea-interactor.js b/plugins/chat/assets/javascripts/discourse/lib/textarea-interactor.js
index 4e7971bb90a..96de51770de 100644
--- a/plugins/chat/assets/javascripts/discourse/lib/textarea-interactor.js
+++ b/plugins/chat/assets/javascripts/discourse/lib/textarea-interactor.js
@@ -4,6 +4,7 @@ import { setOwner } from "@ember/owner";
import { next, schedule } from "@ember/runloop";
import { service } from "@ember/service";
import TextareaTextManipulation from "discourse/lib/textarea-text-manipulation";
+import { bind } from "discourse-common/utils/decorators";
// This class sole purpose is to provide a way to interact with the textarea
// using the existing TextareaTextManipulation mixin without using it directly
@@ -45,6 +46,7 @@ export default class TextareaInteractor extends EmberObject {
this.textarea.dispatchEvent(event);
}
+ @bind
blur() {
next(() => {
schedule("afterRender", () => {
@@ -53,6 +55,7 @@ export default class TextareaInteractor extends EmberObject {
});
}
+ @bind
focus(opts = { ensureAtEnd: false, refreshHeight: true, addText: null }) {
next(() => {
schedule("afterRender", () => {
@@ -80,6 +83,7 @@ export default class TextareaInteractor extends EmberObject {
});
}
+ @bind
ensureCaretAtEnd() {
schedule("afterRender", () => {
this.textarea.setSelectionRange(
@@ -89,6 +93,7 @@ export default class TextareaInteractor extends EmberObject {
});
}
+ @bind
refreshHeight() {
schedule("afterRender", () => {
// this is a quirk which forces us to `auto` first or textarea
@@ -101,18 +106,22 @@ export default class TextareaInteractor extends EmberObject {
});
}
+ @bind
getSelected() {
return this.textManipulation.getSelected(...arguments);
}
+ @bind
applySurround() {
return this.textManipulation.applySurround(...arguments);
}
+ @bind
addText() {
return this.textManipulation.addText(...arguments);
}
+ @bind
isInside() {
return this.textManipulation.isInside(...arguments);
}
diff --git a/plugins/chat/assets/javascripts/discourse/modifiers/chat/emoji-picker-scroll-listener.js b/plugins/chat/assets/javascripts/discourse/modifiers/chat/emoji-picker-scroll-listener.js
deleted file mode 100644
index e00e2cd44a0..00000000000
--- a/plugins/chat/assets/javascripts/discourse/modifiers/chat/emoji-picker-scroll-listener.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import { registerDestructor } from "@ember/destroyable";
-import { service } from "@ember/service";
-import Modifier from "ember-modifier";
-
-export default class EmojiPickerScrollListener extends Modifier {
- @service emojiPickerScrollObserver;
-
- element = null;
-
- constructor(owner, args) {
- super(owner, args);
- registerDestructor(this, (instance) => instance.cleanup());
- }
-
- modify(element) {
- this.element = element;
- this.emojiPickerScrollObserver.observe(element);
- }
-
- cleanup() {
- this.emojiPickerScrollObserver.unobserve(this.element);
- }
-}
diff --git a/plugins/chat/assets/javascripts/discourse/services/chat-channel-emoji-picker-manager.js b/plugins/chat/assets/javascripts/discourse/services/chat-channel-emoji-picker-manager.js
deleted file mode 100644
index fb3975ea87b..00000000000
--- a/plugins/chat/assets/javascripts/discourse/services/chat-channel-emoji-picker-manager.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import ChatEmojiPickerManager from "./chat-emoji-picker-manager";
-
-export default class ChatChannelEmojiPickerManager extends ChatEmojiPickerManager {}
diff --git a/plugins/chat/assets/javascripts/discourse/services/chat-emoji-picker-manager.js b/plugins/chat/assets/javascripts/discourse/services/chat-emoji-picker-manager.js
deleted file mode 100644
index ed96d50a280..00000000000
--- a/plugins/chat/assets/javascripts/discourse/services/chat-emoji-picker-manager.js
+++ /dev/null
@@ -1,91 +0,0 @@
-import { tracked } from "@glimmer/tracking";
-import { action } from "@ember/object";
-import Service, { service } from "@ember/service";
-import { Promise } from "rsvp";
-import { ajax } from "discourse/lib/ajax";
-import { popupAjaxError } from "discourse/lib/ajax-error";
-import { makeArray } from "discourse-common/lib/helpers";
-import discourseLater from "discourse-common/lib/later";
-import { bind } from "discourse-common/utils/decorators";
-
-const TRANSITION_TIME = 125; // CSS transition time
-const DEFAULT_VISIBLE_SECTIONS = ["favorites", "smileys_&_emotion"];
-const DEFAULT_LAST_SECTION = "favorites";
-
-export default class ChatEmojiPickerManager extends Service {
- @service appEvents;
-
- @tracked closing = false;
- @tracked loading = false;
- @tracked picker = null;
- @tracked emojis = null;
- @tracked visibleSections = DEFAULT_VISIBLE_SECTIONS;
- @tracked lastVisibleSection = DEFAULT_LAST_SECTION;
- @tracked element = null;
-
- get sections() {
- return !this.loading && this.emojis ? Object.keys(this.emojis) : [];
- }
-
- @bind
- closeExisting() {
- this.visibleSections = DEFAULT_VISIBLE_SECTIONS;
- this.lastVisibleSection = DEFAULT_LAST_SECTION;
- this.picker = null;
- }
-
- @bind
- close() {
- this.closing = true;
-
- discourseLater(() => {
- if (this.isDestroyed || this.isDestroying) {
- return;
- }
-
- this.visibleSections = DEFAULT_VISIBLE_SECTIONS;
- this.lastVisibleSection = DEFAULT_LAST_SECTION;
- this.closing = false;
- this.picker = null;
- }, TRANSITION_TIME);
- }
-
- addVisibleSections(sections) {
- this.visibleSections = makeArray(this.visibleSections)
- .concat(makeArray(sections))
- .uniq();
- }
-
- open(picker) {
- this.loadEmojis();
-
- if (this.picker) {
- if (this.picker.trigger === picker.trigger) {
- this.closeExisting();
- } else {
- this.closeExisting();
- this.picker = picker;
- }
- } else {
- this.picker = picker;
- }
- }
-
- @action
- loadEmojis() {
- if (this.emojis) {
- return Promise.resolve();
- }
-
- this.loading = true;
-
- return ajax("/chat/emojis.json")
- .then((emojis) => {
- this.emojis = emojis;
- })
- .catch(popupAjaxError)
- .finally(() => {
- this.loading = false;
- });
- }
-}
diff --git a/plugins/chat/assets/javascripts/discourse/services/chat-emoji-reaction-store.js b/plugins/chat/assets/javascripts/discourse/services/chat-emoji-reaction-store.js
deleted file mode 100644
index 86d3e9df315..00000000000
--- a/plugins/chat/assets/javascripts/discourse/services/chat-emoji-reaction-store.js
+++ /dev/null
@@ -1,92 +0,0 @@
-// This class is adapted from emoji-store class in core. We want to maintain separate emoji store for reactions in chat plugin.
-// https://github.com/discourse/discourse/blob/892f7e0506f3a4d40d9a59a4c926ff0a2aa0947e/app/assets/javascripts/discourse/app/services/emoji-store.js
-
-import Service, { service } from "@ember/service";
-import { disableImplicitInjections } from "discourse/lib/implicit-injections";
-import KeyValueStore from "discourse/lib/key-value-store";
-
-@disableImplicitInjections
-export default class ChatEmojiReactionStore extends Service {
- @service siteSettings;
-
- STORE_NAMESPACE = "discourse_chat_emoji_reaction_";
- MAX_DISPLAYED_EMOJIS = 20;
- MAX_TRACKED_EMOJIS = this.MAX_DISPLAYED_EMOJIS * 2;
- SKIN_TONE_STORE_KEY = "emojiSelectedDiversity";
- USER_EMOJIS_STORE_KEY = "emojiUsage";
-
- store = new KeyValueStore(this.STORE_NAMESPACE);
-
- constructor() {
- super(...arguments);
-
- if (!this.store.getObject(this.USER_EMOJIS_STORE_KEY)) {
- this.storedFavorites = [];
- }
- }
-
- get diversity() {
- return this.store.getObject(this.SKIN_TONE_STORE_KEY) || 1;
- }
-
- set diversity(value = 1) {
- this.store.setObject({ key: this.SKIN_TONE_STORE_KEY, value });
- this.notifyPropertyChange("diversity");
- }
-
- get storedFavorites() {
- let value = this.store.getObject(this.USER_EMOJIS_STORE_KEY) || [];
-
- if (value.length < 1) {
- if (!this.siteSettings.default_emoji_reactions) {
- value = [];
- } else {
- value = this.siteSettings.default_emoji_reactions
- .split("|")
- .filter(Boolean);
- }
-
- this.store.setObject({ key: this.USER_EMOJIS_STORE_KEY, value });
- }
-
- return value;
- }
-
- set storedFavorites(value) {
- this.store.setObject({ key: this.USER_EMOJIS_STORE_KEY, value });
- this.notifyPropertyChange("favorites");
- }
-
- get favorites() {
- const computedStored = [
- ...new Set(this._frequencySort(this.storedFavorites)),
- ];
-
- return computedStored.slice(0, this.MAX_DISPLAYED_EMOJIS);
- }
-
- set favorites(value = []) {
- this.store.setObject({ key: this.USER_EMOJIS_STORE_KEY, value });
- }
-
- track(code) {
- const normalizedCode = code.replace(/(^:)|(:$)/g, "");
- let recent = this.storedFavorites;
- recent.unshift(normalizedCode);
- recent.length = Math.min(recent.length, this.MAX_TRACKED_EMOJIS);
- this.storedFavorites = recent;
- }
-
- reset() {
- this.store.setObject({ key: this.USER_EMOJIS_STORE_KEY, value: [] });
- this.store.setObject({ key: this.SKIN_TONE_STORE_KEY, value: 1 });
- }
-
- _frequencySort(array = []) {
- const counters = array.reduce((obj, val) => {
- obj[val] = (obj[val] || 0) + 1;
- return obj;
- }, {});
- return Object.keys(counters).sort((a, b) => counters[b] - counters[a]);
- }
-}
diff --git a/plugins/chat/assets/javascripts/discourse/services/emoji-picker-scroll-observer.js b/plugins/chat/assets/javascripts/discourse/services/emoji-picker-scroll-observer.js
deleted file mode 100644
index 6270dd97b37..00000000000
--- a/plugins/chat/assets/javascripts/discourse/services/emoji-picker-scroll-observer.js
+++ /dev/null
@@ -1,70 +0,0 @@
-import { tracked } from "@glimmer/tracking";
-import Service, { service } from "@ember/service";
-import { bind } from "discourse-common/utils/decorators";
-
-export default class EmojiPickerScrollObserver extends Service {
- @service chatEmojiPickerManager;
-
- @tracked enabled = true;
- direction = "up";
- prevYPosition = 0;
-
- @bind
- _observerCallback(event) {
- if (!this.enabled) {
- return;
- }
-
- this._setScrollDirection(event.target);
-
- const visibleSections = [
- ...document.querySelectorAll(".chat-emoji-picker__section"),
- ].filter((sectionElement) =>
- this._isSectionVisibleInPicker(sectionElement, event.target)
- );
-
- if (visibleSections?.length) {
- let sectionElement;
-
- if (this.direction === "up" || this.prevYPosition < 50) {
- sectionElement = visibleSections.firstObject;
- } else {
- sectionElement = visibleSections.lastObject;
- }
-
- this.chatEmojiPickerManager.lastVisibleSection =
- sectionElement.dataset.section;
-
- this.chatEmojiPickerManager.addVisibleSections(
- visibleSections.map((s) => s.dataset.section)
- );
- }
- }
-
- observe(element) {
- element.addEventListener("scroll", this._observerCallback);
- }
-
- unobserve(element) {
- element.removeEventListener("scroll", this._observerCallback);
- }
-
- _setScrollDirection(target) {
- if (target.scrollTop > this.prevYPosition) {
- this.direction = "down";
- } else {
- this.direction = "up";
- }
-
- this.prevYPosition = target.scrollTop;
- }
-
- _isSectionVisibleInPicker(section, picker) {
- const { bottom, height, top } = section.getBoundingClientRect();
- const containerRect = picker.getBoundingClientRect();
-
- return top <= containerRect.top
- ? containerRect.top - top <= height
- : bottom - containerRect.bottom <= height;
- }
-}
diff --git a/plugins/chat/assets/javascripts/discourse/services/interacted-chat-message.js b/plugins/chat/assets/javascripts/discourse/services/interacted-chat-message.js
new file mode 100644
index 00000000000..8c4c3344bf5
--- /dev/null
+++ b/plugins/chat/assets/javascripts/discourse/services/interacted-chat-message.js
@@ -0,0 +1,7 @@
+import { tracked } from "@glimmer/tracking";
+import Service from "@ember/service";
+
+export default class InteractedChatMessage extends Service {
+ @tracked secondaryOptionsOpen = false;
+ @tracked emojiPickerOpen = false;
+}
diff --git a/plugins/chat/assets/stylesheets/common/base-common.scss b/plugins/chat/assets/stylesheets/common/base-common.scss
index aea8165b6d0..90790eeffb6 100644
--- a/plugins/chat/assets/stylesheets/common/base-common.scss
+++ b/plugins/chat/assets/stylesheets/common/base-common.scss
@@ -137,16 +137,6 @@ html.ios-device.keyboard-visible body #main-outlet .full-page-chat {
color: var(--primary-high);
font-size: var(--font-down-2);
}
-
- .emoji-picker {
- position: fixed;
- }
-
- &:hover {
- .chat-.chat-message-react-btn {
- display: inline-block;
- }
- }
}
.chat-emoji-avatar {
diff --git a/plugins/chat/assets/stylesheets/common/chat-browse.scss b/plugins/chat/assets/stylesheets/common/chat-browse.scss
index 854bfa0fef9..e89e9fda456 100644
--- a/plugins/chat/assets/stylesheets/common/chat-browse.scss
+++ b/plugins/chat/assets/stylesheets/common/chat-browse.scss
@@ -48,11 +48,11 @@
@include breakpoint(tablet) {
flex-direction: column;
- .dc-filter-input-container {
+ .filter-input-container {
margin-top: 1rem;
}
- .dc-filter-input-container,
+ .filter-input-container,
nav {
width: 100%;
}
diff --git a/plugins/chat/assets/stylesheets/common/chat-composer-dropdown.scss b/plugins/chat/assets/stylesheets/common/chat-composer-dropdown.scss
index c50d2f89e72..34edc0d146d 100644
--- a/plugins/chat/assets/stylesheets/common/chat-composer-dropdown.scss
+++ b/plugins/chat/assets/stylesheets/common/chat-composer-dropdown.scss
@@ -1,21 +1,9 @@
-.chat-composer.is-disabled {
- .no-touch & {
- .chat-composer-dropdown__trigger-btn:hover {
- cursor: default;
- .d-icon {
- color: var(--primary-high);
- }
- }
- }
-}
-
.chat-composer-dropdown__trigger-btn {
margin-left: 0.2rem;
- transition: transform 0.25s ease-in-out;
.d-icon {
padding: 5px;
- transition: transform 0.1s ease-in-out;
+
background: var(--primary-low);
border-radius: 100%;
}
@@ -30,17 +18,6 @@
background: none !important;
background-image: none !important;
}
-
- &:hover {
- transform: scale(1.1);
- }
-
- &.-expanded {
- .d-icon {
- transform: rotate(135deg);
- transform-origin: center center;
- }
- }
}
.chat-composer-dropdown__list {
@@ -52,9 +29,4 @@
.chat-composer-dropdown__action-btn {
width: 100%;
justify-content: flex-start;
- background: none;
-
- .d-icon {
- color: var(--primary);
- }
}
diff --git a/plugins/chat/assets/stylesheets/common/chat-composer.scss b/plugins/chat/assets/stylesheets/common/chat-composer.scss
index 4bc604b36c3..c691658de65 100644
--- a/plugins/chat/assets/stylesheets/common/chat-composer.scss
+++ b/plugins/chat/assets/stylesheets/common/chat-composer.scss
@@ -93,6 +93,7 @@
cursor: inherit;
@include chat-scrollbar();
white-space: pre-wrap !important;
+ min-width: 20px;
&[disabled] {
background: none;
diff --git a/plugins/chat/assets/stylesheets/common/chat-message-actions.scss b/plugins/chat/assets/stylesheets/common/chat-message-actions.scss
index dd57d596719..e876ae52fe5 100644
--- a/plugins/chat/assets/stylesheets/common/chat-message-actions.scss
+++ b/plugins/chat/assets/stylesheets/common/chat-message-actions.scss
@@ -12,11 +12,6 @@
border-radius: var(--d-border-radius);
display: flex;
- .emoji-picker-anchor {
- position: absolute;
- height: 34px;
- }
-
.link-to-message-btn {
.d-icon {
transition: all 0.25s ease-in-out;
@@ -34,7 +29,6 @@
.reply-btn,
.chat-message-thread-btn,
.bookmark-btn {
- margin-right: -1px;
padding: 0.5em 0;
width: 2.5em;
diff --git a/plugins/chat/assets/stylesheets/common/chat-message.scss b/plugins/chat/assets/stylesheets/common/chat-message.scss
index 11f0426c124..7111289392a 100644
--- a/plugins/chat/assets/stylesheets/common/chat-message.scss
+++ b/plugins/chat/assets/stylesheets/common/chat-message.scss
@@ -113,7 +113,7 @@
display: flex;
flex-wrap: wrap;
- .chat-message-react-btn {
+ .react-btn {
vertical-align: top;
padding: 0em 0.25em;
background: none;
@@ -166,7 +166,7 @@
&:hover {
.chat-message-reaction-list .chat-message-react-btn {
- display: inline-block;
+ visibility: visible;
}
}
@@ -250,6 +250,6 @@
}
.chat-message-reaction-list .chat-message-react-btn {
- display: none;
+ visibility: hidden;
}
}
diff --git a/plugins/chat/assets/stylesheets/common/index.scss b/plugins/chat/assets/stylesheets/common/index.scss
index 70b67999592..aa6dc98230a 100644
--- a/plugins/chat/assets/stylesheets/common/index.scss
+++ b/plugins/chat/assets/stylesheets/common/index.scss
@@ -18,7 +18,6 @@
@import "chat-composer-button";
@import "chat-message-blocks";
@import "chat-drawer";
-@import "chat-emoji-picker";
@import "chat-form";
@import "chat-index";
@import "chat-mention-warnings";
@@ -41,7 +40,6 @@
@import "chat-upload-drop-zone";
@import "chat-transcript";
@import "core-extensions";
-@import "dc-filter-input";
@import "incoming-chat-webhooks";
@import "reviewable-chat-message";
@import "chat-thread-list-item";
diff --git a/plugins/chat/assets/stylesheets/desktop/chat-composer-uploads.scss b/plugins/chat/assets/stylesheets/desktop/chat-composer-uploads.scss
index b2d096aa0e6..c2211a38e23 100644
--- a/plugins/chat/assets/stylesheets/desktop/chat-composer-uploads.scss
+++ b/plugins/chat/assets/stylesheets/desktop/chat-composer-uploads.scss
@@ -6,8 +6,3 @@
}
}
}
-
-.chat-composer-dropdown__trigger-btn {
- align-self: flex-end;
- height: 50px;
-}
diff --git a/plugins/chat/assets/stylesheets/mobile/chat-composer-dropdown.scss b/plugins/chat/assets/stylesheets/mobile/chat-composer-dropdown.scss
deleted file mode 100644
index c41497911b9..00000000000
--- a/plugins/chat/assets/stylesheets/mobile/chat-composer-dropdown.scss
+++ /dev/null
@@ -1,18 +0,0 @@
-[data-content][data-identifier="chat-composer-dropdown__menu"] {
- width: calc(100% - 2rem);
- max-width: unset;
-
- .fk-d-menu__inner-content {
- width: 100%;
- }
-
- .chat-composer-dropdown {
- &__list {
- width: 100%;
- }
- &__action-btn {
- padding-block: 0.75rem;
- gap: 0.5rem;
- }
- }
-}
diff --git a/plugins/chat/assets/stylesheets/mobile/chat-emoji-picker.scss b/plugins/chat/assets/stylesheets/mobile/chat-emoji-picker.scss
deleted file mode 100644
index c169432dcce..00000000000
--- a/plugins/chat/assets/stylesheets/mobile/chat-emoji-picker.scss
+++ /dev/null
@@ -1,26 +0,0 @@
-.chat-channel-message-emoji-picker-connector {
- position: relative;
-
- .chat-emoji-picker {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- height: 50vh;
- width: 100%;
- box-shadow: var(--shadow-card);
- z-index: z("header") + 2;
- max-width: 100vw;
- box-sizing: border-box;
-
- &__backdrop {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background: rgba(var(--always-black-rgb), 0.75);
- z-index: z("header") + 1;
- }
- }
-}
diff --git a/plugins/chat/assets/stylesheets/mobile/index.scss b/plugins/chat/assets/stylesheets/mobile/index.scss
index f4bec3eb02f..6b3e0e9cd3c 100644
--- a/plugins/chat/assets/stylesheets/mobile/index.scss
+++ b/plugins/chat/assets/stylesheets/mobile/index.scss
@@ -1,12 +1,10 @@
@import "base-mobile";
@import "chat-channel";
@import "chat-composer";
-@import "chat-composer-dropdown";
@import "chat-index";
@import "chat-message-actions";
@import "chat-message";
@import "chat-selection-manager";
-@import "chat-emoji-picker";
@import "chat-composer-upload";
@import "chat-thread";
@import "chat-threads-list";
diff --git a/plugins/chat/config/locales/server.en.yml b/plugins/chat/config/locales/server.en.yml
index 5cba050e7a7..de3c24610fa 100644
--- a/plugins/chat/config/locales/server.en.yml
+++ b/plugins/chat/config/locales/server.en.yml
@@ -15,7 +15,6 @@ en:
chat_minimum_message_length: "Minimum number of characters for a chat message."
chat_allow_uploads: "Allow uploads in public chat channels and direct message channels."
chat_archive_destination_topic_status: "The status that the destination topic should be once a channel archive is completed. This only applies when the destination topic is a new topic, not an existing one."
- default_emoji_reactions: "Default emoji reactions for chat messages. Add up to 5 emojis for quick reaction."
direct_message_enabled_groups: "Allow users within these groups to create user-to-user Personal Chats. Note: staff can always create Personal Chats, and users will be able to reply to Personal Chats initiated by users who have permission to create them."
chat_message_flag_allowed_groups: "Users in these groups are allowed to flag chat messages. Note that admins and moderators can always flag chat messages."
max_mentions_per_chat_message: "Maximum number of @name notifications a user can use in a chat message."
diff --git a/plugins/chat/config/routes.rb b/plugins/chat/config/routes.rb
index 783592a212e..56037280fdb 100644
--- a/plugins/chat/config/routes.rb
+++ b/plugins/chat/config/routes.rb
@@ -91,8 +91,6 @@ Chat::Engine.routes.draw do
put "/user_chat_enabled/:user_id" => "chat#set_user_chat_status"
post "/:chat_channel_id" => "api/channel_messages#create"
- get "/emojis" => "emojis#index"
-
base_c_route = "/c/:channel_title/:channel_id"
get base_c_route => "chat#respond", :as => "channel"
get "#{base_c_route}/:message_id" => "chat#respond"
diff --git a/plugins/chat/config/settings.yml b/plugins/chat/config/settings.yml
index 59e65d0116f..2f396475162 100644
--- a/plugins/chat/config/settings.yml
+++ b/plugins/chat/config/settings.yml
@@ -70,10 +70,6 @@ chat:
default: 0.5
min: 0
max: 1
- default_emoji_reactions:
- type: emoji_list
- default: +1|heart|tada
- client: true
chat_minimum_message_length:
type: integer
default: 1
diff --git a/plugins/chat/spec/system/chat_composer_spec.rb b/plugins/chat/spec/system/chat_composer_spec.rb
index 34625dd392d..4049f72687e 100644
--- a/plugins/chat/spec/system/chat_composer_spec.rb
+++ b/plugins/chat/spec/system/chat_composer_spec.rb
@@ -64,7 +64,7 @@ RSpec.describe "Chat composer", type: :system do
click_link(I18n.t("js.composer.more_emoji"))
- expect(find(".chat-emoji-picker .dc-filter-input").value).to eq("gri")
+ expect(find(".emoji-picker .filter-input").value).to eq("gri")
end
xit "filters with the prefilled input" do
@@ -73,8 +73,8 @@ RSpec.describe "Chat composer", type: :system do
click_link(I18n.t("js.composer.more_emoji"))
- expect(page).to have_selector(".chat-emoji-picker [data-emoji='fr']")
- expect(page).to have_no_selector(".chat-emoji-picker [data-emoji='grinning']")
+ expect(page).to have_selector(".emoji-picker [data-emoji='fr']")
+ expect(page).to have_no_selector(".emoji-picker [data-emoji='grinning']")
end
xit "replaces the partially typed emoji with the selected" do
diff --git a/plugins/chat/spec/system/page_objects/chat/browse.rb b/plugins/chat/spec/system/page_objects/chat/browse.rb
index 789daa1e3f4..6867ee62b76 100644
--- a/plugins/chat/spec/system/page_objects/chat/browse.rb
+++ b/plugins/chat/spec/system/page_objects/chat/browse.rb
@@ -24,7 +24,7 @@ module PageObjects
end
def search(query)
- component.find(".dc-filter-input").fill_in(with: query)
+ component.find(".filter-input").fill_in(with: query)
component.has_css?(".loading-container .spinner", wait: 0)
component.has_no_css?(".loading-container .spinner")
end
diff --git a/plugins/chat/spec/system/page_objects/chat/chat_channel.rb b/plugins/chat/spec/system/page_objects/chat/chat_channel.rb
index 15b31e7a473..bd426a38476 100644
--- a/plugins/chat/spec/system/page_objects/chat/chat_channel.rb
+++ b/plugins/chat/spec/system/page_objects/chat/chat_channel.rb
@@ -83,6 +83,17 @@ module PageObjects
# Scroll to top of message so that the actions are not hidden
page.scroll_to(message, align: :top)
message.hover
+ message
+ end
+
+ def react_to_message(message, emoji_name = nil)
+ message = hover_message(message)
+
+ if emoji_name
+ message.find(".chat-message-actions [data-emoji-name=\"#{emoji_name}\"]").click
+ else
+ message.find(".react-btn").click
+ end
end
def bookmark_message(message)
diff --git a/plugins/chat/spec/system/page_objects/chat/chat_thread.rb b/plugins/chat/spec/system/page_objects/chat/chat_thread.rb
index 9dbc132fb0d..d23aab5e5c6 100644
--- a/plugins/chat/spec/system/page_objects/chat/chat_thread.rb
+++ b/plugins/chat/spec/system/page_objects/chat/chat_thread.rb
@@ -123,7 +123,21 @@ module PageObjects
end
def hover_message(message)
- message_by_id(message.id).hover
+ message = message_by_id(message.id)
+ # Scroll to top of message so that the actions are not hidden
+ page.scroll_to(message, align: :top)
+ message.hover
+ message
+ end
+
+ def react_to_message(message, emoji_name = nil)
+ message = hover_message(message)
+
+ if emoji_name
+ message.find(".react-btn").click
+ else
+ message.find(".chat-message-actions [data-emoji-name=\"#{emoji_name}\"]").click
+ end
end
def message_by_id(id)
diff --git a/plugins/chat/spec/system/react_to_message_spec.rb b/plugins/chat/spec/system/react_to_message_spec.rb
index c0f27c016ee..c209aee523c 100644
--- a/plugins/chat/spec/system/react_to_message_spec.rb
+++ b/plugins/chat/spec/system/react_to_message_spec.rb
@@ -62,9 +62,8 @@ RSpec.describe "React to message", type: :system do
it "adds a reaction" do
sign_in(current_user)
chat.visit_channel(category_channel_1)
- channel.hover_message(message_1)
- find(".chat-message-react-btn").click
- find(".chat-emoji-picker [data-emoji=\"grimacing\"]").click
+ channel.react_to_message(message_1)
+ find(".emoji-picker [data-emoji=\"grimacing\"]").click
expect(channel).to have_reaction(message_1, "grimacing")
end
@@ -83,8 +82,8 @@ RSpec.describe "React to message", type: :system do
using_session(:tab_1) do
channel.hover_message(message_1)
- find(".chat-message-react-btn").click
- find(".chat-emoji-picker [data-emoji=\"#{reaction}\"]").click
+ find(".react-btn").click
+ find(".emoji-picker [data-emoji=\"#{reaction}\"]").click
expect(channel).to have_reaction(message_1, reaction)
end
@@ -101,7 +100,7 @@ RSpec.describe "React to message", type: :system do
chat.visit_channel(category_channel_1)
channel.hover_message(message_1)
find(".chat-message-actions .react-btn").click
- find(".chat-emoji-picker [data-emoji=\"nerd_face\"]").click
+ find(".emoji-picker [data-emoji=\"nerd_face\"]").click
expect(channel).to have_reaction(message_1, reaction_1.emoji)
end
@@ -114,8 +113,8 @@ RSpec.describe "React to message", type: :system do
channel.hover_message(message_1)
find(".chat-message-actions .react-btn").click
- expect(page).to have_no_css(".chat-emoji-picker [data-emoji=\"fu\"]")
- expect(page).to have_no_css(".chat-emoji-picker [data-emoji=\"middle_finger\"]")
+ expect(page).to have_no_css(".emoji-picker [data-emoji=\"fu\"]")
+ expect(page).to have_no_css(".emoji-picker [data-emoji=\"middle_finger\"]")
end
end
diff --git a/plugins/chat/test/javascripts/acceptance/chat-composer-test.js b/plugins/chat/test/javascripts/acceptance/chat-composer-test.js
index 2b4661aca91..72a28528739 100644
--- a/plugins/chat/test/javascripts/acceptance/chat-composer-test.js
+++ b/plugins/chat/test/javascripts/acceptance/chat-composer-test.js
@@ -20,7 +20,7 @@ acceptance("Discourse Chat - Composer", function (needs) {
server.get("/chat/:id/messages.json", () =>
helper.response({ chat_messages: [], meta: {} })
);
- server.get("/chat/emojis.json", () =>
+ server.get("/emojis.json", () =>
helper.response({ favorites: [{ name: "grinning" }] })
);
server.post("/chat/drafts", () => {
diff --git a/plugins/chat/test/javascripts/components/chat-composer-placeholder-test.js b/plugins/chat/test/javascripts/components/chat-composer-placeholder-test.js
index 18d560d0ab7..40419c46ef0 100644
--- a/plugins/chat/test/javascripts/components/chat-composer-placeholder-test.js
+++ b/plugins/chat/test/javascripts/components/chat-composer-placeholder-test.js
@@ -13,7 +13,7 @@ module(
setupRenderingTest(hooks);
test("direct message to self shows Jot something down", async function (assert) {
- pretender.get("/chat/emojis.json", () => [200, [], {}]);
+ pretender.get("/emojis.json", () => [200, [], {}]);
this.currentUser.set("id", 1);
this.channel = ChatChannel.create({
@@ -31,8 +31,8 @@ module(
);
});
- test("direct message to multiple folks shows their names when not a group", async function (assert) {
- pretender.get("/chat/emojis.json", () => [200, [], {}]);
+ test("direct message to multiple folks shows their names when not a group", async function (assert) {
+ pretender.get("/emojis.json", () => [200, [], {}]);
this.channel = ChatChannel.create({
chatable_type: "DirectMessage",
@@ -79,7 +79,7 @@ module(
});
test("message to channel shows send message to channel name", async function (assert) {
- pretender.get("/chat/emojis.json", () => [200, [], {}]);
+ pretender.get("/emojis.json", () => [200, [], {}]);
this.channel = ChatChannel.create({
chatable_type: "Category",
diff --git a/plugins/chat/test/javascripts/components/chat-emoji-picker-test.js b/plugins/chat/test/javascripts/components/chat-emoji-picker-test.js
deleted file mode 100644
index bf8ee56a054..00000000000
--- a/plugins/chat/test/javascripts/components/chat-emoji-picker-test.js
+++ /dev/null
@@ -1,307 +0,0 @@
-import { click, fillIn, render, triggerKeyEvent } from "@ember/test-helpers";
-import hbs from "htmlbars-inline-precompile";
-import { module, test } from "qunit";
-import { setupRenderingTest } from "discourse/tests/helpers/component-test";
-import pretender, { response } from "discourse/tests/helpers/create-pretender";
-
-function emojisResponse() {
- return {
- favorites: [
- {
- name: "grinning",
- tonable: false,
- url: "/images/emoji/twitter/grinning.png?v=12",
- group: "smileys_\u0026_emotion",
- search_aliases: ["smiley_cat", "star_struck"],
- },
- ],
- "smileys_&_emotion": [
- {
- name: "grinning",
- tonable: false,
- url: "/images/emoji/twitter/grinning.png?v=12",
- group: "smileys_\u0026_emotion",
- search_aliases: ["smiley_cat", "star_struck"],
- },
- ],
- "people_&_body": [
- {
- name: "raised_hands",
- tonable: true,
- url: "/images/emoji/twitter/raised_hands.png?v=12",
- group: "people_&_body",
- search_aliases: [],
- },
- {
- name: "man_rowing_boat",
- tonable: true,
- url: "/images/emoji/twitter/man_rowing_boat.png?v=12",
- group: "people_&_body",
- search_aliases: [],
- },
- ],
- objects: [
- {
- name: "womans_clothes",
- tonable: false,
- url: "/images/emoji/twitter/womans_clothes.png?v=12",
- group: "objects",
- search_aliases: [],
- },
- ],
- };
-}
-
-module("Discourse Chat | Component | chat-emoji-picker", function (hooks) {
- setupRenderingTest(hooks);
-
- hooks.afterEach(function () {
- this.emojiReactionStore.diversity = 1;
- });
-
- hooks.beforeEach(function () {
- pretender.get("/chat/emojis.json", () => response(emojisResponse()));
-
- this.chatEmojiPickerManager = this.container.lookup(
- "service:chat-emoji-picker-manager"
- );
- this.chatEmojiPickerManager.open(() => {});
- this.chatEmojiPickerManager.addVisibleSections([
- "smileys_&_emotion",
- "people_&_body",
- "objects",
- ]);
-
- this.emojiReactionStore = this.container.lookup(
- "service:chat-emoji-reaction-store"
- );
- });
-
- test("When displaying navigation", async function (assert) {
- await render(hbs``);
-
- assert
- .dom(`.chat-emoji-picker__section-btn.active[data-section="favorites"]`)
- .exists("it renders first section as active");
- assert
- .dom(`.chat-emoji-picker__section-btn[data-section="smileys_&_emotion"]`)
- .exists();
- assert
- .dom(`.chat-emoji-picker__section-btn[data-section="people_&_body"]`)
- .exists();
- assert
- .dom(`.chat-emoji-picker__section-btn[data-section="objects"]`)
- .exists();
- });
-
- test("When changing tone scale", async function (assert) {
- await render(hbs``);
- await click(".chat-emoji-picker__fitzpatrick-modifier-btn.current.t1");
- await click(".chat-emoji-picker__fitzpatrick-modifier-btn.t6");
-
- assert
- .dom(`img[src="/images/emoji/twitter/raised_hands/6.png"]`)
- .exists("it applies the tone to emojis");
- assert
- .dom(".chat-emoji-picker__fitzpatrick-modifier-btn.current.t6")
- .exists("it changes the current scale to t6");
- });
-
- test("When requesting section", async function (assert) {
- await render(hbs``);
-
- assert.strictEqual(
- document.querySelector("#ember-testing-container").scrollTop,
- 0
- );
-
- await click(`.chat-emoji-picker__section-btn[data-section="objects"]`);
-
- assert.true(
- document.querySelector(".chat-emoji-picker__scrollable-content")
- .scrollTop > 0,
- "it scrolls to the section"
- );
- });
-
- test("When filtering emojis", async function (assert) {
- await render(hbs``);
- await fillIn(".dc-filter-input", "grinning");
-
- assert
- .dom(".chat-emoji-picker__section.filtered > img")
- .exists({ count: 1 }, "it filters the emojis list");
- assert
- .dom('.chat-emoji-picker__section.filtered > img[alt="grinning"]')
- .exists("it filters the correct emoji");
-
- await fillIn(".dc-filter-input", "Grinning");
-
- assert
- .dom('.chat-emoji-picker__section.filtered > img[alt="grinning"]')
- .exists("it is case insensitive");
-
- await fillIn(".dc-filter-input", "smiley_cat");
-
- assert
- .dom('.chat-emoji-picker__section.filtered > img[alt="grinning"]')
- .exists("it filters the correct emoji using search alias");
- });
-
- test("When selecting an emoji", async function (assert) {
- this.didSelectEmoji = (emoji) => assert.step(emoji);
-
- await render(
- hbs``
- );
- await click('img.emoji[data-emoji="grinning"]');
-
- assert.verifySteps(["grinning"]);
- });
-
- test("When navigating sections", async function (assert) {
- await render(hbs``);
-
- await triggerKeyEvent(document.activeElement, "keydown", "ArrowDown");
- assert
- .dom(document.activeElement)
- .hasAttribute(
- "data-emoji",
- "grinning",
- "ArrowDown focuses on the first favorite emoji"
- );
-
- await triggerKeyEvent(document.activeElement, "keydown", "ArrowDown");
- await triggerKeyEvent(document.activeElement, "keydown", "ArrowDown");
- assert
- .dom(document.activeElement)
- .hasAttribute(
- "data-emoji",
- "raised_hands",
- "ArrowDown focuses on the first emoji form the third section"
- );
-
- await triggerKeyEvent(document.activeElement, "keydown", "ArrowRight");
- assert
- .dom(document.activeElement)
- .hasAttribute(
- "data-emoji",
- "man_rowing_boat",
- "ArrowRight focuses on the emoji at the right"
- );
-
- await triggerKeyEvent(document.activeElement, "keydown", "ArrowLeft");
- assert
- .dom(document.activeElement)
- .hasAttribute(
- "data-emoji",
- "raised_hands",
- "ArrowLeft focuses on the emoji at the left"
- );
-
- await triggerKeyEvent(document.activeElement, "keydown", "ArrowUp");
- assert
- .dom(document.activeElement)
- .hasAttribute(
- "data-emoji",
- "grinning",
- "ArrowUp focuses on the first emoji form the second section"
- );
- });
-
- test("When navigating filtered emojis", async function (assert) {
- await render(hbs``);
- await fillIn(".dc-filter-input", "man");
-
- await triggerKeyEvent(document.activeElement, "keydown", "ArrowDown");
- assert
- .dom(document.activeElement)
- .hasAttribute(
- "data-emoji",
- "man_rowing_boat",
- "ArrowDown focuses on the first filtered emoji"
- );
-
- await triggerKeyEvent(document.activeElement, "keydown", "ArrowRight");
- assert
- .dom(document.activeElement)
- .hasAttribute(
- "data-emoji",
- "womans_clothes",
- "ArrowRight focuses on the emoji at the right"
- );
-
- await triggerKeyEvent(document.activeElement, "keydown", "ArrowLeft");
- assert
- .dom(document.activeElement)
- .hasAttribute(
- "data-emoji",
- "man_rowing_boat",
- "ArrowLeft focuses on the emoji at the left"
- );
- });
-
- test("When selecting a toned an emoji", async function (assert) {
- this.didSelectEmoji = (emoji) => assert.step(emoji);
-
- await render(
- hbs``
- );
- this.emojiReactionStore.diversity = 1;
- await click('img.emoji[data-emoji="man_rowing_boat"]');
-
- this.emojiReactionStore.diversity = 2;
- await click('img.emoji[data-emoji="man_rowing_boat"]');
-
- assert.verifySteps(["man_rowing_boat", "man_rowing_boat:t2"]);
- });
-
- test("When opening the picker", async function (assert) {
- await render(hbs``);
-
- assert.dom(document.activeElement).hasClass("dc-filter-input");
- });
-
- test("When hovering an emoji", async function (assert) {
- await render(hbs``);
-
- assert
- .dom(
- '.chat-emoji-picker__section[data-section="people_&_body"] img.emoji:nth-child(1)'
- )
- .hasAttribute("title", ":raised_hands:", "first emoji has a title");
-
- assert
- .dom(
- '.chat-emoji-picker__section[data-section="people_&_body"] img.emoji:nth-child(2)'
- )
- .hasAttribute("title", ":man_rowing_boat:", "second emoji has a title");
-
- await fillIn(".dc-filter-input", "grinning");
- assert
- .dom('img.emoji[data-emoji="grinning"]')
- .hasAttribute("title", ":grinning:", "filtered emoji have a title");
-
- this.emojiReactionStore.diversity = 1;
- await render(hbs``);
-
- assert
- .dom('img.emoji[data-emoji="man_rowing_boat"]')
- .hasAttribute(
- "title",
- ":man_rowing_boat:",
- "it has a title without the scale as diversity value is 1"
- );
-
- this.emojiReactionStore.diversity = 2;
- await render(hbs``);
-
- assert
- .dom('img.emoji[data-emoji="man_rowing_boat"]')
- .hasAttribute(
- "title",
- ":man_rowing_boat:t2:",
- "it has a title with the scale"
- );
- });
-});
diff --git a/plugins/chat/test/javascripts/components/dc-filter-input-test.js b/plugins/chat/test/javascripts/components/dc-filter-input-test.js
deleted file mode 100644
index 5f3010bdd30..00000000000
--- a/plugins/chat/test/javascripts/components/dc-filter-input-test.js
+++ /dev/null
@@ -1,55 +0,0 @@
-import { fillIn, render, triggerEvent } from "@ember/test-helpers";
-import hbs from "htmlbars-inline-precompile";
-import { module, test } from "qunit";
-import { setupRenderingTest } from "discourse/tests/helpers/component-test";
-
-module("Discourse Chat | Component | dc-filter-input", function (hooks) {
- setupRenderingTest(hooks);
-
- test("Left icon", async function (assert) {
- await render(hbs``);
-
- assert.dom(".d-icon-bell.-left").exists();
- });
-
- test("Right icon", async function (assert) {
- await render(hbs``);
-
- assert.dom(".d-icon-bell.-right").exists();
- });
-
- test("containerClass argument", async function (assert) {
- await render(hbs``);
-
- assert.dom(".dc-filter-input-container.foo").exists();
- });
-
- test("Html attributes", async function (assert) {
- await render(hbs``);
-
- assert.dom('.dc-filter-input[data-foo="1"]').exists();
- assert.dom('.dc-filter-input[placeholder="bar"]').exists();
- });
-
- test("Filter action", async function (assert) {
- this.set("value", null);
- this.set("action", (event) => {
- this.set("value", event.target.value);
- });
- await render(hbs``);
- await fillIn(".dc-filter-input", "foo");
-
- assert.strictEqual(this.value, "foo");
- });
-
- test("Focused state", async function (assert) {
- await render(hbs``);
- await triggerEvent(".dc-filter-input", "focusin");
-
- assert.dom(".dc-filter-input-container.is-focused").exists();
-
- await triggerEvent(".dc-filter-input", "focusout");
-
- assert.dom(".dc-filter-input-container.is-focused").doesNotExist();
- });
-});
diff --git a/plugins/chat/test/javascripts/unit/helpers/format-chat-date-test.gjs b/plugins/chat/test/javascripts/unit/helpers/format-chat-date-test.gjs
deleted file mode 100644
index 3609b70a458..00000000000
--- a/plugins/chat/test/javascripts/unit/helpers/format-chat-date-test.gjs
+++ /dev/null
@@ -1,42 +0,0 @@
-import { hash } from "@ember/helper";
-import { getOwner } from "@ember/owner";
-import { render } from "@ember/test-helpers";
-import { module, test } from "qunit";
-import { setupRenderingTest } from "discourse/tests/helpers/component-test";
-import formatChatDate from "discourse/plugins/chat/discourse/helpers/format-chat-date";
-import ChatFabricators from "discourse/plugins/chat/discourse/lib/fabricators";
-
-module("Discourse Chat | Unit | Helpers | format-chat-date", function (hooks) {
- setupRenderingTest(hooks);
-
- test("link to chat message", async function (assert) {
- const channel = new ChatFabricators(getOwner(this)).channel();
- const message = new ChatFabricators(getOwner(this)).message({ channel });
-
- await render({{formatChatDate message}});
-
- assert
- .dom(".chat-time")
- .hasAttribute("href", `/chat/c/-/${channel.id}/${message.id}`);
- });
-
- test("link to chat message thread", async function (assert) {
- const channel = new ChatFabricators(getOwner(this)).channel();
- const thread = new ChatFabricators(getOwner(this)).thread();
- const message = new ChatFabricators(getOwner(this)).message({
- channel,
- thread,
- });
-
- await render(
- {{formatChatDate message (hash threadContext=true)}}
- );
-
- assert
- .dom(".chat-time")
- .hasAttribute(
- "href",
- `/chat/c/-/${channel.id}/t/${thread.id}/${message.id}`
- );
- });
-});
diff --git a/plugins/chat/test/javascripts/unit/helpers/tonable-emoji-title-test.gjs b/plugins/chat/test/javascripts/unit/helpers/tonable-emoji-title-test.gjs
deleted file mode 100644
index 1d6c964b338..00000000000
--- a/plugins/chat/test/javascripts/unit/helpers/tonable-emoji-title-test.gjs
+++ /dev/null
@@ -1,41 +0,0 @@
-import { render } from "@ember/test-helpers";
-import { module, test } from "qunit";
-import { setupRenderingTest } from "discourse/tests/helpers/component-test";
-import tonableEmojiTitle from "discourse/plugins/chat/discourse/helpers/tonable-emoji-title";
-
-module(
- "Discourse Chat | Unit | Helpers | tonable-emoji-title",
- function (hooks) {
- setupRenderingTest(hooks);
-
- test("When emoji is not tonable", async function (assert) {
- const emoji = { name: "foo", tonable: false };
- const diversity = 1;
- await render(
- {{tonableEmojiTitle emoji diversity}}
- );
-
- assert.dom("span").hasText(":foo:");
- });
-
- test("When emoji is tonable and diversity is 1", async function (assert) {
- const emoji = { name: "foo", tonable: true };
- const diversity = 1;
- await render(
- {{tonableEmojiTitle emoji diversity}}
- );
-
- assert.dom("span").hasText(":foo:");
- });
-
- test("When emoji is tonable and diversity is greater than 1", async function (assert) {
- const emoji = { name: "foo", tonable: true };
- const diversity = 2;
- await render(
- {{tonableEmojiTitle emoji diversity}}
- );
-
- assert.dom("span").hasText(":foo:t2:");
- });
- }
-);
diff --git a/plugins/chat/test/javascripts/unit/lib/chat-emoji-reaction-store-test.js b/plugins/chat/test/javascripts/unit/lib/chat-emoji-reaction-store-test.js
deleted file mode 100644
index 0eb98423452..00000000000
--- a/plugins/chat/test/javascripts/unit/lib/chat-emoji-reaction-store-test.js
+++ /dev/null
@@ -1,127 +0,0 @@
-import { getOwner } from "@ember/owner";
-import { setupTest } from "ember-qunit";
-import { module, test } from "qunit";
-
-module("Discourse Chat | Unit | chat-emoji-reaction-store", function (hooks) {
- setupTest(hooks);
-
- hooks.beforeEach(function () {
- this.siteSettings = getOwner(this).lookup("service:site-settings");
- this.chatEmojiReactionStore = getOwner(this).lookup(
- "service:chat-emoji-reaction-store"
- );
-
- this.chatEmojiReactionStore.reset();
- });
-
- hooks.afterEach(function () {
- this.chatEmojiReactionStore.reset();
- });
-
- test("defaults", function (assert) {
- assert.deepEqual(
- this.chatEmojiReactionStore.favorites,
- this.siteSettings.default_emoji_reactions.split("|").filter((val) => val)
- );
- });
-
- test("diversity", function (assert) {
- assert.strictEqual(this.chatEmojiReactionStore.diversity, 1);
-
- this.chatEmojiReactionStore.diversity = 2;
-
- assert.strictEqual(this.chatEmojiReactionStore.diversity, 2);
- });
-
- test("#favorites with defaults", function (assert) {
- this.siteSettings.default_emoji_reactions = "smile|heart|tada";
-
- assert.deepEqual(this.chatEmojiReactionStore.favorites, [
- "smile",
- "heart",
- "tada",
- ]);
- });
-
- test("#favorites", function (assert) {
- this.chatEmojiReactionStore.storedFavorites = ["grinning"];
-
- assert.deepEqual(this.chatEmojiReactionStore.favorites, ["grinning"]);
- });
-
- test("#favorites when tracking multiple times the same emoji", function (assert) {
- this.chatEmojiReactionStore.storedFavorites = [
- "grinning",
- "yum",
- "not_yum",
- "yum",
- ];
-
- assert.deepEqual(
- this.chatEmojiReactionStore.favorites,
- ["yum", "grinning", "not_yum"],
- "it favors count over order"
- );
- });
-
- test("#favorites when reaching displayed limit", function (assert) {
- this.chatEmojiReactionStore.storedFavorites = [];
- [...Array(this.chatEmojiReactionStore.MAX_TRACKED_EMOJIS)].forEach(
- (_, index) => {
- this.chatEmojiReactionStore.track("yum" + index);
- }
- );
- this.chatEmojiReactionStore.track("grinning");
-
- assert.strictEqual(
- this.chatEmojiReactionStore.favorites.length,
- this.chatEmojiReactionStore.MAX_DISPLAYED_EMOJIS,
- "it enforces the max length"
- );
- });
-
- test("#storedFavorites", function (assert) {
- this.chatEmojiReactionStore.storedFavorites = [];
- this.chatEmojiReactionStore.track("yum");
-
- assert.deepEqual(
- this.chatEmojiReactionStore.storedFavorites,
- ["yum"].concat(this.siteSettings.default_emoji_reactions.split("|"))
- );
- });
-
- test("#storedFavorites when tracking different emojis", function (assert) {
- this.chatEmojiReactionStore.storedFavorites = [];
- this.chatEmojiReactionStore.track("yum");
- this.chatEmojiReactionStore.track("not_yum");
- this.chatEmojiReactionStore.track("yum");
- this.chatEmojiReactionStore.track("grinning");
-
- assert.deepEqual(
- this.chatEmojiReactionStore.storedFavorites,
- ["grinning", "yum", "not_yum", "yum"].concat(
- this.siteSettings.default_emoji_reactions.split("|")
- ),
- "it ensures last in is first"
- );
- });
-
- test("#storedFavorites when tracking an emoji after reaching the limit", function (assert) {
- this.chatEmojiReactionStore.storedFavorites = [];
- [...Array(this.chatEmojiReactionStore.MAX_TRACKED_EMOJIS)].forEach(() => {
- this.chatEmojiReactionStore.track("yum");
- });
- this.chatEmojiReactionStore.track("grinning");
-
- assert.strictEqual(
- this.chatEmojiReactionStore.storedFavorites.length,
- this.chatEmojiReactionStore.MAX_TRACKED_EMOJIS,
- "it enforces the max length"
- );
- assert.strictEqual(
- this.chatEmojiReactionStore.storedFavorites.firstObject,
- "grinning",
- "it correctly stores the last tracked emoji"
- );
- });
-});
diff --git a/plugins/chat/test/javascripts/unit/services/chat-emoji-picker-manager-test.js b/plugins/chat/test/javascripts/unit/services/chat-emoji-picker-manager-test.js
deleted file mode 100644
index 435d491ff9b..00000000000
--- a/plugins/chat/test/javascripts/unit/services/chat-emoji-picker-manager-test.js
+++ /dev/null
@@ -1,125 +0,0 @@
-import { getOwner } from "@ember/owner";
-import { settled } from "@ember/test-helpers";
-import { setupTest } from "ember-qunit";
-import { module, test } from "qunit";
-import pretender from "discourse/tests/helpers/create-pretender";
-
-function emojisResponse() {
- return { favorites: [{ name: "sad" }] };
-}
-
-module(
- "Discourse Chat | Unit | Service | chat-emoji-picker-manager",
- function (hooks) {
- setupTest(hooks);
-
- hooks.beforeEach(function () {
- pretender.get("/chat/emojis.json", () => {
- return [200, {}, emojisResponse()];
- });
-
- this.manager = getOwner(this).lookup("service:chat-emoji-picker-manager");
- });
-
- hooks.afterEach(function () {
- this.manager.close();
- });
-
- test("addVisibleSections", async function (assert) {
- this.manager.addVisibleSections(["favorites", "objects"]);
-
- assert.deepEqual(this.manager.visibleSections, [
- "favorites",
- "smileys_&_emotion",
- "objects",
- ]);
- });
-
- test("sections", async function (assert) {
- assert.deepEqual(this.manager.sections, []);
-
- this.manager.open({});
-
- assert.deepEqual(this.manager.sections, []);
-
- await settled();
-
- assert.deepEqual(this.manager.sections, ["favorites"]);
- });
-
- test("open", async function (assert) {
- this.manager.open({ context: "chat-composer" });
-
- assert.true(this.manager.loading);
- assert.deepEqual(this.manager.picker, {
- context: "chat-composer",
- });
- assert.strictEqual(this.manager.picker.context, "chat-composer");
- assert.deepEqual(this.manager.visibleSections, [
- "favorites",
- "smileys_&_emotion",
- ]);
- assert.strictEqual(this.manager.lastVisibleSection, "favorites");
-
- await settled();
-
- assert.deepEqual(this.manager.emojis, emojisResponse());
- assert.false(this.manager.loading);
- });
-
- test("closeExisting", async function (assert) {
- this.manager.open({ context: "channel-composer", trigger: "foo" });
- this.manager.addVisibleSections("objects");
- this.manager.lastVisibleSection = "objects";
- this.manager.open({ context: "thread-composer", trigger: "bar" });
-
- assert.strictEqual(
- this.manager.picker.context,
- "thread-composer",
- "it resets the picker to latest picker"
- );
- assert.deepEqual(
- this.manager.visibleSections,
- ["favorites", "smileys_&_emotion"],
- "it resets sections"
- );
- assert.strictEqual(
- this.manager.lastVisibleSection,
- "favorites",
- "it resets last visible section"
- );
- });
-
- test("close", async function (assert) {
- this.manager.open({ context: "channel-composer" });
-
- assert.deepEqual(this.manager.picker, {
- context: "channel-composer",
- });
-
- this.manager.addVisibleSections("objects");
- this.manager.lastVisibleSection = "objects";
- this.manager.close();
-
- assert.true(this.manager.closing);
- assert.deepEqual(this.manager.picker, {
- context: "channel-composer",
- });
-
- await settled();
-
- assert.strictEqual(this.manager.picker, null);
- assert.false(this.manager.closing);
- assert.deepEqual(
- this.manager.visibleSections,
- ["favorites", "smileys_&_emotion"],
- "it resets visible sections"
- );
- assert.strictEqual(
- this.manager.lastVisibleSection,
- "favorites",
- "it resets last visible section"
- );
- });
- }
-);
diff --git a/plugins/chat/spec/requests/emojis_controller_spec.rb b/spec/requests/emojis_controller_spec.rb
similarity index 76%
rename from plugins/chat/spec/requests/emojis_controller_spec.rb
rename to spec/requests/emojis_controller_spec.rb
index 54ca1d1394f..8c3890d0896 100644
--- a/plugins/chat/spec/requests/emojis_controller_spec.rb
+++ b/spec/requests/emojis_controller_spec.rb
@@ -1,13 +1,9 @@
# frozen_string_literal: true
-RSpec.describe Chat::EmojisController do
+RSpec.describe EmojisController do
fab!(:user_1) { Fabricate(:user) }
- before do
- SiteSetting.chat_enabled = true
- SiteSetting.chat_allowed_groups = Group::AUTO_GROUPS[:everyone]
- sign_in(user_1)
- end
+ before { sign_in(user_1) }
describe "#index" do
before do
@@ -22,7 +18,7 @@ RSpec.describe Chat::EmojisController do
end
it "returns the emojis list" do
- get "/chat/emojis.json"
+ get "/emojis.json"
expect(response.status).to eq(200)
expect(response.parsed_body.keys).to eq(
diff --git a/spec/system/emojis/emoji_deny_list_spec.rb b/spec/system/emojis/emoji_deny_list_spec.rb
index 90900bde268..76ea2de2150 100644
--- a/spec/system/emojis/emoji_deny_list_spec.rb
+++ b/spec/system/emojis/emoji_deny_list_spec.rb
@@ -51,7 +51,7 @@ describe "Emoji deny list", type: :system do
topic_page.visit_topic_and_open_composer(topic)
expect(composer).to be_opened
- composer.click_toolbar_button("insert-emoji")
+ composer.click_toolbar_button("insert-composer-emoji")
expect(composer.emoji_picker).to be_visible
expect(emoji_picker).to have_no_emoji("fu")
diff --git a/spec/system/page_objects/components/form_kit.rb b/spec/system/page_objects/components/form_kit.rb
index e5e0d87a3b5..ad96667c231 100644
--- a/spec/system/page_objects/components/form_kit.rb
+++ b/spec/system/page_objects/components/form_kit.rb
@@ -125,7 +125,7 @@ module PageObjects
selector.dispatchEvent(new Event("input", { bubbles: true, cancelable: true }));
JS
when "menu"
- trigger = component.find(".fk-d-menu__trigger.form-kit__control-menu")
+ trigger = component.find(".fk-d-menu__trigger.form-kit__control-menu-trigger")
trigger.click
menu = find("[aria-labelledby='#{trigger["id"]}']")
item = menu.find(".form-kit__control-menu-item[data-value='#{value}'] .btn")