feat: Make Jitsi WCAG 2.1 compliant (#8921)
* Make Jitsi WCAG 2.1 compliant * Fixed password form keypress handling * Added keypress handler to name form * Removed unneccessary dom query * Fixed mouse hove style * Removed obsolete css rules * accessibilty background feature * Merge remote-tracking branch 'upstream/master' into nic/fix/merge-conflicts * fix error * add german translation * Fixed merge issue * Add id prop back to device selection * Fixed lockfile Co-authored-by: AHMAD KADRI <52747422+ahmadkadri@users.noreply.github.com>
This commit is contained in:
parent
76f1fe8457
commit
e9675453e1
|
@ -32,6 +32,18 @@
|
|||
.dropdown-menu div[style*="transform"] {
|
||||
outline: 1px solid #455166;
|
||||
}
|
||||
.dropdown-menu button:not(:active):not(:hover) > span {
|
||||
color: #B8C7E0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override @atlaskit/tab styling when in a modal because the
|
||||
* tab text color clash with the modal backgrounds.
|
||||
*/
|
||||
div[role="tablist"] > div:not([data-selected]):not(:hover),
|
||||
label > div > span {
|
||||
color: #B8C7E0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -9,6 +9,11 @@
|
|||
max-height: 456px;
|
||||
overflow: auto;
|
||||
width: 300px;
|
||||
&-ul {
|
||||
margin:0;
|
||||
padding:0;
|
||||
list-style-type: none;
|
||||
}
|
||||
}
|
||||
|
||||
&-header {
|
||||
|
@ -64,7 +69,13 @@
|
|||
&-speaker {
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
&-ul {
|
||||
margin:0;
|
||||
padding:0;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
&:hover, &:focus-within, &:focus {
|
||||
.audio-preview-entry {
|
||||
background: #36383C;
|
||||
margin-left: 0;
|
||||
|
@ -81,7 +92,7 @@
|
|||
}
|
||||
|
||||
.audio-preview-entry-text {
|
||||
max-width: 196px;
|
||||
max-width: 178px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -90,7 +101,7 @@
|
|||
}
|
||||
|
||||
.audio-preview-entry-text {
|
||||
max-width: 256px;
|
||||
max-width: 238px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -150,8 +161,9 @@
|
|||
color: #1C2025;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
font-size: 0.8rem;
|
||||
line-height: 24px;
|
||||
padding: 2px 16px;
|
||||
padding: 2px 8px;
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 5px;
|
||||
|
@ -162,4 +174,10 @@
|
|||
right: 16px;
|
||||
top: 14px;
|
||||
}
|
||||
|
||||
// Override @atlaskit/InlineDialog container which is made with styled components
|
||||
& > div:nth-child(2) {
|
||||
outline: none;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -219,8 +219,9 @@ abbr {
|
|||
}
|
||||
|
||||
a {
|
||||
color: #3572b0;
|
||||
color: #44A5FF;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
a:focus,
|
||||
a:hover,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.avatar {
|
||||
background-color: #AAA;
|
||||
border-radius: 50%;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
color: rgba(255, 255, 255, 1);
|
||||
font-weight: 100;
|
||||
object-fit: cover;
|
||||
|
||||
|
@ -25,10 +25,6 @@
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.defaultAvatar {
|
||||
opacity: 0.6
|
||||
}
|
||||
|
||||
.avatar-badge {
|
||||
position: relative;
|
||||
|
||||
|
|
|
@ -99,18 +99,19 @@
|
|||
div {
|
||||
svg {
|
||||
cursor: pointer;
|
||||
fill: white
|
||||
fill: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.chat-header {
|
||||
height: 70px;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
justify-content: space-between;
|
||||
padding: 16px;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
|
@ -132,6 +133,7 @@
|
|||
.send-button {
|
||||
background: #1B67EC;
|
||||
cursor: pointer;
|
||||
margin-left: 0.3rem;
|
||||
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
&:hover {
|
||||
|
@ -188,8 +190,9 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
height: 38px;
|
||||
width: 38px;
|
||||
margin: 2px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
|
@ -226,6 +229,11 @@
|
|||
border: 0px none;
|
||||
box-shadow: none;
|
||||
}
|
||||
#usermsg:focus,
|
||||
#usermsg:active {
|
||||
border-bottom: 1px solid white;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
#nickname {
|
||||
text-align: center;
|
||||
|
@ -234,6 +242,16 @@
|
|||
margin: auto 0;
|
||||
padding: 0 16px;
|
||||
|
||||
#nickname-title {
|
||||
margin-bottom: 5px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
label[for="nickinput"] {
|
||||
> div > span {
|
||||
color: #B8C7E0;
|
||||
}
|
||||
}
|
||||
input {
|
||||
height: 40px;
|
||||
}
|
||||
|
@ -254,7 +272,7 @@
|
|||
cursor: pointer;
|
||||
|
||||
&.disabled {
|
||||
color: #757575;
|
||||
color: #AFB6BC;
|
||||
background: #11336E;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
@ -301,6 +319,19 @@
|
|||
}
|
||||
}
|
||||
|
||||
.sr-only {
|
||||
border: 0 !important;
|
||||
clip: rect(1px, 1px, 1px, 1px) !important;
|
||||
clip-path: inset(50%) !important;
|
||||
height: 1px !important;
|
||||
margin: -1px !important;
|
||||
overflow: hidden !important;
|
||||
padding: 0 !important;
|
||||
position: absolute !important;
|
||||
width: 1px !important;
|
||||
white-space: nowrap !important;
|
||||
}
|
||||
|
||||
.chatmessage {
|
||||
background-color: $chatRemoteMessageBackgroundColor;
|
||||
border-radius: 0px 6px 6px 6px;
|
||||
|
@ -350,10 +381,6 @@
|
|||
color: #757575;
|
||||
}
|
||||
|
||||
.smiley {
|
||||
font-size: 14pt;
|
||||
}
|
||||
|
||||
#smileys {
|
||||
font-size: 20pt;
|
||||
margin: auto;
|
||||
|
@ -382,7 +409,7 @@
|
|||
box-sizing: border-box;
|
||||
background-color: rgba(0, 0, 0, .6) !important;
|
||||
height: auto;
|
||||
max-height: 0;
|
||||
display: none;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
width: calc(#{$sidebarWidth} - 32px);
|
||||
|
@ -398,6 +425,7 @@
|
|||
transition: max-height 0.3s;
|
||||
|
||||
&.show-smileys {
|
||||
display: flex;
|
||||
max-height: 500%;
|
||||
}
|
||||
|
||||
|
@ -413,7 +441,7 @@
|
|||
|
||||
.smileyContainer {
|
||||
width: 40px;
|
||||
height: 36px;
|
||||
height: 40px;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
}
|
||||
|
@ -509,7 +537,7 @@
|
|||
|
||||
&-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin: 16px;
|
||||
width: calc(100% - 32px);
|
||||
|
|
|
@ -8,7 +8,10 @@
|
|||
|
||||
.read-more {
|
||||
cursor: pointer;
|
||||
opacity: .7;
|
||||
opacity: .9;
|
||||
color: #fff;
|
||||
font-size: 0.8rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -125,7 +125,8 @@
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.with-click-handler:hover {
|
||||
&.with-click-handler:hover,
|
||||
&.with-click-handler:focus {
|
||||
background-color: #c7ddff;
|
||||
}
|
||||
|
||||
|
@ -158,7 +159,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.item:hover {
|
||||
.item:hover, .item:focus, .item:focus-within {
|
||||
.delete-meeting {
|
||||
display: block;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,15 @@
|
|||
&-input-area {
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
|
||||
&-label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
color: #ffffff;
|
||||
font-weight: 300;
|
||||
font-size: 15px;
|
||||
line-height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
&-title {
|
||||
|
@ -74,10 +83,10 @@
|
|||
z-index: 1;
|
||||
|
||||
&--warning {
|
||||
background: rgba(241, 173, 51, 0.7)
|
||||
background: rgba(241, 173, 51, 1);
|
||||
}
|
||||
&--ok {
|
||||
background: rgba(49, 183, 106, 0.7);
|
||||
background: rgba(49, 183, 106, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -92,6 +101,8 @@
|
|||
|
||||
&-error-desc {
|
||||
margin-right: 4px;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.settings-button-container {
|
||||
|
|
|
@ -113,7 +113,7 @@
|
|||
cursor: pointer;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-direction: column;
|
||||
font-size: 15px;
|
||||
font-weight: 300;
|
||||
justify-content: center;
|
||||
|
@ -139,6 +139,9 @@
|
|||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
.copy-button{
|
||||
width: 298px;
|
||||
}
|
||||
|
||||
.copy-meeting-text {
|
||||
width: 266px;
|
||||
|
@ -177,7 +180,7 @@
|
|||
}
|
||||
|
||||
&.focused {
|
||||
box-shadow: 0px 0px 4px 3px #0376DA;
|
||||
box-shadow: 0px 0px 1px 1.5px black, 0px 0px 1.3px 4px white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -249,6 +252,10 @@
|
|||
fill: transparent;
|
||||
}
|
||||
|
||||
label {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
|
||||
|
|
|
@ -105,6 +105,7 @@
|
|||
|
||||
.helper-link {
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
display: inline-block;
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
margin-bottom: 16px;
|
||||
position: relative;
|
||||
z-index: $toolbarZ;
|
||||
pointer-events: none;
|
||||
|
||||
.button-group-center,
|
||||
.button-group-left,
|
||||
|
@ -103,6 +104,7 @@
|
|||
flex-direction: column;
|
||||
margin: 0 auto;
|
||||
max-width: 100%;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.toolbox-content-items {
|
||||
|
@ -112,6 +114,7 @@
|
|||
margin: 0 auto;
|
||||
padding: 6px;
|
||||
text-align: center;
|
||||
pointer-events: all;
|
||||
|
||||
>div {
|
||||
margin-left: 8px;
|
||||
|
|
|
@ -79,4 +79,8 @@
|
|||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
// Override @atlaskit/InlineDialog container which is made with styled components
|
||||
& > div:nth-child(2) {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -428,7 +428,7 @@
|
|||
right: 0;
|
||||
z-index: $zindex2;
|
||||
width: 18px;
|
||||
height: 13px;
|
||||
height: 18px;
|
||||
color: #FFF;
|
||||
font-size: 10pt;
|
||||
margin-right: $remoteVideoMenuIconMargin;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 8px 8px 16px;
|
||||
margin-top: 8px;
|
||||
margin-top: 5px;
|
||||
width: calc(100% - 24px);
|
||||
height: 24px;
|
||||
|
||||
|
|
|
@ -9,10 +9,10 @@ input[type=range]{
|
|||
}
|
||||
|
||||
/**
|
||||
* Disable the default focus styles for webkit range inputs (sliders).
|
||||
* Show focus for keyboard accessibility.
|
||||
*/
|
||||
input[type=range]:focus {
|
||||
outline: none;
|
||||
outline: 1px solid white !important;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
padding: 0;
|
||||
margin: 0;
|
||||
border: none;
|
||||
outline: none;
|
||||
|
||||
-webkit-appearance: none;
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
* see.
|
||||
*/
|
||||
.active-speaker {
|
||||
box-shadow: 0 0 5px 3px $videoThumbnailSelected
|
||||
box-shadow: 0px 0px 1px 1.5px black, 0px 0px 1.3px 4px $videoThumbnailSelected;
|
||||
}
|
||||
|
||||
#filmstripRemoteVideos {
|
||||
|
|
|
@ -81,7 +81,7 @@
|
|||
|
||||
.local-video-menu-trigger,
|
||||
.remote-video-menu-trigger {
|
||||
margin-bottom: 7px;
|
||||
margin-bottom: 3px;
|
||||
margin-left: $remoteVideoMenuIconMargin;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -103,7 +103,7 @@
|
|||
font-size: 14px;
|
||||
|
||||
a {
|
||||
color: #2684FF;
|
||||
color: #6FB1EA;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
@ -119,7 +119,7 @@
|
|||
height: 8px;
|
||||
|
||||
.audio-input-preview-level {
|
||||
background: #4C9AFF;
|
||||
background: #75B1FF;
|
||||
border-radius: 5px;
|
||||
height: 100%;
|
||||
-webkit-transition: width .1s ease-in-out;
|
||||
|
|
|
@ -94,5 +94,9 @@
|
|||
};
|
||||
|
||||
}
|
||||
.star-btn:focus,
|
||||
.star-btn:active {
|
||||
outline: 1px solid #B8C7E0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,20 +30,26 @@
|
|||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.info-password-none,
|
||||
.info-password-remote {
|
||||
opacity: 0.5;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.info-password-input {
|
||||
width: 100%;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
background-color: #0E1624;
|
||||
border-radius: 3px;
|
||||
border: 2px solid #202B3D;
|
||||
color: inherit;
|
||||
padding-left: 0;
|
||||
}
|
||||
.info-password-input:focus ,
|
||||
.info-password-input:active {
|
||||
border: 2px solid #B8C7E0;
|
||||
}
|
||||
|
||||
.info-password-local {
|
||||
user-select: text;
|
||||
|
|
|
@ -130,6 +130,7 @@
|
|||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -141,7 +142,7 @@
|
|||
& > a {
|
||||
display: inline-block;
|
||||
height: 24px;
|
||||
width: 48px;
|
||||
min-width: 48px;
|
||||
border-radius: 3px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
|
|
|
@ -16,12 +16,19 @@
|
|||
}
|
||||
|
||||
.mock-atlaskit-label {
|
||||
color: #56637A;
|
||||
color: #b8c7e0;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
line-height: 1.33;
|
||||
padding: 20px 0px 4px 0px;
|
||||
}
|
||||
input[type="checkbox"]:checked + svg {
|
||||
--checkbox-background-color: #6492e7;
|
||||
--checkbox-border-color: #6492e7;
|
||||
}
|
||||
input[type="checkbox"] + svg + span {
|
||||
color: #b8c7e0;
|
||||
}
|
||||
|
||||
input[type="checkbox"] + svg + span {
|
||||
color: #9FB0CC;
|
||||
|
@ -65,4 +72,14 @@
|
|||
.sign-out-cta {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $smallScreen) {
|
||||
.device-selection {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.more-tab {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,6 +86,7 @@
|
|||
|
||||
.video-quality-dialog-label-container.active {
|
||||
color: $videoQualityActive;
|
||||
font-weight: bold;
|
||||
|
||||
&::before {
|
||||
background: $videoQualityActive;
|
||||
|
|
|
@ -7,9 +7,7 @@
|
|||
grid-template-columns: auto auto auto auto auto;
|
||||
column-gap: 9px;
|
||||
cursor: pointer;
|
||||
.desktop-share:hover, .thumbnail:hover, .blur:hover, .slight-blur:hover, .virtual-background-none:hover{
|
||||
height: 56px;
|
||||
width: 103px;
|
||||
.desktop-share:hover, .thumbnail:hover, .blur:hover, .slight-blur:hover, .virtual-background-none:hover{
|
||||
opacity: .5;
|
||||
border: 2px solid #99bbf3;
|
||||
@media (min-width: 432px) and (min-width: 432px) and (max-width: 632px) {
|
||||
|
@ -21,152 +19,75 @@
|
|||
width: 56px;
|
||||
}
|
||||
}
|
||||
.thumbnail {
|
||||
.background-option {
|
||||
margin-top: 8px;
|
||||
border-radius: 6px;
|
||||
object-fit: cover;
|
||||
height: 60px;
|
||||
width: 107px;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.thumbnail {
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.thumbnail:hover ~ .delete-image-icon {
|
||||
display: block;
|
||||
}
|
||||
.thumbnail-selected {
|
||||
margin-top: 8px;
|
||||
border-radius: 6px;
|
||||
object-fit: cover;
|
||||
height: 56px;
|
||||
width: 103px;
|
||||
border: 2px solid #246FE5;
|
||||
}
|
||||
.blur{
|
||||
box-shadow: inset 0 0 12px #000000;
|
||||
margin-top: 8px;
|
||||
background: #7E8287;
|
||||
font-weight: bold;
|
||||
height: 60px;
|
||||
width: 107px;
|
||||
border-radius: 6px;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
line-height: 60px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
.blur-selected {
|
||||
box-shadow: inset 0 0 12px #000000;
|
||||
margin-top: 8px;
|
||||
background: #7E8287;
|
||||
font-weight: bold;
|
||||
height: 56px;
|
||||
width: 103px;
|
||||
border-radius: 6px;
|
||||
border: 2px solid #246FE5;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
line-height: 60px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
.slight-blur{
|
||||
box-shadow: inset 0 0 12px #000000;
|
||||
margin-top: 8px;
|
||||
background: #A4A4A4;
|
||||
font-weight: bold;
|
||||
height: 60px;
|
||||
width: 107px;
|
||||
border-radius: 6px;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
line-height: 60px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
.slight-blur-selected{
|
||||
box-shadow: inset 0 0 12px #000000;
|
||||
margin-top: 8px;
|
||||
background: #A4A4A4;
|
||||
font-weight: bold;
|
||||
height: 56px;
|
||||
width: 103px;
|
||||
border-radius: 6px;
|
||||
border: 2px solid #246FE5;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
line-height: 60px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
.virtual-background-none {
|
||||
margin-top: 8px;
|
||||
background: #525252;
|
||||
font-weight: bold;
|
||||
height: 60px;
|
||||
width: 107px;
|
||||
border-radius: 6px;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
line-height: 60px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
.none-selected {
|
||||
margin-top: 8px;
|
||||
background: #525252;
|
||||
font-weight: bold;
|
||||
height: 56px;
|
||||
width: 103px;
|
||||
border-radius: 6px;
|
||||
border: 2px solid #246FE5;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
line-height: 60px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.desktop-share{
|
||||
margin-top: 8px;
|
||||
background: #525252;
|
||||
font-weight: bold;
|
||||
height: 60px;
|
||||
width: 107px;
|
||||
border-radius: 6px;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
line-height: 60px;
|
||||
}
|
||||
.desktop-share-selected{
|
||||
margin-top: 8px;
|
||||
background: #525252;
|
||||
font-weight: bold;
|
||||
height: 56px;
|
||||
width: 103px;
|
||||
border-radius: 6px;
|
||||
border: 2px solid #246FE5;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
line-height: 60px;
|
||||
}
|
||||
.share-desktop-icon{
|
||||
margin-top: 15%;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
@media (min-width: 432px) and (max-width: 632px) {
|
||||
font-size: 1.5vw;
|
||||
.share-desktop-icon{
|
||||
margin-top: 25%;
|
||||
}
|
||||
.desktop-share, .virtual-background-none, .thumbnail, .blur, .slight-blur{
|
||||
height: 60px;
|
||||
width: 60px;
|
||||
}
|
||||
.desktop-share-selected, .thumbnail-selected, .none-selected, .blur-selected, .slight-blur-selected{
|
||||
height: 56px;
|
||||
width: 56px;
|
||||
}
|
||||
}
|
||||
@media (max-width: 432px){
|
||||
.share-desktop-icon{
|
||||
margin-top: 25%;
|
||||
}
|
||||
font-size: 1.5vw;
|
||||
.desktop-share, .virtual-background-none, .thumbnail, .blur, .slight-blur{
|
||||
height: 60px;
|
||||
width: 60px;
|
||||
}
|
||||
.desktop-share-selected, .thumbnail-selected, .none-selected, .blur-selected, .slight-blur-selected{
|
||||
height: 56px;
|
||||
width: 56px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -205,12 +126,18 @@
|
|||
left: 51px
|
||||
}
|
||||
}
|
||||
|
||||
.delete-image-icon:hover {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.thumbnail-container {
|
||||
position: relative;
|
||||
&:focus-within {
|
||||
.thumbnail ~ .delete-image-icon{
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-background{
|
||||
|
|
|
@ -107,4 +107,4 @@ $selectActiveItemBg: darken($controlBackground, 20%);
|
|||
/**
|
||||
* TODO: Replace by themed component.
|
||||
*/
|
||||
$videoQualityActive: #4C9AFF;
|
||||
$videoQualityActive: #57A0ff;
|
||||
|
|
|
@ -186,10 +186,10 @@
|
|||
<!--#include virtual="static/settingsToolbarAdditionalContent.html" -->
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<noscript aria-hidden="true">
|
||||
<div>JavaScript is disabled. </br>For this site to work you have to enable JavaScript.</div>
|
||||
</noscript>
|
||||
<!--#include virtual="body.html" -->
|
||||
<div id="react"></div>
|
||||
<div id="react" role="main"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -70,12 +70,17 @@
|
|||
},
|
||||
"privateNotice": "Private Nachricht an {{recipient}}",
|
||||
"title": "Chatten",
|
||||
"you": "Sie"
|
||||
"you": "Sie",
|
||||
"message": "Nachricht",
|
||||
"messageAccessibleTitle": "{{user}} sagt:",
|
||||
"messageAccessibleTitleMe": "Ich sage:",
|
||||
"smileysPanel": "Emoji-Auswahl"
|
||||
},
|
||||
"chromeExtensionBanner": {
|
||||
"installExtensionText": "Installieren Sie die Erweiterung für die Integration von Google Calendar und Office 365",
|
||||
"buttonText": "Chrome-Erweiterung installieren",
|
||||
"dontShowAgain": "Hinweis nicht mehr anzeigen"
|
||||
"dontShowAgain": "Hinweis nicht mehr anzeigen",
|
||||
"close": "Schließen"
|
||||
},
|
||||
"connectingOverlay": {
|
||||
"joiningRoom": "Eine Verbindung zu Ihrem Meeting wird hergestellt…"
|
||||
|
@ -204,6 +209,8 @@
|
|||
"e2eeLabel": "Ende-zu-Ende-Verschlüsselung aktivieren",
|
||||
"e2eeWarning": "WARNUNG: Nicht alle Personen dieser Konferenz scheinen Ende-zu-Ende-Verschlüsselung zu unterstützen. Wenn Sie diese aktivieren, können die entsprechenden Personen nichts mehr sehen oder hören.",
|
||||
"enterDisplayName": "Bitte geben Sie hier Ihren Namen ein",
|
||||
"enterDisplayNameToJoin" : "Benutzername für Konferenz eingeben" ,
|
||||
"embedMeeting": "Besprechung einbetten",
|
||||
"error": "Fehler",
|
||||
"gracefulShutdown": "Der Dienst steht momentan wegen Wartungsarbeiten nicht zur Verfügung. Bitte versuchen Sie es später noch einmal.",
|
||||
"grantModeratorDialog": "Möchten Sie wirklich Moderationsrechte an diese Person vergeben?",
|
||||
|
@ -295,7 +302,7 @@
|
|||
"Share": "Teilen",
|
||||
"shareVideoLinkError": "Bitte einen gültigen YouTube-Link angeben.",
|
||||
"shareVideoTitle": "Video teilen",
|
||||
"shareYourScreen": "Bildschirm freigeben",
|
||||
"shareYourScreen": "Bildschirmfreigabe ein-/ausschalten",
|
||||
"shareYourScreenDisabled": "Bildschirmfreigabe deaktiviert.",
|
||||
"startLiveStreaming": "Livestream starten",
|
||||
"startRecording": "Aufnahme starten",
|
||||
|
@ -320,7 +327,9 @@
|
|||
"WaitForHostMsgWOk": "Die Konferenz <b>{{room}}</b> wurde noch nicht gestartet. Falls Sie die Konferenz leiten, authentifizieren Sie sich bitte. Warten Sie andernfalls, bis die Konferenz gestartet wird.",
|
||||
"WaitingForHostTitle": "Warten auf den Beginn der Konferenz …",
|
||||
"Yes": "Ja",
|
||||
"yourEntireScreen": "Ganzer Bildschirm"
|
||||
"yourEntireScreen": "Ganzer Bildschirm",
|
||||
"remoteUserControls": "Remote Benutzersteuerung von {{username}}",
|
||||
"localUserControls": "Lokale Benutzersteuerung"
|
||||
},
|
||||
"dialOut": {
|
||||
"statusMessage": "ist jetzt {{status}}"
|
||||
|
@ -340,8 +349,20 @@
|
|||
"slightBlur": "Hintergrund leicht unscharf",
|
||||
"removeBackground": "Hintergrund entfernen",
|
||||
"uploadImage": "Bild hochladen",
|
||||
"addBackground": "Hintergrund hinzufügen",
|
||||
"pleaseWait": "Bitte warten...",
|
||||
"none": "keiner"
|
||||
"none": "keiner",
|
||||
"uploadedImage": "Hochgeladenes Bild {{index}}",
|
||||
"deleteImage": "Bild löschen",
|
||||
"image1" : "Strand",
|
||||
"image2" : "Weiße neutrale Wand",
|
||||
"image3" : "Weißer leerer Raum",
|
||||
"image4" : "Schwarze Stehlampe",
|
||||
"image5" : "Berg",
|
||||
"image6" : "Wald",
|
||||
"image7" : "Sonnenaufgang",
|
||||
"desktopShareError": "Desktop konnte nicht freigegeben werden",
|
||||
"desktopShare":"Desktopfreigabe"
|
||||
},
|
||||
"feedback": {
|
||||
"average": "Durchschnittlich",
|
||||
|
@ -350,7 +371,8 @@
|
|||
"good": "Gut",
|
||||
"rateExperience": "Bitte bewerten Sie diese Konferenz",
|
||||
"veryBad": "Sehr schlecht",
|
||||
"veryGood": "Sehr gut"
|
||||
"veryGood": "Sehr gut",
|
||||
"star": "Sterne"
|
||||
},
|
||||
"incomingCall": {
|
||||
"answer": "Antworten",
|
||||
|
@ -367,6 +389,7 @@
|
|||
"country": "Land",
|
||||
"dialANumber": "Um am Meeting teilzunehmen, müssen Sie eine dieser Nummern wählen und dann die PIN eingeben.",
|
||||
"dialInConferenceID": "PIN:",
|
||||
"copyNumber":"Nummer kopieren",
|
||||
"dialInNotSupported": "Entschuldigung, leider wird das Einwählen derzeit nicht unterstützt.",
|
||||
"dialInNumber": "Einwählen:",
|
||||
"dialInSummaryError": "Fehler beim Abrufen der Einwahlinformationen. Versuchen Sie es später erneut.",
|
||||
|
@ -404,6 +427,7 @@
|
|||
"support": "Support",
|
||||
"supportMsg": "Wenn der Fehler erneut auftritt, bitte kontaktieren Sie"
|
||||
},
|
||||
"jitsiHome": "{{logo}} Logo, verlinkt zur Homepage",
|
||||
"keyboardShortcuts": {
|
||||
"focusLocal": "Lokales Video fokussieren",
|
||||
"focusRemote": "Auf das Video einer anderen Person fokussieren",
|
||||
|
@ -524,7 +548,19 @@
|
|||
"OldElectronAPPTitle": "Sicherheitslücke!",
|
||||
"oldElectronClientDescription1": "Sie scheinen eine alte Version des Jitsi-Meet-Clients zu nutzen. Diese hat bekannte Schwachstellen. Bitte aktualisieren Sie auf unsere ",
|
||||
"oldElectronClientDescription2": "aktuelle Version",
|
||||
"oldElectronClientDescription3": "!"
|
||||
"oldElectronClientDescription3": "!",
|
||||
"groupTitle": "Benachrichtigungen"
|
||||
},
|
||||
"participantsPane": {
|
||||
"close": "Schließen",
|
||||
"headings": {
|
||||
"lobby": "Lobby ({{count}})",
|
||||
"participantsList": "Teilnehmer ({{count}})"
|
||||
},
|
||||
"actions": {
|
||||
"muteAll": "Alle stummschalten",
|
||||
"stopVideo": "Video stoppen"
|
||||
}
|
||||
},
|
||||
"participantsPane": {
|
||||
"headings": {
|
||||
|
@ -589,6 +625,7 @@
|
|||
"linkCopied": "Link in die Zwischenablage kopiert",
|
||||
"lookGood": "Ihr Mikrofon scheint zu funktionieren.",
|
||||
"or": "oder",
|
||||
"keyboardShortcuts" : "Tastaturkurzbefehle aktivieren",
|
||||
"premeeting": "Vorschau",
|
||||
"showScreen": "Konferenzvorschau aktivieren",
|
||||
"startWithPhone": "Mit Telefonaudio starten",
|
||||
|
@ -612,6 +649,7 @@
|
|||
"ringing": "Es klingelt …"
|
||||
},
|
||||
"profile": {
|
||||
"avatar": "Benutzerbild",
|
||||
"setDisplayNameLabel": "Anzeigename festlegen",
|
||||
"setEmailInput": "E-Mail eingeben",
|
||||
"setEmailLabel": "E-Mail-Adresse für Gravatar",
|
||||
|
@ -728,29 +766,29 @@
|
|||
"title": "Die Konferenz wurde unterbrochen, weil der Standby-Modus aktiviert wurde."
|
||||
},
|
||||
"toolbar": {
|
||||
"accessibilityLabel": {
|
||||
"accessibilityLabel": {
|
||||
"audioOnly": "„Nur Audio“ ein-/ausschalten",
|
||||
"audioRoute": "Audiogerät auswählen",
|
||||
"callQuality": "Qualitätseinstellungen",
|
||||
"cc": "Untertitel ein-/ausschalten",
|
||||
"chat": "Chatfenster ein-/ausblenden",
|
||||
"chat": "Chatfenster öffnen / schließen",
|
||||
"document": "Geteiltes Dokument schließen",
|
||||
"download": "Unsere Apps herunterladen",
|
||||
"embedMeeting": "Konferenz einbetten",
|
||||
"feedback": "Feedback hinterlassen",
|
||||
"fullScreen": "Vollbildmodus ein-/ausschalten",
|
||||
"grantModerator": "Moderationsrechte vergeben",
|
||||
"hangup": "Anruf beenden",
|
||||
"hangup": "Konferenz verlassen",
|
||||
"help": "Hilfe",
|
||||
"invite": "Person einladen",
|
||||
"kick": "Person entfernen",
|
||||
"lobbyButton": "Lobbymodus ein-/ausschalten",
|
||||
"localRecording": "Lokale Aufzeichnungssteuerelemente ein-/ausschalten",
|
||||
"lockRoom": "Konferenzpasswort ein-/ausschalten",
|
||||
"moreActions": "Menü „Weitere Aktionen“ ein-/ausschalten",
|
||||
"moreActionsMenu": "Menü „Weitere Aktionen“",
|
||||
"moreActions": "Menü „Weitere Einstellungen“ ein-/ausschalten",
|
||||
"moreActionsMenu": "Menü „Weitere Einstellungen“",
|
||||
"moreOptions": "Menü „Weitere Optionen“",
|
||||
"mute": "„Audio stummschalten“ ein-/ausschalten",
|
||||
"mute": "Mikrofon aktivieren / deaktivieren",
|
||||
"muteEveryone": "Alle stummschalten",
|
||||
"muteEveryoneElse": "Alle anderen stummschalten",
|
||||
"muteEveryonesVideo": "Alle Kameras ausschalten",
|
||||
|
@ -759,7 +797,7 @@
|
|||
"pip": "Bild-in-Bild-Modus ein-/ausschalten",
|
||||
"privateMessage": "Private Nachricht senden",
|
||||
"profile": "Profil bearbeiten",
|
||||
"raiseHand": "„Melden“ ein-/ausschalten",
|
||||
"raiseHand": "Hand erheben / senken",
|
||||
"recording": "Aufzeichnung ein-/ausschalten",
|
||||
"remoteMute": "Personen stummschalten",
|
||||
"remoteVideoMute": "Kamera von dieser Person ausschalten",
|
||||
|
@ -776,7 +814,9 @@
|
|||
"toggleCamera": "Kamera wechseln",
|
||||
"toggleFilmstrip": "Miniaturansichten ein-/ausschalten",
|
||||
"videomute": "„Video stummschalten“ ein-/ausschalten",
|
||||
"selectBackground": "Hintergrund auswählen"
|
||||
"selectBackground": "Hintergrund auswählen",
|
||||
"expand": "Ausklappen",
|
||||
"collapse": "Einklappen"
|
||||
},
|
||||
"addPeople": "Personen zur Konferenz hinzufügen",
|
||||
"audioSettings": "Ton-Einstellungen",
|
||||
|
@ -797,7 +837,7 @@
|
|||
"exitFullScreen": "Vollbildmodus verlassen",
|
||||
"exitTileView": "Kachelansicht ausschalten",
|
||||
"feedback": "Feedback hinterlassen",
|
||||
"hangup": "Verlassen",
|
||||
"hangup": "Konferenz verlassen",
|
||||
"help": "Hilfe",
|
||||
"invite": "Personen einladen",
|
||||
"lobbyButtonDisable": "Lobbymodus deaktivieren",
|
||||
|
@ -807,7 +847,7 @@
|
|||
"lowerYourHand": "Hand senken",
|
||||
"moreActions": "Weitere Einstellungen",
|
||||
"moreOptions": "Weitere Optionen",
|
||||
"mute": "Stummschaltung aktivieren / deaktivieren",
|
||||
"mute": "Mikrofon aktivieren / deaktivieren",
|
||||
"muteEveryone": "Alle stummschalten",
|
||||
"muteEveryonesVideo": "Alle Kameras ausschalten",
|
||||
"noAudioSignalTitle": "Es kommt kein Input von Ihrem Mikrofon!",
|
||||
|
@ -822,7 +862,7 @@
|
|||
"pip": "Bild-in-Bild-Modus einschalten",
|
||||
"privateMessage": "Private Nachricht senden",
|
||||
"profile": "Profil bearbeiten",
|
||||
"raiseHand": "Hand erheben",
|
||||
"raiseHand": "Hand erheben / senken",
|
||||
"raiseYourHand": "Melden",
|
||||
"security": "Sicherheitsoptionen",
|
||||
"Settings": "Einstellungen",
|
||||
|
@ -867,6 +907,7 @@
|
|||
"react-nativeGrantPermissions": "Wählen Sie <b><i>Erlauben</i></b>, wenn der Browser um Berechtigungen bittet.",
|
||||
"safariGrantPermissions": "Wählen Sie <b><i>OK</i></b>, wenn der Browser um Berechtigungen bittet."
|
||||
},
|
||||
"volumeSlider": "Lautstärkeregler",
|
||||
"videoSIPGW": {
|
||||
"busy": "Es stehen keine freien Ressourcen zur Verfügung. Bitte versuchen Sie es später noch einmal.",
|
||||
"busyTitle": "Keine freien Ressourcen",
|
||||
|
@ -911,6 +952,7 @@
|
|||
"videomute": "Person hat die Kamera angehalten"
|
||||
},
|
||||
"welcomepage": {
|
||||
"addMeetingName": "Besprechungsnamen hinzufügen",
|
||||
"accessibilityLabel": {
|
||||
"join": "Zum Teilnehmen tippen",
|
||||
"roomname": "Konferenzname eingeben"
|
||||
|
@ -933,6 +975,9 @@
|
|||
"join": "ERSTELLEN / BEITRETEN",
|
||||
"jitsiOnMobile": "Jitsi unterwegs – einfach unsere Apps herunterladen und Meetings von überall starten",
|
||||
"moderatedMessage": "Oder <a href=\"{{url}}\" rel=\"noopener noreferrer\" target=\"_blank\">reservieren Sie sich eine Konferenz-URL</a>, die nur Sie moderieren.",
|
||||
"mobileDownLoadLinkIos": "iOS App Download",
|
||||
"mobileDownLoadLinkAndroid": "Android App Download",
|
||||
"mobileDownLoadLinkFDroid": "F-Droid App Download",
|
||||
"privacy": "Datenschutz",
|
||||
"recentList": "Verlauf",
|
||||
"recentListDelete": "Eintrag löschen",
|
||||
|
@ -944,7 +989,15 @@
|
|||
"sendFeedback": "Feedback senden",
|
||||
"startMeeting": "Meeting starten",
|
||||
"terms": "AGB",
|
||||
"title": "Sichere, voll funktionale und komplett kostenlose Videokonferenzen"
|
||||
"title": "Sichere, voll funktionale und komplett kostenlose Videokonferenzen",
|
||||
"logo":{
|
||||
"calendar":"Kalender Logo",
|
||||
"microsoftLogo":"Microsoft Logo",
|
||||
"logoDeepLinking":"Jitsi Meet Logo",
|
||||
"desktopPreviewThumbnail":"Desktop-Vorschau Thumbnail",
|
||||
"googleLogo":"Google Logo",
|
||||
"policyLogo":"Richtlinienlogo"
|
||||
}
|
||||
},
|
||||
"lonelyMeetingExperience": {
|
||||
"button": "Andere einladen",
|
||||
|
|
|
@ -70,12 +70,17 @@
|
|||
},
|
||||
"privateNotice": "Private message to {{recipient}}",
|
||||
"title": "Chat",
|
||||
"you": "you"
|
||||
"you": "you",
|
||||
"message": "Message",
|
||||
"messageAccessibleTitle": "{{user}} says:",
|
||||
"messageAccessibleTitleMe": "me says:",
|
||||
"smileysPanel": "Emoji panel"
|
||||
},
|
||||
"chromeExtensionBanner": {
|
||||
"installExtensionText": "Install the extension for Google Calendar and Office 365 integration",
|
||||
"buttonText": "Install Chrome Extension",
|
||||
"dontShowAgain": "Don’t show me this again"
|
||||
"dontShowAgain": "Don’t show me this again",
|
||||
"close": "Close"
|
||||
},
|
||||
"connectingOverlay": {
|
||||
"joiningRoom": "Connecting you to your meeting..."
|
||||
|
@ -204,6 +209,8 @@
|
|||
"e2eeLabel": "Enable End-to-End Encryption",
|
||||
"e2eeWarning": "WARNING: Not all participants in this meeting seem to have support for End-to-End encryption. If you enable it they won't be able to see nor hear you.",
|
||||
"enterDisplayName": "Please enter your name here",
|
||||
"enterDisplayNameToJoin": "Please enter your name to join",
|
||||
"embedMeeting": "Embed meeting",
|
||||
"error": "Error",
|
||||
"gracefulShutdown": "Our service is currently down for maintenance. Please try again later.",
|
||||
"grantModeratorDialog": "Are you sure you want to make this participant a moderator?",
|
||||
|
@ -320,7 +327,9 @@
|
|||
"WaitForHostMsgWOk": "The conference <b>{{room}}</b> has not yet started. If you are the host then please press Ok to authenticate. Otherwise, please wait for the host to arrive.",
|
||||
"WaitingForHostTitle": "Waiting for the host ...",
|
||||
"Yes": "Yes",
|
||||
"yourEntireScreen": "Your entire screen"
|
||||
"yourEntireScreen": "Your entire screen",
|
||||
"remoteUserControls": "Remote user controls of {{username}}",
|
||||
"localUserControls": "Local user controls"
|
||||
},
|
||||
"dialOut": {
|
||||
"statusMessage": "is now {{status}}"
|
||||
|
@ -341,8 +350,19 @@
|
|||
"slightBlur": "Slight Blur",
|
||||
"removeBackground": "Remove background",
|
||||
"addBackground": "Add background",
|
||||
"pleaseWait": "Please wait...",
|
||||
"none": "None",
|
||||
"desktopShareError": "Could not create desktop share"
|
||||
"uploadedImage": "Uploaded image {{index}}",
|
||||
"deleteImage": "Delete image",
|
||||
"image1" : "Beach",
|
||||
"image2" : "White neutral wall",
|
||||
"image3" : "White empty room",
|
||||
"image4" : "Black floor lamp",
|
||||
"image5" : "Mountain",
|
||||
"image6" : "Forest ",
|
||||
"image7" : "Sunrise",
|
||||
"desktopShareError": "Could not create desktop share",
|
||||
"desktopShare":"Desktop share"
|
||||
},
|
||||
"feedback": {
|
||||
"average": "Average",
|
||||
|
@ -351,7 +371,8 @@
|
|||
"good": "Good",
|
||||
"rateExperience": "Rate your meeting experience",
|
||||
"veryBad": "Very Bad",
|
||||
"veryGood": "Very Good"
|
||||
"veryGood": "Very Good",
|
||||
"star": "Star"
|
||||
},
|
||||
"incomingCall": {
|
||||
"answer": "Answer",
|
||||
|
@ -368,6 +389,7 @@
|
|||
"country": "Country",
|
||||
"dialANumber": "To join your meeting, dial one of these numbers and then enter the pin.",
|
||||
"dialInConferenceID": "PIN:",
|
||||
"copyNumber":"Copy number",
|
||||
"dialInNotSupported": "Sorry, dialing in is currently not supported.",
|
||||
"dialInNumber": "Dial-in:",
|
||||
"dialInSummaryError": "Error fetching dial-in info now. Please try again later.",
|
||||
|
@ -405,6 +427,7 @@
|
|||
"support": "Support",
|
||||
"supportMsg": "If this keeps happening, reach out to"
|
||||
},
|
||||
"jitsiHome": "{{logo}} Logo, links to Homepage",
|
||||
"keyboardShortcuts": {
|
||||
"focusLocal": "Focus on your video",
|
||||
"focusRemote": "Focus on another person's video",
|
||||
|
@ -525,9 +548,11 @@
|
|||
"OldElectronAPPTitle": "Security vulnerability!",
|
||||
"oldElectronClientDescription1": "You appear to be using an old version of the Jitsi Meet client which has known security vulnerabilities. Please make sure you update to our ",
|
||||
"oldElectronClientDescription2": "latest build",
|
||||
"oldElectronClientDescription3": " now!"
|
||||
"oldElectronClientDescription3": " now!",
|
||||
"groupTitle": "Notifications"
|
||||
},
|
||||
"participantsPane": {
|
||||
"close": "Close",
|
||||
"headings": {
|
||||
"lobby": "Lobby ({{count}})",
|
||||
"participantsList": "Meeting participants ({{count}})"
|
||||
|
@ -592,6 +617,7 @@
|
|||
"or": "or",
|
||||
"premeeting": "Pre meeting",
|
||||
"showScreen": "Enable pre meeting screen",
|
||||
"keyboardShortcuts" : "Enable Keyboard shortcuts",
|
||||
"startWithPhone": "Start with phone audio",
|
||||
"screenSharingError": "Screen sharing error:",
|
||||
"videoOnlyError": "Video error:",
|
||||
|
@ -613,6 +639,7 @@
|
|||
"ringing": "Ringing..."
|
||||
},
|
||||
"profile": {
|
||||
"avatar": "avatar",
|
||||
"setDisplayNameLabel": "Set your display name",
|
||||
"setEmailInput": "Enter e-mail",
|
||||
"setEmailLabel": "Set your gravatar email",
|
||||
|
@ -735,24 +762,24 @@
|
|||
"audioRoute": "Select the sound device",
|
||||
"callQuality": "Manage video quality",
|
||||
"cc": "Toggle subtitles",
|
||||
"chat": "Toggle chat window",
|
||||
"chat": "Open / Close chat",
|
||||
"document": "Toggle shared document",
|
||||
"download": "Download our apps",
|
||||
"embedMeeting": "Embed meeting",
|
||||
"feedback": "Leave feedback",
|
||||
"fullScreen": "Toggle full screen",
|
||||
"grantModerator": "Grant Moderator",
|
||||
"hangup": "Leave the call",
|
||||
"hangup": "Leave the meeting",
|
||||
"help": "Help",
|
||||
"invite": "Invite people",
|
||||
"kick": "Kick participant",
|
||||
"lobbyButton": "Enable/disable lobby mode",
|
||||
"localRecording": "Toggle local recording controls",
|
||||
"lockRoom": "Toggle meeting password",
|
||||
"moreActions": "Toggle more actions menu",
|
||||
"moreActions": "More actions",
|
||||
"moreActionsMenu": "More actions menu",
|
||||
"moreOptions": "Show more options",
|
||||
"mute": "Toggle mute audio",
|
||||
"mute": "Mute / Unmute",
|
||||
"muteEveryone": "Mute everyone",
|
||||
"muteEveryoneElse": "Mute everyone else",
|
||||
"muteEveryonesVideo": "Disable everyone's camera",
|
||||
|
@ -761,7 +788,7 @@
|
|||
"pip": "Toggle Picture-in-Picture mode",
|
||||
"privateMessage": "Send private message",
|
||||
"profile": "Edit your profile",
|
||||
"raiseHand": "Toggle raise hand",
|
||||
"raiseHand": "Raise / Lower your hand",
|
||||
"recording": "Toggle recording",
|
||||
"remoteMute": "Mute participant",
|
||||
"remoteVideoMute": "Disable camera of participant",
|
||||
|
@ -770,18 +797,22 @@
|
|||
"shareaudio": "Share audio",
|
||||
"sharedvideo": "Toggle YouTube video sharing",
|
||||
"shareRoom": "Invite someone",
|
||||
"shareYourScreen": "Toggle screenshare",
|
||||
"shareYourScreen": "Start / Stop sharing your screen",
|
||||
"shortcuts": "Toggle shortcuts",
|
||||
"show": "Show on stage",
|
||||
"speakerStats": "Toggle speaker statistics",
|
||||
"tileView": "Toggle tile view",
|
||||
"toggleCamera": "Toggle camera",
|
||||
"toggleFilmstrip": "Toggle filmstrip",
|
||||
"videomute": "Toggle mute video",
|
||||
"selectBackground": "Select Background"
|
||||
"videomute": "Start / Stop camera",
|
||||
"videoblur": "Toggle video blur",
|
||||
"selectBackground": "Select Background",
|
||||
"expand": "Expand",
|
||||
"collapse": "Collapse"
|
||||
},
|
||||
"addPeople": "Add people to your call",
|
||||
"audioSettings": "Audio settings",
|
||||
"videoSettings": "Video settings",
|
||||
"audioOnlyOff": "Disable low bandwidth mode",
|
||||
"audioOnlyOn": "Enable low bandwidth mode",
|
||||
"audioRoute": "Select the sound device",
|
||||
|
@ -799,7 +830,7 @@
|
|||
"exitFullScreen": "Exit full screen",
|
||||
"exitTileView": "Exit tile view",
|
||||
"feedback": "Leave feedback",
|
||||
"hangup": "Leave",
|
||||
"hangup": "Leave the meeting",
|
||||
"help": "Help",
|
||||
"invite": "Invite people",
|
||||
"lobbyButtonDisable": "Disable lobby mode",
|
||||
|
@ -842,7 +873,6 @@
|
|||
"tileViewToggle": "Toggle tile view",
|
||||
"toggleCamera": "Toggle camera",
|
||||
"videomute": "Start / Stop camera",
|
||||
"videoSettings": "Video settings",
|
||||
"selectBackground": "Select background"
|
||||
},
|
||||
"transcribing": {
|
||||
|
@ -869,6 +899,7 @@
|
|||
"react-nativeGrantPermissions": "Select <b><i>Allow</i></b> when your browser asks for permissions.",
|
||||
"safariGrantPermissions": "Select <b><i>OK</i></b> when your browser asks for permissions."
|
||||
},
|
||||
"volumeSlider": "Volume slider",
|
||||
"videoSIPGW": {
|
||||
"busy": "We're working on freeing resources. Please try again in a few minutes.",
|
||||
"busyTitle": "The Room service is currently busy",
|
||||
|
@ -913,6 +944,7 @@
|
|||
"videomute": "Participant has stopped the camera"
|
||||
},
|
||||
"welcomepage": {
|
||||
"addMeetingName": "Add Meeting name",
|
||||
"accessibilityLabel": {
|
||||
"join": "Tap to join",
|
||||
"roomname": "Enter room name"
|
||||
|
@ -934,6 +966,9 @@
|
|||
"info": "Dial-in info",
|
||||
"join": "CREATE / JOIN",
|
||||
"jitsiOnMobile": "Jitsi on mobile – download our apps and start a meeting from anywhere",
|
||||
"mobileDownLoadLinkIos": "Download mobile app for iOS",
|
||||
"mobileDownLoadLinkAndroid": "Download mobile app for Android",
|
||||
"mobileDownLoadLinkFDroid": "Download mobile app for F-Droid",
|
||||
"moderatedMessage": "Or <a href=\"{{url}}\" rel=\"noopener noreferrer\" target=\"_blank\">book a meeting URL</a> in advance where you are the only moderator.",
|
||||
"privacy": "Privacy",
|
||||
"recentList": "Recent",
|
||||
|
@ -946,7 +981,15 @@
|
|||
"sendFeedback": "Send feedback",
|
||||
"startMeeting": "Start meeting",
|
||||
"terms": "Terms",
|
||||
"title": "Secure, fully featured, and completely free video conferencing"
|
||||
"title": "Secure, fully featured, and completely free video conferencing",
|
||||
"logo":{
|
||||
"calendar":"Calendar logo",
|
||||
"microsoftLogo":"Microsoft logo",
|
||||
"logoDeepLinking":"Jitsi meet logo",
|
||||
"desktopPreviewThumbnail":"Desktop preview thumbnail",
|
||||
"googleLogo":"Google Logo",
|
||||
"policyLogo":"Policy logo"
|
||||
}
|
||||
},
|
||||
"lonelyMeetingExperience": {
|
||||
"button": "Invite others",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/* global APP, $ */
|
||||
|
||||
/* global APP */
|
||||
import { jitsiLocalStorage } from '@jitsi/js-utils';
|
||||
import Logger from 'jitsi-meet-logger';
|
||||
|
||||
import {
|
||||
|
@ -30,44 +30,66 @@ const _shortcuts = new Map();
|
|||
const _shortcutsHelp = new Map();
|
||||
|
||||
/**
|
||||
* True if the keyboard shortcuts are enabled and false if not.
|
||||
* @type {boolean}
|
||||
* The key used to save in local storage if keyboard shortcuts are enabled.
|
||||
*/
|
||||
let enabled = true;
|
||||
const _enableShortcutsKey = 'enableShortcuts';
|
||||
|
||||
/**
|
||||
* Prefer keyboard handling of these elements over global shortcuts.
|
||||
* If a button is triggered using the Spacebar it should not trigger PTT.
|
||||
* If an input element is focused and M is pressed it should not mute audio.
|
||||
*/
|
||||
const _elementsBlacklist = [
|
||||
'input',
|
||||
'textarea',
|
||||
'button',
|
||||
'[role=button]',
|
||||
'[role=menuitem]',
|
||||
'[role=radio]',
|
||||
'[role=tab]',
|
||||
'[role=option]',
|
||||
'[role=switch]',
|
||||
'[role=range]',
|
||||
'[role=log]'
|
||||
];
|
||||
|
||||
/**
|
||||
* An element selector for elements that have their own keyboard handling.
|
||||
*/
|
||||
const _focusedElementsSelector = `:focus:is(${_elementsBlacklist.join(',')})`;
|
||||
|
||||
/**
|
||||
* Maps keycode to character, id of popover for given function and function.
|
||||
*/
|
||||
const KeyboardShortcut = {
|
||||
isPushToTalkActive: false,
|
||||
|
||||
init() {
|
||||
this._initGlobalShortcuts();
|
||||
|
||||
window.onkeyup = e => {
|
||||
if (!enabled) {
|
||||
if (!this.getEnabled()) {
|
||||
return;
|
||||
}
|
||||
const key = this._getKeyboardKey(e).toUpperCase();
|
||||
const num = parseInt(key, 10);
|
||||
|
||||
if (!($(':focus').is('input[type=text]')
|
||||
|| $(':focus').is('input[type=password]')
|
||||
|| $(':focus').is('textarea'))) {
|
||||
if (!document.querySelector(_focusedElementsSelector)) {
|
||||
if (_shortcuts.has(key)) {
|
||||
_shortcuts.get(key).function(e);
|
||||
} else if (!isNaN(num) && num >= 0 && num <= 9) {
|
||||
APP.store.dispatch(clickOnVideo(num));
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
window.onkeydown = e => {
|
||||
if (!enabled) {
|
||||
if (!this.getEnabled()) {
|
||||
return;
|
||||
}
|
||||
if (!($(':focus').is('input[type=text]')
|
||||
|| $(':focus').is('input[type=password]')
|
||||
|| $(':focus').is('textarea'))) {
|
||||
const focusedElement = document.querySelector(_focusedElementsSelector);
|
||||
|
||||
if (!focusedElement) {
|
||||
if (this._getKeyboardKey(e).toUpperCase() === ' ') {
|
||||
if (APP.conference.isLocalAudioMuted()) {
|
||||
sendAnalytics(createShortcutEvent(
|
||||
|
@ -75,8 +97,14 @@ const KeyboardShortcut = {
|
|||
PRESSED));
|
||||
logger.log('Talk shortcut pressed');
|
||||
APP.conference.muteAudio(false);
|
||||
this.isPushToTalkActive = true;
|
||||
}
|
||||
}
|
||||
} else if (this._getKeyboardKey(e).toUpperCase() === 'ESCAPE') {
|
||||
// Allow to remove focus from selected elements using ESC key.
|
||||
if (focusedElement && focusedElement.blur) {
|
||||
focusedElement.blur();
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
|
@ -86,7 +114,13 @@ const KeyboardShortcut = {
|
|||
* @param {boolean} value - the new value.
|
||||
*/
|
||||
enable(value) {
|
||||
enabled = value;
|
||||
jitsiLocalStorage.setItem(_enableShortcutsKey, value);
|
||||
},
|
||||
|
||||
getEnabled() {
|
||||
// Should be enabled if not explicitly set to false
|
||||
// eslint-disable-next-line no-unneeded-ternary
|
||||
return jitsiLocalStorage.getItem(_enableShortcutsKey) === 'false' ? false : true;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -198,9 +232,12 @@ const KeyboardShortcut = {
|
|||
// register SPACE shortcut in two steps to insure visibility of help
|
||||
// message
|
||||
this.registerShortcut(' ', null, () => {
|
||||
sendAnalytics(createShortcutEvent('push.to.talk', RELEASED));
|
||||
logger.log('Talk shortcut released');
|
||||
APP.conference.muteAudio(true);
|
||||
if (this.isPushToTalkActive) {
|
||||
sendAnalytics(createShortcutEvent('push.to.talk', RELEASED));
|
||||
logger.log('Talk shortcut released');
|
||||
APP.conference.muteAudio(true);
|
||||
this.isPushToTalkActive = false;
|
||||
}
|
||||
});
|
||||
this._addShortcutToHelp('SPACE', 'keyboardShortcuts.pushToTalk');
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* @flow */
|
||||
/* @flow */
|
||||
|
||||
import jqueryI18next from 'jquery-i18next';
|
||||
|
||||
|
@ -6,6 +6,10 @@ import { i18next } from '../../react/features/base/i18n';
|
|||
|
||||
declare var $: Function;
|
||||
|
||||
type DocumentElement = {
|
||||
lang: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies that the {@link i18next} instance has finished its initialization.
|
||||
*
|
||||
|
@ -13,7 +17,12 @@ declare var $: Function;
|
|||
* @private
|
||||
*/
|
||||
function _onI18nInitialized() {
|
||||
|
||||
const documentElement: DocumentElement
|
||||
= document.documentElement || {};
|
||||
|
||||
$('[data-i18n]').localize();
|
||||
documentElement.lang = i18next.language;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -15262,12 +15262,23 @@
|
|||
}
|
||||
},
|
||||
"react-textarea-autosize": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-7.1.0.tgz",
|
||||
"integrity": "sha512-c2FlR/fP0qbxmlrW96SdrbgP/v0XZMTupqB90zybvmDVDutytUgPl7beU35klwcTeMepUIQEpQUn3P3bdshGPg==",
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.3.0.tgz",
|
||||
"integrity": "sha512-3GLWFAan2pbwBeoeNDoqGmSbrShORtgWfaWX0RJDivsUrpShh01saRM5RU/i4Zmf+whpBVEY5cA90Eq8Ub1N3w==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.1.2",
|
||||
"prop-types": "^15.6.0"
|
||||
"@babel/runtime": "^7.10.2",
|
||||
"use-composed-ref": "^1.0.0",
|
||||
"use-latest": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
"version": "7.12.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz",
|
||||
"integrity": "sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"react-transition-group": {
|
||||
|
@ -17690,6 +17701,11 @@
|
|||
"integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==",
|
||||
"dev": true
|
||||
},
|
||||
"ts-essentials": {
|
||||
"version": "2.0.12",
|
||||
"resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-2.0.12.tgz",
|
||||
"integrity": "sha512-3IVX4nI6B5cc31/GFFE+i8ey/N2eA0CZDbo6n0yrz0zDX8ZJ8djmU1p+XRz7G3is0F3bB3pu2pAroFdAWQKU3w=="
|
||||
},
|
||||
"tslib": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz",
|
||||
|
@ -17975,6 +17991,27 @@
|
|||
"resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.2.5.tgz",
|
||||
"integrity": "sha512-gN3vgMISAgacF7sqsLPByqoePooY3n2emTH59Ur5d/M8eg4WTWu1xp8i8DHjohftIyEx0S08RiYxbffr4j8Peg=="
|
||||
},
|
||||
"use-composed-ref": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.1.0.tgz",
|
||||
"integrity": "sha512-my1lNHGWsSDAhhVAT4MKs6IjBUtG6ZG11uUqexPH9PptiIZDQOzaF4f5tEbJ2+7qvNbtXNBbU3SfmN+fXlWDhg==",
|
||||
"requires": {
|
||||
"ts-essentials": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"use-isomorphic-layout-effect": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.1.tgz",
|
||||
"integrity": "sha512-L7Evj8FGcwo/wpbv/qvSfrkHFtOpCzvM5yl2KVyDJoylVuSvzphiiasmjgQPttIGBAy2WKiBNR98q8w7PiNgKQ=="
|
||||
},
|
||||
"use-latest": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.2.0.tgz",
|
||||
"integrity": "sha512-d2TEuG6nSLKQLAfW3By8mKr8HurOlTkul0sOpxbClIv4SQ4iOd7BYr7VIzdbktUCnv7dua/60xzd8igMU6jmyw==",
|
||||
"requires": {
|
||||
"use-isomorphic-layout-effect": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"use-memo-one": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.2.tgz",
|
||||
|
|
|
@ -91,7 +91,7 @@
|
|||
"react-native-webview": "11.0.2",
|
||||
"react-native-youtube-iframe": "1.2.3",
|
||||
"react-redux": "7.1.0",
|
||||
"react-textarea-autosize": "7.1.0",
|
||||
"react-textarea-autosize": "8.3.0",
|
||||
"react-transition-group": "2.4.0",
|
||||
"react-youtube": "7.13.1",
|
||||
"redux": "4.0.4",
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import React from 'react';
|
||||
|
||||
import { translate } from '../../../../base/i18n';
|
||||
import { Icon } from '../../../icons';
|
||||
import AbstractStatelessAvatar, { type Props as AbstractProps } from '../AbstractStatelessAvatar';
|
||||
|
||||
|
@ -30,14 +31,19 @@ type Props = AbstractProps & {
|
|||
/**
|
||||
* TestId of the element, if any.
|
||||
*/
|
||||
testId?: string
|
||||
testId?: string,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements a stateless avatar component that renders an avatar purely from what gets passed through
|
||||
* props.
|
||||
*/
|
||||
export default class StatelessAvatar extends AbstractStatelessAvatar<Props> {
|
||||
class StatelessAvatar extends AbstractStatelessAvatar<Props> {
|
||||
/**
|
||||
* Implements {@code Component#render}.
|
||||
*
|
||||
|
@ -64,6 +70,7 @@ export default class StatelessAvatar extends AbstractStatelessAvatar<Props> {
|
|||
return (
|
||||
<div className = { this._getBadgeClassName() }>
|
||||
<img
|
||||
alt = { this.props.t('profile.avatar') }
|
||||
className = { this._getAvatarClassName() }
|
||||
data-testid = { this.props.testId }
|
||||
id = { this.props.id }
|
||||
|
@ -88,7 +95,7 @@ export default class StatelessAvatar extends AbstractStatelessAvatar<Props> {
|
|||
xmlnsXlink = 'http://www.w3.org/1999/xlink'>
|
||||
<text
|
||||
dominantBaseline = 'central'
|
||||
fill = 'rgba(255,255,255,.6)'
|
||||
fill = 'rgba(255,255,255,1)'
|
||||
fontSize = '40pt'
|
||||
textAnchor = 'middle'
|
||||
x = '50'
|
||||
|
@ -104,6 +111,7 @@ export default class StatelessAvatar extends AbstractStatelessAvatar<Props> {
|
|||
return (
|
||||
<div className = { this._getBadgeClassName() }>
|
||||
<img
|
||||
alt = { this.props.t('profile.avatar') }
|
||||
className = { this._getAvatarClassName('defaultAvatar') }
|
||||
data-testid = { this.props.testId }
|
||||
id = { this.props.id }
|
||||
|
@ -157,3 +165,5 @@ export default class StatelessAvatar extends AbstractStatelessAvatar<Props> {
|
|||
|
||||
_isIcon: (?string | ?Object) => boolean
|
||||
}
|
||||
|
||||
export default translate(StatelessAvatar);
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
import React, { useState } from 'react';
|
||||
|
||||
import { Icon, IconCheck, IconCopy } from '../../base/icons';
|
||||
import { translate } from '../i18n';
|
||||
import { copyText } from '../util';
|
||||
|
||||
|
||||
|
@ -32,7 +31,12 @@ type Props = {
|
|||
/**
|
||||
* The text displayed on copy success
|
||||
*/
|
||||
textOnCopySuccess: string
|
||||
textOnCopySuccess: string,
|
||||
|
||||
/**
|
||||
* The id of the button
|
||||
*/
|
||||
id?: string,
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -40,7 +44,7 @@ type Props = {
|
|||
*
|
||||
* @returns {React$Element<any>}
|
||||
*/
|
||||
function CopyButton({ className, displayedText, textToCopy, textOnHover, textOnCopySuccess }: Props) {
|
||||
function CopyButton({ className, displayedText, textToCopy, textOnHover, textOnCopySuccess, id }: Props) {
|
||||
const [ isClicked, setIsClicked ] = useState(false);
|
||||
const [ isHovered, setIsHovered ] = useState(false);
|
||||
|
||||
|
@ -83,6 +87,20 @@ function CopyButton({ className, displayedText, textToCopy, textOnHover, textOnC
|
|||
setIsHovered(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* KeyPress handler for accessibility.
|
||||
*
|
||||
* @param {React.KeyboardEventHandler<HTMLDivElement>} e - The key event to handle.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function onKeyPress(e) {
|
||||
if (onClick && (e.key === ' ' || e.key === 'Enter')) {
|
||||
e.preventDefault();
|
||||
onClick();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the content of the link based on the state.
|
||||
*
|
||||
|
@ -93,7 +111,7 @@ function CopyButton({ className, displayedText, textToCopy, textOnHover, textOnC
|
|||
return (
|
||||
<>
|
||||
<div className = 'copy-button-content selected'>
|
||||
{textOnCopySuccess}
|
||||
<span role = { 'alert' }>{ textOnCopySuccess }</span>
|
||||
</div>
|
||||
<Icon src = { IconCheck } />
|
||||
</>
|
||||
|
@ -112,10 +130,17 @@ function CopyButton({ className, displayedText, textToCopy, textOnHover, textOnC
|
|||
|
||||
return (
|
||||
<div
|
||||
aria-label = { textOnHover }
|
||||
className = { `${className} copy-button${isClicked ? ' clicked' : ''}` }
|
||||
id = { id }
|
||||
onBlur = { onHoverOut }
|
||||
onClick = { onClick }
|
||||
onFocus = { onHoverIn }
|
||||
onKeyPress = { onKeyPress }
|
||||
onMouseOut = { onHoverOut }
|
||||
onMouseOver = { onHoverIn }>
|
||||
onMouseOver = { onHoverIn }
|
||||
role = 'button'
|
||||
tabIndex = { 0 }>
|
||||
{ renderContent() }
|
||||
</div>
|
||||
);
|
||||
|
@ -125,4 +150,4 @@ CopyButton.defaultProps = {
|
|||
className: ''
|
||||
};
|
||||
|
||||
export default translate(CopyButton);
|
||||
export default CopyButton;
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
} from '@atlaskit/modal-dialog/dist/es2019/styled/Content';
|
||||
import React from 'react';
|
||||
|
||||
import { translate } from '../../../i18n';
|
||||
import { Icon, IconClose } from '../../../icons';
|
||||
|
||||
const TitleIcon = ({ appearance }: { appearance?: 'danger' | 'warning' }) => {
|
||||
|
@ -45,11 +46,40 @@ type Props = {
|
|||
* @class ModalHeader
|
||||
* @extends {React.Component<Props>}
|
||||
*/
|
||||
export default class ModalHeader extends React.Component<Props> {
|
||||
class ModalHeader extends React.Component<Props> {
|
||||
static defaultProps = {
|
||||
isHeadingMultiline: true
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes a new {@code ModalHeader} instance.
|
||||
*
|
||||
* @param {*} props - The read-only properties with which the new instance
|
||||
* is to be initialized.
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
// Bind event handler so it is only bound once for every instance.
|
||||
this._onKeyPress = this._onKeyPress.bind(this);
|
||||
}
|
||||
|
||||
_onKeyPress: (Object) => void;
|
||||
|
||||
/**
|
||||
* KeyPress handler for accessibility.
|
||||
*
|
||||
* @param {Object} e - The key event to handle.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onKeyPress(e) {
|
||||
if (this.props.onClose && (e.key === ' ' || e.key === 'Enter')) {
|
||||
e.preventDefault();
|
||||
this.props.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
|
@ -65,7 +95,8 @@ export default class ModalHeader extends React.Component<Props> {
|
|||
onClose,
|
||||
showKeyline,
|
||||
isHeadingMultiline,
|
||||
testId
|
||||
testId,
|
||||
t
|
||||
} = this.props;
|
||||
|
||||
if (!heading) {
|
||||
|
@ -83,12 +114,18 @@ export default class ModalHeader extends React.Component<Props> {
|
|||
{heading}
|
||||
</TitleText>
|
||||
</Title>
|
||||
|
||||
{
|
||||
!hideCloseIconButton && <Icon
|
||||
ariaLabel = { t('dialog.close') }
|
||||
onClick = { onClose }
|
||||
src = { IconClose } />
|
||||
onKeyPress = { this._onKeyPress }
|
||||
role = 'button'
|
||||
src = { IconClose }
|
||||
tabIndex = { 0 } />
|
||||
}
|
||||
</Header>
|
||||
);
|
||||
}
|
||||
}
|
||||
export default translate(ModalHeader);
|
||||
|
|
|
@ -118,7 +118,7 @@ class StatelessDialog extends Component<Props> {
|
|||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onCancel = this._onCancel.bind(this);
|
||||
this._onDialogDismissed = this._onDialogDismissed.bind(this);
|
||||
this._onKeyDown = this._onKeyDown.bind(this);
|
||||
this._onKeyPress = this._onKeyPress.bind(this);
|
||||
this._onSubmit = this._onSubmit.bind(this);
|
||||
this._renderFooter = this._renderFooter.bind(this);
|
||||
this._setDialogElement = this._setDialogElement.bind(this);
|
||||
|
@ -159,7 +159,7 @@ class StatelessDialog extends Component<Props> {
|
|||
shouldCloseOnEscapePress = { true }
|
||||
width = { width || 'medium' }>
|
||||
<div
|
||||
onKeyDown = { this._onKeyDown }
|
||||
onKeyPress = { this._onKeyPress }
|
||||
ref = { this._setDialogElement }>
|
||||
<form
|
||||
className = 'modal-dialog-form'
|
||||
|
@ -327,7 +327,7 @@ class StatelessDialog extends Component<Props> {
|
|||
this._dialogElement = element;
|
||||
}
|
||||
|
||||
_onKeyDown: (Object) => void;
|
||||
_onKeyPress: (Object) => void;
|
||||
|
||||
/**
|
||||
* Handles 'Enter' key in the dialog to submit/hide dialog depending on
|
||||
|
@ -337,7 +337,7 @@ class StatelessDialog extends Component<Props> {
|
|||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onKeyDown(event) {
|
||||
_onKeyPress(event) {
|
||||
// If the event coming to the dialog has been subject to preventDefault
|
||||
// we don't handle it here.
|
||||
if (event.defaultPrevented) {
|
||||
|
|
|
@ -33,6 +33,7 @@ export function checkChromeExtensionsInstalled(config: Object = {}) {
|
|||
const img = new Image();
|
||||
|
||||
img.src = `chrome-extension://${info.id}/${info.path}`;
|
||||
img.setAttribute('aria-hidden', 'true');
|
||||
img.onload = function() {
|
||||
resolve(true);
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import { Container } from '../../react/base';
|
||||
import { styleTypeToObject } from '../../styles';
|
||||
|
@ -10,7 +10,7 @@ type Props = {
|
|||
/**
|
||||
* Class name for the web platform, if any.
|
||||
*/
|
||||
className: string,
|
||||
className?: string,
|
||||
|
||||
/**
|
||||
* Color of the icon (if not provided by the style object).
|
||||
|
@ -22,6 +22,11 @@ type Props = {
|
|||
*/
|
||||
id?: string,
|
||||
|
||||
/**
|
||||
* Id of the icon container
|
||||
*/
|
||||
containerId?: string,
|
||||
|
||||
/**
|
||||
* Function to invoke on click.
|
||||
*/
|
||||
|
@ -40,8 +45,63 @@ type Props = {
|
|||
/**
|
||||
* Style object to be applied.
|
||||
*/
|
||||
style?: Object
|
||||
};
|
||||
style?: Object,
|
||||
|
||||
/**
|
||||
* aria disabled flag for the Icon.
|
||||
*/
|
||||
ariaDisabled?: boolean,
|
||||
|
||||
/**
|
||||
* aria label for the Icon.
|
||||
*/
|
||||
ariaLabel?: string,
|
||||
|
||||
/**
|
||||
* whether the element has a popup
|
||||
*/
|
||||
ariaHasPopup?: boolean,
|
||||
|
||||
/**
|
||||
* whether the element has a pressed
|
||||
*/
|
||||
ariaPressed?: boolean,
|
||||
|
||||
/**
|
||||
* id of description label
|
||||
*/
|
||||
ariaDescribedBy?: string,
|
||||
|
||||
/**
|
||||
* whether the element popup is expanded
|
||||
*/
|
||||
ariaExpanded?: boolean,
|
||||
|
||||
/**
|
||||
* The id of the element this button icon controls
|
||||
*/
|
||||
ariaControls?: string,
|
||||
|
||||
/**
|
||||
* tabIndex for the Icon.
|
||||
*/
|
||||
tabIndex?: number,
|
||||
|
||||
/**
|
||||
* role for the Icon.
|
||||
*/
|
||||
role?: string,
|
||||
|
||||
/**
|
||||
* keypress handler.
|
||||
*/
|
||||
onKeyPress?: Function,
|
||||
|
||||
/**
|
||||
* keydown handler.
|
||||
*/
|
||||
onKeyDown?: Function
|
||||
}
|
||||
|
||||
export const DEFAULT_COLOR = navigator.product === 'ReactNative' ? 'white' : undefined;
|
||||
export const DEFAULT_SIZE = navigator.product === 'ReactNative' ? 36 : 22;
|
||||
|
@ -57,11 +117,24 @@ export default function Icon(props: Props) {
|
|||
className,
|
||||
color,
|
||||
id,
|
||||
containerId,
|
||||
onClick,
|
||||
size,
|
||||
src: IconComponent,
|
||||
style
|
||||
} = props;
|
||||
style,
|
||||
ariaHasPopup,
|
||||
ariaLabel,
|
||||
ariaDisabled,
|
||||
ariaExpanded,
|
||||
ariaControls,
|
||||
tabIndex,
|
||||
ariaPressed,
|
||||
ariaDescribedBy,
|
||||
role,
|
||||
onKeyPress,
|
||||
onKeyDown,
|
||||
...rest
|
||||
}: Props = props;
|
||||
|
||||
const {
|
||||
color: styleColor,
|
||||
|
@ -71,11 +144,33 @@ export default function Icon(props: Props) {
|
|||
const calculatedColor = color ?? styleColor ?? DEFAULT_COLOR;
|
||||
const calculatedSize = size ?? styleSize ?? DEFAULT_SIZE;
|
||||
|
||||
const onKeyPressHandler = useCallback(e => {
|
||||
if ((e.key === 'Enter' || e.key === ' ') && onClick) {
|
||||
e.preventDefault();
|
||||
onClick(e);
|
||||
} else if (onKeyPress) {
|
||||
onKeyPress(e);
|
||||
}
|
||||
}, [ onClick, onKeyPress ]);
|
||||
|
||||
return (
|
||||
<Container
|
||||
className = { `jitsi-icon ${className}` }
|
||||
{ ...rest }
|
||||
aria-controls = { ariaControls }
|
||||
aria-describedby = { ariaDescribedBy }
|
||||
aria-disabled = { ariaDisabled }
|
||||
aria-expanded = { ariaExpanded }
|
||||
aria-haspopup = { ariaHasPopup }
|
||||
aria-label = { ariaLabel }
|
||||
aria-pressed = { ariaPressed }
|
||||
className = { `jitsi-icon ${className || ''}` }
|
||||
id = { containerId }
|
||||
onClick = { onClick }
|
||||
style = { restStyle }>
|
||||
onKeyDown = { onKeyDown }
|
||||
onKeyPress = { onKeyPressHandler }
|
||||
role = { role }
|
||||
style = { restStyle }
|
||||
tabIndex = { tabIndex }>
|
||||
<IconComponent
|
||||
fill = { calculatedColor }
|
||||
height = { calculatedSize }
|
||||
|
|
|
@ -129,7 +129,9 @@ class Popover extends Component<Props, State> {
|
|||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onHideDialog = this._onHideDialog.bind(this);
|
||||
this._onShowDialog = this._onShowDialog.bind(this);
|
||||
this._onKeyPress = this._onKeyPress.bind(this);
|
||||
this._drawerContainerRef = React.createRef();
|
||||
this._onEscKey = this._onEscKey.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -207,6 +209,7 @@ class Popover extends Component<Props, State> {
|
|||
<div
|
||||
className = { className }
|
||||
id = { id }
|
||||
onKeyPress = { this._onKeyPress }
|
||||
onMouseEnter = { this._onShowDialog }
|
||||
onMouseLeave = { this._onHideDialog }>
|
||||
<InlineDialog
|
||||
|
@ -231,13 +234,13 @@ class Popover extends Component<Props, State> {
|
|||
this.setState({ showDialog: false });
|
||||
}
|
||||
|
||||
_onShowDialog: () => void;
|
||||
_onShowDialog: (Object) => void;
|
||||
|
||||
/**
|
||||
* Displays the {@code InlineDialog} and calls any registered onPopoverOpen
|
||||
* callbacks.
|
||||
*
|
||||
* @param {MouseEvent} event - The mouse event to intercept.
|
||||
* @param {Object} event - The mouse event or the keypress event to intercept.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
|
@ -252,6 +255,45 @@ class Popover extends Component<Props, State> {
|
|||
}
|
||||
}
|
||||
|
||||
_onKeyPress: (Object) => void;
|
||||
|
||||
/**
|
||||
* KeyPress handler for accessibility.
|
||||
*
|
||||
* @param {Object} e - The key event to handle.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onKeyPress(e) {
|
||||
if (e.key === ' ' || e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
if (this.state.showDialog) {
|
||||
this._onHideDialog();
|
||||
} else {
|
||||
this._onShowDialog(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_onEscKey: (Object) => void;
|
||||
|
||||
/**
|
||||
* KeyPress handler for accessibility.
|
||||
*
|
||||
* @param {Object} e - The key event to handle.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onEscKey(e) {
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (this.state.showDialog) {
|
||||
this._onHideDialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the React Element to be displayed in the {@code InlineDialog}.
|
||||
* Also adds padding to support moving the mouse from the trigger to the
|
||||
|
@ -264,7 +306,9 @@ class Popover extends Component<Props, State> {
|
|||
const { content, position } = this.props;
|
||||
|
||||
return (
|
||||
<div className = 'popover'>
|
||||
<div
|
||||
className = 'popover'
|
||||
onKeyDown = { this._onEscKey }>
|
||||
{ content }
|
||||
<div className = 'popover-mouse-padding-top' />
|
||||
<div className = { _mapPositionToPaddingClass(position) } />
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import { Icon, IconArrowDown } from '../../../icons';
|
||||
|
||||
|
@ -46,10 +46,36 @@ type Props = {
|
|||
*/
|
||||
onClick: Function,
|
||||
|
||||
|
||||
/**
|
||||
* Click handler for options.
|
||||
*/
|
||||
onOptionsClick?: Function
|
||||
onOptionsClick?: Function,
|
||||
|
||||
/**
|
||||
* to navigate with the keyboard.
|
||||
*/
|
||||
tabIndex?: number,
|
||||
|
||||
/**
|
||||
* to give a role to the icon.
|
||||
*/
|
||||
role?: string,
|
||||
|
||||
/**
|
||||
* to give a aria-pressed to the icon.
|
||||
*/
|
||||
ariaPressed?: boolean,
|
||||
|
||||
/**
|
||||
* The Label of the current element
|
||||
*/
|
||||
ariaLabel?: string,
|
||||
|
||||
/**
|
||||
* The Label of the child element
|
||||
*/
|
||||
ariaDropDownLabel?: string
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -66,23 +92,57 @@ function ActionButton({
|
|||
testId,
|
||||
type = 'primary',
|
||||
onClick,
|
||||
onOptionsClick
|
||||
onOptionsClick,
|
||||
tabIndex,
|
||||
role,
|
||||
ariaPressed,
|
||||
ariaLabel,
|
||||
ariaDropDownLabel
|
||||
}: Props) {
|
||||
|
||||
const onKeyPressHandler = useCallback(e => {
|
||||
if (onClick && !disabled && (e.key === ' ' || e.key === 'Enter')) {
|
||||
e.preventDefault();
|
||||
onClick(e);
|
||||
}
|
||||
}, [ onClick, disabled ]);
|
||||
|
||||
const onOptionsKeyPressHandler = useCallback(e => {
|
||||
if (onOptionsClick && !disabled && (e.key === ' ' || e.key === 'Enter')) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onOptionsClick(e);
|
||||
}
|
||||
}, [ onOptionsClick, disabled ]);
|
||||
|
||||
return (
|
||||
<div
|
||||
aria-disabled = { disabled }
|
||||
aria-label = { ariaLabel }
|
||||
className = { `action-btn ${className} ${type} ${disabled ? 'disabled' : ''}` }
|
||||
data-testid = { testId ? testId : undefined }
|
||||
onClick = { disabled ? undefined : onClick }>
|
||||
onClick = { disabled ? undefined : onClick }
|
||||
onKeyPress = { onKeyPressHandler }
|
||||
role = 'button'
|
||||
tabIndex = { 0 } >
|
||||
{children}
|
||||
{hasOptions && <div
|
||||
className = 'options'
|
||||
data-testid = 'prejoin.joinOptions'
|
||||
onClick = { disabled ? undefined : onOptionsClick }>
|
||||
<Icon
|
||||
className = 'icon'
|
||||
size = { 14 }
|
||||
src = { OptionsIcon } />
|
||||
</div>
|
||||
{ hasOptions
|
||||
&& <div
|
||||
aria-disabled = { disabled }
|
||||
aria-haspopup = 'true'
|
||||
aria-label = { ariaDropDownLabel }
|
||||
aria-pressed = { ariaPressed }
|
||||
className = 'options'
|
||||
data-testid = 'prejoin.joinOptions'
|
||||
onClick = { disabled ? undefined : onOptionsClick }
|
||||
onKeyPress = { onOptionsKeyPressHandler }
|
||||
role = { role }
|
||||
tabIndex = { tabIndex }>
|
||||
<Icon
|
||||
className = 'icon'
|
||||
size = { 14 }
|
||||
src = { OptionsIcon } />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// @flow
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
import { translate } from '../../../i18n';
|
||||
import { Icon, IconArrowDownSmall, IconWifi1Bar, IconWifi2Bars, IconWifi3Bars } from '../../../icons';
|
||||
|
@ -65,24 +65,50 @@ function ConnectionStatus({ connectionDetails, t, connectionType }: Props) {
|
|||
? 'con-status-details-visible'
|
||||
: 'con-status-details-hidden';
|
||||
|
||||
const onToggleDetails = useCallback(e => {
|
||||
e.preventDefault();
|
||||
toggleDetails(!showDetails);
|
||||
}, [ showDetails, toggleDetails ]);
|
||||
|
||||
const onKeyPressToggleDetails = useCallback(e => {
|
||||
if (toggleDetails && (e.key === ' ' || e.key === 'Enter')) {
|
||||
e.preventDefault();
|
||||
toggleDetails(!showDetails);
|
||||
}
|
||||
}, [ showDetails, toggleDetails ]);
|
||||
|
||||
return (
|
||||
<div className = 'con-status'>
|
||||
<div className = 'con-status-container'>
|
||||
<div className = 'con-status-header'>
|
||||
<div
|
||||
aria-level = { 1 }
|
||||
className = 'con-status-header'
|
||||
role = 'heading'>
|
||||
<div className = { `con-status-circle ${connectionClass}` }>
|
||||
<Icon
|
||||
size = { 16 }
|
||||
src = { icon } />
|
||||
</div>
|
||||
<span className = 'con-status-text'>{t(connectionText)}</span>
|
||||
<span
|
||||
aria-hidden = { !showDetails }
|
||||
className = 'con-status-text'
|
||||
id = 'connection-status-description'>{t(connectionText)}</span>
|
||||
<Icon
|
||||
ariaDescribedBy = 'connection-status-description'
|
||||
ariaPressed = { showDetails }
|
||||
className = { arrowClassName }
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick = { () => toggleDetails(!showDetails) }
|
||||
onClick = { onToggleDetails }
|
||||
onKeyPress = { onKeyPressToggleDetails }
|
||||
role = 'button'
|
||||
size = { 24 }
|
||||
src = { IconArrowDownSmall } />
|
||||
src = { IconArrowDownSmall }
|
||||
tabIndex = { 0 } />
|
||||
</div>
|
||||
<div className = { `con-status-details ${detailsClassName}` }>{detailsText}</div>
|
||||
<div
|
||||
aria-level = '2'
|
||||
className = { `con-status-details ${detailsClassName}` }
|
||||
role = 'heading'>
|
||||
{detailsText}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import CopyMeetingLinkSection
|
||||
from '../../../../invite/components/add-people-dialog/web/CopyMeetingLinkSection';
|
||||
import { getCurrentConferenceUrl } from '../../../connection';
|
||||
import { translate } from '../../../i18n';
|
||||
import { Icon, IconCopy, IconCheck } from '../../../icons';
|
||||
import { connect } from '../../../redux';
|
||||
import { copyText, getDecodedURI } from '../../../util';
|
||||
|
||||
type Props = {
|
||||
|
||||
|
@ -27,152 +27,11 @@ type Props = {
|
|||
_enableAutomaticUrlCopy: boolean,
|
||||
};
|
||||
|
||||
type State = {
|
||||
|
||||
/**
|
||||
* If true it shows the 'copy link' message.
|
||||
*/
|
||||
showCopyLink: boolean,
|
||||
|
||||
/**
|
||||
* If true it shows the 'link copied' message.
|
||||
*/
|
||||
showLinkCopied: boolean,
|
||||
};
|
||||
|
||||
const COPY_TIMEOUT = 2000;
|
||||
|
||||
/**
|
||||
* Component used to copy meeting url on prejoin page.
|
||||
*/
|
||||
class CopyMeetingUrl extends Component<Props, State> {
|
||||
|
||||
/**
|
||||
* Initializes a new {@code Prejoin} instance.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
showCopyLink: false,
|
||||
showLinkCopied: false
|
||||
};
|
||||
this._copyUrl = this._copyUrl.bind(this);
|
||||
this._hideCopyLink = this._hideCopyLink.bind(this);
|
||||
this._hideLinkCopied = this._hideLinkCopied.bind(this);
|
||||
this._showCopyLink = this._showCopyLink.bind(this);
|
||||
this._showLinkCopied = this._showLinkCopied.bind(this);
|
||||
this._copyUrlAutomatically = this._copyUrlAutomatically.bind(this);
|
||||
}
|
||||
|
||||
_copyUrl: () => void;
|
||||
|
||||
/**
|
||||
* Callback invoked to copy the url to clipboard.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
async _copyUrl() {
|
||||
const success = await copyText(this.props.url);
|
||||
|
||||
if (success) {
|
||||
this._showLinkCopied();
|
||||
window.setTimeout(this._hideLinkCopied, COPY_TIMEOUT);
|
||||
}
|
||||
}
|
||||
|
||||
_hideLinkCopied: () => void;
|
||||
|
||||
/**
|
||||
* Hides the 'Link copied' message.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_hideLinkCopied() {
|
||||
this.setState({
|
||||
showLinkCopied: false
|
||||
});
|
||||
}
|
||||
|
||||
_hideCopyLink: () => void;
|
||||
|
||||
/**
|
||||
* Hides the 'Copy link' text.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_hideCopyLink() {
|
||||
this.setState({
|
||||
showCopyLink: false,
|
||||
showLinkCopied: false
|
||||
});
|
||||
}
|
||||
|
||||
_showCopyLink: () => void;
|
||||
|
||||
/**
|
||||
* Shows the dark 'Copy link' text on hover.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_showCopyLink() {
|
||||
this.setState({
|
||||
showCopyLink: true,
|
||||
showLinkCopied: false
|
||||
});
|
||||
}
|
||||
|
||||
_showLinkCopied: () => void;
|
||||
|
||||
/**
|
||||
* Shows the green 'Link copied' message.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_showLinkCopied() {
|
||||
this.setState({
|
||||
showLinkCopied: true,
|
||||
showCopyLink: false
|
||||
});
|
||||
}
|
||||
|
||||
_copyUrlAutomatically: () => void;
|
||||
|
||||
/**
|
||||
* Attempts to automatically copy invitation URL.
|
||||
* Document has to be focused in order for this to work.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
async _copyUrlAutomatically() {
|
||||
const isCopied = await copyText(this.props.url);
|
||||
|
||||
if (isCopied) {
|
||||
this._showLinkCopied();
|
||||
window.setTimeout(this._hideLinkCopied, COPY_TIMEOUT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#componentDidMount()}. Invoked
|
||||
* immediately before mounting occurs.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
componentDidMount() {
|
||||
const { _enableAutomaticUrlCopy } = this.props;
|
||||
|
||||
if (_enableAutomaticUrlCopy) {
|
||||
setTimeout(this._copyUrlAutomatically, 2000);
|
||||
}
|
||||
}
|
||||
class CopyMeetingUrl extends Component<Props> {
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
|
@ -181,29 +40,9 @@ class CopyMeetingUrl extends Component<Props, State> {
|
|||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { showCopyLink, showLinkCopied } = this.state;
|
||||
const { url, t } = this.props;
|
||||
const { _copyUrl, _showCopyLink, _hideCopyLink } = this;
|
||||
const src = showLinkCopied ? IconCheck : IconCopy;
|
||||
|
||||
return (
|
||||
<div
|
||||
className = 'copy-meeting'
|
||||
onMouseEnter = { _showCopyLink }
|
||||
onMouseLeave = { _hideCopyLink }>
|
||||
<div
|
||||
className = { `url ${showLinkCopied ? 'done' : ''}` }
|
||||
onClick = { _copyUrl } >
|
||||
<div className = 'copy-meeting-text'>
|
||||
{ !showCopyLink && !showLinkCopied && getDecodedURI(url) }
|
||||
{ showCopyLink && t('prejoin.copyAndShare') }
|
||||
{ showLinkCopied && t('prejoin.linkCopied') }
|
||||
</div>
|
||||
<Icon
|
||||
onClick = { _copyUrl }
|
||||
size = { 24 }
|
||||
src = { src } />
|
||||
</div>
|
||||
<div className = 'copy-meeting'>
|
||||
<CopyMeetingLinkSection url = { this.props.url } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -44,7 +44,9 @@ type Props = {
|
|||
/**
|
||||
* Externally provided value.
|
||||
*/
|
||||
value?: string
|
||||
value?: string,
|
||||
id?: string,
|
||||
autoComplete?: string
|
||||
};
|
||||
|
||||
type State = {
|
||||
|
@ -114,9 +116,11 @@ export default class InputField extends PureComponent<Props, State> {
|
|||
render() {
|
||||
return (
|
||||
<input
|
||||
autoComplete = { this.props.autoComplete }
|
||||
autoFocus = { this.props.autoFocus }
|
||||
className = { `field ${this.state.focused ? 'focused' : ''} ${this.props.className || ''}` }
|
||||
data-testid = { this.props.testId ? this.props.testId : undefined }
|
||||
id = { this.props.id }
|
||||
onBlur = { this._onBlur }
|
||||
onChange = { this._onChange }
|
||||
onFocus = { this._onFocus }
|
||||
|
|
|
@ -108,9 +108,9 @@ export default class PreMeetingScreen extends PureComponent<Props> {
|
|||
)}
|
||||
{showConferenceInfo && (
|
||||
<>
|
||||
<div className = 'title'>
|
||||
<h1 className = 'title'>
|
||||
{ title }
|
||||
</div>
|
||||
</h1>
|
||||
{showSharingButton ? <CopyMeetingUrl /> : null}
|
||||
</>
|
||||
)}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import { Icon, IconCheck } from '../../../icons';
|
||||
|
||||
|
@ -32,10 +32,22 @@ type Props = {
|
|||
function ToggleButton({ children, isToggled, onClick }: Props) {
|
||||
const className = isToggled ? `${mainClass} ${mainClass}--toggled` : mainClass;
|
||||
|
||||
const onKeyPressHandler = useCallback(e => {
|
||||
if (onClick && (e.key === ' ')) {
|
||||
e.preventDefault();
|
||||
onClick();
|
||||
}
|
||||
}, [ onClick ]);
|
||||
|
||||
return (
|
||||
<div
|
||||
aria-checked = { isToggled }
|
||||
className = { className }
|
||||
onClick = { onClick }>
|
||||
id = 'toggle-button'
|
||||
onClick = { onClick }
|
||||
onKeyPress = { onKeyPressHandler }
|
||||
role = 'switch'
|
||||
tabIndex = { 0 }>
|
||||
<div className = 'toggle-button-container'>
|
||||
<div className = 'toggle-button-icon-container'>
|
||||
<Icon
|
||||
|
@ -43,7 +55,7 @@ function ToggleButton({ children, isToggled, onClick }: Props) {
|
|||
size = { 10 }
|
||||
src = { IconCheck } />
|
||||
</div>
|
||||
<span>{children}</span>
|
||||
<label htmlFor = 'toggle-button'>{children}</label>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -4,7 +4,8 @@ import React, { Component } from 'react';
|
|||
|
||||
import {
|
||||
getLocalizedDateFormatter,
|
||||
getLocalizedDurationFormatter
|
||||
getLocalizedDurationFormatter,
|
||||
translate
|
||||
} from '../../../i18n';
|
||||
import { Icon, IconTrash } from '../../../icons';
|
||||
|
||||
|
@ -41,7 +42,12 @@ type Props = {
|
|||
/**
|
||||
* Handler for deleting an item.
|
||||
*/
|
||||
onItemDelete?: Function
|
||||
onItemDelete?: Function,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -80,7 +86,7 @@ function _toTimeString(times) {
|
|||
*
|
||||
* @extends Component
|
||||
*/
|
||||
export default class MeetingsList extends Component<Props> {
|
||||
class MeetingsList extends Component<Props> {
|
||||
/**
|
||||
* Constructor of the MeetingsList component.
|
||||
*
|
||||
|
@ -99,7 +105,7 @@ export default class MeetingsList extends Component<Props> {
|
|||
* @returns {React.ReactNode}
|
||||
*/
|
||||
render() {
|
||||
const { listEmptyComponent, meetings } = this.props;
|
||||
const { listEmptyComponent, meetings, t } = this.props;
|
||||
|
||||
/**
|
||||
* If there are no recent meetings we don't want to display anything
|
||||
|
@ -107,7 +113,10 @@ export default class MeetingsList extends Component<Props> {
|
|||
if (meetings) {
|
||||
return (
|
||||
<Container
|
||||
className = 'meetings-list'>
|
||||
aria-label = { t('welcomepage.recentList') }
|
||||
className = 'meetings-list'
|
||||
role = 'menu'
|
||||
tabIndex = '-1'>
|
||||
{
|
||||
meetings.length === 0
|
||||
? listEmptyComponent
|
||||
|
@ -139,6 +148,29 @@ export default class MeetingsList extends Component<Props> {
|
|||
return null;
|
||||
}
|
||||
|
||||
_onKeyPress: string => Function;
|
||||
|
||||
/**
|
||||
* Returns a function that is used in the onPress callback of the items.
|
||||
*
|
||||
* @param {string} url - The URL of the item to navigate to.
|
||||
* @private
|
||||
* @returns {Function}
|
||||
*/
|
||||
_onKeyPress(url) {
|
||||
const { disabled, onPress } = this.props;
|
||||
|
||||
if (!disabled && url && typeof onPress === 'function') {
|
||||
return e => {
|
||||
if (e.key === ' ' || e.key === 'Enter') {
|
||||
onPress(url);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
_onDelete: Object => Function;
|
||||
|
||||
/**
|
||||
|
@ -158,6 +190,27 @@ export default class MeetingsList extends Component<Props> {
|
|||
};
|
||||
}
|
||||
|
||||
_onDeleteKeyPress: Object => Function;
|
||||
|
||||
/**
|
||||
* Returns a function that is used on the onDelete keypress callback.
|
||||
*
|
||||
* @param {Object} item - The item to be deleted.
|
||||
* @private
|
||||
* @returns {Function}
|
||||
*/
|
||||
_onDeleteKeyPress(item) {
|
||||
const { onItemDelete } = this.props;
|
||||
|
||||
return e => {
|
||||
if (onItemDelete && (e.key === ' ' || e.key === 'Enter')) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onItemDelete(item);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
_renderItem: (Object, number) => React$Node;
|
||||
|
||||
/**
|
||||
|
@ -176,17 +229,22 @@ export default class MeetingsList extends Component<Props> {
|
|||
title,
|
||||
url
|
||||
} = meeting;
|
||||
const { hideURL = false, onItemDelete } = this.props;
|
||||
const { hideURL = false, onItemDelete, t } = this.props;
|
||||
const onPress = this._onPress(url);
|
||||
const onKeyPress = this._onKeyPress(url);
|
||||
const rootClassName
|
||||
= `item ${
|
||||
onPress ? 'with-click-handler' : 'without-click-handler'}`;
|
||||
|
||||
return (
|
||||
<Container
|
||||
aria-label = { title }
|
||||
className = { rootClassName }
|
||||
key = { index }
|
||||
onClick = { onPress }>
|
||||
onClick = { onPress }
|
||||
onKeyPress = { onKeyPress }
|
||||
role = 'menuitem'
|
||||
tabIndex = { 0 }>
|
||||
<Container className = 'left-column'>
|
||||
<Text className = 'title'>
|
||||
{ _toDateString(date) }
|
||||
|
@ -216,11 +274,17 @@ export default class MeetingsList extends Component<Props> {
|
|||
{ elementAfter || null }
|
||||
|
||||
{ onItemDelete && <Icon
|
||||
ariaLabel = { t('welcomepage.recentListDelete') }
|
||||
className = 'delete-meeting'
|
||||
onClick = { this._onDelete(meeting) }
|
||||
src = { IconTrash } />}
|
||||
onKeyPress = { this._onDeleteKeyPress(meeting) }
|
||||
role = 'button'
|
||||
src = { IconTrash }
|
||||
tabIndex = { 0 } />}
|
||||
</Container>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(MeetingsList);
|
||||
|
|
|
@ -160,13 +160,15 @@ class Watermarks extends Component<Props, State> {
|
|||
_logoUrl,
|
||||
_showJitsiWatermark
|
||||
} = this.props;
|
||||
const { t } = this.props;
|
||||
let reactElement = null;
|
||||
|
||||
if (_showJitsiWatermark) {
|
||||
const style = {
|
||||
backgroundImage: `url(${_logoUrl})`,
|
||||
maxWidth: 140,
|
||||
maxHeight: 70
|
||||
maxHeight: 70,
|
||||
position: _logoLink ? 'static' : 'absolute'
|
||||
};
|
||||
|
||||
reactElement = (<div
|
||||
|
@ -176,6 +178,8 @@ class Watermarks extends Component<Props, State> {
|
|||
if (_logoLink) {
|
||||
reactElement = (
|
||||
<a
|
||||
aria-label = { t('jitsiHome', { logo: interfaceConfig.APP_NAME }) }
|
||||
className = 'watermark leftwatermark'
|
||||
href = { _logoLink }
|
||||
target = '_new'>
|
||||
{ reactElement }
|
||||
|
|
|
@ -246,14 +246,18 @@ export default class AbstractButton<P: Props, S: *> extends Component<P, S> {
|
|||
* Handles clicking / pressing the button, and toggles the audio mute state
|
||||
* accordingly.
|
||||
*
|
||||
* @param {Object} e - Event.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onClick() {
|
||||
_onClick(e) {
|
||||
const { afterClick } = this.props;
|
||||
|
||||
this._handleClick();
|
||||
afterClick && afterClick();
|
||||
afterClick && afterClick(e);
|
||||
|
||||
// blur after click to release focus from button to allow PTT.
|
||||
e && e.currentTarget && e.currentTarget.blur();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,6 +6,10 @@ import { translate } from '../../i18n';
|
|||
import { Container, Text } from '../../react';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
|
|
|
@ -20,29 +20,21 @@ export default class ToolboxItem extends AbstractToolboxItem<Props> {
|
|||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this._onKeyDown = this._onKeyDown.bind(this);
|
||||
this._onKeyPress = this._onKeyPress.bind(this);
|
||||
}
|
||||
|
||||
_onKeyDown: (Object) => void;
|
||||
_onKeyPress: (Object) => void;
|
||||
|
||||
/**
|
||||
* Handles 'Enter' key on the button to trigger onClick for accessibility.
|
||||
* We should be handling Space onKeyUp but it conflicts with PTT.
|
||||
* Handles 'Enter' and Space key on the button to trigger onClick for accessibility.
|
||||
*
|
||||
* @param {Object} event - The key event.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onKeyDown(event) {
|
||||
// If the event coming to the dialog has been subject to preventDefault
|
||||
// we don't handle it here.
|
||||
if (event.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === 'Enter') {
|
||||
_onKeyPress(event) {
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.props.onClick();
|
||||
}
|
||||
}
|
||||
|
@ -72,9 +64,9 @@ export default class ToolboxItem extends AbstractToolboxItem<Props> {
|
|||
'aria-label': this.accessibilityLabel,
|
||||
className: className + (disabled ? ' disabled' : ''),
|
||||
onClick: disabled ? undefined : onClick,
|
||||
onKeyDown: this._onKeyDown,
|
||||
onKeyPress: this._onKeyPress,
|
||||
tabIndex: 0,
|
||||
role: 'button'
|
||||
role: showLabel ? 'menuitem' : 'button'
|
||||
};
|
||||
|
||||
const elementType = showLabel ? 'li' : 'div';
|
||||
|
|
|
@ -74,6 +74,35 @@ class OverflowMenuItem extends Component<Props> {
|
|||
disabled: false
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes a new {@code OverflowMenuItem} instance.
|
||||
*
|
||||
* @param {*} props - The read-only properties with which the new instance
|
||||
* is to be initialized.
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
// Bind event handler so it is only bound once for every instance.
|
||||
this._onKeyPress = this._onKeyPress.bind(this);
|
||||
}
|
||||
|
||||
_onKeyPress: (Object) => void;
|
||||
|
||||
/**
|
||||
* KeyPress handler for accessibility.
|
||||
*
|
||||
* @param {Object} e - The key event to handle.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onKeyPress(e) {
|
||||
if (!this.props.disabled && this.props.onClick && (e.key === ' ' || e.key === 'Enter')) {
|
||||
e.preventDefault();
|
||||
this.props.onClick();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
|
@ -89,9 +118,13 @@ class OverflowMenuItem extends Component<Props> {
|
|||
|
||||
return (
|
||||
<li
|
||||
aria-disabled = { disabled }
|
||||
aria-label = { accessibilityLabel }
|
||||
className = { className }
|
||||
onClick = { disabled ? null : onClick }>
|
||||
onClick = { disabled ? null : onClick }
|
||||
onKeyPress = { this._onKeyPress }
|
||||
role = 'menuitem'
|
||||
tabIndex = { 0 }>
|
||||
<span className = 'overflow-menu-item-icon'>
|
||||
<Icon
|
||||
id = { iconId }
|
||||
|
|
|
@ -36,6 +36,36 @@ type Props = {
|
|||
* Additional styles.
|
||||
*/
|
||||
styles?: Object,
|
||||
|
||||
/**
|
||||
* aria label for the Icon.
|
||||
*/
|
||||
ariaLabel?: string,
|
||||
|
||||
/**
|
||||
* whether the element has a popup
|
||||
*/
|
||||
ariaHasPopup?: boolean,
|
||||
|
||||
/**
|
||||
* whether the element popup is expanded
|
||||
*/
|
||||
ariaExpanded?: boolean,
|
||||
|
||||
/**
|
||||
* The id of the element this button icon controls
|
||||
*/
|
||||
ariaControls?: string,
|
||||
|
||||
/**
|
||||
* keydown handler for icon.
|
||||
*/
|
||||
onIconKeyDown?: Function,
|
||||
|
||||
/**
|
||||
* The ID of the icon button
|
||||
*/
|
||||
iconId: string
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -51,7 +81,13 @@ export default function ToolboxButtonWithIcon(props: Props) {
|
|||
iconDisabled,
|
||||
iconTooltip,
|
||||
onIconClick,
|
||||
styles
|
||||
onIconKeyDown,
|
||||
styles,
|
||||
ariaLabel,
|
||||
ariaHasPopup,
|
||||
ariaControls,
|
||||
ariaExpanded,
|
||||
iconId
|
||||
} = props;
|
||||
|
||||
const iconProps = {};
|
||||
|
@ -62,6 +98,12 @@ export default function ToolboxButtonWithIcon(props: Props) {
|
|||
} else {
|
||||
iconProps.className = 'settings-button-small-icon';
|
||||
iconProps.onClick = onIconClick;
|
||||
iconProps.onKeyDown = onIconKeyDown;
|
||||
iconProps.role = 'button';
|
||||
iconProps.tabIndex = 0;
|
||||
iconProps.ariaControls = ariaControls;
|
||||
iconProps.ariaExpanded = ariaExpanded;
|
||||
iconProps.containerId = iconId;
|
||||
}
|
||||
|
||||
|
||||
|
@ -77,6 +119,8 @@ export default function ToolboxButtonWithIcon(props: Props) {
|
|||
position = 'top'>
|
||||
<Icon
|
||||
{ ...iconProps }
|
||||
ariaHasPopup = { ariaHasPopup }
|
||||
ariaLabel = { ariaLabel }
|
||||
size = { 9 }
|
||||
src = { icon } />
|
||||
</Tooltip>
|
||||
|
|
|
@ -19,29 +19,21 @@ export default class ToolboxItem extends AbstractToolboxItem<Props> {
|
|||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this._onKeyDown = this._onKeyDown.bind(this);
|
||||
this._onKeyPress = this._onKeyPress.bind(this);
|
||||
}
|
||||
|
||||
_onKeyDown: (Object) => void;
|
||||
_onKeyPress: (Object) => void;
|
||||
|
||||
/**
|
||||
* Handles 'Enter' key on the button to trigger onClick for accessibility.
|
||||
* We should be handling Space onKeyUp but it conflicts with PTT.
|
||||
* Handles 'Enter' and Space key on the button to trigger onClick for accessibility.
|
||||
*
|
||||
* @param {Object} event - The key event.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onKeyDown(event) {
|
||||
// If the event coming to the dialog has been subject to preventDefault
|
||||
// we don't handle it here.
|
||||
if (event.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === 'Enter') {
|
||||
_onKeyPress(event) {
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.props.onClick();
|
||||
}
|
||||
}
|
||||
|
@ -71,9 +63,9 @@ export default class ToolboxItem extends AbstractToolboxItem<Props> {
|
|||
'aria-label': this.accessibilityLabel,
|
||||
className: className + (disabled ? ' disabled' : ''),
|
||||
onClick: disabled ? undefined : onClick,
|
||||
onKeyDown: this._onKeyDown,
|
||||
onKeyPress: this._onKeyPress,
|
||||
tabIndex: 0,
|
||||
role: 'button'
|
||||
role: showLabel ? 'menuitem' : 'button'
|
||||
};
|
||||
|
||||
const elementType = showLabel ? 'li' : 'div';
|
||||
|
|
|
@ -55,6 +55,7 @@ class AddMeetingUrlButton extends Component<Props> {
|
|||
|
||||
// Bind event handler so it is only bound once for every instance.
|
||||
this._onClick = this._onClick.bind(this);
|
||||
this._onKeyPress = this._onKeyPress.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -67,7 +68,9 @@ class AddMeetingUrlButton extends Component<Props> {
|
|||
<Tooltip content = { this.props.t('calendarSync.addMeetingURL') }>
|
||||
<div
|
||||
className = 'button add-button'
|
||||
onClick = { this._onClick }>
|
||||
onClick = { this._onClick }
|
||||
onKeyPress = { this._onKeyPress }
|
||||
role = 'button'>
|
||||
<Icon src = { IconAdd } />
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
@ -88,6 +91,22 @@ class AddMeetingUrlButton extends Component<Props> {
|
|||
|
||||
dispatch(updateCalendarEvent(eventId, calendarId));
|
||||
}
|
||||
|
||||
_onKeyPress: (Object) => void;
|
||||
|
||||
/**
|
||||
* KeyPress handler for accessibility.
|
||||
*
|
||||
* @param {Object} e - The key event to handle.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onKeyPress(e) {
|
||||
if (e.key === ' ' || e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
this._onClick();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(connect()(AddMeetingUrlButton));
|
||||
|
|
|
@ -72,6 +72,7 @@ class CalendarList extends AbstractPage<Props> {
|
|||
this._getRenderListEmptyComponent
|
||||
= this._getRenderListEmptyComponent.bind(this);
|
||||
this._onOpenSettings = this._onOpenSettings.bind(this);
|
||||
this._onKeyPressOpenSettings = this._onKeyPressOpenSettings.bind(this);
|
||||
this._onRefreshEvents = this._onRefreshEvents.bind(this);
|
||||
}
|
||||
|
||||
|
@ -187,7 +188,9 @@ class CalendarList extends AbstractPage<Props> {
|
|||
return (
|
||||
<div className = 'meetings-list-empty'>
|
||||
<div className = 'meetings-list-empty-image'>
|
||||
<img src = './images/calendar.svg' />
|
||||
<img
|
||||
alt = { t('welcomepage.logo.calendar') }
|
||||
src = './images/calendar.svg' />
|
||||
</div>
|
||||
<div className = 'description'>
|
||||
{ t('welcomepage.connectCalendarText', {
|
||||
|
@ -197,7 +200,9 @@ class CalendarList extends AbstractPage<Props> {
|
|||
</div>
|
||||
<div
|
||||
className = 'meetings-list-empty-button'
|
||||
onClick = { this._onOpenSettings }>
|
||||
onClick = { this._onOpenSettings }
|
||||
onKeyPress = { this._onKeyPressOpenSettings }
|
||||
role = 'button'>
|
||||
<Icon
|
||||
className = 'meetings-list-empty-icon'
|
||||
src = { IconPlusCalendar } />
|
||||
|
@ -221,6 +226,22 @@ class CalendarList extends AbstractPage<Props> {
|
|||
this.props.dispatch(openSettingsDialog(SETTINGS_TABS.CALENDAR));
|
||||
}
|
||||
|
||||
_onKeyPressOpenSettings: (Object) => void;
|
||||
|
||||
/**
|
||||
* KeyPress handler for accessibility.
|
||||
*
|
||||
* @param {Object} e - The key event to handle.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onKeyPressOpenSettings(e) {
|
||||
if (e.key === ' ' || e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
this._onOpenSettings();
|
||||
}
|
||||
}
|
||||
|
||||
_onRefreshEvents: () => void;
|
||||
|
||||
|
||||
|
|
|
@ -45,6 +45,7 @@ class JoinButton extends Component<Props> {
|
|||
|
||||
// Bind event handler so it is only bound once for every instance.
|
||||
this._onClick = this._onClick.bind(this);
|
||||
this._onKeyPress = this._onKeyPress.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -60,7 +61,9 @@ class JoinButton extends Component<Props> {
|
|||
content = { t('calendarSync.joinTooltip') }>
|
||||
<div
|
||||
className = 'button join-button'
|
||||
onClick = { this._onClick }>
|
||||
onClick = { this._onClick }
|
||||
onKeyPress = { this._onKeyPress }
|
||||
role = 'button'>
|
||||
<Icon
|
||||
size = '14'
|
||||
src = { IconAdd } />
|
||||
|
@ -81,6 +84,22 @@ class JoinButton extends Component<Props> {
|
|||
_onClick(event) {
|
||||
this.props.onPress(event, this.props.url);
|
||||
}
|
||||
|
||||
_onKeyPress: (Object) => void;
|
||||
|
||||
/**
|
||||
* KeyPress handler for accessibility.
|
||||
*
|
||||
* @param {Object} e - The key event to handle.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onKeyPress(e) {
|
||||
if (e.key === ' ' || e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
this._onClick();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(JoinButton);
|
||||
|
|
|
@ -2,17 +2,29 @@
|
|||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { translate } from '../../base/i18n';
|
||||
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of
|
||||
* {@link MicrosoftSignInButton}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
// The callback to invoke when {@code MicrosoftSignInButton} is clicked.
|
||||
/**
|
||||
* The callback to invoke when {@code MicrosoftSignInButton} is clicked.
|
||||
*/
|
||||
onClick: Function,
|
||||
|
||||
// The text to display within {@code MicrosoftSignInButton}.
|
||||
text: string
|
||||
/**
|
||||
* The text to display within {@code MicrosoftSignInButton}.
|
||||
*/
|
||||
text: string,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -20,7 +32,7 @@ type Props = {
|
|||
*
|
||||
* @extends Component
|
||||
*/
|
||||
export default class MicrosoftSignInButton extends Component<Props> {
|
||||
class MicrosoftSignInButton extends Component<Props> {
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
|
@ -33,6 +45,7 @@ export default class MicrosoftSignInButton extends Component<Props> {
|
|||
className = 'microsoft-sign-in'
|
||||
onClick = { this.props.onClick }>
|
||||
<img
|
||||
alt = { this.props.t('welcomepage.logo.microsoftLogo') }
|
||||
className = 'microsoft-logo'
|
||||
src = 'images/microsoftLogo.svg' />
|
||||
<div className = 'microsoft-cta'>
|
||||
|
@ -42,3 +55,5 @@ export default class MicrosoftSignInButton extends Component<Props> {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(MicrosoftSignInButton);
|
||||
|
|
|
@ -4,6 +4,7 @@ import React from 'react';
|
|||
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { toggleChat } from '../../actions.web';
|
||||
import AbstractChat, {
|
||||
_mapStateToProps,
|
||||
type Props
|
||||
|
@ -51,6 +52,8 @@ class Chat extends AbstractChat<Props> {
|
|||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._renderPanelContent = this._renderPanelContent.bind(this);
|
||||
this._onChatInputResize = this._onChatInputResize.bind(this);
|
||||
this._onEscClick = this._onEscClick.bind(this);
|
||||
this._onToggleChat = this._onToggleChat.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -74,6 +77,21 @@ class Chat extends AbstractChat<Props> {
|
|||
this._scrollMessageContainerToBottom(false);
|
||||
}
|
||||
}
|
||||
_onEscClick: (KeyboardEvent) => void;
|
||||
|
||||
/**
|
||||
* Click handler for the chat sidenav.
|
||||
*
|
||||
* @param {KeyboardEvent} event - Esc key click to close the popup.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onEscClick(event) {
|
||||
if (event.key === 'Escape' && this.props._isOpen) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this._onToggleChat();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
|
@ -135,7 +153,10 @@ class Chat extends AbstractChat<Props> {
|
|||
*/
|
||||
_renderChatHeader() {
|
||||
return (
|
||||
<Header className = 'chat-header' />
|
||||
<Header
|
||||
className = 'chat-header'
|
||||
id = 'chat-header'
|
||||
onCancel = { this._onToggleChat } />
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -177,8 +198,10 @@ class Chat extends AbstractChat<Props> {
|
|||
|
||||
return (
|
||||
<div
|
||||
aria-haspopup = 'true'
|
||||
className = { `sideToolbarContainer ${className}` }
|
||||
id = 'sideToolbarContainer'>
|
||||
id = 'sideToolbarContainer'
|
||||
onKeyDown = { this._onEscClick } >
|
||||
{ ComponentToRender }
|
||||
</div>
|
||||
);
|
||||
|
@ -199,6 +222,18 @@ class Chat extends AbstractChat<Props> {
|
|||
}
|
||||
|
||||
_onSendMessage: (string) => void;
|
||||
|
||||
_onToggleChat: () => void;
|
||||
|
||||
/**
|
||||
* Toggles the chat window.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
_onToggleChat() {
|
||||
this.props.dispatch(toggleChat());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(Chat));
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { Icon, IconClose } from '../../../base/icons';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { toggleChat } from '../../actions.web';
|
||||
|
@ -17,6 +18,11 @@ type Props = {
|
|||
* An optional class name.
|
||||
*/
|
||||
className: string,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -24,17 +30,31 @@ type Props = {
|
|||
*
|
||||
* @returns {React$Element<any>}
|
||||
*/
|
||||
function Header({ onCancel, className }: Props) {
|
||||
function Header({ onCancel, className, t }: Props) {
|
||||
|
||||
const onKeyPressHandler = useCallback(e => {
|
||||
if (onCancel && (e.key === ' ' || e.key === 'Enter')) {
|
||||
e.preventDefault();
|
||||
onCancel();
|
||||
}
|
||||
}, [ onCancel ]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className = { className || 'chat-dialog-header' }>
|
||||
className = { className || 'chat-dialog-header' }
|
||||
role = 'heading'>
|
||||
{ t('chat.title') }
|
||||
<Icon
|
||||
ariaLabel = { t('toolbar.closeChat') }
|
||||
onClick = { onCancel }
|
||||
src = { IconClose } />
|
||||
onKeyPress = { onKeyPressHandler }
|
||||
role = 'button'
|
||||
src = { IconClose }
|
||||
tabIndex = { 0 } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = { onCancel: toggleChat };
|
||||
|
||||
export default connect(null, mapDispatchToProps)(Header);
|
||||
export default translate(connect(null, mapDispatchToProps)(Header));
|
||||
|
|
|
@ -84,6 +84,9 @@ class ChatInput extends Component<Props, State> {
|
|||
this._onSmileySelect = this._onSmileySelect.bind(this);
|
||||
this._onSubmitMessage = this._onSubmitMessage.bind(this);
|
||||
this._onToggleSmileysPanel = this._onToggleSmileysPanel.bind(this);
|
||||
this._onEscHandler = this._onEscHandler.bind(this);
|
||||
this._onToggleSmileysPanelKeyPress = this._onToggleSmileysPanelKeyPress.bind(this);
|
||||
this._onSubmitMessageKeyPress = this._onSubmitMessageKeyPress.bind(this);
|
||||
this._setTextAreaRef = this._setTextAreaRef.bind(this);
|
||||
}
|
||||
|
||||
|
@ -116,8 +119,15 @@ class ChatInput extends Component<Props, State> {
|
|||
<div id = 'smileysarea'>
|
||||
<div id = 'smileys'>
|
||||
<div
|
||||
aria-expanded = { this.state.showSmileysPanel }
|
||||
aria-haspopup = 'smileysContainer'
|
||||
aria-label = { this.props.t('chat.smileysPanel') }
|
||||
className = 'smiley-button'
|
||||
onClick = { this._onToggleSmileysPanel }>
|
||||
onClick = { this._onToggleSmileysPanel }
|
||||
onKeyDown = { this._onEscHandler }
|
||||
onKeyPress = { this._onToggleSmileysPanelKeyPress }
|
||||
role = 'button'
|
||||
tabIndex = { 0 }>
|
||||
<Icon src = { IconSmile } />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -129,19 +139,26 @@ class ChatInput extends Component<Props, State> {
|
|||
</div>
|
||||
<div className = 'usrmsg-form'>
|
||||
<TextareaAutosize
|
||||
autoComplete = 'off'
|
||||
autoFocus = { true }
|
||||
id = 'usermsg'
|
||||
inputRef = { this._setTextAreaRef }
|
||||
maxRows = { 5 }
|
||||
onChange = { this._onMessageChange }
|
||||
onHeightChange = { this.props.onResize }
|
||||
onKeyDown = { this._onDetectSubmit }
|
||||
placeholder = { this.props.t('chat.messagebox') }
|
||||
ref = { this._setTextAreaRef }
|
||||
tabIndex = { 0 }
|
||||
value = { this.state.message } />
|
||||
</div>
|
||||
<div className = 'send-button-container'>
|
||||
<div
|
||||
aria-label = { this.props.t('chat.sendButton') }
|
||||
className = 'send-button'
|
||||
onClick = { this._onSubmitMessage }>
|
||||
onClick = { this._onSubmitMessage }
|
||||
onKeyPress = { this._onSubmitMessageKeyPress }
|
||||
role = 'button'
|
||||
tabIndex = { this.state.message.trim() ? 0 : -1 } >
|
||||
<Icon src = { IconPlane } />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -192,14 +209,32 @@ class ChatInput extends Component<Props, State> {
|
|||
* @returns {void}
|
||||
*/
|
||||
_onDetectSubmit(event) {
|
||||
if (event.keyCode === 13
|
||||
&& event.shiftKey === false) {
|
||||
if (event.key === 'Enter'
|
||||
&& event.shiftKey === false
|
||||
&& event.ctrlKey === false) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
this._onSubmitMessage();
|
||||
}
|
||||
}
|
||||
|
||||
_onSubmitMessageKeyPress: (Object) => void;
|
||||
|
||||
/**
|
||||
* KeyPress handler for accessibility.
|
||||
*
|
||||
* @param {Object} e - The key event to handle.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onSubmitMessageKeyPress(e) {
|
||||
if (e.key === ' ' || e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
this._onSubmitMessage();
|
||||
}
|
||||
}
|
||||
|
||||
_onMessageChange: (Object) => void;
|
||||
|
||||
/**
|
||||
|
@ -224,10 +259,16 @@ class ChatInput extends Component<Props, State> {
|
|||
* @returns {void}
|
||||
*/
|
||||
_onSmileySelect(smileyText) {
|
||||
this.setState({
|
||||
message: `${this.state.message} ${smileyText}`,
|
||||
showSmileysPanel: false
|
||||
});
|
||||
if (smileyText) {
|
||||
this.setState({
|
||||
message: `${this.state.message} ${smileyText}`,
|
||||
showSmileysPanel: false
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
showSmileysPanel: false
|
||||
});
|
||||
}
|
||||
|
||||
this._focus();
|
||||
}
|
||||
|
@ -241,9 +282,44 @@ class ChatInput extends Component<Props, State> {
|
|||
* @returns {void}
|
||||
*/
|
||||
_onToggleSmileysPanel() {
|
||||
if (this.state.showSmileysPanel) {
|
||||
this._focus();
|
||||
}
|
||||
this.setState({ showSmileysPanel: !this.state.showSmileysPanel });
|
||||
}
|
||||
|
||||
this._focus();
|
||||
_onEscHandler: (Object) => void;
|
||||
|
||||
/**
|
||||
* KeyPress handler for accessibility.
|
||||
*
|
||||
* @param {Object} e - The key event to handle.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onEscHandler(e) {
|
||||
// Escape handling does not work in onKeyPress
|
||||
if (this.state.showSmileysPanel && e.key === 'Escape') {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this._onToggleSmileysPanel();
|
||||
}
|
||||
}
|
||||
|
||||
_onToggleSmileysPanelKeyPress: (Object) => void;
|
||||
|
||||
/**
|
||||
* KeyPress handler for accessibility.
|
||||
*
|
||||
* @param {Object} e - The key event to handle.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onToggleSmileysPanelKeyPress(e) {
|
||||
if (e.key === ' ' || e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
this._onToggleSmileysPanel();
|
||||
}
|
||||
}
|
||||
|
||||
_setTextAreaRef: (?HTMLTextAreaElement) => void;
|
||||
|
|
|
@ -23,7 +23,7 @@ class ChatMessage extends AbstractChatMessage<Props> {
|
|||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { message } = this.props;
|
||||
const { message, t } = this.props;
|
||||
const processedMessage = [];
|
||||
|
||||
// content is an array of text and emoji components
|
||||
|
@ -38,12 +38,20 @@ class ChatMessage extends AbstractChatMessage<Props> {
|
|||
});
|
||||
|
||||
return (
|
||||
<div className = 'chatmessage-wrapper'>
|
||||
<div
|
||||
className = 'chatmessage-wrapper'
|
||||
tabIndex = { -1 }>
|
||||
<div className = { `chatmessage ${message.privateMessage ? 'privatemessage' : ''}` }>
|
||||
<div className = 'replywrapper'>
|
||||
<div className = 'messagecontent'>
|
||||
{ this.props.showDisplayName && this._renderDisplayName() }
|
||||
<div className = 'usermessage'>
|
||||
<span className = 'sr-only'>
|
||||
{ this.props.message.displayName === this.props.message.recipient
|
||||
? t('chat.messageAccessibleTitleMe')
|
||||
: t('chat.messageAccessibleTitle',
|
||||
{ user: this.props.message.displayName }) }
|
||||
</span>
|
||||
{ processedMessage }
|
||||
</div>
|
||||
{ message.privateMessage && this._renderPrivateNotice() }
|
||||
|
@ -77,7 +85,9 @@ class ChatMessage extends AbstractChatMessage<Props> {
|
|||
*/
|
||||
_renderDisplayName() {
|
||||
return (
|
||||
<div className = 'display-name'>
|
||||
<div
|
||||
aria-hidden = { true }
|
||||
className = 'display-name'>
|
||||
{ this.props.message.displayName }
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -59,6 +59,7 @@ class DisplayNameForm extends Component<Props, State> {
|
|||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onDisplayNameChange = this._onDisplayNameChange.bind(this);
|
||||
this._onSubmit = this._onSubmit.bind(this);
|
||||
this._onKeyPress = this._onKeyPress.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -74,6 +75,8 @@ class DisplayNameForm extends Component<Props, State> {
|
|||
<div id = 'nickname'>
|
||||
<form onSubmit = { this._onSubmit }>
|
||||
<FieldTextStateless
|
||||
aria-describedby = 'nickname-title'
|
||||
autoComplete = 'name'
|
||||
autoFocus = { true }
|
||||
compact = { true }
|
||||
id = 'nickinput'
|
||||
|
@ -86,7 +89,10 @@ class DisplayNameForm extends Component<Props, State> {
|
|||
</form>
|
||||
<div
|
||||
className = { `enter-chat${this.state.displayName.trim() ? '' : ' disabled'}` }
|
||||
onClick = { this._onSubmit }>
|
||||
onClick = { this._onSubmit }
|
||||
onKeyPress = { this._onKeyPress }
|
||||
role = 'button'
|
||||
tabIndex = { 0 }>
|
||||
{ t('chat.enter') }
|
||||
</div>
|
||||
<KeyboardAvoider />
|
||||
|
@ -125,6 +131,21 @@ class DisplayNameForm extends Component<Props, State> {
|
|||
displayName: this.state.displayName
|
||||
}));
|
||||
}
|
||||
|
||||
_onKeyPress: (Object) => void;
|
||||
|
||||
/**
|
||||
* KeyPress handler for accessibility.
|
||||
*
|
||||
* @param {Object} e - The key event to handle.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onKeyPress(e) {
|
||||
if (e.key === ' ' || e.key === 'Enter') {
|
||||
this._onSubmit(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(connect()(DisplayNameForm));
|
||||
|
|
|
@ -70,9 +70,12 @@ export default class MessageContainer extends AbstractMessageContainer<Props> {
|
|||
|
||||
return (
|
||||
<div
|
||||
aria-labelledby = 'chat-header'
|
||||
id = 'chatconversation'
|
||||
onScroll = { this._onChatScroll }
|
||||
ref = { this._messageListRef }>
|
||||
ref = { this._messageListRef }
|
||||
role = 'log'
|
||||
tabIndex = { 0 }>
|
||||
{ messages }
|
||||
<div ref = { this._messagesListEndRef } />
|
||||
</div>
|
||||
|
|
|
@ -15,6 +15,35 @@ import AbstractMessageRecipient, {
|
|||
* Class to implement the displaying of the recipient of the next message.
|
||||
*/
|
||||
class MessageRecipient extends AbstractMessageRecipient<Props> {
|
||||
/**
|
||||
* Initializes a new {@code MessageRecipient} instance.
|
||||
*
|
||||
* @param {*} props - The read-only properties with which the new instance
|
||||
* is to be initialized.
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
// Bind event handler so it is only bound once for every instance.
|
||||
this._onKeyPress = this._onKeyPress.bind(this);
|
||||
}
|
||||
|
||||
_onKeyPress: (Object) => void;
|
||||
|
||||
/**
|
||||
* KeyPress handler for accessibility.
|
||||
*
|
||||
* @param {Object} e - The key event to handle.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onKeyPress(e) {
|
||||
if (this.props._onRemovePrivateMessageRecipient && (e.key === ' ' || e.key === 'Enter')) {
|
||||
e.preventDefault();
|
||||
this.props._onRemovePrivateMessageRecipient();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements {@code PureComponent#render}.
|
||||
*
|
||||
|
@ -30,13 +59,20 @@ class MessageRecipient extends AbstractMessageRecipient<Props> {
|
|||
const { t } = this.props;
|
||||
|
||||
return (
|
||||
<div id = 'chat-recipient'>
|
||||
<div
|
||||
id = 'chat-recipient'
|
||||
role = 'alert'>
|
||||
<span>
|
||||
{ t('chat.messageTo', {
|
||||
recipient: _privateMessageRecipient
|
||||
}) }
|
||||
</span>
|
||||
<div onClick = { this.props._onRemovePrivateMessageRecipient }>
|
||||
<div
|
||||
aria-label = { t('dialog.close') }
|
||||
onClick = { this.props._onRemovePrivateMessageRecipient }
|
||||
onKeyPress = { this._onKeyPress }
|
||||
role = 'button'
|
||||
tabIndex = { 0 }>
|
||||
<Icon
|
||||
src = { IconCancelSelection } />
|
||||
</div>
|
||||
|
|
|
@ -23,6 +23,69 @@ type Props = {
|
|||
* @extends Component
|
||||
*/
|
||||
class SmileysPanel extends PureComponent<Props> {
|
||||
/**
|
||||
* Initializes a new {@code SmileysPanel} instance.
|
||||
*
|
||||
* @param {*} props - The read-only properties with which the new instance
|
||||
* is to be initialized.
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
// Bind event handler so it is only bound once for every instance.
|
||||
this._onClick = this._onClick.bind(this);
|
||||
this._onKeyPress = this._onKeyPress.bind(this);
|
||||
this._onEscKey = this._onEscKey.bind(this);
|
||||
}
|
||||
|
||||
_onEscKey: (Object) => void;
|
||||
|
||||
/**
|
||||
* KeyPress handler for accessibility.
|
||||
*
|
||||
* @param {Object} e - The key event to handle.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onEscKey(e) {
|
||||
// Escape handling does not work in onKeyPress
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.props.onSmileySelect();
|
||||
}
|
||||
}
|
||||
|
||||
_onKeyPress: (Object) => void;
|
||||
|
||||
/**
|
||||
* KeyPress handler for accessibility.
|
||||
*
|
||||
* @param {Object} e - The key event to handle.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onKeyPress(e) {
|
||||
if (e.key === ' ') {
|
||||
e.preventDefault();
|
||||
this.props.onSmileySelect(e.target.id && smileys[e.target.id]);
|
||||
}
|
||||
}
|
||||
|
||||
_onClick: (Object) => void;
|
||||
|
||||
/**
|
||||
* Click handler for to select emoji.
|
||||
*
|
||||
* @param {Object} e - The key event to handle.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onClick(e) {
|
||||
e.preventDefault();
|
||||
this.props.onSmileySelect(e.currentTarget.id && smileys[e.currentTarget.id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
|
@ -30,40 +93,33 @@ class SmileysPanel extends PureComponent<Props> {
|
|||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const smileyItems = Object.keys(smileys).map(smileyKey => {
|
||||
const onSelectFunction = this._getOnSmileySelectCallback(smileyKey);
|
||||
|
||||
return (
|
||||
<div
|
||||
className = 'smileyContainer'
|
||||
id = { smileyKey }
|
||||
key = { smileyKey }>
|
||||
<Emoji
|
||||
onClick = { onSelectFunction }
|
||||
onlyEmojiClassName = 'smiley'
|
||||
text = { smileys[smileyKey] } />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
const smileyItems = Object.keys(smileys).map(smileyKey => (
|
||||
<div
|
||||
className = 'smileyContainer'
|
||||
id = { smileyKey }
|
||||
key = { smileyKey }
|
||||
onClick = { this._onClick }
|
||||
onKeyDown = { this._onEscKey }
|
||||
onKeyPress = { this._onKeyPress }
|
||||
role = 'option'
|
||||
tabIndex = { 0 }>
|
||||
<Emoji
|
||||
onlyEmojiClassName = 'smiley'
|
||||
text = { smileys[smileyKey] } />
|
||||
</div>
|
||||
));
|
||||
|
||||
return (
|
||||
<div id = 'smileysContainer'>
|
||||
<div
|
||||
aria-orientation = 'horizontal'
|
||||
id = 'smileysContainer'
|
||||
onKeyDown = { this._onEscKey }
|
||||
role = 'listbox'
|
||||
tabIndex = { -1 }>
|
||||
{ smileyItems }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to bind a smiley's click handler.
|
||||
*
|
||||
* @param {string} smileyKey - The key from the {@link smileys} object
|
||||
* that should be added to the chat message.
|
||||
* @private
|
||||
* @returns {Function}
|
||||
*/
|
||||
_getOnSmileySelectCallback(smileyKey) {
|
||||
return () => this.props.onSmileySelect(smileys[smileyKey]);
|
||||
}
|
||||
}
|
||||
|
||||
export default SmileysPanel;
|
||||
|
|
|
@ -107,6 +107,8 @@ class ChromeExtensionBanner extends PureComponent<Props, State> {
|
|||
this._onInstallExtensionClick = this._onInstallExtensionClick.bind(this);
|
||||
this._shouldNotRender = this._shouldNotRender.bind(this);
|
||||
this._onDontShowAgainChange = this._onDontShowAgainChange.bind(this);
|
||||
this._onCloseKeyPress = this._onCloseKeyPress.bind(this);
|
||||
this._onInstallExtensionKeyPress = this._onInstallExtensionKeyPress.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -169,6 +171,22 @@ class ChromeExtensionBanner extends PureComponent<Props, State> {
|
|||
this.setState({ closePressed: true });
|
||||
}
|
||||
|
||||
_onCloseKeyPress: (Object) => void;
|
||||
|
||||
/**
|
||||
* KeyPress handler for accessibility.
|
||||
*
|
||||
* @param {Object} e - The key event to handle.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onCloseKeyPress(e) {
|
||||
if (e.key === ' ' || e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
this._onClosePressed();
|
||||
}
|
||||
}
|
||||
|
||||
_onInstallExtensionClick: () => void;
|
||||
|
||||
/**
|
||||
|
@ -182,6 +200,22 @@ class ChromeExtensionBanner extends PureComponent<Props, State> {
|
|||
this.setState({ closePressed: true });
|
||||
}
|
||||
|
||||
_onInstallExtensionKeyPress: (Object) => void;
|
||||
|
||||
/**
|
||||
* KeyPress handler for accessibility.
|
||||
*
|
||||
* @param {Object} e - The key event to handle.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onInstallExtensionKeyPress(e) {
|
||||
if (e.key === ' ' || e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
this._onClosePressed();
|
||||
}
|
||||
}
|
||||
|
||||
_shouldNotRender: () => boolean;
|
||||
|
||||
/**
|
||||
|
@ -236,16 +270,23 @@ class ChromeExtensionBanner extends PureComponent<Props, State> {
|
|||
|
||||
return (
|
||||
<div className = { mainClassNames }>
|
||||
<div className = 'chrome-extension-banner__container'>
|
||||
<div
|
||||
aria-aria-describedby = 'chrome-extension-banner__text-container'
|
||||
className = 'chrome-extension-banner__container'
|
||||
role = 'banner'>
|
||||
<div className = 'chrome-extension-banner__icon-container' />
|
||||
<div
|
||||
className = 'chrome-extension-banner__icon-container' />
|
||||
<div
|
||||
className = 'chrome-extension-banner__text-container'>
|
||||
className = 'chrome-extension-banner__text-container'
|
||||
id = 'chrome-extension-banner__text-container'>
|
||||
{ t('chromeExtensionBanner.installExtensionText') }
|
||||
</div>
|
||||
<div
|
||||
aria-label = { t('chromeExtensionBanner.close') }
|
||||
className = 'chrome-extension-banner__close-container'
|
||||
onClick = { this._onClosePressed }>
|
||||
onClick = { this._onClosePressed }
|
||||
onKeyPress = { this._onCloseKeyPress }
|
||||
role = 'button'
|
||||
tabIndex = { 0 }>
|
||||
<Icon
|
||||
className = 'gray'
|
||||
size = { 12 }
|
||||
|
@ -255,18 +296,28 @@ class ChromeExtensionBanner extends PureComponent<Props, State> {
|
|||
<div
|
||||
className = 'chrome-extension-banner__button-container'>
|
||||
<div
|
||||
aria-labelledby = 'chrome-extension-banner__button-text'
|
||||
className = 'chrome-extension-banner__button-open-url'
|
||||
onClick = { this._onInstallExtensionClick }>
|
||||
onClick = { this._onInstallExtensionClick }
|
||||
onKeyPress = { this._onInstallExtensionKeyPress }
|
||||
role = 'button'
|
||||
tabIndex = { 0 }>
|
||||
<div
|
||||
className = 'chrome-extension-banner__button-text'>
|
||||
className = 'chrome-extension-banner__button-text'
|
||||
id = 'chrome-extension-banner__button-text'>
|
||||
{ t('chromeExtensionBanner.buttonText') }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className = 'chrome-extension-banner__checkbox-container'>
|
||||
<label className = 'chrome-extension-banner__checkbox-label'>
|
||||
<label
|
||||
className = 'chrome-extension-banner__checkbox-label'
|
||||
htmlFor = 'chrome-extension-banner__checkbox'
|
||||
id = 'chrome-extension-banner__checkbox-label'>
|
||||
<input
|
||||
aria-labelledby = 'chrome-extension-banner__checkbox-label'
|
||||
checked = { this.state.dontShowAgainChecked }
|
||||
id = 'chrome-extension-banner__checkbox'
|
||||
onChange = { this._onDontShowAgainChange }
|
||||
type = 'checkbox' />
|
||||
{ t('chromeExtensionBanner.dontShowAgain') }
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { Icon, IconInviteMore } from '../../../base/icons';
|
||||
|
@ -47,16 +47,28 @@ function InviteMore({
|
|||
onClick,
|
||||
t
|
||||
}: Props) {
|
||||
const onKeyPressHandler = useCallback(e => {
|
||||
if (onClick && (e.key === ' ' || e.key === 'Enter')) {
|
||||
e.preventDefault();
|
||||
onClick();
|
||||
}
|
||||
}, [ onClick ]);
|
||||
|
||||
return (
|
||||
_shouldShow
|
||||
? <div className = { `invite-more-container${_toolboxVisible ? '' : ' elevated'}` }>
|
||||
<div className = 'invite-more-content'>
|
||||
<div className = 'invite-more-header'>
|
||||
<div
|
||||
className = 'invite-more-header'
|
||||
role = 'heading'>
|
||||
{t('addPeople.inviteMoreHeader')}
|
||||
</div>
|
||||
<div
|
||||
className = 'invite-more-button'
|
||||
onClick = { onClick }>
|
||||
onClick = { onClick }
|
||||
onKeyPress = { onKeyPressHandler }
|
||||
role = 'button'
|
||||
tabIndex = { 0 }>
|
||||
<Icon src = { IconInviteMore } />
|
||||
<div className = 'invite-more-button-text'>
|
||||
{t('addPeople.inviteMorePrompt')}
|
||||
|
|
|
@ -572,7 +572,9 @@ class ConnectionStatsTable extends Component<Props> {
|
|||
<span>
|
||||
<a
|
||||
className = 'savelogs link'
|
||||
onClick = { this.props.onSaveLogs } >
|
||||
onClick = { this.props.onSaveLogs }
|
||||
role = 'button'
|
||||
tabIndex = { 0 }>
|
||||
{ this.props.t('connectionindicator.savelogs') }
|
||||
</a>
|
||||
<span> | </span>
|
||||
|
@ -597,7 +599,9 @@ class ConnectionStatsTable extends Component<Props> {
|
|||
return (
|
||||
<a
|
||||
className = 'showmore link'
|
||||
onClick = { this.props.onShowMore } >
|
||||
onClick = { this.props.onShowMore }
|
||||
role = 'button'
|
||||
tabIndex = { 0 }>
|
||||
{ this.props.t(translationKey) }
|
||||
</a>
|
||||
);
|
||||
|
|
|
@ -87,6 +87,7 @@ class DeepLinkingDesktopPage<P : Props> extends Component<P> {
|
|||
HIDE_DEEP_LINKING_LOGO
|
||||
? null
|
||||
: <img
|
||||
alt = { t('welcomepage.logo.logoDeepLinking') }
|
||||
className = 'logo'
|
||||
src = 'images/logo-deep-linking.png' />
|
||||
}
|
||||
|
|
|
@ -119,6 +119,7 @@ class DeepLinkingMobilePage extends Component<Props> {
|
|||
HIDE_DEEP_LINKING_LOGO
|
||||
? null
|
||||
: <img
|
||||
alt = { t('welcomepage.logo.logoDeepLinking') }
|
||||
className = 'logo'
|
||||
src = 'images/logo-deep-linking.png' />
|
||||
}
|
||||
|
@ -127,6 +128,7 @@ class DeepLinkingMobilePage extends Component<Props> {
|
|||
{
|
||||
SHOW_DEEP_LINKING_IMAGE
|
||||
? <img
|
||||
alt = { t('welcomepage.logo.logoDeepLinking') }
|
||||
className = 'image'
|
||||
src = 'images/deep-linking-image.png' />
|
||||
: null
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { translate } from '../../base/i18n';
|
||||
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of
|
||||
* {@link DesktopSourcePreview}.
|
||||
|
@ -35,7 +38,12 @@ type Props = {
|
|||
/**
|
||||
* The source type of the DesktopCapturerSources to display.
|
||||
*/
|
||||
type: string
|
||||
type: string,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -74,6 +82,7 @@ class DesktopSourcePreview extends Component<Props> {
|
|||
onDoubleClick = { this._onDoubleClick }>
|
||||
<div className = 'desktop-source-preview-image-container'>
|
||||
<img
|
||||
alt = { this.props.t('welcomepage.logo.desktopPreviewThumbnail') }
|
||||
className = 'desktop-source-preview-thumbnail'
|
||||
src = { this.props.source.thumbnail.toDataURL() } />
|
||||
</div>
|
||||
|
@ -111,4 +120,4 @@ class DesktopSourcePreview extends Component<Props> {
|
|||
}
|
||||
}
|
||||
|
||||
export default DesktopSourcePreview;
|
||||
export default translate(DesktopSourcePreview);
|
||||
|
|
|
@ -44,6 +44,7 @@ class AudioOutputPreview extends Component<Props> {
|
|||
|
||||
this._audioElementReady = this._audioElementReady.bind(this);
|
||||
this._onClick = this._onClick.bind(this);
|
||||
this._onKeyPress = this._onKeyPress.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -66,7 +67,12 @@ class AudioOutputPreview extends Component<Props> {
|
|||
render() {
|
||||
return (
|
||||
<div className = 'audio-output-preview'>
|
||||
<a onClick = { this._onClick }>
|
||||
<a
|
||||
aria-label = { this.props.t('deviceSelection.testAudio') }
|
||||
onClick = { this._onClick }
|
||||
onKeyPress = { this._onKeyPress }
|
||||
role = 'button'
|
||||
tabIndex = { 0 }>
|
||||
{ this.props.t('deviceSelection.testAudio') }
|
||||
</a>
|
||||
<Audio
|
||||
|
@ -105,6 +111,22 @@ class AudioOutputPreview extends Component<Props> {
|
|||
&& this._audioElement.play();
|
||||
}
|
||||
|
||||
_onKeyPress: (Object) => void;
|
||||
|
||||
/**
|
||||
* KeyPress handler for accessibility.
|
||||
*
|
||||
* @param {Object} e - The key event to handle.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onKeyPress(e) {
|
||||
if (e.key === ' ' || e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
this._onClick();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the target output device for playing the test sound.
|
||||
*
|
||||
|
|
|
@ -232,7 +232,9 @@ class DeviceSelection extends AbstractDialogTab<Props, State> {
|
|||
track = { this.state.previewAudioTrack } /> }
|
||||
</div>
|
||||
<div className = 'device-selection-column column-selectors'>
|
||||
<div className = 'device-selectors'>
|
||||
<div
|
||||
aria-live = 'polite all'
|
||||
className = 'device-selectors'>
|
||||
{ this._renderSelectors() }
|
||||
</div>
|
||||
{ !hideAudioOutputSelect
|
||||
|
@ -344,9 +346,11 @@ class DeviceSelection extends AbstractDialogTab<Props, State> {
|
|||
_renderSelector(deviceSelectorProps) {
|
||||
return (
|
||||
<div key = { deviceSelectorProps.label }>
|
||||
<div className = 'device-selector-label'>
|
||||
<label
|
||||
className = 'device-selector-label'
|
||||
htmlFor = { deviceSelectorProps.id }>
|
||||
{ this.props.t(deviceSelectorProps.label) }
|
||||
</div>
|
||||
</label>
|
||||
<DeviceSelector { ...deviceSelectorProps } />
|
||||
</div>
|
||||
);
|
||||
|
@ -370,6 +374,7 @@ class DeviceSelection extends AbstractDialogTab<Props, State> {
|
|||
isDisabled: this.props.disableAudioInputChange
|
||||
|| this.props.disableDeviceChange,
|
||||
key: 'audioInput',
|
||||
id: 'audioInput',
|
||||
label: 'settings.selectMic',
|
||||
onSelect: selectedAudioInputId =>
|
||||
super._onChange({ selectedAudioInputId }),
|
||||
|
@ -385,6 +390,7 @@ class DeviceSelection extends AbstractDialogTab<Props, State> {
|
|||
icon: 'icon-camera',
|
||||
isDisabled: this.props.disableDeviceChange,
|
||||
key: 'videoInput',
|
||||
id: 'videoInput',
|
||||
label: 'settings.selectCamera',
|
||||
onSelect: selectedVideoInputId =>
|
||||
super._onChange({ selectedVideoInputId }),
|
||||
|
@ -400,6 +406,7 @@ class DeviceSelection extends AbstractDialogTab<Props, State> {
|
|||
icon: 'icon-speaker',
|
||||
isDisabled: this.props.disableDeviceChange,
|
||||
key: 'audioOutput',
|
||||
id: 'audioOutput',
|
||||
label: 'settings.selectAudioOutput',
|
||||
onSelect: selectedAudioOutputId =>
|
||||
super._onChange({ selectedAudioOutputId }),
|
||||
|
|
|
@ -51,7 +51,12 @@ type Props = {
|
|||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: Function
|
||||
t: Function,
|
||||
|
||||
/**
|
||||
* The id of the dropdown element
|
||||
*/
|
||||
id: string
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -81,6 +86,10 @@ class DeviceSelector extends Component<Props> {
|
|||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
if (this.props.hasPermission === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!this.props.hasPermission) {
|
||||
return this._renderNoPermission();
|
||||
}
|
||||
|
@ -134,14 +143,10 @@ class DeviceSelector extends Component<Props> {
|
|||
_createDropdownItem(device) {
|
||||
return (
|
||||
<DropdownItem
|
||||
data-deviceid = { device.deviceId }
|
||||
isSelected = { device.deviceId === this.props.selectedDeviceId }
|
||||
key = { device.deviceId }
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick = {
|
||||
e => {
|
||||
e.stopPropagation();
|
||||
this._onSelect(device.deviceId);
|
||||
}
|
||||
}>
|
||||
onClick = { this._onSelect }>
|
||||
{ device.label || device.deviceId }
|
||||
</DropdownItem>
|
||||
);
|
||||
|
@ -183,7 +188,8 @@ class DeviceSelector extends Component<Props> {
|
|||
shouldFitContainer = { true }
|
||||
trigger = { triggerText }
|
||||
triggerButtonProps = {{
|
||||
shouldFitContainer: true
|
||||
shouldFitContainer: true,
|
||||
id: this.props.id
|
||||
}}
|
||||
triggerType = 'button'>
|
||||
<DropdownItemGroup>
|
||||
|
@ -199,13 +205,16 @@ class DeviceSelector extends Component<Props> {
|
|||
/**
|
||||
* Invokes the passed in callback to notify of selection changes.
|
||||
*
|
||||
* @param {Object} newDeviceId - Selected device id from DropdownMenu option.
|
||||
* @param {Object} e - The key event to handle.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onSelect(newDeviceId) {
|
||||
if (this.props.selectedDeviceId !== newDeviceId) {
|
||||
this.props.onSelect(newDeviceId);
|
||||
_onSelect(e) {
|
||||
const deviceId = e.currentTarget.getAttribute('data-deviceid');
|
||||
|
||||
if (this.props.selectedDeviceId !== deviceId) {
|
||||
this.props.onSelect(deviceId);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -85,6 +85,7 @@ class E2EESection extends Component<Props, State> {
|
|||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onExpand = this._onExpand.bind(this);
|
||||
this._onExpandKeyPress = this._onExpandKeyPress.bind(this);
|
||||
this._onToggle = this._onToggle.bind(this);
|
||||
}
|
||||
|
||||
|
@ -101,12 +102,20 @@ class E2EESection extends Component<Props, State> {
|
|||
|
||||
return (
|
||||
<div id = 'e2ee-section'>
|
||||
<p className = 'description'>
|
||||
<p
|
||||
aria-live = 'polite'
|
||||
className = 'description'
|
||||
id = 'e2ee-section-description'>
|
||||
{ expand && description }
|
||||
{ !expand && description.substring(0, 100) }
|
||||
{ !expand && <span
|
||||
aria-controls = 'e2ee-section-description'
|
||||
aria-expanded = { expand }
|
||||
className = 'read-more'
|
||||
onClick = { this._onExpand }>
|
||||
onClick = { this._onExpand }
|
||||
onKeyPress = { this._onExpandKeyPress }
|
||||
role = 'button'
|
||||
tabIndex = { 0 }>
|
||||
... { t('dialog.readMore') }
|
||||
</span> }
|
||||
</p>
|
||||
|
@ -142,6 +151,22 @@ class E2EESection extends Component<Props, State> {
|
|||
});
|
||||
}
|
||||
|
||||
_onExpandKeyPress: (Object) => void;
|
||||
|
||||
/**
|
||||
* KeyPress handler for accessibility.
|
||||
*
|
||||
* @param {Object} e - The key event to handle.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onExpandKeyPress(e) {
|
||||
if (e.key === ' ' || e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
this._onExpand();
|
||||
}
|
||||
}
|
||||
|
||||
_onToggle: () => void;
|
||||
|
||||
/**
|
||||
|
|
|
@ -44,10 +44,12 @@ function EmbedMeeting({ t, url }: Props) {
|
|||
width = 'small'>
|
||||
<div className = 'embed-meeting-dialog'>
|
||||
<textarea
|
||||
aria-label = { t('dialog.embedMeeting') }
|
||||
className = 'embed-meeting-code'
|
||||
readOnly = { true }
|
||||
value = { getEmbedCode() } />
|
||||
<CopyButton
|
||||
aria-label = { t('addPeople.copyLink') }
|
||||
className = 'embed-meeting-copy'
|
||||
displayedText = { t('dialog.copy') }
|
||||
textOnCopySuccess = { t('dialog.copied') }
|
||||
|
|
|
@ -36,10 +36,28 @@ function EmbedMeetingTrigger({ t, openEmbedDialog }: Props) {
|
|||
openEmbedDialog(EmbedMeetingDialog);
|
||||
}
|
||||
|
||||
/**
|
||||
* KeyPress handler for accessibility.
|
||||
*
|
||||
* @param {React.KeyboardEventHandler<HTMLDivElement>} e - The key event to handle.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function onKeyPress(e) {
|
||||
if (e.key === ' ' || e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
onClick();
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
aria-label = { t('embedMeeting.title') }
|
||||
className = 'embed-meeting-trigger'
|
||||
onClick = { onClick }>
|
||||
onClick = { onClick }
|
||||
onKeyPress = { onKeyPress }
|
||||
role = 'button'
|
||||
tabIndex = { 0 }>
|
||||
{t('embedMeeting.title')}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -154,6 +154,12 @@ class FeedbackDialog extends Component<Props, State> {
|
|||
this._scoreClickConfigurations = SCORES.map((textKey, index) => {
|
||||
return {
|
||||
_onClick: () => this._onScoreSelect(index),
|
||||
_onKeyPres: e => {
|
||||
if (e.key === ' ' || e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
this._onScoreSelect(index);
|
||||
}
|
||||
},
|
||||
_onMouseOver: () => this._onScoreMouseOver(index)
|
||||
};
|
||||
});
|
||||
|
@ -200,6 +206,8 @@ class FeedbackDialog extends Component<Props, State> {
|
|||
const scoreToDisplayAsSelected
|
||||
= mousedOverScore > -1 ? mousedOverScore : score;
|
||||
|
||||
const { t } = this.props;
|
||||
|
||||
const scoreIcons = this._scoreClickConfigurations.map(
|
||||
(config, index) => {
|
||||
const isFilled = index <= scoreToDisplayAsSelected;
|
||||
|
@ -208,11 +216,15 @@ class FeedbackDialog extends Component<Props, State> {
|
|||
= `star-btn ${scoreAnimationClass} ${activeClass}`;
|
||||
|
||||
return (
|
||||
<a
|
||||
<span
|
||||
aria-label = { t(SCORES[index]) }
|
||||
className = { className }
|
||||
key = { index }
|
||||
onClick = { config._onClick }
|
||||
onMouseOver = { config._onMouseOver }>
|
||||
onKeyPress = { config._onKeyPres }
|
||||
onMouseOver = { config._onMouseOver }
|
||||
role = 'button'
|
||||
tabIndex = { 0 }>
|
||||
{ isFilled
|
||||
? <StarFilledIcon
|
||||
label = 'star-filled'
|
||||
|
@ -220,11 +232,10 @@ class FeedbackDialog extends Component<Props, State> {
|
|||
: <StarIcon
|
||||
label = 'star'
|
||||
size = 'xlarge' /> }
|
||||
</a>
|
||||
</span>
|
||||
);
|
||||
});
|
||||
|
||||
const { t } = this.props;
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
|
@ -234,7 +245,9 @@ class FeedbackDialog extends Component<Props, State> {
|
|||
titleKey = 'feedback.rateExperience'>
|
||||
<div className = 'feedback-dialog'>
|
||||
<div className = 'rating'>
|
||||
<div className = 'star-label'>
|
||||
<div
|
||||
aria-label = { this.props.t('feedback.star') }
|
||||
className = 'star-label' >
|
||||
<p id = 'starLabel'>
|
||||
{ t(SCORES[scoreToDisplayAsSelected]) }
|
||||
</p>
|
||||
|
|
|
@ -13,7 +13,8 @@ import { translate } from '../../../base/i18n';
|
|||
import { Icon, IconMenuDown, IconMenuUp } from '../../../base/icons';
|
||||
import { getLocalParticipant } from '../../../base/participants';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { isButtonEnabled } from '../../../toolbox/functions.web';
|
||||
import { showToolbox } from '../../../toolbox/actions.web';
|
||||
import { isButtonEnabled, isToolboxVisible } from '../../../toolbox/functions.web';
|
||||
import { LAYOUTS, getCurrentLayout } from '../../../video-layout';
|
||||
import { setFilmstripVisible } from '../../actions';
|
||||
import { shouldRemoteVideosBeVisible } from '../../functions';
|
||||
|
@ -83,6 +84,11 @@ type Props = {
|
|||
*/
|
||||
_visible: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not the toolbox is displayed.
|
||||
*/
|
||||
_isToolboxVisible: Boolean,
|
||||
|
||||
/**
|
||||
* The redux {@code dispatch} function.
|
||||
*/
|
||||
|
@ -114,6 +120,7 @@ class Filmstrip extends Component <Props> {
|
|||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onShortcutToggleFilmstrip = this._onShortcutToggleFilmstrip.bind(this);
|
||||
this._onToolbarToggleFilmstrip = this._onToolbarToggleFilmstrip.bind(this);
|
||||
this._onTabIn = this._onTabIn.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -238,6 +245,19 @@ class Filmstrip extends Component <Props> {
|
|||
);
|
||||
}
|
||||
|
||||
_onTabIn: () => void;
|
||||
|
||||
/**
|
||||
* Toggle the toolbar visibility when tabbing into it.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onTabIn() {
|
||||
if (!this.props._isToolboxVisible && this.props._visible) {
|
||||
this.props.dispatch(showToolbox());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an action to change the visibility of the filmstrip.
|
||||
*
|
||||
|
@ -298,12 +318,18 @@ class Filmstrip extends Component <Props> {
|
|||
const { t } = this.props;
|
||||
|
||||
return (
|
||||
<div className = 'filmstrip__toolbar'>
|
||||
<div
|
||||
className = 'filmstrip__toolbar'>
|
||||
<button
|
||||
aria-expanded = { this.props._visible }
|
||||
aria-label = { t('toolbar.accessibilityLabel.toggleFilmstrip') }
|
||||
id = 'toggleFilmstripButton'
|
||||
onClick = { this._onToolbarToggleFilmstrip }>
|
||||
<Icon src = { icon } />
|
||||
onClick = { this._onToolbarToggleFilmstrip }
|
||||
onFocus = { this._onTabIn }
|
||||
tabIndex = { 0 }>
|
||||
<Icon
|
||||
aria-label = { t('toolbar.accessibilityLabel.toggleFilmstrip') }
|
||||
src = { icon } />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
@ -342,7 +368,8 @@ function _mapStateToProps(state) {
|
|||
_participants: state['features/base/participants'],
|
||||
_rows: gridDimensions.rows,
|
||||
_videosClassName: videosClassName,
|
||||
_visible: visible
|
||||
_visible: visible,
|
||||
_isToolboxVisible: isToolboxVisible(state)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -737,12 +737,12 @@ class Thumbnail extends Component<Props, State> {
|
|||
participantID = { id } />
|
||||
</div>
|
||||
{ this._renderAvatar(styles.avatar) }
|
||||
<span className = 'localvideomenu'>
|
||||
<LocalVideoMenuTriggerButton />
|
||||
</span>
|
||||
<span className = 'audioindicator-container'>
|
||||
<AudioLevelIndicator audioLevel = { audioLevel } />
|
||||
</span>
|
||||
<span className = 'localvideomenu'>
|
||||
<LocalVideoMenuTriggerButton />
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
@ -867,15 +867,15 @@ class Thumbnail extends Component<Props, State> {
|
|||
className = 'presence-label'
|
||||
participantID = { id } />
|
||||
</div>
|
||||
<span className = 'audioindicator-container'>
|
||||
<AudioLevelIndicator audioLevel = { audioLevel } />
|
||||
</span>
|
||||
<span className = 'remotevideomenu'>
|
||||
<RemoteVideoMenuTriggerButton
|
||||
initialVolumeValue = { volume }
|
||||
onVolumeChange = { onVolumeChange }
|
||||
participantID = { id } />
|
||||
</span>
|
||||
<span className = 'audioindicator-container'>
|
||||
<AudioLevelIndicator audioLevel = { audioLevel } />
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ class GoogleSignInButton extends AbstractGoogleSignInButton {
|
|||
className = 'google-sign-in'
|
||||
onClick = { this.props.onClick }>
|
||||
<img
|
||||
alt = { t('welcomepage.logo.googleLogo') }
|
||||
className = 'google-logo'
|
||||
src = 'images/googleLogo.svg' />
|
||||
<div className = 'google-cta'>
|
||||
|
|
|
@ -28,10 +28,12 @@ type Props = {
|
|||
function CopyMeetingLinkSection({ t, url }: Props) {
|
||||
return (
|
||||
<>
|
||||
<span>{t('addPeople.shareLink')}</span>
|
||||
<label htmlFor = { 'copy-button-id' }>{t('addPeople.shareLink')}</label>
|
||||
<CopyButton
|
||||
aria-label = { t('addPeople.copyLink') }
|
||||
className = 'invite-more-dialog-conference-url'
|
||||
displayedText = { getDecodedURI(url) }
|
||||
id = 'copy-button-id'
|
||||
textOnCopySuccess = { t('addPeople.linkCopied') }
|
||||
textOnHover = { t('addPeople.copyLink') }
|
||||
textToCopy = { url } />
|
||||
|
|
|
@ -49,6 +49,7 @@ class DialInNumber extends Component<Props> {
|
|||
|
||||
// Bind event handler so it is only bound once for every instance.
|
||||
this._onCopyText = this._onCopyText.bind(this);
|
||||
this._onCopyTextKeyPress = this._onCopyTextKeyPress.bind(this);
|
||||
}
|
||||
|
||||
_onCopyText: () => void;
|
||||
|
@ -68,6 +69,22 @@ class DialInNumber extends Component<Props> {
|
|||
copyText(textToCopy);
|
||||
}
|
||||
|
||||
_onCopyTextKeyPress: (Object) => void;
|
||||
|
||||
/**
|
||||
* KeyPress handler for accessibility.
|
||||
*
|
||||
* @param {Object} e - The key event to handle.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onCopyTextKeyPress(e) {
|
||||
if (e.key === ' ' || e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
this._onCopyText();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
|
@ -101,8 +118,12 @@ class DialInNumber extends Component<Props> {
|
|||
</span>
|
||||
</div>
|
||||
<a
|
||||
aria-label = { t('info.copyNumber') }
|
||||
className = 'dial-in-copy'
|
||||
onClick = { this._onCopyText }>
|
||||
onClick = { this._onCopyText }
|
||||
onKeyPress = { this._onCopyTextKeyPress }
|
||||
role = 'button'
|
||||
tabIndex = { 0 }>
|
||||
<Icon src = { IconCopy } />
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
@ -52,6 +52,20 @@ function InviteByEmailSection({ inviteSubject, inviteText, t }: Props) {
|
|||
copyText(inviteText);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the conference invitation to the clipboard.
|
||||
*
|
||||
* @param {Object} e - The key event to handle.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function _onCopyTextKeyPress(e) {
|
||||
if (e.key === ' ' || e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
copyText(inviteText);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the email invite drawer.
|
||||
*
|
||||
|
@ -61,6 +75,20 @@ function InviteByEmailSection({ inviteSubject, inviteText, t }: Props) {
|
|||
setIsActive(!isActive);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the email invite drawer.
|
||||
*
|
||||
* @param {Object} e - The key event to handle.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function _onToggleActiveStateKeyPress(e) {
|
||||
if (e.key === ' ' || e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
setIsActive(!isActive);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders clickable elements that each open an email client
|
||||
* containing a conference invite.
|
||||
|
@ -101,6 +129,7 @@ function InviteByEmailSection({ inviteSubject, inviteText, t }: Props) {
|
|||
key = { idx }
|
||||
position = 'top'>
|
||||
<a
|
||||
aria-label = { t(tooltipKey) }
|
||||
className = 'provider-icon'
|
||||
href = { url }
|
||||
rel = 'noopener noreferrer'
|
||||
|
@ -119,8 +148,13 @@ function InviteByEmailSection({ inviteSubject, inviteText, t }: Props) {
|
|||
<>
|
||||
<div>
|
||||
<div
|
||||
aria-expanded = { isActive }
|
||||
aria-label = { t('addPeople.shareInvite') }
|
||||
className = { `invite-more-dialog email-container${isActive ? ' active' : ''}` }
|
||||
onClick = { _onToggleActiveState }>
|
||||
onClick = { _onToggleActiveState }
|
||||
onKeyPress = { _onToggleActiveStateKeyPress }
|
||||
role = 'button'
|
||||
tabIndex = { 0 }>
|
||||
<span>{t('addPeople.shareInvite')}</span>
|
||||
<Icon src = { IconArrowDownSmall } />
|
||||
</div>
|
||||
|
@ -129,8 +163,12 @@ function InviteByEmailSection({ inviteSubject, inviteText, t }: Props) {
|
|||
content = { t('addPeople.copyInvite') }
|
||||
position = 'top'>
|
||||
<div
|
||||
aria-label = { t('addPeople.copyInvite') }
|
||||
className = 'copy-invite-icon'
|
||||
onClick = { _onCopyText }>
|
||||
onClick = { _onCopyText }
|
||||
onKeyPress = { _onCopyTextKeyPress }
|
||||
role = 'button'
|
||||
tabIndex = { 0 }>
|
||||
<Icon src = { IconCopy } />
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
|
|
@ -76,9 +76,11 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
|
|||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onClearItems = this._onClearItems.bind(this);
|
||||
this._onClearItemsKeyPress = this._onClearItemsKeyPress.bind(this);
|
||||
this._onItemSelected = this._onItemSelected.bind(this);
|
||||
this._onSelectionChange = this._onSelectionChange.bind(this);
|
||||
this._onSubmit = this._onSubmit.bind(this);
|
||||
this._onSubmitKeyPress = this._onSubmitKeyPress.bind(this);
|
||||
this._parseQueryResults = this._parseQueryResults.bind(this);
|
||||
this._setMultiSelectElement = this._setMultiSelectElement.bind(this);
|
||||
this._renderFooterText = this._renderFooterText.bind(this);
|
||||
|
@ -246,6 +248,22 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
|
|||
});
|
||||
}
|
||||
|
||||
_onSubmitKeyPress: (Object) => void;
|
||||
|
||||
/**
|
||||
* KeyPress handler for accessibility.
|
||||
*
|
||||
* @param {Object} e - The key event to handle.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onSubmitKeyPress(e) {
|
||||
if (e.key === ' ' || e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
this._onSubmit();
|
||||
}
|
||||
}
|
||||
|
||||
_onKeyDown: (Object) => void;
|
||||
|
||||
/**
|
||||
|
@ -425,6 +443,22 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
|
|||
this.setState({ inviteItems: [] });
|
||||
}
|
||||
|
||||
_onClearItemsKeyPress: () => void;
|
||||
|
||||
/**
|
||||
* Clears the selected items from state and form.
|
||||
*
|
||||
* @param {Object} e - The key event to handle.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onClearItemsKeyPress(e) {
|
||||
if (e.key === ' ' || e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
this._onClearItems();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the add/cancel actions for the form.
|
||||
*
|
||||
|
@ -441,13 +475,21 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
|
|||
return (
|
||||
<div className = { `invite-more-dialog invite-buttons${this._isAddDisabled() ? ' disabled' : ''}` }>
|
||||
<a
|
||||
aria-label = { t('dialog.Cancel') }
|
||||
className = 'invite-more-dialog invite-buttons-cancel'
|
||||
onClick = { this._onClearItems }>
|
||||
onClick = { this._onClearItems }
|
||||
onKeyPress = { this._onClearItemsKeyPress }
|
||||
role = 'button'
|
||||
tabIndex = { 0 }>
|
||||
{t('dialog.Cancel')}
|
||||
</a>
|
||||
<a
|
||||
aria-label = { t('addPeople.add') }
|
||||
className = 'invite-more-dialog invite-buttons-add'
|
||||
onClick = { this._onSubmit }>
|
||||
onClick = { this._onSubmit }
|
||||
onKeyPress = { this._onSubmitKeyPress }
|
||||
role = 'button'
|
||||
tabIndex = { 0 }>
|
||||
{t('addPeople.add')}
|
||||
</a>
|
||||
</div>
|
||||
|
@ -480,9 +522,9 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
|
|||
</span>
|
||||
<span>
|
||||
<a
|
||||
aria-label = { supportLink }
|
||||
href = { supportLink }
|
||||
rel = 'noopener noreferrer'
|
||||
target = '_blank'>
|
||||
rel = 'noopener noreferrer'>
|
||||
{ t('inlineDialogFailure.support') }
|
||||
</a>
|
||||
</span>
|
||||
|
|
|
@ -71,7 +71,9 @@ class KeyboardShortcutsDialog extends Component<Props> {
|
|||
<li
|
||||
className = 'shortcuts-list__item'
|
||||
key = { keyboardKey }>
|
||||
<span className = 'shortcuts-list__description'>
|
||||
<span
|
||||
aria-label = { this.props.t(translationKey) }
|
||||
className = 'shortcuts-list__description'>
|
||||
{ this.props.t(translationKey) }
|
||||
</span>
|
||||
<span className = 'item-action'>
|
||||
|
|
|
@ -102,7 +102,9 @@ class LargeVideo extends Component<Props> {
|
|||
* another container for the background and the
|
||||
* largeVideoWrapper in order to hide/show them.
|
||||
*/}
|
||||
<div id = 'largeVideoWrapper'>
|
||||
<div
|
||||
id = 'largeVideoWrapper'
|
||||
role = 'figure' >
|
||||
<video
|
||||
autoPlay = { !_noAutoPlayVideo }
|
||||
id = 'largeVideo'
|
||||
|
|
|
@ -89,7 +89,9 @@ class LobbySection extends PureComponent<Props, State> {
|
|||
return (
|
||||
<>
|
||||
<div id = 'lobby-section'>
|
||||
<p className = 'description'>
|
||||
<p
|
||||
className = 'description'
|
||||
role = 'banner'>
|
||||
{ t('lobby.enableDialogText') }
|
||||
</p>
|
||||
<div className = 'control-row'>
|
||||
|
|
|
@ -306,11 +306,15 @@ class LocalRecordingInfoDialog extends Component<Props, State> {
|
|||
<div className = 'localrec-control-action-links'>
|
||||
<div className = 'localrec-control-action-link'>
|
||||
{ isEngaged ? <a
|
||||
onClick = { this._onStop }>
|
||||
onClick = { this._onStop }
|
||||
role = 'button'
|
||||
tabIndex = { 0 }>
|
||||
{ t('localRecording.stop') }
|
||||
</a>
|
||||
: <a
|
||||
onClick = { this._onStart }>
|
||||
onClick = { this._onStart }
|
||||
role = 'button'
|
||||
tabIndex = { 0 }>
|
||||
{ t('localRecording.start') }
|
||||
</a>
|
||||
}
|
||||
|
|
|
@ -81,9 +81,9 @@ class Notification extends AbstractNotification<Props> {
|
|||
|
||||
// the id is used for testing the UI
|
||||
return (
|
||||
<div data-testid = { this._getDescriptionKey() } >
|
||||
<p data-testid = { this._getDescriptionKey() } >
|
||||
{ description }
|
||||
</div>
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
import { FlagGroup } from '@atlaskit/flag';
|
||||
import React from 'react';
|
||||
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { connect } from '../../../base/redux';
|
||||
import AbstractNotificationsContainer, {
|
||||
_abstractMapStateToProps,
|
||||
|
@ -16,7 +17,12 @@ type Props = AbstractProps & {
|
|||
/**
|
||||
* Whether we are a SIP gateway or not.
|
||||
*/
|
||||
_iAmSipGateway: boolean
|
||||
_iAmSipGateway: boolean,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -42,6 +48,7 @@ class NotificationsContainer extends AbstractNotificationsContainer<Props> {
|
|||
return (
|
||||
<FlagGroup
|
||||
id = 'notifications-container'
|
||||
label = { this.props.t('notify.groupTitle') }
|
||||
onDismissed = { this._onDismissed }>
|
||||
{ this._renderFlags() }
|
||||
</FlagGroup>
|
||||
|
@ -73,7 +80,6 @@ class NotificationsContainer extends AbstractNotificationsContainer<Props> {
|
|||
key = { uid }
|
||||
onDismissed = { this._onDismissed }
|
||||
uid = { uid } />
|
||||
|
||||
);
|
||||
});
|
||||
}
|
||||
|
@ -96,4 +102,4 @@ function _mapStateToProps(state) {
|
|||
}
|
||||
|
||||
|
||||
export default connect(_mapStateToProps)(NotificationsContainer);
|
||||
export default translate(connect(_mapStateToProps)(NotificationsContainer));
|
||||
|
|
|
@ -29,12 +29,20 @@ class PageReloadOverlay extends AbstractPageReloadOverlay<Props> {
|
|||
|
||||
return (
|
||||
<OverlayFrame isLightOverlay = { isNetworkFailure }>
|
||||
<div className = 'inlay'>
|
||||
<div
|
||||
aria-describedby = 'reload_overlay_text'
|
||||
aria-labelledby = 'reload_overlay_title'
|
||||
className = 'inlay'
|
||||
role = 'dialog'>
|
||||
<span
|
||||
className = 'reload_overlay_title'>
|
||||
className = 'reload_overlay_title'
|
||||
id = 'reload_overlay_title'
|
||||
role = 'heading'>
|
||||
{ t(title) }
|
||||
</span>
|
||||
<span className = 'reload_overlay_text'>
|
||||
<span
|
||||
className = 'reload_overlay_text'
|
||||
id = 'reload_overlay_text'>
|
||||
{ t(message, { seconds: timeLeft }) }
|
||||
</span>
|
||||
{ this._renderProgressBar() }
|
||||
|
|
|
@ -30,12 +30,17 @@ class UserMediaPermissionsOverlay extends AbstractUserMediaPermissionsOverlay {
|
|||
<div className = 'inlay'>
|
||||
<span className = 'inlay__icon icon-microphone' />
|
||||
<span className = 'inlay__icon icon-camera' />
|
||||
<h3 className = 'inlay__title'>
|
||||
<h3
|
||||
aria-label = { t('startupoverlay.genericTitle') }
|
||||
className = 'inlay__title'
|
||||
role = 'alert' >
|
||||
{
|
||||
t('startupoverlay.genericTitle')
|
||||
}
|
||||
</h3>
|
||||
<span className = 'inlay__text'>
|
||||
<span
|
||||
className = 'inlay__text'
|
||||
role = 'alert' >
|
||||
{
|
||||
translateToHTML(t,
|
||||
`userMedia.${browser}GrantPermissions`)
|
||||
|
@ -43,7 +48,9 @@ class UserMediaPermissionsOverlay extends AbstractUserMediaPermissionsOverlay {
|
|||
</span>
|
||||
</div>
|
||||
<div className = 'policy overlay__policy'>
|
||||
<p className = 'policy__text'>
|
||||
<p
|
||||
className = 'policy__text'
|
||||
role = 'alert'>
|
||||
{ translateToHTML(t, 'startupoverlay.policyText') }
|
||||
</p>
|
||||
{
|
||||
|
@ -66,7 +73,9 @@ class UserMediaPermissionsOverlay extends AbstractUserMediaPermissionsOverlay {
|
|||
if (policyLogoSrc) {
|
||||
return (
|
||||
<div className = 'policy__logo'>
|
||||
<img src = { policyLogoSrc } />
|
||||
<img
|
||||
alt = { this.props.t('welcomepage.logo.policyLogo') }
|
||||
src = { policyLogoSrc } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ export const InviteButton = () => {
|
|||
|
||||
return (
|
||||
<ParticipantInviteButton
|
||||
aria-label = { t('toolbar.accessibilityLabel.invite') }
|
||||
aria-label = { t('participantsPane.actions.invite') }
|
||||
onClick = { onInvite }>
|
||||
<Icon
|
||||
size = { 20 }
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { getIsParticipantAudioMuted, getIsParticipantVideoMuted } from '../../base/tracks';
|
||||
|
@ -38,6 +39,7 @@ export const MeetingParticipantItem = ({
|
|||
onLeave,
|
||||
participant
|
||||
}: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const isAudioMuted = useSelector(getIsParticipantAudioMuted(participant));
|
||||
const isVideoMuted = useSelector(getIsParticipantVideoMuted(participant));
|
||||
|
||||
|
@ -49,7 +51,9 @@ export const MeetingParticipantItem = ({
|
|||
onLeave = { onLeave }
|
||||
participant = { participant }
|
||||
videoMuteState = { isVideoMuted ? MediaState.Muted : MediaState.Unmuted }>
|
||||
<ParticipantActionEllipsis onClick = { onContextMenu } />
|
||||
<ParticipantActionEllipsis
|
||||
aria-label = { t('MeetingParticipantItem.ParticipantActionEllipsis.options') }
|
||||
onClick = { onContextMenu } />
|
||||
</ParticipantItem>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -30,6 +30,12 @@ export const ParticipantsPane = () => {
|
|||
const { t } = useTranslation();
|
||||
|
||||
const closePane = useCallback(() => dispatch(close(), [ dispatch ]));
|
||||
const closePaneKeyPress = useCallback(e => {
|
||||
if (closePane && (e.key === ' ' || e.key === 'Enter')) {
|
||||
e.preventDefault();
|
||||
closePane();
|
||||
}
|
||||
}, [ closePane ]);
|
||||
const muteAll = useCallback(() => dispatch(openDialog(MuteEveryoneDialog)), [ dispatch ]);
|
||||
|
||||
return (
|
||||
|
@ -41,7 +47,12 @@ export const ParticipantsPane = () => {
|
|||
) }>
|
||||
<div className = 'participants_pane-content'>
|
||||
<Header>
|
||||
<Close onClick = { closePane } />
|
||||
<Close
|
||||
aria-label = { t('participantsPane.close', 'Close') }
|
||||
onClick = { closePane }
|
||||
onKeyPress = { closePaneKeyPress }
|
||||
role = 'button'
|
||||
tabIndex = { 0 } />
|
||||
</Header>
|
||||
<Container>
|
||||
<LobbyParticipantList />
|
||||
|
|
|
@ -183,6 +183,9 @@ class Prejoin extends Component<Props, State> {
|
|||
this._onDropdownClose = this._onDropdownClose.bind(this);
|
||||
this._onOptionsClick = this._onOptionsClick.bind(this);
|
||||
this._setName = this._setName.bind(this);
|
||||
this._onJoinConferenceWithoutAudioKeyPress = this._onJoinConferenceWithoutAudioKeyPress.bind(this);
|
||||
this._showDialogKeyPress = this._showDialogKeyPress.bind(this);
|
||||
this._onJoinKeyPress = this._onJoinKeyPress.bind(this);
|
||||
}
|
||||
_onJoinButtonClick: () => void;
|
||||
|
||||
|
@ -205,6 +208,22 @@ class Prejoin extends Component<Props, State> {
|
|||
this.props.joinConference();
|
||||
}
|
||||
|
||||
_onJoinKeyPress: (Object) => void;
|
||||
|
||||
/**
|
||||
* KeyPress handler for accessibility.
|
||||
*
|
||||
* @param {Object} e - The key event to handle.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onJoinKeyPress(e) {
|
||||
if (e.key === ' ' || e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
this._onJoinButtonClick();
|
||||
}
|
||||
}
|
||||
|
||||
_onToggleButtonClick: () => void;
|
||||
|
||||
/**
|
||||
|
@ -283,6 +302,40 @@ class Prejoin extends Component<Props, State> {
|
|||
this._onDropdownClose();
|
||||
}
|
||||
|
||||
_showDialogKeyPress: (Object) => void;
|
||||
|
||||
/**
|
||||
* KeyPress handler for accessibility.
|
||||
*
|
||||
* @param {Object} e - The key event to handle.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_showDialogKeyPress(e) {
|
||||
if (e.key === ' ' || e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
this._showDialog();
|
||||
}
|
||||
}
|
||||
|
||||
_onJoinConferenceWithoutAudioKeyPress: (Object) => void;
|
||||
|
||||
/**
|
||||
* KeyPress handler for accessibility.
|
||||
*
|
||||
* @param {Object} e - The key event to handle.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onJoinConferenceWithoutAudioKeyPress(e) {
|
||||
if (this.props.joinConferenceWithoutAudio
|
||||
&& (e.key === ' '
|
||||
|| e.key === 'Enter')) {
|
||||
e.preventDefault();
|
||||
this.props.joinConferenceWithoutAudio();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
|
@ -305,7 +358,8 @@ class Prejoin extends Component<Props, State> {
|
|||
visibleButtons
|
||||
} = this.props;
|
||||
|
||||
const { _closeDialog, _onDropdownClose, _onJoinButtonClick, _onOptionsClick, _setName, _showDialog } = this;
|
||||
const { _closeDialog, _onDropdownClose, _onJoinButtonClick, _onJoinKeyPress, _showDialogKeyPress,
|
||||
_onJoinConferenceWithoutAudioKeyPress, _onOptionsClick, _setName, _showDialog } = this;
|
||||
const { showJoinByPhoneButtons, showError } = this.state;
|
||||
|
||||
return (
|
||||
|
@ -322,10 +376,16 @@ class Prejoin extends Component<Props, State> {
|
|||
{showJoinActions && (
|
||||
<div className = 'prejoin-input-area-container'>
|
||||
<div className = 'prejoin-input-area'>
|
||||
<label
|
||||
className = 'prejoin-input-area-label'
|
||||
htmlFor = { 'Prejoin-input-field-id' } >
|
||||
{ t('dialog.enterDisplayNameToJoin') }</label>
|
||||
<InputField
|
||||
autoComplete = { 'name' }
|
||||
autoFocus = { true }
|
||||
className = { showError ? 'error' : '' }
|
||||
hasError = { showError }
|
||||
id = { 'Prejoin-input-field-id' }
|
||||
onChange = { _setName }
|
||||
onSubmit = { joinConference }
|
||||
placeHolder = { t('dialog.enterDisplayName') }
|
||||
|
@ -341,7 +401,10 @@ class Prejoin extends Component<Props, State> {
|
|||
<div
|
||||
className = 'prejoin-preview-dropdown-btn'
|
||||
data-testid = 'prejoin.joinWithoutAudio'
|
||||
onClick = { joinConferenceWithoutAudio }>
|
||||
onClick = { joinConferenceWithoutAudio }
|
||||
onKeyPress = { _onJoinConferenceWithoutAudioKeyPress }
|
||||
role = 'button'
|
||||
tabIndex = { 0 }>
|
||||
<Icon
|
||||
className = 'prejoin-preview-dropdown-icon'
|
||||
size = { 24 }
|
||||
|
@ -350,7 +413,10 @@ class Prejoin extends Component<Props, State> {
|
|||
</div>
|
||||
{hasJoinByPhoneButton && <div
|
||||
className = 'prejoin-preview-dropdown-btn'
|
||||
onClick = { _showDialog }>
|
||||
onClick = { _showDialog }
|
||||
onKeyPress = { _showDialogKeyPress }
|
||||
role = 'button'
|
||||
tabIndex = { 0 }>
|
||||
<Icon
|
||||
className = 'prejoin-preview-dropdown-icon'
|
||||
data-testid = 'prejoin.joinByPhone'
|
||||
|
@ -363,9 +429,15 @@ class Prejoin extends Component<Props, State> {
|
|||
onClose = { _onDropdownClose }>
|
||||
<ActionButton
|
||||
OptionsIcon = { showJoinByPhoneButtons ? IconArrowUp : IconArrowDown }
|
||||
ariaDropDownLabel = { t('prejoin.joinWithoutAudio') }
|
||||
ariaLabel = { t('prejoin.joinMeeting') }
|
||||
ariaPressed = { showJoinByPhoneButtons }
|
||||
hasOptions = { true }
|
||||
onClick = { _onJoinButtonClick }
|
||||
onKeyPress = { _onJoinKeyPress }
|
||||
onOptionsClick = { _onOptionsClick }
|
||||
role = 'button'
|
||||
tabIndex = { 0 }
|
||||
testId = 'prejoin.joinMeeting'
|
||||
type = 'primary'>
|
||||
{ t('prejoin.joinMeeting') }
|
||||
|
|
|
@ -172,9 +172,7 @@ class CountryPicker extends PureComponent<Props, State> {
|
|||
* @param {Object} e - The synthetic event.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onCountrySelectorClick(e) {
|
||||
e.stopPropagation();
|
||||
|
||||
_onCountrySelectorClick() {
|
||||
this.setState({
|
||||
isOpen: !this.setState.isOpen
|
||||
});
|
||||
|
@ -215,7 +213,8 @@ class CountryPicker extends PureComponent<Props, State> {
|
|||
* @returns {void}
|
||||
*/
|
||||
_onKeyPress(e) {
|
||||
if (e.key === 'Enter') {
|
||||
if (e.key === ' ' || e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
this.props.onSubmit();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import { Icon, IconArrowDown } from '../../../base/icons';
|
||||
|
||||
|
@ -23,10 +23,18 @@ type Props = {
|
|||
* @returns {ReactElement}
|
||||
*/
|
||||
function CountrySelector({ country: { code, dialCode }, onClick }: Props) {
|
||||
const onKeyPressHandler = useCallback(e => {
|
||||
if (onClick && (e.key === ' ' || e.key === 'Enter')) {
|
||||
e.preventDefault();
|
||||
onClick();
|
||||
}
|
||||
}, [ onClick ]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className = 'cpick-selector'
|
||||
onClick = { onClick }>
|
||||
onClick = { onClick }
|
||||
onKeyPress = { onKeyPressHandler }>
|
||||
<div className = { `prejoin-dialog-flag iti-flag ${code}` } />
|
||||
<span>{`+${dialCode}`}</span>
|
||||
<Icon
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue