1
Fork 0
mirror of https://github.com/jech/galene.git synced 2024-12-22 23:35:46 +01:00

changes for keyboard and scrennreader users

This commit is contained in:
jjsa 2024-12-14 13:19:39 +01:00
parent 2c72a27453
commit 999ef3ba7b
5 changed files with 482 additions and 79 deletions

39
KEYBOARD Normal file
View file

@ -0,0 +1,39 @@
# Use of the keyboard with Galene
People wich are not able to use a mouse need to have other possibilities.
Some user will use the keyboard in order to navigate to the galene Web
Interface and will use the Tab Key to select the wanted element and
other key as arrow up, down, space.
User with visual impairement, may use a screenreader.
For keyboard user we can use the following keys:
- 'u' goto user list
- 'c' go to the chat input box
- 'r' raise, unraise hand
- 'm' mute, unmute
- Esc close contextual menu, close the setting, close the chat box,
close the user list
For the screenreader user, it is difficult to provide shortcuts, most
keys or keys combination are reserved from the OS the screenreader and
the browser.
In order to offer a faster way for selecting the wanted element, we use
thw header navigation (h1, h2. h3).
- The key 1 will got to the main Title of the page.
- The key 2 will allow to select one of the area user list, chat box or media area.
- The key 3 allow to navigate from message to message.
- The Tab and Esc key work as for the normal keyboard user
Screenreader shall announce the error and warning textes issued by galene,
this is also implemented.

View file

@ -1,3 +1,15 @@
button {
background: none;
border: 0;
}
.screenreader {
width: 0px;
height: 0px;
overflow: hidden;
margin: 0;
}
.nav-fixed .topnav { .nav-fixed .topnav {
z-index: 1039; z-index: 1039;
} }
@ -109,7 +121,7 @@
display: none; display: none;
} }
.sidenav .user-logout a { .sidenav .user-logout button {
font-size: 1em; font-size: 1em;
padding: 7px 0 0; padding: 7px 0 0;
color: #e4157e; color: #e4157e;
@ -117,7 +129,7 @@
line-height: .7; line-height: .7;
} }
.sidenav .user-logout a:hover { .sidenav .user-logout button:hover {
color: #ab0659; color: #ab0659;
} }
@ -269,12 +281,12 @@
} }
.full-width { .full-width {
width: calc(100vw - 200px); /*width: calc(100vw - 200px);*/
height: calc(var(--vh, 1vh) * 100 - 56px); height: calc(var(--vh, 1vh) * 100 - 56px);
} }
.full-width-active { .full-width-active {
width: 100vw; /* width: 100vw;*/
} }
.container { .container {
@ -312,7 +324,6 @@
resize: none; resize: none;
overflow: hidden; overflow: hidden;
padding: 5px; padding: 5px;
outline: none;
border: none; border: none;
text-indent: 5px; text-indent: 5px;
box-shadow: none; box-shadow: none;
@ -440,6 +451,13 @@ textarea.form-reply {
white-space: pre-wrap; white-space: pre-wrap;
} }
.message button {
margin: 1em;
padding: .4em .8em;
border-radius: .7em;
background: #a1ceff;
}
.video-container { .video-container {
height: calc(var(--vh, 1vh) * 100 - 56px); height: calc(var(--vh, 1vh) * 100 - 56px);
position: relative; position: relative;
@ -467,7 +485,7 @@ textarea.form-reply {
} }
.collapse-video { .collapse-video {
left: 30px; left: calc(-100vw);
right: inherit; right: inherit;
} }
@ -518,6 +536,7 @@ textarea.form-reply {
background: linear-gradient(180deg, rgb(0 0 0 / 20%) 0%, rgb(0 0 0 / 50%) 0%, rgb(0 0 0 / 70%) 100%); background: linear-gradient(180deg, rgb(0 0 0 / 20%) 0%, rgb(0 0 0 / 50%) 0%, rgb(0 0 0 / 70%) 100%);
} }
.peer:focus-within > .video-controls, .peer:focus-within > .top-video-controls,
.peer:hover > .video-controls, .peer:hover > .top-video-controls { .peer:hover > .video-controls, .peer:hover > .top-video-controls {
opacity: 1; opacity: 1;
} }
@ -529,6 +548,10 @@ textarea.form-reply {
cursor: pointer; cursor: pointer;
} }
.peer button i {
color: #eaeaea;
}
.video-controls span:last-child, .top-video-controls span:last-child { .video-controls span:last-child, .top-video-controls span:last-child {
margin-right: 0; margin-right: 0;
} }
@ -583,6 +606,7 @@ textarea.form-reply {
transition: opacity .5s ease-out; transition: opacity .5s ease-out;
} }
.video-controls .volume:focus-within,
.video-controls .volume:hover { .video-controls .volume:hover {
--ov: 1; --ov: 1;
--dv: inline; --dv: inline;
@ -663,7 +687,7 @@ textarea.form-reply {
} }
.nav-cancel, .muted, .nav-cancel label, .muted label { .nav-cancel, .muted, .nav-cancel label, .muted label {
color: #d03e3e color: #d03e3e;
} }
.nav-cancel:hover, .muted:hover, .nav-cancel label:hover, .muted label:hover { .nav-cancel:hover, .muted:hover, .nav-cancel label:hover, .muted label:hover {
@ -675,7 +699,7 @@ textarea.form-reply {
font-size: 25px; font-size: 25px;
} }
.nav-more { #openside .nav-more {
padding-top: 5px; padding-top: 5px;
margin-left: 0; margin-left: 0;
} }
@ -786,21 +810,25 @@ h1 {
} }
#filterselect { #filterselect {
width: 8em;
text-align-last: center; text-align-last: center;
margin-right: 0.4em; margin-right: 0.4em;
} }
#sendselect { #sendselect {
width: 8em;
text-align-last: center; text-align-last: center;
margin-right: 0.4em; margin-right: 0.4em;
} }
#simulcastselect { #simulcastselect {
width: 8em;
text-align-last: center; text-align-last: center;
margin-right: 0.4em; margin-right: 0.4em;
} }
#requestselect { #requestselect {
width: 8em;
text-align-last: center; text-align-last: center;
} }
@ -864,14 +892,6 @@ h1 {
overflow-y: hidden; overflow-y: hidden;
} }
#input:focus {
outline: none;
}
#inputbutton:focus {
outline: none;
}
#resizer { #resizer {
width: 4px; width: 4px;
margin-left: -4px; margin-left: -4px;
@ -955,7 +975,7 @@ h1 {
overflow-y: hidden; overflow-y: hidden;
} }
.sidenav a { .sidenav button {
padding: 10px 20px; padding: 10px 20px;
text-decoration: none; text-decoration: none;
font-size: 30px; font-size: 30px;
@ -965,7 +985,11 @@ h1 {
line-height: 1.0; line-height: 1.0;
} }
.sidenav a:hover { .sidenav button:hover {
color: #c2a4e0;
}
.sidenav button:hover {
color: #c2a4e0; color: #c2a4e0;
} }
@ -1111,9 +1135,22 @@ header .collapse:hover {
} }
/* Shrinking the sidebar from 200px to 0px */ /* Shrinking the sidebar from 200px to 0px */
#sidebarnav {
display: none;
width: 250px;
}
#sidebarnav[open=true] {
display: block;
}
#left-sidebar.active { #left-sidebar.active {
min-width: 0; display: none;
max-width: 0; }
#left-sidebar:not(.active) {
display: block;
width:200px;
} }
#left-sidebar .sidebar-header strong { #left-sidebar .sidebar-header strong {
@ -1148,17 +1185,23 @@ header .collapse:hover {
cursor: pointer; cursor: pointer;
overflow: hidden; overflow: hidden;
white-space: pre; white-space: pre;
display: block;
width: 100%;
text-align: left;
} }
#left-sidebar.active #users > div { #left-sidebar.active #users > button {
padding: 10px 5px !important; padding: 10px 5px !important;
} }
#users > div:hover { #users > button:hover {
background-color: #f2f2f2; background-color: #f2f2f2;
} }
#users > button:focus, #users > button:focus-visible {
outline-offset: -2px;
}
#users > div::before { #users > button::before {
content: "\f111"; content: "\f111";
font-family: 'Font Awesome 6 Free'; font-family: 'Font Awesome 6 Free';
color: #20b91e; color: #20b91e;
@ -1166,11 +1209,11 @@ header .collapse:hover {
font-weight: 900; font-weight: 900;
} }
#users > div.user-status-raisehand::before { #users > button.user-status-raisehand::before {
content: "\f256"; content: "\f256";
} }
#users > div::after { #users > button::after {
font-family: 'Font Awesome 6 Free'; font-family: 'Font Awesome 6 Free';
color: #808080; color: #808080;
margin-left: 5px; margin-left: 5px;
@ -1178,11 +1221,11 @@ header .collapse:hover {
float: right; float: right;
} }
#users > div.user-status-microphone::after { #users > button.user-status-microphone::after {
content: "\f130"; content: "\f130";
} }
#users > div.user-status-camera::after { #users > button.user-status-camera::after {
content: "\f030"; content: "\f030";
} }
@ -1231,6 +1274,10 @@ header .collapse:hover {
display: none; display: none;
} }
.video-on #chat {
display: none;
}
.video-container { .video-container {
position: fixed; position: fixed;
height: calc(var(--vh, 1vh) * 100 - 56px); height: calc(var(--vh, 1vh) * 100 - 56px);
@ -1261,7 +1308,7 @@ header .collapse:hover {
flex: 100%; flex: 100%;
width: 100vw; width: 100vw;
/* chat is always visible here */ /* chat is always visible here */
display: block !important; display: block /*!important*/;
} }
.coln-right { .coln-right {
@ -1273,6 +1320,7 @@ header .collapse:hover {
width: 100vw; width: 100vw;
} }
/*
#left-sidebar.active { #left-sidebar.active {
min-width: 200px; min-width: 200px;
max-width: 200px; max-width: 200px;
@ -1282,10 +1330,11 @@ header .collapse:hover {
min-width: 0; min-width: 0;
max-width: 0; max-width: 0;
} }
*/
/* Reappearing the sidebar on toggle button click */ /* Reappearing the sidebar on toggle button click */
#left-sidebar { #left-sidebar {
margin-left: 0; margin-left: 0;
z-index: 1;
} }
#left-sidebar .sidebar-header strong { #left-sidebar .sidebar-header strong {
@ -1368,6 +1417,12 @@ header .collapse:hover {
margin-right: 10px; margin-right: 10px;
} }
.contextualMenu button {
outline-offset: 3px;
display: inline-block;
width: calc(100% - 2em);
}
#invite-dialog { #invite-dialog {
background-color: #eee; background-color: #eee;
} }
@ -1394,3 +1449,28 @@ header .collapse:hover {
.toastify.info .toast-close { .toastify.info .toast-close {
color: #000; color: #000;
} }
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
/* wrong preset for some browser! */
video::-webkit-media-controls-enclosure {
display: none !important;
}
video::-webkit-media-controls-panel {
display: none !important;
}
video::-webkit-media-controls {
display: none !important;
}

View file

@ -13,71 +13,75 @@
<link rel="stylesheet" type="text/css" href="/third-party/fontawesome/css/regular.min.css"/> <link rel="stylesheet" type="text/css" href="/third-party/fontawesome/css/regular.min.css"/>
<link rel="stylesheet" type="text/css" href="/third-party/toastify/toastify.css"/> <link rel="stylesheet" type="text/css" href="/third-party/toastify/toastify.css"/>
<link rel="stylesheet" type="text/css" href="/third-party/contextual/contextual.css"/> <link rel="stylesheet" type="text/css" href="/third-party/contextual/contextual.css"/>
<script src="/key.js" ></script>
</head> </head>
<body> <body>
<div id="main" class="app"> <div id="srSpeak" class="sr-only" aria-live="assertive" aria-atomic="true"></div>
<main id="main" class="app">
<div class="row full-height"> <div class="row full-height">
<nav id="left-sidebar"> <nav id="left-sidebar" class="active">
<div class="users-header"> <div class="users-header">
<div class="galene-header">Galène</div> <div class="galene-header">Galène</div>
</div> </div>
<div class="header-sep"></div> <h2 class="screenreader">User list</h2>
<div rollr="region" class="header-sep"></div>
<div id="users"></div> <div id="users"></div>
</nav> </nav>
<div class="container"> <div class="container">
<header> <header>
<nav class="topnav navbar navbar-expand navbar-light fixed-top"> <nav class="topnav navbar navbar-expand navbar-light fixed-top">
<div id="header"> <div id="header">
<div class="collapse" title="Collapse left panel" id="sidebarCollapse"> <button class="collapse" title="Collapse left panel" id="sidebarCollapse" aria-label="Collapse left panel">
<i class="fas fa-align-left" aria-hidden="true"></i> <i class="fas fa-align-left" aria-hidden="true"></i>
</div> </button>
<h1 id="title" class="header-title">Galène</h1> <h1 id="title" class="header-title">Galène</h1>
</div> </div>
<ul class="nav-menu"> <ul class="nav-menu">
<li> <li>
<button id="presentbutton" class="invisible btn btn-success"> <button id="presentbutton" class="invisible btn btn-success" aria-label=" Enable">
<i class="fas fa-play" aria-hidden="true"></i><span class="nav-text"> Enable</span> <i class="fas fa-play" aria-hidden="true"></i><span class="nav-text"> Enable</span>
</button> </button>
</li> </li>
<li> <li>
<button id="unpresentbutton" class="invisible btn btn-cancel"> <button id="unpresentbutton" class="invisible btn btn-cancel" aria-label=" Disable">
<i class="fas fa-stop" aria-hidden="true"></i><span class="nav-text"> Disable</span> <i class="fas fa-stop" aria-hidden="true"></i><span class="nav-text"> Disable</span>
</button> </button>
</li> </li>
<li> <li>
<div id="mutebutton" class="nav-link nav-button"> <button id="mutebutton" class="nav-link nav-button" aria-label="Mute">
<span><i class="fas fa-microphone-slash" aria-hidden="true"></i></span> <span><i class="fas fa-microphone-slash" aria-hidden="true"></i></span>
<label>Mute</label> <label>Mute</label>
</div> </button>
</li> </li>
<li> <li>
<div id="sharebutton" class="invisible nav-link nav-button"> <button id="sharebutton" class="invisible nav-link nav-button" aria-label="Share Screen">
<span><i class="fas fa-share-square" aria-hidden="true"></i></span> <span><i class="fas fa-share-square" aria-hidden="true"></i></span>
<label>Share Screen</label> <label>Share Screen</label>
</div> </button>
</li> </li>
<li> <li>
<div class="nav-button nav-link nav-more" id="openside"> <button class="nav-button nav-link nav-more" id="openside" title="Open settings" aria-label="Open settings">
<span><i class="fas fa-ellipsis-v" aria-hidden="true"></i></span> <span><i class="fas fa-ellipsis-v" aria-hidden="true"></i></span>
</div> </button>
</li> </li>
</ul> </ul>
</nav> </nav>
</header> </header>
<div class="row full-width" id="mainrow"> <div class="row full-width video-on" id="mainrow">
<div class="coln-left" id="left"> <div class="coln-left invisible" id="left" >
<div id="chat"> <div role="region" id="chat">
<h2 class="screenreader">Chat box</h2>
<div id="chatbox"> <div id="chatbox">
<div class="close-chat" id="close-chat" title="Hide chat"> <button class="close-chat" id="close-chat" title="Hide chat" aria-label="Hide chat">
<span class="close-icon"></span> <span class="close-icon"></span>
</div> </button>
<div id="box"></div> <div id="box" tabindex="0" aria-label="Chat Area"></div>
<div class="reply"> <div class="reply">
<form id="inputform"> <form id="inputform">
<textarea id="input" class="form-reply"></textarea> <textarea id="input" class="form-reply" aria-label="Enter message"></textarea>
<input id="inputbutton" type="submit" value="&#10148;" class="btn btn-default"/> <input id="inputbutton" type="submit" value="&#10148;" class="btn btn-default" aria-label="Send message"/>
</form> </form>
</div> </div>
</div> </div>
@ -85,16 +89,16 @@
</div> </div>
<div id="resizer"></div> <div id="resizer"></div>
<div class="coln-right" id="right"> <div class="coln-right" id="right">
<span class="show-video blink invisible" id="show-video"> <button class="show-video blink invisible" id="show-video" aria-label="Show video">
<i class="fas fa-exchange-alt" aria-hidden="true"></i> <i class="fas fa-exchange-alt" aria-hidden="true"></i>
</span> </button>
<div class="chat-btn show-chat invisible" id="show-chat"> <button class="chat-btn show-chat" id="show-chat" aria-label="Show chat">
<i class="far fa-comment-alt icon-chat" title="Show chat"></i> <i class="far fa-comment-alt icon-chat" title="Show chat"></i>
</div> </button>
<div class="chat-btn collapse-video invisible" id="collapse-video"> <button class="chat-btn collapse-video invisible" id="collapse-video" aria-label="Hide video and show chat">
<i class="far fa-comment-alt icon-chat" title="Hide video and show chat"></i> <i class="far fa-comment-alt icon-chat" title="Hide video and show chat"></i>
</div> </button>
<div class="video-container invisible" id="video-container"> <div class="video-container invisible" id="video-container"><h2 class="screenreader">Video area</h2>
<div id="expand-video" class="expand-video"> <div id="expand-video" class="expand-video">
<div id="peers"></div> <div id="peers"></div>
</div> </div>
@ -142,12 +146,12 @@
</div> </div>
</div> </div>
</div> </div>
</div> </main>
<div id="sidebarnav" class="sidenav"> <div id="sidebarnav" class="sidenav" aria-hidden="true">
<div class="sidenav-header"> <div class="sidenav-header">
<h2>Settings</h2> <h2>Settings</h2>
<a class="closebtn" id="clodeside"><i class="fas fa-times" aria-hidden="true"></i></a> <button class="closebtn" id="clodeside" title="Close settings" aria-label="Close settings"><i class="fas fa-times"></i>
</div> </div>
<div class="sidenav-content" id="optionsdiv"> <div class="sidenav-content" id="optionsdiv">
<div id="profile" class="profile invisible"> <div id="profile" class="profile invisible">
@ -161,10 +165,10 @@
<span id="chpwspan" class="invisible"><a id="change-password">Change password</a></span> <span id="chpwspan" class="invisible"><a id="change-password">Change password</a></span>
</div> </div>
<div class="user-logout"> <div class="user-logout">
<a id="disconnectbutton"> <button id="disconnectbutton">
<span class="logout-icon"><i class="fas fa-sign-out-alt"></i></span> <span class="logout-icon"><i class="fas fa-sign-out-alt"></i></span>
<span class="logout-text">Logout</span> <span class="logout-text">Logout</span>
</a> </button>
</div> </div>
</div> </div>
</div> </div>
@ -260,32 +264,36 @@
<div id="videocontrols-template" class="invisible"> <div id="videocontrols-template" class="invisible">
<div class="video-controls vc-overlay"> <div class="video-controls vc-overlay">
<div class="controls-button controls-left"> <div class="controls-button controls-left">
<span class="video-play" title="Play video"> <button class="video-play" title="Play video" aria-label="Play video">
<i class="fas fa-play"></i> <i class="fas fa-play"></i>
</span> </button>
<span class="volume" title="Volume"> <span class="volume" title="Volume">
<i class="fas fa-volume-up volume-mute" aria-hidden="true"></i> <i class="fas fa-volume-up volume-mute" tabindex="0" role='button'></i>
<input class="volume-slider" type="range" max="100" value="100" min="0" step="5" > <input class="volume-slider" type="range" max="100" value="100" min="0" step="5" aria-label="Volume slider">
</span> </span>
</div> </div>
<div class="controls-button controls-right"> <div class="controls-button controls-right">
<span class="pip" title="Picture In Picture"> <button class="pip" title="Picture In Picture" aria-label="Picture In Picture">
<i class="far fa-clone" aria-hidden="true"></i> <i class="far fa-clone" aria-hidden="true"></i>
</span> </button>
<span class="fullscreen" title="Fullscreen"> <button class="fullscreen" title="Fullscreen" aria-label="Fullscreen">
<i class="fas fa-expand" aria-hidden="true"></i> <i class="fas fa-expand" aria-hidden="true"></i>
</span> </button>
</div> </div>
</div> </div>
</div> </div>
<div id="topvideocontrols-template" class="invisible"> <div id="topvideocontrols-template" class="invisible">
<div class="top-video-controls"> <div class="top-video-controls">
<div class="controls-button controls-right"> <div class="controls-button controls-right">
<span class="close-icon video-stop" title="Stop video"> <button class="close-icon video-stop" title="Stop video" aria-label="Stop video">
</span> </button>
</div> </div>
</div> </div>
</div> </div>
<div class="sr-help invisible"> <!-- help for translation -->
<span class='voff'>Muted</span>
<span class='von'>On</span>
</div>
<dialog id="invite-dialog"> <dialog id="invite-dialog">
<form method="dialog"> <form method="dialog">

View file

@ -2039,9 +2039,13 @@ function setVolumeButton(muted, button, slider) {
if(!muted) { if(!muted) {
button.classList.remove("fa-volume-mute"); button.classList.remove("fa-volume-mute");
button.classList.add("fa-volume-up"); button.classList.add("fa-volume-up");
let von = document.querySelector('.sr-help .von').textContent;
button.setAttribute("aria-label", von);
} else { } else {
button.classList.remove("fa-volume-up"); button.classList.remove("fa-volume-up");
button.classList.add("fa-volume-mute"); button.classList.add("fa-volume-mute");
let voff = document.querySelector('.sr-help .voff').textContent;
button.setAttribute("aria-label",voff);
} }
if(!(slider instanceof HTMLInputElement)) if(!(slider instanceof HTMLInputElement))
@ -2416,7 +2420,8 @@ function userMenu(elt) {
*/ */
function addUser(id, userinfo) { function addUser(id, userinfo) {
let div = document.getElementById('users'); let div = document.getElementById('users');
let user = document.createElement('div'); //let user = document.createElement('div');
let user = document.createElement('button');
user.id = 'user-' + id; user.id = 'user-' + id;
user.classList.add("user-p"); user.classList.add("user-p");
setUserStatus(id, user, userinfo); setUserStatus(id, user, userinfo);
@ -3126,7 +3131,8 @@ function addToChatbox(id, peerId, dest, nick, time, privileged, history, kind, m
} }
if(doHeader) { if(doHeader) {
let header = document.createElement('p'); // let header = document.createElement('p');
let header = document.createElement('h3');
let user = document.createElement('span'); let user = document.createElement('span');
let u = dest && serverConnection.users[dest]; let u = dest && serverConnection.users[dest];
let name = (u && u.username); let name = (u && u.username);
@ -4131,11 +4137,16 @@ document.getElementById('disconnectbutton').onclick = function(e) {
}; };
function openNav() { function openNav() {
document.getElementById("sidebarnav").style.width = "250px"; document.getElementById("sidebarnav").setAttribute("open",'true');
document.getElementById("sidebarnav").removeAttribute('aria-hidden');
document.querySelector('#sidebarnav .closebtn').focus();
} }
function closeNav() { function closeNav() {
document.getElementById("sidebarnav").style.width = "0"; document.getElementById("sidebarnav").removeAttribute('open');
document.getElementById("sidebarnav").setAttribute("aria-hidden",'true');
document.querySelector('#openside').focus();
} }
document.getElementById('sidebarCollapse').onclick = function(e) { document.getElementById('sidebarCollapse').onclick = function(e) {
@ -4144,9 +4155,9 @@ document.getElementById('sidebarCollapse').onclick = function(e) {
}; };
document.getElementById('openside').onclick = function(e) { document.getElementById('openside').onclick = function(e) {
e.preventDefault(); //e.preventDefault();
let sidewidth = document.getElementById("sidebarnav").style.width; let open = document.getElementById("sidebarnav").getAttribute('open');
if (sidewidth !== "0px" && sidewidth !== "") { if ( open ) {
closeNav(); closeNav();
return; return;
} else { } else {
@ -4165,6 +4176,7 @@ document.getElementById('collapse-video').onclick = function(e) {
setVisibility('collapse-video', false); setVisibility('collapse-video', false);
setVisibility('show-video', true); setVisibility('show-video', true);
hideVideo(true); hideVideo(true);
document.getElementById('mainrow').classList.remove('video-on');
}; };
document.getElementById('show-video').onclick = function(e) { document.getElementById('show-video').onclick = function(e) {
@ -4172,6 +4184,7 @@ document.getElementById('show-video').onclick = function(e) {
setVisibility('video-container', true); setVisibility('video-container', true);
setVisibility('collapse-video', true); setVisibility('collapse-video', true);
setVisibility('show-video', false); setVisibility('show-video', false);
document.getElementById('mainrow').classList.add('video-on');
}; };
document.getElementById('close-chat').onclick = function(e) { document.getElementById('close-chat').onclick = function(e) {

263
static/key.js Normal file
View file

@ -0,0 +1,263 @@
'use strict';
let galeneKeys = {
from : null,
focusAgain : function() {
if ( galeneKeys.from ) {
galeneKeys.from.focus();
//galeneKeys.from = null;
}
else {
document.body.focus();
}
},
leave : function (event) {
if ( event.target.classList.contains('first') ) {
if ( event.shiftKey ) {
let cm = document.querySelector('.contextualMenu');
if ( cm ) {
cm.remove();
setTimeout(galeneKeys.focusAgain, 20);
return;
}
}
}
if ( event.target.classList.contains('last') ) {
if ( ! event.shiftKey ) {
let cm = document.querySelector('.contextualMenu');
if ( cm ) {
cm.remove();
setTimeout(galeneKeys.focusAgain, 20);
return;
}
}
}
if ( event.key === 'Escape' )
setTimeout(galeneKeys.focusAgain, 20);
},
setTabindexContextMenu : function (target) {
let context = document.querySelectorAll('.contextualMenuItemTitle');
if ( context && context.length) {
context.forEach( c => {
let btn = document.createElement('button');
btn.classList.add('contextualMenuItemTitle');
btn.classList.add('contextualJs');
btn.textContent = c.textContent;
btn.addEventListener('click', galeneKeys.menuClick);
c.replaceWith(btn);
});
context = document.querySelectorAll('.contextualMenuItemTitle');
context[0].classList.add('first');
let last = context.length - 1;
context[last].classList.add('last');
galeneKeys.from = target;
context[0].focus();
let pos = target.getBoundingClientRect();
let contextParent = document.querySelector('.contextualMenu');
contextParent.style.top = pos.top+20+'px';
// the following work only if the display with is enough we may have to correct this.
let x = 180;
let w = window.innerWidth;
if ( x + 200 > window.innerWidth ) {
x = w - 205;
}
contextParent.style.left = x+'px';
let menu = document.querySelector('ul.contextualMenu');
} else {
// Enter pressed
galeneKeys.focusAgain();
}
},
menuClick : function(e) {
galeneKeys.focusAgain();
},
processKey : function (event) {
let target = event.target;
let key = event.key;
switch(key) {
case 'Tab':
if ( document.activeElement.classList.contains('contextualMenuItemTitle') ) {
galeneKeys.leave(event);
return;
}
if ( document.activeElement.id === 'sideBarCollapse')
return;
break;
case 'Escape':
let dialog = document.querySelector('dialog');
if ( dialog ) {
let open = dialog.getAttribute('open');
if ( open == '' ) {
dialog.close();
galeneKeys.focusAgain();
return;
}
}
let cm = document.querySelector('.contextualMenu');
if ( cm ) {
cm.remove();
galeneKeys.leave(event);
return;
}
// if the setting are open close them
let sidebar = document.querySelector('#sidebarnav[open]');
if ( sidebar ) {
event.preventDefault();
sidebar.removeAttribute('open');
return;
}
// check if we are within the chat
let active = document.activeElement;
let chat = document.querySelector('#left:not(.invisible)');
if ( chat ) {
event.preventDefault();
chat.classList.add('invisible');
let showChat = document.querySelector('#show-chat');
showChat.classList.remove('invisible');
return;
}
// finally close possibly the user list
let userList = document.querySelector('#left-sidebar:not(.active)');
if ( userList ) {
event.preventDefault();
document.querySelector('#left-sidebar').classList.add('active');
return;
}
break;
case 'Enter':
case ' ':
if ( target.classList.contains('volume-mute') ) {
let state =
target.click();
if ( translate && translate.translateList ) {
translate.setVolumeAria();
}
return;
}
break;
}
switch(target.nodeName) {
case 'INPUT':
let type = target.getAttribute('type');
if ( type && type === 'submit' ) {
return;
}
case 'TEXTAREA':
case 'SELECT':
return;
break;
}
let usercont = null;
switch(key) {
case 'u':
event.preventDefault();
usercont = document.querySelector('#left-sidebar');
if ( !usercont.classList.contains('active')) {
/* focus first user */
let user = document.querySelector('#users .user-p');
if ( user )
user.focus();
} else {
usercont.classList.remove('active');
}
break;
case 'r':
/* raise hand */
let me = document.querySelector('#left-sidebar #users .user-p');
if ( me ) {
if (me.classList.contains('user-status-raisehand') )
me.classList.remove('user-status-raisehand');
else
me.classList.add('user-status-raisehand');
}
break;
case 'm':
let localMute = getSettings().localMute;
localMute = !localMute;
setLocalMute(localMute, true);
break;
case 'c':
/* Chat */
event.preventDefault();
let chat = document.querySelector('#left:not(.invisible)');
if (!chat) {
setVisibility('left', true);
setVisibility('show-chat', false);
resizePeers();
chat = document.querySelector('#left:not(.invisible) textarea');
}
if (chat)
chat.focus();
break;
}
},
userClick : function (e) {
galeneKeys.from = e.target;
setTimeout(galeneKeys.setTabindexContextMenu,20, e.target);
},
addClickListener : function (mutationList) {
for (const mutation of mutationList) {
if (mutation.type === "childList") {
if (mutation && mutation.addedNodes) {
let idx = mutation.addedNodes.length -1;
if (idx >= 0) {
mutation.addedNodes[idx].addEventListener('click', galeneKeys.userClick);
}
}
}
}
},
focusInput : function() {
let input = document.querySelector('#input');
input.focus();
},
collapseSidebar : function(e) {
let sidebar = document.querySelector('#left-sidebar.active');
if ( !sidebar ) {
// focus the first user-p button done but no outline!
//let user = document.querySelector('.user-p');
let userP = document.querySelector('.user-p');
if (userP)
userP.focus();
}
},
setSr : function (sr, toast) {
sr.textContent = toast.textContent;
},
resetSr : function(sr) {
sr.textContent = '';
},
setSrText : function() {
let toast = document.querySelector('.toastify');
let sr = document.querySelector('#srSpeak');
if ( toast ) {
setTimeout(galeneKeys.setSr, 50, sr, toast);
setTimeout(galeneKeys.resetSr, 4000, sr);
}
},
init : function () {
// add mutation oberver
let toObserve = document.querySelector('#users');
if ( toObserve ) {
const observer = new MutationObserver(galeneKeys.addClickListener);
observer.observe(toObserve, {childList:true, subtree:false});
}
const popup = new MutationObserver(galeneKeys.setSrText);
popup.observe(document.body, {childList: true,subtree: false});
// add event listener to the show-chat button
let showChat = document.querySelector('#show-chat');
if ( showChat ) {
showChat.addEventListener('click',galeneKeys.focusInput);
}
let left = document.querySelector('#sidebarCollapse');
if ( left ) {
left.addEventListener('click', galeneKeys.collapseSidebar);
}
},
};
document.addEventListener('keydown', galeneKeys.processKey);
document.addEventListener('DOMContentLoaded', galeneKeys.init);