ability to hard-delete p2p messages, debugged pinned messages
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -1671,5 +1671,19 @@
|
||||
"translation": "",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"message_not_found": {
|
||||
"translation": "",
|
||||
"defaultMessage": "message not found",
|
||||
"description": "Pinned message is not found",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"message_deleted": {
|
||||
"translation": "",
|
||||
"defaultMessage": "message deleted",
|
||||
"description": "Pinned message is deleted",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1671,5 +1671,19 @@
|
||||
"translation": "Saved messages",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"message_not_found": {
|
||||
"translation": "message not found",
|
||||
"defaultMessage": "message not found",
|
||||
"description": "Pinned message is not found",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"message_deleted": {
|
||||
"translation": "message deleted",
|
||||
"defaultMessage": "message deleted",
|
||||
"description": "Pinned message is deleted",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1671,5 +1671,19 @@
|
||||
"translation": "",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"message_not_found": {
|
||||
"translation": "",
|
||||
"defaultMessage": "message not found",
|
||||
"description": "Pinned message is not found",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"message_deleted": {
|
||||
"translation": "",
|
||||
"defaultMessage": "message deleted",
|
||||
"description": "Pinned message is deleted",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -604,10 +604,18 @@
|
||||
"defaultMessage": "Video call",
|
||||
"description": "Start video call"
|
||||
},
|
||||
"message_deleted": {
|
||||
"defaultMessage": "message deleted",
|
||||
"description": "Pinned message is deleted"
|
||||
},
|
||||
"message_edited_marker": {
|
||||
"defaultMessage": ", edited",
|
||||
"description": "Marker indicating that the message was edited"
|
||||
},
|
||||
"message_not_found": {
|
||||
"defaultMessage": "message not found",
|
||||
"description": "Pinned message is not found"
|
||||
},
|
||||
"message_sending": {
|
||||
"defaultMessage": "sending...",
|
||||
"description": "Message being sent, in place of time stamp"
|
||||
|
||||
@@ -1671,5 +1671,19 @@
|
||||
"translation": "",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"message_not_found": {
|
||||
"translation": "",
|
||||
"defaultMessage": "message not found",
|
||||
"description": "Pinned message is not found",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"message_deleted": {
|
||||
"translation": "",
|
||||
"defaultMessage": "message deleted",
|
||||
"description": "Pinned message is deleted",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1671,5 +1671,19 @@
|
||||
"translation": "",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"message_not_found": {
|
||||
"translation": "",
|
||||
"defaultMessage": "message not found",
|
||||
"description": "Pinned message is not found",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"message_deleted": {
|
||||
"translation": "",
|
||||
"defaultMessage": "message deleted",
|
||||
"description": "Pinned message is deleted",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1671,5 +1671,19 @@
|
||||
"translation": "",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"message_not_found": {
|
||||
"translation": "",
|
||||
"defaultMessage": "message not found",
|
||||
"description": "Pinned message is not found",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"message_deleted": {
|
||||
"translation": "",
|
||||
"defaultMessage": "message deleted",
|
||||
"description": "Pinned message is deleted",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1671,5 +1671,19 @@
|
||||
"translation": "",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"message_not_found": {
|
||||
"translation": "",
|
||||
"defaultMessage": "message not found",
|
||||
"description": "Pinned message is not found",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"message_deleted": {
|
||||
"translation": "",
|
||||
"defaultMessage": "message deleted",
|
||||
"description": "Pinned message is deleted",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1671,5 +1671,19 @@
|
||||
"translation": "",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"message_not_found": {
|
||||
"translation": "",
|
||||
"defaultMessage": "message not found",
|
||||
"description": "Pinned message is not found",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"message_deleted": {
|
||||
"translation": "",
|
||||
"defaultMessage": "message deleted",
|
||||
"description": "Pinned message is deleted",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1671,5 +1671,19 @@
|
||||
"translation": "",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"message_not_found": {
|
||||
"translation": "",
|
||||
"defaultMessage": "message not found",
|
||||
"description": "Pinned message is not found",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"message_deleted": {
|
||||
"translation": "",
|
||||
"defaultMessage": "message deleted",
|
||||
"description": "Pinned message is deleted",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1671,5 +1671,19 @@
|
||||
"translation": "",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"message_not_found": {
|
||||
"translation": "",
|
||||
"defaultMessage": "message not found",
|
||||
"description": "Pinned message is not found",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"message_deleted": {
|
||||
"translation": "",
|
||||
"defaultMessage": "message deleted",
|
||||
"description": "Pinned message is deleted",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1671,5 +1671,19 @@
|
||||
"translation": "",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"message_not_found": {
|
||||
"translation": "",
|
||||
"defaultMessage": "message not found",
|
||||
"description": "Pinned message is not found",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"message_deleted": {
|
||||
"translation": "",
|
||||
"defaultMessage": "message deleted",
|
||||
"description": "Pinned message is deleted",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
+58
-19
@@ -327,6 +327,7 @@ class MessagesView extends React.Component {
|
||||
showGoToLastButton: false,
|
||||
dragging: false,
|
||||
pins: [],
|
||||
pinsLoaded: false,
|
||||
selectedPin: 0,
|
||||
subsVersion: 0
|
||||
};
|
||||
@@ -411,7 +412,8 @@ class MessagesView extends React.Component {
|
||||
maxSeqId: topic.maxMsgSeq(),
|
||||
latestClearId: topic.maxClearId(),
|
||||
channel: topic.isChannelType(),
|
||||
pins: (topic.aux('pins') || []).slice()
|
||||
pins: (topic.aux('pins') || []).slice(),
|
||||
pinsLoaded: false
|
||||
});
|
||||
|
||||
if (nextProps.callTopic == topic.name && shouldPresentCallPanel(nextProps.callState)) {
|
||||
@@ -428,7 +430,8 @@ class MessagesView extends React.Component {
|
||||
avatar: null,
|
||||
peerMessagingDisabled: false,
|
||||
channel: false,
|
||||
pins: []
|
||||
pins: [],
|
||||
pinsLoaded: false
|
||||
});
|
||||
}
|
||||
} else {
|
||||
@@ -798,8 +801,16 @@ class MessagesView extends React.Component {
|
||||
}
|
||||
|
||||
// Load pinned message too (if any);
|
||||
const topic = this.props.tinode.getTopic(this.state.topic);
|
||||
topic.getPinnedMessages();
|
||||
if (!this.state.pinsLoaded) {
|
||||
// This needs to be guarded to avoid an infinite loop.
|
||||
// The loop may happen if some pinned messages are deleted.
|
||||
const topic = this.props.tinode.getTopic(this.state.topic);
|
||||
this.setState({pinsLoaded: true}, _ => {
|
||||
topic.getPinnedMessages();
|
||||
});
|
||||
} else {
|
||||
this.setState({pinsLoaded: false});
|
||||
}
|
||||
}
|
||||
|
||||
handleAuxUpdate(aux) {
|
||||
@@ -896,12 +907,35 @@ class MessagesView extends React.Component {
|
||||
const menuItems = messageSpecificMenuItems || [];
|
||||
const topic = this.props.tinode.getTopic(params.topicName);
|
||||
if (topic) {
|
||||
if (!topic.isChannelType()) {
|
||||
menuItems.push('message_delete');
|
||||
}
|
||||
const acs = topic.getAccessMode();
|
||||
if (acs && acs.isDeleter()) {
|
||||
menuItems.push('message_delete_hard');
|
||||
// Self -> (1) always hard-delete with [Delete] title.
|
||||
// P2P -> (1) hard-delete if allowed and not too old, (2) always soft-delete.
|
||||
// Group -> (1) hard-delete if allowed and not too old or owner, (2) always soft-delete.
|
||||
if (topic.isSelfType()) {
|
||||
// Hard-delete with plain [Delete] title.
|
||||
menuItems.push('message_delete_generic');
|
||||
} else {
|
||||
if (!topic.isChannelType()) {
|
||||
// P2P or normal group: always allow to soft-delete.
|
||||
menuItems.push('message_delete');
|
||||
}
|
||||
|
||||
const acs = topic.getAccessMode();
|
||||
if (acs && acs.isDeleter()) {
|
||||
// Owner can always hard-delete regardless of age.
|
||||
let canDelete = acs.isOwner();
|
||||
if (!canDelete) {
|
||||
// Not owner: check message age if configured on the server.
|
||||
const maxAge = this.props.tinode.getServerParam(Tinode.MSG_DELETE_AGE, 0) | 0;
|
||||
if (maxAge > 0 && params.timestamp) {
|
||||
// Make sure the message is not too old.
|
||||
canDelete = params.timestamp.getTime() > (new Date().getTime() - maxAge * 1000);
|
||||
}
|
||||
}
|
||||
if (canDelete) {
|
||||
// [Delete for All] title.
|
||||
menuItems.push('message_delete_hard');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.props.showContextMenu(params, menuItems);
|
||||
@@ -1700,9 +1734,10 @@ class MessagesView extends React.Component {
|
||||
<div id="topic-last-seen">{lastSeen}</div>
|
||||
</div>
|
||||
<div style={{marginLeft: 'auto'}}/>
|
||||
{!this.props.displayMobile ?
|
||||
{!this.props.displayMobile && this.state.pins.length > 0 ?
|
||||
<PinnedMessages
|
||||
tinode={this.props.tinode}
|
||||
pins={this.state.pins}
|
||||
messages={pinnedMessages}
|
||||
selected={this.state.selectedPin}
|
||||
isAdmin={this.state.isAdmin}
|
||||
@@ -1724,14 +1759,18 @@ class MessagesView extends React.Component {
|
||||
</div>
|
||||
{this.props.displayMobile ?
|
||||
<>
|
||||
<PinnedMessages
|
||||
tinode={this.props.tinode}
|
||||
messages={pinnedMessages}
|
||||
selected={this.state.selectedPin}
|
||||
isAdmin={this.state.isAdmin}
|
||||
setSelected={index => this.setState({selectedPin: index})}
|
||||
onSelected={this.handleQuoteClick}
|
||||
onCancel={this.handleUnpinMessage} />
|
||||
{this.state.pins.length > 0 ?
|
||||
<PinnedMessages
|
||||
tinode={this.props.tinode}
|
||||
pins={this.state.pins}
|
||||
messages={pinnedMessages}
|
||||
selected={this.state.selectedPin}
|
||||
isAdmin={this.state.isAdmin}
|
||||
setSelected={index => this.setState({selectedPin: index})}
|
||||
onSelected={this.handleQuoteClick}
|
||||
onCancel={this.handleUnpinMessage} />
|
||||
: null
|
||||
}
|
||||
<ErrorPanel
|
||||
level={this.props.errorLevel}
|
||||
text={this.props.errorText}
|
||||
|
||||
@@ -129,6 +129,7 @@ class BaseChatMessage extends React.PureComponent {
|
||||
x: e.pageX,
|
||||
pickReply: this.props.pickReply,
|
||||
editMessage: this.props.editMessage,
|
||||
timestamp: this.props.timestamp,
|
||||
}, menuItems);
|
||||
}
|
||||
|
||||
|
||||
@@ -76,7 +76,6 @@ class ContactList extends React.Component {
|
||||
filterOn.push(('' + c.public.fn).toLowerCase());
|
||||
}
|
||||
if (!this.props.filterFunc(this.props.filter, filterOn)) {
|
||||
console.log('Filtered:', key);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,6 +189,14 @@ class ContextMenu extends React.Component {
|
||||
return this.deleteMessages(false, true, params, errorHandler);
|
||||
}
|
||||
},
|
||||
// Hard-delete but with just 'Delete' title.
|
||||
'message_delete_generic': {
|
||||
id: 'message_delete_generic',
|
||||
title: formatMessage(messages.delete),
|
||||
handler: (params, errorHandler) => {
|
||||
return this.deleteMessages(false, true, params, errorHandler);
|
||||
}
|
||||
},
|
||||
'menu_item_send_retry': {
|
||||
id: 'menu_item_send_retry',
|
||||
title: formatMessage(messages.send_retry),
|
||||
|
||||
@@ -1,20 +1,34 @@
|
||||
// Send message form.
|
||||
import React from 'react';
|
||||
import { injectIntl } from 'react-intl';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import { Drafty } from 'tinode-sdk';
|
||||
|
||||
import { previewFormatter } from '../lib/formatters.js';
|
||||
import { MIN_SWIPE_DISTANCE } from '../config.js';
|
||||
|
||||
const messages = defineMessages({
|
||||
message_not_found: {
|
||||
id: 'message_not_found',
|
||||
defaultMessage: 'message not found',
|
||||
description: 'Pinned message is not found'
|
||||
},
|
||||
message_deleted: {
|
||||
id: 'message_deleted',
|
||||
defaultMessage: 'message deleted',
|
||||
description: 'Pinned message is deleted'
|
||||
}
|
||||
});
|
||||
|
||||
class PinnedMessages extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.touchSurface = React.createRef();
|
||||
// Starting location of the touch gesture.
|
||||
this.touchX = null;
|
||||
this.touchY = null;
|
||||
|
||||
this.touchSurface = React.createRef();
|
||||
|
||||
this.getSelectedIndex = this.getSelectedIndex.bind(this);
|
||||
this.handleCancel = this.handleCancel.bind(this);
|
||||
this.handleSelected = this.handleSelected.bind(this);
|
||||
@@ -26,34 +40,29 @@ class PinnedMessages extends React.PureComponent {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.touchSurface.current) {
|
||||
this.touchSurface.current.addEventListener('touchstart', this.handleTouchEventStart, {passive: true});
|
||||
this.touchSurface.current.addEventListener('touchend', this.handleTouchEventEnd, {passive: true});
|
||||
this.touchSurface.current.addEventListener('touchcancel', this.handleTouchCancel, {passive: true});
|
||||
}
|
||||
this.touchSurface.current.addEventListener('touchstart', this.handleTouchEventStart, {passive: true});
|
||||
this.touchSurface.current.addEventListener('touchend', this.handleTouchEventEnd, {passive: true});
|
||||
this.touchSurface.current.addEventListener('touchcancel', this.handleTouchCancel, {passive: true});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.touchSurface.current) {
|
||||
this.touchSurface.current.removeEventListener('touchstart', this.handleTouchEventStart);
|
||||
this.touchSurface.current.removeEventListener('touchend', this.handleTouchEventEnd);
|
||||
this.touchSurface.current.removeEventListener('touchcancel', this.handleTouchCancel);
|
||||
}
|
||||
this.touchSurface.current.removeEventListener('touchstart', this.handleTouchEventStart);
|
||||
this.touchSurface.current.removeEventListener('touchend', this.handleTouchEventEnd);
|
||||
this.touchSurface.current.removeEventListener('touchcancel', this.handleTouchCancel);
|
||||
}
|
||||
|
||||
getSelectedIndex() {
|
||||
const list = (this.props.messages || []);
|
||||
const list = (this.props.pins || []);
|
||||
return list.length - this.props.selected - 1;
|
||||
}
|
||||
|
||||
handleCancel(e) {
|
||||
e.preventDefault();
|
||||
this.props.onCancel(this.props.messages[this.getSelectedIndex()].seq);
|
||||
this.props.onCancel(this.props.pins[this.getSelectedIndex()]);
|
||||
}
|
||||
|
||||
handleSelected(e) {
|
||||
e.preventDefault();
|
||||
this.props.onSelected(this.props.messages[this.getSelectedIndex()].seq);
|
||||
this.props.onSelected(this.props.pins[this.getSelectedIndex()]);
|
||||
}
|
||||
|
||||
handleMoveNext(e, isTouch) {
|
||||
@@ -70,7 +79,7 @@ class PinnedMessages extends React.PureComponent {
|
||||
// Don't call preventDefault for touch events.
|
||||
isTouch || e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const idx = Math.min(this.props.selected + 1, (this.props.messages || []).length - 1);
|
||||
const idx = Math.min(this.props.selected + 1, (this.props.pins || []).length - 1);
|
||||
if (idx != this.props.selected) {
|
||||
this.props.setSelected(idx);
|
||||
}
|
||||
@@ -105,26 +114,32 @@ class PinnedMessages extends React.PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
handleTouchCancel(e) {
|
||||
handleTouchCancel() {
|
||||
this.touchX = null;
|
||||
}
|
||||
|
||||
render() {
|
||||
const selected = this.getSelectedIndex();
|
||||
let shown = (this.props.messages || [])[selected];
|
||||
shown = shown ? Drafty.format(shown.content, previewFormatter, {
|
||||
formatMessage: this.props.intl.formatMessage.bind(this.props.intl),
|
||||
authorizeURL: this.props.tinode.authorizeURL.bind(this.props.tinode)
|
||||
}) : null;
|
||||
let messageShown = (this.props.messages || [])[selected];
|
||||
messageShown = messageShown ?
|
||||
messageShown._deleted ?
|
||||
<i className="gray">{this.props.intl.formatMessage(messages.message_deleted)}</i>
|
||||
:
|
||||
Drafty.format(messageShown.content, previewFormatter, {
|
||||
formatMessage: this.props.intl.formatMessage.bind(this.props.intl),
|
||||
authorizeURL: this.props.tinode.authorizeURL.bind(this.props.tinode)
|
||||
})
|
||||
:
|
||||
<i className="gray">{this.props.intl.formatMessage(messages.message_not_found)}</i>;
|
||||
|
||||
const dots = [];
|
||||
this.props.messages.forEach(_ => {
|
||||
this.props.pins.forEach(seq => {
|
||||
const cn = dots.length == selected ? 'adot' : 'dot';
|
||||
dots.push(<div key={dots.length} className={cn} />);
|
||||
dots.push(<div key={seq} className={cn} />);
|
||||
});
|
||||
|
||||
return shown ?
|
||||
(<div id="pinned-wrapper" ref={this.touchSurface}>
|
||||
return (
|
||||
<div id="pinned-wrapper" ref={this.touchSurface}>
|
||||
{this.props.isAdmin ?
|
||||
<div className="cancel">
|
||||
<a href="#" onClick={this.handleCancel}><i className="material-icons gray">close</i></a>
|
||||
@@ -132,7 +147,7 @@ class PinnedMessages extends React.PureComponent {
|
||||
<div><i className="material-icons gray">push_pin</i></div>
|
||||
}
|
||||
<div className="pinned-scroll">{dots}</div>
|
||||
<div className="pinned" onClick={this.handleSelected}><p>{shown}</p></div>
|
||||
<div className="pinned" onClick={this.handleSelected}><p>{messageShown}</p></div>
|
||||
<div className="pinned-menu">
|
||||
<span className="menuTrigger upper">
|
||||
{selected > 0 ?
|
||||
@@ -151,7 +166,8 @@ class PinnedMessages extends React.PureComponent {
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
</div>) : null;
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import ChipInput from './chip-input.jsx';
|
||||
|
||||
import { Tinode } from 'tinode-sdk';
|
||||
|
||||
import { MAX_TAG_COUNT, MAX_TAG_LENGTH, MIN_TAG_LENGTH } from '../config.js';
|
||||
import { arrayEqual } from '../lib/utils.js';
|
||||
|
||||
@@ -49,7 +51,7 @@ export default class TagManager extends React.Component {
|
||||
}
|
||||
|
||||
handleAddTag(tag) {
|
||||
const maxTagCount = this.props.tinode.getServerParam('maxTagCount', MAX_TAG_COUNT);
|
||||
const maxTagCount = this.props.tinode.getServerParam(Tinode.MAX_TAG_COUNT, MAX_TAG_COUNT);
|
||||
|
||||
if (tag.length > 0 && this.state.tags.length < maxTagCount) {
|
||||
const tags = this.state.tags.slice(0);
|
||||
@@ -87,8 +89,8 @@ export default class TagManager extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const minTagLength = this.props.tinode.getServerParam('minTagLength', MIN_TAG_LENGTH);
|
||||
const maxTagLength = this.props.tinode.getServerParam('maxTagLength', MAX_TAG_LENGTH);
|
||||
const minTagLength = this.props.tinode.getServerParam(Tinode.MIN_TAG_LENGTH, MIN_TAG_LENGTH);
|
||||
const maxTagLength = this.props.tinode.getServerParam(Tinode.MAX_TAG_LENGTH, MAX_TAG_LENGTH);
|
||||
|
||||
let tags = [];
|
||||
if (this.state.activated) {
|
||||
|
||||
Reference in New Issue
Block a user