diff --git a/app/soapbox/actions/chats.js b/app/soapbox/actions/chats.js
index 729dc128e..6f8d1c788 100644
--- a/app/soapbox/actions/chats.js
+++ b/app/soapbox/actions/chats.js
@@ -23,6 +23,10 @@ export const CHAT_READ_REQUEST = 'CHAT_READ_REQUEST';
export const CHAT_READ_SUCCESS = 'CHAT_READ_SUCCESS';
export const CHAT_READ_FAIL = 'CHAT_READ_FAIL';
+export const CHAT_MESSAGE_DELETE_REQUEST = 'CHAT_MESSAGE_DELETE_REQUEST';
+export const CHAT_MESSAGE_DELETE_SUCCESS = 'CHAT_MESSAGE_DELETE_SUCCESS';
+export const CHAT_MESSAGE_DELETE_FAIL = 'CHAT_MESSAGE_DELETE_FAIL';
+
export function fetchChats() {
return (dispatch, getState) => {
dispatch({ type: CHATS_FETCH_REQUEST });
@@ -150,3 +154,14 @@ export function markChatRead(chatId, lastReadId) {
});
};
}
+
+export function deleteChatMessage(chatId, messageId) {
+ return (dispatch, getState) => {
+ dispatch({ type: CHAT_MESSAGE_DELETE_REQUEST, chatId, messageId });
+ api(getState).delete(`/api/v1/pleroma/chats/${chatId}/messages/${messageId}`).then(({ data }) => {
+ dispatch({ type: CHAT_MESSAGE_DELETE_SUCCESS, chatId, messageId, chatMessage: data });
+ }).catch(error => {
+ dispatch({ type: CHAT_MESSAGE_DELETE_FAIL, chatId, messageId, error });
+ });
+ };
+}
diff --git a/app/soapbox/actions/reports.js b/app/soapbox/actions/reports.js
index a1214fc56..9328e0141 100644
--- a/app/soapbox/actions/reports.js
+++ b/app/soapbox/actions/reports.js
@@ -25,6 +25,17 @@ export function initReport(account, status) {
};
};
+export function initReportById(accountId) {
+ return (dispatch, getState) => {
+ dispatch({
+ type: REPORT_INIT,
+ account: getState().getIn(['accounts', accountId]),
+ });
+
+ dispatch(openModal('REPORT'));
+ };
+};
+
export function cancelReport() {
return {
type: REPORT_CANCEL,
diff --git a/app/soapbox/features/chats/components/chat_box.js b/app/soapbox/features/chats/components/chat_box.js
index cded7fd70..2a0b72e89 100644
--- a/app/soapbox/features/chats/components/chat_box.js
+++ b/app/soapbox/features/chats/components/chat_box.js
@@ -18,6 +18,7 @@ import IconButton from 'soapbox/components/icon_button';
const messages = defineMessages({
placeholder: { id: 'chat_box.input.placeholder', defaultMessage: 'Send a messageā¦' },
+ send: { id: 'chat_box.actions.send', defaultMessage: 'Send' },
});
const mapStateToProps = (state, { chatId }) => ({
@@ -164,11 +165,17 @@ class ChatBox extends ImmutablePureComponent {
}
renderActionButton = () => {
+ const { intl } = this.props;
const { resetFileKey } = this.state;
return this.canSubmit() ? (
-
+
) : (
diff --git a/app/soapbox/features/chats/components/chat_message_list.js b/app/soapbox/features/chats/components/chat_message_list.js
index 0c21c6b27..af35f216d 100644
--- a/app/soapbox/features/chats/components/chat_message_list.js
+++ b/app/soapbox/features/chats/components/chat_message_list.js
@@ -5,16 +5,21 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import { injectIntl, defineMessages } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
-import { fetchChatMessages } from 'soapbox/actions/chats';
+import { fetchChatMessages, deleteChatMessage } from 'soapbox/actions/chats';
import emojify from 'soapbox/features/emoji/emoji';
import classNames from 'classnames';
import { openModal } from 'soapbox/actions/modal';
import { escape, throttle } from 'lodash';
import { MediaGallery } from 'soapbox/features/ui/util/async-components';
import Bundle from 'soapbox/features/ui/components/bundle';
+import DropdownMenuContainer from 'soapbox/containers/dropdown_menu_container';
+import { initReportById } from 'soapbox/actions/reports';
const messages = defineMessages({
today: { id: 'chats.dividers.today', defaultMessage: 'Today' },
+ more: { id: 'chats.actions.more', defaultMessage: 'More' },
+ delete: { id: 'chats.actions.delete', defaultMessage: 'Delete message' },
+ report: { id: 'chats.actions.report', defaultMessage: 'Report user' },
});
const timeChange = (prev, curr) => {
@@ -198,7 +203,8 @@ class ChatMessageList extends ImmutablePureComponent {
parseContent = chatMessage => {
const content = chatMessage.get('content') || '';
const pending = chatMessage.get('pending', false);
- const formatted = pending ? this.parsePendingContent(content) : content;
+ const deleting = chatMessage.get('deleting', false);
+ const formatted = (pending && !deleting) ? this.parsePendingContent(content) : content;
const emojiMap = makeEmojiMap(chatMessage);
return emojify(formatted, emojiMap.toJS());
}
@@ -211,8 +217,24 @@ class ChatMessageList extends ImmutablePureComponent {
{text}
)
+ handleDeleteMessage = (chatId, messageId) => {
+ return () => {
+ this.props.dispatch(deleteChatMessage(chatId, messageId));
+ };
+ }
+
+ handleReportUser = (userId) => {
+ return () => {
+ this.props.dispatch(initReportById(userId));
+ };
+ }
+
renderMessage = (chatMessage) => {
- const { me } = this.props;
+ const { me, intl } = this.props;
+ const menu = [
+ { text: intl.formatMessage(messages.delete), action: this.handleDeleteMessage(chatMessage.get('chat_id'), chatMessage.get('id')) },
+ { text: intl.formatMessage(messages.report), action: this.handleReportUser(chatMessage.get('account_id')) },
+ ];
return (
{this.maybeRenderMedia(chatMessage)}
+
+
+
);
diff --git a/app/soapbox/reducers/chat_message_lists.js b/app/soapbox/reducers/chat_message_lists.js
index 3e2d1dbae..8848a8389 100644
--- a/app/soapbox/reducers/chat_message_lists.js
+++ b/app/soapbox/reducers/chat_message_lists.js
@@ -3,6 +3,7 @@ import {
CHAT_MESSAGES_FETCH_SUCCESS,
CHAT_MESSAGE_SEND_REQUEST,
CHAT_MESSAGE_SEND_SUCCESS,
+ CHAT_MESSAGE_DELETE_SUCCESS,
} from 'soapbox/actions/chats';
import { STREAMING_CHAT_UPDATE } from 'soapbox/actions/streaming';
import { Map as ImmutableMap, OrderedSet as ImmutableOrderedSet } from 'immutable';
@@ -59,6 +60,8 @@ export default function chatMessageLists(state = initialState, action) {
return updateList(state, action.chatId, action.chatMessages.map(chat => chat.id));
case CHAT_MESSAGE_SEND_SUCCESS:
return replaceMessage(state, action.chatId, action.uuid, action.chatMessage.id);
+ case CHAT_MESSAGE_DELETE_SUCCESS:
+ return state.update(action.chatId, chat => chat.delete(action.messageId));
default:
return state;
}
diff --git a/app/soapbox/reducers/chat_messages.js b/app/soapbox/reducers/chat_messages.js
index 74d83ef79..ababe85bd 100644
--- a/app/soapbox/reducers/chat_messages.js
+++ b/app/soapbox/reducers/chat_messages.js
@@ -3,6 +3,8 @@ import {
CHAT_MESSAGES_FETCH_SUCCESS,
CHAT_MESSAGE_SEND_REQUEST,
CHAT_MESSAGE_SEND_SUCCESS,
+ CHAT_MESSAGE_DELETE_REQUEST,
+ CHAT_MESSAGE_DELETE_SUCCESS,
} from 'soapbox/actions/chats';
import { STREAMING_CHAT_UPDATE } from 'soapbox/actions/streaming';
import { Map as ImmutableMap, fromJS } from 'immutable';
@@ -43,6 +45,11 @@ export default function chatMessages(state = initialState, action) {
return importMessage(state, fromJS(action.chatMessage)).delete(action.uuid);
case STREAMING_CHAT_UPDATE:
return importLastMessages(state, fromJS([action.chat]));
+ case CHAT_MESSAGE_DELETE_REQUEST:
+ return state.update(action.messageId, chatMessage =>
+ chatMessage.set('pending', true).set('deleting', true));
+ case CHAT_MESSAGE_DELETE_SUCCESS:
+ return state.delete(action.messageId);
default:
return state;
}
diff --git a/app/styles/chats.scss b/app/styles/chats.scss
index 126c371a2..e6fc5d601 100644
--- a/app/styles/chats.scss
+++ b/app/styles/chats.scss
@@ -146,14 +146,23 @@
max-width: 70%;
border-radius: 10px;
background-color: var(--background-color);
- overflow: hidden;
text-overflow: ellipsis;
overflow-wrap: break-word;
white-space: break-spaces;
+ position: relative;
a {
color: var(--brand-color--hicontrast);
}
+
+ &:hover,
+ &:focus,
+ &:active, {
+ .chat-message__menu {
+ opacity: 1;
+ pointer-events: all;
+ }
+ }
}
&--me .chat-message__bubble {
@@ -164,6 +173,17 @@
&--pending .chat-message__bubble {
opacity: 0.5;
}
+
+ &__menu {
+ position: absolute;
+ top: -8px;
+ right: -8px;
+ background: var(--background-color);
+ border-radius: 999px;
+ opacity: 0;
+ pointer-events: none;
+ transition: 0.2s;
+ }
}
.chat-list {
diff --git a/app/styles/components/columns.scss b/app/styles/components/columns.scss
index 0351bd008..9dd1a58ea 100644
--- a/app/styles/components/columns.scss
+++ b/app/styles/components/columns.scss
@@ -713,5 +713,6 @@
.react-toggle-track-check,
.react-toggle-track-x {
height: 16px;
+ color: white;
}
}