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:
Storm Dragon
2026-06-23 00:45:20 -04:00
parent d8817f6b2e
commit e73eb21c45
5 changed files with 114 additions and 48 deletions
@@ -2,7 +2,7 @@
# shellcheck shell=bash disable=SC2034,SC2154
pkgname=skald-git
pkgver=0.0.0.r1540.g26c7808
pkgver=0.0.0.r1541.gd8817f6
pkgrel=1
pkgdesc='Audio-only hall-based conferencing server'
arch=('x86_64' 'aarch64')
+6 -7
View File
@@ -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
commands.
Below the chat input is a session-only virtual chalkboard. It is a
multiline text area suitable for commands, code, and notes that should be
visible to everyone in the hall. Operators and administrators may edit
the chalkboard and may grant or revoke chalkboard editing for connected
users with `/chalkboard user` and `/unchalkboard user`. Users without
edit permission can read and focus the chalkboard, but it is marked
read-only and does not trap the Tab key.
Below the chat input is a session-only virtual chalkboard suitable for
commands, code, and notes that should be visible to everyone in the hall.
Operators and administrators edit it as a multiline text area and may grant
or revoke chalkboard editing for connected users with `/chalkboard user`
and `/unchalkboard user`. Users without edit permission can read and focus
the chalkboard as preformatted text.
### Inviting users
+23 -3
View File
@@ -829,14 +829,33 @@ h1 {
margin-top: 0;
}
#chalkboard {
#chalkboard,
#chalkboard-view {
box-sizing: border-box;
display: block;
width: 100%;
min-height: 8rem;
}
#chalkboard {
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 {
margin-top: 0.35rem;
}
@@ -845,11 +864,12 @@ h1 {
opacity: 0.55;
}
#chalkboard[readonly] {
#chalkboard-view {
background: #f3f3f3;
}
#chalkboard:focus {
#chalkboard:focus,
#chalkboard-view:focus {
outline: 2px solid #0066cc;
outline-offset: 2px;
}
+1
View File
@@ -79,6 +79,7 @@
<div id="chalkboard-area">
<h2 id="chalkboard-heading">Virtual chalkboard</h2>
<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>
</div>
</div>
+83 -37
View File
@@ -2678,6 +2678,36 @@ let chalkboardAnnouncementTimer = null;
let chalkboardHasPendingAnnouncement = false;
let chalkboardPendingHasText = false;
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() {
if(chalkboardAnnouncementTimer) {
@@ -2695,28 +2725,29 @@ function canEditChalkboard() {
}
function updateChalkboardAccess() {
let chalkboard = /** @type {HTMLTextAreaElement} */
(document.getElementById('chalkboard'));
if(!chalkboard)
let editor = getChalkboardEditor();
let view = getChalkboardView();
if(!editor || !view)
return;
let canEdit = canEditChalkboard();
chalkboard.readOnly = !canEdit;
chalkboard.setAttribute('aria-readonly', canEdit ? 'false' : 'true');
chalkboard.setAttribute(
'aria-label',
canEdit ? 'Virtual chalkboard' : 'Virtual chalkboard, read only',
);
let active = document.activeElement;
editor.hidden = !canEdit;
view.hidden = canEdit;
editor.setAttribute('aria-label', 'Virtual chalkboard');
view.setAttribute('aria-label', 'Virtual chalkboard, read only');
if(active === editor && !canEdit)
view.focus();
else if(active === view && canEdit)
editor.focus();
updateChalkboardCopyButton();
}
function updateChalkboardCopyButton() {
let chalkboard = /** @type {HTMLTextAreaElement} */
(document.getElementById('chalkboard'));
let copyButton = /** @type {HTMLButtonElement} */
(document.getElementById('chalkboard-copy'));
if(!chalkboard || !copyButton)
if(!copyButton)
return;
copyButton.disabled = chalkboard.value.length === 0;
copyButton.disabled = chalkboardText.length === 0;
}
function resetChalkboard() {
@@ -2729,26 +2760,38 @@ function resetChalkboard() {
clearTimeout(chalkboardSendTimer);
chalkboardSendTimer = null;
}
let chalkboard = /** @type {HTMLTextAreaElement} */
(document.getElementById('chalkboard'));
if(chalkboard)
chalkboard.value = '';
setChalkboardText('');
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() {
let chalkboard = /** @type {HTMLTextAreaElement} */
(document.getElementById('chalkboard'));
if(!chalkboard || chalkboard.value.length === 0)
if(chalkboardText.length === 0)
return;
try {
if(navigator.clipboard && navigator.clipboard.writeText) {
await navigator.clipboard.writeText(chalkboard.value);
} else {
chalkboard.focus();
chalkboard.select();
if(!document.execCommand('copy'))
throw new Error('copy command failed');
await navigator.clipboard.writeText(chalkboardText);
} else if(!copyTextWithExecCommand(chalkboardText)) {
throw new Error('copy command failed');
}
announceChat('Chalkboard copied to clipboard');
} catch(e) {
@@ -2771,14 +2814,12 @@ function announcePendingChalkboardUpdate() {
}
function scheduleChalkboardUpdateAnnouncement(hasText) {
let chalkboard = /** @type {HTMLTextAreaElement} */
(document.getElementById('chalkboard'));
if(!chalkboard)
if(!getChalkboardEditor() || !getChalkboardView())
return;
chalkboardHasPendingAnnouncement = true;
chalkboardPendingHasText = hasText;
if(document.activeElement === chalkboard)
if(chalkboardHasFocus())
return;
clearChalkboardAnnouncementTimer();
@@ -2802,16 +2843,14 @@ function applyChalkboardUpdate(message) {
return;
chalkboardRevision = revision;
let chalkboard = /** @type {HTMLTextAreaElement} */
(document.getElementById('chalkboard'));
if(!chalkboard)
if(!getChalkboardEditor() || !getChalkboardView())
return;
let isLocalEcho =
chalkboardLocalTextPending !== null &&
text === chalkboardLocalTextPending;
if(chalkboard.value !== text) {
if(chalkboardText !== text) {
applyingChalkboardUpdate = true;
chalkboard.value = text;
setChalkboardText(text);
applyingChalkboardUpdate = false;
if(isLocalEcho) {
chalkboardLocalTextPending = null;
@@ -2831,10 +2870,10 @@ function scheduleChalkboardSend() {
clearTimeout(chalkboardSendTimer);
chalkboardSendTimer = setTimeout(() => {
chalkboardSendTimer = null;
let chalkboard = /** @type {HTMLTextAreaElement} */
(document.getElementById('chalkboard'));
let chalkboard = getChalkboardEditor();
if(!chalkboard || !serverConnection || !serverConnection.socket)
return;
chalkboardText = chalkboard.value;
chalkboardLocalTextPending = chalkboard.value;
serverConnection.hallAction('chalkboard', {
text: chalkboard.value,
@@ -3826,6 +3865,9 @@ document.getElementById('input').onkeypress = function(e) {
document.getElementById('chalkboard').addEventListener('input', function(e) {
if(applyingChalkboardUpdate)
return;
let chalkboard = getChalkboardEditor();
if(chalkboard)
setChalkboardText(chalkboard.value);
updateChalkboardCopyButton();
scheduleChalkboardSend();
});
@@ -3839,6 +3881,10 @@ document.getElementById('chalkboard').addEventListener('focus', function(e) {
announcePendingChalkboardUpdate();
});
document.getElementById('chalkboard-view').addEventListener('focus', function(e) {
announcePendingChalkboardUpdate();
});
document.getElementById('chalkboard-copy').addEventListener('click', function(e) {
e.preventDefault();
copyChalkboardToClipboard();