Merge pull request #5857 from seve12/main

Accessibility:
- Added product name to page titles, settings and global search.
- More accessible header and layout templates, modal dialogs, DOM structure, color contrast.
This commit is contained in:
Lauri Ojansivu 2025-08-09 11:33:50 +03:00 committed by GitHub
commit 4ce7ff7cef
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 87 additions and 20 deletions

View file

@ -16,6 +16,7 @@ template(name="boardBody")
if notDisplayThisBoard
| {{_ 'tableVisibilityMode-allowPrivateOnly'}}
else
.board-wrapper(class=currentBoard.colorClass)
.board-canvas.js-swimlanes(
class="{{#if hasSwimlanes}}dragscroll{{/if}}"

View file

@ -340,12 +340,12 @@ BlazeComponent.extendComponent({
selectable: true,
timezone: 'local',
weekNumbers: true,
header: {
left: 'title today prev,next',
center:
'agendaDay,listDay,timelineDay agendaWeek,listWeek,timelineWeek month,listMonth',
right: '',
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'month,agendaWeek,agendaDay'
},
footerToolbar: false,
// height: 'parent', nope, doesn't work as the parent might be small
height: 'auto',
/* TODO: lists as resources: https://fullcalendar.io/docs/vertical-resource-view */
@ -473,9 +473,29 @@ BlazeComponent.extendComponent({
closeModal();
}
});
document.body.appendChild(modalElement);
document.querySelector('.board-wrapper').appendChild(modalElement); // (optional: for better DOM order)
const openModal = function() {
modalElement.style.display = 'flex';
modalElement.style.display = 'flex';
modalElement.setAttribute('role', 'dialog');
modalElement.setAttribute('aria-modal', 'true');
// Set ARIA attributes for accessibility
modalElement.setAttribute('role', 'dialog');
modalElement.setAttribute('aria-modal', 'true');
// Move focus to the first input or button in the modal
const firstInput = modalElement.querySelector('input, button');
if (firstInput) firstInput.focus();
// Set aria-labelledby and aria-describedby for accessibility
const title = modalElement.querySelector('.modal-title');
if (title) {
title.id = 'modal-title';
modalElement.setAttribute('aria-labelledby', 'modal-title');
}
const desc = modalElement.querySelector('.modal-body');
if (desc) {
desc.id = 'modal-desc';
modalElement.setAttribute('aria-describedby', 'modal-desc');
}
};
const closeModal = function() {
modalElement.style.display = 'none';

View file

@ -1,5 +1,5 @@
template(name="boardHeaderBar")
h1.header-board-menu
h1.header-board-menu {{currentBoard.title}}
with currentBoard
if $eq title 'Templates'
| {{_ 'templates'}}
@ -137,8 +137,11 @@ template(name="boardHeaderBar")
i.fa.fa-check-square-o
span {{#if MultiSelection.isActive}}{{_ 'multi-selection-on'}}{{else}}{{_ 'multi-selection'}}{{/if}}
if MultiSelection.isActive
a.board-header-btn-close.js-multiselection-reset(title="{{_ 'filter-clear'}}")
i.fa.fa-times-thin
a.board-header-btn-close.js-multiselection-reset(
title="{{_ 'deactivate-multi-selection'}}"
aria-label="{{_ 'deactivate-multi-selection'}}"
)
i.fa.fa-times-thin
.separator
a.board-header-btn.js-toggle-sidebar(title="{{_ 'sidebar-open'}} {{_ 'or'}} {{_ 'sidebar-close'}}")

View file

@ -540,7 +540,7 @@ template(name="cardDetails")
else if getDescription
if currentBoard.allowsDescriptionTitle
hr
h3.card-details-item-title {{_ 'description'}}
div.card-details-item-title {{_ 'description'}}
if currentBoard.allowsDescriptionText
+viewer
= getDescription

View file

@ -39,12 +39,20 @@ template(name="header")
if currentSetting.customTopLeftCornerLogoImageUrl
if currentSetting.customTopLeftCornerLogoLinkUrl
a(href="{{currentSetting.customTopLeftCornerLogoLinkUrl}}" alt="{{currentSetting.productName}}" title="{{currentSetting.productName}}")
img(src="{{currentSetting.customTopLeftCornerLogoImageUrl}}" height="{{#if currentSetting.customTopLeftCornerLogoHeight}}#{currentSetting.customTopLeftCornerLogoHeight}{{else}}27{{/if}}" width="auto" margin="0" padding="0")
img(
src="{{currentSetting.customTopLeftCornerLogoImageUrl}}"
alt="{{currentSetting.productName}} logo"
title="{{currentSetting.productName}}"
)
unless currentSetting.customTopLeftCornerLogoLinkUrl
img(src="{{currentSetting.customTopLeftCornerLogoImageUrl}}" height="{{#if currentSetting.customTopLeftCornerLogoHeight}}#{currentSetting.customTopLeftCornerLogoHeight}{{else}}27{{/if}}" width="auto" margin="0" padding="0" alt="{{currentSetting.productName}}" title="{{currentSetting.productName}}")
unless currentSetting.customTopLeftCornerLogoImageUrl
div#headerIsSettingDatabaseCallDone
img(src="{{pathFor '/logo-header.png'}}" alt="{{currentSetting.productName}}" title="{{currentSetting.productName}}")
img(
src="{{pathFor '/logo-header.png'}}"
alt="{{currentSetting.productName}} logo"
title="{{currentSetting.productName}}"
)
span.allBoards
a(href="{{pathFor 'home'}}")
span.fa.fa-home
@ -73,7 +81,8 @@ template(name="header")
// Hide duplicate create board button,
// because it did not show board templates correctly.
//a#header-new-board-icon.js-create-board
// i.fa.fa-plus(title="Create a new board")
button.add-card(aria-label="Add card" title="Add card")
i.fa.fa-plus
+notifications

View file

@ -23,6 +23,39 @@ table, tbody, td, tfoot, th, thread, tr, tt, ul, var {
.panel-heading.note-toolbar .note-color-palette div .note-color-btn {
background: none;
}
input::placeholder, textarea::placeholder {
color: #555 !important;
opacity: 1;
}
.error-notification {
color: #fff !important; /* White text */
background: #d32f2f !important; /* Strong red background */
}
.menu-text, .sidebar-text {
color: #222 !important; /* Very dark grey or black */
}
.notification-link {
color: #0056b3 !important; /* Darker blue for better contrast */
}
.card-title, .header-title {
color: #fff !important; /* White text for dark backgrounds */
}
.checklist-item.finished {
color: #444 !important; /* Darker grey for better contrast */
text-decoration: line-through;
}
.button-dark {
color: #fff !important;
}
.heading {
color: #222 !important;
}
.menu-text {
color: #222 !important;
}
.calendar-event, .calendar-card {
color: #fff !important;
}
a:focus {
outline: unset;
outline-offset: unset;
@ -194,7 +227,7 @@ strong {
p {
-webkit-user-select: text;
user-select: text;
}
p a {

View file

@ -21,11 +21,11 @@ head
template(name="userFormsLayout")
section.auth-layout
if currentSetting.hideLogo
h1.at-form-landing-logo
h1.at-form-landing-logo Wekan
br
br
unless currentSetting.hideLogo
h1.at-form-landing-logo
h1.at-form-landing-logo Wekan
if currentSetting.customLoginLogoImageUrl
if currentSetting.customLoginLogoLinkUrl
a(href="{{currentSetting.customLoginLogoLinkUrl}}")

View file

@ -288,7 +288,7 @@ BlazeComponent.extendComponent({
this.setLoading(false);
}
DocHead.setTitle(productName);
DocHead.setTitle(`Settings - ${productName}`);
},
sendSMTPTestEmail() {

View file

@ -225,8 +225,9 @@ FlowRouter.route('/global-search', {
Utils.manageCustomUI();
Utils.manageMatomo();
DocHead.setTitle(TAPi18n.__('globalSearch-title'));
const currentSetting = ReactiveCache.getCurrentSetting && ReactiveCache.getCurrentSetting();
const productName = currentSetting && currentSetting.productName ? currentSetting.productName : 'Wekan';
DocHead.setTitle(`${TAPi18n.__('globalSearch-title')} - ${productName}`);
if (FlowRouter.getQueryParam('q')) {
Session.set(
'globalQuery',