Compare commits
22
Commits
e05e75ed28
...
6f47cc50b0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f47cc50b0 | ||
|
|
1172adefb4 | ||
|
|
4f38a2d8b1 | ||
|
|
d75448c548 | ||
|
|
9c69b7129c | ||
|
|
4ea82f85ba | ||
|
|
3132294cce | ||
|
|
6f278f5bb5 | ||
|
|
92e3f15c01 | ||
|
|
1f5a76e481 | ||
|
|
4445a90437 | ||
|
|
0afa8bcdcd | ||
|
|
55d7e0d5cb | ||
|
|
4b07c3b5c9 | ||
|
|
a319e2b0bc | ||
|
|
924459f84b | ||
|
|
396b23ee88 | ||
|
|
775bc954e3 | ||
|
|
6fabb6c111 | ||
|
|
e328e6a4c2 | ||
|
|
443245280f | ||
|
|
a248151243 |
@@ -2,8 +2,12 @@
|
|||||||
"presets": ["@babel/preset-react", "@babel/preset-env"],
|
"presets": ["@babel/preset-react", "@babel/preset-env"],
|
||||||
"comments": false,
|
"comments": false,
|
||||||
"plugins": [
|
"plugins": [
|
||||||
["react-intl", {
|
[
|
||||||
"messagesDir": "./src/i18n/"
|
"formatjs",
|
||||||
}]
|
{
|
||||||
|
"idInterpolationPattern": "[sha512:contenthash:base64:6]",
|
||||||
|
"ast": true
|
||||||
|
}
|
||||||
|
]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
+35
-9
@@ -42,7 +42,7 @@ a.gray:focus {
|
|||||||
color: #999;
|
color: #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.flat-button {
|
.flat-button {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin: 0.15rem 0.35rem;
|
margin: 0.15rem 0.35rem;
|
||||||
@@ -55,11 +55,21 @@ a.flat-button {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
a.flat-button:hover,
|
.flat-button:hover,
|
||||||
a.flat-button:focus {
|
.flat-button:focus {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flat-button .faux {
|
||||||
|
height: 1.5em;
|
||||||
|
line-height: 1.5em;
|
||||||
|
text-transform: none;
|
||||||
|
padding-left: .5rem;
|
||||||
|
padding-right: .5rem;
|
||||||
|
border: 1px solid gray;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
form {
|
form {
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
padding: 0.5rem 0.75rem;
|
padding: 0.5rem 0.75rem;
|
||||||
@@ -541,6 +551,11 @@ div.alert .content {
|
|||||||
margin-left: 1rem;
|
margin-left: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Extra spece on the right */
|
||||||
|
.space-right {
|
||||||
|
margin-right: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
/* Container with whitish background and rounded corners */
|
/* Container with whitish background and rounded corners */
|
||||||
.rounded-container {
|
.rounded-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -825,7 +840,7 @@ i.search {
|
|||||||
display: flex;
|
display: flex;
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 0.35rem 0.25rem 0.5rem 0.35rem;
|
padding: 0.35rem 0.25rem 0.5rem 0.35rem;
|
||||||
min-height: 3.5rem;
|
min-height: 3.75rem;
|
||||||
flex-grow: 0;
|
flex-grow: 0;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
/* partial bottom border */
|
/* partial bottom border */
|
||||||
@@ -1075,11 +1090,11 @@ i.search {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.avatar-upload .inputfile.hidden {
|
.avatar-upload .inputfile.hidden {
|
||||||
width: 0.1px;
|
width: 0.1px;
|
||||||
height: 0.1px;
|
height: 0.1px;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar-upload .inputfile + label.round {
|
.avatar-upload .inputfile + label.round {
|
||||||
@@ -2066,3 +2081,14 @@ ul.tabbar li.active > a:after {
|
|||||||
background-color: #1e88e5;
|
background-color: #1e88e5;
|
||||||
border-radius: 0.08rem;
|
border-radius: 0.08rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Highlighted text */
|
||||||
|
.highlight {
|
||||||
|
color: teal;
|
||||||
|
font-weight: bolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .preview {
|
||||||
|
color: #5f9ea0;
|
||||||
|
font-weight: inherit;
|
||||||
|
}
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+5
-5
@@ -29,13 +29,13 @@
|
|||||||
<!-- Styles for local elements -->
|
<!-- Styles for local elements -->
|
||||||
<link rel="stylesheet" href="css/base.css" />
|
<link rel="stylesheet" href="css/base.css" />
|
||||||
<!-- ReactJS -->
|
<!-- ReactJS -->
|
||||||
<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@17/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@17/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@4/dist/react-intl.js"></script>
|
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/react-intl@5/react-intl.umd.js"></script>
|
||||||
<!-- Firebase for push notifications -->
|
<!-- Firebase for push notifications -->
|
||||||
<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/firebase-app.js"></script>
|
||||||
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/firebase@7.20.0/firebase-messaging.js"></script>
|
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/firebase@7/firebase-messaging.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
+5
-5
@@ -22,11 +22,11 @@
|
|||||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,400i,500,700,700i" />
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,400i,500,700,700i" />
|
||||||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
|
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
|
||||||
<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@17.0.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@17.0.1/umd/react-dom.production.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/react-intl@5.10.14/react-intl.umd.min.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.24.0/firebase-app.js"></script>
|
||||||
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/firebase@7.20.0/firebase-messaging.js"></script>
|
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/firebase@7.24.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
+2050
-3578
File diff suppressed because it is too large
Load Diff
+17
-29
@@ -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.9-rc1",
|
"version": "0.17.0",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/tinode/example-react-js.git"
|
"url": "https://github.com/tinode/example-react-js.git"
|
||||||
@@ -31,25 +31,14 @@
|
|||||||
"audio/msg.mp3",
|
"audio/msg.mp3",
|
||||||
"css/base.css",
|
"css/base.css",
|
||||||
"css/base.min.css",
|
"css/base.min.css",
|
||||||
"img/bg_messages.png",
|
"img/*.png",
|
||||||
"img/logo.svg",
|
"img/*.svg",
|
||||||
"img/logo32x32.png",
|
"img/*.jpeg",
|
||||||
"img/logo32x32a.png",
|
|
||||||
"img/logo96.png",
|
|
||||||
"img/logo192.png",
|
|
||||||
"img/badge96.png",
|
|
||||||
"img/broken_image.png",
|
|
||||||
"img/og-logo.jpeg",
|
|
||||||
"index-dev.html",
|
"index-dev.html",
|
||||||
"index.html",
|
"index.html",
|
||||||
"manifest.json",
|
"manifest.json",
|
||||||
"service-worker.js",
|
"service-worker.js",
|
||||||
"firebase-init.js",
|
"firebase-init.js"
|
||||||
"web-desktop-2.png",
|
|
||||||
"web-mob-chat-1.png",
|
|
||||||
"web-mob-contacts-1.png",
|
|
||||||
"web-mob-info-1.png",
|
|
||||||
"web-mob-new-chat-1.png"
|
|
||||||
],
|
],
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"chat",
|
"chat",
|
||||||
@@ -69,24 +58,23 @@
|
|||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"firebase": "^7.24.0",
|
"firebase": "^7.24.0",
|
||||||
"react": "^16.14.0",
|
"react": "^17.0.1",
|
||||||
"react-dom": "^16.14.0",
|
"react-dom": "^17.0.1",
|
||||||
"react-intl": "^4.7.6",
|
"react-intl": "^5.10.14",
|
||||||
"tinode-sdk": "^0.16.9-rc2"
|
"tinode-sdk": "^0.16.10"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.12.10",
|
"@babel/core": "^7.12.10",
|
||||||
"@babel/preset-env": "^7.12.10",
|
"@babel/preset-env": "^7.12.11",
|
||||||
"@babel/preset-react": "^7.12.10",
|
"@babel/preset-react": "^7.12.10",
|
||||||
"babel-loader": "^8.2.2",
|
"babel-loader": "^8.2.2",
|
||||||
"babel-plugin-react-intl": "^7.9.4",
|
"babel-plugin-formatjs": "^9.0.2",
|
||||||
"browserslist": "^4.16.0",
|
"browserslist": "^4.16.1",
|
||||||
"copy-webpack-plugin": "^6.4.0",
|
"copy-webpack-plugin": "^6.4.1",
|
||||||
"cssnano": "^4.1.10",
|
"cssnano": "^4.1.10",
|
||||||
"postcss": "^7.0.35",
|
"postcss": "^8.2.4",
|
||||||
"postcss-cli": "^7.1.2",
|
"postcss-cli": "^8.3.1",
|
||||||
"terser-webpack-plugin": "^3.1.0",
|
"webpack": "^5.13.0",
|
||||||
"webpack": "^4.44.2",
|
"webpack-cli": "^4.3.1"
|
||||||
"webpack-cli": "^3.3.12"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,6 +81,9 @@ export const MAX_ONLINE_IN_TOPIC = 4;
|
|||||||
// Maximum length of user name, topic title, and private comment.
|
// Maximum length of user name, topic title, and private comment.
|
||||||
export const MAX_TITLE_LENGTH = 60;
|
export const MAX_TITLE_LENGTH = 60;
|
||||||
|
|
||||||
|
// Length of message previews in chat list, in characters.
|
||||||
|
export const MESSAGE_PREVIEW_LENGTH = 80;
|
||||||
|
|
||||||
// Link for "Contact Us".
|
// Link for "Contact Us".
|
||||||
export const LINK_CONTACT_US = 'email:info@tinode.co';
|
export const LINK_CONTACT_US = 'email:info@tinode.co';
|
||||||
|
|
||||||
|
|||||||
@@ -3,15 +3,17 @@ export function shortDateFormat(then, locale) {
|
|||||||
locale = locale || window.navigator.userLanguage || window.navigator.language;
|
locale = locale || window.navigator.userLanguage || window.navigator.language;
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
if (then.getFullYear() == now.getFullYear()) {
|
if (then.getFullYear() == now.getFullYear()) {
|
||||||
|
// Same year.
|
||||||
if (then.getMonth() == now.getMonth() && then.getDate() == now.getDate()) {
|
if (then.getMonth() == now.getMonth() && then.getDate() == now.getDate()) {
|
||||||
return then.toLocaleTimeString(locale, {hour12: false, hour: '2-digit', minute: '2-digit'});
|
// Same month and day, show time only.
|
||||||
} else {
|
return then.toLocaleTimeString(locale, {hour12: false, hour: '2-digit', minute: '2-digit'});
|
||||||
return then.toLocaleDateString(locale,
|
|
||||||
{hour12: false, month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit'});
|
|
||||||
}
|
}
|
||||||
|
// Different month and/or day, show month day, time.
|
||||||
|
return then.toLocaleDateString(locale,
|
||||||
|
{hour12: false, month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit'});
|
||||||
}
|
}
|
||||||
return then.toLocaleDateString(locale,
|
// Different year: just show the date.
|
||||||
{hour12: false, year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit'});
|
return then.toLocaleDateString(locale, {year: 'numeric', month: 'short', day: 'numeric'});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert seconds to minutes:seconds, i.e. 156 sec -> 2:36.
|
// Convert seconds to minutes:seconds, i.e. 156 sec -> 2:36.
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
// Odds and ends
|
// Odds and ends
|
||||||
|
|
||||||
|
import Tinode from 'tinode-sdk';
|
||||||
|
|
||||||
// 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.
|
||||||
export function updateFavicon(count) {
|
export function updateFavicon(count) {
|
||||||
const oldIcon = document.getElementById('shortcut-icon');
|
const oldIcon = document.getElementById('shortcut-icon');
|
||||||
@@ -144,3 +146,20 @@ export function sanitizeImageUrl(url) {
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Given message's received status, return name and color of a delivery indicator icon.
|
||||||
|
export function deliveryMarker(received) {
|
||||||
|
switch (received) {
|
||||||
|
case Tinode.MESSAGE_STATUS_SENDING:
|
||||||
|
return { name: 'access_time' }; // watch face
|
||||||
|
case Tinode.MESSAGE_STATUS_FAILED:
|
||||||
|
return { name: 'warning', color: 'amber' }; // yellow icon /!\
|
||||||
|
case Tinode.MESSAGE_STATUS_SENT:
|
||||||
|
return { name: 'done' }; // checkmark
|
||||||
|
case Tinode.MESSAGE_STATUS_RECEIVED:
|
||||||
|
return { name: 'done_all' }; // double checkmark
|
||||||
|
case Tinode.MESSAGE_STATUS_READ:
|
||||||
|
return { name: 'done_all', color: 'blue' }; // blue double checkmark
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|||||||
+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.9-rc1";
|
export const PACKAGE_VERSION = "0.17.0";
|
||||||
|
|||||||
@@ -230,8 +230,8 @@ class InfoView extends React.Component {
|
|||||||
avatar: makeImageUrl(topic.public ? topic.public.photo : null),
|
avatar: makeImageUrl(topic.public ? topic.public.photo : null),
|
||||||
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.isGroupType(),
|
||||||
channel: topic.isChannel(),
|
channel: topic.isChannelType(),
|
||||||
showMemberPanel: false,
|
showMemberPanel: false,
|
||||||
access: acs ? acs.getMode() : undefined,
|
access: acs ? acs.getMode() : undefined,
|
||||||
modeGiven: acs ? acs.getGiven() : undefined,
|
modeGiven: acs ? acs.getGiven() : undefined,
|
||||||
|
|||||||
@@ -159,11 +159,7 @@ 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();
|
let getQuery = topic.startMetaQuery().withLaterDesc().withLaterSub();
|
||||||
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) {
|
||||||
// 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);
|
||||||
@@ -280,7 +276,7 @@ class MessagesView extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
Object.assign(nextState, {
|
Object.assign(nextState, {
|
||||||
channel: topic.isChannel()
|
channel: topic.isChannelType()
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Invalid topic.
|
// Invalid topic.
|
||||||
@@ -601,7 +597,7 @@ class MessagesView extends React.Component {
|
|||||||
const menuItems = messageSpecificMenuItems || [];
|
const menuItems = messageSpecificMenuItems || [];
|
||||||
const topic = this.props.tinode.getTopic(params.topicName);
|
const topic = this.props.tinode.getTopic(params.topicName);
|
||||||
if (topic) {
|
if (topic) {
|
||||||
if (!topic.isChannel()) {
|
if (!topic.isChannelType()) {
|
||||||
menuItems.push('message_delete');
|
menuItems.push('message_delete');
|
||||||
}
|
}
|
||||||
const acs = topic.getAccessMode();
|
const acs = topic.getAccessMode();
|
||||||
@@ -821,8 +817,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 isChannel = topic.isChannel();
|
const isChannel = topic.isChannelType();
|
||||||
const groupTopic = topic.getType() == 'grp' && !isChannel;
|
const groupTopic = topic.isGroupType() && !isChannel;
|
||||||
let messageNodes = [];
|
let messageNodes = [];
|
||||||
let previousFrom = null;
|
let previousFrom = null;
|
||||||
let chatBoxClass = null;
|
let chatBoxClass = null;
|
||||||
|
|||||||
+70
-61
@@ -212,66 +212,69 @@ class TinodeWeb extends React.Component {
|
|||||||
viewportHeight: document.documentElement.clientHeight
|
viewportHeight: document.documentElement.clientHeight
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const keepLoggedIn = LocalStorageUtil.getObject('keep-logged-in');
|
||||||
|
|
||||||
const {formatMessage, locale} = this.props.intl;
|
const {formatMessage, locale} = this.props.intl;
|
||||||
this.tinode = TinodeWeb.tnSetup(this.state.serverAddress, this.state.transport, locale);
|
new Promise((resolve, reject) => {
|
||||||
this.tinode.onConnect = this.handleConnected;
|
this.tinode = TinodeWeb.tnSetup(this.state.serverAddress, this.state.transport, locale, keepLoggedIn, resolve);
|
||||||
this.tinode.onDisconnect = this.handleDisconnect;
|
this.tinode.onConnect = this.handleConnected;
|
||||||
this.tinode.onAutoreconnectIteration = this.handleAutoreconnectIteration;
|
this.tinode.onDisconnect = this.handleDisconnect;
|
||||||
|
this.tinode.onAutoreconnectIteration = this.handleAutoreconnectIteration;
|
||||||
// Initialize desktop alerts.
|
}).then(() => {
|
||||||
if (this.state.desktopAlertsEnabled) {
|
// Initialize desktop alerts.
|
||||||
try {
|
if (this.state.desktopAlertsEnabled) {
|
||||||
this.fbPush = firebase.initializeApp(FIREBASE_INIT, APP_NAME).messaging();
|
try {
|
||||||
this.fbPush.usePublicVapidKey(FIREBASE_INIT.messagingVapidKey);
|
this.fbPush = firebase.initializeApp(FIREBASE_INIT, APP_NAME).messaging();
|
||||||
navigator.serviceWorker.register('/service-worker.js').then((reg) => {
|
this.fbPush.usePublicVapidKey(FIREBASE_INIT.messagingVapidKey);
|
||||||
this.checkForAppUpdate(reg);
|
navigator.serviceWorker.register('/service-worker.js').then((reg) => {
|
||||||
this.fbPush.useServiceWorker(reg);
|
this.checkForAppUpdate(reg);
|
||||||
reg.active.postMessage(JSON.stringify({locale: locale}));
|
this.fbPush.useServiceWorker(reg);
|
||||||
this.initDesktopAlerts();
|
reg.active.postMessage(JSON.stringify({locale: locale}));
|
||||||
if (this.state.desktopAlerts) {
|
this.initDesktopAlerts();
|
||||||
if (!this.state.firebaseToken) {
|
if (this.state.desktopAlerts) {
|
||||||
this.togglePushToken(true);
|
if (!this.state.firebaseToken) {
|
||||||
} else {
|
this.togglePushToken(true);
|
||||||
this.tinode.setDeviceToken(this.state.firebaseToken, true);
|
} else {
|
||||||
|
this.tinode.setDeviceToken(this.state.firebaseToken, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}).catch((err) => {
|
||||||
}).catch((err) => {
|
// registration failed :(
|
||||||
// registration failed :(
|
console.log("Failed to register service worker:", err);
|
||||||
console.log("Failed to register service worker:", err);
|
});
|
||||||
});
|
} catch (err) {
|
||||||
} catch (err) {
|
this.handleError(formatMessage(messages.push_init_failed), 'err');
|
||||||
this.handleError(formatMessage({id: 'push_init_failed'}), 'err');
|
console.log("Failed to initialize push notifications", err);
|
||||||
console.log("Failed to initialize push notifications", err);
|
this.setState({desktopAlertsEnabled: false});
|
||||||
this.setState({desktopAlertsEnabled: false});
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const token = LocalStorageUtil.getObject('keep-logged-in') ?
|
const token = keepLoggedIn ? LocalStorageUtil.getObject('auth-token') : undefined;
|
||||||
LocalStorageUtil.getObject('auth-token') : undefined;
|
|
||||||
|
|
||||||
const parsedNav = HashNavigation.parseUrlHash(window.location.hash);
|
const parsedNav = HashNavigation.parseUrlHash(window.location.hash);
|
||||||
if (token) {
|
if (token) {
|
||||||
this.setState({autoLogin: true});
|
this.setState({autoLogin: true});
|
||||||
|
|
||||||
// When reading from storage, date is returned as string.
|
// When reading from storage, date is returned as string.
|
||||||
token.expires = new Date(token.expires);
|
token.expires = new Date(token.expires);
|
||||||
this.tinode.setAuthToken(token);
|
this.tinode.setAuthToken(token);
|
||||||
this.tinode.connect().catch((err) => {
|
this.tinode.connect().catch((err) => {
|
||||||
// Socket error
|
// Socket error
|
||||||
this.handleError(err.message, 'err');
|
this.handleError(err.message, 'err');
|
||||||
});
|
});
|
||||||
delete parsedNav.params.info;
|
delete parsedNav.params.info;
|
||||||
delete parsedNav.params.tab;
|
delete parsedNav.params.tab;
|
||||||
parsedNav.path[0] = '';
|
parsedNav.path[0] = '';
|
||||||
HashNavigation.navigateTo(HashNavigation.composeUrlHash(parsedNav.path, parsedNav.params));
|
HashNavigation.navigateTo(HashNavigation.composeUrlHash(parsedNav.path, parsedNav.params));
|
||||||
} else if (!parsedNav.params.token) {
|
} else if (!parsedNav.params.token) {
|
||||||
HashNavigation.navigateTo('');
|
HashNavigation.navigateTo('');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.readTimer = null;
|
this.readTimer = null;
|
||||||
this.readTimerCallback = null;
|
this.readTimerCallback = null;
|
||||||
|
|
||||||
this.handleHashRoute();
|
this.handleHashRoute();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
@@ -281,8 +284,9 @@ class TinodeWeb extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Setup transport (usually websocket) and server address. This will terminate connection with the server.
|
// Setup transport (usually websocket) and server address. This will terminate connection with the server.
|
||||||
static tnSetup(serverAddress, transport, locale) {
|
static tnSetup(serverAddress, transport, locale, persistentCache, onSetupCompleted) {
|
||||||
const tinode = new Tinode(APP_NAME, serverAddress, API_KEY, transport, isSecureConnection());
|
const tinode = new Tinode({appName: APP_NAME, host: serverAddress, apiKey: API_KEY, transport: transport,
|
||||||
|
secure: isSecureConnection(), persist: persistentCache}, onSetupCompleted);
|
||||||
tinode.setHumanLanguage(locale);
|
tinode.setHumanLanguage(locale);
|
||||||
tinode.enableLogging(LOGGING_ENABLED, true);
|
tinode.enableLogging(LOGGING_ENABLED, true);
|
||||||
return tinode;
|
return tinode;
|
||||||
@@ -361,7 +365,7 @@ class TinodeWeb extends React.Component {
|
|||||||
if (online) {
|
if (online) {
|
||||||
this.handleError();
|
this.handleError();
|
||||||
} else {
|
} else {
|
||||||
this.handleError(this.props.intl.formatMessage({id: 'no_connection'}), 'warn');
|
this.handleError(this.props.intl.formatMessage(messages.no_connection), 'warn');
|
||||||
}
|
}
|
||||||
this.setState({liveConnection: online});
|
this.setState({liveConnection: online});
|
||||||
}
|
}
|
||||||
@@ -484,7 +488,7 @@ class TinodeWeb extends React.Component {
|
|||||||
if (ctrl.code >= 300 && ctrl.text === 'validate credentials') {
|
if (ctrl.code >= 300 && ctrl.text === 'validate credentials') {
|
||||||
this.setState({loadSpinnerVisible: false});
|
this.setState({loadSpinnerVisible: false});
|
||||||
if (cred) {
|
if (cred) {
|
||||||
this.handleError(this.props.intl.formatMessage({id: 'code_doesnot_match'}), 'warn');
|
this.handleError(this.props.intl.formatMessage(messages.code_doesnot_match), 'warn');
|
||||||
}
|
}
|
||||||
this.handleCredentialsRequest(ctrl.params);
|
this.handleCredentialsRequest(ctrl.params);
|
||||||
} else {
|
} else {
|
||||||
@@ -627,7 +631,7 @@ class TinodeWeb extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Merge search results and contact list to create a single flat
|
// Merge search results and contact list to create a single flat
|
||||||
// list of kown contacts for GroupManager to use.
|
// list of known contacts for GroupManager to use.
|
||||||
static prepareSearchableContacts(chatList, foundContacts) {
|
static prepareSearchableContacts(chatList, foundContacts) {
|
||||||
const merged = {};
|
const merged = {};
|
||||||
|
|
||||||
@@ -816,7 +820,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.isP2P()) {
|
if (topic.isP2PType()) {
|
||||||
// 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) => {
|
||||||
@@ -924,10 +928,12 @@ class TinodeWeb extends React.Component {
|
|||||||
const serverAddress = settings.serverAddress || this.state.serverAddress;
|
const serverAddress = settings.serverAddress || this.state.serverAddress;
|
||||||
const transport = settings.transport || this.state.transport;
|
const transport = settings.transport || this.state.transport;
|
||||||
if (this.tinode) {
|
if (this.tinode) {
|
||||||
|
this.tinode.clearStorage();
|
||||||
this.tinode.onDisconnect = undefined;
|
this.tinode.onDisconnect = undefined;
|
||||||
this.tinode.disconnect();
|
this.tinode.disconnect();
|
||||||
}
|
}
|
||||||
this.tinode = TinodeWeb.tnSetup(serverAddress, transport, this.props.intl.locale);
|
this.tinode = TinodeWeb.tnSetup(serverAddress, transport, this.props.intl.locale,
|
||||||
|
LocalStorageUtil.getObject('keep-logged-in'));
|
||||||
this.tinode.onConnect = this.handleConnected;
|
this.tinode.onConnect = this.handleConnected;
|
||||||
this.tinode.onDisconnect = this.handleDisconnect;
|
this.tinode.onDisconnect = this.handleDisconnect;
|
||||||
|
|
||||||
@@ -1156,11 +1162,14 @@ class TinodeWeb extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.tinode) {
|
if (this.tinode) {
|
||||||
|
this.tinode.clearStorage();
|
||||||
this.tinode.onDisconnect = undefined;
|
this.tinode.onDisconnect = undefined;
|
||||||
this.tinode.disconnect();
|
this.tinode.disconnect();
|
||||||
}
|
}
|
||||||
this.setState(this.getBlankState());
|
this.setState(this.getBlankState());
|
||||||
this.tinode = TinodeWeb.tnSetup(this.state.serverAddress, this.state.transport, this.props.intl.locale);
|
|
||||||
|
this.tinode = TinodeWeb.tnSetup(this.state.serverAddress,
|
||||||
|
this.state.transport, this.props.intl.locale, LocalStorageUtil.getObject('keep-logged-in'));
|
||||||
this.tinode.onConnect = this.handleConnected;
|
this.tinode.onConnect = this.handleConnected;
|
||||||
this.tinode.onDisconnect = this.handleDisconnect;
|
this.tinode.onDisconnect = this.handleDisconnect;
|
||||||
HashNavigation.navigateTo('');
|
HashNavigation.navigateTo('');
|
||||||
@@ -1375,7 +1384,7 @@ class TinodeWeb extends React.Component {
|
|||||||
handleResetPassword(scheme, newPassword, token) {
|
handleResetPassword(scheme, newPassword, token) {
|
||||||
token = base64ReEncode(token);
|
token = base64ReEncode(token);
|
||||||
if (!token) {
|
if (!token) {
|
||||||
this.handleError(this.props.intl.formatMessage({id: 'invalid_security_token'}), 'err');
|
this.handleError(this.props.intl.formatMessage(messages.invalid_security_token), 'err');
|
||||||
} else {
|
} else {
|
||||||
this.tinode.connect()
|
this.tinode.connect()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Single message, sent or received.
|
// Single message, sent or received.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import { Drafty } from 'tinode-sdk'
|
import { Drafty } from 'tinode-sdk';
|
||||||
|
|
||||||
import Attachment from './attachment.jsx';
|
import Attachment from './attachment.jsx';
|
||||||
import LetterTile from './letter-tile.jsx';
|
import LetterTile from './letter-tile.jsx';
|
||||||
@@ -165,12 +165,23 @@ export default class ChatMessage extends React.Component {
|
|||||||
import { BROKEN_IMAGE_SIZE, REM_SIZE } from '../config.js';
|
import { BROKEN_IMAGE_SIZE, REM_SIZE } from '../config.js';
|
||||||
import { fitImageSize } from '../lib/blob-helpers.js';
|
import { fitImageSize } from '../lib/blob-helpers.js';
|
||||||
|
|
||||||
|
// Converts Drafty elements into React classes.
|
||||||
|
// 'this' is set by the caller.
|
||||||
function draftyFormatter(style, data, values, key) {
|
function draftyFormatter(style, data, values, key) {
|
||||||
|
if (style == 'EX') {
|
||||||
|
// attachments are handled elsewhere.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
let el = Drafty.tagName(style);
|
let el = Drafty.tagName(style);
|
||||||
if (el) {
|
if (el) {
|
||||||
const attr = Drafty.attrValue(style, data) || {};
|
const attr = Drafty.attrValue(style, data) || {};
|
||||||
attr.key = key;
|
attr.key = key;
|
||||||
switch (style) {
|
switch (style) {
|
||||||
|
case 'HL':
|
||||||
|
// Highlighted text. Assign class name.
|
||||||
|
attr.className = 'highlight';
|
||||||
|
break;
|
||||||
case 'IM':
|
case 'IM':
|
||||||
// Additional processing for images
|
// Additional processing for images
|
||||||
if (data) {
|
if (data) {
|
||||||
@@ -210,9 +221,16 @@ function draftyFormatter(style, data, values, key) {
|
|||||||
// Form
|
// Form
|
||||||
attr.className = 'bot-form';
|
attr.className = 'bot-form';
|
||||||
break;
|
break;
|
||||||
case 'FE':
|
case 'RW':
|
||||||
// Form element formatting is dependent on element content.
|
// Form element formatting is dependent on element content.
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
if (el == '_UNKN') {
|
||||||
|
// Unknown element.
|
||||||
|
// TODO: make it prettier.
|
||||||
|
el = <><span className="material-icons">extension</span></>;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return React.createElement(el, attr, values);
|
return React.createElement(el, attr, values);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import { Drafty } from 'tinode-sdk';
|
||||||
import Tinode from 'tinode-sdk';
|
import Tinode from 'tinode-sdk';
|
||||||
|
|
||||||
import Contact from './contact.jsx';
|
import Contact from './contact.jsx';
|
||||||
@@ -10,6 +11,8 @@ import ContactAction from './contact-action.jsx';
|
|||||||
|
|
||||||
import { makeImageUrl } from '../lib/blob-helpers.js';
|
import { makeImageUrl } from '../lib/blob-helpers.js';
|
||||||
|
|
||||||
|
import { MESSAGE_PREVIEW_LENGTH } from '../config.js';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
badge_you: {
|
badge_you: {
|
||||||
id: 'badge_you',
|
id: 'badge_you',
|
||||||
@@ -25,7 +28,7 @@ const messages = defineMessages({
|
|||||||
|
|
||||||
class ContactList extends React.Component {
|
class ContactList extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
const {formatMessage} = this.props.intl;
|
const { formatMessage } = this.props.intl;
|
||||||
const showCheckmark = Array.isArray(this.props.topicSelected);
|
const showCheckmark = Array.isArray(this.props.topicSelected);
|
||||||
const contactNodes = [];
|
const contactNodes = [];
|
||||||
let contactsCount = 0;
|
let contactsCount = 0;
|
||||||
@@ -70,14 +73,27 @@ class ContactList extends React.Component {
|
|||||||
badges.push({name: formatMessage(messages.badge_owner), color: 'blue'});
|
badges.push({name: formatMessage(messages.badge_owner), color: 'blue'});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const comment = Array.isArray(c.private) ?
|
const comment = Array.isArray(c.private) ?
|
||||||
c.private.join(',') : (c.private ? c.private.comment : null);
|
c.private.join(',') : (c.private ? c.private.comment : null);
|
||||||
|
let preview;
|
||||||
|
let deliveryStatus;
|
||||||
|
if (!this.props.showMode) {
|
||||||
|
const msg = c.latestMessage();
|
||||||
|
if (msg) {
|
||||||
|
deliveryStatus = c.msgStatus(msg);
|
||||||
|
preview = typeof msg.content == 'string' ?
|
||||||
|
msg.content.substr(0, MESSAGE_PREVIEW_LENGTH) :
|
||||||
|
Drafty.preview(msg.content, MESSAGE_PREVIEW_LENGTH);
|
||||||
|
}
|
||||||
|
}
|
||||||
contactNodes.push(
|
contactNodes.push(
|
||||||
<Contact
|
<Contact
|
||||||
title={c.public ? c.public.fn : null}
|
title={c.public ? c.public.fn : null}
|
||||||
avatar={makeImageUrl(c.public ? c.public.photo : null)}
|
avatar={makeImageUrl(c.public ? c.public.photo : null)}
|
||||||
comment={comment}
|
comment={comment}
|
||||||
|
preview={preview}
|
||||||
|
received={deliveryStatus}
|
||||||
unread={this.props.showUnread ? c.unread : 0}
|
unread={this.props.showUnread ? c.unread : 0}
|
||||||
now={c.online && this.props.connected}
|
now={c.online && this.props.connected}
|
||||||
acs={c.acs}
|
acs={c.acs}
|
||||||
|
|||||||
+65
-5
@@ -2,10 +2,15 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import LetterTile from './letter-tile.jsx';
|
|
||||||
import ContactBadges from './contact-badges.jsx';
|
import ContactBadges from './contact-badges.jsx';
|
||||||
|
import LetterTile from './letter-tile.jsx';
|
||||||
|
import ReceivedMarker from './received-marker.jsx'
|
||||||
import UnreadBadge from './unread-badge.jsx';
|
import UnreadBadge from './unread-badge.jsx';
|
||||||
|
|
||||||
|
import { Drafty } from 'tinode-sdk';
|
||||||
|
|
||||||
|
import { deliveryMarker } from '../lib/utils.js';
|
||||||
|
|
||||||
export default class Contact extends React.Component {
|
export default class Contact extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -35,7 +40,7 @@ export default class Contact extends React.Component {
|
|||||||
title = <i><FormattedMessage id="unnamed_topic" /></i>;
|
title = <i><FormattedMessage id="unnamed_topic" /></i>;
|
||||||
} else if (title.length > 30) {
|
} else if (title.length > 30) {
|
||||||
// FIXME: this is probably wrong for RTL languages.
|
// FIXME: this is probably wrong for RTL languages.
|
||||||
title = title.substring(0, 28) + '...';
|
title = title.substring(0, 28) + '…';
|
||||||
}
|
}
|
||||||
const online = this.props.now ? 'online' : 'offline';
|
const online = this.props.now ? 'online' : 'offline';
|
||||||
const avatar = this.props.avatar ? this.props.avatar : true;
|
const avatar = this.props.avatar ? this.props.avatar : true;
|
||||||
@@ -53,9 +58,16 @@ export default class Contact extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const subtitle = this.props.preview ?
|
||||||
|
(typeof this.props.preview == 'string' ? this.props.preview :
|
||||||
|
React.createElement(React.Fragment, null, Drafty.format(this.props.preview, draftyFormatter, this))) :
|
||||||
|
this.props.comment;
|
||||||
|
|
||||||
|
const icon = deliveryMarker(this.props.received);
|
||||||
|
const marker = icon ? <i className={'material-icons small space-right ' + icon.color}>{icon.name}</i> : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li className={!this.props.showCheckmark && this.props.selected ? "selected" : null}
|
<li className={!this.props.showCheckmark && this.props.selected ? 'selected' : null} onClick={this.handleClick}>
|
||||||
onClick={this.handleClick}>
|
|
||||||
<div className="avatar-box">
|
<div className="avatar-box">
|
||||||
<LetterTile
|
<LetterTile
|
||||||
avatar={avatar}
|
avatar={avatar}
|
||||||
@@ -71,7 +83,7 @@ export default class Contact extends React.Component {
|
|||||||
{this.props.isChannel ? <img src="/img/channel.png" className="channel" alt="channel" /> : null}
|
{this.props.isChannel ? <img src="/img/channel.png" className="channel" alt="channel" /> : null}
|
||||||
<UnreadBadge count={this.props.unread} /><ContactBadges badges={icon_badges} />
|
<UnreadBadge count={this.props.unread} /><ContactBadges badges={icon_badges} />
|
||||||
</div>
|
</div>
|
||||||
{this.props.comment ? <div className="contact-comment">{this.props.comment}</div> : null}
|
<div className="contact-comment">{marker}{subtitle || '\u00A0'}</div>
|
||||||
<span><ContactBadges badges={badges} /></span>
|
<span><ContactBadges badges={badges} /></span>
|
||||||
</div>
|
</div>
|
||||||
{this.props.showContextMenu ?
|
{this.props.showContextMenu ?
|
||||||
@@ -84,3 +96,51 @@ export default class Contact extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Converts Drafty object into a one-line preview.
|
||||||
|
function draftyFormatter(style, data, values, key) {
|
||||||
|
let el = Drafty.tagName(style);
|
||||||
|
const attr = { key: key };
|
||||||
|
if (el) {
|
||||||
|
switch (style) {
|
||||||
|
case 'HL':
|
||||||
|
// Make highlight less prominent in preview.
|
||||||
|
attr.className = 'highlight preview';
|
||||||
|
break;
|
||||||
|
case 'LN':
|
||||||
|
// Disable links in previews.
|
||||||
|
el = 'span';
|
||||||
|
break;
|
||||||
|
case 'IM':
|
||||||
|
// Replace image with '[icon] Image'.
|
||||||
|
el = React.Fragment;
|
||||||
|
values = [<i className="material-icons">photo</i>, 'Picture'];
|
||||||
|
break;
|
||||||
|
case 'BN':
|
||||||
|
el = 'span';
|
||||||
|
attr.className = 'flat-button faux';
|
||||||
|
break;
|
||||||
|
case 'FM':
|
||||||
|
el = React.Fragment;
|
||||||
|
values = [<i className="material-icons">dashboard</i>, 'Form:'];
|
||||||
|
break;
|
||||||
|
case 'RW':
|
||||||
|
el = React.Fragment;
|
||||||
|
break;
|
||||||
|
case 'EX':
|
||||||
|
el = React.Fragment;
|
||||||
|
values = [<i className="material-icons">attachment</i>, 'Attachment'];
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (el == '_UNKN') {
|
||||||
|
el = React.Fragment;
|
||||||
|
values = [<i className="material-icons">extension</i>];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return React.createElement(el, attr, values);
|
||||||
|
} else {
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ class ContextMenu extends React.Component {
|
|||||||
title: formatMessage(messages.clear_messages),
|
title: formatMessage(messages.clear_messages),
|
||||||
handler: (params, errorHandler) => {
|
handler: (params, errorHandler) => {
|
||||||
return props.onShowAlert(
|
return props.onShowAlert(
|
||||||
formatMessage({id: 'menu_item_clear_messages'}), // title
|
formatMessage(messages.menu_item_clear_messages), // title
|
||||||
formatMessage({id: 'clear_messages_warning'}), // content
|
formatMessage({id: 'clear_messages_warning'}), // content
|
||||||
(() => { this.deleteMessages(true, false, params, errorHandler); }), // onConfirm
|
(() => { this.deleteMessages(true, false, params, errorHandler); }), // onConfirm
|
||||||
null, // "OK"
|
null, // "OK"
|
||||||
@@ -121,7 +121,7 @@ class ContextMenu extends React.Component {
|
|||||||
title: formatMessage(messages.clear_for_all),
|
title: formatMessage(messages.clear_for_all),
|
||||||
handler: (params, errorHandler) => {
|
handler: (params, errorHandler) => {
|
||||||
return props.onShowAlert(
|
return props.onShowAlert(
|
||||||
formatMessage({id: 'menu_item_clear_messages_for_all'}), // title
|
formatMessage(messages.menu_item_clear_messages_for_all), // title
|
||||||
formatMessage({id: 'delete_messages_warning'}), // content
|
formatMessage({id: 'delete_messages_warning'}), // content
|
||||||
(() => { return this.deleteMessages(true, true, params, errorHandler); }),
|
(() => { return this.deleteMessages(true, true, params, errorHandler); }),
|
||||||
null, // "OK"
|
null, // "OK"
|
||||||
@@ -171,7 +171,7 @@ class ContextMenu extends React.Component {
|
|||||||
title: formatMessage(messages.block),
|
title: formatMessage(messages.block),
|
||||||
handler: (params, errorHandler) => {
|
handler: (params, errorHandler) => {
|
||||||
return props.onShowAlert(
|
return props.onShowAlert(
|
||||||
formatMessage({id: 'menu_item_block'}), // title
|
formatMessage(messages.menu_item_block), // title
|
||||||
formatMessage(messages.topic_block_warning), // content
|
formatMessage(messages.topic_block_warning), // content
|
||||||
(() => {
|
(() => {
|
||||||
return this.topicPermissionSetter('-JP', params, errorHandler).then((ctrl) => {
|
return this.topicPermissionSetter('-JP', params, errorHandler).then((ctrl) => {
|
||||||
@@ -190,7 +190,7 @@ class ContextMenu extends React.Component {
|
|||||||
title: formatMessage(messages.topic_delete),
|
title: formatMessage(messages.topic_delete),
|
||||||
handler: (params, errorHandler) => {
|
handler: (params, errorHandler) => {
|
||||||
return props.onShowAlert(
|
return props.onShowAlert(
|
||||||
formatMessage({id: 'menu_item_delete_topic'}), // title
|
formatMessage(messages.menu_item_delete_topic), // title
|
||||||
formatMessage(messages.topic_delete_warning), // content
|
formatMessage(messages.topic_delete_warning), // content
|
||||||
(() => {
|
(() => {
|
||||||
const topic = this.props.tinode.getTopic(params.topicName);
|
const topic = this.props.tinode.getTopic(params.topicName);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { defineMessages, injectIntl } from 'react-intl';
|
|||||||
import Tinode from 'tinode-sdk';
|
import Tinode from 'tinode-sdk';
|
||||||
|
|
||||||
import { shortDateFormat } from '../lib/strformat.js';
|
import { shortDateFormat } from '../lib/strformat.js';
|
||||||
|
import { deliveryMarker } from '../lib/utils.js';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
'sending': {
|
'sending': {
|
||||||
@@ -31,18 +32,8 @@ class ReceivedMarker extends React.PureComponent {
|
|||||||
timestamp = shortDateFormat(this.props.timestamp, this.props.intl.locale);
|
timestamp = shortDateFormat(this.props.timestamp, this.props.intl.locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
let marker = null;
|
const icon = deliveryMarker(this.props.received);
|
||||||
if (this.props.received <= Tinode.MESSAGE_STATUS_SENDING) {
|
const marker = icon ? <i className={'material-icons small ' + icon.color}>{icon.name}</i> : null;
|
||||||
marker = (<i className="material-icons small">access_time</i>); // watch face
|
|
||||||
} else if (this.props.received == Tinode.MESSAGE_STATUS_FAILED) {
|
|
||||||
marker = (<i className="material-icons small amber">warning</i>); // yellow icon /!\
|
|
||||||
} else if (this.props.received == Tinode.MESSAGE_STATUS_SENT) {
|
|
||||||
marker = (<i className="material-icons small">done</i>); // checkmark
|
|
||||||
} else if (this.props.received == Tinode.MESSAGE_STATUS_RECEIVED) {
|
|
||||||
marker = (<i className="material-icons small">done_all</i>); // double checkmark
|
|
||||||
} else if (this.props.received == Tinode.MESSAGE_STATUS_READ) {
|
|
||||||
marker = (<i className="material-icons small blue">done_all</i>); // blue double checkmark
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className="timestamp">
|
<span className="timestamp">
|
||||||
|
|||||||
+2315
-1623
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
+1371
-703
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+6
-2
@@ -13,7 +13,7 @@ module.exports = (env, argv) => {
|
|||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.jsx?$/,
|
test: /\.jsx?$/,
|
||||||
loaders: [
|
use: [
|
||||||
'babel-loader',
|
'babel-loader',
|
||||||
],
|
],
|
||||||
exclude: /node_modules/,
|
exclude: /node_modules/,
|
||||||
@@ -26,14 +26,17 @@ module.exports = (env, argv) => {
|
|||||||
publicPath: '/umd/'
|
publicPath: '/umd/'
|
||||||
},
|
},
|
||||||
optimization: {
|
optimization: {
|
||||||
|
minimize: (argv.mode === 'production'),
|
||||||
minimizer: [
|
minimizer: [
|
||||||
new TerserPlugin({
|
new TerserPlugin({
|
||||||
sourceMap: true,
|
|
||||||
terserOptions: {
|
terserOptions: {
|
||||||
ecma: undefined,
|
ecma: undefined,
|
||||||
warnings: false,
|
warnings: false,
|
||||||
parse: {},
|
parse: {},
|
||||||
compress: {},
|
compress: {},
|
||||||
|
format: {
|
||||||
|
comments: false,
|
||||||
|
},
|
||||||
mangle: true, // Note `mangle.properties` is `false` by default.
|
mangle: true, // Note `mangle.properties` is `false` by default.
|
||||||
module: false,
|
module: false,
|
||||||
output: null,
|
output: null,
|
||||||
@@ -44,6 +47,7 @@ module.exports = (env, argv) => {
|
|||||||
keep_fnames: false,
|
keep_fnames: false,
|
||||||
safari10: false,
|
safari10: false,
|
||||||
},
|
},
|
||||||
|
extractComments: false,
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user