diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-skeleton.js b/plugins/chat/assets/javascripts/discourse/components/chat-skeleton.js
index 6af83cf2e41..3710d8dcc9b 100644
--- a/plugins/chat/assets/javascripts/discourse/components/chat-skeleton.js
+++ b/plugins/chat/assets/javascripts/discourse/components/chat-skeleton.js
@@ -4,13 +4,9 @@ import { htmlSafe } from "@ember/template";
export default class ChatSkeleton extends Component {
get placeholders() {
return Array.from({ length: 15 }, () => {
- return {
- image: this.#randomIntFromInterval(1, 10) === 5,
- rows: Array.from({ length: this.#randomIntFromInterval(1, 5) }, () => {
- return htmlSafe(`width: ${this.#randomIntFromInterval(20, 95)}%`);
- }),
- reactions: Array.from({ length: this.#randomIntFromInterval(0, 3) }),
- };
+ return Array.from({ length: this.#randomIntFromInterval(1, 5) }, () => {
+ return htmlSafe(`width: ${this.#randomIntFromInterval(20, 95)}%`);
+ });
});
}
diff --git a/plugins/chat/assets/javascripts/discourse/components/full-page-chat.hbs b/plugins/chat/assets/javascripts/discourse/components/full-page-chat.hbs
index c5c56e5fb51..33661579b6c 100644
--- a/plugins/chat/assets/javascripts/discourse/components/full-page-chat.hbs
+++ b/plugins/chat/assets/javascripts/discourse/components/full-page-chat.hbs
@@ -1,6 +1,7 @@
{{#if this.chat.activeChannel}}
{{/if}}
\ No newline at end of file
diff --git a/plugins/chat/assets/javascripts/discourse/components/full-page-chat.js b/plugins/chat/assets/javascripts/discourse/components/full-page-chat.js
index 94d9c7039f7..1fb7ad3c57e 100644
--- a/plugins/chat/assets/javascripts/discourse/components/full-page-chat.js
+++ b/plugins/chat/assets/javascripts/discourse/components/full-page-chat.js
@@ -1,6 +1,79 @@
-import Component from "@glimmer/component";
+import Component from "@ember/component";
+import { bind } from "discourse-common/utils/decorators";
+import { action } from "@ember/object";
import { inject as service } from "@ember/service";
-export default class FullPageChat extends Component {
- @service chat;
-}
+export default Component.extend({
+ tagName: "",
+ router: service(),
+ chat: service(),
+
+ init() {
+ this._super(...arguments);
+ },
+
+ didInsertElement() {
+ this._super(...arguments);
+
+ this._scrollSidebarToBottom();
+ document.addEventListener("keydown", this._autoFocusChatComposer);
+ },
+
+ willDestroyElement() {
+ this._super(...arguments);
+
+ document.removeEventListener("keydown", this._autoFocusChatComposer);
+ },
+
+ @bind
+ _autoFocusChatComposer(event) {
+ if (
+ !event.key ||
+ // Handles things like Enter, Tab, Shift
+ event.key.length > 1 ||
+ // Don't need to focus if the user is beginning a shortcut.
+ event.metaKey ||
+ event.ctrlKey ||
+ // Space's key comes through as ' ' so it's not covered by event.key
+ event.code === "Space" ||
+ // ? is used for the keyboard shortcut modal
+ event.key === "?"
+ ) {
+ return;
+ }
+
+ if (
+ !event.target ||
+ /^(INPUT|TEXTAREA|SELECT)$/.test(event.target.tagName)
+ ) {
+ return;
+ }
+
+ event.preventDefault();
+ event.stopPropagation();
+
+ const composer = document.querySelector(".chat-composer-input");
+ if (composer && !this.chat.activeChannel.isDraft) {
+ this.appEvents.trigger("chat:insert-text", event.key);
+ composer.focus();
+ }
+ },
+
+ _scrollSidebarToBottom() {
+ if (!this.teamsSidebarOn) {
+ return;
+ }
+
+ const sidebarScroll = document.querySelector(
+ ".sidebar-container .scroll-wrapper"
+ );
+ if (sidebarScroll) {
+ sidebarScroll.scrollTop = sidebarScroll.scrollHeight;
+ }
+ },
+
+ @action
+ navigateToIndex() {
+ this.router.transitionTo("chat.index");
+ },
+});
diff --git a/plugins/chat/assets/javascripts/discourse/controllers/chat-channel.js b/plugins/chat/assets/javascripts/discourse/controllers/chat-channel.js
index 7984545c101..734778d843b 100644
--- a/plugins/chat/assets/javascripts/discourse/controllers/chat-channel.js
+++ b/plugins/chat/assets/javascripts/discourse/controllers/chat-channel.js
@@ -1,11 +1,10 @@
import Controller from "@ember/controller";
import { inject as service } from "@ember/service";
-import { tracked } from "@glimmer/tracking";
export default class ChatChannelController extends Controller {
@service chat;
- @tracked targetMessageId = null;
+ targetMessageId = null;
// Backwards-compatibility
queryParams = ["messageId"];
diff --git a/plugins/chat/assets/javascripts/discourse/controllers/create-channel.js b/plugins/chat/assets/javascripts/discourse/controllers/create-channel.js
index 31bc13b5514..17d698cb8db 100644
--- a/plugins/chat/assets/javascripts/discourse/controllers/create-channel.js
+++ b/plugins/chat/assets/javascripts/discourse/controllers/create-channel.js
@@ -36,7 +36,6 @@ export default class CreateChannelController extends Controller.extend(
categoryPermissionsHint = null;
autoJoinUsers = null;
autoJoinWarning = "";
- loadingPermissionHint = false;
@notEmpty("category") categorySelected;
@gt("siteSettings.max_chat_auto_joined_users", 0) autoJoinAvailable;
@@ -154,8 +153,6 @@ export default class CreateChannelController extends Controller.extend(
if (category) {
const fullSlug = this._buildCategorySlug(category);
- this.set("loadingPermissionHint", true);
-
return this.chatApi
.categoryPermissions(category.id)
.then((catPermissions) => {
@@ -197,9 +194,6 @@ export default class CreateChannelController extends Controller.extend(
}
this.set("categoryPermissionsHint", htmlSafe(hint));
- })
- .finally(() => {
- this.set("loadingPermissionHint", false);
});
} else {
this.set("categoryPermissionsHint", DEFAULT_HINT);
diff --git a/plugins/chat/assets/javascripts/discourse/helpers/format-chat-date.js b/plugins/chat/assets/javascripts/discourse/helpers/format-chat-date.js
index 5d91f205e4f..c31a86ef042 100644
--- a/plugins/chat/assets/javascripts/discourse/helpers/format-chat-date.js
+++ b/plugins/chat/assets/javascripts/discourse/helpers/format-chat-date.js
@@ -7,8 +7,8 @@ import User from "discourse/models/user";
registerUnbound("format-chat-date", function (message, mode) {
const currentUser = User.current();
const tz = currentUser ? currentUser.user_option.timezone : moment.tz.guess();
- const date = moment(new Date(message.createdAt), tz);
- const url = getURL(`/chat/c/-/${message.channelId}/${message.id}`);
+ const date = moment(new Date(message.created_at), tz);
+ const url = getURL(`/chat/c/-/${message.chat_channel_id}/${message.id}`);
const title = date.format(I18n.t("dates.long_with_year"));
const display =
diff --git a/plugins/chat/assets/javascripts/discourse/initializers/chat-cook-function.js b/plugins/chat/assets/javascripts/discourse/initializers/chat-cook-function.js
deleted file mode 100644
index f628c478633..00000000000
--- a/plugins/chat/assets/javascripts/discourse/initializers/chat-cook-function.js
+++ /dev/null
@@ -1,32 +0,0 @@
-import ChatMessage from "discourse/plugins/chat/discourse/models/chat-message";
-import { generateCookFunction } from "discourse/lib/text";
-import simpleCategoryHashMentionTransform from "discourse/plugins/chat/discourse/lib/simple-category-hash-mention-transform";
-
-export default {
- name: "chat-cook-function",
-
- before: "chat-setup",
-
- initialize(container) {
- const site = container.lookup("service:site");
-
- const markdownOptions = {
- featuresOverride:
- site.markdown_additional_options?.chat?.limited_pretty_text_features,
- markdownItRules:
- site.markdown_additional_options?.chat
- ?.limited_pretty_text_markdown_rules,
- hashtagTypesInPriorityOrder: site.hashtag_configurations["chat-composer"],
- hashtagIcons: site.hashtag_icons,
- };
-
- generateCookFunction(markdownOptions).then((cookFunction) => {
- ChatMessage.cookFunction = (raw) => {
- return simpleCategoryHashMentionTransform(
- cookFunction(raw),
- site.categories
- );
- };
- });
- },
-};
diff --git a/plugins/chat/assets/javascripts/discourse/initializers/chat-setup.js b/plugins/chat/assets/javascripts/discourse/initializers/chat-setup.js
index b0ad79f345f..69ee6ab84ee 100644
--- a/plugins/chat/assets/javascripts/discourse/initializers/chat-setup.js
+++ b/plugins/chat/assets/javascripts/discourse/initializers/chat-setup.js
@@ -10,7 +10,6 @@ const MIN_REFRESH_DURATION_MS = 180000; // 3 minutes
export default {
name: "chat-setup",
-
initialize(container) {
this.chatService = container.lookup("service:chat");
this.siteSettings = container.lookup("service:site-settings");
@@ -20,7 +19,6 @@ export default {
if (!this.chatService.userCanChat) {
return;
}
-
withPluginApi("0.12.1", (api) => {
api.registerChatComposerButton({
id: "chat-upload-btn",
diff --git a/plugins/chat/assets/javascripts/discourse/lib/chat-message-flag.js b/plugins/chat/assets/javascripts/discourse/lib/chat-message-flag.js
index 9bd86b4ab40..60a20c2206a 100644
--- a/plugins/chat/assets/javascripts/discourse/lib/chat-message-flag.js
+++ b/plugins/chat/assets/javascripts/discourse/lib/chat-message-flag.js
@@ -38,7 +38,7 @@ export default class ChatMessageFlag {
let flagsAvailable = site.flagTypes;
flagsAvailable = flagsAvailable.filter((flag) => {
- return model.availableFlags.includes(flag.name_key);
+ return model.available_flags.includes(flag.name_key);
});
// "message user" option should be at the top
diff --git a/plugins/chat/assets/javascripts/discourse/models/chat-channel.js b/plugins/chat/assets/javascripts/discourse/models/chat-channel.js
index 15be14f7dd7..c6e75e9edd3 100644
--- a/plugins/chat/assets/javascripts/discourse/models/chat-channel.js
+++ b/plugins/chat/assets/javascripts/discourse/models/chat-channel.js
@@ -7,7 +7,6 @@ import { tracked } from "@glimmer/tracking";
import slugifyChannel from "discourse/plugins/chat/discourse/lib/slugify-channel";
import ChatThreadsManager from "discourse/plugins/chat/discourse/lib/chat-threads-manager";
import { getOwner } from "discourse-common/lib/get-owner";
-import { TrackedArray } from "@ember-compat/tracked-built-ins";
export const CHATABLE_TYPES = {
directMessageChannel: "DirectMessage",
@@ -55,16 +54,6 @@ export default class ChatChannel extends RestModel {
@tracked chatableType;
@tracked status;
@tracked activeThread;
- @tracked messages = new TrackedArray();
- @tracked lastMessageSentAt;
- @tracked canDeleteOthers;
- @tracked canDeleteSelf;
- @tracked canFlag;
- @tracked canLoadMoreFuture;
- @tracked canLoadMorePast;
- @tracked canModerate;
- @tracked userSilenced;
- @tracked draft;
threadsManager = new ChatThreadsManager(getOwner(this));
@@ -85,11 +74,11 @@ export default class ChatChannel extends RestModel {
}
get isDirectMessageChannel() {
- return this.chatableType === CHATABLE_TYPES.directMessageChannel;
+ return this.chatable_type === CHATABLE_TYPES.directMessageChannel;
}
get isCategoryChannel() {
- return this.chatableType === CHATABLE_TYPES.categoryChannel;
+ return this.chatable_type === CHATABLE_TYPES.categoryChannel;
}
get isOpen() {
@@ -116,57 +105,6 @@ export default class ChatChannel extends RestModel {
return this.currentUserMembership.following;
}
- get visibleMessages() {
- return this.messages.filter((message) => message.visible);
- }
-
- set details(details) {
- this.canDeleteOthers = details.can_delete_others ?? false;
- this.canDeleteSelf = details.can_delete_self ?? false;
- this.canFlag = details.can_flag ?? false;
- this.canModerate = details.can_moderate ?? false;
- if (details.can_load_more_future !== undefined) {
- this.canLoadMoreFuture = details.can_load_more_future;
- }
- if (details.can_load_more_past !== undefined) {
- this.canLoadMorePast = details.can_load_more_past;
- }
- this.userSilenced = details.user_silenced ?? false;
- this.status = details.channel_status;
- this.channelMessageBusLastId = details.channel_message_bus_last_id;
- }
-
- clearMessages() {
- this.messages.clear();
-
- this.canLoadMoreFuture = null;
- this.canLoadMorePast = null;
- }
-
- appendMessages(messages) {
- this.messages.pushObjects(messages);
- }
-
- prependMessages(messages) {
- this.messages.unshiftObjects(messages);
- }
-
- findMessage(messageId) {
- return this.messages.find(
- (message) => message.id === parseInt(messageId, 10)
- );
- }
-
- removeMessage(message) {
- return this.messages.removeObject(message);
- }
-
- findStagedMessage(stagedMessageId) {
- return this.messages.find(
- (message) => message.stagedId === stagedMessageId
- );
- }
-
canModifyMessages(user) {
if (user.staff) {
return !STAFF_READONLY_STATUSES.includes(this.status);
@@ -189,10 +127,6 @@ export default class ChatChannel extends RestModel {
return;
}
- if (this.currentUserMembership.last_read_message_id >= messageId) {
- return;
- }
-
return ajax(`/chat/${this.id}/read/${messageId}.json`, {
method: "PUT",
}).then(() => {
@@ -208,17 +142,12 @@ ChatChannel.reopenClass({
this._initUserModels(args);
this._initUserMembership(args);
- this._remapKey(args, "chatable_type", "chatableType");
- this._remapKey(args, "memberships_count", "membershipsCount");
- this._remapKey(args, "last_message_sent_at", "lastMessageSentAt");
+ args.chatableType = args.chatable_type;
+ args.membershipsCount = args.memberships_count;
return this._super(args);
},
- _remapKey(obj, oldKey, newKey) {
- delete Object.assign(obj, { [newKey]: obj[oldKey] })[oldKey];
- },
-
_initUserModels(args) {
if (args.chatable?.users?.length) {
for (let i = 0; i < args.chatable?.users?.length; i++) {
diff --git a/plugins/chat/assets/javascripts/discourse/models/chat-message-draft.js b/plugins/chat/assets/javascripts/discourse/models/chat-message-draft.js
deleted file mode 100644
index 00709add3f3..00000000000
--- a/plugins/chat/assets/javascripts/discourse/models/chat-message-draft.js
+++ /dev/null
@@ -1,62 +0,0 @@
-import { tracked } from "@glimmer/tracking";
-
-export default class ChatMessageDraft {
- static create(args = {}) {
- return new ChatMessageDraft(args ?? {});
- }
-
- @tracked uploads;
- @tracked message;
- @tracked _replyToMsg;
-
- constructor(args = {}) {
- this.message = args.message ?? "";
- this.uploads = args.uploads ?? [];
- this.replyToMsg = args.replyToMsg;
- }
-
- get replyToMsg() {
- return this._replyToMsg;
- }
-
- set replyToMsg(message) {
- this._replyToMsg = message
- ? {
- id: message.id,
- excerpt: message.excerpt,
- user: {
- id: message.user.id,
- name: message.user.name,
- avatar_template: message.user.avatar_template,
- username: message.user.username,
- },
- }
- : null;
- }
-
- toJSON() {
- if (
- this.message?.length === 0 &&
- this.uploads?.length === 0 &&
- !this.replyToMsg
- ) {
- return null;
- }
-
- const data = {};
-
- if (this.uploads?.length > 0) {
- data.uploads = this.uploads;
- }
-
- if (this.message?.length > 0) {
- data.message = this.message;
- }
-
- if (this.replyToMsg) {
- data.replyToMsg = this.replyToMsg;
- }
-
- return JSON.stringify(data);
- }
-}
diff --git a/plugins/chat/assets/javascripts/discourse/models/chat-message-reaction.js b/plugins/chat/assets/javascripts/discourse/models/chat-message-reaction.js
deleted file mode 100644
index db1b7a6cecb..00000000000
--- a/plugins/chat/assets/javascripts/discourse/models/chat-message-reaction.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import { tracked } from "@glimmer/tracking";
-import User from "discourse/models/user";
-import { TrackedArray } from "@ember-compat/tracked-built-ins";
-
-export default class ChatMessageReaction {
- static create(args = {}) {
- return new ChatMessageReaction(args);
- }
-
- @tracked count = 0;
- @tracked reacted = false;
- @tracked users = [];
-
- constructor(args = {}) {
- this.messageId = args.messageId;
- this.count = args.count;
- this.emoji = args.emoji;
- this.users = this.#initUsersModels(args.users);
- this.reacted = args.reacted;
- }
-
- #initUsersModels(users = []) {
- return new TrackedArray(
- users.map((user) => {
- if (user instanceof User) {
- return user;
- }
-
- return User.create(user);
- })
- );
- }
-}
diff --git a/plugins/chat/assets/javascripts/discourse/models/chat-message.js b/plugins/chat/assets/javascripts/discourse/models/chat-message.js
index c11f9b23c7d..8d0c644b5f7 100644
--- a/plugins/chat/assets/javascripts/discourse/models/chat-message.js
+++ b/plugins/chat/assets/javascripts/discourse/models/chat-message.js
@@ -1,193 +1,26 @@
+import RestModel from "discourse/models/rest";
import User from "discourse/models/user";
-import { cached, tracked } from "@glimmer/tracking";
-import { TrackedArray, TrackedObject } from "@ember-compat/tracked-built-ins";
-import ChatMessageReaction from "discourse/plugins/chat/discourse/models/chat-message-reaction";
-import Bookmark from "discourse/models/bookmark";
-import I18n from "I18n";
-import guid from "pretty-text/guid";
+import EmberObject from "@ember/object";
-export default class ChatMessage {
- static cookFunction = null;
+export default class ChatMessage extends RestModel {}
- static create(channel, args = {}) {
- return new ChatMessage(channel, args);
- }
+ChatMessage.reopenClass({
+ create(args = {}) {
+ this._initReactions(args);
+ this._initUserModel(args);
- static createStagedMessage(channel, args = {}) {
- args.staged_id = guid();
- return new ChatMessage(channel, args);
- }
+ return this._super(args);
+ },
- @tracked id;
- @tracked error;
- @tracked selected;
- @tracked channel;
- @tracked stagedId;
- @tracked channelId;
- @tracked createdAt;
- @tracked deletedAt;
- @tracked uploads;
- @tracked excerpt;
- @tracked message;
- @tracked threadId;
- @tracked reactions;
- @tracked reviewableId;
- @tracked user;
- @tracked cooked;
- @tracked inReplyTo;
- @tracked expanded;
- @tracked bookmark;
- @tracked userFlagStatus;
- @tracked hidden;
- @tracked version = 0;
- @tracked edited;
- @tracked chatWebhookEvent = new TrackedObject();
- @tracked mentionWarning;
- @tracked availableFlags;
- @tracked newest = false;
+ _initReactions(args) {
+ args.reactions = EmberObject.create(args.reactions || {});
+ },
- constructor(channel, args = {}) {
- this.channel = channel;
- this.id = args.id;
- this.newest = args.newest;
- this.edited = args.edited;
- this.availableFlags = args.available_flags;
- this.hidden = args.hidden;
- this.threadId = args.thread_id;
- this.channelId = args.chat_channel_id;
- this.chatWebhookEvent = args.chat_webhook_event;
- this.createdAt = args.created_at;
- this.deletedAt = args.deleted_at;
- this.excerpt = args.excerpt;
- this.reviewableId = args.reviewable_id;
- this.userFlagStatus = args.user_flag_status;
- this.inReplyTo = args.in_reply_to
- ? ChatMessage.create(channel, args.in_reply_to)
- : null;
- this.message = args.message;
- this.cooked = args.cooked || ChatMessage.cookFunction(this.message);
- this.reactions = this.#initChatMessageReactionModel(
- args.id,
- args.reactions
- );
- this.stagedId = args.staged_id;
- this.uploads = new TrackedArray(args.uploads || []);
- this.user = this.#initUserModel(args.user);
- this.bookmark = args.bookmark ? Bookmark.create(args.bookmark) : null;
- }
-
- get read() {
- return this.channel.currentUserMembership?.last_read_message_id >= this.id;
- }
-
- get firstMessageOfTheDayAt() {
- if (!this.previousMessage) {
- return this.#calendarDate(this.createdAt);
+ _initUserModel(args) {
+ if (!args.user || args.user instanceof User) {
+ return;
}
- if (
- !this.#areDatesOnSameDay(
- new Date(this.previousMessage.createdAt),
- new Date(this.createdAt)
- )
- ) {
- return this.#calendarDate(this.createdAt);
- }
- }
-
- #calendarDate(date) {
- return moment(date).calendar(moment(), {
- sameDay: `[${I18n.t("chat.chat_message_separator.today")}]`,
- lastDay: `[${I18n.t("chat.chat_message_separator.yesterday")}]`,
- lastWeek: "LL",
- sameElse: "LL",
- });
- }
-
- @cached
- get index() {
- return this.channel.messages.indexOf(this);
- }
-
- @cached
- get previousMessage() {
- return this.channel?.messages?.objectAt?.(this.index - 1);
- }
-
- @cached
- get nextMessage() {
- return this.channel?.messages?.objectAt?.(this.index + 1);
- }
-
- get staged() {
- return this.stagedId?.length > 0;
- }
-
- react(emoji, action, actor, currentUserId) {
- const selfReaction = actor.id === currentUserId;
- const existingReaction = this.reactions.find(
- (reaction) => reaction.emoji === emoji
- );
-
- if (existingReaction) {
- if (action === "add") {
- if (selfReaction && existingReaction.reacted) {
- return false;
- }
-
- existingReaction.count = existingReaction.count + 1;
- if (selfReaction) {
- existingReaction.reacted = true;
- }
- existingReaction.users.pushObject(actor);
- } else {
- existingReaction.count = existingReaction.count - 1;
-
- if (selfReaction) {
- existingReaction.reacted = false;
- }
-
- if (existingReaction.count === 0) {
- this.reactions.removeObject(existingReaction);
- } else {
- existingReaction.users.removeObject(
- existingReaction.users.find((user) => user.id === actor.id)
- );
- }
- }
- } else {
- if (action === "add") {
- this.reactions.pushObject(
- ChatMessageReaction.create({
- count: 1,
- emoji,
- reacted: selfReaction,
- users: [actor],
- })
- );
- }
- }
- }
-
- #initChatMessageReactionModel(messageId, reactions = []) {
- return reactions.map((reaction) =>
- ChatMessageReaction.create(Object.assign({ messageId }, reaction))
- );
- }
-
- #initUserModel(user) {
- if (!user || user instanceof User) {
- return user;
- }
-
- return User.create(user);
- }
-
- #areDatesOnSameDay(a, b) {
- return (
- a.getFullYear() === b.getFullYear() &&
- a.getMonth() === b.getMonth() &&
- a.getDate() === b.getDate()
- );
- }
-}
+ args.user = User.create(args.user);
+ },
+});
diff --git a/plugins/chat/assets/javascripts/discourse/modifiers/chat/track-message-separator-date.js b/plugins/chat/assets/javascripts/discourse/modifiers/chat/track-message-separator-date.js
deleted file mode 100644
index 71a19434521..00000000000
--- a/plugins/chat/assets/javascripts/discourse/modifiers/chat/track-message-separator-date.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import Modifier from "ember-modifier";
-import { registerDestructor } from "@ember/destroyable";
-
-const IS_PINNED_CLASS = "is-pinned";
-
-/*
- This modifier is used to track the date separator in the chat message list.
- The trick is to have an element with `top: -1px` which will stop fully intersecting
- as soon as it's scrolled a little bit.
-*/
-export default class ChatTrackMessageSeparatorDate extends Modifier {
- constructor(owner, args) {
- super(owner, args);
- registerDestructor(this, (instance) => instance.cleanup());
- }
-
- modify(element) {
- this.intersectionObserver = new IntersectionObserver(
- ([event]) => {
- if (event.isIntersecting && event.intersectionRatio < 1) {
- event.target.classList.add(IS_PINNED_CLASS);
- } else {
- event.target.classList.remove(IS_PINNED_CLASS);
- }
- },
- { threshold: [0, 1] }
- );
-
- this.intersectionObserver.observe(element);
- }
-
- cleanup() {
- this.intersectionObserver?.disconnect();
- }
-}
diff --git a/plugins/chat/assets/javascripts/discourse/modifiers/chat/track-message-visibility.js b/plugins/chat/assets/javascripts/discourse/modifiers/chat/track-message-visibility.js
new file mode 100644
index 00000000000..10474b067cf
--- /dev/null
+++ b/plugins/chat/assets/javascripts/discourse/modifiers/chat/track-message-visibility.js
@@ -0,0 +1,23 @@
+import Modifier from "ember-modifier";
+import { inject as service } from "@ember/service";
+import { registerDestructor } from "@ember/destroyable";
+
+export default class TrackMessageVisibility extends Modifier {
+ @service chatMessageVisibilityObserver;
+
+ element = null;
+
+ constructor(owner, args) {
+ super(owner, args);
+ registerDestructor(this, (instance) => instance.cleanup());
+ }
+
+ modify(element) {
+ this.element = element;
+ this.chatMessageVisibilityObserver.observe(element);
+ }
+
+ cleanup() {
+ this.chatMessageVisibilityObserver.unobserve(this.element);
+ }
+}
diff --git a/plugins/chat/assets/javascripts/discourse/modifiers/chat/track-message.js b/plugins/chat/assets/javascripts/discourse/modifiers/chat/track-message.js
deleted file mode 100644
index 469188eaa32..00000000000
--- a/plugins/chat/assets/javascripts/discourse/modifiers/chat/track-message.js
+++ /dev/null
@@ -1,43 +0,0 @@
-import Modifier from "ember-modifier";
-import { registerDestructor } from "@ember/destroyable";
-import { bind } from "discourse-common/utils/decorators";
-
-export default class ChatTrackMessage extends Modifier {
- visibleCallback = null;
- notVisibleCallback = null;
-
- constructor(owner, args) {
- super(owner, args);
- registerDestructor(this, (instance) => instance.cleanup());
- }
-
- modify(element, [visibleCallback, notVisibleCallback]) {
- this.visibleCallback = visibleCallback;
- this.notVisibleCallback = notVisibleCallback;
-
- this.intersectionObserver = new IntersectionObserver(
- this._intersectionObserverCallback,
- {
- root: document,
- threshold: 0.9,
- }
- );
-
- this.intersectionObserver.observe(element);
- }
-
- cleanup() {
- this.intersectionObserver?.disconnect();
- }
-
- @bind
- _intersectionObserverCallback(entries) {
- entries.forEach((entry) => {
- if (entry.isIntersecting) {
- this.visibleCallback?.();
- } else {
- this.notVisibleCallback?.();
- }
- });
- }
-}
diff --git a/plugins/chat/assets/javascripts/discourse/routes/chat-channel.js b/plugins/chat/assets/javascripts/discourse/routes/chat-channel.js
index 34ca9343de6..69ea1c3b3f8 100644
--- a/plugins/chat/assets/javascripts/discourse/routes/chat-channel.js
+++ b/plugins/chat/assets/javascripts/discourse/routes/chat-channel.js
@@ -10,10 +10,6 @@ export default class ChatChannelRoute extends DiscourseRoute {
@action
willTransition(transition) {
- // Technically we could keep messages to avoid re-fetching them, but
- // it's not worth the complexity for now
- this.chat.activeChannel?.clearMessages();
-
this.chat.activeChannel.activeThread = null;
this.chatStateManager.closeSidePanel();
diff --git a/plugins/chat/assets/javascripts/discourse/services/chat-api.js b/plugins/chat/assets/javascripts/discourse/services/chat-api.js
index 4b51143681b..35e680031b3 100644
--- a/plugins/chat/assets/javascripts/discourse/services/chat-api.js
+++ b/plugins/chat/assets/javascripts/discourse/services/chat-api.js
@@ -233,39 +233,6 @@ export default class ChatApi extends Service {
);
}
- /**
- * Returns messages of a channel, from the last message or a specificed target.
- * @param {number} channelId - The ID of the channel.
- * @param {object} data - Params of the query.
- * @param {integer} data.targetMessageId - ID of the targeted message.
- * @param {integer} data.messageId - ID of the targeted message.
- * @param {integer} data.direction - Fetch past or future messages.
- * @param {integer} data.pageSize - Max number of messages to fetch.
- * @returns {Promise}
- */
- async messages(channelId, data = {}) {
- let path;
- const args = {};
-
- if (data.targetMessageId) {
- path = `/chat/lookup/${data.targetMessageId}`;
- args.chat_channel_id = channelId;
- } else {
- args.page_size = data.pageSize;
- path = `/chat/${channelId}/messages`;
-
- if (data.messageId) {
- args.message_id = data.messageId;
- }
-
- if (data.direction) {
- args.direction = data.direction;
- }
- }
-
- return ajax(path, { data: args });
- }
-
/**
* Update notifications settings of current user for a channel.
* @param {number} channelId - The ID of the channel.
diff --git a/plugins/chat/assets/javascripts/discourse/services/chat-channels-manager.js b/plugins/chat/assets/javascripts/discourse/services/chat-channels-manager.js
index 47a4fb88d8b..e70655e3585 100644
--- a/plugins/chat/assets/javascripts/discourse/services/chat-channels-manager.js
+++ b/plugins/chat/assets/javascripts/discourse/services/chat-channels-manager.js
@@ -42,14 +42,6 @@ export default class ChatChannelsManager extends Service {
this.#cache(model);
}
- if (
- channelObject.meta?.message_bus_last_ids?.channel_message_bus_last_id !==
- undefined
- ) {
- model.channelMessageBusLastId =
- channelObject.meta.message_bus_last_ids.channel_message_bus_last_id;
- }
-
return model;
}
@@ -146,7 +138,8 @@ export default class ChatChannelsManager extends Service {
const unreadCountA = a.currentUserMembership.unread_count || 0;
const unreadCountB = b.currentUserMembership.unread_count || 0;
if (unreadCountA === unreadCountB) {
- return new Date(a.lastMessageSentAt) > new Date(b.lastMessageSentAt)
+ return new Date(a.get("last_message_sent_at")) >
+ new Date(b.get("last_message_sent_at"))
? -1
: 1;
} else {
diff --git a/plugins/chat/assets/javascripts/discourse/services/chat-message-visibility-observer.js b/plugins/chat/assets/javascripts/discourse/services/chat-message-visibility-observer.js
new file mode 100644
index 00000000000..a5a77a1d4b0
--- /dev/null
+++ b/plugins/chat/assets/javascripts/discourse/services/chat-message-visibility-observer.js
@@ -0,0 +1,63 @@
+import Service, { inject as service } from "@ember/service";
+import { isTesting } from "discourse-common/config/environment";
+import { bind } from "discourse-common/utils/decorators";
+
+export default class ChatMessageVisibilityObserver extends Service {
+ @service chat;
+
+ intersectionObserver = new IntersectionObserver(
+ this._intersectionObserverCallback,
+ {
+ root: document,
+ rootMargin: "-10px",
+ }
+ );
+
+ mutationObserver = new MutationObserver(this._mutationObserverCallback, {
+ root: document,
+ rootMargin: "-10px",
+ });
+
+ willDestroy() {
+ this.intersectionObserver.disconnect();
+ this.mutationObserver.disconnect();
+ }
+
+ @bind
+ _intersectionObserverCallback(entries) {
+ entries.forEach((entry) => {
+ entry.target.dataset.visible = entry.isIntersecting;
+
+ if (
+ !entry.target.dataset.stagedId &&
+ entry.isIntersecting &&
+ !isTesting()
+ ) {
+ this.chat.updateLastReadMessage();
+ }
+ });
+ }
+
+ @bind
+ _mutationObserverCallback(mutationList) {
+ mutationList.forEach((mutation) => {
+ const data = mutation.target.dataset;
+ if (data.id && data.visible && !data.stagedId) {
+ this.chat.updateLastReadMessage();
+ }
+ });
+ }
+
+ observe(element) {
+ this.intersectionObserver.observe(element);
+ this.mutationObserver.observe(element, {
+ attributes: true,
+ attributeOldValue: true,
+ attributeFilter: ["data-staged-id"],
+ });
+ }
+
+ unobserve(element) {
+ this.intersectionObserver.unobserve(element);
+ }
+}
diff --git a/plugins/chat/assets/javascripts/discourse/services/chat-subscriptions-manager.js b/plugins/chat/assets/javascripts/discourse/services/chat-subscriptions-manager.js
index 8430d083746..42644d9189c 100644
--- a/plugins/chat/assets/javascripts/discourse/services/chat-subscriptions-manager.js
+++ b/plugins/chat/assets/javascripts/discourse/services/chat-subscriptions-manager.js
@@ -154,7 +154,7 @@ export default class ChatSubscriptionsManager extends Service {
}
}
- channel.lastMessageSentAt = new Date();
+ channel.set("last_message_sent_at", new Date());
});
}
@@ -185,14 +185,13 @@ export default class ChatSubscriptionsManager extends Service {
_onUserTrackingStateUpdate(busData) {
this.chatChannelsManager.find(busData.chat_channel_id).then((channel) => {
if (
- !channel?.currentUserMembership?.last_read_message_id ||
- parseInt(channel?.currentUserMembership?.last_read_message_id, 10) <=
- busData.chat_message_id
+ channel?.currentUserMembership?.last_read_message_id <=
+ busData.chat_message_id
) {
channel.currentUserMembership.last_read_message_id =
busData.chat_message_id;
- channel.currentUserMembership.unread_count = busData.unread_count;
- channel.currentUserMembership.unread_mentions = busData.unread_mentions;
+ channel.currentUserMembership.unread_count = 0;
+ channel.currentUserMembership.unread_mentions = 0;
}
});
}
diff --git a/plugins/chat/assets/javascripts/discourse/services/chat.js b/plugins/chat/assets/javascripts/discourse/services/chat.js
index 59a182617e8..e500e84f754 100644
--- a/plugins/chat/assets/javascripts/discourse/services/chat.js
+++ b/plugins/chat/assets/javascripts/discourse/services/chat.js
@@ -3,18 +3,29 @@ import { tracked } from "@glimmer/tracking";
import userSearch from "discourse/lib/user-search";
import { popupAjaxError } from "discourse/lib/ajax-error";
import Service, { inject as service } from "@ember/service";
+import Site from "discourse/models/site";
import { ajax } from "discourse/lib/ajax";
+import { generateCookFunction } from "discourse/lib/text";
import { cancel, next } from "@ember/runloop";
import { and } from "@ember/object/computed";
import { computed } from "@ember/object";
+import { Promise } from "rsvp";
+import simpleCategoryHashMentionTransform from "discourse/plugins/chat/discourse/lib/simple-category-hash-mention-transform";
+import discourseDebounce from "discourse-common/lib/debounce";
import discourseLater from "discourse-common/lib/later";
-import ChatMessageDraft from "discourse/plugins/chat/discourse/models/chat-message-draft";
+import userPresent from "discourse/lib/user-presence";
+
+export const LIST_VIEW = "list_view";
+export const CHAT_VIEW = "chat_view";
+export const DRAFT_CHANNEL_VIEW = "draft_channel_view";
const CHAT_ONLINE_OPTIONS = {
userUnseenTime: 300000, // 5 minutes seconds with no interaction
browserHiddenTime: 300000, // Or the browser has been in the background for 5 minutes
};
+const READ_INTERVAL = 1000;
+
export default class Chat extends Service {
@service appEvents;
@service chatNotificationManager;
@@ -53,6 +64,13 @@ export default class Chat extends Service {
if (this.userCanChat) {
this.presenceChannel = this.presence.getChannel("/chat/online");
+ this.draftStore = {};
+
+ if (this.currentUser.chat_drafts) {
+ this.currentUser.chat_drafts.forEach((draft) => {
+ this.draftStore[draft.channel_id] = JSON.parse(draft.data);
+ });
+ }
}
}
@@ -85,16 +103,6 @@ export default class Chat extends Service {
[...channels.public_channels, ...channels.direct_message_channels].forEach(
(channelObject) => {
const channel = this.chatChannelsManager.store(channelObject);
-
- if (this.currentUser.chat_drafts) {
- const storedDraft = this.currentUser.chat_drafts.find(
- (draft) => draft.channel_id === channel.id
- );
- channel.draft = ChatMessageDraft.create(
- storedDraft ? JSON.parse(storedDraft.data) : null
- );
- }
-
return this.chatChannelsManager.follow(channel);
}
);
@@ -108,6 +116,33 @@ export default class Chat extends Service {
}
}
+ loadCookFunction(categories) {
+ if (this.cook) {
+ return Promise.resolve(this.cook);
+ }
+
+ const markdownOptions = {
+ featuresOverride: Site.currentProp(
+ "markdown_additional_options.chat.limited_pretty_text_features"
+ ),
+ markdownItRules: Site.currentProp(
+ "markdown_additional_options.chat.limited_pretty_text_markdown_rules"
+ ),
+ hashtagTypesInPriorityOrder:
+ this.site.hashtag_configurations["chat-composer"],
+ hashtagIcons: this.site.hashtag_icons,
+ };
+
+ return generateCookFunction(markdownOptions).then((cookFunction) => {
+ return this.set("cook", (raw) => {
+ return simpleCategoryHashMentionTransform(
+ cookFunction(raw),
+ categories
+ );
+ });
+ });
+ }
+
updatePresence() {
next(() => {
if (this.isDestroyed || this.isDestroying) {
@@ -242,6 +277,10 @@ export default class Chat extends Service {
: this.router.transitionTo("chat.channel", ...channel.routeModels);
}
+ _fireOpenMessageAppEvent(messageId) {
+ this.appEvents.trigger("chat-live-pane:highlight-message", messageId);
+ }
+
async followChannel(channel) {
return this.chatChannelsManager.follow(channel);
}
@@ -288,6 +327,84 @@ export default class Chat extends Service {
});
}
+ _saveDraft(channelId, draft) {
+ const data = { chat_channel_id: channelId };
+ if (draft) {
+ data.data = JSON.stringify(draft);
+ }
+
+ ajax("/chat/drafts.json", { type: "POST", data, ignoreUnsent: false })
+ .then(() => {
+ this.markNetworkAsReliable();
+ })
+ .catch((error) => {
+ // we ignore a draft which can't be saved because it's too big
+ // and only deal with network error for now
+ if (!error.jqXHR?.responseJSON?.errors?.length) {
+ this.markNetworkAsUnreliable();
+ }
+ });
+ }
+
+ setDraftForChannel(channel, draft) {
+ if (
+ draft &&
+ (draft.value || draft.uploads.length > 0 || draft.replyToMsg)
+ ) {
+ this.draftStore[channel.id] = draft;
+ } else {
+ delete this.draftStore[channel.id];
+ draft = null; // _saveDraft will destroy draft
+ }
+
+ discourseDebounce(this, this._saveDraft, channel.id, draft, 2000);
+ }
+
+ getDraftForChannel(channelId) {
+ return (
+ this.draftStore[channelId] || {
+ value: "",
+ uploads: [],
+ replyToMsg: null,
+ }
+ );
+ }
+
+ updateLastReadMessage() {
+ discourseDebounce(this, this._queuedReadMessageUpdate, READ_INTERVAL);
+ }
+
+ _queuedReadMessageUpdate() {
+ const visibleMessages = document.querySelectorAll(
+ ".chat-message-container[data-visible=true]"
+ );
+ const channel = this.activeChannel;
+
+ if (
+ !channel?.isFollowing ||
+ visibleMessages?.length === 0 ||
+ !userPresent()
+ ) {
+ return;
+ }
+
+ const latestUnreadMsgId = parseInt(
+ visibleMessages[visibleMessages.length - 1].dataset.id,
+ 10
+ );
+
+ const membership = channel.currentUserMembership;
+ const hasUnreadMessages =
+ latestUnreadMsgId > membership.last_read_message_id;
+ if (
+ hasUnreadMessages ||
+ membership.unread_count > 0 ||
+ membership.unread_mentions > 0
+ ) {
+ channel.updateLastReadMessage(latestUnreadMsgId);
+ }
+ }
+
addToolbarButton() {
deprecated(
"Use the new chat API `api.registerChatComposerButton` instead of `chat.addToolbarButton`"
diff --git a/plugins/chat/assets/javascripts/discourse/templates/modal/create-channel.hbs b/plugins/chat/assets/javascripts/discourse/templates/modal/create-channel.hbs
index ecc01d51c09..db7dc6fc342 100644
--- a/plugins/chat/assets/javascripts/discourse/templates/modal/create-channel.hbs
+++ b/plugins/chat/assets/javascripts/discourse/templates/modal/create-channel.hbs
@@ -54,12 +54,7 @@
/>
{{#if this.categoryPermissionsHint}}
-
+
{{this.categoryPermissionsHint}}
{{/if}}
diff --git a/plugins/chat/assets/stylesheets/common/chat-channel-preview-card.scss b/plugins/chat/assets/stylesheets/common/chat-channel-preview-card.scss
index f59c30e85d1..34035311c73 100644
--- a/plugins/chat/assets/stylesheets/common/chat-channel-preview-card.scss
+++ b/plugins/chat/assets/stylesheets/common/chat-channel-preview-card.scss
@@ -5,7 +5,6 @@
display: flex;
flex-direction: column;
align-items: center;
- z-index: 3;
&.-no-description {
.chat-channel-title {
diff --git a/plugins/chat/assets/stylesheets/common/chat-composer.scss b/plugins/chat/assets/stylesheets/common/chat-composer.scss
index 057c6014f7b..9ae341f2473 100644
--- a/plugins/chat/assets/stylesheets/common/chat-composer.scss
+++ b/plugins/chat/assets/stylesheets/common/chat-composer.scss
@@ -1,8 +1,6 @@
.chat-composer-container {
display: flex;
flex-direction: column;
- z-index: 3;
- background-color: var(--secondary);
#chat-full-page-uploader,
#chat-widget-uploader {
diff --git a/plugins/chat/assets/stylesheets/common/chat-message-actions.scss b/plugins/chat/assets/stylesheets/common/chat-message-actions.scss
index 6ed3e37b13f..815d561d643 100644
--- a/plugins/chat/assets/stylesheets/common/chat-message-actions.scss
+++ b/plugins/chat/assets/stylesheets/common/chat-message-actions.scss
@@ -6,6 +6,10 @@
.chat-message-actions {
.chat-message-reaction {
@include chat-reaction;
+
+ &:not(.show) {
+ display: none;
+ }
}
}
diff --git a/plugins/chat/assets/stylesheets/common/chat-message-separator.scss b/plugins/chat/assets/stylesheets/common/chat-message-separator.scss
index c9d00b079dc..e918d0c850a 100644
--- a/plugins/chat/assets/stylesheets/common/chat-message-separator.scss
+++ b/plugins/chat/assets/stylesheets/common/chat-message-separator.scss
@@ -1,96 +1,42 @@
.chat-message-separator {
@include unselectable;
+ margin: 0.25rem 0 0.25rem 1rem;
display: flex;
+ font-size: var(--font-down-1);
+ position: relative;
+ transform: translateZ(0);
+ position: relative;
- &-new {
- position: relative;
- padding: 20px 0;
+ &.new-message {
+ color: var(--danger-medium);
- .chat-message-separator__text-container {
- text-align: center;
- position: absolute;
- height: 40px;
- width: 100%;
- box-sizing: border-box;
- z-index: 1;
- top: 0;
- display: flex;
- align-items: center;
- justify-content: center;
-
- .chat-message-separator__text {
- color: var(--danger-medium);
- background-color: var(--secondary);
- padding: 0.25rem 0.5rem;
- font-size: var(--font-down-1);
- }
- }
-
- .chat-message-separator__line-container {
- width: 100%;
-
- .chat-message-separator__line {
- border-top: 1px solid var(--danger-medium);
- }
+ .divider {
+ background-color: var(--danger-medium);
}
}
- &-date {
+ &.first-daily-message {
+ .text {
+ color: var(--secondary-low);
+ font-weight: 600;
+ }
+
+ .divider {
+ background-color: var(--secondary-high);
+ }
+ }
+
+ .text {
+ margin: 0 auto;
+ padding: 0 0.75rem;
+ z-index: 1;
+ background: var(--secondary);
+ }
+
+ .divider {
position: absolute;
width: 100%;
- z-index: 1;
- display: flex;
- align-items: flex-start;
- justify-content: center;
- pointer-events: none;
-
- &.last-visit {
- .chat-message-separator__text {
- color: var(--danger-medium);
- }
-
- & + .chat-message-separator__line-container {
- .chat-message-separator__line {
- border-color: var(--danger-medium);
- }
- }
- }
-
- .chat-message-separator__text-container {
- padding-top: 7px;
- position: sticky;
- top: -1px;
-
- &.is-pinned {
- .chat-message-separator__text {
- border: 1px solid var(--primary-medium);
- border-radius: 3px;
- }
- }
- }
-
- .chat-message-separator__text {
- @include unselectable;
- background-color: var(--secondary);
- border: 1px solid transparent;
- color: var(--secondary-low);
- font-size: var(--font-down-1);
- padding: 0.25rem 0.5rem;
- box-sizing: border-box;
- }
-
- & + .chat-message-separator__line-container {
- padding: 20px 0;
- box-sizing: border-box;
-
- .chat-message-separator__line {
- border-top: 1px solid var(--secondary-high);
- left: 0;
- margin: 0 0 -1px;
- position: relative;
- right: 0;
- top: -1px;
- }
- }
+ height: 1px;
+ top: 50%;
}
}
diff --git a/plugins/chat/assets/stylesheets/common/chat-message.scss b/plugins/chat/assets/stylesheets/common/chat-message.scss
index 4b1641b343c..df79c8b70ca 100644
--- a/plugins/chat/assets/stylesheets/common/chat-message.scss
+++ b/plugins/chat/assets/stylesheets/common/chat-message.scss
@@ -42,10 +42,6 @@
background: var(--primary-low);
border-color: var(--primary-low-mid);
}
-
- &:focus {
- background: none;
- }
}
.emoji {
@@ -61,11 +57,13 @@
background-color: var(--secondary);
display: flex;
min-width: 0;
- content-visibility: auto;
- contain-intrinsic-size: auto 200px;
.chat-message-reaction {
@include chat-reaction;
+
+ &:not(.show) {
+ display: none;
+ }
}
&.chat-action {
@@ -88,6 +86,17 @@
transition: 2s linear background-color;
}
+ &.user-info-hidden {
+ .chat-time {
+ color: var(--secondary-medium);
+ flex-shrink: 0;
+ font-size: var(--font-down-2);
+ margin-top: 0.4em;
+ display: none;
+ width: var(--message-left-width);
+ }
+ }
+
&.is-reply {
display: grid;
grid-template-columns: var(--message-left-width) 1fr;
@@ -245,10 +254,6 @@
.chat-message.chat-message-bookmarked {
background: var(--highlight-bg);
-
- &:hover {
- background: var(--highlight-medium);
- }
}
.not-mobile-device & .chat-message-reaction-list .chat-message-react-btn {
@@ -279,6 +284,7 @@
font-style: italic;
}
+.chat-message-container.is-hovered,
.chat-message.chat-message-selected {
background: var(--primary-very-low);
}
diff --git a/plugins/chat/assets/stylesheets/common/chat-skeleton.scss b/plugins/chat/assets/stylesheets/common/chat-skeleton.scss
index cd5e79b3eb0..19eed6f1459 100644
--- a/plugins/chat/assets/stylesheets/common/chat-skeleton.scss
+++ b/plugins/chat/assets/stylesheets/common/chat-skeleton.scss
@@ -1,4 +1,4 @@
-$radius: 3px;
+$radius: 10px;
.chat-skeleton {
height: auto;
@@ -55,35 +55,11 @@ $radius: 3px;
&__message-content {
grid-area: content;
width: 100%;
- padding: 10px 0;
}
-
- &__message-reactions {
- display: flex;
- padding: 5px 0 0 0;
- }
-
- &__message-reaction {
- background-color: var(--primary-100);
- width: 32px;
- height: 18px;
- border-radius: $radius;
-
- & + & {
- margin-left: 0.5rem;
- }
- }
-
- &__message-text {
- display: flex;
- padding: 5px 0;
- flex-direction: column;
- }
-
&__message-msg {
height: 13px;
border-radius: $radius;
- margin: 2px 0;
+ margin: 5px 0;
.chat-skeleton__body:nth-of-type(odd) & {
background-color: var(--primary-100);
@@ -93,14 +69,6 @@ $radius: 3px;
}
}
- &__message-img {
- height: 80px;
- border-radius: $radius;
- margin: 2px 0;
- width: 200px;
- background-color: var(--primary-100);
- }
-
*[class^="chat-skeleton__message-"] {
position: relative;
overflow: hidden;
@@ -110,7 +78,7 @@ $radius: 3px;
position: relative;
overflow: hidden;
- *[class^="chat-skeleton__message-"]:not(.chat-skeleton__message-content):not(.chat-skeleton__message-text):not(.chat-skeleton__message-reactions):after {
+ *[class^="chat-skeleton__message-"]:not(.chat-skeleton__message-content):after {
position: absolute;
top: 0;
right: 0;
diff --git a/plugins/chat/assets/stylesheets/common/common.scss b/plugins/chat/assets/stylesheets/common/common.scss
index 5f7ee16413a..ba3529b856b 100644
--- a/plugins/chat/assets/stylesheets/common/common.scss
+++ b/plugins/chat/assets/stylesheets/common/common.scss
@@ -144,7 +144,6 @@ $float-height: 530px;
.chat-messages-container {
word-wrap: break-word;
white-space: normal;
- position: relative;
.chat-message-container {
display: grid;
@@ -284,8 +283,6 @@ $float-height: 530px;
display: flex;
flex-direction: column-reverse;
z-index: 1;
- margin: 0 3px 0 0;
- will-change: transform;
&::-webkit-scrollbar {
width: 15px;
@@ -326,65 +323,37 @@ $float-height: 530px;
}
.chat-scroll-to-bottom {
- left: calc(50% - calc(45px / 2));
- align-items: center;
- justify-content: center;
+ background: var(--primary-medium);
+ bottom: 1em;
+ border-radius: 100%;
+ left: 50%;
+ opacity: 50%;
+ padding: 0.5em;
position: absolute;
- z-index: 1;
- flex-direction: column;
- bottom: -75px;
- background: none;
- opacity: 0;
- transition: opacity 0.25s ease, transform 0.5s ease;
- transform: scale(0.1);
- padding: 5px;
-
- > * {
- pointer-events: none;
- }
-
- &:hover,
- &:active,
- &:focus {
- background: none !important;
- }
-
- &.visible {
- transform: translateY(-75px) scale(1);
- opacity: 0.8;
- }
-
- &__text {
- color: var(--secondary);
- padding: 0.5rem;
- margin-bottom: 0.5rem;
- background: var(--primary-medium);
- border-radius: 3px;
- text-align: center;
- font-size: var(--font-down-1);
- }
-
- &__arrow {
- display: flex;
- background: var(--primary-medium);
- border-radius: 100%;
- align-items: center;
- justify-content: center;
- height: 35px;
- width: 35px;
-
- .d-icon {
- color: var(--secondary);
- }
- }
+ transform: translateX(-50%);
+ z-index: 2;
&:hover {
- opacity: 1;
+ background: var(--primary-medium);
+ opacity: 100%;
+ }
- .chat-scroll-to-bottom__arrow {
- .d-icon {
- color: var(--secondary);
- }
+ .d-icon {
+ color: var(--primary);
+ margin: 0;
+ }
+
+ &.unread-messages {
+ opacity: 85%;
+ border-radius: 0;
+ transition: border-radius 0.1s linear;
+
+ &:hover {
+ opacity: 100%;
+ }
+
+ .d-icon {
+ margin: 0 0 0 0.5em;
}
}
}
diff --git a/plugins/chat/assets/stylesheets/desktop/chat-composer.scss b/plugins/chat/assets/stylesheets/desktop/chat-composer.scss
index c6af087e68e..3095d1851ad 100644
--- a/plugins/chat/assets/stylesheets/desktop/chat-composer.scss
+++ b/plugins/chat/assets/stylesheets/desktop/chat-composer.scss
@@ -1,6 +1,6 @@
.chat-composer-container {
.chat-composer {
- margin: 0.25rem 5px 0 5px;
+ margin: 0.25rem 10px 0 10px;
}
html.keyboard-visible .footer-nav-ipad & {
margin: 0.25rem 10px 1rem 10px;
diff --git a/plugins/chat/assets/stylesheets/desktop/desktop.scss b/plugins/chat/assets/stylesheets/desktop/desktop.scss
index 3214e03ceb9..ea281cbe347 100644
--- a/plugins/chat/assets/stylesheets/desktop/desktop.scss
+++ b/plugins/chat/assets/stylesheets/desktop/desktop.scss
@@ -53,25 +53,6 @@
.chat-message.user-info-hidden {
padding: 0.15em 1em;
-
- .chat-time {
- color: var(--secondary-medium);
- flex-shrink: 0;
- font-size: var(--font-down-2);
- margin-top: 0.4em;
- display: none;
- width: var(--message-left-width);
- }
-
- &:hover {
- .chat-message-left-gutter__bookmark {
- display: none;
- }
-
- .chat-time {
- display: block;
- }
- }
}
// Full Page Styling in Core
diff --git a/plugins/chat/assets/stylesheets/mobile/chat-message-actions.scss b/plugins/chat/assets/stylesheets/mobile/chat-message-actions.scss
index 733341f0e28..e8361c052e2 100644
--- a/plugins/chat/assets/stylesheets/mobile/chat-message-actions.scss
+++ b/plugins/chat/assets/stylesheets/mobile/chat-message-actions.scss
@@ -22,8 +22,6 @@
border-radius: 8px;
.selected-message-reply {
- margin-left: 5px;
-
&:not(.is-expanded) {
@include ellipsis;
}
diff --git a/plugins/chat/assets/stylesheets/mobile/chat-message.scss b/plugins/chat/assets/stylesheets/mobile/chat-message.scss
index 20c267d2e8c..c3517ab9858 100644
--- a/plugins/chat/assets/stylesheets/mobile/chat-message.scss
+++ b/plugins/chat/assets/stylesheets/mobile/chat-message.scss
@@ -4,3 +4,7 @@
.replying-text {
@include unselectable;
}
+
+.chat-message-container {
+ transform: translateZ(0);
+}
diff --git a/plugins/chat/config/locales/client.en.yml b/plugins/chat/config/locales/client.en.yml
index 1eadba98f47..af2a3c06436 100644
--- a/plugins/chat/config/locales/client.en.yml
+++ b/plugins/chat/config/locales/client.en.yml
@@ -108,7 +108,7 @@ en:
in_reply_to: "In reply to"
heading: "Chat"
join: "Join"
- last_visit: "last visit"
+ new_messages: "new messages"
mention_warning:
dismiss: "dismiss"
cannot_see: "%{username} can't access this channel and was not notified."
diff --git a/plugins/chat/plugin.rb b/plugins/chat/plugin.rb
index 5f85f631e87..d14913e05b3 100644
--- a/plugins/chat/plugin.rb
+++ b/plugins/chat/plugin.rb
@@ -247,7 +247,6 @@ after_initialize do
load File.expand_path("../app/controllers/api/hints_controller.rb", __FILE__)
load File.expand_path("../app/controllers/api/chat_channel_threads_controller.rb", __FILE__)
load File.expand_path("../app/controllers/api/chat_chatables_controller.rb", __FILE__)
- load File.expand_path("../app/queries/chat_channel_unreads_query.rb", __FILE__)
load File.expand_path("../app/queries/chat_channel_memberships_query.rb", __FILE__)
if Discourse.allow_dev_populate?
diff --git a/plugins/chat/spec/queries/chat_channel_memberships_query_spec.rb b/plugins/chat/spec/queries/chat_channel_memberships_query_spec.rb
index 38f43f6d38d..409349c0ef9 100644
--- a/plugins/chat/spec/queries/chat_channel_memberships_query_spec.rb
+++ b/plugins/chat/spec/queries/chat_channel_memberships_query_spec.rb
@@ -17,7 +17,7 @@ describe ChatChannelMembershipsQuery do
context "when no memberships exists" do
it "returns an empty array" do
- expect(described_class.call(channel: channel_1)).to eq([])
+ expect(described_class.call(channel_1)).to eq([])
end
end
@@ -28,7 +28,7 @@ describe ChatChannelMembershipsQuery do
end
it "returns the memberships" do
- memberships = described_class.call(channel: channel_1)
+ memberships = described_class.call(channel_1)
expect(memberships.pluck(:user_id)).to contain_exactly(user_1.id, user_2.id)
end
@@ -49,7 +49,7 @@ describe ChatChannelMembershipsQuery do
end
it "lists the user" do
- memberships = described_class.call(channel: channel_1)
+ memberships = described_class.call(channel_1)
expect(memberships.pluck(:user_id)).to include(user_1.id)
end
@@ -62,16 +62,14 @@ describe ChatChannelMembershipsQuery do
permission_type: CategoryGroup.permission_types[:full],
)
- expect(described_class.call(channel: channel_1).pluck(:user_id)).to contain_exactly(
- user_1.id,
- )
+ expect(described_class.call(channel_1).pluck(:user_id)).to contain_exactly(user_1.id)
end
it "returns the membership if the user still has access through a staff group" do
chatters_group.remove(user_1)
Group.find_by(id: Group::AUTO_GROUPS[:staff]).add(user_1)
- memberships = described_class.call(channel: channel_1)
+ memberships = described_class.call(channel_1)
expect(memberships.pluck(:user_id)).to include(user_1.id)
end
@@ -79,7 +77,7 @@ describe ChatChannelMembershipsQuery do
context "when membership doesn’t exist" do
it "doesn’t list the user" do
- memberships = described_class.call(channel: channel_1)
+ memberships = described_class.call(channel_1)
expect(memberships.pluck(:user_id)).to be_empty
end
@@ -93,7 +91,7 @@ describe ChatChannelMembershipsQuery do
end
it "doesn’t list the user" do
- memberships = described_class.call(channel: channel_1)
+ memberships = described_class.call(channel_1)
expect(memberships).to be_empty
end
@@ -101,7 +99,7 @@ describe ChatChannelMembershipsQuery do
context "when membership doesn’t exist" do
it "doesn’t list the user" do
- memberships = described_class.call(channel: channel_1)
+ memberships = described_class.call(channel_1)
expect(memberships).to be_empty
end
@@ -116,7 +114,7 @@ describe ChatChannelMembershipsQuery do
end
it "returns an empty array" do
- expect(described_class.call(channel: channel_1)).to eq([])
+ expect(described_class.call(channel_1)).to eq([])
end
end
@@ -124,7 +122,7 @@ describe ChatChannelMembershipsQuery do
fab!(:channel_1) { Fabricate(:direct_message_channel, users: [user_1, user_2]) }
it "returns the memberships" do
- memberships = described_class.call(channel: channel_1)
+ memberships = described_class.call(channel_1)
expect(memberships.pluck(:user_id)).to contain_exactly(user_1.id, user_2.id)
end
@@ -141,7 +139,7 @@ describe ChatChannelMembershipsQuery do
describe "offset param" do
it "offsets the results" do
- memberships = described_class.call(channel: channel_1, offset: 1)
+ memberships = described_class.call(channel_1, offset: 1)
expect(memberships.length).to eq(1)
end
@@ -149,7 +147,7 @@ describe ChatChannelMembershipsQuery do
describe "limit param" do
it "limits the results" do
- memberships = described_class.call(channel: channel_1, limit: 1)
+ memberships = described_class.call(channel_1, limit: 1)
expect(memberships.length).to eq(1)
end
@@ -165,7 +163,7 @@ describe ChatChannelMembershipsQuery do
end
it "filters the results" do
- memberships = described_class.call(channel: channel_1, username: user_1.username)
+ memberships = described_class.call(channel_1, username: user_1.username)
expect(memberships.length).to eq(1)
expect(memberships[0].user).to eq(user_1)
@@ -184,7 +182,7 @@ describe ChatChannelMembershipsQuery do
before { SiteSetting.prioritize_username_in_ux = true }
it "is using ascending order on username" do
- memberships = described_class.call(channel: channel_1)
+ memberships = described_class.call(channel_1)
expect(memberships[0].user).to eq(user_1)
expect(memberships[1].user).to eq(user_2)
@@ -195,7 +193,7 @@ describe ChatChannelMembershipsQuery do
before { SiteSetting.prioritize_username_in_ux = false }
it "is using ascending order on name" do
- memberships = described_class.call(channel: channel_1)
+ memberships = described_class.call(channel_1)
expect(memberships[0].user).to eq(user_2)
expect(memberships[1].user).to eq(user_1)
@@ -205,7 +203,7 @@ describe ChatChannelMembershipsQuery do
before { SiteSetting.enable_names = false }
it "is using ascending order on username" do
- memberships = described_class.call(channel: channel_1)
+ memberships = described_class.call(channel_1)
expect(memberships[0].user).to eq(user_1)
expect(memberships[1].user).to eq(user_2)
@@ -224,7 +222,7 @@ describe ChatChannelMembershipsQuery do
end
it "doesn’t list staged users" do
- memberships = described_class.call(channel: channel_1)
+ memberships = described_class.call(channel_1)
expect(memberships).to be_blank
end
end
@@ -244,7 +242,7 @@ describe ChatChannelMembershipsQuery do
end
it "doesn’t list suspended users" do
- memberships = described_class.call(channel: channel_1)
+ memberships = described_class.call(channel_1)
expect(memberships).to be_blank
end
end
@@ -262,7 +260,7 @@ describe ChatChannelMembershipsQuery do
end
it "doesn’t list inactive users" do
- memberships = described_class.call(channel: channel_1)
+ memberships = described_class.call(channel_1)
expect(memberships).to be_blank
end
end
diff --git a/plugins/chat/spec/queries/chat_channel_unreads_query_spec.rb b/plugins/chat/spec/queries/chat_channel_unreads_query_spec.rb
deleted file mode 100644
index fea379ceb46..00000000000
--- a/plugins/chat/spec/queries/chat_channel_unreads_query_spec.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-# frozen_string_literal: true
-
-require "rails_helper"
-
-describe ChatChannelUnreadsQuery do
- fab!(:channel_1) { Fabricate(:category_channel) }
- fab!(:current_user) { Fabricate(:user) }
-
- before do
- SiteSetting.chat_enabled = true
- SiteSetting.chat_allowed_groups = Group::AUTO_GROUPS[:everyone]
- channel_1.add(current_user)
- end
-
- context "with unread message" do
- it "returns a correct unread count" do
- Fabricate(:chat_message, chat_channel: channel_1)
-
- expect(described_class.call(channel_id: channel_1.id, user_id: current_user.id)).to eq(
- { mention_count: 0, unread_count: 1 },
- )
- end
- end
-
- context "with unread mentions" do
- before { Jobs.run_immediately! }
-
- it "returns a correct unread mention" do
- message = Fabricate(:chat_message)
- notification =
- Notification.create!(
- notification_type: Notification.types[:chat_mention],
- user_id: current_user.id,
- data: { chat_message_id: message.id, chat_channel_id: channel_1.id }.to_json,
- )
- ChatMention.create!(notification: notification, user: current_user, chat_message: message)
-
- expect(described_class.call(channel_id: channel_1.id, user_id: current_user.id)).to eq(
- { mention_count: 1, unread_count: 0 },
- )
- end
- end
-
- context "with nothing unread" do
- it "returns a correct state" do
- expect(described_class.call(channel_id: channel_1.id, user_id: current_user.id)).to eq(
- { mention_count: 0, unread_count: 0 },
- )
- end
- end
-end
diff --git a/plugins/chat/spec/requests/chat_controller_spec.rb b/plugins/chat/spec/requests/chat_controller_spec.rb
index 92d0517036d..d0fcbfd19ab 100644
--- a/plugins/chat/spec/requests/chat_controller_spec.rb
+++ b/plugins/chat/spec/requests/chat_controller_spec.rb
@@ -126,17 +126,15 @@ RSpec.describe Chat::ChatController do
it "correctly marks reactions as 'reacted' for the current_user" do
heart_emoji = ":heart:"
smile_emoji = ":smile"
+
last_message = chat_channel.chat_messages.last
last_message.reactions.create(user: user, emoji: heart_emoji)
last_message.reactions.create(user: admin, emoji: smile_emoji)
get "/chat/#{chat_channel.id}/messages.json", params: { page_size: page_size }
-
reactions = response.parsed_body["chat_messages"].last["reactions"]
- heart_reaction = reactions.find { |r| r["emoji"] == heart_emoji }
- expect(heart_reaction["reacted"]).to be true
- smile_reaction = reactions.find { |r| r["emoji"] == smile_emoji }
- expect(smile_reaction["reacted"]).to be false
+ expect(reactions[heart_emoji]["reacted"]).to be true
+ expect(reactions[smile_emoji]["reacted"]).to be false
end
it "sends the last message bus id for the channel" do
diff --git a/plugins/chat/spec/serializer/chat_message_serializer_spec.rb b/plugins/chat/spec/serializer/chat_message_serializer_spec.rb
index 67f11368c2d..ea97d0310de 100644
--- a/plugins/chat/spec/serializer/chat_message_serializer_spec.rb
+++ b/plugins/chat/spec/serializer/chat_message_serializer_spec.rb
@@ -21,14 +21,12 @@ describe ChatMessageSerializer do
it "doesn’t return the reaction" do
Emoji.clear_cache
- trout_reaction = subject.as_json[:reactions].find { |r| r[:emoji] == "trout" }
- expect(trout_reaction).to be_present
+ expect(subject.as_json[:reactions]["trout"]).to be_present
custom_emoji.destroy!
Emoji.clear_cache
- trout_reaction = subject.as_json[:reactions].find { |r| r[:emoji] == "trout" }
- expect(trout_reaction).to_not be_present
+ expect(subject.as_json[:reactions]["trout"]).to_not be_present
end
end
end
diff --git a/plugins/chat/spec/system/chat_channel_spec.rb b/plugins/chat/spec/system/chat_channel_spec.rb
index bacd3a69427..39ecd2b9682 100644
--- a/plugins/chat/spec/system/chat_channel_spec.rb
+++ b/plugins/chat/spec/system/chat_channel_spec.rb
@@ -183,7 +183,7 @@ RSpec.describe "Chat channel", type: :system, js: true do
it "shows a date separator" do
chat.visit_channel(channel_1)
- expect(page).to have_selector(".chat-message-separator__text", text: "Today")
+ expect(page).to have_selector(".first-daily-message", text: "Today")
end
end
diff --git a/plugins/chat/spec/system/create_channel_spec.rb b/plugins/chat/spec/system/create_channel_spec.rb
index f1cfa2e8f87..f6fd4e4da09 100644
--- a/plugins/chat/spec/system/create_channel_spec.rb
+++ b/plugins/chat/spec/system/create_channel_spec.rb
@@ -81,7 +81,6 @@ RSpec.describe "Create channel", type: :system, js: true do
chat_page.visit_browse
chat_page.new_channel_button.click
channel_modal.select_category(private_category_1)
- expect(page).to have_no_css(".loading-permissions")
expect(channel_modal.create_channel_hint["innerHTML"].strip).to include(
"<script>e</script>",
diff --git a/plugins/chat/spec/system/flag_message_spec.rb b/plugins/chat/spec/system/flag_message_spec.rb
index a7afd6af14b..8b40ba93cd8 100644
--- a/plugins/chat/spec/system/flag_message_spec.rb
+++ b/plugins/chat/spec/system/flag_message_spec.rb
@@ -32,7 +32,7 @@ RSpec.describe "Flag message", type: :system, js: true do
context "when direct message channel" do
fab!(:dm_channel_1) { Fabricate(:direct_message_channel, users: [current_user]) }
- fab!(:message_1) { Fabricate(:chat_message, chat_channel: dm_channel_1) }
+ fab!(:message_1) { Fabricate(:chat_message, chat_channel: dm_channel_1, user: current_user) }
it "doesn’t allow to flag a message" do
chat.visit_channel(dm_channel_1)
diff --git a/plugins/chat/spec/system/message_user_info.rb b/plugins/chat/spec/system/message_user_info.rb
deleted file mode 100644
index be97ab1c0d9..00000000000
--- a/plugins/chat/spec/system/message_user_info.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.describe "Sticky date", type: :system, js: true do
- fab!(:current_user) { Fabricate(:user) }
- fab!(:channel_1) { Fabricate(:category_channel) }
-
- let(:chat_page) { PageObjects::Pages::Chat.new }
-
- before do
- chat_system_bootstrap
- sign_in(current_user)
- end
-
- context "when previous message is from a different user" do
- fab!(:message_1) { Fabricate(:chat_message, chat_channel: channel_1) }
- fab!(:message_2) { Fabricate(:chat_message, chat_channel: channel_1) }
-
- it "shows user info on the message" do
- chat_page.visit_channel(channel_1)
-
- expect(page.find("[data-id='#{message_2.id}']")).to have_css(".chat-message-avatar")
- end
- end
-
- context "when previous message is from the same user" do
- fab!(:message_1) { Fabricate(:chat_message, chat_channel: channel_1, user: current_user) }
- fab!(:message_2) { Fabricate(:chat_message, chat_channel: channel_1, user: current_user) }
-
- it "doesn’t show user info on the message" do
- chat_page.visit_channel(channel_1)
-
- expect(page.find("[data-id='#{message_2.id}']")).to have_no_css(".chat-message-avatar")
- end
-
- context "when previous message is old" do
- fab!(:message_1) do
- Fabricate(
- :chat_message,
- chat_channel: channel_1,
- user: current_user,
- created_at: DateTime.parse("2018-11-10 17:00"),
- )
- end
- fab!(:message_2) do
- Fabricate(
- :chat_message,
- chat_channel: channel_1,
- user: current_user,
- created_at: DateTime.parse("2018-11-10 17:30"),
- )
- end
-
- it "shows user info on the message" do
- chat_page.visit_channel(channel_1)
-
- expect(page.find("[data-id='#{message_2.id}']")).to have_no_css(".chat-message-avatar")
- end
- end
- end
-end
diff --git a/plugins/chat/spec/system/navigating_to_message_spec.rb b/plugins/chat/spec/system/navigating_to_message_spec.rb
index c3a3899eb43..9dae4fe8b81 100644
--- a/plugins/chat/spec/system/navigating_to_message_spec.rb
+++ b/plugins/chat/spec/system/navigating_to_message_spec.rb
@@ -60,9 +60,8 @@ RSpec.describe "Navigating to message", type: :system, js: true do
it "highlights the correct message after using the bottom arrow" do
chat_page.visit_channel(channel_1)
-
click_link(link)
- click_button(class: "chat-scroll-to-bottom")
+ click_link(I18n.t("js.chat.scroll_to_bottom"))
click_link(link)
expect(page).to have_css(
@@ -150,9 +149,8 @@ RSpec.describe "Navigating to message", type: :system, js: true do
visit("/")
chat_page.open_from_header
chat_drawer_page.open_channel(channel_1)
-
click_link(link)
- click_button(class: "chat-scroll-to-bottom")
+ click_link(I18n.t("js.chat.scroll_to_bottom"))
click_link(link)
expect(page).to have_css(
diff --git a/plugins/chat/spec/system/shortcuts/chat_composer_spec.rb b/plugins/chat/spec/system/shortcuts/chat_composer_spec.rb
index a7dd347750e..dddfa46baa9 100644
--- a/plugins/chat/spec/system/shortcuts/chat_composer_spec.rb
+++ b/plugins/chat/spec/system/shortcuts/chat_composer_spec.rb
@@ -5,7 +5,6 @@ RSpec.describe "Shortcuts | chat composer", type: :system, js: true do
fab!(:current_user) { Fabricate(:user) }
let(:chat) { PageObjects::Pages::Chat.new }
- let(:channel_page) { PageObjects::Pages::ChatChannel.new }
KEY_MODIFIER = RUBY_PLATFORM =~ /darwin/i ? :meta : :control
@@ -64,9 +63,8 @@ RSpec.describe "Shortcuts | chat composer", type: :system, js: true do
it "edits last editable message" do
chat.visit_channel(channel_1)
- expect(channel_page).to have_message(id: message_1.id)
- find(".chat-composer-input").send_keys(:arrow_up)
+ within(".chat-composer-input") { |composer| composer.send_keys(:arrow_up) }
expect(page.find(".chat-composer-message-details")).to have_content(message_1.message)
end
diff --git a/plugins/chat/spec/system/sticky_date_spec.rb b/plugins/chat/spec/system/sticky_date_spec.rb
deleted file mode 100644
index 9f043afa9f9..00000000000
--- a/plugins/chat/spec/system/sticky_date_spec.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.describe "Sticky date", type: :system, js: true do
- fab!(:current_user) { Fabricate(:user) }
- fab!(:channel_1) { Fabricate(:category_channel) }
-
- let(:chat_page) { PageObjects::Pages::Chat.new }
-
- before do
- chat_system_bootstrap
- channel_1.add(current_user)
- 20.times { Fabricate(:chat_message, chat_channel: channel_1, created_at: 1.day.ago) }
- 25.times { Fabricate(:chat_message, chat_channel: channel_1) }
- sign_in(current_user)
- end
-
- context "when today separator is out of screen" do
- it "shows it as a sticky date" do
- chat_page.visit_channel(channel_1)
-
- expect(page.find(".chat-message-separator__text-container.is-pinned")).to have_content(
- I18n.t("js.chat.chat_message_separator.today"),
- )
- expect(page).to have_css(
- ".chat-message-separator__text-container:not(.is-pinned)",
- visible: :hidden,
- text:
- "#{I18n.t("js.chat.chat_message_separator.yesterday")} - #{I18n.t("js.chat.last_visit")}",
- )
- end
- end
-end
diff --git a/plugins/chat/spec/system/uploads_spec.rb b/plugins/chat/spec/system/uploads_spec.rb
index 09dee0e400d..a012d29b86c 100644
--- a/plugins/chat/spec/system/uploads_spec.rb
+++ b/plugins/chat/spec/system/uploads_spec.rb
@@ -36,21 +36,12 @@ describe "Uploading files in chat messages", type: :system, js: true do
it "allows uploading multiple files" do
chat.visit_channel(channel_1)
-
file_path_1 = file_from_fixtures("logo.png", "images").path
- attach_file([file_path_1]) do
- channel.open_action_menu
- channel.click_action_button("chat-upload-btn")
- find(".chat-composer-input").click
- end
-
file_path_2 = file_from_fixtures("logo.jpg", "images").path
- attach_file([file_path_2]) do
+ attach_file([file_path_1, file_path_2]) do
channel.open_action_menu
channel.click_action_button("chat-upload-btn")
- find(".chat-composer-input").click
end
-
expect(page).to have_css(".chat-composer-upload .preview .preview-img", count: 2)
channel.send_message("upload testing")
diff --git a/plugins/chat/test/javascripts/components/chat-channel-metadata-test.js b/plugins/chat/test/javascripts/components/chat-channel-metadata-test.js
index db01ba779e9..4a8c7f391c8 100644
--- a/plugins/chat/test/javascripts/components/chat-channel-metadata-test.js
+++ b/plugins/chat/test/javascripts/components/chat-channel-metadata-test.js
@@ -9,17 +9,16 @@ module("Discourse Chat | Component | chat-channel-metadata", function (hooks) {
setupRenderingTest(hooks);
test("displays last message sent at", async function (assert) {
- let lastMessageSentAt = moment().subtract(1, "day").format();
+ let lastMessageSentAt = moment().subtract(1, "day");
this.channel = fabricators.directMessageChatChannel({
last_message_sent_at: lastMessageSentAt,
});
-
await render(hbs`
`);
assert.dom(".chat-channel-metadata__date").hasText("Yesterday");
lastMessageSentAt = moment();
- this.channel.lastMessageSentAt = lastMessageSentAt;
+ this.channel.set("last_message_sent_at", lastMessageSentAt);
await render(hbs`
`);
assert
diff --git a/plugins/chat/test/javascripts/components/chat-channel-row-test.js b/plugins/chat/test/javascripts/components/chat-channel-row-test.js
index 624d574da95..0c10cad2158 100644
--- a/plugins/chat/test/javascripts/components/chat-channel-row-test.js
+++ b/plugins/chat/test/javascripts/components/chat-channel-row-test.js
@@ -51,7 +51,9 @@ module("Discourse Chat | Component | chat-channel-row", function (hooks) {
assert
.dom(".chat-channel-metadata")
- .hasText(moment(this.categoryChatChannel.lastMessageSentAt).format("l"));
+ .hasText(
+ moment(this.categoryChatChannel.last_message_sent_at).format("l")
+ );
});
test("renders membership toggling button when necessary", async function (assert) {
diff --git a/plugins/chat/test/javascripts/components/chat-composer-uploads-test.js b/plugins/chat/test/javascripts/components/chat-composer-uploads-test.js
index 1a3872bd2c2..b256887fff8 100644
--- a/plugins/chat/test/javascripts/components/chat-composer-uploads-test.js
+++ b/plugins/chat/test/javascripts/components/chat-composer-uploads-test.js
@@ -8,6 +8,7 @@ import {
import hbs from "htmlbars-inline-precompile";
import { click, render, settled, waitFor } from "@ember/test-helpers";
import { module, test } from "qunit";
+import { run } from "@ember/runloop";
const fakeUpload = {
type: ".png",
@@ -46,11 +47,12 @@ module("Discourse Chat | Component | chat-composer-uploads", function (hooks) {
setupRenderingTest(hooks);
test("loading uploads from an outside source (e.g. draft or editing message)", async function (assert) {
- this.existingUploads = [fakeUpload];
-
await render(hbs`
-
+
`);
+
+ this.appEvents = this.container.lookup("service:appEvents");
+ this.appEvents.trigger("chat-composer:load-uploads", [fakeUpload]);
await settled();
assert.strictEqual(count(".chat-composer-upload"), 1);
@@ -59,7 +61,10 @@ module("Discourse Chat | Component | chat-composer-uploads", function (hooks) {
test("upload starts and completes", async function (assert) {
setupUploadPretender();
- this.set("onUploadChanged", () => {});
+ this.set("changedUploads", null);
+ this.set("onUploadChanged", (uploads) => {
+ this.set("changedUploads", uploads);
+ });
await render(hbs`
@@ -75,31 +80,34 @@ module("Discourse Chat | Component | chat-composer-uploads", function (hooks) {
done();
}
);
+
this.appEvents.trigger(
"upload-mixin:chat-composer-uploader:add-files",
createFile("avatar.png")
);
await waitFor(".chat-composer-upload");
-
- assert.dom(".chat-composer-upload").exists({ count: 1 });
+ assert.strictEqual(count(".chat-composer-upload"), 1);
});
test("removing a completed upload", async function (assert) {
this.set("changedUploads", null);
- this.set("onUploadChanged", () => {});
-
- this.existingUploads = [fakeUpload];
+ this.set("onUploadChanged", (uploads) => {
+ this.set("changedUploads", uploads);
+ });
await render(hbs`
-
+
`);
- assert.dom(".chat-composer-upload").exists({ count: 1 });
+ this.appEvents = this.container.lookup("service:appEvents");
+ run(() =>
+ this.appEvents.trigger("chat-composer:load-uploads", [fakeUpload])
+ );
+ assert.strictEqual(count(".chat-composer-upload"), 1);
await click(".remove-upload");
-
- assert.dom(".chat-composer-upload").exists({ count: 0 });
+ assert.strictEqual(count(".chat-composer-upload"), 0);
});
test("cancelling in progress upload", async function (assert) {
diff --git a/plugins/chat/test/javascripts/components/chat-live-pane-test.js b/plugins/chat/test/javascripts/components/chat-live-pane-test.js
new file mode 100644
index 00000000000..244439eb38e
--- /dev/null
+++ b/plugins/chat/test/javascripts/components/chat-live-pane-test.js
@@ -0,0 +1,44 @@
+import { setupRenderingTest } from "discourse/tests/helpers/component-test";
+import { exists } from "discourse/tests/helpers/qunit-helpers";
+import hbs from "htmlbars-inline-precompile";
+import { module, test } from "qunit";
+import fabricators from "../helpers/fabricators";
+import { render } from "@ember/test-helpers";
+import pretender, { response } from "discourse/tests/helpers/create-pretender";
+import MockPresenceChannel from "../helpers/mock-presence-channel";
+
+function mockChat(context) {
+ const mock = context.container.lookup("service:chat");
+ mock.draftStore = {};
+ mock.currentUser = context.currentUser;
+ mock.presenceChannel = MockPresenceChannel.create();
+ return mock;
+}
+
+module("Discourse Chat | Component | chat-live-pane", function (hooks) {
+ setupRenderingTest(hooks);
+
+ hooks.beforeEach(function () {
+ this.set("chat", mockChat(this));
+ this.set("channel", fabricators.chatChannel());
+ });
+
+ test("Shows skeleton when loading", async function (assert) {
+ pretender.get(`/chat/chat_channels.json`, () => response(this.channel));
+ pretender.get(`/chat/:id/messages.json`, () =>
+ response({ chat_messages: [], meta: { can_delete_self: true } })
+ );
+
+ await render(
+ hbs`
`
+ );
+
+ assert.true(exists(".chat-skeleton"));
+
+ await render(
+ hbs`
`
+ );
+
+ assert.true(exists(".chat-skeleton"));
+ });
+});
diff --git a/plugins/chat/test/javascripts/components/chat-message-avatar-test.js b/plugins/chat/test/javascripts/components/chat-message-avatar-test.js
index dff366aec8b..56244f9803b 100644
--- a/plugins/chat/test/javascripts/components/chat-message-avatar-test.js
+++ b/plugins/chat/test/javascripts/components/chat-message-avatar-test.js
@@ -3,16 +3,12 @@ import hbs from "htmlbars-inline-precompile";
import { exists, query } from "discourse/tests/helpers/qunit-helpers";
import { module, test } from "qunit";
import { render } from "@ember/test-helpers";
-import ChatMessage from "discourse/plugins/chat/discourse/models/chat-message";
-import fabricators from "../helpers/fabricators";
module("Discourse Chat | Component | chat-message-avatar", function (hooks) {
setupRenderingTest(hooks);
test("chat_webhook_event", async function (assert) {
- this.message = ChatMessage.create(fabricators.chatChannel(), {
- chat_webhook_event: { emoji: ":heart:" },
- });
+ this.set("message", { chat_webhook_event: { emoji: ":heart:" } });
await render(hbs`
`);
@@ -20,9 +16,7 @@ module("Discourse Chat | Component | chat-message-avatar", function (hooks) {
});
test("user", async function (assert) {
- this.message = ChatMessage.create(fabricators.chatChannel(), {
- user: { username: "discobot" },
- });
+ this.set("message", { user: { username: "discobot" } });
await render(hbs`
`);
diff --git a/plugins/chat/test/javascripts/components/chat-message-info-test.js b/plugins/chat/test/javascripts/components/chat-message-info-test.js
index f633d71645f..2875e2a73cc 100644
--- a/plugins/chat/test/javascripts/components/chat-message-info-test.js
+++ b/plugins/chat/test/javascripts/components/chat-message-info-test.js
@@ -6,21 +6,21 @@ import I18n from "I18n";
import { module, test } from "qunit";
import { render } from "@ember/test-helpers";
import ChatMessage from "discourse/plugins/chat/discourse/models/chat-message";
-import fabricators from "../helpers/fabricators";
module("Discourse Chat | Component | chat-message-info", function (hooks) {
setupRenderingTest(hooks);
test("chat_webhook_event", async function (assert) {
- this.message = ChatMessage.create(fabricators.chatChannel(), {
- chat_webhook_event: { username: "discobot" },
- });
+ this.set(
+ "message",
+ ChatMessage.create({ chat_webhook_event: { username: "discobot" } })
+ );
await render(hbs`
`);
assert.strictEqual(
query(".chat-message-info__username").innerText.trim(),
- this.message.chatWebhookEvent.username
+ this.message.chat_webhook_event.username
);
assert.strictEqual(
query(".chat-message-info__bot-indicator").textContent.trim(),
@@ -29,9 +29,7 @@ module("Discourse Chat | Component | chat-message-info", function (hooks) {
});
test("user", async function (assert) {
- this.message = ChatMessage.create(fabricators.chatChannel(), {
- user: { username: "discobot" },
- });
+ this.set("message", ChatMessage.create({ user: { username: "discobot" } }));
await render(hbs`
`);
@@ -42,10 +40,13 @@ module("Discourse Chat | Component | chat-message-info", function (hooks) {
});
test("date", async function (assert) {
- this.message = ChatMessage.create(fabricators.chatChannel(), {
- user: { username: "discobot" },
- created_at: moment(),
- });
+ this.set(
+ "message",
+ ChatMessage.create({
+ user: { username: "discobot" },
+ created_at: moment(),
+ })
+ );
await render(hbs`
`);
@@ -53,13 +54,16 @@ module("Discourse Chat | Component | chat-message-info", function (hooks) {
});
test("bookmark (with reminder)", async function (assert) {
- this.message = ChatMessage.create(fabricators.chatChannel(), {
- user: { username: "discobot" },
- bookmark: Bookmark.create({
- reminder_at: moment(),
- name: "some name",
- }),
- });
+ this.set(
+ "message",
+ ChatMessage.create({
+ user: { username: "discobot" },
+ bookmark: Bookmark.create({
+ reminder_at: moment(),
+ name: "some name",
+ }),
+ })
+ );
await render(hbs`
`);
@@ -69,12 +73,15 @@ module("Discourse Chat | Component | chat-message-info", function (hooks) {
});
test("bookmark (no reminder)", async function (assert) {
- this.message = ChatMessage.create(fabricators.chatChannel(), {
- user: { username: "discobot" },
- bookmark: Bookmark.create({
- name: "some name",
- }),
- });
+ this.set(
+ "message",
+ ChatMessage.create({
+ user: { username: "discobot" },
+ bookmark: Bookmark.create({
+ name: "some name",
+ }),
+ })
+ );
await render(hbs`
`);
@@ -83,9 +90,7 @@ module("Discourse Chat | Component | chat-message-info", function (hooks) {
test("user status", async function (assert) {
const status = { description: "off to dentist", emoji: "tooth" };
- this.message = ChatMessage.create(fabricators.chatChannel(), {
- user: { status },
- });
+ this.set("message", ChatMessage.create({ user: { status } }));
await render(hbs`
`);
@@ -93,10 +98,13 @@ module("Discourse Chat | Component | chat-message-info", function (hooks) {
});
test("reviewable", async function (assert) {
- this.message = ChatMessage.create(fabricators.chatChannel(), {
- user: { username: "discobot" },
- user_flag_status: 0,
- });
+ this.set(
+ "message",
+ ChatMessage.create({
+ user: { username: "discobot" },
+ user_flag_status: 0,
+ })
+ );
await render(hbs`
`);
@@ -105,12 +113,13 @@ module("Discourse Chat | Component | chat-message-info", function (hooks) {
I18n.t("chat.you_flagged")
);
- this.message = ChatMessage.create(fabricators.chatChannel(), {
- user: { username: "discobot" },
- reviewable_id: 1,
- });
-
- await render(hbs`
`);
+ this.set(
+ "message",
+ ChatMessage.create({
+ user: { username: "discobot" },
+ reviewable_id: 1,
+ })
+ );
assert.strictEqual(
query(".chat-message-info__flag a .svg-icon-title").title,
@@ -119,15 +128,18 @@ module("Discourse Chat | Component | chat-message-info", function (hooks) {
});
test("with username classes", async function (assert) {
- this.message = ChatMessage.create(fabricators.chatChannel(), {
- user: {
- username: "discobot",
- admin: true,
- moderator: true,
- new_user: true,
- primary_group_name: "foo",
- },
- });
+ this.set(
+ "message",
+ ChatMessage.create({
+ user: {
+ username: "discobot",
+ admin: true,
+ moderator: true,
+ new_user: true,
+ primary_group_name: "foo",
+ },
+ })
+ );
await render(hbs`
`);
@@ -139,9 +151,7 @@ module("Discourse Chat | Component | chat-message-info", function (hooks) {
});
test("without username classes", async function (assert) {
- this.message = ChatMessage.create(fabricators.chatChannel(), {
- user: { username: "discobot" },
- });
+ this.set("message", ChatMessage.create({ user: { username: "discobot" } }));
await render(hbs`
`);
diff --git a/plugins/chat/test/javascripts/components/chat-message-reaction-test.js b/plugins/chat/test/javascripts/components/chat-message-reaction-test.js
index 4a640b949dc..86f1ca04c7f 100644
--- a/plugins/chat/test/javascripts/components/chat-message-reaction-test.js
+++ b/plugins/chat/test/javascripts/components/chat-message-reaction-test.js
@@ -7,6 +7,14 @@ import { module, test } from "qunit";
module("Discourse Chat | Component | chat-message-reaction", function (hooks) {
setupRenderingTest(hooks);
+ test("accepts arbitrary class property", async function (assert) {
+ await render(hbs`
+
+ `);
+
+ assert.true(exists(".chat-message-reaction.foo"));
+ });
+
test("adds reacted class when user reacted", async function (assert) {
await render(hbs`
@@ -21,6 +29,19 @@ module("Discourse Chat | Component | chat-message-reaction", function (hooks) {
assert.true(exists(`.chat-message-reaction[data-emoji-name="heart"]`));
});
+ test("adds show class when count is positive", async function (assert) {
+ this.set("count", 0);
+
+ await render(hbs`
+
+ `);
+
+ assert.false(exists(".chat-message-reaction.show"));
+
+ this.set("count", 1);
+ assert.true(exists(".chat-message-reaction.show"));
+ });
+
test("title/alt attributes", async function (assert) {
await render(hbs`
`);
diff --git a/plugins/chat/test/javascripts/components/chat-message-separator-date-test.js b/plugins/chat/test/javascripts/components/chat-message-separator-date-test.js
deleted file mode 100644
index 415413df7ac..00000000000
--- a/plugins/chat/test/javascripts/components/chat-message-separator-date-test.js
+++ /dev/null
@@ -1,24 +0,0 @@
-import { setupRenderingTest } from "discourse/tests/helpers/component-test";
-import { query } from "discourse/tests/helpers/qunit-helpers";
-import hbs from "htmlbars-inline-precompile";
-import { module, test } from "qunit";
-import { render } from "@ember/test-helpers";
-
-module(
- "Discourse Chat | Component | chat-message-separator-date",
- function (hooks) {
- setupRenderingTest(hooks);
-
- test("first message of the day", async function (assert) {
- this.set("date", moment().format("LLL"));
- this.set("message", { firstMessageOfTheDayAt: this.date });
-
- await render(hbs`
`);
-
- assert.strictEqual(
- query(".chat-message-separator-date").innerText.trim(),
- this.date
- );
- });
- }
-);
diff --git a/plugins/chat/test/javascripts/components/chat-message-separator-new-test.js b/plugins/chat/test/javascripts/components/chat-message-separator-new-test.js
deleted file mode 100644
index ee91a122043..00000000000
--- a/plugins/chat/test/javascripts/components/chat-message-separator-new-test.js
+++ /dev/null
@@ -1,24 +0,0 @@
-import { setupRenderingTest } from "discourse/tests/helpers/component-test";
-import { query } from "discourse/tests/helpers/qunit-helpers";
-import hbs from "htmlbars-inline-precompile";
-import I18n from "I18n";
-import { module, test } from "qunit";
-import { render } from "@ember/test-helpers";
-
-module(
- "Discourse Chat | Component | chat-message-separator-new",
- function (hooks) {
- setupRenderingTest(hooks);
-
- test("newest message", async function (assert) {
- this.set("message", { newest: true });
-
- await render(hbs`
`);
-
- assert.strictEqual(
- query(".chat-message-separator-new").innerText.trim(),
- I18n.t("chat.last_visit")
- );
- });
- }
-);
diff --git a/plugins/chat/test/javascripts/components/chat-message-separator-test.js b/plugins/chat/test/javascripts/components/chat-message-separator-test.js
new file mode 100644
index 00000000000..4b4aad0e565
--- /dev/null
+++ b/plugins/chat/test/javascripts/components/chat-message-separator-test.js
@@ -0,0 +1,35 @@
+import { setupRenderingTest } from "discourse/tests/helpers/component-test";
+import { query } from "discourse/tests/helpers/qunit-helpers";
+import hbs from "htmlbars-inline-precompile";
+import I18n from "I18n";
+import { module, test } from "qunit";
+import { render } from "@ember/test-helpers";
+
+module("Discourse Chat | Component | chat-message-separator", function (hooks) {
+ setupRenderingTest(hooks);
+
+ test("newest message", async function (assert) {
+ this.set("message", { newestMessage: true });
+
+ await render(hbs`
`);
+
+ assert.strictEqual(
+ query(".chat-message-separator.new-message .text").innerText.trim(),
+ I18n.t("chat.new_messages")
+ );
+ });
+
+ test("first message of the day", async function (assert) {
+ this.set("date", moment().format("LLL"));
+ this.set("message", { firstMessageOfTheDayAt: this.date });
+
+ await render(hbs`
`);
+
+ assert.strictEqual(
+ query(
+ ".chat-message-separator.first-daily-message .text"
+ ).innerText.trim(),
+ this.date
+ );
+ });
+});
diff --git a/plugins/chat/test/javascripts/components/chat-message-test.js b/plugins/chat/test/javascripts/components/chat-message-test.js
index 7ffa9da1d08..3dbaf2737e0 100644
--- a/plugins/chat/test/javascripts/components/chat-message-test.js
+++ b/plugins/chat/test/javascripts/components/chat-message-test.js
@@ -1,5 +1,5 @@
import User from "discourse/models/user";
-import { render } from "@ember/test-helpers";
+import { render, waitFor } from "@ember/test-helpers";
import ChatMessage from "discourse/plugins/chat/discourse/models/chat-message";
import { exists } from "discourse/tests/helpers/qunit-helpers";
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
@@ -21,16 +21,9 @@ module("Discourse Chat | Component | chat-message", function (hooks) {
unread_count: 0,
muted: false,
},
- canInteractWithChat: true,
- canDeleteSelf: true,
- canDeleteOthers: true,
- canFlag: true,
- userSilenced: false,
- canModerate: true,
});
return {
message: ChatMessage.create(
- chatChannel,
Object.assign(
{
id: 178,
@@ -45,6 +38,14 @@ module("Discourse Chat | Component | chat-message", function (hooks) {
messageData
)
),
+ canInteractWithChat: true,
+ details: {
+ can_delete_self: true,
+ can_delete_others: true,
+ can_flag: true,
+ user_silenced: false,
+ can_moderate: true,
+ },
chatChannel,
setReplyTo: () => {},
replyMessageClicked: () => {},
@@ -54,9 +55,8 @@ module("Discourse Chat | Component | chat-message", function (hooks) {
onStartSelectingMessages: () => {},
onSelectMessage: () => {},
bulkSelectMessages: () => {},
+ afterReactionAdded: () => {},
onHoverMessage: () => {},
- didShowMessage: () => {},
- didHideMessage: () => {},
};
}
@@ -64,7 +64,8 @@ module("Discourse Chat | Component | chat-message", function (hooks) {
`;
@@ -90,7 +90,6 @@ module("Discourse Chat | Component | chat-message", function (hooks) {
test("Deleted message", async function (assert) {
this.setProperties(generateMessageProps({ deleted_at: moment() }));
await render(template);
-
assert.true(
exists(".chat-message-deleted .chat-message-expand"),
"has the correct deleted css class and expand button within"
@@ -105,4 +104,16 @@ module("Discourse Chat | Component | chat-message", function (hooks) {
"has the correct hidden css class and expand button within"
);
});
+
+ test("Message marked as visible", async function (assert) {
+ this.setProperties(generateMessageProps());
+
+ await render(template);
+ await waitFor("div[data-visible=true]");
+
+ assert.true(
+ exists(".chat-message-container[data-visible=true]"),
+ "message is marked as visible"
+ );
+ });
});
diff --git a/plugins/chat/test/javascripts/components/chat-retention-reminder-test.js b/plugins/chat/test/javascripts/components/chat-retention-reminder-test.js
index 820e28a8308..cb707d011f5 100644
--- a/plugins/chat/test/javascripts/components/chat-retention-reminder-test.js
+++ b/plugins/chat/test/javascripts/components/chat-retention-reminder-test.js
@@ -14,7 +14,9 @@ module(
this.channel = ChatChannel.create({ chatable_type: "Category" });
this.currentUser.set("needs_channel_retention_reminder", true);
- await render(hbs`
`);
+ await render(
+ hbs`
`
+ );
assert.dom(".chat-retention-reminder").includesText(
I18n.t("chat.retention_reminders.public", {
diff --git a/plugins/chat/test/javascripts/helpers/fabricators.js b/plugins/chat/test/javascripts/helpers/fabricators.js
index 07cf409ee24..924685b6e6e 100644
--- a/plugins/chat/test/javascripts/helpers/fabricators.js
+++ b/plugins/chat/test/javascripts/helpers/fabricators.js
@@ -3,7 +3,6 @@ import ChatChannel, {
} from "discourse/plugins/chat/discourse/models/chat-channel";
import EmberObject from "@ember/object";
import { Fabricator } from "./fabricator";
-import ChatMessage from "discourse/plugins/chat/discourse/models/chat-message";
const userFabricator = Fabricator(EmberObject, {
id: 1,
@@ -39,7 +38,7 @@ export default {
},
}),
- chatChannelMessage: Fabricator(ChatMessage, {
+ chatChannelMessage: Fabricator(EmberObject, {
id: 1,
chat_channel_id: 1,
user_id: 1,
diff --git a/plugins/chat/test/javascripts/modifiers/track-message-visibility-test.js b/plugins/chat/test/javascripts/modifiers/track-message-visibility-test.js
new file mode 100644
index 00000000000..e05981401b3
--- /dev/null
+++ b/plugins/chat/test/javascripts/modifiers/track-message-visibility-test.js
@@ -0,0 +1,36 @@
+import { render, waitFor } from "@ember/test-helpers";
+import { exists } from "discourse/tests/helpers/qunit-helpers";
+import { setupRenderingTest } from "discourse/tests/helpers/component-test";
+import hbs from "htmlbars-inline-precompile";
+import { module, test } from "qunit";
+
+module(
+ "Discourse Chat | Modifier | track-message-visibility",
+ function (hooks) {
+ setupRenderingTest(hooks);
+
+ test("Marks message as visible when it intersects with the viewport", async function (assert) {
+ const template = hbs`
`;
+
+ await render(template);
+ await waitFor("div[data-visible=true]");
+
+ assert.ok(
+ exists("div[data-visible=true]"),
+ "message is marked as visible"
+ );
+ });
+
+ test("Marks message as visible when it doesn't intersect with the viewport", async function (assert) {
+ const template = hbs`
`;
+
+ await render(template);
+ await waitFor("div[data-visible=false]");
+
+ assert.ok(
+ exists("div[data-visible=false]"),
+ "message is not marked as visible"
+ );
+ });
+ }
+);
diff --git a/plugins/chat/test/javascripts/unit/helpers/format-chat-date-test.js b/plugins/chat/test/javascripts/unit/helpers/format-chat-date-test.js
index f6acc11a457..081732a054e 100644
--- a/plugins/chat/test/javascripts/unit/helpers/format-chat-date-test.js
+++ b/plugins/chat/test/javascripts/unit/helpers/format-chat-date-test.js
@@ -3,19 +3,12 @@ import hbs from "htmlbars-inline-precompile";
import { render } from "@ember/test-helpers";
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
import { query } from "discourse/tests/helpers/qunit-helpers";
-import fabricators from "../../helpers/fabricators";
-import ChatMessage from "discourse/plugins/chat/discourse/models/chat-message";
module("Discourse Chat | Unit | Helpers | format-chat-date", function (hooks) {
setupRenderingTest(hooks);
test("link to chat message", async function (assert) {
- const channel = fabricators.chatChannel();
- this.message = ChatMessage.create(channel, {
- id: 1,
- chat_channel_id: channel.id,
- });
-
+ this.set("message", { id: 1, chat_channel_id: 1 });
await render(hbs`{{format-chat-date this.message}}`);
assert.equal(query(".chat-time").getAttribute("href"), "/chat/c/-/1/1");