mirror of
https://github.com/hanzoai/forum.git
synced 2026-09-30 21:51:19 +00:00
This feature has been mistakenly removed in https://github.com/discourse/discourse/commit/4fbcbb865ef330a73dbdee406162677fe05aea67 <img width="382" alt="Screenshot 2025-01-13 at 20 49 03" src="https://github.com/user-attachments/assets/9710255d-3a1b-4e52-9acc-4e9c410db1b9" />
444 lines
11 KiB
JavaScript
444 lines
11 KiB
JavaScript
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";
|
|
import { BookmarkFormData } from "discourse/lib/bookmark-form-data";
|
|
import { bind } from "discourse/lib/decorators";
|
|
import getURL from "discourse/lib/get-url";
|
|
import { clipboardCopy } from "discourse/lib/utilities";
|
|
import Bookmark from "discourse/models/bookmark";
|
|
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 ChatMessageReactionModel, {
|
|
REACTIONS,
|
|
} from "discourse/plugins/chat/discourse/models/chat-message-reaction";
|
|
|
|
const removedSecondaryActions = new Set();
|
|
|
|
export function removeChatComposerSecondaryActions(actionIds) {
|
|
actionIds.forEach((id) => removedSecondaryActions.add(id));
|
|
}
|
|
|
|
export function resetRemovedChatComposerSecondaryActions() {
|
|
removedSecondaryActions.clear();
|
|
}
|
|
|
|
export default class ChatemojiReactions {
|
|
@service appEvents;
|
|
@service dialog;
|
|
@service chat;
|
|
@service chatChannelComposer;
|
|
@service chatThreadComposer;
|
|
@service chatChannelPane;
|
|
@service chatThreadPane;
|
|
@service chatApi;
|
|
@service currentUser;
|
|
@service site;
|
|
@service router;
|
|
@service modal;
|
|
@service capabilities;
|
|
@service siteSettings;
|
|
@service menu;
|
|
@service toasts;
|
|
@service interactedChatMessage;
|
|
@service emojiStore;
|
|
|
|
@tracked message = null;
|
|
@tracked context = null;
|
|
|
|
constructor(owner, message, context) {
|
|
setOwner(this, owner);
|
|
|
|
this.message = message;
|
|
this.context = context;
|
|
}
|
|
|
|
get emojiReactions() {
|
|
const defaultReactions = this.siteSettings.default_emoji_reactions
|
|
.split("|")
|
|
.filter(Boolean);
|
|
|
|
return this.emojiStore
|
|
.favoritesForContext(`channel_${this.message.channel.id}`)
|
|
.concat(defaultReactions)
|
|
.slice(0, 3)
|
|
.map(
|
|
(emoji) =>
|
|
this.message.reactions.find((reaction) => reaction.emoji === emoji) ||
|
|
ChatMessageReactionModel.create({ emoji })
|
|
);
|
|
}
|
|
|
|
get pane() {
|
|
return this.context === MESSAGE_CONTEXT_THREAD
|
|
? this.chatThreadPane
|
|
: this.chatChannelPane;
|
|
}
|
|
|
|
get canEdit() {
|
|
return (
|
|
!this.message.deletedAt &&
|
|
this.currentUser.id === this.message.user.id &&
|
|
this.message.channel?.canModifyMessages?.(this.currentUser)
|
|
);
|
|
}
|
|
|
|
get canInteractWithMessage() {
|
|
return (
|
|
!this.message?.deletedAt &&
|
|
this.message?.channel?.canModifyMessages(this.currentUser)
|
|
);
|
|
}
|
|
|
|
get canRestoreMessage() {
|
|
return (
|
|
this.message?.deletedAt &&
|
|
(this.currentUser.staff ||
|
|
(this.message?.user?.id === this.currentUser.id &&
|
|
this.message?.deletedById === this.currentUser.id)) &&
|
|
this.message.channel?.canModifyMessages?.(this.currentUser)
|
|
);
|
|
}
|
|
|
|
get canBookmark() {
|
|
return this.message?.channel?.canModifyMessages?.(this.currentUser);
|
|
}
|
|
|
|
get canReply() {
|
|
return (
|
|
this.canInteractWithMessage && this.context !== MESSAGE_CONTEXT_THREAD
|
|
);
|
|
}
|
|
|
|
get canReact() {
|
|
return this.canInteractWithMessage;
|
|
}
|
|
|
|
get canFlagMessage() {
|
|
return (
|
|
this.currentUser.id !== this.message?.user?.id &&
|
|
this.message?.userFlagStatus === undefined &&
|
|
this.message.channel?.canFlag &&
|
|
!this.message?.chatWebhookEvent &&
|
|
!this.message?.deletedAt
|
|
);
|
|
}
|
|
|
|
get canRebakeMessage() {
|
|
return (
|
|
this.currentUser.staff &&
|
|
this.message.channel?.canModifyMessages?.(this.currentUser)
|
|
);
|
|
}
|
|
|
|
get canDeleteMessage() {
|
|
return (
|
|
this.canDelete &&
|
|
!this.message?.deletedAt &&
|
|
this.message.channel?.canModifyMessages?.(this.currentUser)
|
|
);
|
|
}
|
|
|
|
get canDelete() {
|
|
return this.currentUser.id === this.message.user.id
|
|
? this.message.channel?.canDeleteSelf
|
|
: this.message.channel?.canDeleteOthers;
|
|
}
|
|
|
|
get composer() {
|
|
return this.context === MESSAGE_CONTEXT_THREAD
|
|
? this.chatThreadComposer
|
|
: this.chatChannelComposer;
|
|
}
|
|
|
|
get secondaryActions() {
|
|
const buttons = [];
|
|
|
|
buttons.push({
|
|
id: "copyLink",
|
|
name: i18n("chat.copy_link"),
|
|
icon: "link",
|
|
});
|
|
|
|
if (this.site.mobileView) {
|
|
buttons.push({
|
|
id: "copyText",
|
|
name: i18n("chat.copy_text"),
|
|
icon: "clipboard",
|
|
});
|
|
}
|
|
|
|
if (this.canEdit) {
|
|
buttons.push({
|
|
id: "edit",
|
|
name: i18n("chat.edit"),
|
|
icon: "pencil",
|
|
});
|
|
}
|
|
|
|
if (!this.pane.selectingMessages) {
|
|
buttons.push({
|
|
id: "select",
|
|
name: i18n("chat.select"),
|
|
icon: "list-check",
|
|
});
|
|
}
|
|
|
|
if (this.canFlagMessage) {
|
|
buttons.push({
|
|
id: "flag",
|
|
name: i18n("chat.flag"),
|
|
icon: "flag",
|
|
});
|
|
}
|
|
|
|
if (this.canDeleteMessage) {
|
|
buttons.push({
|
|
id: "delete",
|
|
name: i18n("chat.delete"),
|
|
icon: "trash-can",
|
|
});
|
|
}
|
|
|
|
if (this.canRestoreMessage) {
|
|
buttons.push({
|
|
id: "restore",
|
|
name: i18n("chat.restore"),
|
|
icon: "arrow-rotate-left",
|
|
});
|
|
}
|
|
|
|
if (this.canRebakeMessage) {
|
|
buttons.push({
|
|
id: "rebake",
|
|
name: i18n("chat.rebake_message"),
|
|
icon: "rotate",
|
|
});
|
|
}
|
|
|
|
return buttons.reject((button) => removedSecondaryActions.has(button.id));
|
|
}
|
|
|
|
select(checked = true) {
|
|
this.message.selected = checked;
|
|
this.pane.onSelectMessage(this.message);
|
|
}
|
|
|
|
bulkSelect(checked) {
|
|
const manager = this.message.manager;
|
|
const lastSelectedIndex = manager.findIndexOfMessage(
|
|
this.pane.lastSelectedMessage.id
|
|
);
|
|
const newlySelectedIndex = manager.findIndexOfMessage(this.message.id);
|
|
const sortedIndices = [lastSelectedIndex, newlySelectedIndex].sort(
|
|
(a, b) => a - b
|
|
);
|
|
|
|
for (let i = sortedIndices[0]; i <= sortedIndices[1]; i++) {
|
|
manager.messages[i].selected = checked;
|
|
}
|
|
}
|
|
|
|
copyText() {
|
|
clipboardCopy(this.message.message);
|
|
this.toasts.success({
|
|
duration: 3000,
|
|
data: { message: i18n("chat.text_copied") },
|
|
});
|
|
}
|
|
|
|
copyLink() {
|
|
const { protocol, host } = window.location;
|
|
const channelId = this.message.channel.id;
|
|
const threadId = this.message.thread?.id;
|
|
|
|
let url;
|
|
if (this.context === MESSAGE_CONTEXT_THREAD && threadId) {
|
|
url = getURL(`/chat/c/-/${channelId}/t/${threadId}/${this.message.id}`);
|
|
} else {
|
|
url = getURL(`/chat/c/-/${channelId}/${this.message.id}`);
|
|
}
|
|
|
|
url = url.indexOf("/") === 0 ? protocol + "//" + host + url : url;
|
|
clipboardCopy(url);
|
|
this.toasts.success({
|
|
duration: 1500,
|
|
data: { message: i18n("chat.link_copied") },
|
|
});
|
|
}
|
|
|
|
@action
|
|
react(emoji, reactAction) {
|
|
if (!this.chat.userCanInteractWithChat) {
|
|
return;
|
|
}
|
|
|
|
if (this.pane.reacting) {
|
|
return;
|
|
}
|
|
|
|
if (this.capabilities.userHasBeenActive && this.capabilities.canVibrate) {
|
|
navigator.vibrate(5);
|
|
}
|
|
|
|
if (this.site.mobileView) {
|
|
this.chat.activeMessage = null;
|
|
}
|
|
|
|
this.pane.reacting = true;
|
|
|
|
this.message.react(
|
|
emoji,
|
|
reactAction,
|
|
this.currentUser,
|
|
this.currentUser.id
|
|
);
|
|
|
|
return this.chatApi
|
|
.publishReaction(
|
|
this.message.channel.id,
|
|
this.message.id,
|
|
emoji,
|
|
reactAction
|
|
)
|
|
.catch((errResult) => {
|
|
popupAjaxError(errResult);
|
|
this.message.react(
|
|
emoji,
|
|
REACTIONS.remove,
|
|
this.currentUser,
|
|
this.currentUser.id
|
|
);
|
|
})
|
|
.finally(() => {
|
|
this.pane.reacting = false;
|
|
});
|
|
}
|
|
|
|
@action
|
|
toggleBookmark() {
|
|
// somehow, this works around a low-level chrome rendering issue which
|
|
// causes a complete browser crash when saving/deleting bookmarks in chat.
|
|
// Error message: "Check failed: !NeedsToUpdateCachedValues()."
|
|
// Internal topic: t/143485
|
|
// Hopefully, this can be dropped in future chrome versions
|
|
document.activeElement?.blur();
|
|
|
|
this.modal.show(BookmarkModal, {
|
|
model: {
|
|
bookmark: new BookmarkFormData(
|
|
this.message.bookmark ||
|
|
Bookmark.createFor(
|
|
this.currentUser,
|
|
"Chat::Message",
|
|
this.message.id
|
|
)
|
|
),
|
|
afterSave: (bookmarkFormData) => {
|
|
const bookmark = Bookmark.create(bookmarkFormData.saveData);
|
|
this.message.bookmark = bookmark;
|
|
this.appEvents.trigger(
|
|
"bookmarks:changed",
|
|
bookmarkFormData.saveData,
|
|
bookmark.attachedTo()
|
|
);
|
|
},
|
|
afterDelete: () => {
|
|
this.message.bookmark = null;
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
@action
|
|
flag() {
|
|
const model = new ChatMessage(this.message.channel, this.message);
|
|
model.username = this.message.user?.username;
|
|
model.user_id = this.message.user?.id;
|
|
this.modal.show(FlagModal, {
|
|
model: {
|
|
flagTarget: new ChatMessageFlag(getOwner(this)),
|
|
flagModel: model,
|
|
setHidden: () => model.set("hidden", true),
|
|
},
|
|
});
|
|
}
|
|
|
|
@action
|
|
delete() {
|
|
return this.chatApi
|
|
.trashMessage(this.message.channel.id, this.message.id)
|
|
.catch(popupAjaxError);
|
|
}
|
|
|
|
@action
|
|
restore() {
|
|
return this.chatApi
|
|
.restoreMessage(this.message.channel.id, this.message.id)
|
|
.catch(popupAjaxError);
|
|
}
|
|
|
|
@action
|
|
rebake() {
|
|
return this.chatApi
|
|
.rebakeMessage(this.message.channel.id, this.message.id)
|
|
.catch(popupAjaxError);
|
|
}
|
|
|
|
@action
|
|
reply() {
|
|
this.composer.replyTo(this.message);
|
|
}
|
|
|
|
@action
|
|
edit() {
|
|
this.composer.edit(this.message);
|
|
}
|
|
|
|
@action
|
|
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
|
|
selectReaction(emoji) {
|
|
if (!this.chat.userCanInteractWithChat) {
|
|
return;
|
|
}
|
|
|
|
this.react(emoji, REACTIONS.add);
|
|
}
|
|
|
|
@action
|
|
handleSecondaryActions(id) {
|
|
this[id](this.message);
|
|
}
|
|
}
|