Compare commits
54
Commits
caf02056ab
...
ebe47b345d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ebe47b345d | ||
|
|
454dc204c8 | ||
|
|
d0c88fbd0d | ||
|
|
e68ebc8336 | ||
|
|
cca97d410e | ||
|
|
eafd87fb17 | ||
|
|
71ee3fbec4 | ||
|
|
11fe81fdf1 | ||
|
|
9640c604b9 | ||
|
|
6273b3287f | ||
|
|
9c5e3a3eca | ||
|
|
9098c1f6a6 | ||
|
|
a2439be540 | ||
|
|
a2fb3fe1a0 | ||
|
|
560e3369e4 | ||
|
|
491175cecc | ||
|
|
6d96d327f1 | ||
|
|
6ee2851943 | ||
|
|
192a3d4ec2 | ||
|
|
c6f7911c82 | ||
|
|
9a1f0c734a | ||
|
|
52f879490e | ||
|
|
8df3c34cf4 | ||
|
|
2fb6a9bb0a | ||
|
|
5ae58fb2a6 | ||
|
|
43798ea1d9 | ||
|
|
323042bd96 | ||
|
|
172b9eb26d | ||
|
|
1b90c613f7 | ||
|
|
c6b4460389 | ||
|
|
497318b722 | ||
|
|
423f53880d | ||
|
|
a93ede25e9 | ||
|
|
b4d3e0274f | ||
|
|
4f116ab3e6 | ||
|
|
75b2880b63 | ||
|
|
1149980a47 | ||
|
|
87b48cd67d | ||
|
|
4be09a034e | ||
|
|
3c0b57333c | ||
|
|
0c9b23eee9 | ||
|
|
d11f94095f | ||
|
|
133ed3cc53 | ||
|
|
7bc50fac36 | ||
|
|
fbdd017d38 | ||
|
|
8fdb4a8a4c | ||
|
|
e1b1ae503f | ||
|
|
7b44913724 | ||
|
|
7e3b2fc254 | ||
|
|
fc6a8fbafc | ||
|
|
111b9d8641 | ||
|
|
eeea27ca82 | ||
|
|
fc2ff79329 | ||
|
|
f755314c3b |
+115
-13
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -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>
|
||||
|
||||
Generated
+3997
-5361
File diff suppressed because it is too large
Load Diff
+19
-18
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
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
+22
-1
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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";
|
||||
|
||||
@@ -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
@@ -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}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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>);
|
||||
|
||||
@@ -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
@@ -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(_ => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>);
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 ?
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
@@ -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' });
|
||||
|
||||
@@ -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)}>
|
||||
|
||||
@@ -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'});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -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\"> {c.name}</span>\n <span className=\"dial-code\"> +{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\"> {c.name}</span>\n <span className=\"dial-code\"> +{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
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
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
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
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
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+478
-168
File diff suppressed because it is too large
Load Diff
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
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\"> {c.name}</span>\n <span className=\"dial-code\"> +{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\"> {c.name}</span>\n <span className=\"dial-code\"> +{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
Reference in New Issue
Block a user