Change chalkboard to pre tag for people who do not have write access because some screen readers seem to have problems with read only edit boxes.
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
# shellcheck shell=bash disable=SC2034,SC2154
|
# shellcheck shell=bash disable=SC2034,SC2154
|
||||||
|
|
||||||
pkgname=skald-git
|
pkgname=skald-git
|
||||||
pkgver=0.0.0.r1540.g26c7808
|
pkgver=0.0.0.r1541.gd8817f6
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc='Audio-only hall-based conferencing server'
|
pkgdesc='Audio-only hall-based conferencing server'
|
||||||
arch=('x86_64' 'aarch64')
|
arch=('x86_64' 'aarch64')
|
||||||
|
|||||||
@@ -75,13 +75,12 @@ than navigating the user interface. Commands start with a slash character
|
|||||||
message to a given user. Type `/help` to display the list of available
|
message to a given user. Type `/help` to display the list of available
|
||||||
commands.
|
commands.
|
||||||
|
|
||||||
Below the chat input is a session-only virtual chalkboard. It is a
|
Below the chat input is a session-only virtual chalkboard suitable for
|
||||||
multiline text area suitable for commands, code, and notes that should be
|
commands, code, and notes that should be visible to everyone in the hall.
|
||||||
visible to everyone in the hall. Operators and administrators may edit
|
Operators and administrators edit it as a multiline text area and may grant
|
||||||
the chalkboard and may grant or revoke chalkboard editing for connected
|
or revoke chalkboard editing for connected users with `/chalkboard user`
|
||||||
users with `/chalkboard user` and `/unchalkboard user`. Users without
|
and `/unchalkboard user`. Users without edit permission can read and focus
|
||||||
edit permission can read and focus the chalkboard, but it is marked
|
the chalkboard as preformatted text.
|
||||||
read-only and does not trap the Tab key.
|
|
||||||
|
|
||||||
### Inviting users
|
### Inviting users
|
||||||
|
|
||||||
|
|||||||
+23
-3
@@ -829,14 +829,33 @@ h1 {
|
|||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#chalkboard {
|
#chalkboard,
|
||||||
|
#chalkboard-view {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 8rem;
|
min-height: 8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#chalkboard {
|
||||||
resize: vertical;
|
resize: vertical;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#chalkboard-view {
|
||||||
|
border: 1px solid #767676;
|
||||||
|
border-radius: 2px;
|
||||||
|
font-family: monospace;
|
||||||
|
margin: 0;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 2px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
#chalkboard[hidden],
|
||||||
|
#chalkboard-view[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
#chalkboard-copy {
|
#chalkboard-copy {
|
||||||
margin-top: 0.35rem;
|
margin-top: 0.35rem;
|
||||||
}
|
}
|
||||||
@@ -845,11 +864,12 @@ h1 {
|
|||||||
opacity: 0.55;
|
opacity: 0.55;
|
||||||
}
|
}
|
||||||
|
|
||||||
#chalkboard[readonly] {
|
#chalkboard-view {
|
||||||
background: #f3f3f3;
|
background: #f3f3f3;
|
||||||
}
|
}
|
||||||
|
|
||||||
#chalkboard:focus {
|
#chalkboard:focus,
|
||||||
|
#chalkboard-view:focus {
|
||||||
outline: 2px solid #0066cc;
|
outline: 2px solid #0066cc;
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,6 +79,7 @@
|
|||||||
<div id="chalkboard-area">
|
<div id="chalkboard-area">
|
||||||
<h2 id="chalkboard-heading">Virtual chalkboard</h2>
|
<h2 id="chalkboard-heading">Virtual chalkboard</h2>
|
||||||
<textarea id="chalkboard" aria-labelledby="chalkboard-heading"></textarea>
|
<textarea id="chalkboard" aria-labelledby="chalkboard-heading"></textarea>
|
||||||
|
<pre id="chalkboard-view" tabindex="0" role="region" aria-labelledby="chalkboard-heading" hidden></pre>
|
||||||
<button id="chalkboard-copy" type="button" class="btn btn-default" disabled>Copy to clipboard</button>
|
<button id="chalkboard-copy" type="button" class="btn btn-default" disabled>Copy to clipboard</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+83
-37
@@ -2678,6 +2678,36 @@ let chalkboardAnnouncementTimer = null;
|
|||||||
let chalkboardHasPendingAnnouncement = false;
|
let chalkboardHasPendingAnnouncement = false;
|
||||||
let chalkboardPendingHasText = false;
|
let chalkboardPendingHasText = false;
|
||||||
let chalkboardLocalTextPending = null;
|
let chalkboardLocalTextPending = null;
|
||||||
|
let chalkboardText = '';
|
||||||
|
|
||||||
|
function getChalkboardEditor() {
|
||||||
|
return /** @type {HTMLTextAreaElement} */
|
||||||
|
(document.getElementById('chalkboard'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getChalkboardView() {
|
||||||
|
return document.getElementById('chalkboard-view');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} text
|
||||||
|
*/
|
||||||
|
function setChalkboardText(text) {
|
||||||
|
chalkboardText = text;
|
||||||
|
|
||||||
|
let editor = getChalkboardEditor();
|
||||||
|
if(editor && editor.value !== text)
|
||||||
|
editor.value = text;
|
||||||
|
|
||||||
|
let view = getChalkboardView();
|
||||||
|
if(view && view.textContent !== text)
|
||||||
|
view.textContent = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
function chalkboardHasFocus() {
|
||||||
|
let active = document.activeElement;
|
||||||
|
return active === getChalkboardEditor() || active === getChalkboardView();
|
||||||
|
}
|
||||||
|
|
||||||
function clearChalkboardAnnouncementTimer() {
|
function clearChalkboardAnnouncementTimer() {
|
||||||
if(chalkboardAnnouncementTimer) {
|
if(chalkboardAnnouncementTimer) {
|
||||||
@@ -2695,28 +2725,29 @@ function canEditChalkboard() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateChalkboardAccess() {
|
function updateChalkboardAccess() {
|
||||||
let chalkboard = /** @type {HTMLTextAreaElement} */
|
let editor = getChalkboardEditor();
|
||||||
(document.getElementById('chalkboard'));
|
let view = getChalkboardView();
|
||||||
if(!chalkboard)
|
if(!editor || !view)
|
||||||
return;
|
return;
|
||||||
let canEdit = canEditChalkboard();
|
let canEdit = canEditChalkboard();
|
||||||
chalkboard.readOnly = !canEdit;
|
let active = document.activeElement;
|
||||||
chalkboard.setAttribute('aria-readonly', canEdit ? 'false' : 'true');
|
editor.hidden = !canEdit;
|
||||||
chalkboard.setAttribute(
|
view.hidden = canEdit;
|
||||||
'aria-label',
|
editor.setAttribute('aria-label', 'Virtual chalkboard');
|
||||||
canEdit ? 'Virtual chalkboard' : 'Virtual chalkboard, read only',
|
view.setAttribute('aria-label', 'Virtual chalkboard, read only');
|
||||||
);
|
if(active === editor && !canEdit)
|
||||||
|
view.focus();
|
||||||
|
else if(active === view && canEdit)
|
||||||
|
editor.focus();
|
||||||
updateChalkboardCopyButton();
|
updateChalkboardCopyButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateChalkboardCopyButton() {
|
function updateChalkboardCopyButton() {
|
||||||
let chalkboard = /** @type {HTMLTextAreaElement} */
|
|
||||||
(document.getElementById('chalkboard'));
|
|
||||||
let copyButton = /** @type {HTMLButtonElement} */
|
let copyButton = /** @type {HTMLButtonElement} */
|
||||||
(document.getElementById('chalkboard-copy'));
|
(document.getElementById('chalkboard-copy'));
|
||||||
if(!chalkboard || !copyButton)
|
if(!copyButton)
|
||||||
return;
|
return;
|
||||||
copyButton.disabled = chalkboard.value.length === 0;
|
copyButton.disabled = chalkboardText.length === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetChalkboard() {
|
function resetChalkboard() {
|
||||||
@@ -2729,26 +2760,38 @@ function resetChalkboard() {
|
|||||||
clearTimeout(chalkboardSendTimer);
|
clearTimeout(chalkboardSendTimer);
|
||||||
chalkboardSendTimer = null;
|
chalkboardSendTimer = null;
|
||||||
}
|
}
|
||||||
let chalkboard = /** @type {HTMLTextAreaElement} */
|
setChalkboardText('');
|
||||||
(document.getElementById('chalkboard'));
|
|
||||||
if(chalkboard)
|
|
||||||
chalkboard.value = '';
|
|
||||||
updateChalkboardAccess();
|
updateChalkboardAccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} text
|
||||||
|
*/
|
||||||
|
function copyTextWithExecCommand(text) {
|
||||||
|
let fallback = document.createElement('textarea');
|
||||||
|
fallback.value = text;
|
||||||
|
fallback.readOnly = true;
|
||||||
|
fallback.style.position = 'fixed';
|
||||||
|
fallback.style.left = '-9999px';
|
||||||
|
fallback.style.top = '0';
|
||||||
|
document.body.appendChild(fallback);
|
||||||
|
try {
|
||||||
|
fallback.focus();
|
||||||
|
fallback.select();
|
||||||
|
return document.execCommand('copy');
|
||||||
|
} finally {
|
||||||
|
document.body.removeChild(fallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function copyChalkboardToClipboard() {
|
async function copyChalkboardToClipboard() {
|
||||||
let chalkboard = /** @type {HTMLTextAreaElement} */
|
if(chalkboardText.length === 0)
|
||||||
(document.getElementById('chalkboard'));
|
|
||||||
if(!chalkboard || chalkboard.value.length === 0)
|
|
||||||
return;
|
return;
|
||||||
try {
|
try {
|
||||||
if(navigator.clipboard && navigator.clipboard.writeText) {
|
if(navigator.clipboard && navigator.clipboard.writeText) {
|
||||||
await navigator.clipboard.writeText(chalkboard.value);
|
await navigator.clipboard.writeText(chalkboardText);
|
||||||
} else {
|
} else if(!copyTextWithExecCommand(chalkboardText)) {
|
||||||
chalkboard.focus();
|
throw new Error('copy command failed');
|
||||||
chalkboard.select();
|
|
||||||
if(!document.execCommand('copy'))
|
|
||||||
throw new Error('copy command failed');
|
|
||||||
}
|
}
|
||||||
announceChat('Chalkboard copied to clipboard');
|
announceChat('Chalkboard copied to clipboard');
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
@@ -2771,14 +2814,12 @@ function announcePendingChalkboardUpdate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function scheduleChalkboardUpdateAnnouncement(hasText) {
|
function scheduleChalkboardUpdateAnnouncement(hasText) {
|
||||||
let chalkboard = /** @type {HTMLTextAreaElement} */
|
if(!getChalkboardEditor() || !getChalkboardView())
|
||||||
(document.getElementById('chalkboard'));
|
|
||||||
if(!chalkboard)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
chalkboardHasPendingAnnouncement = true;
|
chalkboardHasPendingAnnouncement = true;
|
||||||
chalkboardPendingHasText = hasText;
|
chalkboardPendingHasText = hasText;
|
||||||
if(document.activeElement === chalkboard)
|
if(chalkboardHasFocus())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
clearChalkboardAnnouncementTimer();
|
clearChalkboardAnnouncementTimer();
|
||||||
@@ -2802,16 +2843,14 @@ function applyChalkboardUpdate(message) {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
chalkboardRevision = revision;
|
chalkboardRevision = revision;
|
||||||
let chalkboard = /** @type {HTMLTextAreaElement} */
|
if(!getChalkboardEditor() || !getChalkboardView())
|
||||||
(document.getElementById('chalkboard'));
|
|
||||||
if(!chalkboard)
|
|
||||||
return;
|
return;
|
||||||
let isLocalEcho =
|
let isLocalEcho =
|
||||||
chalkboardLocalTextPending !== null &&
|
chalkboardLocalTextPending !== null &&
|
||||||
text === chalkboardLocalTextPending;
|
text === chalkboardLocalTextPending;
|
||||||
if(chalkboard.value !== text) {
|
if(chalkboardText !== text) {
|
||||||
applyingChalkboardUpdate = true;
|
applyingChalkboardUpdate = true;
|
||||||
chalkboard.value = text;
|
setChalkboardText(text);
|
||||||
applyingChalkboardUpdate = false;
|
applyingChalkboardUpdate = false;
|
||||||
if(isLocalEcho) {
|
if(isLocalEcho) {
|
||||||
chalkboardLocalTextPending = null;
|
chalkboardLocalTextPending = null;
|
||||||
@@ -2831,10 +2870,10 @@ function scheduleChalkboardSend() {
|
|||||||
clearTimeout(chalkboardSendTimer);
|
clearTimeout(chalkboardSendTimer);
|
||||||
chalkboardSendTimer = setTimeout(() => {
|
chalkboardSendTimer = setTimeout(() => {
|
||||||
chalkboardSendTimer = null;
|
chalkboardSendTimer = null;
|
||||||
let chalkboard = /** @type {HTMLTextAreaElement} */
|
let chalkboard = getChalkboardEditor();
|
||||||
(document.getElementById('chalkboard'));
|
|
||||||
if(!chalkboard || !serverConnection || !serverConnection.socket)
|
if(!chalkboard || !serverConnection || !serverConnection.socket)
|
||||||
return;
|
return;
|
||||||
|
chalkboardText = chalkboard.value;
|
||||||
chalkboardLocalTextPending = chalkboard.value;
|
chalkboardLocalTextPending = chalkboard.value;
|
||||||
serverConnection.hallAction('chalkboard', {
|
serverConnection.hallAction('chalkboard', {
|
||||||
text: chalkboard.value,
|
text: chalkboard.value,
|
||||||
@@ -3826,6 +3865,9 @@ document.getElementById('input').onkeypress = function(e) {
|
|||||||
document.getElementById('chalkboard').addEventListener('input', function(e) {
|
document.getElementById('chalkboard').addEventListener('input', function(e) {
|
||||||
if(applyingChalkboardUpdate)
|
if(applyingChalkboardUpdate)
|
||||||
return;
|
return;
|
||||||
|
let chalkboard = getChalkboardEditor();
|
||||||
|
if(chalkboard)
|
||||||
|
setChalkboardText(chalkboard.value);
|
||||||
updateChalkboardCopyButton();
|
updateChalkboardCopyButton();
|
||||||
scheduleChalkboardSend();
|
scheduleChalkboardSend();
|
||||||
});
|
});
|
||||||
@@ -3839,6 +3881,10 @@ document.getElementById('chalkboard').addEventListener('focus', function(e) {
|
|||||||
announcePendingChalkboardUpdate();
|
announcePendingChalkboardUpdate();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.getElementById('chalkboard-view').addEventListener('focus', function(e) {
|
||||||
|
announcePendingChalkboardUpdate();
|
||||||
|
});
|
||||||
|
|
||||||
document.getElementById('chalkboard-copy').addEventListener('click', function(e) {
|
document.getElementById('chalkboard-copy').addEventListener('click', function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
copyChalkboardToClipboard();
|
copyChalkboardToClipboard();
|
||||||
|
|||||||
Reference in New Issue
Block a user