moving contact management to <TinodeWeb />
This commit is contained in:
+3
-2
@@ -1339,12 +1339,13 @@ ul.tabbar > li {
|
|||||||
ul.tabbar > li > a {
|
ul.tabbar > li > a {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-top: 0.75rem;
|
padding-top: 0.75rem;
|
||||||
|
padding-left: 1rem;
|
||||||
|
padding-right: 1rem;
|
||||||
display: block;
|
display: block;
|
||||||
min-width: 6.5rem;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
line-height: 2rem;
|
line-height: 2rem;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: 1.25rem;
|
font-size: 1.3rem;
|
||||||
font-variant: small-caps;
|
font-variant: small-caps;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: rgba(33,150,243,0.6);
|
color: rgba(33,150,243,0.6);
|
||||||
|
|||||||
+6
-2
@@ -28,7 +28,6 @@
|
|||||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
|
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
|
||||||
<!-- Styles for local elements -->
|
<!-- Styles for local elements -->
|
||||||
<link rel="stylesheet" href="css/base.css" />
|
<link rel="stylesheet" href="css/base.css" />
|
||||||
|
|
||||||
<!-- ReactJs scripts -->
|
<!-- ReactJs scripts -->
|
||||||
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
|
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
|
||||||
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
|
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
|
||||||
@@ -36,7 +35,12 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="mountPoint"></div>
|
<div id="mountPoint"></div>
|
||||||
<!-- core tinode library -->
|
<script>
|
||||||
|
// Shim to imitate modules.
|
||||||
|
var module = {};
|
||||||
|
</script>
|
||||||
|
<!-- core tinode libraries -->
|
||||||
|
<script src="src/drafty.js"></script>
|
||||||
<script src="src/tinode.js"></script>
|
<script src="src/tinode.js"></script>
|
||||||
<!-- the application -->
|
<!-- the application -->
|
||||||
<script type="text/babel" src="src/webapp.jsx"></script>
|
<script type="text/babel" src="src/webapp.jsx"></script>
|
||||||
|
|||||||
@@ -23,7 +23,6 @@
|
|||||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
|
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
|
||||||
<!-- Styles for local elements -->
|
<!-- Styles for local elements -->
|
||||||
<link rel="stylesheet" href="css/base.min.css" />
|
<link rel="stylesheet" href="css/base.min.css" />
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="mountPoint"></div>
|
<div id="mountPoint"></div>
|
||||||
|
|||||||
+3
-2
@@ -51,12 +51,13 @@
|
|||||||
"react",
|
"react",
|
||||||
"web"
|
"web"
|
||||||
],
|
],
|
||||||
"main": "index.js",
|
"main": "./umd/tinode.prod.js",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/tinode/example-react-js/issues"
|
"url": "https://github.com/tinode/example-react-js/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/tinode/chat",
|
"homepage": "https://github.com/tinode/chat",
|
||||||
"email": "gene@tinode.co",
|
"email": "info@tinode.co",
|
||||||
|
"author": "Tinode Authors <info@tinode.co>",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react": "^16.4.2",
|
"react": "^16.4.2",
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
var React = require('react');
|
var React = require('react');
|
||||||
var ReactDOM = require('react-dom');
|
var ReactDOM = require('react-dom');
|
||||||
var Tinode = require('tinode-sdk');
|
var Tinode = require('tinode-sdk');
|
||||||
var TinodeWeb = require('../webapp.js')
|
var TinodeWeb = require('../webapp.js');
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
React.createElement(TinodeWeb, null),
|
React.createElement(TinodeWeb, null),
|
||||||
|
|||||||
+128
-132
@@ -11,9 +11,6 @@ const DEFAULT_HOST = KNOWN_HOSTS.hosted;
|
|||||||
// Sound to play on message received.
|
// Sound to play on message received.
|
||||||
const POP_SOUND = new Audio('audio/msg.mp3');
|
const POP_SOUND = new Audio('audio/msg.mp3');
|
||||||
|
|
||||||
// Unicode symbol used to clear objects
|
|
||||||
const DEL_CHAR = "\u2421";
|
|
||||||
|
|
||||||
// API key. Use https://github.com/tinode/chat/tree/master/keygen to generate your own
|
// API key. Use https://github.com/tinode/chat/tree/master/keygen to generate your own
|
||||||
const API_KEY = "AQEAAAABAAD_rAp4DJh05a1HAwFT3A6K";
|
const API_KEY = "AQEAAAABAAD_rAp4DJh05a1HAwFT3A6K";
|
||||||
|
|
||||||
@@ -59,9 +56,6 @@ const MAX_IMAGE_DIM = 768;
|
|||||||
const SUPPORTED_IMAGE_FORMATS = ['image/jpeg', 'image/gif', 'image/png', 'image/svg', 'image/svg+xml'];
|
const SUPPORTED_IMAGE_FORMATS = ['image/jpeg', 'image/gif', 'image/png', 'image/svg', 'image/svg+xml'];
|
||||||
const MIME_EXTENSIONS = ['jpg', 'gif', 'png', 'svg', 'svg'];
|
const MIME_EXTENSIONS = ['jpg', 'gif', 'png', 'svg', 'svg'];
|
||||||
|
|
||||||
// Tinode is defined in 'tinode.js'.
|
|
||||||
var Drafty = Tinode.Drafty;
|
|
||||||
|
|
||||||
// Helper functions for storing values in localStorage.
|
// Helper functions for storing values in localStorage.
|
||||||
// By default localStorage can store only strings, not objects or other types.
|
// By default localStorage can store only strings, not objects or other types.
|
||||||
Storage.prototype.setObject = function(key, value) {
|
Storage.prototype.setObject = function(key, value) {
|
||||||
@@ -482,7 +476,7 @@ class ContextMenu extends React.Component {
|
|||||||
console.log("Topic not found: ", params.topicName);
|
console.log("Topic not found: ", params.topicName);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
topic.delTopic().catch(function(err) {
|
topic.delTopic().catch((err) => {
|
||||||
if (errorHandler) {
|
if (errorHandler) {
|
||||||
errorHandler(err.message, "err");
|
errorHandler(err.message, "err");
|
||||||
}
|
}
|
||||||
@@ -1394,16 +1388,15 @@ class AvatarUpload extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleFileUpload(e) {
|
handleFileUpload(e) {
|
||||||
var instance = this;
|
|
||||||
imageFileScaledToBase64(e.target.files[0], AVATAR_SIZE, AVATAR_SIZE, true,
|
imageFileScaledToBase64(e.target.files[0], AVATAR_SIZE, AVATAR_SIZE, true,
|
||||||
// Success
|
// Success
|
||||||
function(base64bits, mime) {
|
(base64bits, mime) => {
|
||||||
var du = makeImageUrl({data: base64bits, type: mime});
|
var du = makeImageUrl({data: base64bits, type: mime});
|
||||||
instance.setState({dataUrl: du});
|
this.setState({dataUrl: du});
|
||||||
instance.props.onImageChanged(du);
|
this.props.onImageChanged(du);
|
||||||
},
|
},
|
||||||
// Failure
|
// Failure
|
||||||
function(err) {
|
(err) => {
|
||||||
this.props.onError(err, "err");
|
this.props.onError(err, "err");
|
||||||
});
|
});
|
||||||
// Clear the value so the same file can be uploaded again.
|
// Clear the value so the same file can be uploaded again.
|
||||||
@@ -1676,6 +1669,7 @@ class SidepanelView extends React.Component {
|
|||||||
myUserId={this.props.myUserId}
|
myUserId={this.props.myUserId}
|
||||||
connected={this.props.connected}
|
connected={this.props.connected}
|
||||||
topicSelected={this.props.topicSelected}
|
topicSelected={this.props.topicSelected}
|
||||||
|
chatList={this.props.chatList}
|
||||||
showContextMenu={this.props.showContextMenu}
|
showContextMenu={this.props.showContextMenu}
|
||||||
messageSounds={this.props.messageSounds}
|
messageSounds={this.props.messageSounds}
|
||||||
onTopicSelected={this.props.onTopicSelected}
|
onTopicSelected={this.props.onTopicSelected}
|
||||||
@@ -1685,7 +1679,7 @@ class SidepanelView extends React.Component {
|
|||||||
view === 'newtpk' ?
|
view === 'newtpk' ?
|
||||||
<NewTopicView
|
<NewTopicView
|
||||||
contactsSearchQuery={this.props.contactsSearchQuery}
|
contactsSearchQuery={this.props.contactsSearchQuery}
|
||||||
foundContacts={this.props.foundContacts}
|
searchResults={this.props.searchResults}
|
||||||
onInitFind={this.props.onInitFind}
|
onInitFind={this.props.onInitFind}
|
||||||
onSearchContacts={this.props.onSearchContacts}
|
onSearchContacts={this.props.onSearchContacts}
|
||||||
onCreateTopic={this.props.onCreateTopic}
|
onCreateTopic={this.props.onCreateTopic}
|
||||||
@@ -2123,116 +2117,24 @@ class ContactsView extends React.Component {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.prepareContactList = this.prepareContactList.bind(this);
|
this.state = ContactsView.getDerivedStateFromProps(props, {});
|
||||||
|
|
||||||
this.state = {
|
|
||||||
contactList: this.prepareContactList()
|
|
||||||
};
|
|
||||||
|
|
||||||
this.tnMeContactUpdate = this.tnMeContactUpdate.bind(this);
|
|
||||||
this.tnMeSubsUpdated = this.tnMeSubsUpdated.bind(this);
|
|
||||||
this.resetContactList = this.resetContactList.bind(this);
|
|
||||||
|
|
||||||
var me = this.props.tinode.getMeTopic();
|
|
||||||
me.onContactUpdate = this.tnMeContactUpdate;
|
|
||||||
me.onSubsUpdated = this.tnMeSubsUpdated;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
static getDerivedStateFromProps(nextProps, prevState) {
|
||||||
var me = this.props.tinode.getMeTopic();
|
let contacts = [];
|
||||||
me.onContactUpdate = undefined;
|
let unreadThreads = 0;
|
||||||
me.onSubsUpdated = undefined;
|
nextProps.chatList.map((c) => {
|
||||||
}
|
|
||||||
|
|
||||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
|
||||||
if (this.props.topicSelected != nextProps.topicSelected) {
|
|
||||||
// If topicSelecetd is changed externally, update the
|
|
||||||
// topic with online status and access mode.
|
|
||||||
for (var i=0;i<this.state.contactList.length;i++) {
|
|
||||||
var c = this.state.contactList[i];
|
|
||||||
if (c.topic == nextProps.topicSelected) {
|
|
||||||
nextProps.onOnlineChange(c.online);
|
|
||||||
nextProps.onAcsChange(c.acs);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reactions to updates to the contact list.
|
|
||||||
tnMeContactUpdate(what, cont) {
|
|
||||||
if (what == "on" || what == "off") {
|
|
||||||
this.resetContactList();
|
|
||||||
if (this.props.topicSelected == cont.topic) {
|
|
||||||
this.props.onOnlineChange(what === "on");
|
|
||||||
}
|
|
||||||
} else if (what === "read") {
|
|
||||||
this.resetContactList();
|
|
||||||
} else if (what === "msg") {
|
|
||||||
// New message received
|
|
||||||
// Skip update if the topic is currently open, otherwise the badge will annoyingly flash.
|
|
||||||
if (this.props.topicSelected !== cont.topic) {
|
|
||||||
if (this.props.messageSounds) {
|
|
||||||
POP_SOUND.play();
|
|
||||||
}
|
|
||||||
this.resetContactList();
|
|
||||||
} else if (document.hidden && this.props.messageSounds) {
|
|
||||||
POP_SOUND.play();
|
|
||||||
}
|
|
||||||
} else if (what === "recv") {
|
|
||||||
// Explicitly ignoring "recv" -- it causes no visible updates to contact list.
|
|
||||||
} else if (what === "gone" || what === "unsub") {
|
|
||||||
// Topic deleted or user unsubscribed. Remove topic from view.
|
|
||||||
// If the currently selected topic is gone, clear the selection.
|
|
||||||
if (this.props.topicSelected === cont.topic) {
|
|
||||||
this.props.onTopicSelected(null);
|
|
||||||
}
|
|
||||||
// Redraw without the deleted topic.
|
|
||||||
this.resetContactList();
|
|
||||||
} else if (what === "acs") {
|
|
||||||
// Permissions changed. If it's for the currently selected topic,
|
|
||||||
// update the views.
|
|
||||||
if (this.props.topicSelected === cont.topic) {
|
|
||||||
this.props.onAcsChange(cont.acs);
|
|
||||||
}
|
|
||||||
} else if (what == "del") {
|
|
||||||
// messages deleted (hard or soft) -- update pill counter.
|
|
||||||
} else {
|
|
||||||
// TODO(gene): handle other types of notifications:
|
|
||||||
// * ua -- user agent changes (maybe display a pictogram for mobile/desktop).
|
|
||||||
// * upd -- topic 'public' updated, issue getMeta().
|
|
||||||
console.log("Unsupported (yet) presence update:" + what + " in: " + cont.topic);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tnMeSubsUpdated(names) {
|
|
||||||
this.resetContactList();
|
|
||||||
}
|
|
||||||
|
|
||||||
prepareContactList() {
|
|
||||||
var contacts = [];
|
|
||||||
var unreadThreads = 0;
|
|
||||||
this.props.tinode.getMeTopic().contacts((c) => {
|
|
||||||
// Force to integers;
|
|
||||||
c.seq = ~~c.seq;
|
|
||||||
c.read = ~~c.read;
|
|
||||||
c.unread = c.seq - c.read;
|
|
||||||
unreadThreads += c.unread > 0 ? 1 : 0;
|
unreadThreads += c.unread > 0 ? 1 : 0;
|
||||||
contacts.push(c);
|
contacts.push(c);
|
||||||
if (this.props.topicSelected == c.topic) {
|
|
||||||
this.props.onOnlineChange(c.online);
|
|
||||||
this.props.onAcsChange(c.acs);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
contacts.sort(function(a,b){
|
|
||||||
|
contacts.sort(function(a, b){
|
||||||
return b.touched - a.touched;
|
return b.touched - a.touched;
|
||||||
});
|
});
|
||||||
updateFavicon(unreadThreads);
|
|
||||||
return contacts;
|
|
||||||
}
|
|
||||||
|
|
||||||
resetContactList() {
|
updateFavicon(unreadThreads);
|
||||||
this.setState({contactList: this.prepareContactList()});
|
|
||||||
|
return {contactList: contacts};
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@@ -2498,7 +2400,7 @@ class NewTopicView extends React.Component {
|
|||||||
this.state = {
|
this.state = {
|
||||||
tabSelected: "p2p",
|
tabSelected: "p2p",
|
||||||
searchQuery: props.contactsSearchQuery,
|
searchQuery: props.contactsSearchQuery,
|
||||||
contactList: props.foundContacts
|
contactList: props.searchResults
|
||||||
};
|
};
|
||||||
|
|
||||||
this.handleTabClick = this.handleTabClick.bind(this);
|
this.handleTabClick = this.handleTabClick.bind(this);
|
||||||
@@ -2514,7 +2416,7 @@ class NewTopicView extends React.Component {
|
|||||||
static getDerivedStateFromProps(nextProps, prevState) {
|
static getDerivedStateFromProps(nextProps, prevState) {
|
||||||
return {
|
return {
|
||||||
searchQuery: nextProps.contactsSearchQuery,
|
searchQuery: nextProps.contactsSearchQuery,
|
||||||
contactList: nextProps.foundContacts
|
contactList: nextProps.searchResults
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2676,7 +2578,7 @@ class SearchContacts extends React.Component {
|
|||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
if (this.state.search) {
|
if (this.state.search) {
|
||||||
this.setState({search: ''});
|
this.setState({search: ''});
|
||||||
this.props.onSearchContacts(DEL_CHAR);
|
this.props.onSearchContacts(Tinode.DEL_CHAR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2691,13 +2593,13 @@ class SearchContacts extends React.Component {
|
|||||||
this.setState({edited: true});
|
this.setState({edited: true});
|
||||||
this.props.onSearchContacts(query);
|
this.props.onSearchContacts(query);
|
||||||
} else if (this.props.searchQuery) {
|
} else if (this.props.searchQuery) {
|
||||||
this.props.onSearchContacts(DEL_CHAR);
|
this.props.onSearchContacts(Tinode.DEL_CHAR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClear() {
|
handleClear() {
|
||||||
if (this.state.edited || this.state.search) {
|
if (this.state.edited || this.state.search) {
|
||||||
this.props.onSearchContacts(DEL_CHAR);
|
this.props.onSearchContacts(Tinode.DEL_CHAR);
|
||||||
}
|
}
|
||||||
this.setState({search: '', edited: false});
|
this.setState({search: '', edited: false});
|
||||||
}
|
}
|
||||||
@@ -3213,7 +3115,7 @@ class InfoView extends React.Component {
|
|||||||
members={this.state.contactList}
|
members={this.state.contactList}
|
||||||
requiredMember={this.props.myUserId}
|
requiredMember={this.props.myUserId}
|
||||||
myUserId={this.props.myUserId}
|
myUserId={this.props.myUserId}
|
||||||
contacts={this.props.foundContacts}
|
contacts={this.props.searchableContacts}
|
||||||
onCancel={this.handleHideAddMembers}
|
onCancel={this.handleHideAddMembers}
|
||||||
onSubmit={this.handleMemberUpdateRequest} />
|
onSubmit={this.handleMemberUpdateRequest} />
|
||||||
:
|
:
|
||||||
@@ -4363,6 +4265,9 @@ class TinodeWeb extends React.Component {
|
|||||||
this.handleLoginSuccessful = this.handleLoginSuccessful.bind(this);
|
this.handleLoginSuccessful = this.handleLoginSuccessful.bind(this);
|
||||||
this.handleDisconnect = this.handleDisconnect.bind(this);
|
this.handleDisconnect = this.handleDisconnect.bind(this);
|
||||||
this.tnMeMetaDesc = this.tnMeMetaDesc.bind(this);
|
this.tnMeMetaDesc = this.tnMeMetaDesc.bind(this);
|
||||||
|
this.tnMeContactUpdate = this.tnMeContactUpdate.bind(this);
|
||||||
|
this.tnMeSubsUpdated = this.tnMeSubsUpdated.bind(this);
|
||||||
|
this.resetContactList = this.resetContactList.bind(this);
|
||||||
this.tnData = this.tnData.bind(this);
|
this.tnData = this.tnData.bind(this);
|
||||||
this.tnInitFind = this.tnInitFind.bind(this);
|
this.tnInitFind = this.tnInitFind.bind(this);
|
||||||
this.tnFndSubsUpdated = this.tnFndSubsUpdated.bind(this);
|
this.tnFndSubsUpdated = this.tnFndSubsUpdated.bind(this);
|
||||||
@@ -4406,11 +4311,15 @@ class TinodeWeb extends React.Component {
|
|||||||
connected: false,
|
connected: false,
|
||||||
transport: settings.transport || null,
|
transport: settings.transport || null,
|
||||||
serverAddress: settings.serverAddress || detectServerAddress(),
|
serverAddress: settings.serverAddress || detectServerAddress(),
|
||||||
|
serverVersion: 'no connection',
|
||||||
// "On" is the default, so saving the "off" state.
|
// "On" is the default, so saving the "off" state.
|
||||||
messageSounds: !settings.messageSoundsOff,
|
messageSounds: !settings.messageSoundsOff,
|
||||||
desktopAlerts: settings.desktopAlerts,
|
desktopAlerts: settings.desktopAlerts,
|
||||||
desktopAlertsEnabled: isSecureConnection(),
|
desktopAlertsEnabled: isSecureConnection(),
|
||||||
|
|
||||||
|
errorText: '',
|
||||||
|
errorLevel: null,
|
||||||
|
|
||||||
sidePanelSelected: 'login',
|
sidePanelSelected: 'login',
|
||||||
sidePanelTitle: null,
|
sidePanelTitle: null,
|
||||||
sidePanelAvatar: null,
|
sidePanelAvatar: null,
|
||||||
@@ -4419,8 +4328,6 @@ class TinodeWeb extends React.Component {
|
|||||||
login: '',
|
login: '',
|
||||||
password: '',
|
password: '',
|
||||||
myUserId: null,
|
myUserId: null,
|
||||||
errorText: '',
|
|
||||||
errorLevel: null,
|
|
||||||
liveConnection: navigator.onLine,
|
liveConnection: navigator.onLine,
|
||||||
topicSelected: '',
|
topicSelected: '',
|
||||||
topicSelectedOnline: false,
|
topicSelectedOnline: false,
|
||||||
@@ -4429,14 +4336,20 @@ class TinodeWeb extends React.Component {
|
|||||||
displayMobile: (window.innerWidth <= MEDIA_BREAKPOINT),
|
displayMobile: (window.innerWidth <= MEDIA_BREAKPOINT),
|
||||||
showInfoPanel: false,
|
showInfoPanel: false,
|
||||||
mobilePanel: 'sidepanel',
|
mobilePanel: 'sidepanel',
|
||||||
|
|
||||||
contextMenuVisible: false,
|
contextMenuVisible: false,
|
||||||
contextMenuBounds: null,
|
contextMenuBounds: null,
|
||||||
contextMenuClickAt: null,
|
contextMenuClickAt: null,
|
||||||
contextMenuParams: null,
|
contextMenuParams: null,
|
||||||
contextMenuItems: [],
|
contextMenuItems: [],
|
||||||
serverVersion: 'no connection',
|
|
||||||
contactsSearchQuery: undefined,
|
contactsSearchQuery: undefined,
|
||||||
foundContacts: [],
|
// Chats as shown in the ContactsView
|
||||||
|
chatList: [],
|
||||||
|
// Contacts returned by a search query.
|
||||||
|
searchResults: [],
|
||||||
|
// Merged results of a search query and p2p chats.
|
||||||
|
searchableContacts: [],
|
||||||
credMethod: undefined,
|
credMethod: undefined,
|
||||||
credCode: undefined
|
credCode: undefined
|
||||||
};
|
};
|
||||||
@@ -4664,6 +4577,8 @@ class TinodeWeb extends React.Component {
|
|||||||
// Logged in fine, subscribe to 'me' attaching callbacks from the contacts view.
|
// Logged in fine, subscribe to 'me' attaching callbacks from the contacts view.
|
||||||
var me = this.state.tinode.getMeTopic();
|
var me = this.state.tinode.getMeTopic();
|
||||||
me.onMetaDesc = this.tnMeMetaDesc;
|
me.onMetaDesc = this.tnMeMetaDesc;
|
||||||
|
me.onContactUpdate = this.tnMeContactUpdate;
|
||||||
|
me.onSubsUpdated = this.tnMeSubsUpdated;
|
||||||
this.setState({
|
this.setState({
|
||||||
connected: true,
|
connected: true,
|
||||||
credMethod: undefined,
|
credMethod: undefined,
|
||||||
@@ -4706,6 +4621,77 @@ class TinodeWeb extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reactions to updates to the contact list.
|
||||||
|
tnMeContactUpdate(what, cont) {
|
||||||
|
if (what == "on" || what == "off") {
|
||||||
|
this.resetContactList();
|
||||||
|
if (this.state.topicSelected == cont.topic) {
|
||||||
|
this.setState({topicSelectedOnline: (what === "on")});
|
||||||
|
}
|
||||||
|
} else if (what == "read") {
|
||||||
|
this.resetContactList();
|
||||||
|
} else if (what == "msg") {
|
||||||
|
// New message received
|
||||||
|
// Skip update if the topic is currently open, otherwise the badge will annoyingly flash.
|
||||||
|
if (this.this.topicSelected !== cont.topic) {
|
||||||
|
if (this.state.messageSounds) {
|
||||||
|
POP_SOUND.play();
|
||||||
|
}
|
||||||
|
this.resetContactList();
|
||||||
|
} else if (document.hidden && this.state.messageSounds) {
|
||||||
|
POP_SOUND.play();
|
||||||
|
}
|
||||||
|
} else if (what == "recv") {
|
||||||
|
// Explicitly ignoring "recv" -- it causes no visible updates to contact list.
|
||||||
|
} else if (what == "gone" || what == "unsub") {
|
||||||
|
// Topic deleted or user unsubscribed. Remove topic from view.
|
||||||
|
// If the currently selected topic is gone, clear the selection.
|
||||||
|
if (this.state.topicSelected == cont.topic) {
|
||||||
|
this.handleTopicSelected(null);
|
||||||
|
}
|
||||||
|
// Redraw without the deleted topic.
|
||||||
|
this.resetContactList();
|
||||||
|
} else if (what == "acs") {
|
||||||
|
// Permissions changed. If it's for the currently selected topic,
|
||||||
|
// update the views.
|
||||||
|
if (this.state.topicSelected == cont.topic) {
|
||||||
|
this.setState({topicSelectedAcs: cont.acs});
|
||||||
|
}
|
||||||
|
} else if (what == "del") {
|
||||||
|
// messages deleted (hard or soft) -- update pill counter.
|
||||||
|
} else {
|
||||||
|
// TODO(gene): handle other types of notifications:
|
||||||
|
// * ua -- user agent changes (maybe display a pictogram for mobile/desktop).
|
||||||
|
// * upd -- topic 'public' updated, issue getMeta().
|
||||||
|
console.log("Unsupported (yet) presence update:" + what + " in: " + cont.topic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tnMeSubsUpdated(unused) {
|
||||||
|
this.resetContactList();
|
||||||
|
}
|
||||||
|
|
||||||
|
resetContactList() {
|
||||||
|
let newState = {
|
||||||
|
chatList: []
|
||||||
|
};
|
||||||
|
this.state.tinode.getMeTopic().contacts((c) => {
|
||||||
|
newState.chatList.push(c);
|
||||||
|
if (this.state.topicSelected == c.topic) {
|
||||||
|
newState.topicSelected = c.online;
|
||||||
|
newState.topicSelectedAcs = c.acs;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Merge search results and chat list.
|
||||||
|
let merged = {};
|
||||||
|
for (const c of newState.chatList.concat(this.state.searchResults)) {
|
||||||
|
merged[c.topic] = c;
|
||||||
|
}
|
||||||
|
newState.searchableContacts = Object.values(merged);
|
||||||
|
console.log("Resetting contact list 2", newState);
|
||||||
|
this.setState(newState);
|
||||||
|
}
|
||||||
|
|
||||||
// Sending "received" notifications
|
// Sending "received" notifications
|
||||||
tnData(data) {
|
tnData(data) {
|
||||||
let topic = this.state.tinode.getTopic(data.topic);
|
let topic = this.state.tinode.getTopic(data.topic);
|
||||||
@@ -4720,7 +4706,7 @@ class TinodeWeb extends React.Component {
|
|||||||
|
|
||||||
/* Fnd topic: find contacts by tokens */
|
/* Fnd topic: find contacts by tokens */
|
||||||
tnInitFind() {
|
tnInitFind() {
|
||||||
var fnd = this.state.tinode.getFndTopic();
|
let fnd = this.state.tinode.getFndTopic();
|
||||||
if (fnd.isSubscribed()) {
|
if (fnd.isSubscribed()) {
|
||||||
this.setState({contactsSearchQuery: ''});
|
this.setState({contactsSearchQuery: ''});
|
||||||
this.tnFndSubsUpdated();
|
this.tnFndSubsUpdated();
|
||||||
@@ -4733,12 +4719,15 @@ class TinodeWeb extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tnFndSubsUpdated() {
|
tnFndSubsUpdated() {
|
||||||
var contacts = [];
|
let contacts = [];
|
||||||
// Don't attempt to create P2P topics which already exist. Server will reject the duplicates.
|
// Don't attempt to create P2P topics which already exist. Server will reject the duplicates.
|
||||||
this.state.tinode.getFndTopic().contacts((s) => {
|
this.state.tinode.getFndTopic().contacts((s) => {
|
||||||
contacts.push(s);
|
contacts.push(s);
|
||||||
});
|
});
|
||||||
this.setState({foundContacts: contacts});
|
this.setState({
|
||||||
|
searchResults: contacts,
|
||||||
|
searchableContacts: mergeContacts(contacts, this.state.chatList)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Called when the user enters a contact into the contact search field in the NewAccount panel
|
/** Called when the user enters a contact into the contact search field in the NewAccount panel
|
||||||
@@ -4746,7 +4735,7 @@ class TinodeWeb extends React.Component {
|
|||||||
*/
|
*/
|
||||||
handleSearchContacts(query) {
|
handleSearchContacts(query) {
|
||||||
var fnd = this.state.tinode.getFndTopic();
|
var fnd = this.state.tinode.getFndTopic();
|
||||||
this.setState({contactsSearchQuery: query == DEL_CHAR ? '' : query});
|
this.setState({contactsSearchQuery: query == Tinode.DEL_CHAR ? '' : query});
|
||||||
fnd.setMeta({desc: {public: query}}).then((ctrl) => {
|
fnd.setMeta({desc: {public: query}}).then((ctrl) => {
|
||||||
return fnd.getMeta(fnd.startMetaQuery().withSub().build());
|
return fnd.getMeta(fnd.startMetaQuery().withSub().build());
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
@@ -4807,10 +4796,16 @@ class TinodeWeb extends React.Component {
|
|||||||
handleSendMessage(msg, promise, uploader) {
|
handleSendMessage(msg, promise, uploader) {
|
||||||
let topic = this.state.tinode.getTopic(this.state.topicSelected);
|
let topic = this.state.tinode.getTopic(this.state.topicSelected);
|
||||||
let dft = Drafty.parse(msg);
|
let dft = Drafty.parse(msg);
|
||||||
|
let mimeType, attachments;
|
||||||
if (dft && !Drafty.isPlainText(dft)) {
|
if (dft && !Drafty.isPlainText(dft)) {
|
||||||
msg = dft;
|
msg = dft;
|
||||||
|
mimeType = Drafty.getContentType();
|
||||||
|
if (Drafty.hasAttachments(data)) {
|
||||||
|
attachments = [];
|
||||||
|
Drafty.attachments(data, (val) => { attachments.push(val); });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
msg = topic.createMessage(msg);
|
msg = topic.createMessage(msg, false, mimeType, attachments);
|
||||||
msg._uploader = uploader;
|
msg._uploader = uploader;
|
||||||
|
|
||||||
if (promise) {
|
if (promise) {
|
||||||
@@ -5124,6 +5119,7 @@ class TinodeWeb extends React.Component {
|
|||||||
errorText={this.state.errorText}
|
errorText={this.state.errorText}
|
||||||
errorLevel={this.state.errorLevel}
|
errorLevel={this.state.errorLevel}
|
||||||
topicSelected={this.state.topicSelected}
|
topicSelected={this.state.topicSelected}
|
||||||
|
chatList={this.state.chatList}
|
||||||
credMethod={this.state.credMethod}
|
credMethod={this.state.credMethod}
|
||||||
credCode={this.state.credCode}
|
credCode={this.state.credCode}
|
||||||
|
|
||||||
@@ -5152,7 +5148,7 @@ class TinodeWeb extends React.Component {
|
|||||||
|
|
||||||
onInitFind={this.tnInitFind}
|
onInitFind={this.tnInitFind}
|
||||||
contactsSearchQuery={this.state.contactsSearchQuery}
|
contactsSearchQuery={this.state.contactsSearchQuery}
|
||||||
foundContacts={this.state.foundContacts}
|
searchResults={this.state.searchResults}
|
||||||
onSearchContacts={this.handleSearchContacts}
|
onSearchContacts={this.handleSearchContacts}
|
||||||
|
|
||||||
showContextMenu={this.handleShowContextMenu} />
|
showContextMenu={this.handleShowContextMenu} />
|
||||||
@@ -5186,7 +5182,7 @@ class TinodeWeb extends React.Component {
|
|||||||
connected={this.state.connected}
|
connected={this.state.connected}
|
||||||
displayMobile={this.state.displayMobile}
|
displayMobile={this.state.displayMobile}
|
||||||
topic={this.state.topicSelected}
|
topic={this.state.topicSelected}
|
||||||
foundContacts={this.state.foundContacts}
|
searchableContacts={this.state.searchableContacts}
|
||||||
myUserId={this.state.myUserId}
|
myUserId={this.state.myUserId}
|
||||||
errorText={this.state.errorText}
|
errorText={this.state.errorText}
|
||||||
errorLevel={this.state.errorLevel}
|
errorLevel={this.state.errorLevel}
|
||||||
|
|||||||
Reference in New Issue
Block a user