mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-02-04 04:50:20 +00:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0eccaf9a21 | ||
|
|
be0950c1ec | ||
|
|
6ecd150f75 | ||
|
|
02fb37189b | ||
|
|
8fe2536996 | ||
|
|
4b9e156c5d | ||
|
|
9265e1ffec | ||
|
|
d11735b04c | ||
|
|
d33b700477 | ||
|
|
97d75c2cb9 | ||
|
|
287115f4c3 | ||
|
|
63a221212b | ||
|
|
6a916fd0e1 | ||
|
|
220691d61d | ||
|
|
5cafc4bcbd | ||
|
|
b3a78dc2e6 | ||
|
|
bebc6eabe5 | ||
|
|
26dc6a4ac2 | ||
|
|
ff2626723a | ||
|
|
72bb897269 | ||
|
|
f46387a226 | ||
|
|
a4cbbccb2a | ||
|
|
3e1a008399 | ||
|
|
7e70a8c1de | ||
|
|
8efee04a10 | ||
|
|
a35099f949 |
@@ -10,7 +10,7 @@ MVN_HTTP=0
|
||||
DEFAULT_SDK_VERSION=$(grep sdkVersion ${THIS_DIR}/../gradle.properties | cut -d"=" -f2)
|
||||
SDK_VERSION=${OVERRIDE_SDK_VERSION:-${DEFAULT_SDK_VERSION}}
|
||||
RN_VERSION=$(jq -r '.dependencies."react-native"' ${THIS_DIR}/../../package.json)
|
||||
JSC_VERSION="r"$(jq -r '.dependencies."jsc-android"' ${THIS_DIR}/../../node_modules/react-native/package.json | cut -d . -f 1)
|
||||
JSC_VERSION="r"$(jq -r '.dependencies."jsc-android"' ${THIS_DIR}/../../node_modules/react-native/package.json | cut -d . -f 1 | cut -c 2-)
|
||||
DO_GIT_TAG=${GIT_TAG:-0}
|
||||
|
||||
if [[ $THE_MVN_REPO == http* ]]; then
|
||||
|
||||
12
config.js
12
config.js
@@ -419,9 +419,15 @@ var config = {
|
||||
// the menu has option to flip the locally seen video for local presentations
|
||||
// disableLocalVideoFlip: false
|
||||
|
||||
// If specified a 'Help' button will be displayed in the overflow menu with a link to the specified URL for
|
||||
// user documentation.
|
||||
// userDocumentationURL: 'https://docs.example.com/video-meetings.html'
|
||||
// Deployment specific URLs.
|
||||
// deploymentUrls: {
|
||||
// // If specified a 'Help' button will be displayed in the overflow menu with a link to the specified URL for
|
||||
// // user documentation.
|
||||
// userDocumentationURL: 'https://docs.example.com/video-meetings.html',
|
||||
// // If specified a 'Download our apps' button will be displayed in the overflow menu with a link
|
||||
// // to the specified URL for an app download page.
|
||||
// downloadAppsUrl: 'https://docs.example.com/our-apps.html'
|
||||
// }
|
||||
|
||||
// List of undocumented settings used in jitsi-meet
|
||||
/**
|
||||
|
||||
@@ -82,9 +82,10 @@
|
||||
|
||||
#chat-recipient {
|
||||
align-items: center;
|
||||
background-color: $defaultWarningColor;
|
||||
background-color: $chatPrivateMessageBackgroundColor;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-weight: 100;
|
||||
padding: 10px;
|
||||
|
||||
span {
|
||||
@@ -132,6 +133,7 @@
|
||||
#chat-input {
|
||||
border-top: 1px solid $chatInputSeparatorColor;
|
||||
display: flex;
|
||||
padding: 5px 10px;
|
||||
|
||||
* {
|
||||
background-color: transparent;
|
||||
@@ -152,8 +154,7 @@
|
||||
box-shadow: none;
|
||||
color: white;
|
||||
font-size: 15px;
|
||||
line-height: 30px;
|
||||
padding: 5px;
|
||||
padding: 10px;
|
||||
overflow-y: auto;
|
||||
resize: none;
|
||||
width: 100%;
|
||||
@@ -183,6 +184,7 @@
|
||||
.display-name {
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
@@ -196,7 +198,6 @@
|
||||
color: white;
|
||||
margin-top: 3px;
|
||||
max-width: 100%;
|
||||
padding-bottom: 3px;
|
||||
position: relative;
|
||||
|
||||
&.localuser {
|
||||
@@ -219,8 +220,12 @@
|
||||
}
|
||||
|
||||
.privatemessagenotice {
|
||||
color: $defaultWarningColor;
|
||||
font-style: italic;
|
||||
font-size: 11px;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
.messagecontent {
|
||||
margin: 5px 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,10 +302,6 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#usermsg::-webkit-input-placeholder {
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
#usermsg::-webkit-scrollbar-track-piece {
|
||||
background: #3a3a3a;
|
||||
}
|
||||
@@ -315,6 +316,10 @@
|
||||
.chatmessage {
|
||||
background-color: $chatLocalMessageBackgroundColor;
|
||||
border-radius: 6px 0px 6px 6px;
|
||||
|
||||
&.privatemessage {
|
||||
background-color: $chatPrivateMessageBackgroundColor;
|
||||
}
|
||||
}
|
||||
|
||||
.display-name {
|
||||
@@ -328,8 +333,9 @@
|
||||
|
||||
&.error {
|
||||
.chatmessage {
|
||||
background-color: $defaultWarningColor;
|
||||
border-radius: 0px;
|
||||
color: red;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
.display-name {
|
||||
@@ -345,8 +351,17 @@
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
.toolbox-icon {
|
||||
cursor: pointer;
|
||||
.messageactions {
|
||||
align-self: stretch;
|
||||
border-left: 1px solid $chatActionsSeparatorColor;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 5px;
|
||||
|
||||
.toolbox-icon {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -357,6 +372,9 @@
|
||||
display: inline-block;
|
||||
margin-top: 3px;
|
||||
color: white;
|
||||
padding: 8px;
|
||||
|
||||
&.privatemessage {
|
||||
background-color: $chatPrivateMessageBackgroundColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
66
css/_mini_toolbox.scss
Normal file
66
css/_mini_toolbox.scss
Normal file
@@ -0,0 +1,66 @@
|
||||
.filmstrip-toolbox,
|
||||
.always-on-top-toolbox {
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
border-radius: 3px;
|
||||
display: flex;
|
||||
z-index: $toolbarZ;
|
||||
|
||||
.toolbox-icon {
|
||||
cursor: pointer;
|
||||
padding: 7px;
|
||||
|
||||
&.toggled {
|
||||
background: $AOTToolbarButtonToggleColor;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
cursor: initial;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.always-on-top-toolbox {
|
||||
flex-direction: row;
|
||||
left: 50%;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
transform: translateX(-50%);
|
||||
|
||||
.toolbox-button {
|
||||
&:first-child {
|
||||
.toolbox-icon {
|
||||
border-top-left-radius: 3px;
|
||||
border-bottom-left-radius: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
svg {
|
||||
fill: $hangupColor;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
.toolbox-icon {
|
||||
border-top-right-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.filmstrip-toolbox {
|
||||
flex-direction: column;
|
||||
|
||||
.toolbox-button {
|
||||
&:nth-child(1) {
|
||||
svg {
|
||||
fill: $hangupColor;
|
||||
}
|
||||
}
|
||||
|
||||
.toolbox-icon {
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -270,89 +270,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.always-on-top-toolbox,
|
||||
.filmstrip-toolbox {
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
z-index: $toolbarZ;
|
||||
|
||||
i {
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
}
|
||||
|
||||
i:hover {
|
||||
background-color: $AOTToolbarButtonHoverColor;
|
||||
}
|
||||
|
||||
i.toggled {
|
||||
background: $AOTToolbarButtonToggleColor;
|
||||
}
|
||||
|
||||
i.toggled:hover:not(.disabled) {
|
||||
background-color: $AOTToolbarButtonHoverColor;
|
||||
}
|
||||
|
||||
.toolbox-button {
|
||||
color: $toolbarButtonColor;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.always-on-top-toolbox {
|
||||
flex-direction: row;
|
||||
left: 50%;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
transform: translateX(-50%);
|
||||
z-index: $toolbarZ;
|
||||
|
||||
i {
|
||||
font-size: $newToolbarFontSize;
|
||||
height: $newToolbarSize;
|
||||
line-height: $newToolbarSize;
|
||||
width: $newToolbarSize;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
cursor: initial;
|
||||
}
|
||||
|
||||
.toolbox-button:first-child i {
|
||||
border-top-left-radius: 3px;
|
||||
border-bottom-left-radius: 3px;
|
||||
}
|
||||
|
||||
.toolbox-button:last-child i {
|
||||
border-top-right-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.filmstrip-toolbox {
|
||||
i {
|
||||
font-size: 1.9em;
|
||||
height: 37px;
|
||||
line-height: 37px;
|
||||
width: 37px;
|
||||
}
|
||||
|
||||
.toolbox-button:first-child i {
|
||||
border-top-left-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
}
|
||||
|
||||
.toolbox-button:last-child i {
|
||||
border-bottom-left-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* START of fade in animation for main toolbar
|
||||
*/
|
||||
|
||||
@@ -86,10 +86,12 @@ $modalTextColor: #333;
|
||||
/**
|
||||
* Chat
|
||||
*/
|
||||
$chatActionsSeparatorColor: rgb(173, 105, 112);
|
||||
$chatHeaderBackgroundColor: rgba(42, 58, 75, 0.9);
|
||||
$chatInputSeparatorColor: #A4B8D1;
|
||||
$chatLocalMessageBackgroundColor: rgba(26, 108, 180, 1);
|
||||
$chatRemoteMessageBackgroundColor: rgba(240, 243, 247, 0.15);
|
||||
$chatLocalMessageBackgroundColor: rgb(4, 98, 178);
|
||||
$chatPrivateMessageBackgroundColor: rgb(153, 69, 77);
|
||||
$chatRemoteMessageBackgroundColor: rgb(86, 101, 114);
|
||||
$sidebarWidth: 375px;
|
||||
|
||||
/**
|
||||
@@ -178,8 +180,17 @@ $welcomePageHeaderTextMarginTop: 35px;
|
||||
$welcomePageHeaderTextMarginBottom: 35px;
|
||||
|
||||
$welcomePageHeaderTextTitleMarginBottom: 16px;
|
||||
$welcomePageHeaderTextTitleFontSize: 2.5rem;
|
||||
$welcomePageHeaderTextTitleFontWeight: 500;
|
||||
$welcomePageHeaderTextTitleLineHeight: 1.18;
|
||||
$welcomePageHeaderTextTitleOpacity: 1;
|
||||
|
||||
$welcomePageHeaderTextDescriptionDisplay: inherit;
|
||||
$welcomePageHeaderTextDescriptionFontSize: 1rem;
|
||||
$welcomePageHeaderTextDescriptionFontWeight: 400;
|
||||
$welcomePageHeaderTextDescriptionLineHeight: 24px;
|
||||
$welcomePageHeaderTextDescriptionMarginBottom: 20px;
|
||||
$welcomePageHeaderTextDescriptionAlignSelf: inherit;
|
||||
|
||||
$welcomePageEnterRoomWidth: 680px;
|
||||
$welcomePageEnterRoomPadding: 25px 30px;
|
||||
@@ -198,6 +209,8 @@ $welcomePageTabButtonsDisplay: flex;
|
||||
$welcomePageTabDisplay: block;
|
||||
|
||||
$welcomePageButtonWidth: 51px;
|
||||
$welcomePageButtonMinWidth: inherit;
|
||||
$welcomePageButtonFontSize: 14px;
|
||||
$welcomePageButtonHeight: 35px;
|
||||
$welcomePageButtonFontWeight: inherit;
|
||||
$welcomePageButtonBorderRadius: 4px;
|
||||
|
||||
@@ -38,19 +38,21 @@ body.welcome-page {
|
||||
|
||||
.header-text-title {
|
||||
color: $welcomePageTitleColor;
|
||||
font-size: 2.5rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.18;
|
||||
font-size: $welcomePageHeaderTextTitleFontSize;
|
||||
font-weight: $welcomePageHeaderTextTitleFontWeight;
|
||||
line-height: $welcomePageHeaderTextTitleLineHeight;
|
||||
margin-bottom: $welcomePageHeaderTextTitleMarginBottom;
|
||||
opacity: $welcomePageHeaderTextTitleOpacity;
|
||||
}
|
||||
|
||||
.header-text-description {
|
||||
display: $welcomePageHeaderTextDescriptionDisplay;
|
||||
color: $welcomePageDescriptionColor;
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
margin-bottom: 20px;
|
||||
font-size: $welcomePageHeaderTextDescriptionFontSize;
|
||||
font-weight: $welcomePageHeaderTextDescriptionFontWeight;
|
||||
line-height: $welcomePageHeaderTextDescriptionLineHeight;
|
||||
margin-bottom: $welcomePageHeaderTextDescriptionMarginBottom;
|
||||
align-self: $welcomePageHeaderTextDescriptionAlignSelf;
|
||||
}
|
||||
|
||||
#enter_room {
|
||||
@@ -148,8 +150,9 @@ body.welcome-page {
|
||||
|
||||
.welcome-page-button {
|
||||
width: $welcomePageButtonWidth;
|
||||
min-width: $welcomePageButtonMinWidth;
|
||||
height: $welcomePageButtonHeight;
|
||||
font-size: 14px;
|
||||
font-size: $welcomePageButtonFontSize;
|
||||
font-weight: $welcomePageButtonFontWeight;
|
||||
background: #0074E0;
|
||||
border-radius: $welcomePageButtonBorderRadius;
|
||||
|
||||
@@ -32,6 +32,7 @@ $flagsImagePath: "../images/";
|
||||
@import 'overlay/overlay';
|
||||
@import 'inlay';
|
||||
@import 'reload_overlay/reload_overlay';
|
||||
@import 'mini_toolbox';
|
||||
@import 'modals/desktop-picker/desktop-picker';
|
||||
@import 'modals/device-selection/device-selection';
|
||||
@import 'modals/dialog';
|
||||
|
||||
@@ -63,6 +63,8 @@
|
||||
width: -webkit-max-content;
|
||||
word-break: break-all;
|
||||
max-width: 400px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.info-dialog-dial-in {
|
||||
@@ -86,6 +88,15 @@
|
||||
cursor: inherit;
|
||||
}
|
||||
|
||||
.info-dialog-url-icon {
|
||||
display: inline-block;
|
||||
margin-left: 5px;
|
||||
|
||||
svg {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.info-dialog-title {
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
@@ -214,4 +225,10 @@
|
||||
-moz-user-select: text;
|
||||
-webkit-user-select: text;
|
||||
}
|
||||
|
||||
.info-dialog-url-text-unselectable {
|
||||
user-select: none;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,29 +19,37 @@ server {
|
||||
ssl_certificate_key /etc/jitsi/meet/jitsi-meet.example.com.key;
|
||||
|
||||
root /usr/share/jitsi-meet;
|
||||
ssi on;
|
||||
index index.html index.htm;
|
||||
error_page 404 /static/404.html;
|
||||
|
||||
location /config.js {
|
||||
location = /config.js {
|
||||
alias /etc/jitsi/meet/jitsi-meet.example.com-config.js;
|
||||
}
|
||||
|
||||
location /external_api.js {
|
||||
location = /external_api.js {
|
||||
alias /usr/share/jitsi-meet/libs/external_api.min.js;
|
||||
}
|
||||
|
||||
location ~ ^/([a-zA-Z0-9=\?]+)$ {
|
||||
rewrite ^/(.*)$ / break;
|
||||
}
|
||||
|
||||
location / {
|
||||
ssi on;
|
||||
#ensure all static content can always be found first
|
||||
location ~ ^/(libs|css|static|images|fonts|lang|sounds|connection_optimization|.well-known)/(.*)$
|
||||
{
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
alias /usr/share/jitsi-meet/$1/$2;
|
||||
}
|
||||
|
||||
# BOSH
|
||||
location /http-bind {
|
||||
location = /http-bind {
|
||||
proxy_pass http://localhost:5280/http-bind;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_set_header Host $http_host;
|
||||
}
|
||||
|
||||
location ~ ^/([^?&:’“]+)$ {
|
||||
try_files $uri @root_path;
|
||||
}
|
||||
|
||||
location @root_path {
|
||||
rewrite ^/(.*)$ / break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
var subdomain = "<!--# echo var="subdomain" default="" -->";
|
||||
if (subdomain) {
|
||||
subdomain = subdomain.substr(0,subdomain.length-1).split('.').join('_').toLowerCase() + '.';
|
||||
}
|
||||
|
||||
var config = {
|
||||
hosts: {
|
||||
domain: 'jitsi.example.com',
|
||||
muc: 'conference.'+subdomain+'jitsi.example.com', // FIXME: use XEP-0030
|
||||
focus: 'focus.jitsi.example.com',
|
||||
},
|
||||
useNicks: false,
|
||||
bosh: '//jitsi.example.com/http-bind' // FIXME: use xep-0156 for that
|
||||
};
|
||||
@@ -0,0 +1,67 @@
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
server_name jitsi.example.com;
|
||||
# set the root
|
||||
root /srv/jitsi.example.com;
|
||||
# ssi on with javascript for multidomain variables in config.js
|
||||
ssi on;
|
||||
ssi_types application/x-javascript application/javascript;
|
||||
index index.html;
|
||||
set $prefix "";
|
||||
|
||||
|
||||
# BOSH
|
||||
location /http-bind {
|
||||
proxy_pass http://localhost:5280/http-bind;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_set_header Host $http_host;
|
||||
}
|
||||
|
||||
# xmpp websockets
|
||||
location /xmpp-websocket {
|
||||
proxy_pass http://localhost:5280/xmpp-websocket;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
tcp_nodelay on;
|
||||
}
|
||||
|
||||
location ~ ^/([^/?&:'"]+)$ {
|
||||
try_files $uri @root_path;
|
||||
}
|
||||
|
||||
location @root_path {
|
||||
rewrite ^/(.*)$ / break;
|
||||
}
|
||||
|
||||
location / {
|
||||
ssi on;
|
||||
}
|
||||
|
||||
location ~ ^/([^/?&:'"]+)/config.js$
|
||||
{
|
||||
set $subdomain "$1.";
|
||||
set $subdir "$1/";
|
||||
|
||||
alias /etc/jitsi/meet/{{jitsi_meet_domain_name}}-config.js;
|
||||
}
|
||||
|
||||
#Anything that didn't match above, and isn't a real file, assume it's a room name and redirect to /
|
||||
location ~ ^/([^/?&:'"]+)/(.*)$ {
|
||||
set $subdomain "$1.";
|
||||
set $subdir "$1/";
|
||||
rewrite ^/([^/?&:'"]+)/(.*)$ /$2;
|
||||
}
|
||||
|
||||
# BOSH for subdomains
|
||||
location ~ ^/([^/?&:'"]+)/http-bind {
|
||||
set $subdomain "$1.";
|
||||
set $subdir "$1/";
|
||||
set $prefix "$1";
|
||||
|
||||
rewrite ^/(.*)$ /http-bind;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
-- Prosody XMPP Server Configuration
|
||||
--
|
||||
-- Information on configuring Prosody can be found on our
|
||||
-- website at http://prosody.im/doc/configure
|
||||
--
|
||||
-- Tip: You can check that the syntax of this file is correct
|
||||
-- when you have finished by running: prosodyctl check config
|
||||
-- If there are any errors, it will let you know what and where
|
||||
-- they are, otherwise it will keep quiet.
|
||||
--
|
||||
-- Good luck, and happy Jabbering!
|
||||
|
||||
|
||||
---------- Server-wide settings ----------
|
||||
-- Settings in this section apply to the whole server and are the default settings
|
||||
-- for any virtual hosts
|
||||
|
||||
-- This is a (by default, empty) list of accounts that are admins
|
||||
-- for the server. Note that you must create the accounts separately
|
||||
-- (see http://prosody.im/doc/creating_accounts for info)
|
||||
-- Example: admins = { "user1@example.com", "user2@example.net" }
|
||||
admins = { }
|
||||
daemonize = true
|
||||
cross_domain_bosh = true;
|
||||
component_ports = { 5347 }
|
||||
--component_interface = "192.168.0.10"
|
||||
|
||||
-- Enable use of libevent for better performance under high load
|
||||
-- For more information see: http://prosody.im/doc/libevent
|
||||
--use_libevent = true
|
||||
|
||||
-- This is the list of modules Prosody will load on startup.
|
||||
-- It looks for mod_modulename.lua in the plugins folder, so make sure that exists too.
|
||||
-- Documentation on modules can be found at: http://prosody.im/doc/modules
|
||||
modules_enabled = {
|
||||
|
||||
-- Generally required
|
||||
"roster"; -- Allow users to have a roster. Recommended ;)
|
||||
"saslauth"; -- Authentication for clients and servers. Recommended if you want to log in.
|
||||
"tls"; -- Add support for secure TLS on c2s/s2s connections
|
||||
"dialback"; -- s2s dialback support
|
||||
"disco"; -- Service discovery
|
||||
"posix"; -- POSIX functionality, sends server to background, enables syslog, etc.
|
||||
|
||||
-- Not essential, but recommended
|
||||
"private"; -- Private XML storage (for room bookmarks, etc.)
|
||||
"vcard"; -- Allow users to set vCards
|
||||
|
||||
-- These are commented by default as they have a performance impact
|
||||
--"privacy"; -- Support privacy lists
|
||||
"compression"; -- Stream compression (requires the lua-zlib package installed)
|
||||
|
||||
-- Nice to have
|
||||
"version"; -- Replies to server version requests
|
||||
"uptime"; -- Report how long server has been running
|
||||
"time"; -- Let others know the time here on this server
|
||||
"ping"; -- Replies to XMPP pings with pongs
|
||||
"pep"; -- Enables users to publish their mood, activity, playing music and more
|
||||
"register"; -- Allow users to register on this server using a client and change passwords
|
||||
|
||||
-- Admin interfaces
|
||||
"admin_adhoc"; -- Allows administration via an XMPP client that supports ad-hoc commands
|
||||
--"admin_telnet"; -- Opens telnet console interface on localhost port 5582
|
||||
|
||||
-- HTTP modules
|
||||
"bosh"; -- Enable BOSH clients, aka "Jabber over HTTP"
|
||||
--"http_files"; -- Serve static files from a directory over HTTP
|
||||
|
||||
-- Other specific functionality
|
||||
--"groups"; -- Shared roster support
|
||||
--"announce"; -- Send announcement to all online users
|
||||
--"welcome"; -- Welcome users who register accounts
|
||||
--"watchregistrations"; -- Alert admins of registrations
|
||||
--"motd"; -- Send a message to users when they log in
|
||||
--"legacyauth"; -- Legacy authentication. Only used by some old clients and bots.
|
||||
-- jitsi
|
||||
"smacks";
|
||||
"carbons";
|
||||
"mam";
|
||||
"lastactivity";
|
||||
"offline";
|
||||
"pubsub";
|
||||
"adhoc";
|
||||
"websocket";
|
||||
"http_altconnect";
|
||||
-- include domain mapper as global level module
|
||||
"muc_domain_mapper";
|
||||
}
|
||||
|
||||
-- domain mapper options, must at least have domain base set to use the mapper
|
||||
muc_mapper_domain_base = "jitsi.example.com";
|
||||
|
||||
-- These modules are auto-loaded, but should you want
|
||||
-- to disable them then uncomment them here:
|
||||
modules_disabled = {
|
||||
-- "offline"; -- Store offline messages
|
||||
-- "c2s"; -- Handle client connections
|
||||
-- "s2s"; -- Handle server-to-server connections
|
||||
}
|
||||
|
||||
-- Disable account creation by default, for security
|
||||
-- For more information see http://prosody.im/doc/creating_accounts
|
||||
allow_registration = false
|
||||
|
||||
-- These are the SSL/TLS-related settings. If you don't want
|
||||
-- to use SSL/TLS, you may comment or remove this
|
||||
ssl = {
|
||||
key = "/etc/prosody/certs/localhost.key";
|
||||
certificate = "/etc/prosody/certs/localhost.crt";
|
||||
}
|
||||
|
||||
-- Force clients to use encrypted connections? This option will
|
||||
-- prevent clients from authenticating unless they are using encryption.
|
||||
|
||||
-- c2s_require_encryption = true
|
||||
|
||||
-- Force certificate authentication for server-to-server connections?
|
||||
-- This provides ideal security, but requires servers you communicate
|
||||
-- with to support encryption AND present valid, trusted certificates.
|
||||
-- NOTE: Your version of LuaSec must support certificate verification!
|
||||
-- For more information see http://prosody.im/doc/s2s#security
|
||||
|
||||
-- s2s_secure_auth = false
|
||||
|
||||
-- Many servers don't support encryption or have invalid or self-signed
|
||||
-- certificates. You can list domains here that will not be required to
|
||||
-- authenticate using certificates. They will be authenticated using DNS.
|
||||
|
||||
--s2s_insecure_domains = { "gmail.com" }
|
||||
|
||||
-- Even if you leave s2s_secure_auth disabled, you can still require valid
|
||||
-- certificates for some domains by specifying a list here.
|
||||
|
||||
--s2s_secure_domains = { "jabber.org" }
|
||||
|
||||
-- Required for init scripts and prosodyctl
|
||||
pidfile = "/var/run/prosody/prosody.pid"
|
||||
|
||||
-- Select the authentication backend to use. The 'internal' providers
|
||||
-- use Prosody's configured data storage to store the authentication data.
|
||||
-- To allow Prosody to offer secure authentication mechanisms to clients, the
|
||||
-- default provider stores passwords in plaintext. If you do not trust your
|
||||
-- server please see http://prosody.im/doc/modules/mod_auth_internal_hashed
|
||||
-- for information about using the hashed backend.
|
||||
|
||||
-- authentication = "internal_plain"
|
||||
authentication = "internal_hashed"
|
||||
|
||||
-- Select the storage backend to use. By default Prosody uses flat files
|
||||
-- in its configured data directory, but it also supports more backends
|
||||
-- through modules. An "sql" backend is included by default, but requires
|
||||
-- additional dependencies. See http://prosody.im/doc/storage for more info.
|
||||
|
||||
--storage = "sql" -- Default is "internal"
|
||||
|
||||
-- For the "sql" backend, you can uncomment *one* of the below to configure:
|
||||
--sql = { driver = "SQLite3", database = "prosody.sqlite" } -- Default. 'database' is the filename.
|
||||
--sql = { driver = "MySQL", database = "prosody", username = "prosody", password = "secret", host = "localhost" }
|
||||
--sql = { driver = "PostgreSQL", database = "prosody", username = "prosody", password = "secret", host = "localhost" }
|
||||
|
||||
-- Logging configuration
|
||||
-- For advanced logging see http://prosody.im/doc/logging
|
||||
log = {
|
||||
info = "/var/log/prosody/prosody.log"; -- Change 'info' to 'debug' for verbose logging
|
||||
error = "/var/log/prosody/prosody.err";
|
||||
"*syslog";
|
||||
}
|
||||
|
||||
----------- Virtual hosts -----------
|
||||
-- You need to add a VirtualHost entry for each domain you wish Prosody to serve.
|
||||
-- Settings under each VirtualHost entry apply *only* to that host.
|
||||
|
||||
--VirtualHost "localhost"
|
||||
|
||||
VirtualHost "jitsi.example.com"
|
||||
-- enabled = false -- Remove this line to enable this host
|
||||
authentication = "anonymous"
|
||||
-- Assign this host a certificate for TLS, otherwise it would use the one
|
||||
-- set in the global section (if any).
|
||||
-- Note that old-style SSL on port 5223 only supports one certificate, and will always
|
||||
-- use the global one.
|
||||
ssl = {
|
||||
key = "/var/lib/prosody/jitsi.example.com.key";
|
||||
certificate = "/var/lib/prosody/jitsi.example.com.crt";
|
||||
}
|
||||
|
||||
c2s_require_encryption = false
|
||||
|
||||
VirtualHost "auth.jitsi.example.com"
|
||||
ssl = {
|
||||
key = "/var/lib/prosody/auth.jitsi.example.com.key";
|
||||
certificate = "/var/lib/prosody/auth.jitsi.example.com.crt";
|
||||
}
|
||||
authentication = "internal_plain"
|
||||
|
||||
------ Components ------
|
||||
-- You can specify components to add hosts that provide special services,
|
||||
-- like multi-user conferences, and transports.
|
||||
-- For more information on components, see http://prosody.im/doc/components
|
||||
|
||||
---Set up a MUC (multi-user chat) room server on conference.example.com:
|
||||
--Component "conference.example.com" "muc"
|
||||
|
||||
-- Set up a SOCKS5 bytestream proxy for server-proxied file transfers:
|
||||
--Component "proxy.example.com" "proxy65"
|
||||
|
||||
---Set up an external component (default component port is 5347)
|
||||
--
|
||||
-- External components allow adding various services, such as gateways/
|
||||
-- transports to other networks like ICQ, MSN and Yahoo. For more info
|
||||
-- see: http://prosody.im/doc/components#adding_an_external_component
|
||||
--
|
||||
--Component "gateway.example.com"
|
||||
-- component_secret = "password"
|
||||
|
||||
Component "conference.jitsi.example.com" "muc"
|
||||
modules_enabled = { "muc_domain_mapper" }
|
||||
|
||||
Component "jitsi-videobridge.jitsi.example.com"
|
||||
component_secret = "IfGaish6"
|
||||
@@ -51,7 +51,7 @@ var interfaceConfig = {
|
||||
'fodeviceselection', 'hangup', 'profile', 'info', 'chat', 'recording',
|
||||
'livestreaming', 'etherpad', 'sharedvideo', 'settings', 'raisehand',
|
||||
'videoquality', 'filmstrip', 'invite', 'feedback', 'stats', 'shortcuts',
|
||||
'tileview', 'videobackgroundblur'
|
||||
'tileview', 'videobackgroundblur', 'download', 'help'
|
||||
],
|
||||
|
||||
SETTINGS_SECTIONS: [ 'devices', 'language', 'moderator', 'profile', 'calendar' ],
|
||||
|
||||
103
ios/ci/build-ipa.sh
Executable file
103
ios/ci/build-ipa.sh
Executable file
@@ -0,0 +1,103 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Mandatory arguments with no default values provided:
|
||||
# PR_REPO_SLUG - the Github name of the repo to be merged into the origin/master
|
||||
# PR_BRANCH - the branch to be merged, if set to "master" no merge will happen
|
||||
# IPA_DEPLOY_LOCATION - the location understandable by the "scp" command
|
||||
# executed at the end of the script to deploy the output .ipa file
|
||||
# LIB_JITSI_MEET_PKG (optional) - the npm package for lib-jitsi-meet which will
|
||||
# be put in place of the current version in the package.json file.
|
||||
#
|
||||
# Other than that the script requires the following env variables to be set:
|
||||
#
|
||||
# DEPLOY_SSH_CERT_URL - the SSH private key used by the 'scp' command to deploy
|
||||
# the .ipa. It is expected to be encrypted with the $ENCRYPTION_PASSWORD.
|
||||
# ENCRYPTION_PASSWORD - the password used to decrypt certificate/key files used
|
||||
# in the script.
|
||||
# IOS_TEAM_ID - the team ID inserted into build-ipa-.plist.template file in
|
||||
# place of "YOUR_TEAM_ID".
|
||||
|
||||
function echoAndExit1() {
|
||||
echo $1
|
||||
exit 1
|
||||
}
|
||||
|
||||
if [ -z $PR_REPO_SLUG ]; then
|
||||
echoAndExit1 "No PR_REPO_SLUG defined"
|
||||
fi
|
||||
if [ -z $PR_BRANCH ]; then
|
||||
echoAndExit1 "No PR_BRANCH defined"
|
||||
fi
|
||||
if [ -z $IPA_DEPLOY_LOCATION ]; then
|
||||
echoAndExit1 "No IPA_DEPLOY_LOCATION defined"
|
||||
fi
|
||||
|
||||
echo "PR_REPO_SLUG=${PR_REPO_SLUG} PR_BRANCH=${PR_BRANCH}"
|
||||
|
||||
# do the marge and git log
|
||||
|
||||
if [ $PR_BRANCH != "master" ]; then
|
||||
echo "Will merge ${PR_REPO_SLUG}/${PR_BRANCH} into master"
|
||||
git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"
|
||||
git fetch origin master
|
||||
git checkout master
|
||||
git pull https://github.com/${PR_REPO_SLUG}.git $PR_BRANCH --no-edit
|
||||
fi
|
||||
|
||||
# Link this lib-jitsi-meet checkout in jitsi-meet through the package.json
|
||||
if [ ! -z ${LIB_JITSI_MEET_PKG} ];
|
||||
then
|
||||
echo "Adjusting lib-jitsi-meet package in package.json to ${LIB_JITSI_MEET_PKG}"
|
||||
# escape for the sed
|
||||
LIB_JITSI_MEET_PKG=$(echo $LIB_JITSI_MEET_PKG | sed -e 's/\\/\\\\/g; s/\//\\\//g; s/&/\\\&/g')
|
||||
sed -i.bak -e "s/\"lib-jitsi-meet.*/\"lib-jitsi-meet\"\: \"${LIB_JITSI_MEET_PKG}\",/g" package.json
|
||||
echo "Package.json lib-jitsi-meet line:"
|
||||
grep lib-jitsi-meet package.json
|
||||
else
|
||||
echo "LIB_JITSI_MEET_PKG var not set - will not modify the package.json"
|
||||
fi
|
||||
|
||||
git log -20 --graph --pretty=format':%C(yellow)%h%Cblue%d%Creset %s %C(white) %an, %ar%Creset'
|
||||
|
||||
# certificates
|
||||
|
||||
CERT_DIR="ios/ci/certs"
|
||||
|
||||
mkdir -p $CERT_DIR
|
||||
|
||||
curl -L -o ${CERT_DIR}/id_rsa.enc ${DEPLOY_SSH_CERT_URL}
|
||||
openssl aes-256-cbc -k "$ENCRYPTION_PASSWORD" -in ${CERT_DIR}/id_rsa.enc -d -a -out ${CERT_DIR}/id_rsa
|
||||
chmod 0600 ${CERT_DIR}/id_rsa
|
||||
ssh-add ${CERT_DIR}/id_rsa
|
||||
|
||||
npm install
|
||||
|
||||
# Ever since the Apple Watch app has been added the bitcode for WebRTC needs to be downloaded in order to build successfully
|
||||
./node_modules/react-native-webrtc/tools/downloadBitcode.sh
|
||||
|
||||
cd ios
|
||||
pod install --repo-update --no-ansi
|
||||
cd ..
|
||||
|
||||
mkdir -p /tmp/jitsi-meet/
|
||||
|
||||
xcodebuild archive -quiet -workspace ios/jitsi-meet.xcworkspace -scheme jitsi-meet -configuration Release -archivePath /tmp/jitsi-meet/jitsi-meet.xcarchive
|
||||
|
||||
sed -e "s/YOUR_TEAM_ID/${IOS_TEAM_ID}/g" ios/ci/build-ipa.plist.template > ios/ci/build-ipa.plist
|
||||
|
||||
IPA_EXPORT_DIR=/tmp/jitsi-meet/jitsi-meet-ipa
|
||||
|
||||
xcodebuild -quiet -exportArchive -archivePath /tmp/jitsi-meet/jitsi-meet.xcarchive -exportPath $IPA_EXPORT_DIR -exportOptionsPlist ios/ci/build-ipa.plist
|
||||
|
||||
echo "Will try deploy the .ipa to: ${IPA_DEPLOY_LOCATION}"
|
||||
|
||||
if [ ! -z ${SCP_PROXY_HOST} ];
|
||||
then
|
||||
scp -o ProxyCommand="ssh -t -A -l %r ${SCP_PROXY_HOST} -o \"StrictHostKeyChecking no\" -o \"BatchMode yes\" -W %h:%p" -o StrictHostKeyChecking=no -o LogLevel=DEBUG "${IPA_EXPORT_DIR}/jitsi-meet.ipa" "${IPA_DEPLOY_LOCATION}"
|
||||
else
|
||||
scp -o StrictHostKeyChecking=no -o LogLevel=DEBUG "${IPA_EXPORT_DIR}/jitsi-meet.ipa" "${IPA_DEPLOY_LOCATION}"
|
||||
fi
|
||||
|
||||
rm -r /tmp/jitsi-meet/
|
||||
rm -r $CERT_DIR
|
||||
100
ios/ci/setup-certificates.sh
Executable file
100
ios/ci/setup-certificates.sh
Executable file
@@ -0,0 +1,100 @@
|
||||
# The script is based on tutorial written by Antonis Tsakiridis published at:
|
||||
# https://medium.com/@atsakiridis/continuous-deployment-for-ios-using-travis-ci-55dcea342d9
|
||||
#
|
||||
# APPLE_CERT_URL - the URL pointing to Apple certificate (set to
|
||||
# http://developer.apple.com/certificationauthority/AppleWWDRCA.cer by default)
|
||||
# DEPLOY_SSH_CERT_URL - the SSH private key used by the 'scp' command to deploy
|
||||
# the .ipa. It is expected to be encrypted with the $ENCRYPTION_PASSWORD.
|
||||
# ENCRYPTION_PASSWORD - the password used to decrypt certificate/key files used
|
||||
# in the script.
|
||||
# IOS_DEV_CERT_KEY_URL - URL pointing to provisioning profile certificate key
|
||||
# file (development-key.p12.enc from the tutorial) encrypted with the
|
||||
# $ENCRYPTION_PASSWORD.
|
||||
# IOS_DEV_CERT_URL - URL pointing to provisioning profile certificate file
|
||||
# (development-cert.cer.enc from the tutorial) encrypted with the
|
||||
# $ENCRYPTION_PASSWORD.
|
||||
# IOS_DEV_PROV_PROFILE_URL - URL pointing to provisioning profile file
|
||||
# (profile-development-olympus.mobileprovision.enc from the tutorial) encrypted
|
||||
# IOS_DEV_WATCH_PROV_PROFILE_URL - URL pointing to watch app provisioning profile file(encrypted).
|
||||
# with the $ENCRYPTION_PASSWORD.
|
||||
# IOS_SIGNING_CERT_PASSWORD - the password to the provisioning profile
|
||||
# certificate key (used to open development-key.p12 from the tutorial).
|
||||
|
||||
function echoAndExit1() {
|
||||
echo $1
|
||||
exit 1
|
||||
}
|
||||
|
||||
CERT_DIR=$1
|
||||
|
||||
if [ -z $CERT_DIR ]; then
|
||||
echoAndExit1 "First argument must be certificates directory"
|
||||
fi
|
||||
|
||||
if [ -z $APPLE_CERT_URL ]; then
|
||||
APPLE_CERT_URL="http://developer.apple.com/certificationauthority/AppleWWDRCA.cer"
|
||||
fi
|
||||
|
||||
if [ -z $DEPLOY_SSH_CERT_URL ]; then
|
||||
echoAndExit1 "DEPLOY_SSH_CERT_URL env var is not defined"
|
||||
fi
|
||||
|
||||
if [ -z $ENCRYPTION_PASSWORD ]; then
|
||||
echoAndExit1 "ENCRYPTION_PASSWORD env var is not defined"
|
||||
fi
|
||||
|
||||
if [ -z $IOS_DEV_CERT_KEY_URL ]; then
|
||||
echoAndExit1 "IOS_DEV_CERT_KEY_URL env var is not defined"
|
||||
fi
|
||||
|
||||
if [ -z $IOS_DEV_CERT_URL ]; then
|
||||
echoAndExit1 "IOS_DEV_CERT_URL env var is not defined"
|
||||
fi
|
||||
|
||||
if [ -z $IOS_DEV_PROV_PROFILE_URL ]; then
|
||||
echoAndExit1 "IOS_DEV_PROV_PROFILE_URL env var is not defined"
|
||||
fi
|
||||
|
||||
if [ -z $IOS_DEV_WATCH_PROV_PROFILE_URL ]; then
|
||||
echoAndExit1 "IOS_DEV_WATCH_PROV_PROFILE_URL env var is not defined"
|
||||
fi
|
||||
|
||||
if [ -z $IOS_SIGNING_CERT_PASSWORD ]; then
|
||||
echoAndExit1 "IOS_SIGNING_CERT_PASSWORD env var is not defined"
|
||||
fi
|
||||
|
||||
# certificates
|
||||
|
||||
curl -L -o ${CERT_DIR}/AppleWWDRCA.cer 'http://developer.apple.com/certificationauthority/AppleWWDRCA.cer'
|
||||
curl -L -o ${CERT_DIR}/dev-cert.cer.enc ${IOS_DEV_CERT_URL}
|
||||
curl -L -o ${CERT_DIR}/dev-key.p12.enc ${IOS_DEV_CERT_KEY_URL}
|
||||
curl -L -o ${CERT_DIR}/dev-profile.mobileprovision.enc ${IOS_DEV_PROV_PROFILE_URL}
|
||||
curl -L -o ${CERT_DIR}/dev-watch-profile.mobileprovision.enc ${IOS_DEV_WATCH_PROV_PROFILE_URL}
|
||||
|
||||
|
||||
openssl aes-256-cbc -k "$ENCRYPTION_PASSWORD" -in ${CERT_DIR}/dev-cert.cer.enc -d -a -out ${CERT_DIR}/dev-cert.cer
|
||||
openssl aes-256-cbc -k "$ENCRYPTION_PASSWORD" -in ${CERT_DIR}/dev-key.p12.enc -d -a -out ${CERT_DIR}/dev-key.p12
|
||||
openssl aes-256-cbc -k "$ENCRYPTION_PASSWORD" -in ${CERT_DIR}/dev-profile.mobileprovision.enc -d -a -out ${CERT_DIR}/dev-profile.mobileprovision
|
||||
openssl aes-256-cbc -k "$ENCRYPTION_PASSWORD" -in ${CERT_DIR}/dev-watch-profile.mobileprovision.enc -d -a -out ${CERT_DIR}/dev-watch-profile.mobileprovision
|
||||
|
||||
security create-keychain -p $ENCRYPTION_PASSWORD ios-build.keychain
|
||||
security default-keychain -s ios-build.keychain
|
||||
security unlock-keychain -p $ENCRYPTION_PASSWORD ios-build.keychain
|
||||
security set-keychain-settings -t 3600 -l ~/Library/Keychains/ios-build.keychain
|
||||
|
||||
echo "importing Apple cert"
|
||||
security import ${CERT_DIR}/AppleWWDRCA.cer -k ios-build.keychain -A
|
||||
echo "importing dev-cert.cer"
|
||||
security import ${CERT_DIR}/dev-cert.cer -k ios-build.keychain -A
|
||||
echo "importing dev-key.p12"
|
||||
security import ${CERT_DIR}/dev-key.p12 -k ios-build.keychain -P $IOS_SIGNING_CERT_PASSWORD -A
|
||||
|
||||
echo "will set-key-partition-list"
|
||||
# Fix for OS X Sierra that hungs in the codesign step
|
||||
security set-key-partition-list -S apple-tool:,apple: -s -k $ENCRYPTION_PASSWORD ios-build.keychain > /dev/null
|
||||
echo "done set-key-partition-list"
|
||||
|
||||
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
|
||||
|
||||
cp "${CERT_DIR}/dev-profile.mobileprovision" ~/Library/MobileDevice/Provisioning\ Profiles/
|
||||
cp "${CERT_DIR}/dev-watch-profile.mobileprovision" ~/Library/MobileDevice/Provisioning\ Profiles/
|
||||
@@ -24,6 +24,7 @@
|
||||
#import "ReactUtils.h"
|
||||
|
||||
#import <RNGoogleSignin/RNGoogleSignin.h>
|
||||
#import <WebRTC/RTCLogging.h>
|
||||
|
||||
|
||||
@implementation JitsiMeet {
|
||||
@@ -54,6 +55,11 @@
|
||||
|
||||
// Register a log handler for React.
|
||||
registerReactLogHandler();
|
||||
|
||||
#if 0
|
||||
// Enable WebRTC logs
|
||||
RTCSetMinDebugLogLevel(RTCLoggingSeverityInfo);
|
||||
#endif
|
||||
}
|
||||
|
||||
return self;
|
||||
|
||||
@@ -37,6 +37,7 @@ set -e
|
||||
# IOS_TEAM_ID - the team ID inserted into build-ipa-.plist.template file in
|
||||
# place of "YOUR_TEAM_ID".
|
||||
|
||||
|
||||
# Travis will not print the last echo if there's no sleep 1
|
||||
function echoSleepAndExit1() {
|
||||
echo $1
|
||||
@@ -57,10 +58,6 @@ if [ -z $IPA_DEPLOY_LOCATION ]; then
|
||||
echoSleepAndExit1 "No IPA_DEPLOY_LOCATION defined"
|
||||
fi
|
||||
|
||||
if [ -z $APPLE_CERT_URL ]; then
|
||||
APPLE_CERT_URL="http://developer.apple.com/certificationauthority/AppleWWDRCA.cer"
|
||||
fi
|
||||
|
||||
echo "PR_REPO_SLUG=${PR_REPO_SLUG} PR_BRANCH=${PR_BRANCH}"
|
||||
|
||||
# do the marge and git log
|
||||
@@ -88,47 +85,17 @@ fi
|
||||
|
||||
git log -20 --graph --pretty=format':%C(yellow)%h%Cblue%d%Creset %s %C(white) %an, %ar%Creset'
|
||||
|
||||
# certificates
|
||||
|
||||
#certificates
|
||||
CERT_DIR="ios/travis-ci/certs"
|
||||
|
||||
mkdir $CERT_DIR
|
||||
mkdir -p $CERT_DIR
|
||||
|
||||
./ios/ci/setup-certificates.sh $CERT_DIR
|
||||
|
||||
curl -L -o ${CERT_DIR}/AppleWWDRCA.cer 'http://developer.apple.com/certificationauthority/AppleWWDRCA.cer'
|
||||
curl -L -o ${CERT_DIR}/dev-cert.cer.enc ${IOS_DEV_CERT_URL}
|
||||
curl -L -o ${CERT_DIR}/dev-key.p12.enc ${IOS_DEV_CERT_KEY_URL}
|
||||
curl -L -o ${CERT_DIR}/dev-profile.mobileprovision.enc ${IOS_DEV_PROV_PROFILE_URL}
|
||||
curl -L -o ${CERT_DIR}/dev-watch-profile.mobileprovision.enc ${IOS_DEV_WATCH_PROV_PROFILE_URL}
|
||||
curl -L -o ${CERT_DIR}/id_rsa.enc ${DEPLOY_SSH_CERT_URL}
|
||||
|
||||
openssl aes-256-cbc -k "$ENCRYPTION_PASSWORD" -in ${CERT_DIR}/dev-cert.cer.enc -d -a -out ${CERT_DIR}/dev-cert.cer
|
||||
openssl aes-256-cbc -k "$ENCRYPTION_PASSWORD" -in ${CERT_DIR}/dev-key.p12.enc -d -a -out ${CERT_DIR}/dev-key.p12
|
||||
openssl aes-256-cbc -k "$ENCRYPTION_PASSWORD" -in ${CERT_DIR}/dev-profile.mobileprovision.enc -d -a -out ${CERT_DIR}/dev-profile.mobileprovision
|
||||
openssl aes-256-cbc -k "$ENCRYPTION_PASSWORD" -in ${CERT_DIR}/dev-watch-profile.mobileprovision.enc -d -a -out ${CERT_DIR}/dev-watch-profile.mobileprovision
|
||||
openssl aes-256-cbc -k "$ENCRYPTION_PASSWORD" -in ${CERT_DIR}/id_rsa.enc -d -a -out ${CERT_DIR}/id_rsa
|
||||
chmod 0600 ${CERT_DIR}/id_rsa
|
||||
|
||||
security create-keychain -p $ENCRYPTION_PASSWORD ios-build.keychain
|
||||
security default-keychain -s ios-build.keychain
|
||||
security unlock-keychain -p $ENCRYPTION_PASSWORD ios-build.keychain
|
||||
security set-keychain-settings -t 3600 -l ~/Library/Keychains/ios-build.keychain
|
||||
|
||||
echo "importing Apple cert"
|
||||
security import ${CERT_DIR}/AppleWWDRCA.cer -k ios-build.keychain -A
|
||||
echo "importing dev-cert.cer"
|
||||
security import ${CERT_DIR}/dev-cert.cer -k ios-build.keychain -A
|
||||
echo "importing dev-key.p12"
|
||||
security import ${CERT_DIR}/dev-key.p12 -k ios-build.keychain -P $IOS_SIGNING_CERT_PASSWORD -A
|
||||
|
||||
echo "will set-key-partition-list"
|
||||
# Fix for OS X Sierra that hungs in the codesign step
|
||||
security set-key-partition-list -S apple-tool:,apple: -s -k $ENCRYPTION_PASSWORD ios-build.keychain > /dev/null
|
||||
echo "done set-key-partition-list"
|
||||
|
||||
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
|
||||
|
||||
cp "${CERT_DIR}/dev-profile.mobileprovision" ~/Library/MobileDevice/Provisioning\ Profiles/
|
||||
cp "${CERT_DIR}/dev-watch-profile.mobileprovision" ~/Library/MobileDevice/Provisioning\ Profiles/
|
||||
ssh-add ${CERT_DIR}/id_rsa
|
||||
|
||||
npm install
|
||||
|
||||
@@ -136,24 +103,21 @@ npm install
|
||||
./node_modules/react-native-webrtc/tools/downloadBitcode.sh
|
||||
|
||||
cd ios
|
||||
pod update
|
||||
pod install
|
||||
pod install --repo-update --no-ansi
|
||||
cd ..
|
||||
|
||||
mkdir -p /tmp/jitsi-meet/
|
||||
|
||||
xcodebuild archive -quiet -workspace ios/jitsi-meet.xcworkspace -scheme jitsi-meet -configuration Release -archivePath /tmp/jitsi-meet/jitsi-meet.xcarchive
|
||||
|
||||
sed -e "s/YOUR_TEAM_ID/${IOS_TEAM_ID}/g" ios/travis-ci/build-ipa.plist.template > ios/travis-ci/build-ipa.plist
|
||||
sed -e "s/YOUR_TEAM_ID/${IOS_TEAM_ID}/g" ios/ci/build-ipa.plist.template > ios/ci/build-ipa.plist
|
||||
|
||||
IPA_EXPORT_DIR=/tmp/jitsi-meet/jitsi-meet-ipa
|
||||
|
||||
xcodebuild -quiet -exportArchive -archivePath /tmp/jitsi-meet/jitsi-meet.xcarchive -exportPath $IPA_EXPORT_DIR -exportOptionsPlist ios/travis-ci/build-ipa.plist
|
||||
xcodebuild -quiet -exportArchive -archivePath /tmp/jitsi-meet/jitsi-meet.xcarchive -exportPath $IPA_EXPORT_DIR -exportOptionsPlist ios/ci/build-ipa.plist
|
||||
|
||||
echo "Will try deploy the .ipa to: ${IPA_DEPLOY_LOCATION}"
|
||||
|
||||
ssh-add ${CERT_DIR}/id_rsa
|
||||
|
||||
if [ ! -z ${SCP_PROXY_HOST} ];
|
||||
then
|
||||
scp -o ProxyCommand="ssh -t -A -l %r ${SCP_PROXY_HOST} -o \"StrictHostKeyChecking no\" -o \"BatchMode yes\" -W %h:%p" -o StrictHostKeyChecking=no -o LogLevel=DEBUG "${IPA_EXPORT_DIR}/jitsi-meet.ipa" "${IPA_DEPLOY_LOCATION}"
|
||||
|
||||
@@ -570,6 +570,7 @@
|
||||
"cc": "Toggle subtitles",
|
||||
"chat": "Toggle chat window",
|
||||
"document": "Toggle shared document",
|
||||
"download": "Download our apps",
|
||||
"feedback": "Leave feedback",
|
||||
"fullScreen": "Toggle full screen",
|
||||
"hangup": "Leave the call",
|
||||
@@ -609,6 +610,7 @@
|
||||
"closeChat": "Close chat",
|
||||
"documentClose": "Close shared document",
|
||||
"documentOpen": "Open shared document",
|
||||
"download": "Download our apps",
|
||||
"enterFullScreen": "View full screen",
|
||||
"enterTileView": "Enter tile view",
|
||||
"exitFullScreen": "Exit full screen",
|
||||
@@ -725,7 +727,7 @@
|
||||
"connectCalendarButton": "Connect your calendar",
|
||||
"connectCalendarText": "Connect your calendar to view all your meetings in {{app}}. Plus, add {{provider}} meetings to your calendar and start them with one click.",
|
||||
"enterRoomTitle": "Start a new meeting",
|
||||
"onlyAsciiAllowed": "Meeting name should only contain latin characters and numbers.",
|
||||
"roomNameAllowedChars": "Meeting name should not contain any of these characters: ?, &, :, ', \", %, #.",
|
||||
"go": "GO",
|
||||
"join": "JOIN",
|
||||
"info": "Info",
|
||||
|
||||
3042
package-lock.json
generated
3042
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
12
package.json
12
package.json
@@ -49,7 +49,7 @@
|
||||
"i18next-browser-languagedetector": "3.0.1",
|
||||
"i18next-xhr-backend": "3.0.0",
|
||||
"jQuery-Impromptu": "github:trentrichardson/jQuery-Impromptu#v6.0.0",
|
||||
"jitsi-meet-logger": "github:jitsi/jitsi-meet-logger#a885cc98688ef2c3972284bda901596a26ffee52",
|
||||
"jitsi-meet-logger": "github:jitsi/jitsi-meet-logger#5ec92357570dc8f0b7ffc1528820721c84c6af8b",
|
||||
"jquery": "3.4.0",
|
||||
"jquery-contextmenu": "2.4.5",
|
||||
"jquery-i18next": "1.2.1",
|
||||
@@ -57,7 +57,7 @@
|
||||
"js-utils": "github:jitsi/js-utils#192b1c996e8c05530eb1f19e82a31069c3021e31",
|
||||
"jsrsasign": "8.0.12",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#f9808adb8eb523bae3318f9f8ef49b544651485f",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#5521a40aa85cb6f128f8a6dad9b72a5646132484",
|
||||
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
|
||||
"lodash": "4.17.13",
|
||||
"moment": "2.19.4",
|
||||
@@ -80,7 +80,7 @@
|
||||
"react-native-svg-transformer": "0.13.0",
|
||||
"react-native-swipeout": "2.3.6",
|
||||
"react-native-watch-connectivity": "0.2.0",
|
||||
"react-native-webrtc": "github:jitsi/react-native-webrtc#047b019a7ce1ec93ab4a2f6796e997d7a02e8e5d",
|
||||
"react-native-webrtc": "github:react-native-webrtc/react-native-webrtc#a12a6cdfdefe53d03b388394e4cf10966bd99fca",
|
||||
"react-native-webview": "7.4.1",
|
||||
"react-redux": "7.1.0",
|
||||
"react-textarea-autosize": "7.1.0",
|
||||
@@ -122,14 +122,14 @@
|
||||
"imports-loader": "0.7.1",
|
||||
"jetifier": "1.6.4",
|
||||
"metro-react-native-babel-preset": "0.56.0",
|
||||
"node-sass": "4.10.0",
|
||||
"node-sass": "4.12.0",
|
||||
"precommit-hook": "3.0.0",
|
||||
"string-replace-loader": "2.1.1",
|
||||
"style-loader": "0.19.0",
|
||||
"webpack": "4.26.1",
|
||||
"webpack": "4.27.1",
|
||||
"webpack-bundle-analyzer": "3.4.1",
|
||||
"webpack-cli": "3.1.2",
|
||||
"webpack-dev-server": "3.1.14"
|
||||
"webpack-dev-server": "3.8.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0",
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
} from '../../base/dialog';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { JitsiConnectionErrors } from '../../base/lib-jitsi-meet';
|
||||
import { StyleType } from '../../base/styles';
|
||||
import type { StyleType } from '../../base/styles';
|
||||
|
||||
import { authenticateAndUpgradeRole, cancelLogin } from '../actions';
|
||||
import styles from './styles';
|
||||
|
||||
@@ -153,6 +153,8 @@ class ColorSchemeRegistry {
|
||||
const colorScheme = toState(stateful)['features/base/color-scheme'];
|
||||
|
||||
return {
|
||||
...defaultScheme._defaultTheme,
|
||||
...colorScheme._defaultTheme,
|
||||
...defaultScheme[componentName],
|
||||
...colorScheme[componentName]
|
||||
}[colorDefinition];
|
||||
|
||||
@@ -6,18 +6,26 @@ import { ColorPalette, getRGBAFormat } from '../styles';
|
||||
* The default color scheme of the application.
|
||||
*/
|
||||
export default {
|
||||
'BottomSheet': {
|
||||
'_defaultTheme': {
|
||||
// Generic app theme colors that are used accross the entire app.
|
||||
// All scheme definitions below inherit these values.
|
||||
background: 'rgb(255, 255, 255)',
|
||||
icon: '#1c2025',
|
||||
label: '#1c2025'
|
||||
icon: 'rgb(28, 32, 37)',
|
||||
text: 'rgb(28, 32, 37)'
|
||||
},
|
||||
'Chat': {
|
||||
displayName: 'rgb(94, 109, 121)',
|
||||
localMsgBackground: 'rgb(215, 230, 249)',
|
||||
privateMsgBackground: 'rgb(250, 219, 219)',
|
||||
privateMsgNotice: 'rgb(186, 39, 58)',
|
||||
remoteMsgBackground: 'rgb(241, 242, 246)',
|
||||
replyBorder: 'rgb(219, 197, 200)',
|
||||
replyIcon: 'rgb(94, 109, 121)'
|
||||
},
|
||||
'Dialog': {
|
||||
background: 'rgb(255, 255, 255)',
|
||||
border: 'rgba(0, 3, 6, 0.6)',
|
||||
buttonBackground: ColorPalette.blue,
|
||||
buttonLabel: ColorPalette.white,
|
||||
icon: '#1c2025',
|
||||
text: '#1c2025'
|
||||
buttonLabel: ColorPalette.white
|
||||
},
|
||||
'Header': {
|
||||
background: ColorPalette.blue,
|
||||
@@ -30,8 +38,7 @@ export default {
|
||||
background: 'rgb(42, 58, 75)'
|
||||
},
|
||||
'LoadConfigOverlay': {
|
||||
background: 'rgb(249, 249, 249)',
|
||||
text: 'rgb(28, 32, 37)'
|
||||
background: 'rgb(249, 249, 249)'
|
||||
},
|
||||
'Thumbnail': {
|
||||
activeParticipantHighlight: 'rgb(81, 214, 170)',
|
||||
|
||||
@@ -147,7 +147,7 @@ ColorSchemeRegistry.register('BottomSheet', {
|
||||
* Style for the label in a generic item rendered in the menu.
|
||||
*/
|
||||
labelStyle: {
|
||||
color: schemeColor('label'),
|
||||
color: schemeColor('text'),
|
||||
flexShrink: 1,
|
||||
fontSize: MD_FONT_SIZE,
|
||||
marginLeft: 32,
|
||||
|
||||
@@ -23,6 +23,11 @@ type Props = {
|
||||
*/
|
||||
id?: string,
|
||||
|
||||
/**
|
||||
* Function to invoke on click.
|
||||
*/
|
||||
onClick?: Function,
|
||||
|
||||
/**
|
||||
* The size of the icon (if not provided by the style object).
|
||||
*/
|
||||
@@ -53,6 +58,7 @@ export default function Icon(props: Props) {
|
||||
className,
|
||||
color,
|
||||
id,
|
||||
onClick,
|
||||
size,
|
||||
src: IconComponent,
|
||||
style
|
||||
@@ -69,6 +75,7 @@ export default function Icon(props: Props) {
|
||||
return (
|
||||
<Container
|
||||
className = { `jitsi-icon ${className}` }
|
||||
onClick = { onClick }
|
||||
style = { restStyle }>
|
||||
<IconComponent
|
||||
fill = { calculatedColor }
|
||||
|
||||
3
react/features/base/icons/svg/copy.svg
Normal file
3
react/features/base/icons/svg/copy.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4 4C4 2.89543 4.89543 2 6 2H14C15.1046 2 16 2.89543 16 4H6V18C4.89543 18 4 17.1046 4 16V4ZM10 8V20H18V8H10ZM10 6H18C19.1046 6 20 6.89543 20 8V20C20 21.1046 19.1046 22 18 22H10C8.89543 22 8 21.1046 8 20V8C8 6.89543 8.89543 6 10 6Z" fill="#5E6D7A"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 401 B |
3
react/features/base/icons/svg/download.svg
Executable file
3
react/features/base/icons/svg/download.svg
Executable file
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11 13.3705V5C11 4.44772 11.4477 4 12 4C12.5523 4 13 4.44772 13 5V13.4667L15.631 10.5433C16.0005 10.1328 16.6328 10.0995 17.0433 10.469C17.4538 10.8384 17.4871 11.4707 17.1176 11.8812L12.1064 17.4492C12.0727 17.4867 12.0139 17.4867 11.9802 17.4492L6.96897 11.8812C6.59951 11.4707 6.63278 10.8384 7.04329 10.469C7.4538 10.0995 8.08609 10.1328 8.45555 10.5433L11 13.3705ZM20 15C20 14.4477 20.4477 14 21 14C21.5523 14 22 14.4477 22 15V21C22 21.5523 21.5523 22 21 22H3C2.96548 22 2.93137 21.9983 2.89776 21.9948C2.3935 21.9436 2 21.5178 2 21V15C2 14.4477 2.44772 14 3 14C3.55228 14 4 14.4477 4 15V20H20V15Z" fill="#A4B8D1"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 773 B |
@@ -18,11 +18,13 @@ export { default as IconClose } from './close.svg';
|
||||
export { default as IconClosedCaption } from './closed_caption.svg';
|
||||
export { default as IconConnectionActive } from './gsm-bars.svg';
|
||||
export { default as IconConnectionInactive } from './ninja.svg';
|
||||
export { default as IconCopy } from './copy.svg';
|
||||
export { default as IconDeviceBluetooth } from './bluetooth.svg';
|
||||
export { default as IconDeviceEarpiece } from './phone-talk.svg';
|
||||
export { default as IconDeviceHeadphone } from './headset.svg';
|
||||
export { default as IconDeviceSpeaker } from './volume.svg';
|
||||
export { default as IconDominantSpeaker } from './dominant-speaker.svg';
|
||||
export { default as IconDownload } from './download.svg';
|
||||
export { default as IconEventNote } from './event_note.svg';
|
||||
export { default as IconExitFullScreen } from './exit-full-screen.svg';
|
||||
export { default as IconFeedback } from './feedback.svg';
|
||||
|
||||
@@ -63,13 +63,13 @@ export default class JitsiMeetLogStorage {
|
||||
for (let i = 0, len = logEntries.length; i < len; i++) {
|
||||
const logEntry = logEntries[i];
|
||||
|
||||
if (typeof logEntry === 'object') {
|
||||
// Aggregated message
|
||||
logMessage += `(${logEntry.count}) ${logEntry.text}\n`;
|
||||
} else {
|
||||
// Regular message
|
||||
logMessage += `${logEntry}\n`;
|
||||
if (logEntry.timestamp) {
|
||||
logMessage += `${logEntry.timestamp} `;
|
||||
}
|
||||
if (logEntry.count > 1) {
|
||||
logMessage += `(${logEntry.count}) `;
|
||||
}
|
||||
logMessage += `${logEntry.text}\n`;
|
||||
}
|
||||
logMessage += '"}';
|
||||
|
||||
|
||||
@@ -39,7 +39,8 @@ function buildTransport() {
|
||||
'warn',
|
||||
'error'
|
||||
].reduce((logger, logName) => {
|
||||
logger[logName] = (...args: Array<string>) => {
|
||||
logger[logName] = (timestamp: string, ...args: Array<string>) => {
|
||||
// It ignores the timestamp argument, because LogBridge will add it on the native side anyway
|
||||
const nargs = args.map(arg => {
|
||||
if (arg instanceof Error) {
|
||||
const errorBody = {
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
} from '../../media';
|
||||
import { Container, TintedView } from '../../react';
|
||||
import { connect } from '../../redux';
|
||||
import { StyleType } from '../../styles';
|
||||
import type { StyleType } from '../../styles';
|
||||
import { TestHint } from '../../testing/components';
|
||||
import { getTrackByMediaTypeAndParticipant } from '../../tracks';
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ export default class Linkify extends Component<Props> {
|
||||
return (
|
||||
<ReactLinkify
|
||||
componentDecorator = { this._componentDecorator }>
|
||||
<Text>
|
||||
<Text selectable = { true }>
|
||||
{ this.props.children }
|
||||
</Text>
|
||||
</ReactLinkify>
|
||||
|
||||
@@ -4,6 +4,8 @@ import { PureComponent } from 'react';
|
||||
|
||||
import { getLocalizedDateFormatter } from '../../base/i18n';
|
||||
|
||||
import { MESSAGE_TYPE_ERROR, MESSAGE_TYPE_LOCAL } from '../constants';
|
||||
|
||||
/**
|
||||
* Formatter string to display the message timestamp.
|
||||
*/
|
||||
@@ -65,7 +67,7 @@ export default class AbstractChatMessage<P: Props> extends PureComponent<P> {
|
||||
_getMessageText() {
|
||||
const { message } = this.props;
|
||||
|
||||
return message.messageType === 'error'
|
||||
return message.messageType === MESSAGE_TYPE_ERROR
|
||||
? this.props.t('chat.error', {
|
||||
error: message.message
|
||||
})
|
||||
@@ -81,7 +83,7 @@ export default class AbstractChatMessage<P: Props> extends PureComponent<P> {
|
||||
const { message, t } = this.props;
|
||||
|
||||
return t('chat.privateNotice', {
|
||||
recipient: message.messageType === 'local' ? message.recipient : t('chat.you')
|
||||
recipient: message.messageType === MESSAGE_TYPE_LOCAL ? message.recipient : t('chat.you')
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { getParticipantDisplayName } from '../../base/participants';
|
||||
|
||||
import { setPrivateMessageRecipient } from '../actions';
|
||||
|
||||
type Props = {
|
||||
export type Props = {
|
||||
|
||||
/**
|
||||
* Function used to translate i18n labels.
|
||||
@@ -27,7 +27,7 @@ type Props = {
|
||||
/**
|
||||
* Abstract class for the {@code MessageRecipient} component.
|
||||
*/
|
||||
export default class AbstractMessageRecipient extends PureComponent<Props> {
|
||||
export default class AbstractMessageRecipient<P: Props> extends PureComponent<P> {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -3,15 +3,16 @@
|
||||
import React from 'react';
|
||||
import { KeyboardAvoidingView, SafeAreaView } from 'react-native';
|
||||
|
||||
import { ColorSchemeRegistry } from '../../../base/color-scheme';
|
||||
import { translate } from '../../../base/i18n';
|
||||
|
||||
import { HeaderWithNavigation, SlidingView } from '../../../base/react';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { StyleType } from '../../../base/styles';
|
||||
|
||||
import AbstractChat, {
|
||||
_mapDispatchToProps,
|
||||
_mapStateToProps,
|
||||
type Props
|
||||
_mapStateToProps as _abstractMapStateToProps,
|
||||
type Props as AbstractProps
|
||||
} from '../AbstractChat';
|
||||
|
||||
import ChatInputBar from './ChatInputBar';
|
||||
@@ -19,6 +20,14 @@ import MessageContainer from './MessageContainer';
|
||||
import MessageRecipient from './MessageRecipient';
|
||||
import styles from './styles';
|
||||
|
||||
type Props = AbstractProps & {
|
||||
|
||||
/**
|
||||
* The color-schemed stylesheet of the feature.
|
||||
*/
|
||||
_styles: StyleType
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements a React native component that renders the chat window (modal) of
|
||||
* the mobile client.
|
||||
@@ -41,6 +50,8 @@ class Chat extends AbstractChat<Props> {
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { _styles } = this.props;
|
||||
|
||||
return (
|
||||
<SlidingView
|
||||
onHide = { this._onClose }
|
||||
@@ -52,7 +63,7 @@ class Chat extends AbstractChat<Props> {
|
||||
<HeaderWithNavigation
|
||||
headerLabelKey = 'chat.title'
|
||||
onPressBack = { this._onClose } />
|
||||
<SafeAreaView style = { styles.backdrop }>
|
||||
<SafeAreaView style = { _styles.backdrop }>
|
||||
<MessageContainer messages = { this.props._messages } />
|
||||
<MessageRecipient />
|
||||
<ChatInputBar onSend = { this.props._onSendMessage } />
|
||||
@@ -80,4 +91,17 @@ class Chat extends AbstractChat<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of the redux state to the props of this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
return {
|
||||
..._abstractMapStateToProps(state),
|
||||
_styles: ColorSchemeRegistry.get(state, 'Chat')
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps, _mapDispatchToProps)(Chat));
|
||||
|
||||
@@ -4,16 +4,28 @@ import React from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
|
||||
import { Avatar } from '../../../base/avatar';
|
||||
import { ColorSchemeRegistry } from '../../../base/color-scheme';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { Linkify } from '../../../base/react';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { type StyleType } from '../../../base/styles';
|
||||
|
||||
import { MESSAGE_TYPE_ERROR, MESSAGE_TYPE_LOCAL } from '../../constants';
|
||||
import { replaceNonUnicodeEmojis } from '../../functions';
|
||||
|
||||
import AbstractChatMessage, { type Props } from '../AbstractChatMessage';
|
||||
import AbstractChatMessage, { type Props as AbstractProps } from '../AbstractChatMessage';
|
||||
import PrivateMessageButton from '../PrivateMessageButton';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
type Props = AbstractProps & {
|
||||
|
||||
/**
|
||||
* The color-schemed stylesheet of the feature.
|
||||
*/
|
||||
_styles: StyleType
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders a single chat message.
|
||||
*/
|
||||
@@ -24,55 +36,58 @@ class ChatMessage extends AbstractChatMessage<Props> {
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { message } = this.props;
|
||||
const localMessage = message.messageType === 'local';
|
||||
const { _styles, message } = this.props;
|
||||
const localMessage = message.messageType === MESSAGE_TYPE_LOCAL;
|
||||
const { privateMessage } = message;
|
||||
|
||||
// Style arrays that need to be updated in various scenarios, such as
|
||||
// error messages or others.
|
||||
const detailsWrapperStyle = [
|
||||
styles.detailsWrapper
|
||||
];
|
||||
const textWrapperStyle = [
|
||||
styles.textWrapper
|
||||
const messageBubbleStyle = [
|
||||
styles.messageBubble
|
||||
];
|
||||
|
||||
if (localMessage) {
|
||||
// This is a message sent by the local participant.
|
||||
|
||||
// The wrapper needs to be aligned to the right.
|
||||
detailsWrapperStyle.push(styles.ownMessageDetailsWrapper);
|
||||
|
||||
// The bubble needs to be differently styled.
|
||||
textWrapperStyle.push(styles.ownTextWrapper);
|
||||
} else if (message.messageType === 'error') {
|
||||
// The bubble needs to be differently styled.
|
||||
textWrapperStyle.push(styles.systemTextWrapper);
|
||||
// The bubble needs some additional styling
|
||||
messageBubbleStyle.push(_styles.localMessageBubble);
|
||||
} else if (message.messageType === MESSAGE_TYPE_ERROR) {
|
||||
// This is a system message.
|
||||
|
||||
// The bubble needs some additional styling
|
||||
messageBubbleStyle.push(styles.systemMessageBubble);
|
||||
} else {
|
||||
// This is a remote message sent by a remote participant.
|
||||
|
||||
// The bubble needs some additional styling
|
||||
messageBubbleStyle.push(_styles.remoteMessageBubble);
|
||||
}
|
||||
|
||||
if (privateMessage) {
|
||||
messageBubbleStyle.push(_styles.privateMessageBubble);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style = { styles.messageWrapper } >
|
||||
{ this._renderAvatar() }
|
||||
<View style = { detailsWrapperStyle }>
|
||||
<View style = { styles.replyWrapper }>
|
||||
<View style = { textWrapperStyle } >
|
||||
{
|
||||
this.props.showDisplayName
|
||||
&& this._renderDisplayName()
|
||||
}
|
||||
<View style = { messageBubbleStyle }>
|
||||
<View style = { styles.textWrapper } >
|
||||
{ this._renderDisplayName() }
|
||||
<Linkify linkStyle = { styles.chatLink }>
|
||||
{ replaceNonUnicodeEmojis(this._getMessageText()) }
|
||||
</Linkify>
|
||||
{
|
||||
message.privateMessage
|
||||
&& this._renderPrivateNotice()
|
||||
}
|
||||
{ this._renderPrivateNotice() }
|
||||
</View>
|
||||
{ message.privateMessage && !localMessage
|
||||
&& <PrivateMessageButton
|
||||
participantID = { message.id }
|
||||
reply = { true }
|
||||
showLabel = { false }
|
||||
toggledStyles = { styles.replyStyles } /> }
|
||||
{ this._renderPrivateReplyButton() }
|
||||
</View>
|
||||
{ this.props.showTimestamp && this._renderTimestamp() }
|
||||
{ this._renderTimestamp() }
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
@@ -104,37 +119,77 @@ class ChatMessage extends AbstractChatMessage<Props> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the display name of the sender.
|
||||
* Renders the display name of the sender if necessary.
|
||||
*
|
||||
* @returns {React$Element<*>}
|
||||
* @returns {React$Element<*> | null}
|
||||
*/
|
||||
_renderDisplayName() {
|
||||
const { _styles, message, showDisplayName } = this.props;
|
||||
|
||||
if (!showDisplayName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Text style = { styles.displayName }>
|
||||
{ this.props.message.displayName }
|
||||
<Text style = { _styles.displayName }>
|
||||
{ message.displayName }
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the message privacy notice.
|
||||
* Renders the message privacy notice, if necessary.
|
||||
*
|
||||
* @returns {React$Element<*>}
|
||||
* @returns {React$Element<*> | null}
|
||||
*/
|
||||
_renderPrivateNotice() {
|
||||
const { _styles, message } = this.props;
|
||||
|
||||
if (!message.privateMessage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Text style = { styles.privateNotice }>
|
||||
<Text style = { _styles.privateNotice }>
|
||||
{ this._getPrivateNoticeMessage() }
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the time at which the message was sent.
|
||||
* Renders the private reply button, if necessary.
|
||||
*
|
||||
* @returns {React$Element<*>}
|
||||
* @returns {React$Element<*> | null}
|
||||
*/
|
||||
_renderPrivateReplyButton() {
|
||||
const { _styles, message } = this.props;
|
||||
const { messageType, privateMessage } = message;
|
||||
|
||||
if (!privateMessage || messageType === MESSAGE_TYPE_LOCAL) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style = { _styles.replyContainer }>
|
||||
<PrivateMessageButton
|
||||
participantID = { message.id }
|
||||
reply = { true }
|
||||
showLabel = { false }
|
||||
toggledStyles = { _styles.replyStyles } />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the time at which the message was sent, if necessary.
|
||||
*
|
||||
* @returns {React$Element<*> | null}
|
||||
*/
|
||||
_renderTimestamp() {
|
||||
if (!this.props.showTimestamp) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Text style = { styles.timeText }>
|
||||
{ this._getFormattedTimestamp() }
|
||||
@@ -143,4 +198,16 @@ class ChatMessage extends AbstractChatMessage<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(ChatMessage);
|
||||
/**
|
||||
* Maps part of the redux state to the props of this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
return {
|
||||
_styles: ColorSchemeRegistry.get(state, 'Chat')
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(ChatMessage));
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
import React, { Component } from 'react';
|
||||
import { FlatList } from 'react-native';
|
||||
|
||||
import { MESSAGE_TYPE_LOCAL, MESSAGE_TYPE_REMOTE } from '../../constants';
|
||||
|
||||
import ChatMessage from './ChatMessage';
|
||||
import styles from './styles';
|
||||
|
||||
@@ -73,11 +75,11 @@ export default class ChatMessageGroup extends Component<Props> {
|
||||
<ChatMessage
|
||||
message = { message }
|
||||
showAvatar = {
|
||||
this.props.messages[0].messageType !== 'local'
|
||||
this.props.messages[0].messageType !== MESSAGE_TYPE_LOCAL
|
||||
&& index === this.props.messages.length - 1
|
||||
}
|
||||
showDisplayName = {
|
||||
this.props.messages[0].messageType === 'remote'
|
||||
this.props.messages[0].messageType === MESSAGE_TYPE_REMOTE
|
||||
&& index === this.props.messages.length - 1
|
||||
}
|
||||
showTimestamp = { index === 0 } />
|
||||
|
||||
@@ -3,28 +3,37 @@
|
||||
import React from 'react';
|
||||
import { Text, TouchableHighlight, View } from 'react-native';
|
||||
|
||||
import { ColorSchemeRegistry } from '../../../base/color-scheme';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { Icon, IconCancelSelection } from '../../../base/icons';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { type StyleType } from '../../../base/styles';
|
||||
|
||||
import AbstractMessageRecipient, {
|
||||
_mapDispatchToProps,
|
||||
_mapStateToProps
|
||||
_mapStateToProps as _abstractMapStateToProps,
|
||||
type Props as AbstractProps
|
||||
} from '../AbstractMessageRecipient';
|
||||
|
||||
import styles from './styles';
|
||||
type Props = AbstractProps & {
|
||||
|
||||
/**
|
||||
* The color-schemed stylesheet of the feature.
|
||||
*/
|
||||
_styles: StyleType
|
||||
};
|
||||
|
||||
/**
|
||||
* Class to implement the displaying of the recipient of the next message.
|
||||
*/
|
||||
class MessageRecipient extends AbstractMessageRecipient {
|
||||
class MessageRecipient extends AbstractMessageRecipient<Props> {
|
||||
/**
|
||||
* Implements {@code PureComponent#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { _privateMessageRecipient } = this.props;
|
||||
const { _privateMessageRecipient, _styles } = this.props;
|
||||
|
||||
if (!_privateMessageRecipient) {
|
||||
return null;
|
||||
@@ -33,8 +42,8 @@ class MessageRecipient extends AbstractMessageRecipient {
|
||||
const { t } = this.props;
|
||||
|
||||
return (
|
||||
<View style = { styles.messageRecipientContainer }>
|
||||
<Text style = { styles.messageRecipientText }>
|
||||
<View style = { _styles.messageRecipientContainer }>
|
||||
<Text style = { _styles.messageRecipientText }>
|
||||
{ t('chat.messageTo', {
|
||||
recipient: _privateMessageRecipient
|
||||
}) }
|
||||
@@ -42,11 +51,24 @@ class MessageRecipient extends AbstractMessageRecipient {
|
||||
<TouchableHighlight onPress = { this.props._onRemovePrivateMessageRecipient }>
|
||||
<Icon
|
||||
src = { IconCancelSelection }
|
||||
style = { styles.messageRecipientCancelIcon } />
|
||||
style = { _styles.messageRecipientCancelIcon } />
|
||||
</TouchableHighlight>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of the redux state to the props of this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
return {
|
||||
..._abstractMapStateToProps(state),
|
||||
_styles: ColorSchemeRegistry.get(state, 'Chat')
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps, _mapDispatchToProps)(MessageRecipient));
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
// @flow
|
||||
|
||||
import { ColorSchemeRegistry, schemeColor } from '../../../base/color-scheme';
|
||||
import { BoxModel, ColorPalette } from '../../../base/styles';
|
||||
|
||||
const BUBBLE_RADIUS = 8;
|
||||
|
||||
/**
|
||||
* The styles of the feature chat.
|
||||
*
|
||||
@@ -20,14 +23,6 @@ export default {
|
||||
width: 32
|
||||
},
|
||||
|
||||
/**
|
||||
* Background of the chat screen.
|
||||
*/
|
||||
backdrop: {
|
||||
backgroundColor: ColorPalette.white,
|
||||
flex: 1
|
||||
},
|
||||
|
||||
chatContainer: {
|
||||
alignItems: 'stretch',
|
||||
flex: 1,
|
||||
@@ -47,14 +42,6 @@ export default {
|
||||
flexDirection: 'column'
|
||||
},
|
||||
|
||||
/**
|
||||
* The text node for the display name.
|
||||
*/
|
||||
displayName: {
|
||||
color: 'rgb(118, 136, 152)',
|
||||
fontSize: 13
|
||||
},
|
||||
|
||||
/**
|
||||
* A special padding to avoid issues on some devices (such as Android devices with custom suggestions bar).
|
||||
*/
|
||||
@@ -76,35 +63,16 @@ export default {
|
||||
height: 48
|
||||
},
|
||||
|
||||
messageBubble: {
|
||||
alignItems: 'center',
|
||||
borderRadius: BUBBLE_RADIUS,
|
||||
flexDirection: 'row'
|
||||
},
|
||||
|
||||
messageContainer: {
|
||||
flex: 1
|
||||
},
|
||||
|
||||
messageRecipientCancelIcon: {
|
||||
color: ColorPalette.white,
|
||||
fontSize: 18
|
||||
},
|
||||
|
||||
messageRecipientContainer: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: ColorPalette.warning,
|
||||
flexDirection: 'row',
|
||||
padding: BoxModel.padding
|
||||
},
|
||||
|
||||
messageRecipientText: {
|
||||
color: ColorPalette.white,
|
||||
flex: 1
|
||||
},
|
||||
|
||||
/**
|
||||
* The message text itself.
|
||||
*/
|
||||
messageText: {
|
||||
color: 'rgb(28, 32, 37)',
|
||||
fontSize: 15
|
||||
},
|
||||
|
||||
/**
|
||||
* Wrapper View for the entire block.
|
||||
*/
|
||||
@@ -123,34 +91,11 @@ export default {
|
||||
alignItems: 'flex-end'
|
||||
},
|
||||
|
||||
/**
|
||||
* Style modifier for the {@code textWrapper} for own messages.
|
||||
*/
|
||||
ownTextWrapper: {
|
||||
backgroundColor: 'rgb(210, 231, 249)',
|
||||
borderTopLeftRadius: 8,
|
||||
borderTopRightRadius: 0
|
||||
},
|
||||
|
||||
replyWrapper: {
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row'
|
||||
},
|
||||
|
||||
replyStyles: {
|
||||
iconStyle: {
|
||||
color: 'rgb(118, 136, 152)',
|
||||
fontSize: 22,
|
||||
margin: BoxModel.margin / 2
|
||||
}
|
||||
},
|
||||
|
||||
privateNotice: {
|
||||
color: ColorPalette.warning,
|
||||
fontSize: 13,
|
||||
fontStyle: 'italic'
|
||||
},
|
||||
|
||||
sendButtonIcon: {
|
||||
color: ColorPalette.darkGrey,
|
||||
fontSize: 22
|
||||
@@ -159,7 +104,7 @@ export default {
|
||||
/**
|
||||
* Style modifier for system (error) messages.
|
||||
*/
|
||||
systemTextWrapper: {
|
||||
systemMessageBubble: {
|
||||
backgroundColor: 'rgb(247, 215, 215)'
|
||||
},
|
||||
|
||||
@@ -168,9 +113,6 @@ export default {
|
||||
*/
|
||||
textWrapper: {
|
||||
alignItems: 'flex-start',
|
||||
backgroundColor: 'rgb(240, 243, 247)',
|
||||
borderRadius: 8,
|
||||
borderTopLeftRadius: 0,
|
||||
flexDirection: 'column',
|
||||
padding: 9
|
||||
},
|
||||
@@ -183,3 +125,73 @@ export default {
|
||||
fontSize: 13
|
||||
}
|
||||
};
|
||||
|
||||
ColorSchemeRegistry.register('Chat', {
|
||||
/**
|
||||
* Background of the chat screen.
|
||||
*/
|
||||
backdrop: {
|
||||
backgroundColor: schemeColor('background'),
|
||||
flex: 1
|
||||
},
|
||||
|
||||
/**
|
||||
* The text node for the display name.
|
||||
*/
|
||||
displayName: {
|
||||
color: schemeColor('displayName'),
|
||||
fontSize: 13
|
||||
},
|
||||
|
||||
localMessageBubble: {
|
||||
backgroundColor: schemeColor('localMsgBackground'),
|
||||
borderTopRightRadius: 0
|
||||
},
|
||||
|
||||
messageRecipientCancelIcon: {
|
||||
color: schemeColor('icon'),
|
||||
fontSize: 18
|
||||
},
|
||||
|
||||
messageRecipientContainer: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: schemeColor('privateMsgBackground'),
|
||||
flexDirection: 'row',
|
||||
padding: BoxModel.padding
|
||||
},
|
||||
|
||||
messageRecipientText: {
|
||||
color: schemeColor('text'),
|
||||
flex: 1
|
||||
},
|
||||
|
||||
privateNotice: {
|
||||
color: schemeColor('privateMsgNotice'),
|
||||
fontSize: 11,
|
||||
marginTop: 6
|
||||
},
|
||||
|
||||
privateMessageBubble: {
|
||||
backgroundColor: schemeColor('privateMsgBackground')
|
||||
},
|
||||
|
||||
remoteMessageBubble: {
|
||||
backgroundColor: schemeColor('remoteMsgBackground'),
|
||||
borderTopLeftRadius: 0
|
||||
},
|
||||
|
||||
replyContainer: {
|
||||
alignSelf: 'stretch',
|
||||
borderLeftColor: schemeColor('replyBorder'),
|
||||
borderLeftWidth: 1,
|
||||
justifyContent: 'center'
|
||||
},
|
||||
|
||||
replyStyles: {
|
||||
iconStyle: {
|
||||
color: schemeColor('replyIcon'),
|
||||
fontSize: 22,
|
||||
padding: 8
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import React from 'react';
|
||||
import Transition from 'react-transition-group/Transition';
|
||||
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { Icon, IconClose } from '../../../base/icons';
|
||||
import { connect } from '../../../base/redux';
|
||||
|
||||
import AbstractChat, {
|
||||
@@ -137,7 +138,9 @@ class Chat extends AbstractChat<Props> {
|
||||
<div className = 'chat-header'>
|
||||
<div
|
||||
className = 'chat-close'
|
||||
onClick = { this.props._onToggleChat }>X</div>
|
||||
onClick = { this.props._onToggleChat }>
|
||||
<Icon src = { IconClose } />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import { toArray } from 'react-emoji-render';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { Linkify } from '../../../base/react';
|
||||
|
||||
import { MESSAGE_TYPE_LOCAL } from '../../constants';
|
||||
|
||||
import AbstractChatMessage, {
|
||||
type Props
|
||||
} from '../AbstractChatMessage';
|
||||
@@ -39,19 +41,25 @@ class ChatMessage extends AbstractChatMessage<Props> {
|
||||
|
||||
return (
|
||||
<div className = 'chatmessage-wrapper'>
|
||||
<div className = 'replywrapper'>
|
||||
<div className = 'chatmessage'>
|
||||
{ this.props.showDisplayName && this._renderDisplayName() }
|
||||
<div className = 'usermessage'>
|
||||
{ processedMessage }
|
||||
<div className = { `chatmessage ${message.privateMessage ? 'privatemessage' : ''}` }>
|
||||
<div className = 'replywrapper'>
|
||||
<div className = 'messagecontent'>
|
||||
{ this.props.showDisplayName && this._renderDisplayName() }
|
||||
<div className = 'usermessage'>
|
||||
{ processedMessage }
|
||||
</div>
|
||||
{ message.privateMessage && this._renderPrivateNotice() }
|
||||
</div>
|
||||
{ message.privateMessage && this._renderPrivateNotice() }
|
||||
{ message.privateMessage && message.messageType !== MESSAGE_TYPE_LOCAL
|
||||
&& (
|
||||
<div className = 'messageactions'>
|
||||
<PrivateMessageButton
|
||||
participantID = { message.id }
|
||||
reply = { true }
|
||||
showLabel = { false } />
|
||||
</div>
|
||||
) }
|
||||
</div>
|
||||
{ message.privateMessage && message.messageType !== 'local'
|
||||
&& <PrivateMessageButton
|
||||
participantID = { message.id }
|
||||
reply = { true }
|
||||
showLabel = { false } /> }
|
||||
</div>
|
||||
{ this.props.showTimestamp && this._renderTimestamp() }
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { MESSAGE_TYPE_REMOTE } from '../../constants';
|
||||
|
||||
import AbstractMessageContainer, { type Props }
|
||||
from '../AbstractMessageContainer';
|
||||
|
||||
@@ -61,7 +63,7 @@ export default class MessageContainer extends AbstractMessageContainer {
|
||||
|
||||
return (
|
||||
<ChatMessageGroup
|
||||
className = { messageType || 'remote' }
|
||||
className = { messageType || MESSAGE_TYPE_REMOTE }
|
||||
key = { index }
|
||||
messages = { group } />
|
||||
);
|
||||
|
||||
@@ -8,13 +8,14 @@ import { connect } from '../../../base/redux';
|
||||
|
||||
import AbstractMessageRecipient, {
|
||||
_mapDispatchToProps,
|
||||
_mapStateToProps
|
||||
_mapStateToProps,
|
||||
type Props
|
||||
} from '../AbstractMessageRecipient';
|
||||
|
||||
/**
|
||||
* Class to implement the displaying of the recipient of the next message.
|
||||
*/
|
||||
class MessageRecipient extends AbstractMessageRecipient {
|
||||
class MessageRecipient extends AbstractMessageRecipient<Props> {
|
||||
/**
|
||||
* Implements {@code PureComponent#render}.
|
||||
*
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// @flow
|
||||
|
||||
/**
|
||||
* The audio ID of the audio element for which the {@link playAudio} action is
|
||||
* triggered when new chat message is received.
|
||||
@@ -5,3 +7,18 @@
|
||||
* @type {string}
|
||||
*/
|
||||
export const INCOMING_MSG_SOUND_ID = 'INCOMING_MSG_SOUND';
|
||||
|
||||
/**
|
||||
* The {@code messageType} of error (system) messages.
|
||||
*/
|
||||
export const MESSAGE_TYPE_ERROR = 'error';
|
||||
|
||||
/**
|
||||
* The {@code messageType} of local messages.
|
||||
*/
|
||||
export const MESSAGE_TYPE_LOCAL = 'local';
|
||||
|
||||
/**
|
||||
* The {@code messageType} of remote messages.
|
||||
*/
|
||||
export const MESSAGE_TYPE_REMOTE = 'remote';
|
||||
|
||||
@@ -22,7 +22,7 @@ import { isButtonEnabled, showToolbox } from '../toolbox';
|
||||
import { SEND_MESSAGE, SET_PRIVATE_MESSAGE_RECIPIENT } from './actionTypes';
|
||||
import { addMessage, clearMessages, toggleChat } from './actions';
|
||||
import { ChatPrivacyDialog } from './components';
|
||||
import { INCOMING_MSG_SOUND_ID } from './constants';
|
||||
import { INCOMING_MSG_SOUND_ID, MESSAGE_TYPE_ERROR, MESSAGE_TYPE_LOCAL, MESSAGE_TYPE_REMOTE } from './constants';
|
||||
import { INCOMING_MSG_SOUND_FILE } from './sounds';
|
||||
|
||||
declare var APP: Object;
|
||||
@@ -194,7 +194,7 @@ function _addChatMsgListener(conference, store) {
|
||||
function _handleChatError({ dispatch }, error) {
|
||||
dispatch(addMessage({
|
||||
hasRead: true,
|
||||
messageType: 'error',
|
||||
messageType: MESSAGE_TYPE_ERROR,
|
||||
message: error,
|
||||
privateMessage: false,
|
||||
timestamp: Date.now()
|
||||
@@ -231,7 +231,7 @@ function _handleReceivedMessage({ dispatch, getState }, { id, message, nick, pri
|
||||
displayName,
|
||||
hasRead,
|
||||
id,
|
||||
messageType: participant.local ? 'local' : 'remote',
|
||||
messageType: participant.local ? MESSAGE_TYPE_LOCAL : MESSAGE_TYPE_REMOTE,
|
||||
message,
|
||||
privateMessage,
|
||||
recipient: getParticipantDisplayName(state, localParticipant.id),
|
||||
@@ -285,7 +285,7 @@ function _persistSentPrivateMessage({ dispatch, getState }, recipientID, message
|
||||
displayName,
|
||||
hasRead: true,
|
||||
id: localParticipant.id,
|
||||
messageType: 'local',
|
||||
messageType: MESSAGE_TYPE_LOCAL,
|
||||
message,
|
||||
privateMessage: true,
|
||||
recipient: getParticipantDisplayName(getState, recipientID),
|
||||
@@ -323,7 +323,7 @@ function _shouldSendPrivateMessageTo(state, action): ?string {
|
||||
const lastMessage = navigator.product === 'ReactNative'
|
||||
? messages[0] : messages[messages.length - 1];
|
||||
|
||||
if (lastMessage.messageType === 'local') {
|
||||
if (lastMessage.messageType === MESSAGE_TYPE_LOCAL) {
|
||||
// The sender is probably aware of any private messages as already sent
|
||||
// a message since then. Doesn't make sense to display the notice now.
|
||||
return undefined;
|
||||
@@ -339,7 +339,7 @@ function _shouldSendPrivateMessageTo(state, action): ?string {
|
||||
const now = Date.now();
|
||||
const recentPrivateMessages = messages.filter(
|
||||
message =>
|
||||
message.messageType !== 'local'
|
||||
message.messageType !== MESSAGE_TYPE_LOCAL
|
||||
&& message.privateMessage
|
||||
&& message.timestamp + PRIVACY_NOTICE_TIMEOUT > now);
|
||||
const recentPrivateMessage = navigator.product === 'ReactNative'
|
||||
|
||||
@@ -40,12 +40,12 @@ class Toolbar extends Component<Props> {
|
||||
<div
|
||||
className = 'filmstrip-toolbox'
|
||||
id = 'new-toolbox'>
|
||||
<AudioMuteButton
|
||||
tooltipPosition = 'left'
|
||||
visible = { this._shouldShowButton('microphone') } />
|
||||
<HangupButton
|
||||
tooltipPosition = 'left'
|
||||
visible = { this._shouldShowButton('hangup') } />
|
||||
<AudioMuteButton
|
||||
tooltipPosition = 'left'
|
||||
visible = { this._shouldShowButton('microphone') } />
|
||||
<VideoMuteButton
|
||||
tooltipPosition = 'left'
|
||||
visible = { this._shouldShowButton('camera') } />
|
||||
|
||||
@@ -139,7 +139,6 @@ function _onFollowMeCommand(attributes = {}, id, store) {
|
||||
// For now gate etherpad checks behind a web-app check to be extra safe
|
||||
// against calling a web-app global.
|
||||
if (typeof APP !== 'undefined'
|
||||
&& state['features/etherpad'].initialized
|
||||
&& oldState.sharedDocumentVisible !== attributes.sharedDocumentVisible) {
|
||||
const isEtherpadVisible = attributes.sharedDocumentVisible === 'true';
|
||||
const documentManager = APP.UI.getSharedDocumentManager();
|
||||
|
||||
@@ -7,7 +7,7 @@ import { setPassword } from '../../../../base/conference';
|
||||
import { getInviteURL } from '../../../../base/connection';
|
||||
import { Dialog } from '../../../../base/dialog';
|
||||
import { translate } from '../../../../base/i18n';
|
||||
import { Icon, IconInfo } from '../../../../base/icons';
|
||||
import { Icon, IconInfo, IconCopy } from '../../../../base/icons';
|
||||
import { connect } from '../../../../base/redux';
|
||||
import {
|
||||
isLocalParticipantModerator,
|
||||
@@ -136,6 +136,7 @@ type State = {
|
||||
*/
|
||||
class InfoDialog extends Component<Props, State> {
|
||||
_copyElement: ?Object;
|
||||
_copyUrlElement: ?Object;
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#getDerivedStateFromProps()}.
|
||||
@@ -197,12 +198,14 @@ class InfoDialog extends Component<Props, State> {
|
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onClickURLText = this._onClickURLText.bind(this);
|
||||
this._onCopyInviteURL = this._onCopyInviteURL.bind(this);
|
||||
this._onCopyInviteInfo = this._onCopyInviteInfo.bind(this);
|
||||
this._onCopyInviteUrl = this._onCopyInviteUrl.bind(this);
|
||||
this._onPasswordRemove = this._onPasswordRemove.bind(this);
|
||||
this._onPasswordSubmit = this._onPasswordSubmit.bind(this);
|
||||
this._onTogglePasswordEditState
|
||||
= this._onTogglePasswordEditState.bind(this);
|
||||
this._setCopyElement = this._setCopyElement.bind(this);
|
||||
this._setCopyUrlElement = this._setCopyUrlElement.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -239,12 +242,18 @@ class InfoDialog extends Component<Props, State> {
|
||||
<span className = 'spacer'> </span>
|
||||
<span className = 'info-value'>
|
||||
<a
|
||||
className = 'info-dialog-url-text'
|
||||
className = 'info-dialog-url-text info-dialog-url-text-unselectable'
|
||||
href = { this.props._inviteURL }
|
||||
onClick = { this._onClickURLText } >
|
||||
{ decodeURI(this._getURLToDisplay()) }
|
||||
</a>
|
||||
</span>
|
||||
<span className = 'info-dialog-url-icon'>
|
||||
<Icon
|
||||
onClick = { this._onCopyInviteUrl }
|
||||
size = { 18 }
|
||||
src = { IconCopy } />
|
||||
</span>
|
||||
</div>
|
||||
<div className = 'info-dialog-dial-in'>
|
||||
{ this._renderDialInDisplay() }
|
||||
@@ -262,7 +271,7 @@ class InfoDialog extends Component<Props, State> {
|
||||
<div className = 'info-dialog-action-link'>
|
||||
<a
|
||||
className = 'info-copy'
|
||||
onClick = { this._onCopyInviteURL }>
|
||||
onClick = { this._onCopyInviteInfo }>
|
||||
{ t('dialog.copy') }
|
||||
</a>
|
||||
</div>
|
||||
@@ -275,6 +284,12 @@ class InfoDialog extends Component<Props, State> {
|
||||
ref = { this._setCopyElement }
|
||||
tabIndex = '-1'
|
||||
value = { this._getTextToCopy() } />
|
||||
<textarea
|
||||
className = 'info-dialog-copy-element'
|
||||
readOnly = { true }
|
||||
ref = { this._setCopyUrlElement }
|
||||
tabIndex = '-1'
|
||||
value = { this.props._inviteURL } />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -365,7 +380,7 @@ class InfoDialog extends Component<Props, State> {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
_onCopyInviteURL: () => void;
|
||||
_onCopyInviteInfo: () => void;
|
||||
|
||||
/**
|
||||
* Callback invoked to copy the contents of {@code this._copyElement} to the
|
||||
@@ -374,7 +389,7 @@ class InfoDialog extends Component<Props, State> {
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onCopyInviteURL() {
|
||||
_onCopyInviteInfo() {
|
||||
try {
|
||||
if (!this._copyElement) {
|
||||
throw new Error('No element to copy from.');
|
||||
@@ -388,6 +403,28 @@ class InfoDialog extends Component<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
_onCopyInviteUrl: () => void;
|
||||
|
||||
/**
|
||||
* Callback invoked to copy the contents of {@code this._copyUrlElement} to the clipboard.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onCopyInviteUrl() {
|
||||
try {
|
||||
if (!this._copyUrlElement) {
|
||||
throw new Error('No element to copy from.');
|
||||
}
|
||||
|
||||
this._copyUrlElement && this._copyUrlElement.select();
|
||||
document.execCommand('copy');
|
||||
this._copyUrlElement && this._copyUrlElement.blur();
|
||||
} catch (err) {
|
||||
logger.error('error when copying the text', err);
|
||||
}
|
||||
}
|
||||
|
||||
_onPasswordRemove: () => void;
|
||||
|
||||
/**
|
||||
@@ -565,6 +602,21 @@ class InfoDialog extends Component<Props, State> {
|
||||
_setCopyElement(element: Object) {
|
||||
this._copyElement = element;
|
||||
}
|
||||
|
||||
_setCopyUrlElement: () => void;
|
||||
|
||||
/**
|
||||
* Sets the internal reference to the DOM/HTML element backing the React
|
||||
* {@code Component} input.
|
||||
*
|
||||
* @param {HTMLInputElement} element - The DOM/HTML element for this
|
||||
* {@code Component}'s input.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_setCopyUrlElement(element: Object) {
|
||||
this._copyUrlElement = element;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -577,5 +577,12 @@ export function _decodeRoomURI(url: string) {
|
||||
roomUrl = decodeURI(roomUrl);
|
||||
}
|
||||
|
||||
// Handles a special case where the room name has % encoded, the decoded will have
|
||||
// % followed by a char (non-digit) which is not a valid URL and room name ... so we do not
|
||||
// want to show this decoded
|
||||
if (roomUrl.match(/.*%[^\d].*/)) {
|
||||
return url;
|
||||
}
|
||||
|
||||
return roomUrl;
|
||||
}
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
/**
|
||||
* The type of redux action to add a network request to the redux store/state.
|
||||
*
|
||||
* {
|
||||
* type: _ADD_NETWORK_REQUEST,
|
||||
* request: Object
|
||||
* }
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
export const _ADD_NETWORK_REQUEST = '_ADD_NETWORK_REQUEST';
|
||||
|
||||
/**
|
||||
* The type of redux action to remove all network requests from the redux
|
||||
* store/state.
|
||||
*
|
||||
* {
|
||||
* type: _REMOVE_ALL_NETWORK_REQUESTS,
|
||||
* }
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
export const _REMOVE_ALL_NETWORK_REQUESTS
|
||||
= '_REMOVE_ALL_NETWORK_REQUESTS';
|
||||
|
||||
/**
|
||||
* The type of redux action to remove a network request from the redux
|
||||
* store/state.
|
||||
*
|
||||
* {
|
||||
* type: _REMOVE_NETWORK_REQUEST,
|
||||
* request: Object
|
||||
* }
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
export const _REMOVE_NETWORK_REQUEST = '_REMOVE_NETWORK_REQUEST';
|
||||
@@ -1,55 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { LoadingIndicator } from '../../../base/react';
|
||||
import { connect } from '../../../base/redux';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of
|
||||
* {@link NetworkActivityIndicator}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Indicates whether there is network activity i.e. ongoing network
|
||||
* requests.
|
||||
*/
|
||||
_networkActivity: boolean
|
||||
};
|
||||
|
||||
/**
|
||||
* The React {@code Component} which renders a progress indicator when there
|
||||
* are ongoing network requests.
|
||||
*/
|
||||
class NetworkActivityIndicator extends Component<Props> {
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
return this.props._networkActivity ? <LoadingIndicator /> : null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to the React {@code Component} props of
|
||||
* {@code NetworkActivityIndicator}.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _networkActivity: boolean
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
const { requests } = state['features/network-activity'];
|
||||
|
||||
return {
|
||||
_networkActivity: Boolean(requests && requests.size)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps)(NetworkActivityIndicator);
|
||||
@@ -1,3 +0,0 @@
|
||||
export {
|
||||
default as NetworkActivityIndicator
|
||||
} from './NetworkActivityIndicator';
|
||||
@@ -1,4 +0,0 @@
|
||||
export * from './components';
|
||||
|
||||
import './middleware';
|
||||
import './reducer';
|
||||
@@ -1,81 +0,0 @@
|
||||
/* @flow */
|
||||
|
||||
import XHRInterceptor from 'react-native/Libraries/Network/XHRInterceptor';
|
||||
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../base/app';
|
||||
import { MiddlewareRegistry } from '../../base/redux';
|
||||
|
||||
import {
|
||||
_ADD_NETWORK_REQUEST,
|
||||
_REMOVE_ALL_NETWORK_REQUESTS,
|
||||
_REMOVE_NETWORK_REQUEST
|
||||
} from './actionTypes';
|
||||
|
||||
/**
|
||||
* Middleware which captures app startup and conference actions in order to
|
||||
* clear the image cache.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
const result = next(action);
|
||||
|
||||
switch (action.type) {
|
||||
case APP_WILL_MOUNT:
|
||||
_startNetInterception(store);
|
||||
break;
|
||||
|
||||
case APP_WILL_UNMOUNT:
|
||||
_stopNetInterception(store);
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
/**
|
||||
* Starts intercepting network requests. Only XHR requests are supported at the
|
||||
* moment.
|
||||
*
|
||||
* Ongoing request information is kept in redux, and it's removed as soon as a
|
||||
* given request is complete.
|
||||
*
|
||||
* @param {Object} store - The redux store.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _startNetInterception({ dispatch }) {
|
||||
XHRInterceptor.setOpenCallback((method, url, xhr) => dispatch({
|
||||
type: _ADD_NETWORK_REQUEST,
|
||||
request: xhr,
|
||||
|
||||
// The following are not really necessary read anywhere at the time of
|
||||
// this writing but are provided anyway if the reducer chooses to
|
||||
// remember them:
|
||||
method,
|
||||
url
|
||||
}));
|
||||
XHRInterceptor.setResponseCallback((...args) => dispatch({
|
||||
type: _REMOVE_NETWORK_REQUEST,
|
||||
|
||||
// XXX The XHR is the last argument of the responseCallback.
|
||||
request: args[args.length - 1]
|
||||
}));
|
||||
|
||||
XHRInterceptor.enableInterception();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops intercepting network requests.
|
||||
*
|
||||
* @param {Object} store - The redux store.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _stopNetInterception({ dispatch }) {
|
||||
XHRInterceptor.disableInterception();
|
||||
|
||||
dispatch({
|
||||
type: _REMOVE_ALL_NETWORK_REQUESTS
|
||||
});
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import { ReducerRegistry, set } from '../../base/redux';
|
||||
|
||||
import {
|
||||
_ADD_NETWORK_REQUEST,
|
||||
_REMOVE_ALL_NETWORK_REQUESTS,
|
||||
_REMOVE_NETWORK_REQUEST
|
||||
} from './actionTypes';
|
||||
|
||||
/**
|
||||
* The default/initial redux state of the feature network-activity.
|
||||
*
|
||||
* @type {{
|
||||
* requests: Map
|
||||
* }}
|
||||
*/
|
||||
const DEFAULT_STATE = {
|
||||
/**
|
||||
* The ongoing network requests i.e. the network request which have been
|
||||
* added to the redux store/state and have not been removed.
|
||||
*
|
||||
* @type {Map}
|
||||
*/
|
||||
requests: new Map()
|
||||
};
|
||||
|
||||
ReducerRegistry.register(
|
||||
'features/network-activity',
|
||||
(state = DEFAULT_STATE, action) => {
|
||||
switch (action.type) {
|
||||
case _ADD_NETWORK_REQUEST: {
|
||||
const {
|
||||
type, // eslint-disable-line no-unused-vars
|
||||
|
||||
request: key,
|
||||
...value
|
||||
} = action;
|
||||
const requests = new Map(state.requests);
|
||||
|
||||
requests.set(key, value);
|
||||
|
||||
return set(state, 'requests', requests);
|
||||
}
|
||||
|
||||
case _REMOVE_ALL_NETWORK_REQUESTS:
|
||||
return set(state, 'requests', DEFAULT_STATE.requests);
|
||||
|
||||
case _REMOVE_NETWORK_REQUEST: {
|
||||
const { request: key } = action;
|
||||
const requests = new Map(state.requests);
|
||||
|
||||
requests.delete(key);
|
||||
|
||||
return set(state, 'requests', requests);
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
});
|
||||
@@ -18,7 +18,7 @@ export function toDisplayableList(recentList) {
|
||||
date: item.date,
|
||||
duration: item.duration,
|
||||
time: [ item.date ],
|
||||
title: decodeURIComponent(parseURIString(item.conference).room),
|
||||
title: parseURIString(item.conference).room,
|
||||
url: item.conference
|
||||
};
|
||||
}));
|
||||
|
||||
@@ -2,4 +2,5 @@
|
||||
* The URL that is the main landing page for YouTube live streaming and should
|
||||
* have a user's live stream key.
|
||||
*/
|
||||
export const YOUTUBE_LIVE_DASHBOARD_URL = 'https://www.youtube.com/live_dashboard';
|
||||
export const YOUTUBE_LIVE_DASHBOARD_URL
|
||||
= 'https://www.youtube.com/live_dashboard';
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { Text, TextInput, TouchableOpacity, View } from 'react-native';
|
||||
import { Linking, Text, TextInput, TouchableOpacity, View } from 'react-native';
|
||||
|
||||
import { _abstractMapStateToProps } from '../../../../base/dialog';
|
||||
import { translate } from '../../../../base/i18n';
|
||||
import { connect } from '../../../../base/redux';
|
||||
import { StyleType } from '../../../../base/styles';
|
||||
import { openURLInBrowser } from '../../../../base/util';
|
||||
|
||||
import AbstractStreamKeyForm, {
|
||||
type Props as AbstractProps
|
||||
@@ -121,7 +120,7 @@ class StreamKeyForm extends AbstractStreamKeyForm<Props> {
|
||||
const { helpURL } = this;
|
||||
|
||||
if (typeof helpURL === 'string') {
|
||||
openURLInBrowser(helpURL);
|
||||
Linking.openURL(helpURL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import {
|
||||
Linking,
|
||||
Text,
|
||||
TouchableHighlight,
|
||||
TouchableOpacity,
|
||||
@@ -12,7 +13,6 @@ import { _abstractMapStateToProps } from '../../../../base/dialog';
|
||||
import { translate } from '../../../../base/i18n';
|
||||
import { connect } from '../../../../base/redux';
|
||||
import { StyleType } from '../../../../base/styles';
|
||||
import { openURLInBrowser } from '../../../../base/util';
|
||||
|
||||
import { YOUTUBE_LIVE_DASHBOARD_URL } from '../constants';
|
||||
|
||||
@@ -153,7 +153,7 @@ class StreamKeyPicker extends Component<Props, State> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_onOpenYoutubeDashboard() {
|
||||
openURLInBrowser(YOUTUBE_LIVE_DASHBOARD_URL);
|
||||
Linking.openURL(YOUTUBE_LIVE_DASHBOARD_URL);
|
||||
}
|
||||
|
||||
_onStreamPick: string => Function
|
||||
|
||||
56
react/features/toolbox/components/DownloadButton.js
Normal file
56
react/features/toolbox/components/DownloadButton.js
Normal file
@@ -0,0 +1,56 @@
|
||||
// @flow
|
||||
|
||||
import { createToolbarEvent, sendAnalytics } from '../../analytics';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { IconDownload } from '../../base/icons';
|
||||
import { connect } from '../../base/redux';
|
||||
import { openURLInBrowser } from '../../base/util';
|
||||
import { AbstractButton, type AbstractButtonProps } from '../../base/toolbox';
|
||||
|
||||
|
||||
type Props = AbstractButtonProps & {
|
||||
|
||||
/**
|
||||
* The URL to the applications page.
|
||||
*/
|
||||
_downloadAppsUrl: string
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements an {@link AbstractButton} to open the applications page in a new window.
|
||||
*/
|
||||
class DownloadButton extends AbstractButton<Props, *> {
|
||||
accessibilityLabel = 'toolbar.accessibilityLabel.download';
|
||||
icon = IconDownload;
|
||||
label = 'toolbar.download';
|
||||
|
||||
/**
|
||||
* Handles clicking / pressing the button, and opens a new window with the user documentation.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
sendAnalytics(createToolbarEvent('download.pressed'));
|
||||
openURLInBrowser(this.props._downloadAppsUrl);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Maps part of the redux state to the component's props.
|
||||
*
|
||||
* @param {Object} state - The redux store/state.
|
||||
* @returns {Object}
|
||||
*/
|
||||
function _mapStateToProps(state: Object) {
|
||||
const { downloadAppsUrl } = state['features/base/config'].deploymentUrls || {};
|
||||
const visible = typeof downloadAppsUrl === 'string';
|
||||
|
||||
return {
|
||||
_downloadAppsUrl: downloadAppsUrl,
|
||||
visible
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(DownloadButton));
|
||||
@@ -44,7 +44,7 @@ class HelpButton extends AbstractButton<Props, *> {
|
||||
* @returns {Object}
|
||||
*/
|
||||
function _mapStateToProps(state: Object) {
|
||||
const { userDocumentationURL } = state['features/base/config'];
|
||||
const { userDocumentationURL } = state['features/base/config'].deploymentUrls || {};
|
||||
const visible = typeof userDocumentationURL === 'string';
|
||||
|
||||
return {
|
||||
|
||||
@@ -69,6 +69,7 @@ import {
|
||||
setToolbarHovered
|
||||
} from '../../actions';
|
||||
import AudioMuteButton from '../AudioMuteButton';
|
||||
import DownloadButton from '../DownloadButton';
|
||||
import { isToolboxVisible } from '../../functions';
|
||||
import HangupButton from '../HangupButton';
|
||||
import HelpButton from '../HelpButton';
|
||||
@@ -975,6 +976,10 @@ class Toolbox extends Component<Props, State> {
|
||||
key = 'shortcuts'
|
||||
onClick = { this._onToolbarOpenKeyboardShortcuts }
|
||||
text = { t('toolbar.shortcuts') } />,
|
||||
this._shouldShowButton('download')
|
||||
&& <DownloadButton
|
||||
key = 'download'
|
||||
showLabel = { true } />,
|
||||
this._shouldShowButton('help')
|
||||
&& <HelpButton
|
||||
key = 'help'
|
||||
|
||||
@@ -1,18 +1,27 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { ColorSchemeRegistry } from '../../base/color-scheme';
|
||||
import { LoadingIndicator } from '../../base/react';
|
||||
import { connect } from '../../base/redux';
|
||||
import { StyleType } from '../../base/styles';
|
||||
import { destroyLocalTracks } from '../../base/tracks';
|
||||
import { NetworkActivityIndicator } from '../../mobile/network-activity';
|
||||
|
||||
import LocalVideoTrackUnderlay from './LocalVideoTrackUnderlay';
|
||||
import styles from './styles';
|
||||
|
||||
/**
|
||||
* The type of React {@code Component} props of {@link BlankPage}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The color schemed style of the component.
|
||||
*/
|
||||
_styles: StyleType,
|
||||
|
||||
dispatch: Dispatch<any>
|
||||
};
|
||||
|
||||
@@ -40,12 +49,32 @@ class BlankPage extends Component<Props> {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { _styles } = this.props;
|
||||
|
||||
return (
|
||||
<LocalVideoTrackUnderlay>
|
||||
<NetworkActivityIndicator />
|
||||
</LocalVideoTrackUnderlay>
|
||||
<View
|
||||
style = { [
|
||||
styles.blankPageWrapper,
|
||||
_styles.loadingOverlayWrapper
|
||||
] }>
|
||||
<LoadingIndicator
|
||||
color = { _styles.indicatorColor }
|
||||
size = 'large' />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect()(BlankPage);
|
||||
/**
|
||||
* Maps part of the Redux state to the props of this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
return {
|
||||
_styles: ColorSchemeRegistry.get(state, 'LoadConfigOverlay')
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps)(BlankPage);
|
||||
|
||||
@@ -12,6 +12,12 @@ import { SettingsButton, SETTINGS_TABS } from '../../settings';
|
||||
import { AbstractWelcomePage, _mapStateToProps } from './AbstractWelcomePage';
|
||||
import Tabs from './Tabs';
|
||||
|
||||
/**
|
||||
* The pattern used to validate room name.
|
||||
* @type {string}
|
||||
*/
|
||||
export const ROOM_NAME_VALIDATE_PATTERN_STR = '^[^?&:\u0022\u0027%#]+$';
|
||||
|
||||
/**
|
||||
* The Web container rendering the welcome page.
|
||||
*
|
||||
@@ -53,6 +59,8 @@ class WelcomePage extends AbstractWelcomePage {
|
||||
*/
|
||||
this._additionalContentRef = null;
|
||||
|
||||
this._roomInputRef = null;
|
||||
|
||||
/**
|
||||
* The HTML Element used as the container for additional toolbar content. Used
|
||||
* for directly appending the additional content template to the dom.
|
||||
@@ -88,6 +96,7 @@ class WelcomePage extends AbstractWelcomePage {
|
||||
this._onRoomChange = this._onRoomChange.bind(this);
|
||||
this._setAdditionalContentRef
|
||||
= this._setAdditionalContentRef.bind(this);
|
||||
this._setRoomInputRef = this._setRoomInputRef.bind(this);
|
||||
this._setAdditionalToolbarContentRef
|
||||
= this._setAdditionalToolbarContentRef.bind(this);
|
||||
this._onTabSelected = this._onTabSelected.bind(this);
|
||||
@@ -184,9 +193,10 @@ class WelcomePage extends AbstractWelcomePage {
|
||||
className = 'enter-room-input'
|
||||
id = 'enter_room_field'
|
||||
onChange = { this._onRoomChange }
|
||||
pattern = '^[a-zA-Z0-9=\?]+$'
|
||||
pattern = { ROOM_NAME_VALIDATE_PATTERN_STR }
|
||||
placeholder = { this.state.roomPlaceholder }
|
||||
title = { t('welcomepage.onlyAsciiAllowed') }
|
||||
ref = { this._setRoomInputRef }
|
||||
title = { t('welcomepage.roomNameAllowedChars') }
|
||||
type = 'text'
|
||||
value = { this.state.room } />
|
||||
</form>
|
||||
@@ -194,7 +204,7 @@ class WelcomePage extends AbstractWelcomePage {
|
||||
<div
|
||||
className = 'welcome-page-button'
|
||||
id = 'enter_room_button'
|
||||
onClick = { this._onJoin }>
|
||||
onClick = { this._onFormSubmit }>
|
||||
{ t('welcomepage.go') }
|
||||
</div>
|
||||
</div>
|
||||
@@ -219,7 +229,9 @@ class WelcomePage extends AbstractWelcomePage {
|
||||
_onFormSubmit(event) {
|
||||
event.preventDefault();
|
||||
|
||||
this._onJoin();
|
||||
if (!this._roomInputRef || this._roomInputRef.reportValidity()) {
|
||||
this._onJoin();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -311,6 +323,18 @@ class WelcomePage extends AbstractWelcomePage {
|
||||
this._additionalToolbarContentRef = el;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the internal reference to the HTMLInputElement used to hold the
|
||||
* welcome page input room element.
|
||||
*
|
||||
* @param {HTMLInputElement} el - The HTMLElement for the input of the room name on the welcome page.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_setRoomInputRef(el) {
|
||||
this._roomInputRef = el;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not additional content should be displayed below
|
||||
* the welcome page's header for entering a room name.
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import { StyleSheet } from 'react-native';
|
||||
|
||||
import { BoxModel, ColorPalette } from '../../base/styles';
|
||||
|
||||
export const PLACEHOLDER_TEXT_COLOR = 'rgba(255, 255, 255, 0.3)';
|
||||
@@ -38,6 +40,17 @@ export default {
|
||||
flexDirection: 'row'
|
||||
},
|
||||
|
||||
/**
|
||||
* View that is rendered when there is no welcome page.
|
||||
*/
|
||||
blankPageWrapper: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
|
||||
/**
|
||||
* Join button style.
|
||||
*/
|
||||
|
||||
159
resources/prosody-plugins/mod_muc_domain_mapper.lua
Normal file
159
resources/prosody-plugins/mod_muc_domain_mapper.lua
Normal file
@@ -0,0 +1,159 @@
|
||||
-- Maps MUC JIDs like room1@muc.foo.example.com to JIDs like [foo]room1@muc.example.com
|
||||
-- Must be loaded on the client host in Prosody
|
||||
|
||||
module:set_global();
|
||||
|
||||
-- It is recommended to set muc_mapper_domain_base to the main domain being served (example.com)
|
||||
|
||||
local jid = require "util.jid";
|
||||
|
||||
local filters = require "util.filters";
|
||||
|
||||
local muc_domain_prefix = module:get_option_string("muc_mapper_domain_prefix", "conference");
|
||||
|
||||
local muc_domain_base = module:get_option_string("muc_mapper_domain_base");
|
||||
if not muc_domain_base then
|
||||
module:log("warn", "No 'muc_domain_base' option set, disabling muc_mapper plugin inactive");
|
||||
return
|
||||
end
|
||||
|
||||
-- The "real" MUC domain that we are proxying to
|
||||
local muc_domain = module:get_option_string("muc_mapper_domain", muc_domain_prefix.."."..muc_domain_base);
|
||||
|
||||
local escaped_muc_domain_base = muc_domain_base:gsub("%p", "%%%1");
|
||||
local escaped_muc_domain_prefix = muc_domain_prefix:gsub("%p", "%%%1");
|
||||
-- The pattern used to extract the target subdomain (e.g. extract 'foo' from 'foo.muc.example.com')
|
||||
local target_subdomain_pattern = "^"..escaped_muc_domain_prefix..".([^%.]+)%."..escaped_muc_domain_base;
|
||||
|
||||
-- table to store all incoming iqs without roomname in it, like discoinfo to the muc compoent
|
||||
local roomless_iqs = {};
|
||||
|
||||
if not muc_domain then
|
||||
module:log("warn", "No 'muc_mapper_domain' option set, disabling muc_mapper plugin inactive");
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
-- Utility function to check and convert a room JID from virtual room1@muc.foo.example.com to real [foo]room1@muc.example.com
|
||||
local function match_rewrite_to_jid(room_jid, stanza)
|
||||
local node, host, resource = jid.split(room_jid);
|
||||
local target_subdomain = host and host:match(target_subdomain_pattern);
|
||||
if not target_subdomain then
|
||||
module:log("debug", "No need to rewrite out 'to' %s", room_jid);
|
||||
return room_jid;
|
||||
end
|
||||
-- Ok, rewrite room_jid address to new format
|
||||
local new_node, new_host, new_resource;
|
||||
if node then
|
||||
new_node, new_host, new_resource = "["..target_subdomain.."]"..node, muc_domain, resource;
|
||||
else
|
||||
module:log("debug", "No room name provided so rewriting only host 'to' %s", room_jid);
|
||||
new_host, new_resource = muc_domain, resource;
|
||||
|
||||
if (stanza.attr and stanza.attr.id) then
|
||||
roomless_iqs[stanza.attr.id] = stanza.attr.to;
|
||||
end
|
||||
end
|
||||
room_jid = jid.join(new_node, new_host, new_resource);
|
||||
module:log("debug", "Rewrote to %s", room_jid);
|
||||
return room_jid
|
||||
end
|
||||
|
||||
-- Utility function to check and convert a room JID from real [foo]room1@muc.example.com to virtual room1@muc.foo.example.com
|
||||
local function match_rewrite_from_jid(room_jid, stanza)
|
||||
local node, host, resource = jid.split(room_jid);
|
||||
if host ~= muc_domain or not node then
|
||||
module:log("debug", "No need to rewrite %s (not from the MUC host) %s, %s", room_jid, stanza.attr.id, roomless_iqs[stanza.attr.id]);
|
||||
|
||||
if (stanza.attr and stanza.attr.id and roomless_iqs[stanza.attr.id]) then
|
||||
local result = roomless_iqs[stanza.attr.id];
|
||||
roomless_iqs[stanza.attr.id] = nil;
|
||||
return result;
|
||||
end
|
||||
|
||||
return room_jid;
|
||||
end
|
||||
local target_subdomain, target_node = node:match("^%[([^%]]+)%](.+)$");
|
||||
if not (target_node and target_subdomain) then
|
||||
module:log("debug", "Not rewriting... unexpected node format: %s", node);
|
||||
return room_jid;
|
||||
end
|
||||
-- Ok, rewrite room_jid address to pretty format
|
||||
local new_node, new_host, new_resource = target_node, muc_domain_prefix..".".. target_subdomain.."."..muc_domain_base, resource;
|
||||
room_jid = jid.join(new_node, new_host, new_resource);
|
||||
module:log("debug", "Rewrote to %s", room_jid);
|
||||
return room_jid
|
||||
end
|
||||
|
||||
|
||||
-- We must filter stanzas in order to hook in to all incoming and outgoing messaging which skips the stanza routers
|
||||
function filter_stanza(stanza)
|
||||
if stanza.name == "message" or stanza.name == "iq" or stanza.name == "presence" then
|
||||
module:log("debug", "Filtering stanza type %s to %s from %s",stanza.name,stanza.attr.to,stanza.attr.from);
|
||||
if stanza.name == "iq" then
|
||||
local conf = stanza:get_child('conference')
|
||||
if conf then
|
||||
module:log("debug", "Filtering stanza conference %s to %s from %s",conf.attr.room,stanza.attr.to,stanza.attr.from);
|
||||
conf.attr.room = match_rewrite_to_jid(conf.attr.room, stanza)
|
||||
end
|
||||
end
|
||||
if stanza.attr.to then
|
||||
stanza.attr.to = match_rewrite_to_jid(stanza.attr.to, stanza)
|
||||
end
|
||||
if stanza.attr.from then
|
||||
stanza.attr.from = match_rewrite_from_jid(stanza.attr.from, stanza)
|
||||
end
|
||||
end
|
||||
return stanza;
|
||||
end
|
||||
|
||||
function filter_session(session)
|
||||
module:log("warn", "Session filters applied");
|
||||
-- filters.add_filter(session, "stanzas/in", filter_stanza_in);
|
||||
filters.add_filter(session, "stanzas/out", filter_stanza);
|
||||
end
|
||||
|
||||
function module.load()
|
||||
if module.reloading then
|
||||
module:log("debug", "Reloading MUC mapper!");
|
||||
else
|
||||
module:log("debug", "First load of MUC mapper!");
|
||||
end
|
||||
filters.add_filter_hook(filter_session);
|
||||
end
|
||||
|
||||
function module.unload()
|
||||
filters.remove_filter_hook(filter_session);
|
||||
end
|
||||
|
||||
|
||||
local function outgoing_stanza_rewriter(event)
|
||||
local stanza = event.stanza;
|
||||
if stanza.attr.to then
|
||||
stanza.attr.to = match_rewrite_to_jid(stanza.attr.to, stanza)
|
||||
end
|
||||
end
|
||||
|
||||
local function incoming_stanza_rewriter(event)
|
||||
local stanza = event.stanza;
|
||||
if stanza.attr.from then
|
||||
stanza.attr.from = match_rewrite_from_jid(stanza.attr.from, stanza)
|
||||
end
|
||||
end
|
||||
|
||||
-- The stanza rewriters helper functions are attached for all stanza router hooks
|
||||
local function hook_all_stanzas(handler, host_module, event_prefix)
|
||||
for _, stanza_type in ipairs({ "message", "presence", "iq" }) do
|
||||
for _, jid_type in ipairs({ "host", "bare", "full" }) do
|
||||
host_module:hook((event_prefix or "")..stanza_type.."/"..jid_type, handler);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function module.add_host(host_module)
|
||||
module:log("info",
|
||||
"Loading mod_muc_domain_mapper for host %s!", host_module.host);
|
||||
hook_all_stanzas(incoming_stanza_rewriter, host_module);
|
||||
hook_all_stanzas(outgoing_stanza_rewriter, host_module, "pre-");
|
||||
end
|
||||
@@ -62,11 +62,12 @@ end
|
||||
local SpeakerStats = {};
|
||||
SpeakerStats.__index = SpeakerStats;
|
||||
|
||||
function new_SpeakerStats(nick)
|
||||
function new_SpeakerStats(nick, context_user)
|
||||
return setmetatable({
|
||||
totalDominantSpeakerTime = 0;
|
||||
_dominantSpeakerStart = 0;
|
||||
nick = nick;
|
||||
context_user = context_user;
|
||||
displayName = nil;
|
||||
}, SpeakerStats);
|
||||
end
|
||||
@@ -106,6 +107,7 @@ end
|
||||
function occupant_joined(event)
|
||||
local room = event.room;
|
||||
local occupant = event.occupant;
|
||||
|
||||
local nick = jid_resource(occupant.nick);
|
||||
|
||||
if room.speakerStats then
|
||||
@@ -150,7 +152,8 @@ function occupant_joined(event)
|
||||
room:route_stanza(stanza);
|
||||
end
|
||||
|
||||
room.speakerStats[occupant.jid] = new_SpeakerStats(nick);
|
||||
local context_user = event.origin and event.origin.jitsi_meet_context_user or nil;
|
||||
room.speakerStats[occupant.jid] = new_SpeakerStats(nick, context_user);
|
||||
end
|
||||
end
|
||||
|
||||
@@ -200,4 +203,4 @@ if prosody.hosts[muc_component_host] == nil then
|
||||
prosody.events.add_handler("host-activated", process_host);
|
||||
else
|
||||
process_host(muc_component_host);
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user