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;
|
height: 2.5rem;
|
||||||
border-bottom: 1px solid #ccc;
|
border-bottom: 1px solid #ccc;
|
||||||
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.1);
|
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"],
|
input[type="text"],
|
||||||
@@ -273,11 +273,6 @@ textarea.inplace-edit {
|
|||||||
background-color: rgba(0,0,0,0.3);
|
background-color: rgba(0,0,0,0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Firefox only */
|
|
||||||
* {
|
|
||||||
scrollbar-width: thin;
|
|
||||||
scrollbar-color: silver;
|
|
||||||
}
|
|
||||||
/* END of Scrollbar styling. */
|
/* END of Scrollbar styling. */
|
||||||
|
|
||||||
/* BEGIN Popup/dropdown menu */
|
/* BEGIN Popup/dropdown menu */
|
||||||
@@ -315,9 +310,8 @@ ul.menu > li.separator:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* The down arrow head - trigger for context menu */
|
/* The down arrow head - trigger for context menu */
|
||||||
li .menuTrigger {
|
.menuTrigger {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0.25rem;
|
|
||||||
right: 0.25rem;
|
right: 0.25rem;
|
||||||
width: 1.4rem;
|
width: 1.4rem;
|
||||||
height: 1.4rem;
|
height: 1.4rem;
|
||||||
@@ -327,6 +321,13 @@ li .menuTrigger {
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
background-color: inherit;
|
background-color: inherit;
|
||||||
}
|
}
|
||||||
|
.menuTrigger a {
|
||||||
|
color: #757575;
|
||||||
|
}
|
||||||
|
|
||||||
|
li .menuTrigger {
|
||||||
|
top: 0.25rem;
|
||||||
|
}
|
||||||
li:hover .menuTrigger {
|
li:hover .menuTrigger {
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
}
|
}
|
||||||
@@ -1392,6 +1393,7 @@ img.channel {
|
|||||||
#topic-users {
|
#topic-users {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: 1rem;
|
margin-right: 1rem;
|
||||||
|
width: min-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
#topic-users .avatar-box {
|
#topic-users .avatar-box {
|
||||||
@@ -1513,6 +1515,13 @@ img.channel {
|
|||||||
color: #2196f3!important;
|
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 */
|
/* New chat invitation panel */
|
||||||
|
|
||||||
.accept-invite-panel {
|
.accept-invite-panel {
|
||||||
@@ -1608,6 +1617,7 @@ img.channel {
|
|||||||
line-height: 1.5rem;
|
line-height: 1.5rem;
|
||||||
background-color: #eceff1;
|
background-color: #eceff1;
|
||||||
box-shadow: 0px -2px 3px -1px rgba(0,0,0,0.1); /* Faint shadow above panel */
|
box-shadow: 0px -2px 3px -1px rgba(0,0,0,0.1); /* Faint shadow above panel */
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#image-preview-container .material-icons {
|
#image-preview-container .material-icons {
|
||||||
@@ -1856,9 +1866,9 @@ video {
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
color: #212121;
|
color: #212121;
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
box-shadow: 0 1px 0.5px rgba(0, 0, 0, 0.12);
|
|
||||||
transition: ease-out background-color 0.5s;
|
transition: ease-out background-color 0.5s;
|
||||||
transition-delay: 0.3s;
|
transition-delay: 0.3s;
|
||||||
|
filter: drop-shadow(0.5px 0.5px 0.7px rgb(0,0,0,0.3));
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-box li.single {
|
.chat-box li.single {
|
||||||
@@ -1926,7 +1936,7 @@ video {
|
|||||||
.meta .bubble.date {
|
.meta .bubble.date {
|
||||||
font-size: smaller;
|
font-size: smaller;
|
||||||
padding: 0.5rem 0.5rem 0.5rem 0.5rem;
|
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;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1940,7 +1950,7 @@ video {
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
background-color: #c5e1a5;
|
background-color: #c5e1a5;
|
||||||
clip-path: polygon(100% 0, 0 110%, 100% 110%);
|
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;
|
border-bottom-left-radius: 0.3rem;
|
||||||
transition: ease-out background-color 0.5s;
|
transition: ease-out background-color 0.5s;
|
||||||
transition-delay: 0.3s;
|
transition-delay: 0.3s;
|
||||||
@@ -1960,7 +1970,7 @@ video {
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
background-color: #fafafa;
|
background-color: #fafafa;
|
||||||
clip-path: polygon(0 0, 100% 110%, 0 110%);
|
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;
|
border-bottom-right-radius: 0.3rem;
|
||||||
transition: ease-out background-color 0.5s;
|
transition: ease-out background-color 0.5s;
|
||||||
transition-delay: 0.3s;
|
transition-delay: 0.3s;
|
||||||
@@ -2317,6 +2327,7 @@ ul.tabbar li.active > a {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
padding-bottom: 100%;
|
padding-bottom: 100%;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Div with letter tile content */
|
/* Div with letter tile content */
|
||||||
@@ -3016,6 +3027,84 @@ ul.tabbar li.active > a {
|
|||||||
color: #666C;
|
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.
|
Drag-and-drop area.
|
||||||
*/
|
*/
|
||||||
@@ -3103,6 +3192,10 @@ ul.phone-country-selector span.country {
|
|||||||
#mountPoint {
|
#mountPoint {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pinned {
|
||||||
|
min-width: 12rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
@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 */
|
/* Must make it a bit visible on mobile because there is no :hover */
|
||||||
li .menuTrigger {
|
.menuTrigger {
|
||||||
opacity: 0.3;
|
opacity: 0.3;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3136,4 +3229,13 @@ ul.phone-country-selector span.country {
|
|||||||
/* Just an indicator that the screen is in mobile mode */
|
/* Just an indicator that the screen is in mobile mode */
|
||||||
transition-property: -tinode-mobile;
|
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" />
|
<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@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-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>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript><div id="noscript-message"><div id="noscript-title">Javascript support is required.</div>
|
<noscript><div id="noscript-message"><div id="noscript-title">Javascript support is required.</div>
|
||||||
|
|||||||
Generated
+3997
-5361
File diff suppressed because it is too large
Load Diff
+19
-18
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "tinode-webapp",
|
"name": "tinode-webapp",
|
||||||
"description": "Tinode messenger for the web",
|
"description": "Tinode messenger for the web",
|
||||||
"version": "0.21.0",
|
"version": "0.23.0-rc1",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/tinode/example-react-js.git"
|
"url": "https://github.com/tinode/webapp.git"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "echo 'Read installation instructions'",
|
"start": "echo 'Read installation instructions'",
|
||||||
@@ -58,35 +58,36 @@
|
|||||||
],
|
],
|
||||||
"main": "umd/index.prod.js",
|
"main": "umd/index.prod.js",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/tinode/example-react-js/issues"
|
"url": "https://github.com/tinode/webapp/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/tinode/chat",
|
"homepage": "https://github.com/tinode/chat",
|
||||||
"email": "info@tinode.co",
|
"email": "info@tinode.co",
|
||||||
"author": "Tinode Authors <info@tinode.co>",
|
"author": "Tinode Authors <info@tinode.co>",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"firebase": "^9.14.0",
|
"firebase": "^9.17.2",
|
||||||
"libphonenumber-js": "^1.10.18",
|
"libphonenumber-js": "^1.10.24",
|
||||||
|
"npm-check-updates": "^16.7.12",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-intl": "^6.2.1",
|
"react-intl": "^6.3.0",
|
||||||
"tinode-sdk": "^0.21.0",
|
"tinode-sdk": "^0.23.0-rc1",
|
||||||
"webm-duration-fix": "^1.0.4"
|
"webm-duration-fix": "^1.0.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.20.2",
|
"@babel/core": "^7.21.3",
|
||||||
"@babel/preset-env": "^7.20.2",
|
"@babel/preset-env": "^7.20.2",
|
||||||
"@babel/preset-react": "^7.18.6",
|
"@babel/preset-react": "^7.18.6",
|
||||||
"@formatjs/cli": "^5.1.3",
|
"@formatjs/cli": "^6.0.4",
|
||||||
"babel-loader": "^9.1.0",
|
"babel-loader": "^9.1.2",
|
||||||
"babel-plugin-formatjs": "^10.3.31",
|
"babel-plugin-formatjs": "^10.4.0",
|
||||||
"browserslist": "^4.21.4",
|
"browserslist": "^4.21.5",
|
||||||
"copy-webpack-plugin": "^11.0.0",
|
"copy-webpack-plugin": "^11.0.0",
|
||||||
"cssnano": "^5.1.14",
|
"cssnano": "^5.1.15",
|
||||||
"glob": "^8.0.3",
|
"glob": "^9.3.0",
|
||||||
"postcss": "^8.4.19",
|
"postcss": "^8.4.21",
|
||||||
"postcss-cli": "^10.0.0",
|
"postcss-cli": "^10.1.0",
|
||||||
"webpack": "^5.75.0",
|
"webpack": "^5.76.1",
|
||||||
"webpack-cli": "^5.0.0"
|
"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,
|
"missing": true,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
"change_phonel": {
|
"change_phone": {
|
||||||
"translation": "",
|
"translation": "",
|
||||||
"defaultMessage": "Change phone number",
|
"defaultMessage": "Change phone number",
|
||||||
"description": "Prompt to change current phone number",
|
"description": "Prompt to change current phone number",
|
||||||
"missing": true,
|
"missing": true,
|
||||||
"obsolete": false
|
"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,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
"change_phonel": {
|
"change_phone": {
|
||||||
"translation": "Change phone number",
|
"translation": "Change phone number",
|
||||||
"defaultMessage": "Change phone number",
|
"defaultMessage": "Change phone number",
|
||||||
"description": "Prompt to change current phone number",
|
"description": "Prompt to change current phone number",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": 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,
|
"missing": true,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
"change_phonel": {
|
"change_phone": {
|
||||||
"translation": "",
|
"translation": "",
|
||||||
"defaultMessage": "Change phone number",
|
"defaultMessage": "Change phone number",
|
||||||
"description": "Prompt to change current phone number",
|
"description": "Prompt to change current phone number",
|
||||||
"missing": true,
|
"missing": true,
|
||||||
"obsolete": false
|
"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",
|
"defaultMessage": "Update",
|
||||||
"description": "Button [Update]"
|
"description": "Button [Update]"
|
||||||
},
|
},
|
||||||
|
"call_busy": {
|
||||||
|
"defaultMessage": "busy",
|
||||||
|
"description": "Label for busy call line"
|
||||||
|
},
|
||||||
"call_cancelled": {
|
"call_cancelled": {
|
||||||
"defaultMessage": "cancelled",
|
"defaultMessage": "cancelled",
|
||||||
"description": "Label for cancelled outgoing call"
|
"description": "Label for cancelled outgoing call"
|
||||||
@@ -159,7 +163,7 @@
|
|||||||
"defaultMessage": "Change email",
|
"defaultMessage": "Change email",
|
||||||
"description": "Prompt to change current email"
|
"description": "Prompt to change current email"
|
||||||
},
|
},
|
||||||
"change_phonel": {
|
"change_phone": {
|
||||||
"defaultMessage": "Change phone number",
|
"defaultMessage": "Change phone number",
|
||||||
"description": "Prompt to change current phone number"
|
"description": "Prompt to change current phone number"
|
||||||
},
|
},
|
||||||
@@ -752,6 +756,10 @@
|
|||||||
"defaultMessage": "phone",
|
"defaultMessage": "phone",
|
||||||
"description": "Dative case of 'phone', i.e. 'phone' in 'by 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": {
|
"private_editing_placeholder": {
|
||||||
"defaultMessage": "Visible to you only",
|
"defaultMessage": "Visible to you only",
|
||||||
"description": "Placeholder for editing 'private'"
|
"description": "Placeholder for editing 'private'"
|
||||||
@@ -908,6 +916,10 @@
|
|||||||
"defaultMessage": "Unnamed",
|
"defaultMessage": "Unnamed",
|
||||||
"description": "Title shown when the topic has no name"
|
"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": {
|
"unrecognized_video_format": {
|
||||||
"defaultMessage": "Format of this video is not recognized",
|
"defaultMessage": "Format of this video is not recognized",
|
||||||
"description": "Error message when uploaded video is invalid"
|
"description": "Error message when uploaded video is invalid"
|
||||||
|
|||||||
+22
-1
@@ -1634,11 +1634,32 @@
|
|||||||
"missing": true,
|
"missing": true,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
"change_phonel": {
|
"change_phone": {
|
||||||
"translation": "",
|
"translation": "",
|
||||||
"defaultMessage": "Change phone number",
|
"defaultMessage": "Change phone number",
|
||||||
"description": "Prompt to change current phone number",
|
"description": "Prompt to change current phone number",
|
||||||
"missing": true,
|
"missing": true,
|
||||||
"obsolete": false
|
"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,
|
"missing": true,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
"change_phonel": {
|
"change_phone": {
|
||||||
"translation": "",
|
"translation": "",
|
||||||
"defaultMessage": "Change phone number",
|
"defaultMessage": "Change phone number",
|
||||||
"description": "Prompt to change current phone number",
|
"description": "Prompt to change current phone number",
|
||||||
"missing": true,
|
"missing": true,
|
||||||
"obsolete": false
|
"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,
|
"missing": true,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
"change_phonel": {
|
"change_phone": {
|
||||||
"translation": "",
|
"translation": "",
|
||||||
"defaultMessage": "Change phone number",
|
"defaultMessage": "Change phone number",
|
||||||
"description": "Prompt to change current phone number",
|
"description": "Prompt to change current phone number",
|
||||||
"missing": true,
|
"missing": true,
|
||||||
"obsolete": false
|
"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,
|
"missing": true,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
"change_phonel": {
|
"change_phone": {
|
||||||
"translation": "",
|
"translation": "",
|
||||||
"defaultMessage": "Change phone number",
|
"defaultMessage": "Change phone number",
|
||||||
"description": "Prompt to change current phone number",
|
"description": "Prompt to change current phone number",
|
||||||
"missing": true,
|
"missing": true,
|
||||||
"obsolete": false
|
"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,
|
"missing": true,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
"change_phonel": {
|
"change_phone": {
|
||||||
"translation": "",
|
"translation": "",
|
||||||
"defaultMessage": "Change phone number",
|
"defaultMessage": "Change phone number",
|
||||||
"description": "Prompt to change current phone number",
|
"description": "Prompt to change current phone number",
|
||||||
"missing": true,
|
"missing": true,
|
||||||
"obsolete": false
|
"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
|
"obsolete": false
|
||||||
},
|
},
|
||||||
"label_default_access_mode": {
|
"label_default_access_mode": {
|
||||||
"translation": "蓦然访问模式:",
|
"translation": "默认访问模式:",
|
||||||
"defaultMessage": "Default access mode:",
|
"defaultMessage": "Default access mode:",
|
||||||
"description": "Label for default access mode",
|
"description": "Label for default access mode",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
@@ -1634,11 +1634,32 @@
|
|||||||
"missing": true,
|
"missing": true,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
"change_phonel": {
|
"change_phone": {
|
||||||
"translation": "",
|
"translation": "",
|
||||||
"defaultMessage": "Change phone number",
|
"defaultMessage": "Change phone number",
|
||||||
"description": "Prompt to change current phone number",
|
"description": "Prompt to change current phone number",
|
||||||
"missing": true,
|
"missing": true,
|
||||||
"obsolete": false
|
"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';
|
import HashNavigation from './lib/navigation.js';
|
||||||
|
|
||||||
// Insert google analytics script and tag if configured.
|
// 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];
|
const head = document.getElementsByTagName('head')[0];
|
||||||
let script = document.createElement('script');
|
let script = document.createElement('script');
|
||||||
script.src = 'https://www.googletagmanager.com/gtag/js?id=' + FIREBASE_INIT.measurementId;
|
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.
|
// Get mime type from data URL header.
|
||||||
export function getMimeType(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;
|
return (mime && mime.length > 1) ? mime[1] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -236,8 +236,6 @@ function handleVideoData(el, data, attr) {
|
|||||||
if (data.ref || data.val) {
|
if (data.ref || data.val) {
|
||||||
attr.onClick = this.onVideoPreview;
|
attr.onClick = this.onVideoPreview;
|
||||||
attr.loading = 'lazy';
|
attr.loading = 'lazy';
|
||||||
} else {
|
|
||||||
attr.src = null;
|
|
||||||
}
|
}
|
||||||
el = InlineVideo;
|
el = InlineVideo;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export function secondsToTime(seconds, fixedMin) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let min = (Math.floor(seconds / 60) | 0) % 60;
|
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) {
|
if (fixedMin || hours > 0) {
|
||||||
min = min < 10 ? `0${min}` : min;
|
min = min < 10 ? `0${min}` : min;
|
||||||
|
|||||||
@@ -172,6 +172,7 @@ export function deliveryMarker(received) {
|
|||||||
case Tinode.MESSAGE_STATUS_SENDING:
|
case Tinode.MESSAGE_STATUS_SENDING:
|
||||||
return { name: 'access_time' }; // watch face
|
return { name: 'access_time' }; // watch face
|
||||||
case Tinode.MESSAGE_STATUS_FAILED:
|
case Tinode.MESSAGE_STATUS_FAILED:
|
||||||
|
case Tinode.MESSAGE_STATUS_FATAL:
|
||||||
return { name: 'warning', color: 'danger-color' }; // yellow icon /!\
|
return { name: 'warning', color: 'danger-color' }; // yellow icon /!\
|
||||||
case Tinode.MESSAGE_STATUS_SENT:
|
case Tinode.MESSAGE_STATUS_SENT:
|
||||||
return { name: 'done' }; // checkmark
|
return { name: 'done' }; // checkmark
|
||||||
|
|||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
// This is a generated file. Don't edit.
|
// This is a generated file. Don't edit.
|
||||||
|
|
||||||
export const PACKAGE_VERSION = "0.21.0";
|
export const PACKAGE_VERSION = "0.23.0-rc1";
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export default class ContactsView extends React.Component {
|
|||||||
const contacts = [];
|
const contacts = [];
|
||||||
let unreadThreads = 0;
|
let unreadThreads = 0;
|
||||||
let archivedCount = 0;
|
let archivedCount = 0;
|
||||||
props.chatList.map((c) => {
|
props.chatList.forEach(c => {
|
||||||
const blocked = c.acs && !c.acs.isJoiner();
|
const blocked = c.acs && !c.acs.isJoiner();
|
||||||
// Show only blocked contacts only when props.blocked == true.
|
// Show only blocked contacts only when props.blocked == true.
|
||||||
if (blocked && props.blocked) {
|
if (blocked && props.blocked) {
|
||||||
|
|||||||
+114
-41
@@ -17,6 +17,7 @@ import LetterTile from '../widgets/letter-tile.jsx';
|
|||||||
import LoadSpinner from '../widgets/load-spinner.jsx';
|
import LoadSpinner from '../widgets/load-spinner.jsx';
|
||||||
import LogoView from './logo-view.jsx';
|
import LogoView from './logo-view.jsx';
|
||||||
import MetaMessage from '../widgets/meta-message.jsx';
|
import MetaMessage from '../widgets/meta-message.jsx';
|
||||||
|
import PinnedMessages from '../widgets/pinned-messages.jsx';
|
||||||
import SendMessage from '../widgets/send-message.jsx';
|
import SendMessage from '../widgets/send-message.jsx';
|
||||||
import VideoPreview from '../widgets/video-preview.jsx';
|
import VideoPreview from '../widgets/video-preview.jsx';
|
||||||
|
|
||||||
@@ -32,6 +33,8 @@ import { bytesToHumanSize, relativeDateFormat, shortDateFormat } from '../lib/st
|
|||||||
const NOTIFICATION_EXEC_INTERVAL = 300;
|
const NOTIFICATION_EXEC_INTERVAL = 300;
|
||||||
// Scroll distance before [go to latest message] button is shown.
|
// Scroll distance before [go to latest message] button is shown.
|
||||||
const SHOW_GO_TO_LAST_DIST = 100;
|
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({
|
const messages = defineMessages({
|
||||||
online_now: {
|
online_now: {
|
||||||
@@ -121,6 +124,7 @@ class MessagesView extends React.Component {
|
|||||||
this.handleDescChange = this.handleDescChange.bind(this);
|
this.handleDescChange = this.handleDescChange.bind(this);
|
||||||
this.handleSubsUpdated = this.handleSubsUpdated.bind(this);
|
this.handleSubsUpdated = this.handleSubsUpdated.bind(this);
|
||||||
this.handleMessageUpdate = this.handleMessageUpdate.bind(this);
|
this.handleMessageUpdate = this.handleMessageUpdate.bind(this);
|
||||||
|
this.handleAuxUpdate = this.handleAuxUpdate.bind(this);
|
||||||
this.handleAllMessagesReceived = this.handleAllMessagesReceived.bind(this);
|
this.handleAllMessagesReceived = this.handleAllMessagesReceived.bind(this);
|
||||||
this.handleInfoReceipt = this.handleInfoReceipt.bind(this);
|
this.handleInfoReceipt = this.handleInfoReceipt.bind(this);
|
||||||
this.handleExpandMedia = this.handleExpandMedia.bind(this);
|
this.handleExpandMedia = this.handleExpandMedia.bind(this);
|
||||||
@@ -142,9 +146,11 @@ class MessagesView extends React.Component {
|
|||||||
this.handleEditMessage = this.handleEditMessage.bind(this);
|
this.handleEditMessage = this.handleEditMessage.bind(this);
|
||||||
this.handleCancelReply = this.handleCancelReply.bind(this);
|
this.handleCancelReply = this.handleCancelReply.bind(this);
|
||||||
this.handleQuoteClick = this.handleQuoteClick.bind(this);
|
this.handleQuoteClick = this.handleQuoteClick.bind(this);
|
||||||
|
this.handleUnpinMessage = this.handleUnpinMessage.bind(this);
|
||||||
this.handleCallHangup = this.handleCallHangup.bind(this);
|
this.handleCallHangup = this.handleCallHangup.bind(this);
|
||||||
|
|
||||||
this.isDragEnabled = this.isDragEnabled.bind(this);
|
this.isDragEnabled = this.isDragEnabled.bind(this);
|
||||||
|
this.handleDragStart = this.handleDragStart.bind(this);
|
||||||
this.handleDragIn = this.handleDragIn.bind(this);
|
this.handleDragIn = this.handleDragIn.bind(this);
|
||||||
this.handleDragOut = this.handleDragOut.bind(this);
|
this.handleDragOut = this.handleDragOut.bind(this);
|
||||||
this.handleDrag = this.handleDrag.bind(this);
|
this.handleDrag = this.handleDrag.bind(this);
|
||||||
@@ -181,12 +187,7 @@ class MessagesView extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Drag and drop events
|
// Drag and drop events
|
||||||
if (this.dndRef) {
|
this.mountDnDEvents(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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
@@ -199,6 +200,7 @@ class MessagesView extends React.Component {
|
|||||||
|
|
||||||
// Drag and drop events
|
// Drag and drop events
|
||||||
if (this.dndRef) {
|
if (this.dndRef) {
|
||||||
|
this.dndRef.removeEventListener('dragstart', this.handleDragStart);
|
||||||
this.dndRef.removeEventListener('dragenter', this.handleDragIn);
|
this.dndRef.removeEventListener('dragenter', this.handleDragIn);
|
||||||
this.dndRef.removeEventListener('dragleave', this.handleDragOut);
|
this.dndRef.removeEventListener('dragleave', this.handleDragOut);
|
||||||
this.dndRef.removeEventListener('dragover', this.handleDrag);
|
this.dndRef.removeEventListener('dragover', this.handleDrag);
|
||||||
@@ -210,12 +212,12 @@ class MessagesView extends React.Component {
|
|||||||
// or vertical shrinking.
|
// or vertical shrinking.
|
||||||
componentDidUpdate(prevProps, prevState) {
|
componentDidUpdate(prevProps, prevState) {
|
||||||
if (this.messagesScroller &&
|
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.
|
// New message.
|
||||||
if (this.state.scrollPosition < SHOW_GO_TO_LAST_DIST) {
|
if (this.state.scrollPosition < SHOW_GO_TO_LAST_DIST) {
|
||||||
this.messagesScroller.scrollTop = this.messagesScroller.scrollHeight -
|
this.messagesScroller.scrollTop = this.messagesScroller.scrollHeight -
|
||||||
this.state.scrollPosition -
|
this.state.scrollPosition - this.messagesScroller.offsetHeight;
|
||||||
this.messagesScroller.offsetHeight;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,6 +237,7 @@ class MessagesView extends React.Component {
|
|||||||
topic.onMetaDesc = this.handleDescChange;
|
topic.onMetaDesc = this.handleDescChange;
|
||||||
topic.onSubsUpdated = this.handleSubsUpdated;
|
topic.onSubsUpdated = this.handleSubsUpdated;
|
||||||
topic.onPres = this.handleSubsUpdated;
|
topic.onPres = this.handleSubsUpdated;
|
||||||
|
topic.onAuxUpdated = this.handleAuxUpdate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,7 +264,8 @@ class MessagesView extends React.Component {
|
|||||||
if (!nextProps.topic) {
|
if (!nextProps.topic) {
|
||||||
// Default state: no topic.
|
// Default state: no topic.
|
||||||
nextState = {
|
nextState = {
|
||||||
messageCount: 0,
|
minSeqId: -1,
|
||||||
|
maxSeqId: -1,
|
||||||
latestClearId: -1,
|
latestClearId: -1,
|
||||||
onlineSubs: [],
|
onlineSubs: [],
|
||||||
topic: null,
|
topic: null,
|
||||||
@@ -285,7 +289,9 @@ class MessagesView extends React.Component {
|
|||||||
reply: null,
|
reply: null,
|
||||||
contentToEdit: null,
|
contentToEdit: null,
|
||||||
showGoToLastButton: false,
|
showGoToLastButton: false,
|
||||||
dragging: false
|
dragging: false,
|
||||||
|
pins: [],
|
||||||
|
selectedPin: 0
|
||||||
};
|
};
|
||||||
} else if (nextProps.topic != prevState.topic) {
|
} else if (nextProps.topic != prevState.topic) {
|
||||||
const topic = nextProps.tinode.getTopic(nextProps.topic);
|
const topic = nextProps.tinode.getTopic(nextProps.topic);
|
||||||
@@ -304,7 +310,8 @@ class MessagesView extends React.Component {
|
|||||||
fetchingMessages: false,
|
fetchingMessages: false,
|
||||||
showGoToLastButton: false,
|
showGoToLastButton: false,
|
||||||
contentToEdit: null,
|
contentToEdit: null,
|
||||||
dragging: false
|
dragging: false,
|
||||||
|
selectedPin: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
if (nextProps.forwardMessage) {
|
if (nextProps.forwardMessage) {
|
||||||
@@ -356,9 +363,11 @@ class MessagesView extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
Object.assign(nextState, {
|
Object.assign(nextState, {
|
||||||
messageCount: topic.messageCount(),
|
minSeqId: topic.minMsgSeq(),
|
||||||
|
maxSeqId: topic.maxMsgSeq(),
|
||||||
latestClearId: topic.maxClearId(),
|
latestClearId: topic.maxClearId(),
|
||||||
channel: topic.isChannelType()
|
channel: topic.isChannelType(),
|
||||||
|
pins: (topic.aux('pins') || []).slice()
|
||||||
});
|
});
|
||||||
|
|
||||||
if (nextProps.callTopic == topic.name && shouldPresentCallPanel(nextProps.callState)) {
|
if (nextProps.callTopic == topic.name && shouldPresentCallPanel(nextProps.callState)) {
|
||||||
@@ -367,13 +376,15 @@ class MessagesView extends React.Component {
|
|||||||
} else {
|
} else {
|
||||||
// Invalid topic.
|
// Invalid topic.
|
||||||
Object.assign(nextState, {
|
Object.assign(nextState, {
|
||||||
messageCount: 0,
|
minSeqId: -1,
|
||||||
|
maxSeqId: -1,
|
||||||
latestClearId: -1,
|
latestClearId: -1,
|
||||||
onlineSubs: [],
|
onlineSubs: [],
|
||||||
title: '',
|
title: '',
|
||||||
avatar: null,
|
avatar: null,
|
||||||
peerMessagingDisabled: false,
|
peerMessagingDisabled: false,
|
||||||
channel: false
|
channel: false,
|
||||||
|
pins: []
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -391,6 +402,9 @@ class MessagesView extends React.Component {
|
|||||||
if (nextProps.acs.isReader() != prevState.isReader) {
|
if (nextProps.acs.isReader() != prevState.isReader) {
|
||||||
nextState.isReader = !prevState.isReader;
|
nextState.isReader = !prevState.isReader;
|
||||||
}
|
}
|
||||||
|
if (nextProps.acs.isAdmin() != prevState.isAdmin) {
|
||||||
|
nextState.isAdmin = !prevState.isAdmin;
|
||||||
|
}
|
||||||
if (!nextProps.acs.isReader('given') != prevState.readingBlocked) {
|
if (!nextProps.acs.isReader('given') != prevState.readingBlocked) {
|
||||||
nextState.readingBlocked = !prevState.readingBlocked;
|
nextState.readingBlocked = !prevState.readingBlocked;
|
||||||
}
|
}
|
||||||
@@ -404,6 +418,9 @@ class MessagesView extends React.Component {
|
|||||||
if (prevState.isReader) {
|
if (prevState.isReader) {
|
||||||
nextState.isReader = false;
|
nextState.isReader = false;
|
||||||
}
|
}
|
||||||
|
if (prevState.isAdmin) {
|
||||||
|
nextState.isAdmin = false;
|
||||||
|
}
|
||||||
if (!prevState.readingBlocked) {
|
if (!prevState.readingBlocked) {
|
||||||
prevState.readingBlocked = true;
|
prevState.readingBlocked = true;
|
||||||
}
|
}
|
||||||
@@ -434,7 +451,7 @@ class MessagesView extends React.Component {
|
|||||||
|
|
||||||
// Don't request the tags. They are useless unless the user
|
// Don't request the tags. They are useless unless the user
|
||||||
// is the owner and is editing the topic.
|
// is the owner and is editing the topic.
|
||||||
let getQuery = topic.startMetaQuery().withLaterDesc().withLaterSub();
|
let getQuery = topic.startMetaQuery().withLaterDesc().withLaterSub().withAux();
|
||||||
if (this.state.isReader || newTopic) {
|
if (this.state.isReader || newTopic) {
|
||||||
// Reading is either permitted or we don't know because it's a new topic. Ask for messages.
|
// Reading is either permitted or we don't know because it's a new topic. Ask for messages.
|
||||||
getQuery = getQuery.withLaterData(MESSAGES_PAGE);
|
getQuery = getQuery.withLaterData(MESSAGES_PAGE);
|
||||||
@@ -457,24 +474,24 @@ class MessagesView extends React.Component {
|
|||||||
this.setState({topic: ctrl.topic});
|
this.setState({topic: ctrl.topic});
|
||||||
}
|
}
|
||||||
this.props.onNewTopicCreated(this.props.topic, ctrl.topic);
|
this.props.onNewTopicCreated(this.props.topic, ctrl.topic);
|
||||||
// If there are unsent messages (except video call messages),
|
// If there are unsent messages (except hard-failed and video call messages),
|
||||||
// try sending them now. Unsent video call messages will be dropped.
|
// try sending them now. Hard-failed and unsent video call messages will be dropped.
|
||||||
let calls = [];
|
let discard = [];
|
||||||
topic.queuedMessages((pub) => {
|
topic.queuedMessages(pub => {
|
||||||
if (pub._sending) {
|
if (pub._sending) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (pub.head && pub.head.webrtc) {
|
if (pub._fatal || (pub.head && pub.head.webrtc)) {
|
||||||
// Filter out unsent video call messages.
|
// Filter out unsent failed & video call messages.
|
||||||
calls.push(pub.seq);
|
discard.push(pub.seq);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (topic.isSubscribed()) {
|
if (topic.isSubscribed()) {
|
||||||
this.retrySend(pub);
|
this.retrySend(pub);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (calls.length > 0) {
|
if (discard.length > 0) {
|
||||||
topic.delMessagesList(calls, true);
|
topic.delMessagesList(discard, true);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
@@ -505,6 +522,7 @@ class MessagesView extends React.Component {
|
|||||||
oldTopic.onMetaDesc = undefined;
|
oldTopic.onMetaDesc = undefined;
|
||||||
oldTopic.onSubsUpdated = undefined;
|
oldTopic.onSubsUpdated = undefined;
|
||||||
oldTopic.onPres = undefined;
|
oldTopic.onPres = undefined;
|
||||||
|
oldTopic.onAuxUpdated = undefined;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -514,7 +532,8 @@ class MessagesView extends React.Component {
|
|||||||
if (node) {
|
if (node) {
|
||||||
node.addEventListener('scroll', this.handleScrollEvent);
|
node.addEventListener('scroll', this.handleScrollEvent);
|
||||||
this.messagesScroller = node;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.target.scrollTop <= 0) {
|
if (event.target.scrollTop <= FETCH_PAGE_TRIGGER) {
|
||||||
const topic = this.props.tinode.getTopic(this.state.topic);
|
const topic = this.props.tinode.getTopic(this.state.topic);
|
||||||
if (topic && topic.isSubscribed() && topic.msgHasMoreMessages()) {
|
if (topic && topic.isSubscribed() && topic.msgHasMoreMessages()) {
|
||||||
this.setState({fetchingMessages: true}, _ => {
|
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) {
|
mountDnDEvents(dnd) {
|
||||||
if (dnd) {
|
if (dnd) {
|
||||||
|
dnd.addEventListener('dragstart', this.handleDragStart);
|
||||||
dnd.addEventListener('dragenter', this.handleDragIn);
|
dnd.addEventListener('dragenter', this.handleDragIn);
|
||||||
dnd.addEventListener('dragleave', this.handleDragOut);
|
dnd.addEventListener('dragleave', this.handleDragOut);
|
||||||
dnd.addEventListener('dragover', this.handleDrag);
|
dnd.addEventListener('dragover', this.handleDrag);
|
||||||
@@ -557,7 +577,7 @@ class MessagesView extends React.Component {
|
|||||||
goToLatestMessage() {
|
goToLatestMessage() {
|
||||||
this.setState({scrollPosition: 0});
|
this.setState({scrollPosition: 0});
|
||||||
if (this.messagesScroller) {
|
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({
|
this.setState({
|
||||||
isWriter: desc.acs.isWriter(),
|
isWriter: desc.acs.isWriter(),
|
||||||
isReader: desc.acs.isReader(),
|
isReader: desc.acs.isReader(),
|
||||||
|
isAdmin: desc.acs.isAdmin(),
|
||||||
readingBlocked: !desc.acs.isReader('given'),
|
readingBlocked: !desc.acs.isReader('given'),
|
||||||
unconfirmed: isUnconfirmed(desc.acs),
|
unconfirmed: isUnconfirmed(desc.acs),
|
||||||
});
|
});
|
||||||
@@ -684,7 +705,7 @@ class MessagesView extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
clearTimeout(this.keyPressTimer)
|
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
|
// 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.
|
// 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.
|
// Implemented as a callback to be sure the scroll height has been updated.
|
||||||
@@ -694,6 +715,11 @@ class MessagesView extends React.Component {
|
|||||||
} else {
|
} else {
|
||||||
this.goToLatestMessage();
|
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) {
|
handleInfoReceipt(info) {
|
||||||
switch (info.what) {
|
switch (info.what) {
|
||||||
case 'kp': {
|
case 'kp': {
|
||||||
@@ -739,9 +777,9 @@ class MessagesView extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (content.video) {
|
if (content.video) {
|
||||||
this.setState({ videoPostview: content });
|
this.setState({videoPostview: content});
|
||||||
} else {
|
} else {
|
||||||
this.setState({ imagePostview: content });
|
this.setState({imagePostview: content});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -766,7 +804,7 @@ class MessagesView extends React.Component {
|
|||||||
params.set(key, data.resp[key]);
|
params.set(key, data.resp[key]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
['name', 'seq'].map((key) => {
|
['name', 'seq'].forEach(key => {
|
||||||
if (data[key]) {
|
if (data[key]) {
|
||||||
params.set(key, data[key]);
|
params.set(key, data[key]);
|
||||||
}
|
}
|
||||||
@@ -1057,8 +1095,8 @@ class MessagesView extends React.Component {
|
|||||||
// Small preview to show while uploading.
|
// Small preview to show while uploading.
|
||||||
b64conv[2] = imageScaled(previewBlob, VIDEO_PREVIEW_DIM, VIDEO_PREVIEW_DIM, -1, false)
|
b64conv[2] = imageScaled(previewBlob, VIDEO_PREVIEW_DIM, VIDEO_PREVIEW_DIM, -1, false)
|
||||||
.then(scaled => blobToBase64(scaled.blob));
|
.then(scaled => blobToBase64(scaled.blob));
|
||||||
Promise.all(b64conv)
|
|
||||||
// Convert tiny image into base64 for serialization and previewing.
|
// Convert tiny image into base64 for serialization and previewing.
|
||||||
|
Promise.all(b64conv)
|
||||||
.then(b64s => {
|
.then(b64s => {
|
||||||
const [video, img, preview] = b64s;
|
const [video, img, preview] = b64s;
|
||||||
let msg = Drafty.insertVideo(null, 0, {
|
let msg = Drafty.insertVideo(null, 0, {
|
||||||
@@ -1074,6 +1112,7 @@ class MessagesView extends React.Component {
|
|||||||
size: videoBlob.size,
|
size: videoBlob.size,
|
||||||
urlPromise: uploadCompletionPromise
|
urlPromise: uploadCompletionPromise
|
||||||
});
|
});
|
||||||
|
|
||||||
if (caption) {
|
if (caption) {
|
||||||
msg = Drafty.appendLineBreak(msg);
|
msg = Drafty.appendLineBreak(msg);
|
||||||
msg = Drafty.append(msg, Drafty.parse(caption));
|
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() {
|
isDragEnabled() {
|
||||||
return this.state.isWriter && !this.state.unconfirmed && !this.props.forwardMessage && !this.state.peerMessagingDisabled;
|
return this.state.isWriter && !this.state.unconfirmed && !this.props.forwardMessage && !this.state.peerMessagingDisabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDrag(e) {
|
handleDragStart(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
e.dataTransfer.clearData();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDragIn(e) {
|
handleDragIn(e) {
|
||||||
@@ -1280,13 +1325,17 @@ class MessagesView extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleDrag(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
handleDrop(e) {
|
handleDrop(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
this.setState({dragging: false});
|
this.setState({dragging: false});
|
||||||
if (this.isDragEnabled() && e.dataTransfer.files && e.dataTransfer.files.length > 0) {
|
if (this.isDragEnabled() && e.dataTransfer.files && e.dataTransfer.files.length > 0) {
|
||||||
this.handleFileDrop(e.dataTransfer.files);
|
this.handleFileDrop(e.dataTransfer.files);
|
||||||
e.dataTransfer.clearData();
|
|
||||||
this.dragCounter = 0;
|
this.dragCounter = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1295,9 +1344,7 @@ class MessagesView extends React.Component {
|
|||||||
const {formatMessage} = this.props.intl;
|
const {formatMessage} = this.props.intl;
|
||||||
|
|
||||||
let component;
|
let component;
|
||||||
if (this.props.hideSelf) {
|
if (!this.state.topic) {
|
||||||
component = null;
|
|
||||||
} else if (!this.state.topic) {
|
|
||||||
component = (
|
component = (
|
||||||
<LogoView
|
<LogoView
|
||||||
serverVersion={this.props.serverVersion}
|
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 = [];
|
const messageNodes = [];
|
||||||
let previousFrom = null;
|
let previousFrom = null;
|
||||||
let prevDate = null;
|
let prevDate = null;
|
||||||
@@ -1466,6 +1516,8 @@ class MessagesView extends React.Component {
|
|||||||
received={deliveryStatus}
|
received={deliveryStatus}
|
||||||
uploader={msg._uploader}
|
uploader={msg._uploader}
|
||||||
userIsWriter={this.state.isWriter}
|
userIsWriter={this.state.isWriter}
|
||||||
|
userIsAdmin={this.state.isAdmin}
|
||||||
|
pinned={this.state.pins.includes(msg.seq)}
|
||||||
viewportWidth={this.props.viewportWidth} // Used by `formatter`.
|
viewportWidth={this.props.viewportWidth} // Used by `formatter`.
|
||||||
showContextMenu={this.handleShowMessageContextMenu}
|
showContextMenu={this.handleShowMessageContextMenu}
|
||||||
onExpandMedia={this.handleExpandMedia}
|
onExpandMedia={this.handleExpandMedia}
|
||||||
@@ -1581,11 +1633,22 @@ class MessagesView extends React.Component {
|
|||||||
}<ContactBadges badges={icon_badges} /></div>
|
}<ContactBadges badges={icon_badges} /></div>
|
||||||
<div id="topic-last-seen">{lastSeen}</div>
|
<div id="topic-last-seen">{lastSeen}</div>
|
||||||
</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 ?
|
{groupTopic ?
|
||||||
<GroupSubs
|
<GroupSubs
|
||||||
tinode={this.props.tinode}
|
tinode={this.props.tinode}
|
||||||
subscribers={this.state.onlineSubs} /> :
|
subscribers={this.state.onlineSubs} /> :
|
||||||
<div id="topic-users" />
|
null
|
||||||
}
|
}
|
||||||
<div>
|
<div>
|
||||||
<a href="#" onClick={this.handleContextClick}>
|
<a href="#" onClick={this.handleContextClick}>
|
||||||
@@ -1594,10 +1657,20 @@ class MessagesView extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{this.props.displayMobile ?
|
{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} />
|
||||||
<ErrorPanel
|
<ErrorPanel
|
||||||
level={this.props.errorLevel}
|
level={this.props.errorLevel}
|
||||||
text={this.props.errorText}
|
text={this.props.errorText}
|
||||||
onClearError={this.props.onError} />
|
onClearError={this.props.onError} />
|
||||||
|
</>
|
||||||
: null}
|
: null}
|
||||||
<LoadSpinner show={this.state.fetchingMessages} />
|
<LoadSpinner show={this.state.fetchingMessages} />
|
||||||
{messagesComponent}
|
{messagesComponent}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export default class SettingsView extends React.PureComponent {
|
|||||||
render() {
|
render() {
|
||||||
const names = {def: "default", ws: "websocket", lp: "long polling"};
|
const names = {def: "default", ws: "websocket", lp: "long polling"};
|
||||||
const transportOptions = [];
|
const transportOptions = [];
|
||||||
['def', 'ws', 'lp'].map((item) => {
|
['def', 'ws', 'lp'].forEach(item => {
|
||||||
const id = 'transport-' + item;
|
const id = 'transport-' + item;
|
||||||
const name = names[item];
|
const name = names[item];
|
||||||
transportOptions.push(
|
transportOptions.push(
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ class SidepanelView extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="sidepanel" className={this.props.hideSelf ? 'nodisplay' : null}>
|
<div id="sidepanel">
|
||||||
<SideNavbar
|
<SideNavbar
|
||||||
state={view}
|
state={view}
|
||||||
title={title}
|
title={title}
|
||||||
|
|||||||
+20
-11
@@ -277,8 +277,10 @@ class TinodeWeb extends React.Component {
|
|||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
window.addEventListener('resize', this.handleResize);
|
window.addEventListener('resize', this.handleResize);
|
||||||
window.addEventListener('online', (e) => { this.handleOnline(true); });
|
this.handleOnlineOn = _ => { this.handleOnline(true); }
|
||||||
window.addEventListener('offline', (e) => { this.handleOnline(false); });
|
window.addEventListener('online', this.handleOnlineOn);
|
||||||
|
this.handleOnlineOff = _ => { this.handleOnline(false); }
|
||||||
|
window.addEventListener('offline', this.handleOnlineOff);
|
||||||
window.addEventListener('hashchange', this.handleHashRoute);
|
window.addEventListener('hashchange', this.handleHashRoute);
|
||||||
|
|
||||||
// Process background notifications from the service worker.
|
// 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.
|
// Read contacts from cache.
|
||||||
this.resetContactList();
|
this.resetContactList();
|
||||||
|
|
||||||
@@ -337,6 +336,8 @@ class TinodeWeb extends React.Component {
|
|||||||
this.readTimer = null;
|
this.readTimer = null;
|
||||||
this.readTimerCallback = null;
|
this.readTimerCallback = null;
|
||||||
|
|
||||||
|
// Parse the hash navigation params.
|
||||||
|
const parsedNav = HashNavigation.parseUrlHash(window.location.hash);
|
||||||
// Maybe navigate to home screen.
|
// Maybe navigate to home screen.
|
||||||
if (!['cred', 'reset', 'register'].includes(parsedNav.path[0])) {
|
if (!['cred', 'reset', 'register'].includes(parsedNav.path[0])) {
|
||||||
// Save possible topic name.
|
// Save possible topic name.
|
||||||
@@ -354,6 +355,8 @@ class TinodeWeb extends React.Component {
|
|||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
window.removeEventListener('resize', this.handleResize);
|
window.removeEventListener('resize', this.handleResize);
|
||||||
window.removeEventListener('hashchange', this.handleHashRoute);
|
window.removeEventListener('hashchange', this.handleHashRoute);
|
||||||
|
window.removeEventListener('online', this.handleOnlineOn);
|
||||||
|
window.removeEventListener('offline', this.handleOnlineOff);
|
||||||
document.removeEventListener('visibilitychange', this.handleVisibilityEvent);
|
document.removeEventListener('visibilitychange', this.handleVisibilityEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -503,8 +506,12 @@ class TinodeWeb extends React.Component {
|
|||||||
let topicName = hash.path[1] || null;
|
let topicName = hash.path[1] || null;
|
||||||
if (topicName != this.state.topicSelected) {
|
if (topicName != this.state.topicSelected) {
|
||||||
if (!Tinode.topicType(topicName)) {
|
if (!Tinode.topicType(topicName)) {
|
||||||
// Clear invalid topic name.
|
// Clear invalid topic name and hide messages view on mobile.
|
||||||
topicName = null;
|
topicName = null;
|
||||||
|
newState.mobilePanel = 'sidepanel';
|
||||||
|
} else {
|
||||||
|
// Topic valid: show messages view on mobile.
|
||||||
|
newState.mobilePanel = 'topic-view';
|
||||||
}
|
}
|
||||||
Object.assign(newState, {
|
Object.assign(newState, {
|
||||||
topicSelected: topicName,
|
topicSelected: topicName,
|
||||||
@@ -1679,14 +1686,14 @@ class TinodeWeb extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (added && added.length > 0) {
|
if (added && added.length > 0) {
|
||||||
added.map((uid) => {
|
added.forEach(uid => {
|
||||||
topic.invite(uid, null)
|
topic.invite(uid, null)
|
||||||
.catch(err => this.handleError(err.message, 'err'));
|
.catch(err => this.handleError(err.message, 'err'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (removed && removed.length > 0) {
|
if (removed && removed.length > 0) {
|
||||||
removed.map((uid) => {
|
removed.forEach(uid => {
|
||||||
topic.delSubscription(uid)
|
topic.delSubscription(uid)
|
||||||
.catch(err => this.handleError(err.message, 'err'));
|
.catch(err => this.handleError(err.message, 'err'));
|
||||||
});
|
});
|
||||||
@@ -1981,11 +1988,12 @@ class TinodeWeb extends React.Component {
|
|||||||
(_ => {this.setState({alertVisible: false}); this.state.alertParams.onConfirm();}) : null}
|
(_ => {this.setState({alertVisible: false}); this.state.alertParams.onConfirm();}) : null}
|
||||||
confirm={this.state.alertParams.confirm}
|
confirm={this.state.alertParams.confirm}
|
||||||
/> : null}
|
/> : null}
|
||||||
|
|
||||||
|
{!this.state.displayMobile || this.state.mobilePanel == 'sidepanel' ?
|
||||||
<SidepanelView
|
<SidepanelView
|
||||||
tinode={this.tinode}
|
tinode={this.tinode}
|
||||||
connected={this.state.connected}
|
connected={this.state.connected}
|
||||||
displayMobile={this.state.displayMobile}
|
displayMobile={this.state.displayMobile}
|
||||||
hideSelf={this.state.displayMobile && this.state.mobilePanel !== 'sidepanel'}
|
|
||||||
state={this.state.sidePanelSelected}
|
state={this.state.sidePanelSelected}
|
||||||
title={this.state.sidePanelTitle}
|
title={this.state.sidePanelTitle}
|
||||||
avatar={this.state.sidePanelAvatar}
|
avatar={this.state.sidePanelAvatar}
|
||||||
@@ -2052,7 +2060,9 @@ class TinodeWeb extends React.Component {
|
|||||||
onSearchContacts={this.handleSearchContacts}
|
onSearchContacts={this.handleSearchContacts}
|
||||||
|
|
||||||
showContextMenu={this.handleShowContextMenu} />
|
showContextMenu={this.handleShowContextMenu} />
|
||||||
|
: null}
|
||||||
|
|
||||||
|
{!this.state.displayMobile || (this.state.mobilePanel == 'topic-view' && !this.state.infoPanel) ?
|
||||||
<MessagesView
|
<MessagesView
|
||||||
tinode={this.tinode}
|
tinode={this.tinode}
|
||||||
connected={this.state.connected}
|
connected={this.state.connected}
|
||||||
@@ -2062,8 +2072,6 @@ class TinodeWeb extends React.Component {
|
|||||||
displayMobile={this.state.displayMobile}
|
displayMobile={this.state.displayMobile}
|
||||||
viewportWidth={this.state.viewportWidth}
|
viewportWidth={this.state.viewportWidth}
|
||||||
viewportHeight={this.state.viewportHeight}
|
viewportHeight={this.state.viewportHeight}
|
||||||
hideSelf={this.state.displayMobile &&
|
|
||||||
(this.state.mobilePanel !== 'topic-view' || this.state.infoPanel)}
|
|
||||||
topic={this.state.topicSelected}
|
topic={this.state.topicSelected}
|
||||||
myUserId={this.state.myUserId}
|
myUserId={this.state.myUserId}
|
||||||
// User public.fn.
|
// User public.fn.
|
||||||
@@ -2101,6 +2109,7 @@ class TinodeWeb extends React.Component {
|
|||||||
onNewChat={this.handleNewChatInvitation}
|
onNewChat={this.handleNewChatInvitation}
|
||||||
sendMessage={this.handleSendMessage}
|
sendMessage={this.handleSendMessage}
|
||||||
onVideoCallClosed={this.handleCallClose} />
|
onVideoCallClosed={this.handleCallClose} />
|
||||||
|
: null}
|
||||||
|
|
||||||
{this.state.infoPanel ?
|
{this.state.infoPanel ?
|
||||||
<InfoView
|
<InfoView
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class BadgeList extends React.PureComponent {
|
|||||||
let badges = null;
|
let badges = null;
|
||||||
if (this.props.trustedBadges && this.props.trustedBadges.length > 0) {
|
if (this.props.trustedBadges && this.props.trustedBadges.length > 0) {
|
||||||
badges = [];
|
badges = [];
|
||||||
this.props.trustedBadges.map((b) => {
|
this.props.trustedBadges.forEach(b => {
|
||||||
const comment = this.props.short ? null : formatMessage(messages['badge_' + b]);
|
const comment = this.props.short ? null : formatMessage(messages['badge_' + b]);
|
||||||
const style = 'material-icons ' + b + '-color';
|
const style = 'material-icons ' + b + '-color';
|
||||||
badges.push(<div className="trusted-badge" key={b}><i className={style}>{icon_mapping[b] || b}</i> {comment}</div>);
|
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 {
|
export default class CallMessage extends React.PureComponent {
|
||||||
render() {
|
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 successClass = 'material-icons medium ' + (isCallDropped ? 'red' : 'green');
|
||||||
const callIcon = this.props.incoming ?
|
const callIcon = this.props.incoming ?
|
||||||
(isCallDropped ? 'call_missed' : 'call_received') :
|
(isCallDropped ? 'call_missed' : 'call_received') :
|
||||||
@@ -15,6 +15,9 @@ export default class CallMessage extends React.PureComponent {
|
|||||||
let duration;
|
let duration;
|
||||||
if (isCallDropped) {
|
if (isCallDropped) {
|
||||||
switch (this.props.callState) {
|
switch (this.props.callState) {
|
||||||
|
case 'busy':
|
||||||
|
duration = <FormattedMessage id="call_busy" defaultMessage="busy" description="Label for busy call line" />;
|
||||||
|
break;
|
||||||
case 'declined':
|
case 'declined':
|
||||||
duration = <FormattedMessage id="call_declined" defaultMessage="declined" description="Label for declined call" />;
|
duration = <FormattedMessage id="call_declined" defaultMessage="declined" description="Label for declined call" />;
|
||||||
break;
|
break;
|
||||||
|
|||||||
+16
-10
@@ -121,7 +121,8 @@ class CallPanel extends React.PureComponent {
|
|||||||
|
|
||||||
handleVideoCallAccepted(info) {
|
handleVideoCallAccepted(info) {
|
||||||
RING_SOUND.pause();
|
RING_SOUND.pause();
|
||||||
const pc = this.createPeerConnection();
|
// Create peer connection with a data channel.
|
||||||
|
const pc = this.createPeerConnection(true);
|
||||||
const stream = this.state.localStream;
|
const stream = this.state.localStream;
|
||||||
stream.getTracks().forEach(track => {
|
stream.getTracks().forEach(track => {
|
||||||
pc.addTrack(track, stream);
|
pc.addTrack(track, stream);
|
||||||
@@ -230,7 +231,7 @@ class CallPanel extends React.PureComponent {
|
|||||||
this.state.pc.onicecandidateerror = null;
|
this.state.pc.onicecandidateerror = null;
|
||||||
this.state.pc.ondatachannel = 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.dataChannel.close();
|
||||||
}
|
}
|
||||||
this.state.pc.close();
|
this.state.pc.close();
|
||||||
@@ -284,11 +285,12 @@ class CallPanel extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDataChannelClose() {
|
handleDataChannelClose(event) {
|
||||||
/* no action */
|
console.log('close data channel:', event);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDataChannelEvent(event) {
|
handleDataChannelEvent(event) {
|
||||||
|
console.log('data channel event:', event);
|
||||||
const channel = event.channel;
|
const channel = event.channel;
|
||||||
channel.onerror = this.handleDataChannelError;
|
channel.onerror = this.handleDataChannelError;
|
||||||
channel.onmessage = this.handleDataChannelMessage;
|
channel.onmessage = this.handleDataChannelMessage;
|
||||||
@@ -297,7 +299,7 @@ class CallPanel extends React.PureComponent {
|
|||||||
this.setState({dataChannel: channel});
|
this.setState({dataChannel: channel});
|
||||||
}
|
}
|
||||||
|
|
||||||
createPeerConnection() {
|
createPeerConnection(withDataChannel) {
|
||||||
const iceServers = this.props.tinode.getServerParam('iceServers', null);
|
const iceServers = this.props.tinode.getServerParam('iceServers', null);
|
||||||
const pc = iceServers ? new RTCPeerConnection({iceServers: iceServers}) : new RTCPeerConnection();
|
const pc = iceServers ? new RTCPeerConnection({iceServers: iceServers}) : new RTCPeerConnection();
|
||||||
|
|
||||||
@@ -310,15 +312,17 @@ class CallPanel extends React.PureComponent {
|
|||||||
pc.ontrack = this.handleTrackEvent;
|
pc.ontrack = this.handleTrackEvent;
|
||||||
pc.ondatachannel = this.handleDataChannelEvent;
|
pc.ondatachannel = this.handleDataChannelEvent;
|
||||||
|
|
||||||
// creating data channel events
|
let stateUpdate = {pc: pc, waitingForPeer: false};
|
||||||
|
if (withDataChannel) {
|
||||||
|
// Create data channel for exchanging events.
|
||||||
const channel = pc.createDataChannel("events", {ordered: true});
|
const channel = pc.createDataChannel("events", {ordered: true});
|
||||||
channel.onerror = this.handleDataChannelError;
|
channel.onerror = this.handleDataChannelError;
|
||||||
channel.onmessage = this.handleDataChannelMessage;
|
channel.onmessage = this.handleDataChannelMessage;
|
||||||
channel.onopen = this.handleDataChannelOpen;
|
channel.onopen = this.handleDataChannelOpen;
|
||||||
channel.onclose = this.handleDataChannelClose;
|
channel.onclose = this.handleDataChannelClose;
|
||||||
|
stateUpdate.dataChannel = channel;
|
||||||
this.setState({pc: pc, dataChannel: channel, waitingForPeer: false});
|
}
|
||||||
|
this.setState(stateUpdate);
|
||||||
return pc;
|
return pc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -441,7 +445,9 @@ class CallPanel extends React.PureComponent {
|
|||||||
|
|
||||||
handleVideoOfferMsg(info) {
|
handleVideoOfferMsg(info) {
|
||||||
let localStream = null;
|
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);
|
const desc = new RTCSessionDescription(info.payload);
|
||||||
|
|
||||||
pc.setRemoteDescription(desc).then(_ => {
|
pc.setRemoteDescription(desc).then(_ => {
|
||||||
|
|||||||
@@ -9,13 +9,16 @@ import { secondsToTime } from '../lib/strformat.js';
|
|||||||
// success: true | false.
|
// success: true | false.
|
||||||
export default class CallStatus extends React.PureComponent {
|
export default class CallStatus extends React.PureComponent {
|
||||||
render() {
|
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 ?
|
const icon2 = this.props.incoming ?
|
||||||
(isCallDropped ? 'call_missed' : 'call_received') :
|
(isCallDropped ? 'call_missed' : 'call_received') :
|
||||||
(isCallDropped ? 'call_missed_outgoing' : 'call_made');
|
(isCallDropped ? 'call_missed_outgoing' : 'call_made');
|
||||||
let duration;
|
let duration;
|
||||||
if (isCallDropped) {
|
if (isCallDropped) {
|
||||||
switch (this.props.callState) {
|
switch (this.props.callState) {
|
||||||
|
case 'busy':
|
||||||
|
duration = <FormattedMessage id="call_busy" defaultMessage="busy" description="Label for busy call line" />;
|
||||||
|
break;
|
||||||
case 'declined':
|
case 'declined':
|
||||||
duration = <FormattedMessage id="call_declined" defaultMessage="declined" description="Label for declined call" />;
|
duration = <FormattedMessage id="call_declined" defaultMessage="declined" description="Label for declined call" />;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -93,7 +93,8 @@ class BaseChatMessage extends React.PureComponent {
|
|||||||
if (this.props.received == Tinode.MESSAGE_STATUS_FAILED) {
|
if (this.props.received == Tinode.MESSAGE_STATUS_FAILED) {
|
||||||
menuItems.push('menu_item_send_retry');
|
menuItems.push('menu_item_send_retry');
|
||||||
}
|
}
|
||||||
if (this.props.userIsWriter && this.props.received > Tinode.MESSAGE_STATUS_FAILED) {
|
if (this.props.received > Tinode.MESSAGE_STATUS_FATAL) {
|
||||||
|
if (this.props.userIsWriter) {
|
||||||
menuItems.push('menu_item_reply');
|
menuItems.push('menu_item_reply');
|
||||||
if (!this.props.response) {
|
if (!this.props.response) {
|
||||||
let immutable = false;
|
let immutable = false;
|
||||||
@@ -112,6 +113,10 @@ class BaseChatMessage extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (this.props.userIsAdmin) {
|
||||||
|
menuItems.push(this.props.pinned ? 'menu_item_unpin' : 'menu_item_pin');
|
||||||
|
}
|
||||||
|
}
|
||||||
menuItems.push('menu_item_forward');
|
menuItems.push('menu_item_forward');
|
||||||
|
|
||||||
this.props.showContextMenu({
|
this.props.showContextMenu({
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export default class ChipInput extends React.Component {
|
|||||||
static indexChips(chips) {
|
static indexChips(chips) {
|
||||||
const index = {};
|
const index = {};
|
||||||
let count = 0;
|
let count = 0;
|
||||||
chips.map((item) => {
|
chips.forEach(item => {
|
||||||
index[item.user] = count;
|
index[item.user] = count;
|
||||||
count ++;
|
count ++;
|
||||||
});
|
});
|
||||||
@@ -55,7 +55,7 @@ export default class ChipInput extends React.Component {
|
|||||||
static sortChips(chips, keep) {
|
static sortChips(chips, keep) {
|
||||||
const required = [];
|
const required = [];
|
||||||
const normal = [];
|
const normal = [];
|
||||||
chips.map((item) => {
|
chips.forEach(item => {
|
||||||
if (keep && keep.includes(item.user)) {
|
if (keep && keep.includes(item.user)) {
|
||||||
required.push(item);
|
required.push(item);
|
||||||
} else {
|
} else {
|
||||||
@@ -115,7 +115,7 @@ export default class ChipInput extends React.Component {
|
|||||||
const chips = [];
|
const chips = [];
|
||||||
let count = 0;
|
let count = 0;
|
||||||
const staticMembers = this.props.staticMembers || [];
|
const staticMembers = this.props.staticMembers || [];
|
||||||
this.state.sortedChips.map((item) => {
|
this.state.sortedChips.forEach(item => {
|
||||||
chips.push(
|
chips.push(
|
||||||
<Chip
|
<Chip
|
||||||
tinode={this.props.tinode}
|
tinode={this.props.tinode}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export default class ContactBadges extends React.PureComponent {
|
|||||||
let badges = null;
|
let badges = null;
|
||||||
if (this.props.badges && this.props.badges.length > 0) {
|
if (this.props.badges && this.props.badges.length > 0) {
|
||||||
badges = [];
|
badges = [];
|
||||||
this.props.badges.map((b) => {
|
this.props.badges.forEach(b => {
|
||||||
const color = (b.color ? ' ' + b.color : '');
|
const color = (b.color ? ' ' + b.color : '');
|
||||||
if (b.icon) {
|
if (b.icon) {
|
||||||
badges.push(<i className={'material-icons as-badge' + color} key={b.key || b.icon}>{icon_mapping[b.icon] || b.icon}</i>);
|
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 = [];
|
const contactNodes = [];
|
||||||
let contactsCount = 0;
|
let contactsCount = 0;
|
||||||
if (this.props.contacts && this.props.contacts.length > 0) {
|
if (this.props.contacts && this.props.contacts.length > 0) {
|
||||||
this.props.contacts.map((c) => {
|
this.props.contacts.forEach(c => {
|
||||||
if (c.action) {
|
if (c.action) {
|
||||||
// Action item
|
// Action item
|
||||||
contactNodes.push(
|
contactNodes.push(
|
||||||
|
|||||||
@@ -116,6 +116,16 @@ const messages = defineMessages({
|
|||||||
defaultMessage: 'Are you sure you want to clear all messages? It cannot be undone.',
|
defaultMessage: 'Are you sure you want to clear all messages? It cannot be undone.',
|
||||||
description: 'Alert dialog warning when deleting all messages.'
|
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 {
|
class ContextMenu extends React.Component {
|
||||||
@@ -205,6 +215,20 @@ class ContextMenu extends React.Component {
|
|||||||
return this.editMessage(params, errorHandler);
|
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': {
|
'topic_unmute': {
|
||||||
id: 'topic_unmute',
|
id: 'topic_unmute',
|
||||||
title: formatMessage(messages.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.
|
// Function is used by context menu to set permissions.
|
||||||
topicPermissionSetter(mode, params, errorHandler) {
|
topicPermissionSetter(mode, params, errorHandler) {
|
||||||
const topic = this.props.tinode.getTopic(params.topicName);
|
const topic = this.props.tinode.getTopic(params.topicName);
|
||||||
@@ -454,7 +488,7 @@ class ContextMenu extends React.Component {
|
|||||||
render() {
|
render() {
|
||||||
const menu = [];
|
const menu = [];
|
||||||
let count = 0;
|
let count = 0;
|
||||||
this.props.items.map((item) => {
|
this.props.items.forEach(item => {
|
||||||
if (typeof item == 'string') {
|
if (typeof item == 'string') {
|
||||||
item = this.MenuItems[item];
|
item = this.MenuItems[item];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ class CredentialEdit extends React.PureComponent {
|
|||||||
|
|
||||||
const changePhone = (
|
const changePhone = (
|
||||||
<><div className="panel-form-row">
|
<><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>
|
description="Prompt to change current phone number"/></label>
|
||||||
</div>
|
</div>
|
||||||
<div className="group">
|
<div className="group">
|
||||||
|
|||||||
@@ -17,6 +17,22 @@ export default class DocPreview extends React.PureComponent {
|
|||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.handleSendDoc = this.handleSendDoc.bind(this);
|
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) {
|
handleSendDoc(caption) {
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class GroupManager extends React.Component {
|
|||||||
|
|
||||||
static indexMembers(members) {
|
static indexMembers(members) {
|
||||||
let index = {};
|
let index = {};
|
||||||
members.map((m) => {
|
members.forEach(m => {
|
||||||
index[m.user] = {delta: 0, present: true}; // Delta: 0 unchanged, +1 added, -1 removed
|
index[m.user] = {delta: 0, present: true}; // Delta: 0 unchanged, +1 added, -1 removed
|
||||||
});
|
});
|
||||||
return index;
|
return index;
|
||||||
@@ -49,7 +49,7 @@ class GroupManager extends React.Component {
|
|||||||
|
|
||||||
static staticMembers(members, keepInitial, requiredMember) {
|
static staticMembers(members, keepInitial, requiredMember) {
|
||||||
let stat = [];
|
let stat = [];
|
||||||
members.map((m) => {
|
members.forEach(m => {
|
||||||
if (keepInitial || m.user == requiredMember) {
|
if (keepInitial || m.user == requiredMember) {
|
||||||
stat.push(m.user);
|
stat.push(m.user);
|
||||||
}
|
}
|
||||||
@@ -59,7 +59,7 @@ class GroupManager extends React.Component {
|
|||||||
|
|
||||||
static selectedContacts(members) {
|
static selectedContacts(members) {
|
||||||
let sel = [];
|
let sel = [];
|
||||||
members.map((m) => {
|
members.forEach(m => {
|
||||||
sel.push(m.user);
|
sel.push(m.user);
|
||||||
});
|
});
|
||||||
return sel;
|
return sel;
|
||||||
@@ -130,20 +130,19 @@ class GroupManager extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleSubmit() {
|
handleSubmit() {
|
||||||
var instance = this;
|
const members = [];
|
||||||
var members = [];
|
const added = [];
|
||||||
var added = [];
|
const removed = [];
|
||||||
var removed = [];
|
|
||||||
|
|
||||||
var keys = Object.keys(this.state.index);
|
const keys = Object.keys(this.state.index);
|
||||||
keys.map(function(k) {
|
keys.forEach(k => {
|
||||||
if (instance.state.index[k].present) {
|
if (this.state.index[k].present) {
|
||||||
members.push(k);
|
members.push(k);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (instance.state.index[k].delta > 0) {
|
if (this.state.index[k].delta > 0) {
|
||||||
added.push(k);
|
added.push(k);
|
||||||
} else if (instance.state.index[k].delta < 0) {
|
} else if (this.state.index[k].delta < 0) {
|
||||||
removed.push(k);
|
removed.push(k);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -155,7 +154,6 @@ class GroupManager extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {formatMessage} = this.props.intl;
|
|
||||||
return (
|
return (
|
||||||
<div id="group-manager">
|
<div id="group-manager">
|
||||||
<div className="panel-form-row">
|
<div className="panel-form-row">
|
||||||
|
|||||||
@@ -30,7 +30,9 @@ export default class GroupSubs extends React.Component {
|
|||||||
);
|
);
|
||||||
return usersOnline.length == countToShow;
|
return usersOnline.length == countToShow;
|
||||||
});
|
});
|
||||||
|
if (usersOnline.length == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div id="topic-users">{usersOnline} {totalCount > countToShow ?
|
<div id="topic-users">{usersOnline} {totalCount > countToShow ?
|
||||||
<span>
|
<span>
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export default class HostSelector extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
var hostOptions = [];
|
const hostOptions = [];
|
||||||
for (let key in KNOWN_HOSTS) {
|
for (let key in KNOWN_HOSTS) {
|
||||||
let item = KNOWN_HOSTS[key];
|
let item = KNOWN_HOSTS[key];
|
||||||
hostOptions.push(
|
hostOptions.push(
|
||||||
|
|||||||
@@ -16,6 +16,26 @@ export default class ImagePreview extends React.PureComponent {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.handleSendImage = this.handleSendImage.bind(this);
|
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) {
|
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>
|
<a href="#" onClick={(e) => {e.preventDefault(); this.props.onClose();}}><i className="material-icons gray">close</i></a>
|
||||||
</div>
|
</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} />
|
<img src={this.props.content.url} style={size} className="image-preview" alt={this.props.content.filename} />
|
||||||
</div>
|
</div>
|
||||||
{this.props.onSendMessage ?
|
{this.props.onSendMessage ?
|
||||||
|
|||||||
@@ -27,11 +27,12 @@ export default class InlineVideo extends React.PureComponent {
|
|||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
{React.createElement('img', this.props)}
|
{React.createElement('img', this.props)}
|
||||||
{this.props.onClick ?
|
|
||||||
<div className="play-control">
|
<div className="play-control">
|
||||||
|
{this.props.onClick ?
|
||||||
<i className="material-icons white bigger">play_arrow</i>
|
<i className="material-icons white bigger">play_arrow</i>
|
||||||
|
:
|
||||||
|
<img src="img/broken_video.png" style={{filter: 'invert(100%)'}} width="36" height="36" />}
|
||||||
</div>
|
</div>
|
||||||
: null}
|
|
||||||
{duration ? <div className="duration">{duration}</div> : null}
|
{duration ? <div className="duration">{duration}</div> : null}
|
||||||
</div>
|
</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;
|
let timestamp;
|
||||||
if (this.props.received <= Tinode.MESSAGE_STATUS_SENDING) {
|
if (this.props.received <= Tinode.MESSAGE_STATUS_SENDING) {
|
||||||
timestamp = formatMessage(messages.message_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);
|
timestamp = formatMessage(messages.message_sending_failed);
|
||||||
} else {
|
} else {
|
||||||
timestamp = this.props.timestamp.toLocaleTimeString(this.props.intl.locale, { timeStyle: 'short' });
|
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.
|
// Set focus on desktop, but not on mobile: focus causes soft keyboard to pop up.
|
||||||
this.messageEditArea.focus();
|
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) {
|
if (prevProps.topicName != this.props.topicName) {
|
||||||
@@ -276,7 +280,7 @@ class SendMessage extends React.PureComponent {
|
|||||||
</Suspense>) :
|
</Suspense>) :
|
||||||
<textarea id="send-message-input" placeholder={prompt}
|
<textarea id="send-message-input" placeholder={prompt}
|
||||||
value={this.state.message} onChange={this.handleMessageTyping}
|
value={this.state.message} onChange={this.handleMessageTyping}
|
||||||
onKeyPress={this.handleKeyPress}
|
onKeyDown={this.handleKeyPress}
|
||||||
ref={ref => {this.messageEditArea = ref;}} />)}
|
ref={ref => {this.messageEditArea = ref;}} />)}
|
||||||
{this.state.message || !audioEnabled ?
|
{this.state.message || !audioEnabled ?
|
||||||
<a href="#" onClick={this.handleSend} title={formatMessage(messages.icon_title_send)}>
|
<a href="#" onClick={this.handleSend} title={formatMessage(messages.icon_title_send)}>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export default class SideNavbar extends React.PureComponent {
|
|||||||
render() {
|
render() {
|
||||||
const icon_badges = [];
|
const icon_badges = [];
|
||||||
if (this.props.trustedBadges) {
|
if (this.props.trustedBadges) {
|
||||||
this.props.trustedBadges.map((b) => {
|
this.props.trustedBadges.forEach(b => {
|
||||||
icon_badges.push({icon: b, color: 'badge-inv'});
|
icon_badges.push({icon: b, color: 'badge-inv'});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,11 +92,11 @@ export default class TagManager extends React.Component {
|
|||||||
|
|
||||||
let tags = [];
|
let tags = [];
|
||||||
if (this.state.activated) {
|
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)});
|
tags.push({user: tag, invalid: (tag.length < minTagLength || tag.length > maxTagLength)});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.state.tags.map((tag) => {
|
this.state.tags.forEach(tag => {
|
||||||
tags.push(<span className="badge" key={tags.length}>{tag}</span>);
|
tags.push(<span className="badge" key={tags.length}>{tag}</span>);
|
||||||
});
|
});
|
||||||
if (tags.length == 0) {
|
if (tags.length == 0) {
|
||||||
|
|||||||
@@ -19,6 +19,26 @@ class VideoPreview extends React.PureComponent {
|
|||||||
this.videoRef = React.createRef();
|
this.videoRef = React.createRef();
|
||||||
|
|
||||||
this.handleSendVideo = this.handleSendVideo.bind(this);
|
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) {
|
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
+460
-150
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", {
|
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", {
|
||||||
className: "large"
|
className: "large"
|
||||||
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
|
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
|
||||||
id: "change_phonel",
|
id: "change_phone",
|
||||||
defaultMessage: [{
|
defaultMessage: [{
|
||||||
"type": 0,
|
"type": 0,
|
||||||
"value": "Change phone number"
|
"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