cleanup
This commit is contained in:
@@ -55,6 +55,17 @@ export default class HashNavigation {
|
||||
return HashNavigation.composeUrlHash(parsed.path, parsed.params);
|
||||
}
|
||||
|
||||
static removeUrlParams(hash, keys) {
|
||||
if (!Array.isArray(keys)) {
|
||||
keys = [keys];
|
||||
}
|
||||
const parsed = HashNavigation.parseUrlHash(hash);
|
||||
for (const key of keys) {
|
||||
delete parsed.params[key];
|
||||
}
|
||||
return HashNavigation.composeUrlHash(parsed.path, parsed.params);
|
||||
}
|
||||
|
||||
static setUrlSidePanel(hash, sidepanel) {
|
||||
const parsed = HashNavigation.parseUrlHash(hash);
|
||||
parsed.path[0] = sidepanel;
|
||||
|
||||
@@ -391,9 +391,12 @@ class MessagesView extends React.Component {
|
||||
|
||||
if (nextProps.forwardMessage) {
|
||||
if (nextProps.forwardMessage.content) {
|
||||
const content = nextProps.forwardMessage.content;
|
||||
const size = content?.length || (content?.size || 0);
|
||||
nextState.docPreview = {
|
||||
object: nextProps.forwardMessage.content,
|
||||
type: nextProps.forwardMessage.type
|
||||
object: content,
|
||||
type: nextProps.forwardMessage.type,
|
||||
size: size
|
||||
};
|
||||
} else {
|
||||
// We are forwarding a message. Show preview.
|
||||
@@ -1022,7 +1025,7 @@ class MessagesView extends React.Component {
|
||||
// sendMessage sends the message with an optional subscription to topic first.
|
||||
sendMessage(msg, uploadCompletionPromise, uploader) {
|
||||
let head;
|
||||
if (this.props.forwardMessage) {
|
||||
if (!msg && this.props.forwardMessage) {
|
||||
// We are forwarding a message.
|
||||
msg = this.props.forwardMessage.msg;
|
||||
head = this.props.forwardMessage.head;
|
||||
@@ -1068,7 +1071,7 @@ class MessagesView extends React.Component {
|
||||
const maxInbandAttachmentSize = (this.props.tinode.getServerParam('maxMessageSize',
|
||||
MAX_INBAND_ATTACHMENT_SIZE) * 0.75 - 1024) | 0;
|
||||
|
||||
if (TheCard.isFileSupported(file.type, file.name)) {
|
||||
if (TheCard.isFileSupported(file.type, file.name) || file.type == TheCard.contentType) {
|
||||
// This is a vCard file, try to convert and send as TheCard.
|
||||
if (this.sendTheCardAttachment(file, maxInbandAttachmentSize)) {
|
||||
// Successfully sent as TheCard.
|
||||
@@ -1118,6 +1121,12 @@ class MessagesView extends React.Component {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (file.type == TheCard.contentType) {
|
||||
// Already TheCard, no need to convert.
|
||||
this.sendMessage(Drafty.appendTheCard(null, file.object));
|
||||
return true;
|
||||
}
|
||||
|
||||
importVCard(file)
|
||||
.then(card => {
|
||||
this.sendMessage(Drafty.appendTheCard(null, card));
|
||||
|
||||
@@ -34,9 +34,13 @@ class NewTopicView extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const urlParams = HashNavigation.parseUrlHash(window.location.hash).params;
|
||||
const initialTab = urlParams['tab'] || 'find';
|
||||
const initialQuery = initialTab == 'find' ? (urlParams['q'] || null) : null;
|
||||
|
||||
this.state = {
|
||||
tabSelected: 'find',
|
||||
searchQuery: null
|
||||
tabSelected: initialTab,
|
||||
searchQuery: initialQuery
|
||||
};
|
||||
|
||||
this.handleTabClick = this.handleTabClick.bind(this);
|
||||
@@ -83,19 +87,19 @@ class NewTopicView extends React.Component {
|
||||
|
||||
handleSearchResultSelected(topicName) {
|
||||
if (this.state.tabSelected == 'find') {
|
||||
HashNavigation.navigateTo(HashNavigation.removeUrlParam(window.location.hash, 'tab'));
|
||||
HashNavigation.navigateTo(HashNavigation.removeUrlParams(window.location.hash, ['tab', 'q']));
|
||||
this.props.onCreateTopic(topicName);
|
||||
}
|
||||
}
|
||||
|
||||
handleNewGroupSubmit(name, description, dataUrl, priv, tags, isChannel) {
|
||||
HashNavigation.navigateTo(HashNavigation.removeUrlParam(window.location.hash, 'tab'));
|
||||
HashNavigation.navigateTo(HashNavigation.removeUrlParams(window.location.hash, ['tab', 'q']));
|
||||
this.props.onCreateTopic(undefined,
|
||||
{public: theCard(name, dataUrl, null, description), private: priv, tags: tags}, isChannel);
|
||||
}
|
||||
|
||||
handleGroupByID(topicName) {
|
||||
HashNavigation.navigateTo(HashNavigation.removeUrlParam(window.location.hash, 'tab'));
|
||||
HashNavigation.navigateTo(HashNavigation.removeUrlParams(window.location.hash, ['tab', 'q']));
|
||||
this.props.onCreateTopic(topicName);
|
||||
}
|
||||
|
||||
@@ -131,26 +135,29 @@ class NewTopicView extends React.Component {
|
||||
tinode={this.props.tinode}
|
||||
onSubmit={this.handleNewGroupSubmit}
|
||||
onError={this.props.onError} /> :
|
||||
this.state.tabSelected === 'byid' ?
|
||||
<NewTopicById
|
||||
myURI={Tinode.URI_TOPIC_ID_PREFIX + this.props.tinode.getCurrentUserID()}
|
||||
onSubmit={this.handleGroupByID}
|
||||
onError={this.props.onError} /> :
|
||||
<div className="flex-column">
|
||||
<SearchContacts
|
||||
placeholder={search_placeholder}
|
||||
onSearchContacts={this.handleSearchContacts} />
|
||||
<ContactList
|
||||
tinode={this.props.tinode}
|
||||
contacts={this.props.searchResults}
|
||||
myUserId={this.props.myUserId}
|
||||
emptyListMessage={no_contacts_placeholder}
|
||||
showSelfTopic={!this.state.searchQuery}
|
||||
showOnline={false}
|
||||
showUnread={false}
|
||||
showContextMenu={false}
|
||||
onTopicSelected={this.handleSearchResultSelected} />
|
||||
</div>}
|
||||
|
||||
this.state.tabSelected === 'byid' ?
|
||||
<NewTopicById
|
||||
myURI={Tinode.URI_TOPIC_ID_PREFIX + this.props.tinode.getCurrentUserID()}
|
||||
onSubmit={this.handleGroupByID}
|
||||
onError={this.props.onError} /> :
|
||||
|
||||
<div className="flex-column">
|
||||
<SearchContacts
|
||||
placeholder={search_placeholder}
|
||||
initialQuery={this.state.searchQuery || ''}
|
||||
onSearchContacts={this.handleSearchContacts} />
|
||||
<ContactList
|
||||
tinode={this.props.tinode}
|
||||
contacts={this.props.searchResults}
|
||||
myUserId={this.props.myUserId}
|
||||
emptyListMessage={no_contacts_placeholder}
|
||||
showSelfTopic={!this.state.searchQuery}
|
||||
showOnline={false}
|
||||
showUnread={false}
|
||||
showContextMenu={false}
|
||||
onTopicSelected={this.handleSearchResultSelected} />
|
||||
</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -320,7 +320,7 @@ class TinodeWeb extends React.Component {
|
||||
|
||||
// Window/tab visible or invisible for pausing timers.
|
||||
document.addEventListener('visibilitychange', this.handleVisibilityEvent);
|
||||
|
||||
|
||||
// Listen for Escape key to clear selections.
|
||||
document.addEventListener('keydown', this.handleEscapeKey);
|
||||
|
||||
@@ -1150,7 +1150,6 @@ class TinodeWeb extends React.Component {
|
||||
// - head - head dictionary to be attached to the message
|
||||
handleSendMessage(msg, uploadCompletionPromise, uploader, head) {
|
||||
const topic = this.tinode.getTopic(this.state.topicSelected);
|
||||
/* TODO: check if return is required */
|
||||
return this.sendMessageToTopic(topic, msg, uploadCompletionPromise, uploader, head);
|
||||
}
|
||||
|
||||
|
||||
@@ -346,11 +346,11 @@ class CallPanel extends React.PureComponent {
|
||||
}
|
||||
|
||||
handleDataChannelClose(event) {
|
||||
console.log('close data channel:', event);
|
||||
console.info('close data channel:', event);
|
||||
}
|
||||
|
||||
handleDataChannelEvent(event) {
|
||||
console.log('data channel event:', event);
|
||||
console.info('data channel event:', event);
|
||||
const channel = event.channel;
|
||||
channel.onerror = this.handleDataChannelError;
|
||||
channel.onmessage = this.handleDataChannelMessage;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import React from 'react';
|
||||
import { FormattedMessage, injectIntl } from 'react-intl';
|
||||
import { Drafty, Tinode } from 'tinode-sdk';
|
||||
import { Drafty, Tinode, TheCard } from 'tinode-sdk';
|
||||
|
||||
import Attachment from './attachment.jsx';
|
||||
import LetterTile from './letter-tile.jsx';
|
||||
@@ -10,6 +10,7 @@ import ReceivedMarker from './received-marker.jsx'
|
||||
|
||||
import { fullFormatter } from '../lib/formatters.js';
|
||||
import { sanitizeUrl } from '../lib/utils.js';
|
||||
import HashNavigation from '../lib/navigation.js';
|
||||
|
||||
class BaseChatMessage extends React.PureComponent {
|
||||
constructor(props) {
|
||||
@@ -55,13 +56,26 @@ class BaseChatMessage extends React.PureComponent {
|
||||
this.handleQuoteClick(e);
|
||||
break;
|
||||
case 'contact_chat':
|
||||
e.preventDefault();
|
||||
try {
|
||||
// tinode:topic/usr123abc -> usr123abc
|
||||
const pathname = new URL(e.target.dataset.val)?.pathname;
|
||||
const parts = pathname.split('/').filter(Boolean);
|
||||
HashNavigation.navigateTo(HashNavigation.setUrlTopic('', parts.pop() || ''));
|
||||
} catch (error) {
|
||||
console.error("Invalid URL:", error);
|
||||
}
|
||||
break;
|
||||
case 'contact_find':
|
||||
e.preventDefault();
|
||||
console.log('Contact click handling not implemented yet.', action, e.target.dataset);
|
||||
let hashUrl = HashNavigation.setUrlSidePanel(window.location.hash, 'newtpk');
|
||||
hashUrl = HashNavigation.addUrlParam(hashUrl, 'q', e.target.dataset.val);
|
||||
hashUrl = HashNavigation.addUrlParam(hashUrl, 'tab', 'find');
|
||||
HashNavigation.navigateTo(hashUrl);
|
||||
break;
|
||||
default:
|
||||
// No special handling; let the browser deal with it.
|
||||
console.log('Unhandled drafty action.', action, e.target.dataset);
|
||||
console.info('Unhandled drafty action.', action, e.target.dataset);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ export default class SearchContacts extends React.PureComponent {
|
||||
|
||||
this.state = {
|
||||
edited: false,
|
||||
search: ''
|
||||
search: this.props.initialQuery || ''
|
||||
};
|
||||
|
||||
this.handleSearchChange = this.handleSearchChange.bind(this);
|
||||
@@ -47,7 +47,8 @@ export default class SearchContacts extends React.PureComponent {
|
||||
if (e.key === 'Enter') {
|
||||
this.handleSearch(e);
|
||||
} else if (e.key === 'Escape') {
|
||||
this.handleClear();
|
||||
e.stopPropagation();
|
||||
this.handleClear(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -71,20 +71,24 @@ class TheCardMini extends React.PureComponent {
|
||||
</div>
|
||||
</div>
|
||||
<div className="contact-actions">
|
||||
{uid &&
|
||||
<a className="flat-button" data-val={uid} onClick={this.props.onChatClick}>
|
||||
<FormattedMessage id="chat_now" defaultMessage="Chat"
|
||||
description="Label for message button in contact card" />
|
||||
</a>
|
||||
{uid ?
|
||||
<>
|
||||
<a className="flat-button" data-val={uid} onClick={this.props.onChatClick}>
|
||||
<FormattedMessage id="chat_now" defaultMessage="Chat"
|
||||
description="Label for message button in contact card" />
|
||||
</a>
|
||||
<div className="divider"></div>
|
||||
</>
|
||||
: contacts.length > 0 ?
|
||||
<>
|
||||
<a className="flat-button" data-val={contacts.join(',')} onClick={this.props.onFindClick}>
|
||||
<FormattedMessage id="find_user" defaultMessage="Find"
|
||||
description="Label for search button in contact card" />
|
||||
</a>
|
||||
<div className="divider"></div>
|
||||
</>
|
||||
: null
|
||||
}
|
||||
{uid && <div className="divider"></div>}
|
||||
{contacts.length > 0 &&
|
||||
<a className="flat-button" data-val={contacts.join(',')} onClick={this.props.onChatClick}>
|
||||
<FormattedMessage id="find_user" defaultMessage="Find"
|
||||
description="Label for search button in contact card" />
|
||||
</a>
|
||||
}
|
||||
{contacts.length > 0 && <div className="divider"></div>}
|
||||
<a className="flat-button" onClick={this.handleDownload}>
|
||||
<FormattedMessage id="save_action" defaultMessage="Save" description="Label for Save button" />
|
||||
</a>
|
||||
|
||||
Reference in New Issue
Block a user