Compare commits

...
54 Commits
Author SHA1 Message Date
or-else ebe47b345d pix editing ofpinned messages 2023-04-05 10:14:07 -07:00
or-else 454dc204c8 minor visual tweaks 2023-03-25 11:13:04 -07:00
or-else d0c88fbd0d finishing touches, all seems to work 2023-03-24 15:15:40 -07:00
or-else e68ebc8336 works as expected on desktop, mobile needs work 2023-03-24 13:56:24 -07:00
or-else cca97d410e widget for pinned messages 2023-03-23 18:22:30 -07:00
or-else eafd87fb17 make bg change on click more subtle 2023-03-23 18:16:32 -07:00
or-else 71ee3fbec4 widget for showing pinned messages (wip) 2023-03-23 18:09:11 -07:00
or-else 11fe81fdf1 adding pinned messages (wip) 2023-03-22 17:50:33 -07:00
or-else 9640c604b9 Merge branch 'devel' into next 2023-03-22 11:00:37 -07:00
or-else 6273b3287f rebuilt with correct version of the sdk 2023-03-22 09:22:39 -07:00
or-else 9c5e3a3eca dependencies updated 2023-03-22 09:19:31 -07:00
or-else 9098c1f6a6 merge devel 2023-03-20 17:21:25 -07:00
or-else a2439be540 Merge branch 'devel' into next 2023-03-20 16:12:41 -07:00
or-else a2fb3fe1a0 rebuilt 2023-03-20 16:12:36 -07:00
or-else 560e3369e4 rebuilt 2023-03-20 16:11:43 -07:00
or-else 491175cecc dependencies updated 2023-03-20 16:10:36 -07:00
or-else 6d96d327f1 fix backward scrolling on fetching older messages 2023-03-20 11:35:01 -07:00
or-else 6ee2851943 rebuilt 2023-03-19 15:14:24 -07:00
or-else 192a3d4ec2 don't crash if FIREBASE_INIT is not defined 2023-03-19 15:14:00 -07:00
or-else c6f7911c82 dependencies updated, version to 0.22.0, rebuilt 2023-03-15 10:43:29 -07:00
or-else 9a1f0c734a dependencies updated 2023-03-15 08:41:22 -07:00
or-else 52f879490e fix for tinode/chat/issues/838 2023-03-14 18:14:33 -07:00
or-else 8df3c34cf4 moved hiding components to the top level 2023-03-14 15:54:22 -07:00
or-else 2fb6a9bb0a escape key closes image, video, or document preview 2023-03-14 14:23:16 -07:00
or-else 5ae58fb2a6 allow message input textarea height to grow with more text 2023-03-14 13:53:21 -07:00
or-else 43798ea1d9 minor tweaks 2023-03-14 09:44:54 -07:00
or-else 323042bd96 rebuild 2023-03-03 15:56:09 -08:00
or-else 172b9eb26d removed duplicate styling 2023-03-03 15:52:53 -08:00
or-else 1b90c613f7 fix drag and drop in firefox 2023-03-02 16:19:13 -08:00
or-else c6b4460389 merge devel and bump version to 0.22.0-rc1 2023-03-02 13:32:42 -08:00
or-else 497318b722 updated dependencies and bumped version 2023-03-01 16:49:14 -08:00
or-else 423f53880d Merge branch 'devel' 2023-03-01 14:34:32 -08:00
or-else a93ede25e9 make date bubble lighter 2023-02-24 18:50:09 -08:00
or-else b4d3e0274f fix color of the date bubble 2023-02-24 16:23:22 -08:00
or-else 4f116ab3e6 replace box-shadow with filter: drop-shadow to remove artifacts 2023-02-24 15:39:08 -08:00
or-else 75b2880b63 bump version 2023-02-20 16:18:28 -08:00
or-else 1149980a47 rebuilt 2023-02-20 16:05:36 -08:00
aforge 87b48cd67d Fix strutil.secondsToTime(). 2023-02-16 13:26:55 -08:00
aforge 4be09a034e Calls: create data channel only on the caller's side.
Removes redundancy and a memory leak (and thus an error upon closing).
2023-02-16 13:25:15 -08:00
aforge 3c0b57333c Calls: create data channel only on the caller's side.
Removes redundancy and a memory leak (and thus an error upon closing).
2023-02-15 17:55:29 -08:00
Gene 0c9b23eee9 Merge pull request #118 from sun2008mt/master
Update zh.json
2023-02-09 08:33:58 -08:00
Marc SUN d11f94095f Update zh.json 2023-02-09 18:14:08 +08:00
or-else 133ed3cc53 merge devel 2023-02-08 13:40:09 -08:00
or-else 7bc50fac36 handing of "busy" webrtc status 2023-02-08 13:30:50 -08:00
aforge fbdd017d38 Fix strutil.secondsToTime(). 2023-02-06 22:35:48 -08:00
or-else 8fdb4a8a4c bump version to 0.21.1 and rebuild 2023-02-06 10:22:00 -08:00
or-else e1b1ae503f fix issues with video uploading and display 2023-02-06 09:58:56 -08:00
or-else 7b44913724 fix display of broken videos 2023-02-06 09:56:50 -08:00
or-else 7e3b2fc254 switch tinode-sdk from browserify to webpack 2023-02-06 09:54:12 -08:00
aforge fc6a8fbafc Do not crash in MessagesView.handleMessageUpdate if topic gets unset. 2023-02-06 09:51:53 -08:00
or-else 111b9d8641 rebuilt with the sdk fix 2023-02-05 17:49:31 -08:00
or-else eeea27ca82 fix issues with video uploading and display 2023-02-05 17:08:41 -08:00
or-else fc2ff79329 fix display of broken videos 2023-02-05 13:46:08 -08:00
or-else f755314c3b rebuilt 2023-02-05 12:43:17 -08:00
109 changed files with 5986 additions and 9874 deletions
+115 -13
View File
@@ -197,7 +197,7 @@ textarea {
height: 2.5rem;
border-bottom: 1px solid #ccc;
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.1);
transition: all 0.1s ease-in-out;
transition: all 0.1s ease-in-out, height 0s;
}
input[type="text"],
@@ -273,11 +273,6 @@ textarea.inplace-edit {
background-color: rgba(0,0,0,0.3);
}
/* Firefox only */
* {
scrollbar-width: thin;
scrollbar-color: silver;
}
/* END of Scrollbar styling. */
/* BEGIN Popup/dropdown menu */
@@ -315,9 +310,8 @@ ul.menu > li.separator:hover {
}
/* The down arrow head - trigger for context menu */
li .menuTrigger {
.menuTrigger {
position: absolute;
top: 0.25rem;
right: 0.25rem;
width: 1.4rem;
height: 1.4rem;
@@ -327,6 +321,13 @@ li .menuTrigger {
opacity: 0;
background-color: inherit;
}
.menuTrigger a {
color: #757575;
}
li .menuTrigger {
top: 0.25rem;
}
li:hover .menuTrigger {
opacity: 0.9;
}
@@ -1392,6 +1393,7 @@ img.channel {
#topic-users {
margin-left: auto;
margin-right: 1rem;
width: min-content;
}
#topic-users .avatar-box {
@@ -1513,6 +1515,13 @@ img.channel {
color: #2196f3!important;
}
/* The textarea where the message is entered. */
#send-message-input {
overflow: hidden auto;
min-height: 2.5rem;
max-height: 6rem;
}
/* New chat invitation panel */
.accept-invite-panel {
@@ -1608,6 +1617,7 @@ img.channel {
line-height: 1.5rem;
background-color: #eceff1;
box-shadow: 0px -2px 3px -1px rgba(0,0,0,0.1); /* Faint shadow above panel */
z-index: 1;
}
#image-preview-container .material-icons {
@@ -1856,9 +1866,9 @@ video {
text-align: left;
color: #212121;
border-radius: 0.5rem;
box-shadow: 0 1px 0.5px rgba(0, 0, 0, 0.12);
transition: ease-out background-color 0.5s;
transition-delay: 0.3s;
filter: drop-shadow(0.5px 0.5px 0.7px rgb(0,0,0,0.3));
}
.chat-box li.single {
@@ -1926,7 +1936,7 @@ video {
.meta .bubble.date {
font-size: smaller;
padding: 0.5rem 0.5rem 0.5rem 0.5rem;
background-color: rgb(244 244 244 / 66%);
background-color: rgb(255 255 255 / 80%);
text-transform: uppercase;
}
@@ -1940,7 +1950,7 @@ video {
bottom: 0;
background-color: #c5e1a5;
clip-path: polygon(100% 0, 0 110%, 100% 110%);
box-shadow: 0 1px 0.5px rgb(0 0 0 / 12%);
-webkit-clip-path: polygon(100% 0, 0 110%, 100% 110%);
border-bottom-left-radius: 0.3rem;
transition: ease-out background-color 0.5s;
transition-delay: 0.3s;
@@ -1960,7 +1970,7 @@ video {
bottom: 0;
background-color: #fafafa;
clip-path: polygon(0 0, 100% 110%, 0 110%);
box-shadow: 0 1px 0.5px rgb(0 0 0 / 12%);
-webkit-clip-path: polygon(0 0, 100% 110%, 0 110%);
border-bottom-right-radius: 0.3rem;
transition: ease-out background-color 0.5s;
transition-delay: 0.3s;
@@ -2317,6 +2327,7 @@ ul.tabbar li.active > a {
width: 100%;
padding-bottom: 100%;
border-radius: 50%;
user-select: none;
}
/* Div with letter tile content */
@@ -3016,6 +3027,84 @@ ul.tabbar li.active > a {
color: #666C;
}
/* Pinned messages */
#pinned-wrapper {
position: relative;
display: flex;
align-items: center;
height: 100%;
margin-right: 1rem;
}
.pinned {
position: relative;
display: flex;
align-items: center;
padding: 0.15rem 0.5rem 0.25rem 0.5rem;
background-color: #fafafa;
background-position: center;
line-height: 1.5em;
font-weight: lighter;
border-radius: 0 0.25rem 0.25rem 0;
color: #666;
height: 100%;
width: 100%;
min-width: 24rem;
cursor: pointer;
transition: background 0.4s;
overflow-x: hidden;
text-overflow: ellipsis;
}
.pinned:hover {
background-color: #fafdff;
}
.pinned:active {
background-color: #d8e8ff;
transition: background 0s;
}
.pinned-scroll {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
border-radius: 0.25rem 0 0 0.25rem;
background-color: #eeee;
justify-content: space-evenly;
height: 100%;
padding: 0.1rem;
margin-left: 0.35rem;
}
.pinned-scroll .dot {
width: 0.3rem;
height: 0.3rem;
border-radius: 50%;
background-color: #999;
}
.pinned-scroll .adot {
width: 0.45rem;
height: 0.45rem;
border-radius: 50%;
background-color: #039be5;
}
#pinned-wrapper .menuTrigger.upper {
top: 0.25rem;
}
#pinned-wrapper .menuTrigger.lower {
bottom: 0.25rem;
}
#pinned-wrapper:hover .menuTrigger {
opacity: 0.9;
}
#pinned-wrapper:hover .menuTrigger a {
color: #757575;
}
/*
Drag-and-drop area.
*/
@@ -3103,6 +3192,10 @@ ul.phone-country-selector span.country {
#mountPoint {
padding: 0;
}
.pinned {
min-width: 12rem;
}
}
@media (max-width: 640px) {
@@ -3128,7 +3221,7 @@ ul.phone-country-selector span.country {
}
/* Must make it a bit visible on mobile because there is no :hover */
li .menuTrigger {
.menuTrigger {
opacity: 0.3;
}
@@ -3136,4 +3229,13 @@ ul.phone-country-selector span.country {
/* Just an indicator that the screen is in mobile mode */
transition-property: -tinode-mobile;
}
#pinned-wrapper {
height: 3.5rem;
margin: 0.25rem;
}
.pinned {
min-width: unset;
}
}
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -25,7 +25,7 @@
<link rel="stylesheet" href="css/base.min.css" />
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/react@18.2.0/umd/react.production.min.js"></script>
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/react-dom@18.2.0/umd/react-dom.production.min.js"></script>
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/react-intl@6.2.1/react-intl.iife.min.js"></script>
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/react-intl@6.3.2/react-intl.iife.min.js"></script>
</head>
<body>
<noscript><div id="noscript-message"><div id="noscript-title">Javascript support is required.</div>
+3997 -5361
View File
File diff suppressed because it is too large Load Diff
+19 -18
View File
@@ -1,10 +1,10 @@
{
"name": "tinode-webapp",
"description": "Tinode messenger for the web",
"version": "0.21.0",
"version": "0.23.0-rc1",
"repository": {
"type": "git",
"url": "https://github.com/tinode/example-react-js.git"
"url": "https://github.com/tinode/webapp.git"
},
"scripts": {
"start": "echo 'Read installation instructions'",
@@ -58,35 +58,36 @@
],
"main": "umd/index.prod.js",
"bugs": {
"url": "https://github.com/tinode/example-react-js/issues"
"url": "https://github.com/tinode/webapp/issues"
},
"homepage": "https://github.com/tinode/chat",
"email": "info@tinode.co",
"author": "Tinode Authors <info@tinode.co>",
"license": "Apache-2.0",
"dependencies": {
"firebase": "^9.14.0",
"libphonenumber-js": "^1.10.18",
"firebase": "^9.17.2",
"libphonenumber-js": "^1.10.24",
"npm-check-updates": "^16.7.12",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-intl": "^6.2.1",
"tinode-sdk": "^0.21.0",
"react-intl": "^6.3.0",
"tinode-sdk": "^0.23.0-rc1",
"webm-duration-fix": "^1.0.4"
},
"devDependencies": {
"@babel/core": "^7.20.2",
"@babel/core": "^7.21.3",
"@babel/preset-env": "^7.20.2",
"@babel/preset-react": "^7.18.6",
"@formatjs/cli": "^5.1.3",
"babel-loader": "^9.1.0",
"babel-plugin-formatjs": "^10.3.31",
"browserslist": "^4.21.4",
"@formatjs/cli": "^6.0.4",
"babel-loader": "^9.1.2",
"babel-plugin-formatjs": "^10.4.0",
"browserslist": "^4.21.5",
"copy-webpack-plugin": "^11.0.0",
"cssnano": "^5.1.14",
"glob": "^8.0.3",
"postcss": "^8.4.19",
"postcss-cli": "^10.0.0",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.0"
"cssnano": "^5.1.15",
"glob": "^9.3.0",
"postcss": "^8.4.21",
"postcss-cli": "^10.1.0",
"webpack": "^5.76.1",
"webpack-cli": "^5.0.1"
}
}
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+22 -1
View File
@@ -1634,11 +1634,32 @@
"missing": true,
"obsolete": false
},
"change_phonel": {
"change_phone": {
"translation": "",
"defaultMessage": "Change phone number",
"description": "Prompt to change current phone number",
"missing": true,
"obsolete": false
},
"call_busy": {
"translation": "besetzt",
"defaultMessage": "busy",
"description": "Label for busy call line",
"missing": false,
"obsolete": false
},
"pin_message": {
"translation": "",
"defaultMessage": "Pin",
"description": "Menu item [Pin] for pinning message to the top.",
"missing": true,
"obsolete": false
},
"unpin_message": {
"translation": "",
"defaultMessage": "Unpin",
"description": "Menu item [Unpin] for un-pinning the message.",
"missing": true,
"obsolete": false
}
}
+22 -1
View File
@@ -1634,11 +1634,32 @@
"missing": false,
"obsolete": false
},
"change_phonel": {
"change_phone": {
"translation": "Change phone number",
"defaultMessage": "Change phone number",
"description": "Prompt to change current phone number",
"missing": false,
"obsolete": false
},
"call_busy": {
"translation": "busy",
"defaultMessage": "busy",
"description": "Label for busy call line",
"missing": false,
"obsolete": false
},
"pin_message": {
"translation": "Pin",
"defaultMessage": "Pin",
"description": "Menu item [Pin] for pinning message to the top.",
"missing": false,
"obsolete": false
},
"unpin_message": {
"translation": "Unpin",
"defaultMessage": "Unpin",
"description": "Menu item [Unpin] for un-pinning the message.",
"missing": false,
"obsolete": false
}
}
+22 -1
View File
@@ -1634,11 +1634,32 @@
"missing": true,
"obsolete": false
},
"change_phonel": {
"change_phone": {
"translation": "",
"defaultMessage": "Change phone number",
"description": "Prompt to change current phone number",
"missing": true,
"obsolete": false
},
"call_busy": {
"translation": "ocupado",
"defaultMessage": "busy",
"description": "Label for busy call line",
"missing": false,
"obsolete": false
},
"pin_message": {
"translation": "",
"defaultMessage": "Pin",
"description": "Menu item [Pin] for pinning message to the top.",
"missing": true,
"obsolete": false
},
"unpin_message": {
"translation": "",
"defaultMessage": "Unpin",
"description": "Menu item [Unpin] for un-pinning the message.",
"missing": true,
"obsolete": false
}
}
+13 -1
View File
@@ -119,6 +119,10 @@
"defaultMessage": "Update",
"description": "Button [Update]"
},
"call_busy": {
"defaultMessage": "busy",
"description": "Label for busy call line"
},
"call_cancelled": {
"defaultMessage": "cancelled",
"description": "Label for cancelled outgoing call"
@@ -159,7 +163,7 @@
"defaultMessage": "Change email",
"description": "Prompt to change current email"
},
"change_phonel": {
"change_phone": {
"defaultMessage": "Change phone number",
"description": "Prompt to change current phone number"
},
@@ -752,6 +756,10 @@
"defaultMessage": "phone",
"description": "Dative case of 'phone', i.e. 'phone' in 'by phone'"
},
"pin_message": {
"defaultMessage": "Pin",
"description": "Menu item [Pin] for pinning message to the top."
},
"private_editing_placeholder": {
"defaultMessage": "Visible to you only",
"description": "Placeholder for editing 'private'"
@@ -908,6 +916,10 @@
"defaultMessage": "Unnamed",
"description": "Title shown when the topic has no name"
},
"unpin_message": {
"defaultMessage": "Unpin",
"description": "Menu item [Unpin] for un-pinning the message."
},
"unrecognized_video_format": {
"defaultMessage": "Format of this video is not recognized",
"description": "Error message when uploaded video is invalid"
+22 -1
View File
@@ -1634,11 +1634,32 @@
"missing": true,
"obsolete": false
},
"change_phonel": {
"change_phone": {
"translation": "",
"defaultMessage": "Change phone number",
"description": "Prompt to change current phone number",
"missing": true,
"obsolete": false
},
"call_busy": {
"translation": "occupé",
"defaultMessage": "busy",
"description": "Label for busy call line",
"missing": false,
"obsolete": false
},
"pin_message": {
"translation": "",
"defaultMessage": "Pin",
"description": "Menu item [Pin] for pinning message to the top.",
"missing": true,
"obsolete": false
},
"unpin_message": {
"translation": "",
"defaultMessage": "Unpin",
"description": "Menu item [Unpin] for un-pinning the message.",
"missing": true,
"obsolete": false
}
}
+22 -1
View File
@@ -1634,11 +1634,32 @@
"missing": true,
"obsolete": false
},
"change_phonel": {
"change_phone": {
"translation": "",
"defaultMessage": "Change phone number",
"description": "Prompt to change current phone number",
"missing": true,
"obsolete": false
},
"call_busy": {
"translation": "바쁘다",
"defaultMessage": "busy",
"description": "Label for busy call line",
"missing": false,
"obsolete": false
},
"pin_message": {
"translation": "",
"defaultMessage": "Pin",
"description": "Menu item [Pin] for pinning message to the top.",
"missing": true,
"obsolete": false
},
"unpin_message": {
"translation": "",
"defaultMessage": "Unpin",
"description": "Menu item [Unpin] for un-pinning the message.",
"missing": true,
"obsolete": false
}
}
+22 -1
View File
@@ -1634,11 +1634,32 @@
"missing": true,
"obsolete": false
},
"change_phonel": {
"change_phone": {
"translation": "",
"defaultMessage": "Change phone number",
"description": "Prompt to change current phone number",
"missing": true,
"obsolete": false
},
"call_busy": {
"translation": "ocupat",
"defaultMessage": "busy",
"description": "Label for busy call line",
"missing": false,
"obsolete": false
},
"pin_message": {
"translation": "",
"defaultMessage": "Pin",
"description": "Menu item [Pin] for pinning message to the top.",
"missing": true,
"obsolete": false
},
"unpin_message": {
"translation": "",
"defaultMessage": "Unpin",
"description": "Menu item [Unpin] for un-pinning the message.",
"missing": true,
"obsolete": false
}
}
+22 -1
View File
@@ -1634,11 +1634,32 @@
"missing": true,
"obsolete": false
},
"change_phonel": {
"change_phone": {
"translation": "",
"defaultMessage": "Change phone number",
"description": "Prompt to change current phone number",
"missing": true,
"obsolete": false
},
"call_busy": {
"translation": "занято",
"defaultMessage": "busy",
"description": "Label for busy call line",
"missing": false,
"obsolete": false
},
"pin_message": {
"translation": "",
"defaultMessage": "Pin",
"description": "Menu item [Pin] for pinning message to the top.",
"missing": true,
"obsolete": false
},
"unpin_message": {
"translation": "",
"defaultMessage": "Unpin",
"description": "Menu item [Unpin] for un-pinning the message.",
"missing": true,
"obsolete": false
}
}
+22 -1
View File
@@ -1634,11 +1634,32 @@
"missing": true,
"obsolete": false
},
"change_phonel": {
"change_phone": {
"translation": "",
"defaultMessage": "Change phone number",
"description": "Prompt to change current phone number",
"missing": true,
"obsolete": false
},
"call_busy": {
"translation": "忙碌的",
"defaultMessage": "busy",
"description": "Label for busy call line",
"missing": false,
"obsolete": false
},
"pin_message": {
"translation": "",
"defaultMessage": "Pin",
"description": "Menu item [Pin] for pinning message to the top.",
"missing": true,
"obsolete": false
},
"unpin_message": {
"translation": "",
"defaultMessage": "Unpin",
"description": "Menu item [Unpin] for un-pinning the message.",
"missing": true,
"obsolete": false
}
}
+23 -2
View File
@@ -448,7 +448,7 @@
"obsolete": false
},
"label_default_access_mode": {
"translation": "蓦然访问模式:",
"translation": "默认访问模式:",
"defaultMessage": "Default access mode:",
"description": "Label for default access mode",
"missing": false,
@@ -1634,11 +1634,32 @@
"missing": true,
"obsolete": false
},
"change_phonel": {
"change_phone": {
"translation": "",
"defaultMessage": "Change phone number",
"description": "Prompt to change current phone number",
"missing": true,
"obsolete": false
},
"call_busy": {
"translation": "忙碌的",
"defaultMessage": "busy",
"description": "Label for busy call line",
"missing": false,
"obsolete": false
},
"pin_message": {
"translation": "",
"defaultMessage": "Pin",
"description": "Menu item [Pin] for pinning message to the top.",
"missing": true,
"obsolete": false
},
"unpin_message": {
"translation": "",
"defaultMessage": "Unpin",
"description": "Menu item [Unpin] for un-pinning the message.",
"missing": true,
"obsolete": false
}
}
+1 -1
View File
@@ -10,7 +10,7 @@ import TinodeWeb from './views/tinode-web.jsx';
import HashNavigation from './lib/navigation.js';
// Insert google analytics script and tag if configured.
if (FIREBASE_INIT && FIREBASE_INIT.measurementId) {
if (typeof FIREBASE_INIT != 'undefined' && FIREBASE_INIT && FIREBASE_INIT.measurementId) {
const head = document.getElementsByTagName('head')[0];
let script = document.createElement('script');
script.src = 'https://www.googletagmanager.com/gtag/js?id=' + FIREBASE_INIT.measurementId;
+1 -1
View File
@@ -227,7 +227,7 @@ export function filePasted(event, onImageSuccess, onAttachmentSuccess, onError)
// Get mime type from data URL header.
export function getMimeType(header) {
var mime = /^data:(image\/[-+a-z0-9.]+);base64/.exec(header);
const mime = /^data:(image\/[-+a-z0-9.]+);base64/.exec(header);
return (mime && mime.length > 1) ? mime[1] : null;
}
-2
View File
@@ -236,8 +236,6 @@ function handleVideoData(el, data, attr) {
if (data.ref || data.val) {
attr.onClick = this.onVideoPreview;
attr.loading = 'lazy';
} else {
attr.src = null;
}
el = InlineVideo;
} else {
+1 -1
View File
@@ -39,7 +39,7 @@ export function secondsToTime(seconds, fixedMin) {
}
let min = (Math.floor(seconds / 60) | 0) % 60;
let hours = Math.floor(seconds / 36000) | 0;
let hours = Math.floor(seconds / 3600) | 0;
if (fixedMin || hours > 0) {
min = min < 10 ? `0${min}` : min;
+1
View File
@@ -172,6 +172,7 @@ export function deliveryMarker(received) {
case Tinode.MESSAGE_STATUS_SENDING:
return { name: 'access_time' }; // watch face
case Tinode.MESSAGE_STATUS_FAILED:
case Tinode.MESSAGE_STATUS_FATAL:
return { name: 'warning', color: 'danger-color' }; // yellow icon /!\
case Tinode.MESSAGE_STATUS_SENT:
return { name: 'done' }; // checkmark
+1 -1
View File
@@ -1,3 +1,3 @@
// This is a generated file. Don't edit.
export const PACKAGE_VERSION = "0.21.0";
export const PACKAGE_VERSION = "0.23.0-rc1";
+1 -1
View File
@@ -27,7 +27,7 @@ export default class ContactsView extends React.Component {
const contacts = [];
let unreadThreads = 0;
let archivedCount = 0;
props.chatList.map((c) => {
props.chatList.forEach(c => {
const blocked = c.acs && !c.acs.isJoiner();
// Show only blocked contacts only when props.blocked == true.
if (blocked && props.blocked) {
+118 -45
View File
@@ -17,6 +17,7 @@ import LetterTile from '../widgets/letter-tile.jsx';
import LoadSpinner from '../widgets/load-spinner.jsx';
import LogoView from './logo-view.jsx';
import MetaMessage from '../widgets/meta-message.jsx';
import PinnedMessages from '../widgets/pinned-messages.jsx';
import SendMessage from '../widgets/send-message.jsx';
import VideoPreview from '../widgets/video-preview.jsx';
@@ -32,6 +33,8 @@ import { bytesToHumanSize, relativeDateFormat, shortDateFormat } from '../lib/st
const NOTIFICATION_EXEC_INTERVAL = 300;
// Scroll distance before [go to latest message] button is shown.
const SHOW_GO_TO_LAST_DIST = 100;
// Sctoll distance from the top when fetching the page of earlier messages is triggered.
const FETCH_PAGE_TRIGGER = 40;
const messages = defineMessages({
online_now: {
@@ -121,6 +124,7 @@ class MessagesView extends React.Component {
this.handleDescChange = this.handleDescChange.bind(this);
this.handleSubsUpdated = this.handleSubsUpdated.bind(this);
this.handleMessageUpdate = this.handleMessageUpdate.bind(this);
this.handleAuxUpdate = this.handleAuxUpdate.bind(this);
this.handleAllMessagesReceived = this.handleAllMessagesReceived.bind(this);
this.handleInfoReceipt = this.handleInfoReceipt.bind(this);
this.handleExpandMedia = this.handleExpandMedia.bind(this);
@@ -142,9 +146,11 @@ class MessagesView extends React.Component {
this.handleEditMessage = this.handleEditMessage.bind(this);
this.handleCancelReply = this.handleCancelReply.bind(this);
this.handleQuoteClick = this.handleQuoteClick.bind(this);
this.handleUnpinMessage = this.handleUnpinMessage.bind(this);
this.handleCallHangup = this.handleCallHangup.bind(this);
this.isDragEnabled = this.isDragEnabled.bind(this);
this.handleDragStart = this.handleDragStart.bind(this);
this.handleDragIn = this.handleDragIn.bind(this);
this.handleDragOut = this.handleDragOut.bind(this);
this.handleDrag = this.handleDrag.bind(this);
@@ -181,12 +187,7 @@ class MessagesView extends React.Component {
}
// Drag and drop events
if (this.dndRef) {
this.dndRef.addEventListener('dragenter', this.handleDragIn);
this.dndRef.addEventListener('dragleave', this.handleDragOut);
this.dndRef.addEventListener('dragover', this.handleDrag);
this.dndRef.addEventListener('drop', this.handleDrop);
}
this.mountDnDEvents(this.dndRef);
}
componentWillUnmount() {
@@ -199,6 +200,7 @@ class MessagesView extends React.Component {
// Drag and drop events
if (this.dndRef) {
this.dndRef.removeEventListener('dragstart', this.handleDragStart);
this.dndRef.removeEventListener('dragenter', this.handleDragIn);
this.dndRef.removeEventListener('dragleave', this.handleDragOut);
this.dndRef.removeEventListener('dragover', this.handleDrag);
@@ -210,12 +212,12 @@ class MessagesView extends React.Component {
// or vertical shrinking.
componentDidUpdate(prevProps, prevState) {
if (this.messagesScroller &&
(prevState.topic != this.state.topic || prevState.messageCount != this.state.messageCount)) {
(prevState.topic != this.state.topic || prevState.maxSeqId != this.state.maxSeqId ||
prevState.minSeqId != this.state.minSeqId)) {
// New message.
if (this.state.scrollPosition < SHOW_GO_TO_LAST_DIST) {
this.messagesScroller.scrollTop = this.messagesScroller.scrollHeight -
this.state.scrollPosition -
this.messagesScroller.offsetHeight;
this.state.scrollPosition - this.messagesScroller.offsetHeight;
}
}
@@ -235,6 +237,7 @@ class MessagesView extends React.Component {
topic.onMetaDesc = this.handleDescChange;
topic.onSubsUpdated = this.handleSubsUpdated;
topic.onPres = this.handleSubsUpdated;
topic.onAuxUpdated = this.handleAuxUpdate;
}
}
@@ -261,7 +264,8 @@ class MessagesView extends React.Component {
if (!nextProps.topic) {
// Default state: no topic.
nextState = {
messageCount: 0,
minSeqId: -1,
maxSeqId: -1,
latestClearId: -1,
onlineSubs: [],
topic: null,
@@ -285,7 +289,9 @@ class MessagesView extends React.Component {
reply: null,
contentToEdit: null,
showGoToLastButton: false,
dragging: false
dragging: false,
pins: [],
selectedPin: 0
};
} else if (nextProps.topic != prevState.topic) {
const topic = nextProps.tinode.getTopic(nextProps.topic);
@@ -304,7 +310,8 @@ class MessagesView extends React.Component {
fetchingMessages: false,
showGoToLastButton: false,
contentToEdit: null,
dragging: false
dragging: false,
selectedPin: 0
};
if (nextProps.forwardMessage) {
@@ -356,9 +363,11 @@ class MessagesView extends React.Component {
});
}
Object.assign(nextState, {
messageCount: topic.messageCount(),
minSeqId: topic.minMsgSeq(),
maxSeqId: topic.maxMsgSeq(),
latestClearId: topic.maxClearId(),
channel: topic.isChannelType()
channel: topic.isChannelType(),
pins: (topic.aux('pins') || []).slice()
});
if (nextProps.callTopic == topic.name && shouldPresentCallPanel(nextProps.callState)) {
@@ -367,13 +376,15 @@ class MessagesView extends React.Component {
} else {
// Invalid topic.
Object.assign(nextState, {
messageCount: 0,
minSeqId: -1,
maxSeqId: -1,
latestClearId: -1,
onlineSubs: [],
title: '',
avatar: null,
peerMessagingDisabled: false,
channel: false
channel: false,
pins: []
});
}
} else {
@@ -391,6 +402,9 @@ class MessagesView extends React.Component {
if (nextProps.acs.isReader() != prevState.isReader) {
nextState.isReader = !prevState.isReader;
}
if (nextProps.acs.isAdmin() != prevState.isAdmin) {
nextState.isAdmin = !prevState.isAdmin;
}
if (!nextProps.acs.isReader('given') != prevState.readingBlocked) {
nextState.readingBlocked = !prevState.readingBlocked;
}
@@ -404,6 +418,9 @@ class MessagesView extends React.Component {
if (prevState.isReader) {
nextState.isReader = false;
}
if (prevState.isAdmin) {
nextState.isAdmin = false;
}
if (!prevState.readingBlocked) {
prevState.readingBlocked = true;
}
@@ -434,7 +451,7 @@ class MessagesView extends React.Component {
// Don't request the tags. They are useless unless the user
// is the owner and is editing the topic.
let getQuery = topic.startMetaQuery().withLaterDesc().withLaterSub();
let getQuery = topic.startMetaQuery().withLaterDesc().withLaterSub().withAux();
if (this.state.isReader || newTopic) {
// Reading is either permitted or we don't know because it's a new topic. Ask for messages.
getQuery = getQuery.withLaterData(MESSAGES_PAGE);
@@ -457,24 +474,24 @@ class MessagesView extends React.Component {
this.setState({topic: ctrl.topic});
}
this.props.onNewTopicCreated(this.props.topic, ctrl.topic);
// If there are unsent messages (except video call messages),
// try sending them now. Unsent video call messages will be dropped.
let calls = [];
topic.queuedMessages((pub) => {
// If there are unsent messages (except hard-failed and video call messages),
// try sending them now. Hard-failed and unsent video call messages will be dropped.
let discard = [];
topic.queuedMessages(pub => {
if (pub._sending) {
return;
}
if (pub.head && pub.head.webrtc) {
// Filter out unsent video call messages.
calls.push(pub.seq);
if (pub._fatal || (pub.head && pub.head.webrtc)) {
// Filter out unsent failed & video call messages.
discard.push(pub.seq);
return;
}
if (topic.isSubscribed()) {
this.retrySend(pub);
}
});
if (calls.length > 0) {
topic.delMessagesList(calls, true);
if (discard.length > 0) {
topic.delMessagesList(discard, true);
}
})
.catch(err => {
@@ -505,6 +522,7 @@ class MessagesView extends React.Component {
oldTopic.onMetaDesc = undefined;
oldTopic.onSubsUpdated = undefined;
oldTopic.onPres = undefined;
oldTopic.onAuxUpdated = undefined;
});
}
}
@@ -514,7 +532,8 @@ class MessagesView extends React.Component {
if (node) {
node.addEventListener('scroll', this.handleScrollEvent);
this.messagesScroller = node;
this.messagesScroller.scrollTop = this.messagesScroller.scrollHeight - this.state.scrollPosition;
this.messagesScroller.scrollTop = this.messagesScroller.scrollHeight -
this.state.scrollPosition - this.messagesScroller.offsetHeight;
}
}
@@ -531,7 +550,7 @@ class MessagesView extends React.Component {
return;
}
if (event.target.scrollTop <= 0) {
if (event.target.scrollTop <= FETCH_PAGE_TRIGGER) {
const topic = this.props.tinode.getTopic(this.state.topic);
if (topic && topic.isSubscribed() && topic.msgHasMoreMessages()) {
this.setState({fetchingMessages: true}, _ => {
@@ -543,9 +562,10 @@ class MessagesView extends React.Component {
}
}
/* Mound draf and drop events */
/* Mount drag and drop events */
mountDnDEvents(dnd) {
if (dnd) {
dnd.addEventListener('dragstart', this.handleDragStart);
dnd.addEventListener('dragenter', this.handleDragIn);
dnd.addEventListener('dragleave', this.handleDragOut);
dnd.addEventListener('dragover', this.handleDrag);
@@ -557,7 +577,7 @@ class MessagesView extends React.Component {
goToLatestMessage() {
this.setState({scrollPosition: 0});
if (this.messagesScroller) {
this.messagesScroller.scrollTop = this.messagesScroller.scrollHeight;
this.messagesScroller.scrollTop = this.messagesScroller.scrollHeight - this.messagesScroller.offsetHeight;
}
}
@@ -578,6 +598,7 @@ class MessagesView extends React.Component {
this.setState({
isWriter: desc.acs.isWriter(),
isReader: desc.acs.isReader(),
isAdmin: desc.acs.isAdmin(),
readingBlocked: !desc.acs.isReader('given'),
unconfirmed: isUnconfirmed(desc.acs),
});
@@ -684,7 +705,7 @@ class MessagesView extends React.Component {
}
clearTimeout(this.keyPressTimer)
this.setState({messageCount: topic.messageCount(), typingIndicator: false}, _ => {
this.setState({maxSeqId: topic.maxMsgSeq(), minSeqId: topic.minMsgSeq(), typingIndicator: false}, _ => {
// Scroll to the bottom if the message is added to the end of the message
// list if already at the bottom, otherwise show [go to latest] button.
// Implemented as a callback to be sure the scroll height has been updated.
@@ -694,6 +715,11 @@ class MessagesView extends React.Component {
} else {
this.goToLatestMessage();
}
} else {
if (this.messagesScroller) {
this.messagesScroller.scrollTop = this.messagesScroller.scrollHeight - this.state.scrollPosition -
this.messagesScroller.offsetHeight;
}
}
});
@@ -713,6 +739,18 @@ class MessagesView extends React.Component {
}
}
handleAuxUpdate(aux) {
const pins = (aux['pins'] || []).slice();
let selectedPin = this.state.selectedPin;
if (pins.length > this.state.pins.length) {
// New pins added, show the latest.
selectedPin = 0;
} else if (selectedPin >= pins.length) {
selectedPin = Math.max(0, pins.length - 1);
}
this.setState({pins: pins, selectedPin: selectedPin});
}
handleInfoReceipt(info) {
switch (info.what) {
case 'kp': {
@@ -739,9 +777,9 @@ class MessagesView extends React.Component {
}
if (content.video) {
this.setState({ videoPostview: content });
this.setState({videoPostview: content});
} else {
this.setState({ imagePostview: content });
this.setState({imagePostview: content});
}
}
@@ -766,7 +804,7 @@ class MessagesView extends React.Component {
params.set(key, data.resp[key]);
}
}
['name', 'seq'].map((key) => {
['name', 'seq'].forEach(key => {
if (data[key]) {
params.set(key, data[key]);
}
@@ -1057,8 +1095,8 @@ class MessagesView extends React.Component {
// Small preview to show while uploading.
b64conv[2] = imageScaled(previewBlob, VIDEO_PREVIEW_DIM, VIDEO_PREVIEW_DIM, -1, false)
.then(scaled => blobToBase64(scaled.blob));
// Convert tiny image into base64 for serialization and previewing.
Promise.all(b64conv)
// Convert tiny image into base64 for serialization and previewing.
.then(b64s => {
const [video, img, preview] = b64s;
let msg = Drafty.insertVideo(null, 0, {
@@ -1074,6 +1112,7 @@ class MessagesView extends React.Component {
size: videoBlob.size,
urlPromise: uploadCompletionPromise
});
if (caption) {
msg = Drafty.appendLineBreak(msg);
msg = Drafty.append(msg, Drafty.parse(caption));
@@ -1253,13 +1292,19 @@ class MessagesView extends React.Component {
}
}
handleUnpinMessage(seq) {
const topic = this.props.tinode.getTopic(this.state.topic);
topic.pinMessage(seq, false);
}
isDragEnabled() {
return this.state.isWriter && !this.state.unconfirmed && !this.props.forwardMessage && !this.state.peerMessagingDisabled;
}
handleDrag(e) {
handleDragStart(e) {
e.preventDefault();
e.stopPropagation();
e.dataTransfer.clearData();
}
handleDragIn(e) {
@@ -1280,13 +1325,17 @@ class MessagesView extends React.Component {
}
}
handleDrag(e) {
e.preventDefault();
e.stopPropagation();
}
handleDrop(e) {
e.preventDefault();
e.stopPropagation();
this.setState({dragging: false});
if (this.isDragEnabled() && e.dataTransfer.files && e.dataTransfer.files.length > 0) {
this.handleFileDrop(e.dataTransfer.files);
e.dataTransfer.clearData();
this.dragCounter = 0;
}
}
@@ -1295,9 +1344,7 @@ class MessagesView extends React.Component {
const {formatMessage} = this.props.intl;
let component;
if (this.props.hideSelf) {
component = null;
} else if (!this.state.topic) {
if (!this.state.topic) {
component = (
<LogoView
serverVersion={this.props.serverVersion}
@@ -1390,6 +1437,9 @@ class MessagesView extends React.Component {
}
}
const pinnedMessages = [];
this.state.pins.forEach(seq => pinnedMessages.push(topic.latestMsgVersion(seq)));
const messageNodes = [];
let previousFrom = null;
let prevDate = null;
@@ -1466,6 +1516,8 @@ class MessagesView extends React.Component {
received={deliveryStatus}
uploader={msg._uploader}
userIsWriter={this.state.isWriter}
userIsAdmin={this.state.isAdmin}
pinned={this.state.pins.includes(msg.seq)}
viewportWidth={this.props.viewportWidth} // Used by `formatter`.
showContextMenu={this.handleShowMessageContextMenu}
onExpandMedia={this.handleExpandMedia}
@@ -1581,11 +1633,22 @@ class MessagesView extends React.Component {
}<ContactBadges badges={icon_badges} /></div>
<div id="topic-last-seen">{lastSeen}</div>
</div>
<div style={{marginLeft: 'auto'}}/>
{!this.props.displayMobile ?
<PinnedMessages
tinode={this.props.tinode}
messages={pinnedMessages}
selected={this.state.selectedPin}
isAdmin={this.state.isAdmin}
setSelected={index => this.setState({selectedPin: index})}
onSelected={this.handleQuoteClick}
onCancel={this.handleUnpinMessage} />
: null}
{groupTopic ?
<GroupSubs
tinode={this.props.tinode}
subscribers={this.state.onlineSubs} /> :
<div id="topic-users" />
null
}
<div>
<a href="#" onClick={this.handleContextClick}>
@@ -1594,10 +1657,20 @@ class MessagesView extends React.Component {
</div>
</div>
{this.props.displayMobile ?
<ErrorPanel
level={this.props.errorLevel}
text={this.props.errorText}
onClearError={this.props.onError} />
<>
<PinnedMessages
tinode={this.props.tinode}
messages={pinnedMessages}
selected={this.state.selectedPin}
isAdmin={this.state.isAdmin}
setSelected={index => this.setState({selectedPin: index})}
onSelected={this.handleQuoteClick}
onCancel={this.handleUnpinMessage} />
<ErrorPanel
level={this.props.errorLevel}
text={this.props.errorText}
onClearError={this.props.onError} />
</>
: null}
<LoadSpinner show={this.state.fetchingMessages} />
{messagesComponent}
+1 -1
View File
@@ -46,7 +46,7 @@ export default class SettingsView extends React.PureComponent {
render() {
const names = {def: "default", ws: "websocket", lp: "long polling"};
const transportOptions = [];
['def', 'ws', 'lp'].map((item) => {
['def', 'ws', 'lp'].forEach(item => {
const id = 'transport-' + item;
const name = names[item];
transportOptions.push(
+1 -1
View File
@@ -124,7 +124,7 @@ class SidepanelView extends React.PureComponent {
}
return (
<div id="sidepanel" className={this.props.hideSelf ? 'nodisplay' : null}>
<div id="sidepanel">
<SideNavbar
state={view}
title={title}
+124 -115
View File
@@ -277,8 +277,10 @@ class TinodeWeb extends React.Component {
componentDidMount() {
window.addEventListener('resize', this.handleResize);
window.addEventListener('online', (e) => { this.handleOnline(true); });
window.addEventListener('offline', (e) => { this.handleOnline(false); });
this.handleOnlineOn = _ => { this.handleOnline(true); }
window.addEventListener('online', this.handleOnlineOn);
this.handleOnlineOff = _ => { this.handleOnline(false); }
window.addEventListener('offline', this.handleOnlineOff);
window.addEventListener('hashchange', this.handleHashRoute);
// Process background notifications from the service worker.
@@ -315,9 +317,6 @@ class TinodeWeb extends React.Component {
});
}
// Parse and save the hash navigation params.
const parsedNav = HashNavigation.parseUrlHash(window.location.hash);
// Read contacts from cache.
this.resetContactList();
@@ -337,6 +336,8 @@ class TinodeWeb extends React.Component {
this.readTimer = null;
this.readTimerCallback = null;
// Parse the hash navigation params.
const parsedNav = HashNavigation.parseUrlHash(window.location.hash);
// Maybe navigate to home screen.
if (!['cred', 'reset', 'register'].includes(parsedNav.path[0])) {
// Save possible topic name.
@@ -354,6 +355,8 @@ class TinodeWeb extends React.Component {
componentWillUnmount() {
window.removeEventListener('resize', this.handleResize);
window.removeEventListener('hashchange', this.handleHashRoute);
window.removeEventListener('online', this.handleOnlineOn);
window.removeEventListener('offline', this.handleOnlineOff);
document.removeEventListener('visibilitychange', this.handleVisibilityEvent);
}
@@ -503,8 +506,12 @@ class TinodeWeb extends React.Component {
let topicName = hash.path[1] || null;
if (topicName != this.state.topicSelected) {
if (!Tinode.topicType(topicName)) {
// Clear invalid topic name.
// Clear invalid topic name and hide messages view on mobile.
topicName = null;
newState.mobilePanel = 'sidepanel';
} else {
// Topic valid: show messages view on mobile.
newState.mobilePanel = 'topic-view';
}
Object.assign(newState, {
topicSelected: topicName,
@@ -1679,14 +1686,14 @@ class TinodeWeb extends React.Component {
}
if (added && added.length > 0) {
added.map((uid) => {
added.forEach(uid => {
topic.invite(uid, null)
.catch(err => this.handleError(err.message, 'err'));
});
}
if (removed && removed.length > 0) {
removed.map((uid) => {
removed.forEach(uid => {
topic.delSubscription(uid)
.catch(err => this.handleError(err.message, 'err'));
});
@@ -1981,126 +1988,128 @@ class TinodeWeb extends React.Component {
(_ => {this.setState({alertVisible: false}); this.state.alertParams.onConfirm();}) : null}
confirm={this.state.alertParams.confirm}
/> : null}
<SidepanelView
tinode={this.tinode}
connected={this.state.connected}
displayMobile={this.state.displayMobile}
hideSelf={this.state.displayMobile && this.state.mobilePanel !== 'sidepanel'}
state={this.state.sidePanelSelected}
title={this.state.sidePanelTitle}
avatar={this.state.sidePanelAvatar}
trustedBadges={this.state.myTrustedBadges}
login={this.state.login}
persist={this.state.persist}
myUserId={this.state.myUserId}
loginDisabled={this.state.loginDisabled}
loadSpinnerVisible={this.state.loadSpinnerVisible}
errorText={this.state.errorText}
errorLevel={this.state.errorLevel}
errorAction={this.state.errorAction}
errorActionText={this.state.errorActionText}
{!this.state.displayMobile || this.state.mobilePanel == 'sidepanel' ?
<SidepanelView
tinode={this.tinode}
connected={this.state.connected}
displayMobile={this.state.displayMobile}
state={this.state.sidePanelSelected}
title={this.state.sidePanelTitle}
avatar={this.state.sidePanelAvatar}
trustedBadges={this.state.myTrustedBadges}
login={this.state.login}
persist={this.state.persist}
myUserId={this.state.myUserId}
loginDisabled={this.state.loginDisabled}
loadSpinnerVisible={this.state.loadSpinnerVisible}
topicSelected={this.state.topicSelected}
chatList={this.state.chatList}
credMethod={this.state.credMethod}
credCode={this.state.credCode}
credToken={this.state.credToken}
errorText={this.state.errorText}
errorLevel={this.state.errorLevel}
errorAction={this.state.errorAction}
errorActionText={this.state.errorActionText}
transport={this.state.transport}
messageSounds={this.state.messageSounds}
desktopAlerts={this.state.desktopAlerts}
desktopAlertsEnabled={this.state.desktopAlertsEnabled}
incognitoMode={this.state.incognitoMode}
serverAddress={this.state.serverAddress}
secureConnection={this.state.secureConnection}
serverVersion={this.state.serverVersion}
reqCredMethod={this.state.reqCredMethod}
topicSelected={this.state.topicSelected}
chatList={this.state.chatList}
credMethod={this.state.credMethod}
credCode={this.state.credCode}
credToken={this.state.credToken}
onGlobalSettings={this.handleGlobalSettings}
onSignUp={this.handleNewAccount}
onSettings={this.handleSettings}
onNavigate={this.basicNavigator}
onLoginRequest={this.handleLoginRequest}
onPersistenceChange={this.handlePersistenceChange}
onCreateAccount={this.handleNewAccountRequest}
onUpdateAccountDesc={this.handleTopicUpdateRequest}
onUpdatePassword={this.handleUpdatePasswordRequest}
onUpdateAccountTags={this.handleUpdateAccountTagsRequest}
onTogglePushNotifications={this.toggleFCMToken}
onToggleMessageSounds={this.handleToggleMessageSounds}
onToggleIncognitoMode={this.handleToggleIncognitoMode}
onCredAdd={this.handleCredAdd}
onCredDelete={this.handleCredDelete}
onCredConfirm={this.handleCredConfirm}
onTopicSelected={this.handleTopicSelected}
onCreateTopic={this.handleStartTopicRequest}
onLogout={this.handleLogout}
onDeleteAccount={this.handleDeleteAccount}
onShowAlert={this.handleShowAlert}
onCancel={this.handleSidepanelCancel}
onError={this.handleError}
onValidateCredentials={this.handleValidateCredentialsRequest}
onPasswordResetRequest={this.handlePasswordResetRequest}
onResetPassword={this.handleResetPassword}
onShowArchive={this.handleShowArchive}
onShowBlocked={this.handleShowBlocked}
onShowCountrySelector={this.handleShowCountrySelector}
transport={this.state.transport}
messageSounds={this.state.messageSounds}
desktopAlerts={this.state.desktopAlerts}
desktopAlertsEnabled={this.state.desktopAlertsEnabled}
incognitoMode={this.state.incognitoMode}
serverAddress={this.state.serverAddress}
secureConnection={this.state.secureConnection}
serverVersion={this.state.serverVersion}
reqCredMethod={this.state.reqCredMethod}
onInitFind={this.tnInitFind}
searchResults={this.state.searchResults}
onSearchContacts={this.handleSearchContacts}
onGlobalSettings={this.handleGlobalSettings}
onSignUp={this.handleNewAccount}
onSettings={this.handleSettings}
onNavigate={this.basicNavigator}
onLoginRequest={this.handleLoginRequest}
onPersistenceChange={this.handlePersistenceChange}
onCreateAccount={this.handleNewAccountRequest}
onUpdateAccountDesc={this.handleTopicUpdateRequest}
onUpdatePassword={this.handleUpdatePasswordRequest}
onUpdateAccountTags={this.handleUpdateAccountTagsRequest}
onTogglePushNotifications={this.toggleFCMToken}
onToggleMessageSounds={this.handleToggleMessageSounds}
onToggleIncognitoMode={this.handleToggleIncognitoMode}
onCredAdd={this.handleCredAdd}
onCredDelete={this.handleCredDelete}
onCredConfirm={this.handleCredConfirm}
onTopicSelected={this.handleTopicSelected}
onCreateTopic={this.handleStartTopicRequest}
onLogout={this.handleLogout}
onDeleteAccount={this.handleDeleteAccount}
onShowAlert={this.handleShowAlert}
onCancel={this.handleSidepanelCancel}
onError={this.handleError}
onValidateCredentials={this.handleValidateCredentialsRequest}
onPasswordResetRequest={this.handlePasswordResetRequest}
onResetPassword={this.handleResetPassword}
onShowArchive={this.handleShowArchive}
onShowBlocked={this.handleShowBlocked}
onShowCountrySelector={this.handleShowCountrySelector}
showContextMenu={this.handleShowContextMenu} />
onInitFind={this.tnInitFind}
searchResults={this.state.searchResults}
onSearchContacts={this.handleSearchContacts}
<MessagesView
tinode={this.tinode}
connected={this.state.connected}
ready={this.state.ready}
online={this.state.topicSelectedOnline}
acs={this.state.topicSelectedAcs}
displayMobile={this.state.displayMobile}
viewportWidth={this.state.viewportWidth}
viewportHeight={this.state.viewportHeight}
hideSelf={this.state.displayMobile &&
(this.state.mobilePanel !== 'topic-view' || this.state.infoPanel)}
topic={this.state.topicSelected}
myUserId={this.state.myUserId}
// User public.fn.
myUserName={this.state.sidePanelTitle}
serverVersion={this.state.serverVersion}
serverAddress={this.state.serverAddress}
applicationVisible={this.state.applicationVisible}
showContextMenu={this.handleShowContextMenu} />
: null}
forwardMessage={this.state.forwardMessage}
onCancelForwardMessage={this.handleHideForwardDialog}
{!this.state.displayMobile || (this.state.mobilePanel == 'topic-view' && !this.state.infoPanel) ?
<MessagesView
tinode={this.tinode}
connected={this.state.connected}
ready={this.state.ready}
online={this.state.topicSelectedOnline}
acs={this.state.topicSelectedAcs}
displayMobile={this.state.displayMobile}
viewportWidth={this.state.viewportWidth}
viewportHeight={this.state.viewportHeight}
topic={this.state.topicSelected}
myUserId={this.state.myUserId}
// User public.fn.
myUserName={this.state.sidePanelTitle}
serverVersion={this.state.serverVersion}
serverAddress={this.state.serverAddress}
applicationVisible={this.state.applicationVisible}
callTopic={this.state.callTopic}
callSeq={this.state.callSeq}
callState={this.state.callState}
callAudioOnly={this.state.callAudioOnly}
onCallHangup={this.handleCallHangup}
forwardMessage={this.state.forwardMessage}
onCancelForwardMessage={this.handleHideForwardDialog}
onCallInvite={this.handleCallInvite}
onCallSendOffer={this.handleCallSendOffer}
onCallIceCandidate={this.handleCallIceCandidate}
onCallSendAnswer={this.handleCallSendAnswer}
callTopic={this.state.callTopic}
callSeq={this.state.callSeq}
callState={this.state.callState}
callAudioOnly={this.state.callAudioOnly}
onCallHangup={this.handleCallHangup}
errorText={this.state.errorText}
errorLevel={this.state.errorLevel}
errorAction={this.state.errorAction}
errorActionText={this.state.errorActionText}
onCallInvite={this.handleCallInvite}
onCallSendOffer={this.handleCallSendOffer}
onCallIceCandidate={this.handleCallIceCandidate}
onCallSendAnswer={this.handleCallSendAnswer}
newTopicParams={this.state.newTopicParams}
errorText={this.state.errorText}
errorLevel={this.state.errorLevel}
errorAction={this.state.errorAction}
errorActionText={this.state.errorActionText}
onHideMessagesView={this.handleHideMessagesView}
onError={this.handleError}
onNewTopicCreated={this.handleNewTopicCreated}
showContextMenu={this.handleShowContextMenu}
onChangePermissions={this.handleChangePermissions}
onNewChat={this.handleNewChatInvitation}
sendMessage={this.handleSendMessage}
onVideoCallClosed={this.handleCallClose} />
newTopicParams={this.state.newTopicParams}
onHideMessagesView={this.handleHideMessagesView}
onError={this.handleError}
onNewTopicCreated={this.handleNewTopicCreated}
showContextMenu={this.handleShowContextMenu}
onChangePermissions={this.handleChangePermissions}
onNewChat={this.handleNewChatInvitation}
sendMessage={this.handleSendMessage}
onVideoCallClosed={this.handleCallClose} />
: null}
{this.state.infoPanel ?
<InfoView
+1 -1
View File
@@ -29,7 +29,7 @@ class BadgeList extends React.PureComponent {
let badges = null;
if (this.props.trustedBadges && this.props.trustedBadges.length > 0) {
badges = [];
this.props.trustedBadges.map((b) => {
this.props.trustedBadges.forEach(b => {
const comment = this.props.short ? null : formatMessage(messages['badge_' + b]);
const style = 'material-icons ' + b + '-color';
badges.push(<div className="trusted-badge" key={b}><i className={style}>{icon_mapping[b] || b}</i> {comment}</div>);
+4 -1
View File
@@ -7,7 +7,7 @@ import { secondsToTime } from '../lib/strformat.js';
export default class CallMessage extends React.PureComponent {
render() {
const isCallDropped = ['declined', 'disconnected', 'missed'].includes(this.props.callState);
const isCallDropped = ['busy', 'declined', 'disconnected', 'missed'].includes(this.props.callState);
const successClass = 'material-icons medium ' + (isCallDropped ? 'red' : 'green');
const callIcon = this.props.incoming ?
(isCallDropped ? 'call_missed' : 'call_received') :
@@ -15,6 +15,9 @@ export default class CallMessage extends React.PureComponent {
let duration;
if (isCallDropped) {
switch (this.props.callState) {
case 'busy':
duration = <FormattedMessage id="call_busy" defaultMessage="busy" description="Label for busy call line" />;
break;
case 'declined':
duration = <FormattedMessage id="call_declined" defaultMessage="declined" description="Label for declined call" />;
break;
+21 -15
View File
@@ -121,7 +121,8 @@ class CallPanel extends React.PureComponent {
handleVideoCallAccepted(info) {
RING_SOUND.pause();
const pc = this.createPeerConnection();
// Create peer connection with a data channel.
const pc = this.createPeerConnection(true);
const stream = this.state.localStream;
stream.getTracks().forEach(track => {
pc.addTrack(track, stream);
@@ -230,7 +231,7 @@ class CallPanel extends React.PureComponent {
this.state.pc.onicecandidateerror = null;
this.state.pc.ondatachannel = null;
if (this.state.dataChannel) {
if (this.state.dataChannel && (this.state.dataChannel.readyState == 'open' || this.state.dataChannel.readyState == 'connecting')) {
this.state.dataChannel.close();
}
this.state.pc.close();
@@ -284,11 +285,12 @@ class CallPanel extends React.PureComponent {
}
}
handleDataChannelClose() {
/* no action */
handleDataChannelClose(event) {
console.log('close data channel:', event);
}
handleDataChannelEvent(event) {
console.log('data channel event:', event);
const channel = event.channel;
channel.onerror = this.handleDataChannelError;
channel.onmessage = this.handleDataChannelMessage;
@@ -297,7 +299,7 @@ class CallPanel extends React.PureComponent {
this.setState({dataChannel: channel});
}
createPeerConnection() {
createPeerConnection(withDataChannel) {
const iceServers = this.props.tinode.getServerParam('iceServers', null);
const pc = iceServers ? new RTCPeerConnection({iceServers: iceServers}) : new RTCPeerConnection();
@@ -310,15 +312,17 @@ class CallPanel extends React.PureComponent {
pc.ontrack = this.handleTrackEvent;
pc.ondatachannel = this.handleDataChannelEvent;
// creating data channel events
const channel = pc.createDataChannel("events", {ordered: true});
channel.onerror = this.handleDataChannelError;
channel.onmessage = this.handleDataChannelMessage;
channel.onopen = this.handleDataChannelOpen;
channel.onclose = this.handleDataChannelClose;
this.setState({pc: pc, dataChannel: channel, waitingForPeer: false});
let stateUpdate = {pc: pc, waitingForPeer: false};
if (withDataChannel) {
// Create data channel for exchanging events.
const channel = pc.createDataChannel("events", {ordered: true});
channel.onerror = this.handleDataChannelError;
channel.onmessage = this.handleDataChannelMessage;
channel.onopen = this.handleDataChannelOpen;
channel.onclose = this.handleDataChannelClose;
stateUpdate.dataChannel = channel;
}
this.setState(stateUpdate);
return pc;
}
@@ -441,7 +445,9 @@ class CallPanel extends React.PureComponent {
handleVideoOfferMsg(info) {
let localStream = null;
const pc = this.state.pc ? this.state.pc : this.createPeerConnection();
// If needed, create peer connection.
// Not creating data channel since it should be created by the peer.
const pc = this.state.pc ? this.state.pc : this.createPeerConnection(false);
const desc = new RTCSessionDescription(info.payload);
pc.setRemoteDescription(desc).then(_ => {
+4 -1
View File
@@ -9,13 +9,16 @@ import { secondsToTime } from '../lib/strformat.js';
// success: true | false.
export default class CallStatus extends React.PureComponent {
render() {
const isCallDropped = ['declined', 'disconnected', 'missed'].includes(this.props.callState);
const isCallDropped = ['busy', 'declined', 'disconnected', 'missed'].includes(this.props.callState);
const icon2 = this.props.incoming ?
(isCallDropped ? 'call_missed' : 'call_received') :
(isCallDropped ? 'call_missed_outgoing' : 'call_made');
let duration;
if (isCallDropped) {
switch (this.props.callState) {
case 'busy':
duration = <FormattedMessage id="call_busy" defaultMessage="busy" description="Label for busy call line" />;
break;
case 'declined':
duration = <FormattedMessage id="call_declined" defaultMessage="declined" description="Label for declined call" />;
break;
+19 -14
View File
@@ -93,23 +93,28 @@ class BaseChatMessage extends React.PureComponent {
if (this.props.received == Tinode.MESSAGE_STATUS_FAILED) {
menuItems.push('menu_item_send_retry');
}
if (this.props.userIsWriter && this.props.received > Tinode.MESSAGE_STATUS_FAILED) {
menuItems.push('menu_item_reply');
if (!this.props.response) {
let immutable = false;
Drafty.entities(this.props.content, (_0, _1, tp) => {
immutable = ['AU', 'EX', 'FM', 'IM', 'VC', 'VD'].includes(tp);
return immutable;
});
if (!immutable) {
Drafty.styles(this.props.content, tp => {
immutable = ['QQ'].includes(tp);
if (this.props.received > Tinode.MESSAGE_STATUS_FATAL) {
if (this.props.userIsWriter) {
menuItems.push('menu_item_reply');
if (!this.props.response) {
let immutable = false;
Drafty.entities(this.props.content, (_0, _1, tp) => {
immutable = ['AU', 'EX', 'FM', 'IM', 'VC', 'VD'].includes(tp);
return immutable;
});
if (!immutable) {
Drafty.styles(this.props.content, tp => {
immutable = ['QQ'].includes(tp);
return immutable;
});
}
if (!immutable) {
menuItems.push('menu_item_edit');
}
}
if (!immutable) {
menuItems.push('menu_item_edit');
}
}
if (this.props.userIsAdmin) {
menuItems.push(this.props.pinned ? 'menu_item_unpin' : 'menu_item_pin');
}
}
menuItems.push('menu_item_forward');
+3 -3
View File
@@ -44,7 +44,7 @@ export default class ChipInput extends React.Component {
static indexChips(chips) {
const index = {};
let count = 0;
chips.map((item) => {
chips.forEach(item => {
index[item.user] = count;
count ++;
});
@@ -55,7 +55,7 @@ export default class ChipInput extends React.Component {
static sortChips(chips, keep) {
const required = [];
const normal = [];
chips.map((item) => {
chips.forEach(item => {
if (keep && keep.includes(item.user)) {
required.push(item);
} else {
@@ -115,7 +115,7 @@ export default class ChipInput extends React.Component {
const chips = [];
let count = 0;
const staticMembers = this.props.staticMembers || [];
this.state.sortedChips.map((item) => {
this.state.sortedChips.forEach(item => {
chips.push(
<Chip
tinode={this.props.tinode}
+1 -1
View File
@@ -8,7 +8,7 @@ export default class ContactBadges extends React.PureComponent {
let badges = null;
if (this.props.badges && this.props.badges.length > 0) {
badges = [];
this.props.badges.map((b) => {
this.props.badges.forEach(b => {
const color = (b.color ? ' ' + b.color : '');
if (b.icon) {
badges.push(<i className={'material-icons as-badge' + color} key={b.key || b.icon}>{icon_mapping[b.icon] || b.icon}</i>);
+1 -1
View File
@@ -32,7 +32,7 @@ class ContactList extends React.Component {
const contactNodes = [];
let contactsCount = 0;
if (this.props.contacts && this.props.contacts.length > 0) {
this.props.contacts.map((c) => {
this.props.contacts.forEach(c => {
if (c.action) {
// Action item
contactNodes.push(
+35 -1
View File
@@ -116,6 +116,16 @@ const messages = defineMessages({
defaultMessage: 'Are you sure you want to clear all messages? It cannot be undone.',
description: 'Alert dialog warning when deleting all messages.'
},
pin_message: {
id: 'pin_message',
defaultMessage: 'Pin',
description: 'Menu item [Pin] for pinning message to the top.'
},
unpin_message: {
id: 'unpin_message',
defaultMessage: 'Unpin',
description: 'Menu item [Unpin] for un-pinning the message.'
},
});
class ContextMenu extends React.Component {
@@ -205,6 +215,20 @@ class ContextMenu extends React.Component {
return this.editMessage(params, errorHandler);
}
},
'menu_item_pin': {
id: 'menu_item_pin',
title: formatMessage(messages.pin_message),
handler: (params, errorHandler) => {
return this.pinMessage(true, params, errorHandler);
}
},
'menu_item_unpin': {
id: 'menu_item_unpin',
title: formatMessage(messages.unpin_message),
handler: (params, errorHandler) => {
return this.pinMessage(false, params, errorHandler);
}
},
'topic_unmute': {
id: 'topic_unmute',
title: formatMessage(messages.unmute),
@@ -428,6 +452,16 @@ class ContextMenu extends React.Component {
});
}
// Pin or unpin the message.
pinMessage(pin, params, errorHandler) {
const topic = this.props.tinode.getTopic(params.topicName);
if (!topic) {
return;
}
topic.pinMessage(params.seq, pin)
.catch(err => errorHandler ? errorHandler(err.message, 'err') : null);
}
// Function is used by context menu to set permissions.
topicPermissionSetter(mode, params, errorHandler) {
const topic = this.props.tinode.getTopic(params.topicName);
@@ -454,7 +488,7 @@ class ContextMenu extends React.Component {
render() {
const menu = [];
let count = 0;
this.props.items.map((item) => {
this.props.items.forEach(item => {
if (typeof item == 'string') {
item = this.MenuItems[item];
}
+1 -1
View File
@@ -84,7 +84,7 @@ class CredentialEdit extends React.PureComponent {
const changePhone = (
<><div className="panel-form-row">
<label className="large"><FormattedMessage id="change_phonel" defaultMessage="Change phone number"
<label className="large"><FormattedMessage id="change_phone" defaultMessage="Change phone number"
description="Prompt to change current phone number"/></label>
</div>
<div className="group">
+16
View File
@@ -17,6 +17,22 @@ export default class DocPreview extends React.PureComponent {
super(props);
this.handleSendDoc = this.handleSendDoc.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this);
}
componentDidMount() {
document.addEventListener('keydown', this.handleKeyDown);
}
componentWillUnmount() {
document.removeEventListener('keydown', this.handleKeyDown);
}
handleKeyDown(e) {
e.preventDefault();
if (e.key === 'Escape') {
this.props.onClose();
}
}
handleSendDoc(caption) {
+11 -13
View File
@@ -41,7 +41,7 @@ class GroupManager extends React.Component {
static indexMembers(members) {
let index = {};
members.map((m) => {
members.forEach(m => {
index[m.user] = {delta: 0, present: true}; // Delta: 0 unchanged, +1 added, -1 removed
});
return index;
@@ -49,7 +49,7 @@ class GroupManager extends React.Component {
static staticMembers(members, keepInitial, requiredMember) {
let stat = [];
members.map((m) => {
members.forEach(m => {
if (keepInitial || m.user == requiredMember) {
stat.push(m.user);
}
@@ -59,7 +59,7 @@ class GroupManager extends React.Component {
static selectedContacts(members) {
let sel = [];
members.map((m) => {
members.forEach(m => {
sel.push(m.user);
});
return sel;
@@ -130,20 +130,19 @@ class GroupManager extends React.Component {
}
handleSubmit() {
var instance = this;
var members = [];
var added = [];
var removed = [];
const members = [];
const added = [];
const removed = [];
var keys = Object.keys(this.state.index);
keys.map(function(k) {
if (instance.state.index[k].present) {
const keys = Object.keys(this.state.index);
keys.forEach(k => {
if (this.state.index[k].present) {
members.push(k);
}
if (instance.state.index[k].delta > 0) {
if (this.state.index[k].delta > 0) {
added.push(k);
} else if (instance.state.index[k].delta < 0) {
} else if (this.state.index[k].delta < 0) {
removed.push(k);
}
});
@@ -155,7 +154,6 @@ class GroupManager extends React.Component {
}
render() {
const {formatMessage} = this.props.intl;
return (
<div id="group-manager">
<div className="panel-form-row">
+3 -1
View File
@@ -30,7 +30,9 @@ export default class GroupSubs extends React.Component {
);
return usersOnline.length == countToShow;
});
if (usersOnline.length == 0) {
return null;
}
return (
<div id="topic-users">{usersOnline} {totalCount > countToShow ?
<span>
+1 -1
View File
@@ -36,7 +36,7 @@ export default class HostSelector extends React.PureComponent {
}
render() {
var hostOptions = [];
const hostOptions = [];
for (let key in KNOWN_HOSTS) {
let item = KNOWN_HOSTS[key];
hostOptions.push(
+21 -1
View File
@@ -16,6 +16,26 @@ export default class ImagePreview extends React.PureComponent {
};
this.handleSendImage = this.handleSendImage.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this);
}
componentDidMount() {
document.addEventListener('keydown', this.handleKeyDown);
}
componentWillUnmount() {
document.removeEventListener('keydown', this.handleKeyDown);
}
handleKeyDown(e) {
if (this.props.onSendMessage) {
return;
}
e.preventDefault();
if (e.key === 'Escape') {
this.props.onClose();
}
}
assignWidth(node) {
@@ -64,7 +84,7 @@ export default class ImagePreview extends React.PureComponent {
}
<a href="#" onClick={(e) => {e.preventDefault(); this.props.onClose();}}><i className="material-icons gray">close</i></a>
</div>
<div id="image-preview-container" ref={(node) => this.assignWidth(node)}>
<div id="image-preview-container" ref={node => this.assignWidth(node)}>
<img src={this.props.content.url} style={size} className="image-preview" alt={this.props.content.filename} />
</div>
{this.props.onSendMessage ?
+5 -4
View File
@@ -27,11 +27,12 @@ export default class InlineVideo extends React.PureComponent {
return (
<div className={className}>
{React.createElement('img', this.props)}
<div className="play-control">
{this.props.onClick ?
<div className="play-control">
<i className="material-icons white bigger">play_arrow</i>
</div>
: null}
<i className="material-icons white bigger">play_arrow</i>
:
<img src="img/broken_video.png" style={{filter: 'invert(100%)'}} width="36" height="36" />}
</div>
{duration ? <div className="duration">{duration}</div> : null}
</div>
);
+94
View File
@@ -0,0 +1,94 @@
// Send message form.
import React from 'react';
import { injectIntl } from 'react-intl';
import { Drafty } from 'tinode-sdk';
import { previewFormatter } from '../lib/formatters.js';
class PinnedMessages extends React.PureComponent {
constructor(props) {
super(props);
this.getSelectedIndex = this.getSelectedIndex.bind(this);
this.handleCancel = this.handleCancel.bind(this);
this.handleSelected = this.handleSelected.bind(this);
this.handleMoveNext = this.handleMoveNext.bind(this);
this.handleMovePrev = this.handleMovePrev.bind(this);
}
getSelectedIndex() {
const list = (this.props.messages || []);
return list.length - this.props.selected - 1;
}
handleCancel(e) {
e.preventDefault();
this.props.onCancel(this.props.messages[this.getSelectedIndex()].seq);
}
handleSelected(e) {
e.preventDefault();
this.props.onSelected(this.props.messages[this.getSelectedIndex()].seq);
}
handleMoveNext(e) {
e.preventDefault();
e.stopPropagation();
const idx = Math.max(this.props.selected - 1, 0);
if (idx != this.props.selected) {
this.props.setSelected(idx);
}
}
handleMovePrev(e) {
e.preventDefault();
e.stopPropagation();
const idx = Math.min(this.props.selected + 1, (this.props.messages || []).length - 1);
if (idx != this.props.selected) {
this.props.setSelected(idx);
}
}
render() {
const selected = this.getSelectedIndex();
let shown = (this.props.messages || [])[selected];
shown = shown ? Drafty.format(shown.content, previewFormatter, {
formatMessage: this.props.intl.formatMessage.bind(this.props.intl),
authorizeURL: this.props.tinode.authorizeURL.bind(this.props.tinode)
}) : null;
const dots = [];
this.props.messages.forEach(_ => {
const cn = dots.length == selected ? 'adot' : 'dot';
dots.push(<div key={dots.length} className={cn} />);
});
return shown ?
(<div id="pinned-wrapper">
{this.props.isAdmin ?
<div className="cancel">
<a href="#" onClick={this.handleCancel}><i className="material-icons gray">close</i></a>
</div> :
<div><i className="material-icons gray">push_pin</i></div>
}
<div className="pinned-scroll">{dots}</div>
<div className="pinned" onClick={this.handleSelected}>{shown}</div>
{selected > 0 ?
<span className="menuTrigger upper">
<a href="#" onClick={this.handleMovePrev}>
<i className="material-icons">expand_less</i>
</a>
</span>
: null}
{this.props.selected > 0 ?
<span className="menuTrigger lower">
<a href="#" onClick={this.handleMoveNext}>
<i className="material-icons">expand_more</i>
</a>
</span>
: null}
</div>) : null;
}
}
export default injectIntl(PinnedMessages);
+2 -1
View File
@@ -30,7 +30,8 @@ class ReceivedMarker extends React.PureComponent {
let timestamp;
if (this.props.received <= Tinode.MESSAGE_STATUS_SENDING) {
timestamp = formatMessage(messages.message_sending);
} else if (this.props.received == Tinode.MESSAGE_STATUS_FAILED) {
} else if (this.props.received == Tinode.MESSAGE_STATUS_FAILED ||
this.props.received == Tinode.MESSAGE_STATUS_FATAL) {
timestamp = formatMessage(messages.message_sending_failed);
} else {
timestamp = this.props.timestamp.toLocaleTimeString(this.props.intl.locale, { timeStyle: 'short' });
+5 -1
View File
@@ -111,6 +111,10 @@ class SendMessage extends React.PureComponent {
// Set focus on desktop, but not on mobile: focus causes soft keyboard to pop up.
this.messageEditArea.focus();
}
// Adjust height of the message area for the amount of text.
this.messageEditArea.style.height = '0px';
this.messageEditArea.style.height = this.messageEditArea.scrollHeight + 'px';
}
if (prevProps.topicName != this.props.topicName) {
@@ -276,7 +280,7 @@ class SendMessage extends React.PureComponent {
</Suspense>) :
<textarea id="send-message-input" placeholder={prompt}
value={this.state.message} onChange={this.handleMessageTyping}
onKeyPress={this.handleKeyPress}
onKeyDown={this.handleKeyPress}
ref={ref => {this.messageEditArea = ref;}} />)}
{this.state.message || !audioEnabled ?
<a href="#" onClick={this.handleSend} title={formatMessage(messages.icon_title_send)}>
+1 -1
View File
@@ -12,7 +12,7 @@ export default class SideNavbar extends React.PureComponent {
render() {
const icon_badges = [];
if (this.props.trustedBadges) {
this.props.trustedBadges.map((b) => {
this.props.trustedBadges.forEach(b => {
icon_badges.push({icon: b, color: 'badge-inv'});
});
}
+2 -2
View File
@@ -92,11 +92,11 @@ export default class TagManager extends React.Component {
let tags = [];
if (this.state.activated) {
this.state.tags.map((tag) => {
this.state.tags.forEach(tag => {
tags.push({user: tag, invalid: (tag.length < minTagLength || tag.length > maxTagLength)});
});
} else {
this.state.tags.map((tag) => {
this.state.tags.forEach(tag => {
tags.push(<span className="badge" key={tags.length}>{tag}</span>);
});
if (tags.length == 0) {
+20
View File
@@ -19,6 +19,26 @@ class VideoPreview extends React.PureComponent {
this.videoRef = React.createRef();
this.handleSendVideo = this.handleSendVideo.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this);
}
componentDidMount() {
document.addEventListener('keydown', this.handleKeyDown);
}
componentWillUnmount() {
document.removeEventListener('keydown', this.handleKeyDown);
}
handleKeyDown(e) {
if (this.props.onSendMessage) {
return;
}
e.preventDefault();
if (e.key === 'Escape') {
this.props.onClose();
}
}
handleSendVideo(caption) {
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"290.prod.js","mappings":"2MASA,MAAMA,UAA6BC,IAAAA,cACjCC,YAAYC,GACVC,MAAMD,GAENE,KAAKC,UAAY,GACjB,MAAM,kBAAEC,GAAsBJ,EAAMK,KACpCC,EAAAA,SAAuBC,IACPA,EAAGC,KAAKC,MAAM,KACtBC,SAAQC,IACZT,KAAKC,UAAUS,KAAK,CAClBJ,KAAMG,EAAKE,OACXC,KAAMP,EAAGO,KACTC,MAAMC,EAAAA,EAAAA,IAAUT,EAAGO,MACnBG,KAAMb,EAAkBG,EAAGO,KAAM,CAACI,KAAM,YACxC,GACF,IAGJhB,KAAKC,UAAUgB,MAAK,CAACC,EAAGC,IAAMD,EAAEH,KAAKK,cAAcD,EAAEJ,OACvD,CAEAM,oBACMrB,KAAKsB,aACPtB,KAAKsB,YAAYC,eAAe,CAACC,MAAO,SAAUC,OAAQ,WAE9D,CAEAC,SACE,MAAMzB,EAAY,GACZ0B,EAAW3B,KAAKF,MAAM6B,UAAY,KAYxC,OAXA3B,KAAKC,UAAUO,SAAQ,CAACoB,EAAGC,KACzB,MAAMC,EAASF,EAAEhB,MAAQe,EAAW,YAAc,GAClD1B,EAAUS,KAAK,wBAAIqB,UAAWD,EAAOE,IAAKH,EACxCI,IAAKA,IAAYL,EAAEhB,MAAQe,IAAW3B,KAAKsB,YAAcW,EAAG,EAC5DC,QAASC,GAAKnC,KAAKF,MAAMsC,SAASR,EAAEhB,KAAMgB,EAAEtB,OAC5C,0BAAMyB,UAAU,gBAAgBH,EAAEf,MAClC,0BAAMkB,UAAU,WAAU,IAAOH,EAAEb,MACnC,0BAAMgB,UAAU,aAAY,KAAQH,EAAEtB,OAClC,IAIN,yBAAKyB,UAAU,mBAAmBD,MAAO,CAACO,OAAQ,UAChD,wBAAIN,UAAU,0BACX9B,GAIT,EAGF,SAAeqC,EAAAA,EAAAA,YAAW3C,E","sources":["webpack://tinode-webapp/./src/widgets/phone-country-selector.jsx"],"sourcesContent":["// Selector for country dialing code.\n\nimport React from 'react';\nimport { injectIntl } from 'react-intl';\n\nimport * as dcodes from '../dcodes.json';\n\nimport { flagEmoji } from '../lib/strformat';\n\nclass PhoneCountrySelector extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.countries = [];\n const { formatDisplayName } = props.intl;\n dcodes.default.forEach(dc => {\n const parts = dc.dial.split(',');\n parts.forEach(part => {\n this.countries.push({\n dial: part.trim(),\n code: dc.code,\n flag: flagEmoji(dc.code),\n name: formatDisplayName(dc.code, {type: 'region'})\n });\n });\n });\n\n this.countries.sort((a, b) => a.name.localeCompare(b.name));\n }\n\n componentDidMount() {\n if (this.selectedRef) {\n this.selectedRef.scrollIntoView({block: 'center', inline: 'nearest'});\n }\n }\n\n render() {\n const countries = [];\n const selected = this.props.selected || 'US';\n this.countries.forEach((c, idx) => {\n const style = (c.code == selected ? 'selected ' : '');\n countries.push(<li className={style} key={idx}\n ref={ref => {if (c.code == selected) {this.selectedRef = ref}}}\n onClick={_ => this.props.onSubmit(c.code, c.dial)}>\n <span className=\"country-flag\">{c.flag}</span>\n <span className=\"country\">&nbsp;{c.name}</span>\n <span className=\"dial-code\">&nbsp;+{c.dial}</span>\n </li>);\n });\n\n return (\n <div className=\"scrollable-panel\" style={{height: '30rem'}}>\n <ul className=\"phone-country-selector\">\n {countries}\n </ul>\n </div>\n );\n }\n}\n\nexport default injectIntl(PhoneCountrySelector);\n"],"names":["PhoneCountrySelector","React","constructor","props","super","this","countries","formatDisplayName","intl","dcodes","dc","dial","split","forEach","part","push","trim","code","flag","flagEmoji","name","type","sort","a","b","localeCompare","componentDidMount","selectedRef","scrollIntoView","block","inline","render","selected","c","idx","style","className","key","ref","onClick","_","onSubmit","height","injectIntl"],"sourceRoot":""}
{"version":3,"file":"290.prod.js","mappings":"2MASA,MAAMA,UAA6BC,IAAAA,cACjCC,YAAYC,GACVC,MAAMD,GAENE,KAAKC,UAAY,GACjB,MAAM,kBAAEC,GAAsBJ,EAAMK,KACpCC,EAAAA,SAAuBC,IACPA,EAAGC,KAAKC,MAAM,KACtBC,SAAQC,IACZT,KAAKC,UAAUS,KAAK,CAClBJ,KAAMG,EAAKE,OACXC,KAAMP,EAAGO,KACTC,MAAMC,EAAAA,EAAAA,IAAUT,EAAGO,MACnBG,KAAMb,EAAkBG,EAAGO,KAAM,CAACI,KAAM,YACxC,GACF,IAGJhB,KAAKC,UAAUgB,MAAK,CAACC,EAAGC,IAAMD,EAAEH,KAAKK,cAAcD,EAAEJ,OACvD,CAEAM,oBACMrB,KAAKsB,aACPtB,KAAKsB,YAAYC,eAAe,CAACC,MAAO,SAAUC,OAAQ,WAE9D,CAEAC,SACE,MAAMzB,EAAY,GACZ0B,EAAW3B,KAAKF,MAAM6B,UAAY,KAYxC,OAXA3B,KAAKC,UAAUO,SAAQ,CAACoB,EAAGC,KACzB,MAAMC,EAASF,EAAEhB,MAAQe,EAAW,YAAc,GAClD1B,EAAUS,KAAKd,IAAAA,cAAA,MAAImC,UAAWD,EAAOE,IAAKH,EACxCI,IAAKA,IAAYL,EAAEhB,MAAQe,IAAW3B,KAAKsB,YAAcW,EAAG,EAC5DC,QAASC,GAAKnC,KAAKF,MAAMsC,SAASR,EAAEhB,KAAMgB,EAAEtB,OAC5CV,IAAAA,cAAA,QAAMmC,UAAU,gBAAgBH,EAAEf,MAClCjB,IAAAA,cAAA,QAAMmC,UAAU,WAAU,IAAOH,EAAEb,MACnCnB,IAAAA,cAAA,QAAMmC,UAAU,aAAY,KAAQH,EAAEtB,OAClC,IAINV,IAAAA,cAAA,OAAKmC,UAAU,mBAAmBD,MAAO,CAACO,OAAQ,UAChDzC,IAAAA,cAAA,MAAImC,UAAU,0BACX9B,GAIT,EAGF,SAAeqC,EAAAA,EAAAA,YAAW3C,E","sources":["webpack://tinode-webapp/./src/widgets/phone-country-selector.jsx"],"sourcesContent":["// Selector for country dialing code.\n\nimport React from 'react';\nimport { injectIntl } from 'react-intl';\n\nimport * as dcodes from '../dcodes.json';\n\nimport { flagEmoji } from '../lib/strformat';\n\nclass PhoneCountrySelector extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.countries = [];\n const { formatDisplayName } = props.intl;\n dcodes.default.forEach(dc => {\n const parts = dc.dial.split(',');\n parts.forEach(part => {\n this.countries.push({\n dial: part.trim(),\n code: dc.code,\n flag: flagEmoji(dc.code),\n name: formatDisplayName(dc.code, {type: 'region'})\n });\n });\n });\n\n this.countries.sort((a, b) => a.name.localeCompare(b.name));\n }\n\n componentDidMount() {\n if (this.selectedRef) {\n this.selectedRef.scrollIntoView({block: 'center', inline: 'nearest'});\n }\n }\n\n render() {\n const countries = [];\n const selected = this.props.selected || 'US';\n this.countries.forEach((c, idx) => {\n const style = (c.code == selected ? 'selected ' : '');\n countries.push(<li className={style} key={idx}\n ref={ref => {if (c.code == selected) {this.selectedRef = ref}}}\n onClick={_ => this.props.onSubmit(c.code, c.dial)}>\n <span className=\"country-flag\">{c.flag}</span>\n <span className=\"country\">&nbsp;{c.name}</span>\n <span className=\"dial-code\">&nbsp;+{c.dial}</span>\n </li>);\n });\n\n return (\n <div className=\"scrollable-panel\" style={{height: '30rem'}}>\n <ul className=\"phone-country-selector\">\n {countries}\n </ul>\n </div>\n );\n }\n}\n\nexport default injectIntl(PhoneCountrySelector);\n"],"names":["PhoneCountrySelector","React","constructor","props","super","this","countries","formatDisplayName","intl","dcodes","dc","dial","split","forEach","part","push","trim","code","flag","flagEmoji","name","type","sort","a","b","localeCompare","componentDidMount","selectedRef","scrollIntoView","block","inline","render","selected","c","idx","style","className","key","ref","onClick","_","onSubmit","height","injectIntl"],"sourceRoot":""}
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+2
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
-2
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+478 -168
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -351,7 +351,7 @@ class CredentialEdit extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureC
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", {
className: "large"
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
id: "change_phonel",
id: "change_phone",
defaultMessage: [{
"type": 0,
"value": "Change phone number"
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
{"version":3,"file":"src_widgets_phone-country-selector_jsx.dev.js","mappings":";;;;;;;;;;;;;;;;;;;AAE0B;AACc;AAEC;AAEI;AAE7C,MAAMI,oBAAoB,SAASJ,4DAAmB,CAAC;EACrDM,WAAW,CAACC,KAAK,EAAE;IACjB,KAAK,CAACA,KAAK,CAAC;IAEZ,IAAI,CAACC,SAAS,GAAG,EAAE;IACnB,MAAM;MAAEC;IAAkB,CAAC,GAAGF,KAAK,CAACG,IAAI;IACxCR,iDAAsB,CAACW,EAAE,IAAI;MAC3B,MAAMC,KAAK,GAAGD,EAAE,CAACE,IAAI,CAACC,KAAK,CAAC,GAAG,CAAC;MAChCF,KAAK,CAACF,OAAO,CAACK,IAAI,IAAI;QACpB,IAAI,CAACT,SAAS,CAACU,IAAI,CAAC;UAClBH,IAAI,EAAEE,IAAI,CAACE,IAAI,EAAE;UACjBC,IAAI,EAAEP,EAAE,CAACO,IAAI;UACbC,IAAI,EAAElB,yDAAS,CAACU,EAAE,CAACO,IAAI,CAAC;UACxBE,IAAI,EAAEb,iBAAiB,CAACI,EAAE,CAACO,IAAI,EAAE;YAACG,IAAI,EAAE;UAAQ,CAAC;QACnD,CAAC,CAAC;MACJ,CAAC,CAAC;IACJ,CAAC,CAAC;IAEF,IAAI,CAACf,SAAS,CAACgB,IAAI,CAAC,CAACC,CAAC,EAAEC,CAAC,KAAKD,CAAC,CAACH,IAAI,CAACK,aAAa,CAACD,CAAC,CAACJ,IAAI,CAAC,CAAC;EAC7D;EAEAM,iBAAiB,GAAG;IAClB,IAAI,IAAI,CAACC,WAAW,EAAE;MACpB,IAAI,CAACA,WAAW,CAACC,cAAc,CAAC;QAACC,KAAK,EAAE,QAAQ;QAAEC,MAAM,EAAE;MAAS,CAAC,CAAC;IACvE;EACF;EAEAC,MAAM,GAAG;IACP,MAAMzB,SAAS,GAAG,EAAE;IACpB,MAAM0B,QAAQ,GAAG,IAAI,CAAC3B,KAAK,CAAC2B,QAAQ,IAAI,IAAI;IAC5C,IAAI,CAAC1B,SAAS,CAACI,OAAO,CAAC,CAACuB,CAAC,EAAEC,GAAG,KAAK;MACjC,MAAMC,KAAK,GAAIF,CAAC,CAACf,IAAI,IAAIc,QAAQ,GAAG,WAAW,GAAG,EAAG;MACrD1B,SAAS,CAACU,IAAI,CAAC;QAAI,SAAS,EAAEmB,KAAM;QAAC,GAAG,EAAED,GAAI;QAC5C,GAAG,EAAEE,GAAG,IAAI;UAAC,IAAIH,CAAC,CAACf,IAAI,IAAIc,QAAQ,EAAE;YAAC,IAAI,CAACL,WAAW,GAAGS,GAAG;UAAA;QAAC,CAAE;QAC/D,OAAO,EAAEC,CAAC,IAAI,IAAI,CAAChC,KAAK,CAACiC,QAAQ,CAACL,CAAC,CAACf,IAAI,EAAEe,CAAC,CAACpB,IAAI;MAAE,GAClD;QAAM,SAAS,EAAC;MAAc,GAAEoB,CAAC,CAACd,IAAI,CAAQ,EAC9C;QAAM,SAAS,EAAC;MAAS,GAAC,MAAM,EAACc,CAAC,CAACb,IAAI,CAAQ,EAC/C;QAAM,SAAS,EAAC;MAAW,GAAC,OAAO,EAACa,CAAC,CAACpB,IAAI,CAAQ,CAC/C,CAAC;IACR,CAAC,CAAC;IAEF,OACE;MAAK,SAAS,EAAC,kBAAkB;MAAC,KAAK,EAAE;QAAC0B,MAAM,EAAE;MAAO;IAAE,GACzD;MAAI,SAAS,EAAC;IAAwB,GACnCjC,SAAS,CACP,CACD;EAEV;AACF;AAEA,iEAAeP,sDAAU,CAACG,oBAAoB,CAAC","sources":["webpack://tinode-webapp/./src/widgets/phone-country-selector.jsx"],"sourcesContent":["// Selector for country dialing code.\n\nimport React from 'react';\nimport { injectIntl } from 'react-intl';\n\nimport * as dcodes from '../dcodes.json';\n\nimport { flagEmoji } from '../lib/strformat';\n\nclass PhoneCountrySelector extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.countries = [];\n const { formatDisplayName } = props.intl;\n dcodes.default.forEach(dc => {\n const parts = dc.dial.split(',');\n parts.forEach(part => {\n this.countries.push({\n dial: part.trim(),\n code: dc.code,\n flag: flagEmoji(dc.code),\n name: formatDisplayName(dc.code, {type: 'region'})\n });\n });\n });\n\n this.countries.sort((a, b) => a.name.localeCompare(b.name));\n }\n\n componentDidMount() {\n if (this.selectedRef) {\n this.selectedRef.scrollIntoView({block: 'center', inline: 'nearest'});\n }\n }\n\n render() {\n const countries = [];\n const selected = this.props.selected || 'US';\n this.countries.forEach((c, idx) => {\n const style = (c.code == selected ? 'selected ' : '');\n countries.push(<li className={style} key={idx}\n ref={ref => {if (c.code == selected) {this.selectedRef = ref}}}\n onClick={_ => this.props.onSubmit(c.code, c.dial)}>\n <span className=\"country-flag\">{c.flag}</span>\n <span className=\"country\">&nbsp;{c.name}</span>\n <span className=\"dial-code\">&nbsp;+{c.dial}</span>\n </li>);\n });\n\n return (\n <div className=\"scrollable-panel\" style={{height: '30rem'}}>\n <ul className=\"phone-country-selector\">\n {countries}\n </ul>\n </div>\n );\n }\n}\n\nexport default injectIntl(PhoneCountrySelector);\n"],"names":["React","injectIntl","dcodes","flagEmoji","PhoneCountrySelector","PureComponent","constructor","props","countries","formatDisplayName","intl","default","forEach","dc","parts","dial","split","part","push","trim","code","flag","name","type","sort","a","b","localeCompare","componentDidMount","selectedRef","scrollIntoView","block","inline","render","selected","c","idx","style","ref","_","onSubmit","height"],"sourceRoot":""}
{"version":3,"file":"src_widgets_phone-country-selector_jsx.dev.js","mappings":";;;;;;;;;;;;;;;;;;;AAE0B;AACc;AAEC;AAEI;AAE7C,MAAMI,oBAAoB,SAASJ,4DAAmB,CAAC;EACrDM,WAAWA,CAACC,KAAK,EAAE;IACjB,KAAK,CAACA,KAAK,CAAC;IAEZ,IAAI,CAACC,SAAS,GAAG,EAAE;IACnB,MAAM;MAAEC;IAAkB,CAAC,GAAGF,KAAK,CAACG,IAAI;IACxCR,iDAAsB,CAACW,EAAE,IAAI;MAC3B,MAAMC,KAAK,GAAGD,EAAE,CAACE,IAAI,CAACC,KAAK,CAAC,GAAG,CAAC;MAChCF,KAAK,CAACF,OAAO,CAACK,IAAI,IAAI;QACpB,IAAI,CAACT,SAAS,CAACU,IAAI,CAAC;UAClBH,IAAI,EAAEE,IAAI,CAACE,IAAI,EAAE;UACjBC,IAAI,EAAEP,EAAE,CAACO,IAAI;UACbC,IAAI,EAAElB,yDAAS,CAACU,EAAE,CAACO,IAAI,CAAC;UACxBE,IAAI,EAAEb,iBAAiB,CAACI,EAAE,CAACO,IAAI,EAAE;YAACG,IAAI,EAAE;UAAQ,CAAC;QACnD,CAAC,CAAC;MACJ,CAAC,CAAC;IACJ,CAAC,CAAC;IAEF,IAAI,CAACf,SAAS,CAACgB,IAAI,CAAC,CAACC,CAAC,EAAEC,CAAC,KAAKD,CAAC,CAACH,IAAI,CAACK,aAAa,CAACD,CAAC,CAACJ,IAAI,CAAC,CAAC;EAC7D;EAEAM,iBAAiBA,CAAA,EAAG;IAClB,IAAI,IAAI,CAACC,WAAW,EAAE;MACpB,IAAI,CAACA,WAAW,CAACC,cAAc,CAAC;QAACC,KAAK,EAAE,QAAQ;QAAEC,MAAM,EAAE;MAAS,CAAC,CAAC;IACvE;EACF;EAEAC,MAAMA,CAAA,EAAG;IACP,MAAMzB,SAAS,GAAG,EAAE;IACpB,MAAM0B,QAAQ,GAAG,IAAI,CAAC3B,KAAK,CAAC2B,QAAQ,IAAI,IAAI;IAC5C,IAAI,CAAC1B,SAAS,CAACI,OAAO,CAAC,CAACuB,CAAC,EAAEC,GAAG,KAAK;MACjC,MAAMC,KAAK,GAAIF,CAAC,CAACf,IAAI,IAAIc,QAAQ,GAAG,WAAW,GAAG,EAAG;MACrD1B,SAAS,CAACU,IAAI,CAAClB,0DAAA;QAAIuC,SAAS,EAAEF,KAAM;QAACG,GAAG,EAAEJ,GAAI;QAC5CK,GAAG,EAAEA,GAAG,IAAI;UAAC,IAAIN,CAAC,CAACf,IAAI,IAAIc,QAAQ,EAAE;YAAC,IAAI,CAACL,WAAW,GAAGY,GAAG;UAAA;QAAC,CAAE;QAC/DC,OAAO,EAAEC,CAAC,IAAI,IAAI,CAACpC,KAAK,CAACqC,QAAQ,CAACT,CAAC,CAACf,IAAI,EAAEe,CAAC,CAACpB,IAAI;MAAE,GAClDf,0DAAA;QAAMuC,SAAS,EAAC;MAAc,GAAEJ,CAAC,CAACd,IAAI,CAAQ,EAC9CrB,0DAAA;QAAMuC,SAAS,EAAC;MAAS,GAAC,MAAM,EAACJ,CAAC,CAACb,IAAI,CAAQ,EAC/CtB,0DAAA;QAAMuC,SAAS,EAAC;MAAW,GAAC,OAAO,EAACJ,CAAC,CAACpB,IAAI,CAAQ,CAC/C,CAAC;IACR,CAAC,CAAC;IAEF,OACEf,0DAAA;MAAKuC,SAAS,EAAC,kBAAkB;MAACF,KAAK,EAAE;QAACQ,MAAM,EAAE;MAAO;IAAE,GACzD7C,0DAAA;MAAIuC,SAAS,EAAC;IAAwB,GACnC/B,SAAS,CACP,CACD;EAEV;AACF;AAEA,iEAAeP,sDAAU,CAACG,oBAAoB,CAAC","sources":["webpack://tinode-webapp/./src/widgets/phone-country-selector.jsx"],"sourcesContent":["// Selector for country dialing code.\n\nimport React from 'react';\nimport { injectIntl } from 'react-intl';\n\nimport * as dcodes from '../dcodes.json';\n\nimport { flagEmoji } from '../lib/strformat';\n\nclass PhoneCountrySelector extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.countries = [];\n const { formatDisplayName } = props.intl;\n dcodes.default.forEach(dc => {\n const parts = dc.dial.split(',');\n parts.forEach(part => {\n this.countries.push({\n dial: part.trim(),\n code: dc.code,\n flag: flagEmoji(dc.code),\n name: formatDisplayName(dc.code, {type: 'region'})\n });\n });\n });\n\n this.countries.sort((a, b) => a.name.localeCompare(b.name));\n }\n\n componentDidMount() {\n if (this.selectedRef) {\n this.selectedRef.scrollIntoView({block: 'center', inline: 'nearest'});\n }\n }\n\n render() {\n const countries = [];\n const selected = this.props.selected || 'US';\n this.countries.forEach((c, idx) => {\n const style = (c.code == selected ? 'selected ' : '');\n countries.push(<li className={style} key={idx}\n ref={ref => {if (c.code == selected) {this.selectedRef = ref}}}\n onClick={_ => this.props.onSubmit(c.code, c.dial)}>\n <span className=\"country-flag\">{c.flag}</span>\n <span className=\"country\">&nbsp;{c.name}</span>\n <span className=\"dial-code\">&nbsp;+{c.dial}</span>\n </li>);\n });\n\n return (\n <div className=\"scrollable-panel\" style={{height: '30rem'}}>\n <ul className=\"phone-country-selector\">\n {countries}\n </ul>\n </div>\n );\n }\n}\n\nexport default injectIntl(PhoneCountrySelector);\n"],"names":["React","injectIntl","dcodes","flagEmoji","PhoneCountrySelector","PureComponent","constructor","props","countries","formatDisplayName","intl","default","forEach","dc","parts","dial","split","part","push","trim","code","flag","name","type","sort","a","b","localeCompare","componentDidMount","selectedRef","scrollIntoView","block","inline","render","selected","c","idx","style","createElement","className","key","ref","onClick","_","onSubmit","height"],"sourceRoot":""}

Some files were not shown because too many files have changed in this diff Show More