mirror of
https://github.com/oxen-io/session-desktop.git
synced 2023-12-14 02:12:57 +01:00
Add more functionality to the conversation right click menu, add right click for messages, change some of the ways toasts/confirmation dialogs are created, auto focus text input for nickname, clean up some stuff
This commit is contained in:
parent
bf76767ed8
commit
d0d57ea8c7
|
@ -1801,6 +1801,15 @@
|
|||
"message": "Copied public key",
|
||||
"description": "A toast message telling the user that the key was copied"
|
||||
},
|
||||
"copyMessage": {
|
||||
"message": "Copy message text",
|
||||
"description":
|
||||
"Button action that the user can click to copy their public keys"
|
||||
},
|
||||
"copiedMessage": {
|
||||
"message": "Copied message text",
|
||||
"description": "A toast message telling the user that the message text was copied"
|
||||
},
|
||||
"editDisplayName": {
|
||||
"message": "Edit display name",
|
||||
"description":
|
||||
|
|
|
@ -623,9 +623,15 @@
|
|||
}
|
||||
});
|
||||
|
||||
Whisper.events.on('showToast', options => {
|
||||
if (appView && appView.inboxView && appView.inboxView.conversation_stack) {
|
||||
appView.inboxView.conversation_stack.showToast(options);
|
||||
}
|
||||
});
|
||||
|
||||
Whisper.events.on('showConfirmationDialog', options => {
|
||||
if (appView) {
|
||||
appView.showConfirmationDialog(options);
|
||||
if (appView && appView.inboxView && appView.inboxView.conversation_stack) {
|
||||
appView.inboxView.conversation_stack.showConfirmationDialog(options);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
/* global Backbone: false */
|
||||
/* global BlockedNumberController: false */
|
||||
/* global ConversationController: false */
|
||||
/* global clipboard: false */
|
||||
/* global i18n: false */
|
||||
/* global profileImages: false */
|
||||
/* global storage: false */
|
||||
|
@ -415,10 +416,17 @@
|
|||
text: this.lastMessage,
|
||||
},
|
||||
isOnline: this.isOnline(),
|
||||
isMe: this.isMe(),
|
||||
hasNickname: !!this.getNickname(),
|
||||
|
||||
onClick: () => this.trigger('select', this),
|
||||
onBlockContact: () => this.block(),
|
||||
onUnblockContact: () => this.unblock(),
|
||||
onChangeNickname: () => this.changeNickname(),
|
||||
onClearNickname: async () => this.setNickname(null),
|
||||
onCopyPublicKey: () => this.copyPublicKey(),
|
||||
onDeleteContact: () => this.deleteContact(),
|
||||
onDeleteMessages: () => this.deleteMessages(),
|
||||
};
|
||||
|
||||
return result;
|
||||
|
@ -2044,6 +2052,35 @@
|
|||
});
|
||||
},
|
||||
|
||||
copyPublicKey() {
|
||||
clipboard.writeText(this.id);
|
||||
window.Whisper.events.trigger('showToast', {
|
||||
message: i18n('copiedPublicKey'),
|
||||
});
|
||||
},
|
||||
|
||||
changeNickname() {
|
||||
window.Whisper.events.trigger('showNicknameDialog', {
|
||||
pubKey: this.id,
|
||||
nickname: this.getNickname(),
|
||||
onOk: newName => this.setNickname(newName),
|
||||
});
|
||||
},
|
||||
|
||||
deleteContact() {
|
||||
Whisper.events.trigger('showConfirmationDialog', {
|
||||
message: i18n('deleteContactConfirmation'),
|
||||
onOk: () => ConversationController.deleteContact(this.id),
|
||||
});
|
||||
},
|
||||
|
||||
deleteMessages() {
|
||||
Whisper.events.trigger('showConfirmationDialog', {
|
||||
message: i18n('deleteConversationConfirmation'),
|
||||
onOk: () => this.destroyMessages(),
|
||||
});
|
||||
},
|
||||
|
||||
async destroyMessages() {
|
||||
await window.Signal.Data.removeAllMessagesInConversation(this.id, {
|
||||
MessageCollection: Whisper.MessageCollection,
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
/* global storage: false */
|
||||
/* global filesize: false */
|
||||
/* global ConversationController: false */
|
||||
/* global clipboard: false */
|
||||
/* global getAccountManager: false */
|
||||
/* global i18n: false */
|
||||
/* global Signal: false */
|
||||
|
@ -593,6 +594,7 @@
|
|||
expirationTimestamp,
|
||||
isP2p: !!this.get('isP2p'),
|
||||
|
||||
onCopyText: () => this.copyText(),
|
||||
onReply: () => this.trigger('reply', this),
|
||||
onRetrySend: () => this.retrySend(),
|
||||
onShowDetail: () => this.trigger('show-message-detail', this),
|
||||
|
@ -872,6 +874,13 @@
|
|||
};
|
||||
},
|
||||
|
||||
copyText() {
|
||||
clipboard.writeText(this.get('body'));
|
||||
window.Whisper.events.trigger('showToast', {
|
||||
message: i18n('copiedMessage'),
|
||||
});
|
||||
},
|
||||
|
||||
// One caller today: event handler for the 'Retry Send' entry in triple-dot menu
|
||||
async retrySend() {
|
||||
if (!textsecure.messaging) {
|
||||
|
|
|
@ -176,15 +176,6 @@
|
|||
});
|
||||
}
|
||||
},
|
||||
showConfirmationDialog({ title, message, onOk, onCancel }) {
|
||||
const dialog = new Whisper.ConfirmationDialogView({
|
||||
title,
|
||||
message,
|
||||
resolve: onOk,
|
||||
reject: onCancel,
|
||||
});
|
||||
this.el.append(dialog.el);
|
||||
},
|
||||
showNicknameDialog({ pubKey, title, message, nickname, onOk, onCancel }) {
|
||||
const _title = title || `Change nickname for ${pubKey}`;
|
||||
const dialog = new Whisper.NicknameDialogView({
|
||||
|
@ -195,6 +186,7 @@
|
|||
reject: onCancel,
|
||||
});
|
||||
this.el.append(dialog.el);
|
||||
dialog.focusInput();
|
||||
},
|
||||
showPasswordDialog({ type, resolve, reject }) {
|
||||
const dialog = Whisper.getPasswordDialogView(type, resolve, reject);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* global Whisper, Signal, Backbone, ConversationController, i18n */
|
||||
/* global Whisper, Signal, Backbone */
|
||||
|
||||
// eslint-disable-next-line func-names
|
||||
(function() {
|
||||
|
@ -26,23 +26,7 @@
|
|||
},
|
||||
|
||||
getProps() {
|
||||
const modelProps = this.model.getPropsForListItem();
|
||||
const props = {
|
||||
...modelProps,
|
||||
onDeleteContact: () => {
|
||||
Whisper.events.trigger('showConfirmationDialog', {
|
||||
message: i18n('deleteContactConfirmation'),
|
||||
onOk: () => ConversationController.deleteContact(this.model.id),
|
||||
});
|
||||
},
|
||||
onDeleteMessages: () => {
|
||||
Whisper.events.trigger('showConfirmationDialog', {
|
||||
message: i18n('deleteConversationConfirmation'),
|
||||
onOk: () => this.model.destroyMessages(),
|
||||
});
|
||||
},
|
||||
};
|
||||
return props;
|
||||
return this.model.getPropsForListItem();
|
||||
},
|
||||
|
||||
render() {
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
textsecure,
|
||||
Whisper,
|
||||
ConversationController,
|
||||
clipboard
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line func-names
|
||||
|
@ -208,7 +207,7 @@
|
|||
onSetDisappearingMessages: seconds =>
|
||||
this.setDisappearingMessages(seconds),
|
||||
onDeleteMessages: () => this.destroyMessages(),
|
||||
onDeleteContact: () => this.deleteContact(),
|
||||
onDeleteContact: () => this.model.deleteContact(),
|
||||
onResetSession: () => this.endSession(),
|
||||
|
||||
// These are view only and don't update the Conversation model, so they
|
||||
|
@ -236,22 +235,13 @@
|
|||
this.model.unblock();
|
||||
},
|
||||
onChangeNickname: () => {
|
||||
window.Whisper.events.trigger('showNicknameDialog', {
|
||||
pubKey: this.model.id,
|
||||
nickname: this.model.getNickname(),
|
||||
onOk: newName => this.model.setNickname(newName),
|
||||
});
|
||||
this.model.changeNickname()
|
||||
},
|
||||
onClearNickname: async () => {
|
||||
this.model.setNickname(null);
|
||||
},
|
||||
onCopyPublicKey: () => {
|
||||
clipboard.writeText(this.model.id);
|
||||
const toast = new Whisper.MessageToastView({
|
||||
message: i18n('copiedPublicKey'),
|
||||
});
|
||||
toast.$el.appendTo(this.$el);
|
||||
toast.render();
|
||||
this.model.copyPublicKey()
|
||||
},
|
||||
};
|
||||
};
|
||||
|
@ -1448,33 +1438,16 @@
|
|||
}
|
||||
},
|
||||
|
||||
async deleteContact() {
|
||||
destroyMessages() {
|
||||
Whisper.events.trigger('showConfirmationDialog', {
|
||||
message: i18n('deleteContactConfirmation'),
|
||||
onOk: () => {
|
||||
ConversationController.deleteContact(this.model.id);
|
||||
message: i18n('deleteConversationConfirmation'),
|
||||
onOk: async () => {
|
||||
await this.model.destroyMessages();
|
||||
this.remove();
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
async destroyMessages() {
|
||||
try {
|
||||
await this.confirm(i18n('deleteConversationConfirmation'));
|
||||
try {
|
||||
await this.model.destroyMessages();
|
||||
this.remove();
|
||||
} catch (error) {
|
||||
window.log.error(
|
||||
'destroyMessages: Failed to successfully delete conversation',
|
||||
error && error.stack ? error.stack : error
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
// nothing to see here, user canceled out of dialog
|
||||
}
|
||||
},
|
||||
|
||||
showSendConfirmationDialog(e, contacts) {
|
||||
let message;
|
||||
const isUnverified = this.model.isUnverified();
|
||||
|
|
|
@ -44,6 +44,22 @@
|
|||
$el.remove();
|
||||
}
|
||||
},
|
||||
showToast({ message }) {
|
||||
const toast = new Whisper.MessageToastView({
|
||||
message,
|
||||
});
|
||||
toast.$el.appendTo(this.$el);
|
||||
toast.render();
|
||||
},
|
||||
showConfirmationDialog({ title, message, onOk, onCancel }) {
|
||||
const dialog = new Whisper.ConfirmationDialogView({
|
||||
title,
|
||||
message,
|
||||
resolve: onOk,
|
||||
reject: onCancel,
|
||||
});
|
||||
this.el.append(dialog.el);
|
||||
},
|
||||
});
|
||||
|
||||
Whisper.FontSizeView = Whisper.View.extend({
|
||||
|
|
|
@ -90,8 +90,8 @@
|
|||
}
|
||||
event.preventDefault();
|
||||
},
|
||||
focusCancel() {
|
||||
this.$('.cancel').focus();
|
||||
focusInput() {
|
||||
this.$input.focus();
|
||||
},
|
||||
});
|
||||
})();
|
||||
|
|
|
@ -30,12 +30,17 @@ interface Props {
|
|||
showFriendRequestIndicator?: boolean;
|
||||
isBlocked: boolean;
|
||||
isOnline: boolean;
|
||||
isMe: boolean;
|
||||
hasNickname: boolean;
|
||||
|
||||
i18n: Localizer;
|
||||
onClick?: () => void;
|
||||
onDeleteMessages?: () => void;
|
||||
onDeleteContact?: () => void;
|
||||
onBlockContact?: () => void;
|
||||
onChangeNickname?: () => void;
|
||||
onClearNickname?: () => void;
|
||||
onCopyPublicKey?: () => void;
|
||||
onUnblockContact?: () => void;
|
||||
}
|
||||
|
||||
|
@ -136,21 +141,38 @@ export class ConversationListItem extends React.Component<Props> {
|
|||
const {
|
||||
i18n,
|
||||
isBlocked,
|
||||
isMe,
|
||||
hasNickname,
|
||||
onDeleteContact,
|
||||
onDeleteMessages,
|
||||
onBlockContact,
|
||||
onChangeNickname,
|
||||
onClearNickname,
|
||||
onCopyPublicKey,
|
||||
onUnblockContact,
|
||||
} = this.props;
|
||||
|
||||
const blockTitle = isBlocked ? i18n('unblockUser') : i18n('blockUser');
|
||||
const blockHandler = isBlocked ? onUnblockContact : onBlockContact;
|
||||
|
||||
return (
|
||||
<ContextMenu id={triggerId}>
|
||||
{isBlocked ? (
|
||||
<MenuItem onClick={onUnblockContact}>{i18n('unblockUser')}</MenuItem>
|
||||
) : (
|
||||
<MenuItem onClick={onBlockContact}>{i18n('blockUser')}</MenuItem>
|
||||
)}
|
||||
{!isMe ? (
|
||||
<MenuItem onClick={blockHandler}>{blockTitle}</MenuItem>
|
||||
) : null}
|
||||
{!isMe ? (
|
||||
<MenuItem onClick={onChangeNickname}>
|
||||
{i18n('changeNickname')}
|
||||
</MenuItem>
|
||||
) : null}
|
||||
{!isMe && hasNickname ? (
|
||||
<MenuItem onClick={onClearNickname}>{i18n('clearNickname')}</MenuItem>
|
||||
) : null}
|
||||
<MenuItem onClick={onCopyPublicKey}>{i18n('copyPublicKey')}</MenuItem>
|
||||
<MenuItem onClick={onDeleteMessages}>{i18n('deleteMessages')}</MenuItem>
|
||||
<MenuItem onClick={onDeleteContact}>{i18n('deleteContact')}</MenuItem>
|
||||
{!isMe ? (
|
||||
<MenuItem onClick={onDeleteContact}>{i18n('deleteContact')}</MenuItem>
|
||||
) : null}
|
||||
</ContextMenu>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -248,7 +248,9 @@ export class ConversationHeader extends React.Component<Props> {
|
|||
) : null}
|
||||
<MenuItem onClick={onCopyPublicKey}>{i18n('copyPublicKey')}</MenuItem>
|
||||
<MenuItem onClick={onDeleteMessages}>{i18n('deleteMessages')}</MenuItem>
|
||||
<MenuItem onClick={onDeleteContact}>{i18n('deleteContact')}</MenuItem>
|
||||
{!isMe ? (
|
||||
<MenuItem onClick={onDeleteContact}>{i18n('deleteContact')}</MenuItem>
|
||||
) : null}
|
||||
</ContextMenu>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -83,6 +83,7 @@ export interface Props {
|
|||
|
||||
onClickAttachment?: (attachment: AttachmentType) => void;
|
||||
onClickLinkPreview?: (url: string) => void;
|
||||
onCopyText?: () => void;
|
||||
onReply?: () => void;
|
||||
onRetrySend?: () => void;
|
||||
onDownload?: (isDangerous: boolean) => void;
|
||||
|
@ -785,6 +786,7 @@ export class Message extends React.Component<Props, State> {
|
|||
public renderContextMenu(triggerId: string) {
|
||||
const {
|
||||
attachments,
|
||||
onCopyText,
|
||||
direction,
|
||||
status,
|
||||
onDelete,
|
||||
|
@ -817,6 +819,11 @@ export class Message extends React.Component<Props, State> {
|
|||
{i18n('downloadAttachment')}
|
||||
</MenuItem>
|
||||
) : null}
|
||||
<MenuItem
|
||||
onClick={onCopyText}
|
||||
>
|
||||
{i18n('copyMessage')}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
attributes={{
|
||||
className: 'module-message__context__reply',
|
||||
|
@ -933,6 +940,7 @@ export class Message extends React.Component<Props, State> {
|
|||
// This id is what connects our triple-dot click with our associated pop-up menu.
|
||||
// It needs to be unique.
|
||||
const triggerId = String(id || `${authorPhoneNumber}-${timestamp}`);
|
||||
const rightClickTriggerId = `${authorPhoneNumber}-ctx-${timestamp}`;
|
||||
|
||||
if (expired) {
|
||||
return null;
|
||||
|
@ -942,40 +950,45 @@ export class Message extends React.Component<Props, State> {
|
|||
const isShowingImage = this.isShowingImage();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'module-message',
|
||||
`module-message--${direction}`,
|
||||
expiring ? 'module-message--expired' : null
|
||||
)}
|
||||
>
|
||||
{this.renderError(direction === 'incoming')}
|
||||
{this.renderMenu(direction === 'outgoing', triggerId)}
|
||||
<div
|
||||
className={classNames(
|
||||
'module-message__container',
|
||||
`module-message__container--${direction}`,
|
||||
direction === 'incoming'
|
||||
? `module-message__container--incoming-${authorColor}`
|
||||
: null
|
||||
)}
|
||||
style={{
|
||||
width: isShowingImage ? width : undefined,
|
||||
}}
|
||||
>
|
||||
{this.renderAuthor()}
|
||||
{this.renderQuote()}
|
||||
{this.renderAttachment()}
|
||||
{this.renderPreview()}
|
||||
{this.renderEmbeddedContact()}
|
||||
{this.renderText()}
|
||||
{this.renderMetadata()}
|
||||
{this.renderSendMessageButton()}
|
||||
{this.renderAvatar()}
|
||||
</div>
|
||||
{this.renderError(direction === 'outgoing')}
|
||||
{this.renderMenu(direction === 'incoming', triggerId)}
|
||||
{this.renderContextMenu(triggerId)}
|
||||
<div>
|
||||
<ContextMenuTrigger id={rightClickTriggerId}>
|
||||
<div
|
||||
className={classNames(
|
||||
'module-message',
|
||||
`module-message--${direction}`,
|
||||
expiring ? 'module-message--expired' : null
|
||||
)}
|
||||
>
|
||||
{this.renderError(direction === 'incoming')}
|
||||
{this.renderMenu(direction === 'outgoing', triggerId)}
|
||||
<div
|
||||
className={classNames(
|
||||
'module-message__container',
|
||||
`module-message__container--${direction}`,
|
||||
direction === 'incoming'
|
||||
? `module-message__container--incoming-${authorColor}`
|
||||
: null
|
||||
)}
|
||||
style={{
|
||||
width: isShowingImage ? width : undefined,
|
||||
}}
|
||||
>
|
||||
{this.renderAuthor()}
|
||||
{this.renderQuote()}
|
||||
{this.renderAttachment()}
|
||||
{this.renderPreview()}
|
||||
{this.renderEmbeddedContact()}
|
||||
{this.renderText()}
|
||||
{this.renderMetadata()}
|
||||
{this.renderSendMessageButton()}
|
||||
{this.renderAvatar()}
|
||||
</div>
|
||||
{this.renderError(direction === 'outgoing')}
|
||||
{this.renderMenu(direction === 'incoming', triggerId)}
|
||||
{this.renderContextMenu(triggerId)}
|
||||
{this.renderContextMenu(rightClickTriggerId)}
|
||||
</div>
|
||||
</ContextMenuTrigger>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue