Merge branch 'devel'
This commit is contained in:
+3
-3
@@ -32,10 +32,10 @@
|
|||||||
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/react@16/umd/react.development.js"></script>
|
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/react@16/umd/react.development.js"></script>
|
||||||
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/react-dom@16/umd/react-dom.development.js"></script>
|
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/react-dom@16/umd/react-dom.development.js"></script>
|
||||||
<!-- React-Intl for translations -->
|
<!-- React-Intl for translations -->
|
||||||
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/react-intl@3/dist/react-intl.js"></script>
|
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/react-intl@4/dist/react-intl.js"></script>
|
||||||
<!-- Firebase for push notifications -->
|
<!-- Firebase for push notifications -->
|
||||||
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/firebase@7.14.6/firebase-app.js"></script>
|
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/firebase@7.20.0/firebase-app.js"></script>
|
||||||
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/firebase@7.14.6/firebase-messaging.js"></script>
|
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/firebase@7.20.0/firebase-messaging.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
+3
-3
@@ -24,9 +24,9 @@
|
|||||||
<link rel="stylesheet" href="css/base.min.css" />
|
<link rel="stylesheet" href="css/base.min.css" />
|
||||||
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/react@16.13.1/umd/react.production.min.js"></script>
|
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/react@16.13.1/umd/react.production.min.js"></script>
|
||||||
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/react-dom@16.13.1/umd/react-dom.production.min.js"></script>
|
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/react-dom@16.13.1/umd/react-dom.production.min.js"></script>
|
||||||
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/react-intl@3.12.1/dist/react-intl.min.js"></script>
|
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/react-intl@4.7.6/dist/react-intl.min.js"></script>
|
||||||
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/firebase@7.14.6/firebase-app.js"></script>
|
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/firebase@7.20.0/firebase-app.js"></script>
|
||||||
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/firebase@7.14.6/firebase-messaging.js"></script>
|
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/firebase@7.20.0/firebase-messaging.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript><div id="noscript-message"><div id="noscript-title">Javascript support is required.</div>
|
<noscript><div id="noscript-message"><div id="noscript-title">Javascript support is required.</div>
|
||||||
|
|||||||
Generated
+1231
-923
File diff suppressed because it is too large
Load Diff
+11
-11
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "tinode-webapp",
|
"name": "tinode-webapp",
|
||||||
"description": "Tinode messenger for the web",
|
"description": "Tinode messenger for the web",
|
||||||
"version": "0.16.7",
|
"version": "0.16.8",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/tinode/example-react-js.git"
|
"url": "https://github.com/tinode/example-react-js.git"
|
||||||
@@ -68,25 +68,25 @@
|
|||||||
"author": "Tinode Authors <info@tinode.co>",
|
"author": "Tinode Authors <info@tinode.co>",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"firebase": "^7.16.1",
|
"firebase": "^7.20.0",
|
||||||
"react": "^16.13.1",
|
"react": "^16.13.1",
|
||||||
"react-dom": "^16.13.1",
|
"react-dom": "^16.13.1",
|
||||||
"react-intl": "^4.7.6",
|
"react-intl": "^4.7.6",
|
||||||
"tinode-sdk": "^0.16.7"
|
"tinode-sdk": "^0.16.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.10.5",
|
"@babel/core": "^7.11.6",
|
||||||
"@babel/preset-env": "^7.10.4",
|
"@babel/preset-env": "^7.11.5",
|
||||||
"@babel/preset-react": "^7.10.4",
|
"@babel/preset-react": "^7.10.4",
|
||||||
"babel-loader": "^8.1.0",
|
"babel-loader": "^8.1.0",
|
||||||
"babel-plugin-react-intl": "^7.8.2",
|
"babel-plugin-react-intl": "^7.9.4",
|
||||||
"browserslist": "^4.13.0",
|
"browserslist": "^4.14.2",
|
||||||
"copy-webpack-plugin": "^6.0.3",
|
"copy-webpack-plugin": "^6.1.0",
|
||||||
"cssnano": "^4.1.10",
|
"cssnano": "^4.1.10",
|
||||||
"postcss": "^7.0.32",
|
"postcss": "^7.0.32",
|
||||||
"postcss-cli": "^7.1.1",
|
"postcss-cli": "^7.1.2",
|
||||||
"terser-webpack-plugin": "^3.0.7",
|
"terser-webpack-plugin": "^3.1.0",
|
||||||
"webpack": "^4.43.0",
|
"webpack": "^4.44.1",
|
||||||
"webpack-cli": "^3.3.12"
|
"webpack-cli": "^3.3.12"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
// Must be located at the root.
|
// Must be located at the root.
|
||||||
importScripts('https://cdn.jsdelivr.net/npm/firebase@7.14.6/firebase-app.js');
|
importScripts('https://cdn.jsdelivr.net/npm/firebase@7.20.0/firebase-app.js');
|
||||||
importScripts('https://cdn.jsdelivr.net/npm/firebase@7.14.6/firebase-messaging.js');
|
importScripts('https://cdn.jsdelivr.net/npm/firebase@7.20.0/firebase-messaging.js');
|
||||||
importScripts('firebase-init.js');
|
importScripts('firebase-init.js');
|
||||||
|
|
||||||
firebase.initializeApp(FIREBASE_INIT);
|
firebase.initializeApp(FIREBASE_INIT);
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ export const MAX_TAG_COUNT = 16;
|
|||||||
export const DEFAULT_P2P_ACCESS_MODE = 'JRWPS';
|
export const DEFAULT_P2P_ACCESS_MODE = 'JRWPS';
|
||||||
// Access mode for new group topics created by the current user.
|
// Access mode for new group topics created by the current user.
|
||||||
export const NEW_GRP_ACCESS_MODE = 'JRWPSAO';
|
export const NEW_GRP_ACCESS_MODE = 'JRWPSAO';
|
||||||
|
// Access mode for a channel.
|
||||||
|
export const CHANNEL_ACCESS_MODE = 'JR';
|
||||||
|
|
||||||
// Access mode for no access.
|
// Access mode for no access.
|
||||||
export const NO_ACCESS_MODE = 'N';
|
export const NO_ACCESS_MODE = 'N';
|
||||||
|
|||||||
+2
-1
@@ -171,5 +171,6 @@
|
|||||||
"tags_not_found": "Keine Schlagworte definiert. Erstellen Sie welche.",
|
"tags_not_found": "Keine Schlagworte definiert. Erstellen Sie welche.",
|
||||||
"tags_editor_no_tags": "Schlagworte hinzufügen",
|
"tags_editor_no_tags": "Schlagworte hinzufügen",
|
||||||
"title_manage_tags": "Verwalten",
|
"title_manage_tags": "Verwalten",
|
||||||
"channel_prompt": "Dies ist ein Kanal"
|
"channel_prompt": "Dies ist ein Sender",
|
||||||
|
"channel": "Sender"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,6 +65,7 @@
|
|||||||
"online_now": "online now",
|
"online_now": "online now",
|
||||||
"last_seen_timestamp": "Last seen",
|
"last_seen_timestamp": "Last seen",
|
||||||
"title_not_found": "Not found",
|
"title_not_found": "Not found",
|
||||||
|
"channel": "channel",
|
||||||
"unnamed_topic": "Unnamed",
|
"unnamed_topic": "Unnamed",
|
||||||
"messages_not_readable": "no access to messages",
|
"messages_not_readable": "no access to messages",
|
||||||
"peers_messaging_disabled": "Peer's messaging is disabled.",
|
"peers_messaging_disabled": "Peer's messaging is disabled.",
|
||||||
|
|||||||
+2
-1
@@ -171,5 +171,6 @@
|
|||||||
"tags_not_found": "No hay etiquetas definidas. Añade unas.",
|
"tags_not_found": "No hay etiquetas definidas. Añade unas.",
|
||||||
"tags_editor_no_tags": "Añadir etiquetas",
|
"tags_editor_no_tags": "Añadir etiquetas",
|
||||||
"title_manage_tags": "Gestionar",
|
"title_manage_tags": "Gestionar",
|
||||||
"channel_prompt": "Este es un canal"
|
"channel_prompt": "Este es un canal",
|
||||||
|
"channel": "canal"
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -174,5 +174,6 @@
|
|||||||
"granted_permissions": "승낙하다",
|
"granted_permissions": "승낙하다",
|
||||||
"messages_not_readable": "메시지에 액세스 할 수 없습니다",
|
"messages_not_readable": "메시지에 액세스 할 수 없습니다",
|
||||||
"deleted_content": "내용이 삭제되었습니다",
|
"deleted_content": "내용이 삭제되었습니다",
|
||||||
"channel_prompt": "이 채널입니다"
|
"channel_prompt": "이 채널입니다",
|
||||||
|
"channel": "채널"
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -171,5 +171,6 @@
|
|||||||
"invalid_security_token": "Токен некорректен",
|
"invalid_security_token": "Токен некорректен",
|
||||||
"no_connection": "Нет связи",
|
"no_connection": "Нет связи",
|
||||||
"code_doesnot_match": "Код не совпадает",
|
"code_doesnot_match": "Код не совпадает",
|
||||||
"channel_prompt": "Создать канал"
|
"channel_prompt": "Создать канал",
|
||||||
|
"channel": "канал"
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -171,5 +171,6 @@
|
|||||||
"invalid_security_token": "无效的安全令牌",
|
"invalid_security_token": "无效的安全令牌",
|
||||||
"no_connection": "无连接",
|
"no_connection": "无连接",
|
||||||
"code_doesnot_match": "代码不匹配",
|
"code_doesnot_match": "代码不匹配",
|
||||||
"channel_prompt": "这是一个频道"
|
"channel_prompt": "这是一个频道",
|
||||||
|
"channel": "频道"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// File and image helper functions.
|
// File and image helper functions.
|
||||||
import { MAX_INBAND_ATTACHMENT_SIZE, MAX_IMAGE_DIM } from '../config.js';
|
import { MAX_INBAND_ATTACHMENT_SIZE, MAX_IMAGE_DIM } from '../config.js';
|
||||||
|
import { bytesToHumanSize } from './strformat.js'
|
||||||
|
|
||||||
// Supported image MIME types and corresponding file extensions.
|
// Supported image MIME types and corresponding file extensions.
|
||||||
export const SUPPORTED_IMAGE_FORMATS = ['image/jpeg', 'image/gif', 'image/png', 'image/svg', 'image/svg+xml'];
|
export const SUPPORTED_IMAGE_FORMATS = ['image/jpeg', 'image/gif', 'image/png', 'image/svg', 'image/svg+xml'];
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
export default class HashNavigation {
|
export default class HashNavigation {
|
||||||
static parseUrlHash(hash) {
|
static parseUrlHash(hash) {
|
||||||
// Split path from args, path -> parts[0], args->path[1]
|
// Split path from args, path -> parts[0], args->path[1]
|
||||||
let parts = hash.split('?', 2);
|
const parts = hash.split('?', 2);
|
||||||
let params = {};
|
const params = {};
|
||||||
let path = [];
|
let path = [];
|
||||||
if (parts[0]) {
|
if (parts[0]) {
|
||||||
path = parts[0].substr(1).split('/');
|
path = parts[0].substr(1).split('/');
|
||||||
@@ -27,9 +27,9 @@ export default class HashNavigation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static composeUrlHash(path, params) {
|
static composeUrlHash(path, params) {
|
||||||
var url = path.join('/');
|
let url = path.join('/');
|
||||||
var args = [];
|
const args = [];
|
||||||
for (var key in params) {
|
for (const key in params) {
|
||||||
if (params.hasOwnProperty(key)) {
|
if (params.hasOwnProperty(key)) {
|
||||||
args.push(key + '=' + params[key]);
|
args.push(key + '=' + params[key]);
|
||||||
}
|
}
|
||||||
@@ -41,25 +41,25 @@ export default class HashNavigation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static addUrlParam(hash, key, value) {
|
static addUrlParam(hash, key, value) {
|
||||||
var parsed = this.parseUrlHash(hash);
|
const parsed = this.parseUrlHash(hash);
|
||||||
parsed.params[key] = value;
|
parsed.params[key] = value;
|
||||||
return this.composeUrlHash(parsed.path, parsed.params);
|
return this.composeUrlHash(parsed.path, parsed.params);
|
||||||
}
|
}
|
||||||
|
|
||||||
static removeUrlParam(hash, key) {
|
static removeUrlParam(hash, key) {
|
||||||
var parsed = this.parseUrlHash(hash);
|
const parsed = this.parseUrlHash(hash);
|
||||||
delete parsed.params[key];
|
delete parsed.params[key];
|
||||||
return this.composeUrlHash(parsed.path, parsed.params);
|
return this.composeUrlHash(parsed.path, parsed.params);
|
||||||
}
|
}
|
||||||
|
|
||||||
static setUrlSidePanel(hash, sidepanel) {
|
static setUrlSidePanel(hash, sidepanel) {
|
||||||
var parsed = this.parseUrlHash(hash);
|
const parsed = this.parseUrlHash(hash);
|
||||||
parsed.path[0] = sidepanel;
|
parsed.path[0] = sidepanel;
|
||||||
return this.composeUrlHash(parsed.path, parsed.params);
|
return this.composeUrlHash(parsed.path, parsed.params);
|
||||||
}
|
}
|
||||||
|
|
||||||
static setUrlTopic(hash, topic) {
|
static setUrlTopic(hash, topic) {
|
||||||
var parsed = this.parseUrlHash(hash);
|
const parsed = this.parseUrlHash(hash);
|
||||||
parsed.path[1] = topic;
|
parsed.path[1] = topic;
|
||||||
// Close InfoView on topic change.
|
// Close InfoView on topic change.
|
||||||
delete parsed.params.info;
|
delete parsed.params.info;
|
||||||
|
|||||||
+11
-5
@@ -172,7 +172,8 @@
|
|||||||
"tags_not_found": "Keine Schlagworte definiert. Erstellen Sie welche.",
|
"tags_not_found": "Keine Schlagworte definiert. Erstellen Sie welche.",
|
||||||
"tags_editor_no_tags": "Schlagworte hinzufügen",
|
"tags_editor_no_tags": "Schlagworte hinzufügen",
|
||||||
"title_manage_tags": "Verwalten",
|
"title_manage_tags": "Verwalten",
|
||||||
"channel_prompt": "Dies ist ein Kanal"
|
"channel_prompt": "Dies ist ein Sender",
|
||||||
|
"channel": "Sender"
|
||||||
},
|
},
|
||||||
"en": {
|
"en": {
|
||||||
"validate_credential_action": "confirm",
|
"validate_credential_action": "confirm",
|
||||||
@@ -241,6 +242,7 @@
|
|||||||
"online_now": "online now",
|
"online_now": "online now",
|
||||||
"last_seen_timestamp": "Last seen",
|
"last_seen_timestamp": "Last seen",
|
||||||
"title_not_found": "Not found",
|
"title_not_found": "Not found",
|
||||||
|
"channel": "channel",
|
||||||
"unnamed_topic": "Unnamed",
|
"unnamed_topic": "Unnamed",
|
||||||
"messages_not_readable": "no access to messages",
|
"messages_not_readable": "no access to messages",
|
||||||
"peers_messaging_disabled": "Peer's messaging is disabled.",
|
"peers_messaging_disabled": "Peer's messaging is disabled.",
|
||||||
@@ -522,7 +524,8 @@
|
|||||||
"tags_not_found": "No hay etiquetas definidas. Añade unas.",
|
"tags_not_found": "No hay etiquetas definidas. Añade unas.",
|
||||||
"tags_editor_no_tags": "Añadir etiquetas",
|
"tags_editor_no_tags": "Añadir etiquetas",
|
||||||
"title_manage_tags": "Gestionar",
|
"title_manage_tags": "Gestionar",
|
||||||
"channel_prompt": "Este es un canal"
|
"channel_prompt": "Este es un canal",
|
||||||
|
"channel": "canal"
|
||||||
},
|
},
|
||||||
"ko": {
|
"ko": {
|
||||||
"validate_credential_action": "확인",
|
"validate_credential_action": "확인",
|
||||||
@@ -697,7 +700,8 @@
|
|||||||
"granted_permissions": "승낙하다",
|
"granted_permissions": "승낙하다",
|
||||||
"messages_not_readable": "메시지에 액세스 할 수 없습니다",
|
"messages_not_readable": "메시지에 액세스 할 수 없습니다",
|
||||||
"deleted_content": "내용이 삭제되었습니다",
|
"deleted_content": "내용이 삭제되었습니다",
|
||||||
"channel_prompt": "이 채널입니다"
|
"channel_prompt": "이 채널입니다",
|
||||||
|
"channel": "채널"
|
||||||
},
|
},
|
||||||
"ru": {
|
"ru": {
|
||||||
"contacts_not_found": "Чатов нет<br />¯∖_(ツ)_/¯",
|
"contacts_not_found": "Чатов нет<br />¯∖_(ツ)_/¯",
|
||||||
@@ -872,7 +876,8 @@
|
|||||||
"invalid_security_token": "Токен некорректен",
|
"invalid_security_token": "Токен некорректен",
|
||||||
"no_connection": "Нет связи",
|
"no_connection": "Нет связи",
|
||||||
"code_doesnot_match": "Код не совпадает",
|
"code_doesnot_match": "Код не совпадает",
|
||||||
"channel_prompt": "Создать канал"
|
"channel_prompt": "Создать канал",
|
||||||
|
"channel": "канал"
|
||||||
},
|
},
|
||||||
"zh": {
|
"zh": {
|
||||||
"archived_contacts": "已归档联系人 ({count})",
|
"archived_contacts": "已归档联系人 ({count})",
|
||||||
@@ -1047,6 +1052,7 @@
|
|||||||
"invalid_security_token": "无效的安全令牌",
|
"invalid_security_token": "无效的安全令牌",
|
||||||
"no_connection": "无连接",
|
"no_connection": "无连接",
|
||||||
"code_doesnot_match": "代码不匹配",
|
"code_doesnot_match": "代码不匹配",
|
||||||
"channel_prompt": "这是一个频道"
|
"channel_prompt": "这是一个频道",
|
||||||
|
"channel": "频道"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
// This is a generated file. Don't edit.
|
// This is a generated file. Don't edit.
|
||||||
|
|
||||||
export const PACKAGE_VERSION = "0.16.7";
|
export const PACKAGE_VERSION = "0.16.8";
|
||||||
|
|||||||
+28
-16
@@ -106,6 +106,7 @@ class InfoView extends React.Component {
|
|||||||
muted: false,
|
muted: false,
|
||||||
address: null,
|
address: null,
|
||||||
groupTopic: undefined,
|
groupTopic: undefined,
|
||||||
|
channel: undefined,
|
||||||
fullName: undefined,
|
fullName: undefined,
|
||||||
avatar: null,
|
avatar: null,
|
||||||
private: null,
|
private: null,
|
||||||
@@ -230,6 +231,7 @@ class InfoView extends React.Component {
|
|||||||
private: topic.private ? topic.private.comment : null,
|
private: topic.private ? topic.private.comment : null,
|
||||||
address: topic.name,
|
address: topic.name,
|
||||||
groupTopic: (topic.getType() == 'grp'),
|
groupTopic: (topic.getType() == 'grp'),
|
||||||
|
channel: topic.isChannel(),
|
||||||
showMemberPanel: false,
|
showMemberPanel: false,
|
||||||
access: acs ? acs.getMode() : undefined,
|
access: acs ? acs.getMode() : undefined,
|
||||||
modeGiven: acs ? acs.getGiven() : undefined,
|
modeGiven: acs ? acs.getGiven() : undefined,
|
||||||
@@ -335,9 +337,17 @@ class InfoView extends React.Component {
|
|||||||
case 'want':
|
case 'want':
|
||||||
toEdit = this.state.modeWant;
|
toEdit = this.state.modeWant;
|
||||||
toCompare = this.state.modeGiven;
|
toCompare = this.state.modeGiven;
|
||||||
toSkip = this.state.owner ? 'O' : // Don't allow owner to unset 'O' permission.
|
if (this.state.owner) {
|
||||||
|
// Don't allow owner to unset 'O' permission. All other permissions are OK.
|
||||||
|
toSkip = 'O';
|
||||||
|
} else {
|
||||||
// Allow accepting any of 'ASDO' permissions but don't allow asking for them.
|
// Allow accepting any of 'ASDO' permissions but don't allow asking for them.
|
||||||
Tinode.AccessMode.encode(Tinode.AccessMode.diff('ASDO', this.state.modeGiven));
|
toSkip = Tinode.AccessMode.encode(Tinode.AccessMode.diff('ASDO', this.state.modeGiven));
|
||||||
|
if (this.state.channel) {
|
||||||
|
// Channels are read-only.
|
||||||
|
toSkip += 'W';
|
||||||
|
}
|
||||||
|
}
|
||||||
titleEdit = formatMessage(messages.requested);
|
titleEdit = formatMessage(messages.requested);
|
||||||
titleCompare = formatMessage(messages.granted);
|
titleCompare = formatMessage(messages.granted);
|
||||||
break;
|
break;
|
||||||
@@ -357,11 +367,11 @@ class InfoView extends React.Component {
|
|||||||
toSkip = 'O';
|
toSkip = 'O';
|
||||||
break;
|
break;
|
||||||
case 'user': {
|
case 'user': {
|
||||||
let topic = this.props.tinode.getTopic(this.props.topic);
|
const topic = this.props.tinode.getTopic(this.props.topic);
|
||||||
if (!topic) {
|
if (!topic) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var user = topic.subscriber(uid);
|
const user = topic.subscriber(uid);
|
||||||
if (!user || !user.acs) {
|
if (!user || !user.acs) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -695,11 +705,15 @@ class InfoView extends React.Component {
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
<div className="panel-form-column">
|
<div className="panel-form-column">
|
||||||
<a href="#" className="flat-button" onClick={this.handleDeleteMessages}>
|
{!this.state.channel ?
|
||||||
<i className="material-icons">delete_outline</i> {
|
<a href="#" className="flat-button" onClick={this.handleDeleteMessages}>
|
||||||
formatMessage(this.state.deleter ? messages.delete_messages : messages.clear_messages)
|
<i className="material-icons">delete_outline</i> {
|
||||||
}
|
formatMessage(this.state.deleter ? messages.delete_messages : messages.clear_messages)
|
||||||
</a>
|
}
|
||||||
|
</a>
|
||||||
|
:
|
||||||
|
null
|
||||||
|
}
|
||||||
<a href="#" className="red flat-button" onClick={this.handleLeave}>
|
<a href="#" className="red flat-button" onClick={this.handleLeave}>
|
||||||
<i className="material-icons">exit_to_app</i> {formatMessage(messages.leave_chat)}
|
<i className="material-icons">exit_to_app</i> {formatMessage(messages.leave_chat)}
|
||||||
</a>
|
</a>
|
||||||
@@ -718,7 +732,7 @@ class InfoView extends React.Component {
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
{this.state.groupTopic ?
|
{this.state.groupTopic && this.state.sharer ?
|
||||||
<>
|
<>
|
||||||
<div className="hr" />
|
<div className="hr" />
|
||||||
<div className="panel-form-column">
|
<div className="panel-form-column">
|
||||||
@@ -729,12 +743,10 @@ class InfoView extends React.Component {
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className="panel-form-row">
|
<div className="panel-form-row">
|
||||||
{this.state.sharer ?
|
<a href="#" className="flat-button" onClick={this.handleShowAddMembers}>
|
||||||
<a href="#" className="flat-button" onClick={this.handleShowAddMembers}>
|
<i className="material-icons">person_add</i> <FormattedMessage id="button_add_members"
|
||||||
<i className="material-icons">person_add</i> <FormattedMessage id="button_add_members"
|
defaultMessage="Add members" description="Flat button [Add members] (to topic)" />
|
||||||
defaultMessage="Add members" description="Flat button [Add members] (to topic)" />
|
</a>
|
||||||
</a>
|
|
||||||
: null}
|
|
||||||
</div>
|
</div>
|
||||||
<FormattedMessage id="group_has_no_members" defaultMessage="No members"
|
<FormattedMessage id="group_has_no_members" defaultMessage="No members"
|
||||||
description="Shown in place of group members">{
|
description="Shown in place of group members">{
|
||||||
|
|||||||
+71
-30
@@ -19,18 +19,19 @@ import { DEFAULT_P2P_ACCESS_MODE, KEYPRESS_DELAY, MESSAGES_PAGE, MAX_EXTERN_ATTA
|
|||||||
MAX_IMAGE_DIM, MAX_INBAND_ATTACHMENT_SIZE, READ_DELAY } from '../config.js';
|
MAX_IMAGE_DIM, MAX_INBAND_ATTACHMENT_SIZE, READ_DELAY } from '../config.js';
|
||||||
import { SUPPORTED_IMAGE_FORMATS, filePasted, fileToBase64, imageFileToBase64,
|
import { SUPPORTED_IMAGE_FORMATS, filePasted, fileToBase64, imageFileToBase64,
|
||||||
imageFileScaledToBase64, makeImageUrl } from '../lib/blob-helpers.js';
|
imageFileScaledToBase64, makeImageUrl } from '../lib/blob-helpers.js';
|
||||||
|
import HashNavigation from '../lib/navigation.js';
|
||||||
import { bytesToHumanSize, shortDateFormat } from '../lib/strformat.js';
|
import { bytesToHumanSize, shortDateFormat } from '../lib/strformat.js';
|
||||||
|
|
||||||
// Run timer with this frequency (ms) for checking notification queue.
|
// Run timer with this frequency (ms) for checking notification queue.
|
||||||
const NOTIFICATION_EXEC_INTERVAL = 300;
|
const NOTIFICATION_EXEC_INTERVAL = 300;
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
online_now: {
|
'online_now': {
|
||||||
id: 'online_now',
|
id: 'online_now',
|
||||||
defaultMessage: 'online now',
|
defaultMessage: 'online now',
|
||||||
description: 'Indicator that the user or topic is currently online',
|
description: 'Indicator that the user or topic is currently online',
|
||||||
},
|
},
|
||||||
last_seen: {
|
'last_seen': {
|
||||||
id: 'last_seen_timestamp',
|
id: 'last_seen_timestamp',
|
||||||
defaultMessage: 'Last seen',
|
defaultMessage: 'Last seen',
|
||||||
description: 'Label for the timestamp of when the user or topic was last online'
|
description: 'Label for the timestamp of when the user or topic was last online'
|
||||||
@@ -39,6 +40,11 @@ const messages = defineMessages({
|
|||||||
id: 'title_not_found',
|
id: 'title_not_found',
|
||||||
defaultMessage: 'Not found',
|
defaultMessage: 'Not found',
|
||||||
description: 'Title shown when topic is not found'
|
description: 'Title shown when topic is not found'
|
||||||
|
},
|
||||||
|
'channel': {
|
||||||
|
id: 'channel',
|
||||||
|
defaultMessage: 'channel',
|
||||||
|
description: 'Subtitle shown for channels in MessagesView instead of last seen'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -152,9 +158,13 @@ class MessagesView extends React.Component {
|
|||||||
|
|
||||||
// Don't request the tags. They are useless unless the user
|
// Don't request the tags. They are useless unless the user
|
||||||
// is the owner and is editing the topic.
|
// is the owner and is editing the topic.
|
||||||
let getQuery = topic.startMetaQuery().withLaterDesc().withLaterSub();
|
let getQuery = topic.startMetaQuery().withLaterDesc();
|
||||||
|
if (this.state.isSharer || (newTopic && !topic.isChannel())) {
|
||||||
|
// Request subscriptions only if one of S,O,A is given or it's a new non-channel topic.
|
||||||
|
getQuery = getQuery.withLaterSub();
|
||||||
|
}
|
||||||
if (this.state.isReader || newTopic) {
|
if (this.state.isReader || newTopic) {
|
||||||
// If reading is either permitted or we don't know because it's a new topic. Ask for messages.
|
// Reading is either permitted or we don't know because it's a new topic. Ask for messages.
|
||||||
getQuery = getQuery.withLaterData(MESSAGES_PAGE);
|
getQuery = getQuery.withLaterData(MESSAGES_PAGE);
|
||||||
if (this.state.isReader) {
|
if (this.state.isReader) {
|
||||||
getQuery = getQuery.withLaterDel();
|
getQuery = getQuery.withLaterDel();
|
||||||
@@ -165,6 +175,11 @@ class MessagesView extends React.Component {
|
|||||||
const setQuery = newTopic ? this.props.newTopicParams : undefined;
|
const setQuery = newTopic ? this.props.newTopicParams : undefined;
|
||||||
topic.subscribe(getQuery.build(), setQuery)
|
topic.subscribe(getQuery.build(), setQuery)
|
||||||
.then((ctrl) => {
|
.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) {
|
if (this.state.topic != ctrl.topic) {
|
||||||
this.setState({topic: ctrl.topic});
|
this.setState({topic: ctrl.topic});
|
||||||
}
|
}
|
||||||
@@ -202,7 +217,8 @@ class MessagesView extends React.Component {
|
|||||||
typingIndicator: false,
|
typingIndicator: false,
|
||||||
scrollPosition: 0,
|
scrollPosition: 0,
|
||||||
fetchingMessages: false,
|
fetchingMessages: false,
|
||||||
peerMessagingDisabled: false
|
peerMessagingDisabled: false,
|
||||||
|
channel: false
|
||||||
};
|
};
|
||||||
} else if (nextProps.topic != prevState.topic) {
|
} else if (nextProps.topic != prevState.topic) {
|
||||||
const topic = nextProps.tinode.getTopic(nextProps.topic);
|
const topic = nextProps.tinode.getTopic(nextProps.topic);
|
||||||
@@ -262,6 +278,9 @@ class MessagesView extends React.Component {
|
|||||||
peerMessagingDisabled: false
|
peerMessagingDisabled: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Object.assign(nextState, {
|
||||||
|
channel: topic.isChannel()
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
// Invalid topic.
|
// Invalid topic.
|
||||||
Object.assign(nextState, {
|
Object.assign(nextState, {
|
||||||
@@ -269,7 +288,8 @@ class MessagesView extends React.Component {
|
|||||||
onlineSubs: [],
|
onlineSubs: [],
|
||||||
title: '',
|
title: '',
|
||||||
avatar: null,
|
avatar: null,
|
||||||
peerMessagingDisabled: false
|
peerMessagingDisabled: false,
|
||||||
|
channel: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -284,6 +304,9 @@ class MessagesView extends React.Component {
|
|||||||
if (!nextProps.acs.isReader('given') != prevState.readingBlocked) {
|
if (!nextProps.acs.isReader('given') != prevState.readingBlocked) {
|
||||||
nextState.readingBlocked = !prevState.readingBlocked;
|
nextState.readingBlocked = !prevState.readingBlocked;
|
||||||
}
|
}
|
||||||
|
if (nextProps.acs.isSharer() != prevState.isSharer) {
|
||||||
|
nextState.isSharer = !prevState.isSharer;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (prevState.isWriter) {
|
if (prevState.isWriter) {
|
||||||
nextState.isWriter = false;
|
nextState.isWriter = false;
|
||||||
@@ -294,6 +317,9 @@ class MessagesView extends React.Component {
|
|||||||
if (!prevState.readingBlocked) {
|
if (!prevState.readingBlocked) {
|
||||||
prevState.readingBlocked = true;
|
prevState.readingBlocked = true;
|
||||||
}
|
}
|
||||||
|
if (prevState.isSharer) {
|
||||||
|
nextState.isSharer = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isUnconfirmed(nextProps.acs) == !prevState.unconformed) {
|
if (isUnconfirmed(nextProps.acs) == !prevState.unconformed) {
|
||||||
@@ -569,9 +595,11 @@ class MessagesView extends React.Component {
|
|||||||
handleShowContextMenuMessage(params, messageSpecificMenuItems) {
|
handleShowContextMenuMessage(params, messageSpecificMenuItems) {
|
||||||
params.topicName = this.state.topic;
|
params.topicName = this.state.topic;
|
||||||
const menuItems = messageSpecificMenuItems || [];
|
const menuItems = messageSpecificMenuItems || [];
|
||||||
menuItems.push('message_delete');
|
|
||||||
const topic = this.props.tinode.getTopic(params.topicName);
|
const topic = this.props.tinode.getTopic(params.topicName);
|
||||||
if (topic) {
|
if (topic) {
|
||||||
|
if (!topic.isChannel()) {
|
||||||
|
menuItems.push('message_delete');
|
||||||
|
}
|
||||||
const acs = topic.getAccessMode();
|
const acs = topic.getAccessMode();
|
||||||
if (acs && acs.isDeleter()) {
|
if (acs && acs.isDeleter()) {
|
||||||
menuItems.push('message_delete_hard');
|
menuItems.push('message_delete_hard');
|
||||||
@@ -726,7 +754,8 @@ class MessagesView extends React.Component {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const topic = this.props.tinode.getTopic(this.state.topic);
|
const topic = this.props.tinode.getTopic(this.state.topic);
|
||||||
const groupTopic = topic.getType() == 'grp';
|
const isChannel = topic.isChannel();
|
||||||
|
const groupTopic = topic.getType() == 'grp' && !isChannel;
|
||||||
let messageNodes = [];
|
let messageNodes = [];
|
||||||
let previousFrom = null;
|
let previousFrom = null;
|
||||||
let chatBoxClass = null;
|
let chatBoxClass = null;
|
||||||
@@ -735,32 +764,33 @@ class MessagesView extends React.Component {
|
|||||||
let nextFrom = null;
|
let nextFrom = null;
|
||||||
|
|
||||||
if (i + 1 < this.state.messages.length) {
|
if (i + 1 < this.state.messages.length) {
|
||||||
nextFrom = this.state.messages[i+1].from
|
nextFrom = this.state.messages[i+1].from || 'chan';
|
||||||
}
|
}
|
||||||
|
|
||||||
let sequence = 'single';
|
let sequence = 'single';
|
||||||
if (msg.from == previousFrom) {
|
let thisFrom = msg.from || 'chan';
|
||||||
if (msg.from == nextFrom) {
|
if (thisFrom == previousFrom) {
|
||||||
|
if (thisFrom == nextFrom) {
|
||||||
sequence = 'middle';
|
sequence = 'middle';
|
||||||
} else {
|
} else {
|
||||||
sequence = 'last';
|
sequence = 'last';
|
||||||
}
|
}
|
||||||
} else if (msg.from == nextFrom) {
|
} else if (thisFrom == nextFrom) {
|
||||||
sequence = 'first';
|
sequence = 'first';
|
||||||
}
|
}
|
||||||
previousFrom = msg.from;
|
previousFrom = thisFrom;
|
||||||
|
|
||||||
const isReply = !(msg.from == this.props.myUserId);
|
const isReply = !(thisFrom == this.props.myUserId);
|
||||||
const deliveryStatus = topic.msgStatus(msg);
|
const deliveryStatus = topic.msgStatus(msg);
|
||||||
|
|
||||||
let userName, userAvatar, userFrom;
|
let userName, userAvatar, userFrom;
|
||||||
if (groupTopic) {
|
if (groupTopic) {
|
||||||
const user = topic.userDesc(msg.from);
|
const user = topic.userDesc(thisFrom);
|
||||||
if (user && user.public) {
|
if (user && user.public) {
|
||||||
userName = user.public.fn;
|
userName = user.public.fn;
|
||||||
userAvatar = makeImageUrl(user.public.photo);
|
userAvatar = makeImageUrl(user.public.photo);
|
||||||
}
|
}
|
||||||
userFrom = msg.from;
|
userFrom = thisFrom;
|
||||||
chatBoxClass='chat-box group';
|
chatBoxClass='chat-box group';
|
||||||
} else {
|
} else {
|
||||||
chatBoxClass='chat-box';
|
chatBoxClass='chat-box';
|
||||||
@@ -769,13 +799,20 @@ class MessagesView extends React.Component {
|
|||||||
messageNodes.push(
|
messageNodes.push(
|
||||||
<ChatMessage
|
<ChatMessage
|
||||||
tinode={this.props.tinode}
|
tinode={this.props.tinode}
|
||||||
content={msg.content} deleted={msg.hi}
|
content={msg.content}
|
||||||
|
deleted={msg.hi}
|
||||||
mimeType={msg.head ? msg.head.mime : null}
|
mimeType={msg.head ? msg.head.mime : null}
|
||||||
timestamp={msg.ts} response={isReply} seq={msg.seq}
|
timestamp={msg.ts}
|
||||||
userFrom={userFrom} userName={userName} userAvatar={userAvatar}
|
response={isReply}
|
||||||
sequence={sequence} received={deliveryStatus} uploader={msg._uploader}
|
seq={msg.seq}
|
||||||
|
userFrom={userFrom}
|
||||||
|
userName={userName}
|
||||||
|
userAvatar={userAvatar}
|
||||||
|
sequence={sequence}
|
||||||
|
received={deliveryStatus}
|
||||||
|
uploader={msg._uploader}
|
||||||
viewportWidth={this.props.viewportWidth}
|
viewportWidth={this.props.viewportWidth}
|
||||||
showContextMenu={this.handleShowContextMenuMessage}
|
showContextMenu={this.state.channel? false : this.handleShowContextMenuMessage}
|
||||||
onImagePreview={this.handleImagePostview}
|
onImagePreview={this.handleImagePostview}
|
||||||
onFormResponse={this.handleFormResponse}
|
onFormResponse={this.handleFormResponse}
|
||||||
onError={this.props.onError}
|
onError={this.props.onError}
|
||||||
@@ -784,14 +821,18 @@ class MessagesView extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let lastSeen = null;
|
let lastSeen = null;
|
||||||
const cont = this.props.tinode.getMeTopic().getContact(this.state.topic);
|
if (isChannel) {
|
||||||
if (cont && Tinode.topicType(cont.topic) == 'p2p') {
|
lastSeen = formatMessage(messages.channel);
|
||||||
if (cont.online) {
|
} else {
|
||||||
lastSeen = formatMessage(messages.online_now);
|
const cont = this.props.tinode.getMeTopic().getContact(this.state.topic);
|
||||||
} else if (cont.seen) {
|
if (cont && Tinode.isP2PTopicName(cont.topic)) {
|
||||||
lastSeen = formatMessage(messages.last_seen) + ": " +
|
if (cont.online) {
|
||||||
shortDateFormat(cont.seen.when, this.props.intl.locale);
|
lastSeen = formatMessage(messages.online_now);
|
||||||
// TODO: also handle user agent in c.seen.ua
|
} else if (cont.seen) {
|
||||||
|
lastSeen = formatMessage(messages.last_seen) + ": " +
|
||||||
|
shortDateFormat(cont.seen.when, this.props.intl.locale);
|
||||||
|
// TODO: also handle user agent in c.seen.ua
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const avatar = this.state.avatar || true;
|
const avatar = this.state.avatar || true;
|
||||||
@@ -811,7 +852,7 @@ class MessagesView extends React.Component {
|
|||||||
avatar={avatar}
|
avatar={avatar}
|
||||||
topic={this.state.topic}
|
topic={this.state.topic}
|
||||||
title={this.state.title} />
|
title={this.state.title} />
|
||||||
<span className={online} />
|
{!isChannel ? <span className={online} /> : null}
|
||||||
</div>
|
</div>
|
||||||
<div id="topic-title-group">
|
<div id="topic-title-group">
|
||||||
<div id="topic-title" className="panel-title">{
|
<div id="topic-title" className="panel-title">{
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ class NewTopicView extends React.Component {
|
|||||||
handleContactSelected(sel) {
|
handleContactSelected(sel) {
|
||||||
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, undefined);
|
this.props.onCreateTopic(sel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+18
-14
@@ -108,7 +108,7 @@ class TinodeWeb extends React.Component {
|
|||||||
this.togglePushToken = this.togglePushToken.bind(this);
|
this.togglePushToken = this.togglePushToken.bind(this);
|
||||||
this.requestPushToken = this.requestPushToken.bind(this);
|
this.requestPushToken = this.requestPushToken.bind(this);
|
||||||
this.handleSidepanelCancel = this.handleSidepanelCancel.bind(this);
|
this.handleSidepanelCancel = this.handleSidepanelCancel.bind(this);
|
||||||
this.handleNewTopicRequest = this.handleNewTopicRequest.bind(this);
|
this.handleStartTopicRequest = this.handleStartTopicRequest.bind(this);
|
||||||
this.handleNewTopicCreated = this.handleNewTopicCreated.bind(this);
|
this.handleNewTopicCreated = this.handleNewTopicCreated.bind(this);
|
||||||
this.handleTopicUpdateRequest = this.handleTopicUpdateRequest.bind(this);
|
this.handleTopicUpdateRequest = this.handleTopicUpdateRequest.bind(this);
|
||||||
this.handleChangePermissions = this.handleChangePermissions.bind(this);
|
this.handleChangePermissions = this.handleChangePermissions.bind(this);
|
||||||
@@ -634,7 +634,7 @@ class TinodeWeb extends React.Component {
|
|||||||
// For chatList topics merge only p2p topics and convert them to the
|
// For chatList topics merge only p2p topics and convert them to the
|
||||||
// same format as foundContacts.
|
// same format as foundContacts.
|
||||||
for (const c of chatList) {
|
for (const c of chatList) {
|
||||||
if (Tinode.topicType(c.topic) == 'p2p') {
|
if (Tinode.isP2PTopicName(c.topic)) {
|
||||||
merged[c.topic] = {
|
merged[c.topic] = {
|
||||||
user: c.topic,
|
user: c.topic,
|
||||||
updated: c.updated,
|
updated: c.updated,
|
||||||
@@ -726,7 +726,7 @@ class TinodeWeb extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// User clicked on a contact in the side panel or deleted a contact.
|
// User clicked on a contact in the side panel or deleted a contact.
|
||||||
handleTopicSelected(topicName, unused_index, online, acs) {
|
handleTopicSelected(topicName) {
|
||||||
// Clear newTopicParams after use.
|
// Clear newTopicParams after use.
|
||||||
if (this.state.newTopicParams && this.state.newTopicParams._topicName != topicName) {
|
if (this.state.newTopicParams && this.state.newTopicParams._topicName != topicName) {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -744,8 +744,8 @@ class TinodeWeb extends React.Component {
|
|||||||
// Different contact selected.
|
// Different contact selected.
|
||||||
if (this.state.topicSelected != topicName) {
|
if (this.state.topicSelected != topicName) {
|
||||||
this.setState({
|
this.setState({
|
||||||
topicSelectedOnline: online,
|
topicSelectedOnline: this.tinode.isTopicOnline(topicName),
|
||||||
topicSelectedAcs: acs
|
topicSelectedAcs: this.tinode.getTopicAccessMode(topicName)
|
||||||
});
|
});
|
||||||
HashNavigation.navigateTo(HashNavigation.setUrlTopic('', topicName));
|
HashNavigation.navigateTo(HashNavigation.setUrlTopic('', topicName));
|
||||||
}
|
}
|
||||||
@@ -813,7 +813,7 @@ class TinodeWeb extends React.Component {
|
|||||||
// Accept given permissions.
|
// Accept given permissions.
|
||||||
const mode = topic.getAccessMode().getGiven();
|
const mode = topic.getAccessMode().getGiven();
|
||||||
response = topic.setMeta({sub: {mode: mode}});
|
response = topic.setMeta({sub: {mode: mode}});
|
||||||
if (topic.getType() == 'p2p') {
|
if (topic.isP2P()) {
|
||||||
// For P2P topics change 'given' permission of the peer too.
|
// For P2P topics change 'given' permission of the peer too.
|
||||||
// In p2p topics the other user has the same name as the topic.
|
// In p2p topics the other user has the same name as the topic.
|
||||||
response = response.then((ctrl) => {
|
response = response.then((ctrl) => {
|
||||||
@@ -1060,22 +1060,26 @@ class TinodeWeb extends React.Component {
|
|||||||
HashNavigation.navigateTo(HashNavigation.setUrlSidePanel(window.location.hash, hash));
|
HashNavigation.navigateTo(HashNavigation.setUrlSidePanel(window.location.hash, hash));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request to start a new topic. New P2P topic requires peer's name.
|
// Request to start a topic, new or selected from search results.
|
||||||
handleNewTopicRequest(peerName, pub, priv, tags, isChannel) {
|
handleStartTopicRequest(topicName, pub, priv, tags, isChannel) {
|
||||||
|
// Check if topic is indeed new. If not, launch it.
|
||||||
|
if (topicName && this.tinode.getTopic(topicName)) {
|
||||||
|
this.handleTopicSelected(topicName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const topicName = peerName || this.tinode.newGroupTopicName(isChannel);
|
const params = {};
|
||||||
const params = {
|
if (Tinode.isP2PTopicName(topicName)) {
|
||||||
_topicName: topicName,
|
|
||||||
};
|
|
||||||
if (peerName) {
|
|
||||||
// Because we are initialing the subscription, set 'want' to all permissions.
|
// Because we are initialing the subscription, set 'want' to all permissions.
|
||||||
params.sub = {mode: DEFAULT_P2P_ACCESS_MODE};
|
params.sub = {mode: DEFAULT_P2P_ACCESS_MODE};
|
||||||
// Give the other user all permissions too.
|
// Give the other user all permissions too.
|
||||||
params.desc = {defacs: {auth: DEFAULT_P2P_ACCESS_MODE}};
|
params.desc = {defacs: {auth: DEFAULT_P2P_ACCESS_MODE}};
|
||||||
} else {
|
} else {
|
||||||
|
topicName = topicName || this.tinode.newGroupTopicName(isChannel);
|
||||||
params.desc = {public: pub, private: {comment: priv}};
|
params.desc = {public: pub, private: {comment: priv}};
|
||||||
params.tags = tags;
|
params.tags = tags;
|
||||||
}
|
}
|
||||||
|
params._topicName = topicName;
|
||||||
this.setState({newTopicParams: params}, () => {this.handleTopicSelected(topicName)});
|
this.setState({newTopicParams: params}, () => {this.handleTopicSelected(topicName)});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1458,7 +1462,7 @@ class TinodeWeb extends React.Component {
|
|||||||
onCredDelete={this.handleCredDelete}
|
onCredDelete={this.handleCredDelete}
|
||||||
onCredConfirm={this.handleCredConfirm}
|
onCredConfirm={this.handleCredConfirm}
|
||||||
onTopicSelected={this.handleTopicSelected}
|
onTopicSelected={this.handleTopicSelected}
|
||||||
onCreateTopic={this.handleNewTopicRequest}
|
onCreateTopic={this.handleStartTopicRequest}
|
||||||
onLogout={this.handleLogout}
|
onLogout={this.handleLogout}
|
||||||
onDeleteAccount={this.handleDeleteAccount}
|
onDeleteAccount={this.handleDeleteAccount}
|
||||||
onShowAlert={this.handleShowAlert}
|
onShowAlert={this.handleShowAlert}
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ export default class ChatMessage extends React.Component {
|
|||||||
received={this.props.received} />
|
received={this.props.received} />
|
||||||
: null}
|
: null}
|
||||||
</div>
|
</div>
|
||||||
{this.props.deleted ?
|
{this.props.deleted || !this.props.showContextMenu ?
|
||||||
null :
|
null :
|
||||||
<span className="menuTrigger">
|
<span className="menuTrigger">
|
||||||
<a href="#" onClick={this.handleContextClick}>
|
<a href="#" onClick={this.handleContextClick}>
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import Tinode from 'tinode-sdk';
|
||||||
|
|
||||||
import Contact from './contact.jsx';
|
import Contact from './contact.jsx';
|
||||||
import ContactAction from './contact-action.jsx';
|
import ContactAction from './contact-action.jsx';
|
||||||
|
|
||||||
@@ -55,6 +57,7 @@ class ContactList extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isChannel = Tinode.isChannelTopicName(key);
|
||||||
const selected = showCheckmark ?
|
const selected = showCheckmark ?
|
||||||
(this.props.topicSelected.indexOf(key) > -1) :
|
(this.props.topicSelected.indexOf(key) > -1) :
|
||||||
(this.props.topicSelected === key);
|
(this.props.topicSelected === key);
|
||||||
@@ -82,7 +85,7 @@ class ContactList extends React.Component {
|
|||||||
badges={badges}
|
badges={badges}
|
||||||
showCheckmark={showCheckmark}
|
showCheckmark={showCheckmark}
|
||||||
selected={selected}
|
selected={selected}
|
||||||
showOnline={this.props.showOnline}
|
showOnline={this.props.showOnline && !isChannel}
|
||||||
onSelected={this.props.onTopicSelected}
|
onSelected={this.props.onTopicSelected}
|
||||||
showContextMenu={this.props.showContextMenu}
|
showContextMenu={this.props.showContextMenu}
|
||||||
item={key}
|
item={key}
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ export default class Contact extends React.Component {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (this.props.onSelected) {
|
if (this.props.onSelected) {
|
||||||
this.props.onSelected(this.props.item, this.props.index, this.props.now, this.props.acs);
|
// Index this.props.index is used by GroupManager.
|
||||||
|
this.props.onSelected(this.props.item, this.props.index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+141
-78
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
+177
-47
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