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 # 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')
+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 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
View File
@@ -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;
} }
+1
View File
@@ -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>
+82 -36
View File
@@ -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,25 +2760,37 @@ 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();
chalkboard.select();
if(!document.execCommand('copy'))
throw new Error('copy command failed'); throw new Error('copy command failed');
} }
announceChat('Chalkboard copied to clipboard'); announceChat('Chalkboard copied to clipboard');
@@ -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();