merge next into next-reply
This commit is contained in:
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+3
-2
@@ -57,6 +57,7 @@ const fbMessaging = firebase.messaging();
|
|||||||
// This method shows the push notifications while the window is in background.
|
// This method shows the push notifications while the window is in background.
|
||||||
fbMessaging.onBackgroundMessage((payload) => {
|
fbMessaging.onBackgroundMessage((payload) => {
|
||||||
if (payload.data.silent == 'true') {
|
if (payload.data.silent == 'true') {
|
||||||
|
// TODO: if this is an 'msg', fetch the new message in the background.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,8 +69,8 @@ fbMessaging.onBackgroundMessage((payload) => {
|
|||||||
const pushType = payload.data.what || 'msg';
|
const pushType = payload.data.what || 'msg';
|
||||||
const title = payload.data.title || self.i18nMessage(pushType == 'msg' ? 'new_message' : 'new_chat');
|
const title = payload.data.title || self.i18nMessage(pushType == 'msg' ? 'new_message' : 'new_chat');
|
||||||
const options = {
|
const options = {
|
||||||
body: payload.data.content || '',
|
body: payload.data.content || '', // TODO: content for 'sub' should be topic's or user's title.
|
||||||
icon: '/img/logo96.png',
|
icon: '/img/logo96.png', // TODO: use topic's or user's avatar (would have to fetch for 'sub', read from db for 'msg').
|
||||||
badge: '/img/badge96.png',
|
badge: '/img/badge96.png',
|
||||||
tag: payload.data.topic || undefined,
|
tag: payload.data.topic || undefined,
|
||||||
data: {
|
data: {
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export function bytesToHumanSize(bytes) {
|
|||||||
|
|
||||||
// Get 32 bit integer hash value for a string. Ideally it should produce the same value
|
// Get 32 bit integer hash value for a string. Ideally it should produce the same value
|
||||||
// as Java's String#hash().
|
// as Java's String#hash().
|
||||||
function stringToColorHash(value) {
|
export function stringToColorHash(value) {
|
||||||
let hash = 0;
|
let hash = 0;
|
||||||
value = '' + value;
|
value = '' + value;
|
||||||
for (let i = 0; i < value.length; i++) {
|
for (let i = 0; i < value.length; i++) {
|
||||||
|
|||||||
+2
-2
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
import Tinode from 'tinode-sdk';
|
import Tinode from 'tinode-sdk';
|
||||||
|
|
||||||
import { stringHash } from './strformat.js';
|
import { stringToColorHash } from './strformat.js';
|
||||||
|
|
||||||
// User avatar letter color id.
|
// User avatar letter color id.
|
||||||
export function letterTileColor(userId) {
|
export function letterTileColor(userId) {
|
||||||
return Math.abs(stringHash(userId)) % 16;
|
return Math.abs(stringToColorHash(userId)) % 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make shortcut icon appear with a green dot + show unread count in title.
|
// Make shortcut icon appear with a green dot + show unread count in title.
|
||||||
|
|||||||
+91
-93
@@ -91,9 +91,11 @@ class MessagesView extends React.Component {
|
|||||||
this.state = MessagesView.getDerivedStateFromProps(props, {});
|
this.state = MessagesView.getDerivedStateFromProps(props, {});
|
||||||
|
|
||||||
this.leave = this.leave.bind(this);
|
this.leave = this.leave.bind(this);
|
||||||
|
this.sendMessage = this.sendMessage.bind(this);
|
||||||
this.sendImageAttachment = this.sendImageAttachment.bind(this);
|
this.sendImageAttachment = this.sendImageAttachment.bind(this);
|
||||||
this.sendFileAttachment = this.sendFileAttachment.bind(this);
|
this.sendFileAttachment = this.sendFileAttachment.bind(this);
|
||||||
this.sendKeyPress = this.sendKeyPress.bind(this);
|
this.sendKeyPress = this.sendKeyPress.bind(this);
|
||||||
|
this.subscribe = this.subscribe.bind(this);
|
||||||
this.handleScrollReference = this.handleScrollReference.bind(this);
|
this.handleScrollReference = this.handleScrollReference.bind(this);
|
||||||
this.handleScrollEvent = this.handleScrollEvent.bind(this);
|
this.handleScrollEvent = this.handleScrollEvent.bind(this);
|
||||||
this.handleDescChange = this.handleDescChange.bind(this);
|
this.handleDescChange = this.handleDescChange.bind(this);
|
||||||
@@ -114,8 +116,6 @@ class MessagesView extends React.Component {
|
|||||||
this.postReadNotification = this.postReadNotification.bind(this);
|
this.postReadNotification = this.postReadNotification.bind(this);
|
||||||
this.clearNotificationQueue = this.clearNotificationQueue.bind(this);
|
this.clearNotificationQueue = this.clearNotificationQueue.bind(this);
|
||||||
|
|
||||||
this.doSendMessage = this.doSendMessage.bind(this);
|
|
||||||
this.handleSendMessage = this.handleSendMessage.bind(this);
|
|
||||||
this.handlePickReply = this.handlePickReply.bind(this);
|
this.handlePickReply = this.handlePickReply.bind(this);
|
||||||
this.handleCancelReply = this.handleCancelReply.bind(this);
|
this.handleCancelReply = this.handleCancelReply.bind(this);
|
||||||
this.handleQuoteClick = this.handleQuoteClick.bind(this);
|
this.handleQuoteClick = this.handleQuoteClick.bind(this);
|
||||||
@@ -145,7 +145,7 @@ class MessagesView extends React.Component {
|
|||||||
// or vertical shrinking.
|
// or vertical shrinking.
|
||||||
componentDidUpdate(prevProps, prevState) {
|
componentDidUpdate(prevProps, prevState) {
|
||||||
if (this.messagesScroller) {
|
if (this.messagesScroller) {
|
||||||
if (prevState.topic != this.state.topic || prevState.latestMsgSeq != this.state.latestMsgSeq) {
|
if (prevState.topic != this.state.topic || prevState.messageCount != this.state.messageCount) {
|
||||||
// New message
|
// New message
|
||||||
this.messagesScroller.scrollTop = this.messagesScroller.scrollHeight - this.state.scrollPosition;
|
this.messagesScroller.scrollTop = this.messagesScroller.scrollHeight - this.state.scrollPosition;
|
||||||
} else if (prevProps.viewportHeight > this.props.viewportHeight) {
|
} else if (prevProps.viewportHeight > this.props.viewportHeight) {
|
||||||
@@ -178,49 +178,8 @@ class MessagesView extends React.Component {
|
|||||||
this.postReadNotification(0);
|
this.postReadNotification(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (topic && !topic.isSubscribed() && this.props.ready &&
|
if ((this.state.topic != prevState.topic) || !prevProps.ready) {
|
||||||
((this.state.topic != prevState.topic) || !prevProps.ready)) {
|
this.subscribe(topic);
|
||||||
// Is this a new topic?
|
|
||||||
const newTopic = (this.props.newTopicParams && this.props.newTopicParams._topicName == this.props.topic);
|
|
||||||
|
|
||||||
// Don't request the tags. They are useless unless the user
|
|
||||||
// is the owner and is editing the topic.
|
|
||||||
let getQuery = topic.startMetaQuery().withLaterDesc().withLaterSub();
|
|
||||||
if (this.state.isReader || newTopic) {
|
|
||||||
// Reading is either permitted or we don't know because it's a new topic. Ask for messages.
|
|
||||||
getQuery = getQuery.withLaterData(MESSAGES_PAGE);
|
|
||||||
if (this.state.isReader) {
|
|
||||||
getQuery = getQuery.withLaterDel();
|
|
||||||
}
|
|
||||||
// And show "loading" spinner.
|
|
||||||
this.setState({ fetchingMessages: true });
|
|
||||||
}
|
|
||||||
const setQuery = newTopic ? this.props.newTopicParams : undefined;
|
|
||||||
topic.subscribe(getQuery.build(), setQuery)
|
|
||||||
.then((ctrl) => {
|
|
||||||
if (ctrl.code == 303) {
|
|
||||||
// Redirect to another topic requested.
|
|
||||||
HashNavigation.navigateTo(HashNavigation.setUrlTopic('', ctrl.params.topic));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.state.topic != ctrl.topic) {
|
|
||||||
this.setState({topic: ctrl.topic});
|
|
||||||
}
|
|
||||||
this.props.onNewTopicCreated(this.props.topic, ctrl.topic);
|
|
||||||
// If there are unsent messages, try sending them now.
|
|
||||||
topic.queuedMessages((pub) => {
|
|
||||||
if (!pub._sending && topic.isSubscribed()) {
|
|
||||||
topic.publishMessage(pub);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.log("Failed subscription to", this.state.topic);
|
|
||||||
this.props.onError(err.message, 'err');
|
|
||||||
const blankState = MessagesView.getDerivedStateFromProps({}, {});
|
|
||||||
blankState.title = this.props.intl.formatMessage(messages.not_found);
|
|
||||||
this.setState(blankState);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,7 +188,7 @@ class MessagesView extends React.Component {
|
|||||||
if (!nextProps.topic) {
|
if (!nextProps.topic) {
|
||||||
// Default state: no topic.
|
// Default state: no topic.
|
||||||
nextState = {
|
nextState = {
|
||||||
latestMsgSeq: -1,
|
messageCount: 0,
|
||||||
latestClearId: -1,
|
latestClearId: -1,
|
||||||
onlineSubs: [],
|
onlineSubs: [],
|
||||||
topic: null,
|
topic: null,
|
||||||
@@ -300,14 +259,14 @@ class MessagesView extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
Object.assign(nextState, {
|
Object.assign(nextState, {
|
||||||
latestMsgSeq: topic.maxMsgSeq(),
|
messageCount: topic.messageCount(),
|
||||||
latestClearId: topic.maxClearId(),
|
latestClearId: topic.maxClearId(),
|
||||||
channel: topic.isChannelType()
|
channel: topic.isChannelType()
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Invalid topic.
|
// Invalid topic.
|
||||||
Object.assign(nextState, {
|
Object.assign(nextState, {
|
||||||
latestMsgSeq: -1,
|
messageCount: 0,
|
||||||
latestClearId: -1,
|
latestClearId: -1,
|
||||||
onlineSubs: [],
|
onlineSubs: [],
|
||||||
title: '',
|
title: '',
|
||||||
@@ -358,6 +317,55 @@ class MessagesView extends React.Component {
|
|||||||
return nextState;
|
return nextState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
subscribe(topic) {
|
||||||
|
if (!topic || topic.isSubscribed() || !this.props.ready) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is this a new topic?
|
||||||
|
const newTopic = (this.props.newTopicParams && this.props.newTopicParams._topicName == this.props.topic);
|
||||||
|
|
||||||
|
// Don't request the tags. They are useless unless the user
|
||||||
|
// is the owner and is editing the topic.
|
||||||
|
let getQuery = topic.startMetaQuery().withLaterDesc().withLaterSub();
|
||||||
|
if (this.state.isReader || newTopic) {
|
||||||
|
// Reading is either permitted or we don't know because it's a new topic. Ask for messages.
|
||||||
|
getQuery = getQuery.withLaterData(MESSAGES_PAGE);
|
||||||
|
if (this.state.isReader) {
|
||||||
|
getQuery = getQuery.withLaterDel();
|
||||||
|
}
|
||||||
|
// And show "loading" spinner.
|
||||||
|
this.setState({ fetchingMessages: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const setQuery = newTopic ? this.props.newTopicParams : undefined;
|
||||||
|
topic.subscribe(getQuery.build(), setQuery)
|
||||||
|
.then((ctrl) => {
|
||||||
|
if (ctrl.code == 303) {
|
||||||
|
// Redirect to another topic requested.
|
||||||
|
HashNavigation.navigateTo(HashNavigation.setUrlTopic('', ctrl.params.topic));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.state.topic != ctrl.topic) {
|
||||||
|
this.setState({topic: ctrl.topic});
|
||||||
|
}
|
||||||
|
this.props.onNewTopicCreated(this.props.topic, ctrl.topic);
|
||||||
|
// If there are unsent messages, try sending them now.
|
||||||
|
topic.queuedMessages((pub) => {
|
||||||
|
if (!pub._sending && topic.isSubscribed()) {
|
||||||
|
this.sendMessage(pub);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log("Failed subscription to", this.state.topic);
|
||||||
|
this.props.onError(err.message, 'err');
|
||||||
|
const blankState = MessagesView.getDerivedStateFromProps({}, {});
|
||||||
|
blankState.title = this.props.intl.formatMessage(messages.not_found);
|
||||||
|
this.setState(blankState);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
leave(oldTopicName) {
|
leave(oldTopicName) {
|
||||||
if (!oldTopicName || !this.props.tinode.isTopicCached(oldTopicName)) {
|
if (!oldTopicName || !this.props.tinode.isTopicCached(oldTopicName)) {
|
||||||
return;
|
return;
|
||||||
@@ -391,23 +399,19 @@ class MessagesView extends React.Component {
|
|||||||
// Get older messages
|
// Get older messages
|
||||||
handleScrollEvent(event) {
|
handleScrollEvent(event) {
|
||||||
this.setState({scrollPosition: event.target.scrollHeight - event.target.scrollTop});
|
this.setState({scrollPosition: event.target.scrollHeight - event.target.scrollTop});
|
||||||
|
if (this.state.fetchingMessages) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (event.target.scrollTop <= 0) {
|
if (event.target.scrollTop <= 0) {
|
||||||
this.setState((prevState, props) => {
|
const topic = this.props.tinode.getTopic(this.state.topic);
|
||||||
const newState = {};
|
if (topic && topic.isSubscribed() && topic.msgHasMoreMessages()) {
|
||||||
if (!prevState.fetchingMessages) {
|
this.setState({fetchingMessages: true}, () => {
|
||||||
const topic = this.props.tinode.getTopic(this.state.topic);
|
topic.getMessagesPage(MESSAGES_PAGE)
|
||||||
if (topic && topic.isSubscribed() && topic.msgHasMoreMessages()) {
|
.catch((err) => this.props.onError(err.message, 'err'))
|
||||||
newState.fetchingMessages = true;
|
.finally(() => this.setState({fetchingMessages: false}));
|
||||||
topic.getMessagesPage(MESSAGES_PAGE)
|
});
|
||||||
.then(() => this.setState({fetchingMessages: false}))
|
}
|
||||||
.catch((err) => {
|
|
||||||
this.setState({fetchingMessages: false});
|
|
||||||
this.props.onError(err.message, 'err');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return newState;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -530,10 +534,11 @@ class MessagesView extends React.Component {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({latestMsgSeq: topic.maxMsgSeq()});
|
clearTimeout(this.keyPressTimer)
|
||||||
|
this.setState({messageCount: topic.messageCount(), typingIndicator: false});
|
||||||
|
|
||||||
// If the message is added to the end of the message list,
|
// Scroll to the bottom if the message is added to the end of the message list.
|
||||||
// scroll to the bottom.
|
// TODO: This should be replaced by showing a "scroll to bottom" button.
|
||||||
if (topic.isNewMessage(msg.seq)) {
|
if (topic.isNewMessage(msg.seq)) {
|
||||||
this.setState({scrollPosition: 0});
|
this.setState({scrollPosition: 0});
|
||||||
}
|
}
|
||||||
@@ -560,9 +565,8 @@ class MessagesView extends React.Component {
|
|||||||
switch (info.what) {
|
switch (info.what) {
|
||||||
case 'kp': {
|
case 'kp': {
|
||||||
clearTimeout(this.keyPressTimer);
|
clearTimeout(this.keyPressTimer);
|
||||||
var instance = this;
|
this.keyPressTimer = setTimeout(() => {
|
||||||
this.keyPressTimer = setTimeout(function() {
|
this.setState({typingIndicator: false});
|
||||||
instance.setState({typingIndicator: false});
|
|
||||||
}, KEYPRESS_DELAY + 1000);
|
}, KEYPRESS_DELAY + 1000);
|
||||||
if (!this.state.typingIndicator) {
|
if (!this.state.typingIndicator) {
|
||||||
this.setState({typingIndicator: true});
|
this.setState({typingIndicator: true});
|
||||||
@@ -592,7 +596,7 @@ class MessagesView extends React.Component {
|
|||||||
|
|
||||||
handleFormResponse(action, text, data) {
|
handleFormResponse(action, text, data) {
|
||||||
if (action == 'pub') {
|
if (action == 'pub') {
|
||||||
this.props.sendMessage(Drafty.attachJSON(Drafty.parse(text), data));
|
this.sendMessage(Drafty.attachJSON(Drafty.parse(text), data));
|
||||||
} else if (action == 'url') {
|
} else if (action == 'url') {
|
||||||
const url = new URL(data.ref);
|
const url = new URL(data.ref);
|
||||||
const params = url.searchParams;
|
const params = url.searchParams;
|
||||||
@@ -653,15 +657,19 @@ class MessagesView extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
doSendMessage(msg, uploadCompletionPromise, uploader) {
|
// sendMessage sends the message with an optional subscription to topic first.
|
||||||
let head = null;
|
sendMessage(msg, uploadCompletionPromise, uploader) {
|
||||||
|
let head;
|
||||||
if (this.state.reply && this.state.reply.content) {
|
if (this.state.reply && this.state.reply.content) {
|
||||||
head = {replyToSeq: this.state.reply.seq};
|
head = {replyToSeq: this.state.reply.seq};
|
||||||
let quote = this.state.reply.content;
|
// Turn it into Drafty so we can make a quoted Drafty object later.
|
||||||
msg = Drafty.attachQuote(msg, quote);
|
if (typeof msg == 'string') {
|
||||||
|
msg = Drafty.parse(msg);
|
||||||
|
}
|
||||||
|
msg = Drafty.attachQuote(msg, this.state.reply.content);
|
||||||
|
this.setState({reply: null});
|
||||||
}
|
}
|
||||||
this.props.sendMessage(msg, uploadCompletionPromise, uploader, head);
|
this.props.sendMessage(msg, uploadCompletionPromise, uploader, head);
|
||||||
this.setState({reply: null});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send attachment as Drafty message:
|
// Send attachment as Drafty message:
|
||||||
@@ -683,13 +691,12 @@ class MessagesView extends React.Component {
|
|||||||
urlPromise: uploadCompletionPromise
|
urlPromise: uploadCompletionPromise
|
||||||
});
|
});
|
||||||
// Pass data and the uploader to the TinodeWeb.
|
// Pass data and the uploader to the TinodeWeb.
|
||||||
this.doSendMessage(msg, uploadCompletionPromise, uploader);
|
this.sendMessage(msg, uploadCompletionPromise, uploader);
|
||||||
} else {
|
} else {
|
||||||
// Small enough to send inband.
|
// Small enough to send inband.
|
||||||
fileToBase64(file,
|
fileToBase64(file,
|
||||||
(mime, bits, fname) => {
|
(mime, bits, fname) => {
|
||||||
const msg = Drafty.attachFile(null, {mime: mime, data: bits, filename: fname});
|
this.sendMessage(Drafty.attachFile(null, {mime: mime, data: bits, filename: fname}));
|
||||||
this.doSendMessage(msg, undefined, undefined);
|
|
||||||
},
|
},
|
||||||
this.props.onError
|
this.props.onError
|
||||||
);
|
);
|
||||||
@@ -753,7 +760,7 @@ class MessagesView extends React.Component {
|
|||||||
msg = Drafty.append(msg, Drafty.parse(caption));
|
msg = Drafty.append(msg, Drafty.parse(caption));
|
||||||
}
|
}
|
||||||
// Pass data and the uploader to the TinodeWeb.
|
// Pass data and the uploader to the TinodeWeb.
|
||||||
this.doSendMessage(msg, uploadCompletionPromise, uploader);
|
this.sendMessage(msg, uploadCompletionPromise, uploader);
|
||||||
}
|
}
|
||||||
)},
|
)},
|
||||||
// Failure
|
// Failure
|
||||||
@@ -778,7 +785,7 @@ class MessagesView extends React.Component {
|
|||||||
msg = Drafty.appendLineBreak(msg);
|
msg = Drafty.appendLineBreak(msg);
|
||||||
msg = Drafty.append(msg, Drafty.parse(caption));
|
msg = Drafty.append(msg, Drafty.parse(caption));
|
||||||
}
|
}
|
||||||
this.doSendMessage(msg, undefined, undefined);
|
this.sendMessage(msg);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -920,14 +927,6 @@ class MessagesView extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSendMessage(msg) {
|
|
||||||
if (this.state.reply && this.state.reply.content && typeof msg == 'string') {
|
|
||||||
// Turn it into Drafty so we can make a quoted Drafty object later.
|
|
||||||
msg = Drafty.parse(msg);
|
|
||||||
}
|
|
||||||
this.doSendMessage(msg, undefined, undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleCancelReply() {
|
handleCancelReply() {
|
||||||
this.setState({reply: null})
|
this.setState({reply: null})
|
||||||
}
|
}
|
||||||
@@ -1159,16 +1158,15 @@ class MessagesView extends React.Component {
|
|||||||
<Invitation onAction={this.handleNewChatAcceptance} />
|
<Invitation onAction={this.handleNewChatAcceptance} />
|
||||||
:
|
:
|
||||||
<SendMessage
|
<SendMessage
|
||||||
|
tinode={this.props.tinode}
|
||||||
disabled={!this.state.isWriter}
|
disabled={!this.state.isWriter}
|
||||||
onKeyPress={this.sendKeyPress}
|
onKeyPress={this.sendKeyPress}
|
||||||
|
onSendMessage={this.sendMessage}
|
||||||
onAttachFile={this.handleAttachFile}
|
onAttachFile={this.handleAttachFile}
|
||||||
onAttachImage={this.handleAttachImage}
|
onAttachImage={this.handleAttachImage}
|
||||||
onError={this.props.onError}
|
onError={this.props.onError}
|
||||||
|
|
||||||
tinode={this.props.tinode}
|
|
||||||
replyTo={this.state.reply}
|
replyTo={this.state.reply}
|
||||||
onQuoteClick={this.handleQuoteClick}
|
onQuoteClick={this.handleQuoteClick}
|
||||||
onSendMessage={this.handleSendMessage}
|
|
||||||
onFormatQuote={draftyFormatter}
|
onFormatQuote={draftyFormatter}
|
||||||
onCancelReply={this.handleCancelReply} />}
|
onCancelReply={this.handleCancelReply} />}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -56,16 +56,17 @@ class NewTopicView extends React.Component {
|
|||||||
this.setState({searchQuery: Tinode.isNullValue(query) ? null : query});
|
this.setState({searchQuery: Tinode.isNullValue(query) ? null : query});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleContactSelected(sel) {
|
handleContactSelected(uid) {
|
||||||
if (this.state.tabSelected === 'p2p') {
|
if (this.state.tabSelected == 'p2p') {
|
||||||
HashNavigation.navigateTo(HashNavigation.removeUrlParam(window.location.hash, 'tab'));
|
HashNavigation.navigateTo(HashNavigation.removeUrlParam(window.location.hash, 'tab'));
|
||||||
this.props.onCreateTopic(sel);
|
this.props.onCreateTopic(uid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleNewGroupSubmit(name, description, dataUrl, priv, tags, isChannel) {
|
handleNewGroupSubmit(name, description, dataUrl, priv, tags, isChannel) {
|
||||||
HashNavigation.navigateTo(HashNavigation.removeUrlParam(window.location.hash, 'tab'));
|
HashNavigation.navigateTo(HashNavigation.removeUrlParam(window.location.hash, 'tab'));
|
||||||
this.props.onCreateTopic(undefined, theCard(name, dataUrl, null, description), priv, tags, isChannel);
|
this.props.onCreateTopic(undefined,
|
||||||
|
{public: theCard(name, dataUrl, null, description), private: priv, tags: tags}, isChannel);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleGroupByID(topicName) {
|
handleGroupByID(topicName) {
|
||||||
@@ -112,6 +113,7 @@ class NewTopicView extends React.Component {
|
|||||||
type="p2p"
|
type="p2p"
|
||||||
onSearchContacts={this.handleSearchContacts} />
|
onSearchContacts={this.handleSearchContacts} />
|
||||||
<ContactList
|
<ContactList
|
||||||
|
tinode={this.props.tinode}
|
||||||
contacts={this.props.searchResults}
|
contacts={this.props.searchResults}
|
||||||
myUserId={this.props.myUserId}
|
myUserId={this.props.myUserId}
|
||||||
emptyListMessage={no_contacts_placeholder}
|
emptyListMessage={no_contacts_placeholder}
|
||||||
|
|||||||
@@ -917,10 +917,20 @@ class TinodeWeb extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!topic.isSubscribed()) {
|
if (!topic.isSubscribed()) {
|
||||||
|
// Topic is not subscribed yet. Subscribe.
|
||||||
if (!promise) {
|
if (!promise) {
|
||||||
promise = Promise.resolve();
|
promise = Promise.resolve();
|
||||||
}
|
}
|
||||||
promise = promise.then(() => { return topic.subscribe(); });
|
promise = promise
|
||||||
|
.then(() => topic.subscribe())
|
||||||
|
.then(() => {
|
||||||
|
// If there are unsent messages, try sending them now.
|
||||||
|
topic.queuedMessages((pub) => {
|
||||||
|
if (!pub._sending && topic.isSubscribed()) {
|
||||||
|
this.sendMessage(pub);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (promise) {
|
if (promise) {
|
||||||
@@ -1184,7 +1194,7 @@ class TinodeWeb extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Request to start a topic, new or selected from search results, or "by ID".
|
// Request to start a topic, new or selected from search results, or "by ID".
|
||||||
handleStartTopicRequest(topicName, pub, priv, tags, isChannel) {
|
handleStartTopicRequest(topicName, newTopicParams, isChannel) {
|
||||||
// Check if topic is indeed new. If not, launch it.
|
// Check if topic is indeed new. If not, launch it.
|
||||||
if (topicName && this.tinode.isTopicCached(topicName)) {
|
if (topicName && this.tinode.isTopicCached(topicName)) {
|
||||||
this.handleTopicSelected(topicName);
|
this.handleTopicSelected(topicName);
|
||||||
@@ -1199,14 +1209,14 @@ class TinodeWeb extends React.Component {
|
|||||||
params.desc = {defacs: {auth: DEFAULT_P2P_ACCESS_MODE}};
|
params.desc = {defacs: {auth: DEFAULT_P2P_ACCESS_MODE}};
|
||||||
} else {
|
} else {
|
||||||
topicName = topicName || this.tinode.newGroupTopicName(isChannel);
|
topicName = topicName || this.tinode.newGroupTopicName(isChannel);
|
||||||
params.desc = {public: pub, private: {comment: priv}};
|
params.desc = {public: newTopicParams.public, private: {comment: newTopicParams.private}};
|
||||||
params.tags = tags;
|
params.tags = newTopicParams.tags;
|
||||||
}
|
}
|
||||||
params._topicName = topicName;
|
params._topicName = topicName;
|
||||||
this.setState({newTopicParams: params}, () => {this.handleTopicSelected(topicName)});
|
this.setState({newTopicParams: params}, () => {this.handleTopicSelected(topicName)});
|
||||||
}
|
}
|
||||||
|
|
||||||
// New topic was creted, here is the new topic name.
|
// New topic was created, here is the new topic name.
|
||||||
handleNewTopicCreated(oldName, newName) {
|
handleNewTopicCreated(oldName, newName) {
|
||||||
if (this.state.topicSelected == oldName && oldName != newName) {
|
if (this.state.topicSelected == oldName && oldName != newName) {
|
||||||
// If the current URl contains the old topic name, replace it with new.
|
// If the current URl contains the old topic name, replace it with new.
|
||||||
|
|||||||
@@ -354,7 +354,7 @@ class ContextMenu extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!item) {
|
if (!item) {
|
||||||
console.log("Invalid menu item ID", e.currentTarget.dataset.id);
|
console.error("Invalid menu item ID", e.currentTarget.dataset.id);
|
||||||
} else {
|
} else {
|
||||||
this.props.onAction(
|
this.props.onAction(
|
||||||
item.id,
|
item.id,
|
||||||
@@ -425,12 +425,12 @@ class ContextMenu extends React.Component {
|
|||||||
|
|
||||||
replyToMessage(params, errorHandler) {
|
replyToMessage(params, errorHandler) {
|
||||||
console.log("Reply to message", params);
|
console.log("Reply to message", params);
|
||||||
params.pickReply({ seq: params.seq, content: params.content })
|
params.pickReply({ seq: params.seq, content: params.content });
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const menu = [];
|
||||||
let count = 0;
|
let count = 0;
|
||||||
let menu = [];
|
|
||||||
this.props.items.map((item) => {
|
this.props.items.map((item) => {
|
||||||
if (typeof item == 'string') {
|
if (typeof item == 'string') {
|
||||||
item = this.MenuItems[item];
|
item = this.MenuItems[item];
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export default class InPlaceEdit extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handeTextChange(e) {
|
handeTextChange(e) {
|
||||||
this.setState({value: e.target.value});
|
this.setState({value: e.target.value || ''});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleKeyDown(e) {
|
handleKeyDown(e) {
|
||||||
@@ -59,13 +59,13 @@ export default class InPlaceEdit extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleEditingFinished(event) {
|
handleEditingFinished(event) {
|
||||||
if (this.props.required && !event.target.checkValidity()) {
|
const value = this.state.value.trim();
|
||||||
|
if (this.props.required && (!event.target.checkValidity() || !value)) {
|
||||||
// Empty input
|
// Empty input
|
||||||
this.setState({value: this.props.value, active: false});
|
this.setState({value: this.props.value, active: false});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.setState({active: false});
|
this.setState({active: false});
|
||||||
const value = this.state.value.trim();
|
|
||||||
if ((value || this.props.value) && (value !== this.props.value)) {
|
if ((value || this.props.value) && (value !== this.props.value)) {
|
||||||
this.props.onFinished(value);
|
this.props.onFinished(value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export default class NewTopicGroup extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleFieldEdit(name, e) {
|
handleFieldEdit(name, e) {
|
||||||
this.setState({[name]: e.target.value});
|
this.setState({[name]: e.target.value || ''});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleImageChanged(img) {
|
handleImageChanged(img) {
|
||||||
@@ -82,7 +82,7 @@ export default class NewTopicGroup extends React.PureComponent {
|
|||||||
<FormattedMessage id="topic_name_editing_placeholder" defaultMessage="Freeform name of the group"
|
<FormattedMessage id="topic_name_editing_placeholder" defaultMessage="Freeform name of the group"
|
||||||
description="Prompt for entering topic name">{
|
description="Prompt for entering topic name">{
|
||||||
(placeholder) => <input type="text" id="new-topic-fn" placeholder={placeholder}
|
(placeholder) => <input type="text" id="new-topic-fn" placeholder={placeholder}
|
||||||
ref={this.fullName} value={this.state.fullName} onChange={this.handleFieldEdit.bind('fullName')}
|
ref={this.fullName} value={this.state.fullName} onChange={this.handleFieldEdit.bind(this, 'fullName')}
|
||||||
autoFocus required tabIndex={0} />
|
autoFocus required tabIndex={0} />
|
||||||
}</FormattedMessage>
|
}</FormattedMessage>
|
||||||
</div>
|
</div>
|
||||||
@@ -94,7 +94,7 @@ export default class NewTopicGroup extends React.PureComponent {
|
|||||||
<FormattedMessage id="private_editing_placeholder" defaultMessage="Visible to you only"
|
<FormattedMessage id="private_editing_placeholder" defaultMessage="Visible to you only"
|
||||||
description="Placeholder for editing 'private'">{
|
description="Placeholder for editing 'private'">{
|
||||||
(placeholder) => <input type="text" id="new-topic-priv" placeholder={placeholder}
|
(placeholder) => <input type="text" id="new-topic-priv" placeholder={placeholder}
|
||||||
value={this.state.private} onChange={this.handleFieldEdit.bind('private')} tabIndex={1} />
|
value={this.state.private} onChange={this.handleFieldEdit.bind(this, 'private')} tabIndex={1} />
|
||||||
}</FormattedMessage>
|
}</FormattedMessage>
|
||||||
</div>
|
</div>
|
||||||
<div className="group">
|
<div className="group">
|
||||||
@@ -105,7 +105,7 @@ export default class NewTopicGroup extends React.PureComponent {
|
|||||||
<FormattedMessage id="description_editing_placeholder" defaultMessage="Description (optional)"
|
<FormattedMessage id="description_editing_placeholder" defaultMessage="Description (optional)"
|
||||||
description="Placeholder for editing topic description">{
|
description="Placeholder for editing topic description">{
|
||||||
(placeholder) => <input type="text" id="new-topic-desc" placeholder={placeholder}
|
(placeholder) => <input type="text" id="new-topic-desc" placeholder={placeholder}
|
||||||
value={this.state.description} onChange={this.handleFieldEdit.bind('description')} tabIndex={2} />
|
value={this.state.description} onChange={this.handleFieldEdit.bind(this, 'description')} tabIndex={2} />
|
||||||
}</FormattedMessage>
|
}</FormattedMessage>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -196,11 +196,11 @@ export default class TopicDescEdit extends React.Component {
|
|||||||
<div>
|
<div>
|
||||||
<FormattedMessage id="full_name_prompt" defaultMessage="Full name, e.g. John Doe"
|
<FormattedMessage id="full_name_prompt" defaultMessage="Full name, e.g. John Doe"
|
||||||
description="Input placeholder for person's full name">{
|
description="Input placeholder for person's full name">{
|
||||||
(full_name_placeholder) => <InPlaceEdit
|
(full_name_placeholder) => <InPlaceEdit
|
||||||
placeholder={full_name_placeholder}
|
placeholder={full_name_placeholder}
|
||||||
value={this.state.fullName}
|
value={this.state.fullName}
|
||||||
required={true}
|
required={true}
|
||||||
onFinished={this.handleFullNameUpdate} />
|
onFinished={this.handleFullNameUpdate} />
|
||||||
}</FormattedMessage>
|
}</FormattedMessage>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -212,12 +212,15 @@ export default class TopicDescEdit extends React.Component {
|
|||||||
description="Label for editing topic name" />
|
description="Label for editing topic name" />
|
||||||
</label></div>
|
</label></div>
|
||||||
<div>
|
<div>
|
||||||
<InPlaceEdit
|
<FormattedMessage id="topic_name_editing_placeholder" defaultMessage="Freeform name of the group"
|
||||||
placeholder={this.state.groupTopic ? "Group name" : <i>Unknown</i>}
|
description="Prompt for entering topic name">{
|
||||||
readOnly={!editable}
|
(group_name_placeholder) => <InPlaceEdit
|
||||||
value={this.state.fullName}
|
placeholder={group_name_placeholder}
|
||||||
required={true}
|
readOnly={!editable}
|
||||||
onFinished={this.handleFullNameUpdate} />
|
value={this.state.fullName}
|
||||||
|
required={true}
|
||||||
|
onFinished={this.handleFullNameUpdate} />
|
||||||
|
}</FormattedMessage>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="group">
|
<div className="group">
|
||||||
@@ -228,8 +231,7 @@ export default class TopicDescEdit extends React.Component {
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<FormattedMessage id="private_editing_placeholder"
|
<FormattedMessage id="private_editing_placeholder" defaultMessage="Visible to you only"
|
||||||
defaultMessage="Visible to you only"
|
|
||||||
description="Placeholder for editing 'private'">{
|
description="Placeholder for editing 'private'">{
|
||||||
(private_placeholder) => <InPlaceEdit
|
(private_placeholder) => <InPlaceEdit
|
||||||
placeholder={private_placeholder}
|
placeholder={private_placeholder}
|
||||||
|
|||||||
+320
-347
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+525
-133
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user