diff --git a/public/login.html b/public/login.html index 11456a22a..26fcb9196 100644 --- a/public/login.html +++ b/public/login.html @@ -84,7 +84,7 @@ - + diff --git a/public/script.js b/public/script.js index 1dde54adf..1a9510c8c 100644 --- a/public/script.js +++ b/public/script.js @@ -266,6 +266,7 @@ import { initDataMaid } from './scripts/data-maid.js'; import { clearItemizedPrompts, deleteItemizedPrompts, findItemizedPromptSet, initItemizedPrompts, itemizedParams, itemizedPrompts, loadItemizedPrompts, promptItemize, replaceItemizedPromptText, saveItemizedPrompts } from './scripts/itemized-prompts.js'; import { getSystemMessageByType, initSystemMessages, SAFETY_CHAT, sendSystemMessage, system_message_types, system_messages } from './scripts/system-messages.js'; import { event_types, eventSource } from './scripts/events.js'; +import { initAccessibility } from './scripts/a11y.js'; // API OBJECT FOR EXTERNAL WIRING globalThis.SillyTavern = { @@ -687,6 +688,7 @@ async function firstLoadInit() { initCustomSelectedSamplers(); initDataMaid(); initItemizedPrompts(); + initAccessibility(); addDebugFunctions(); doDailyExtensionUpdatesCheck(); await hideLoader(); diff --git a/public/scripts/a11y.js b/public/scripts/a11y.js new file mode 100644 index 000000000..ad38ccd17 --- /dev/null +++ b/public/scripts/a11y.js @@ -0,0 +1,104 @@ +/** + * Shared module between login and main app. + * Be careful what you import! + */ + +const buttonSelectors = [ + '.menu_button', + '.right_menu_button', + '.mes_button', + '.drawer-icon', + '.inline-drawer-icon', + '.swipe_left', + '.swipe_right', + '.character_select', + '.tags .tag', +].join(', '); + +const listSelectors = [ + '.options-content', + '.list-group', + '#rm_print_characters_block', + '#rm_group_members', + '#rm_group_add_members', + '.tag_view_list_tags', + '.secretKeyManagerList', + '.recentChatList', + '.dataMaidCategoryContent', + '#userList', +].join(', '); + +const listItemSelectors = [ + '.options-content .interactable', + '.list-group .list-group-item', + '#rm_print_characters_block .entity_block', + '#rm_group_members .group_member', + '#rm_group_add_members .group_member', + '.tag_view_list_tags .tag_view_item', + '.secretKeyManagerList .secretKeyManagerItem', + '.recentChatList .recentChat', + '.dataMaidCategoryContent .dataMaidItem', + '#userList .userSelect', +].join(', '); + +/** @type {Record void>} */ +const a11yRules = { + [buttonSelectors]: (element) => { + element.setAttribute('role', 'button'); + }, + [listSelectors]: (element) => { + element.setAttribute('role', 'list'); + }, + [listItemSelectors]: (element) => { + element.setAttribute('role', 'listitem'); + }, + '#toast-container .toast-message': (element) => { + element.setAttribute('role', 'alert'); + }, +}; + +/** + * Apply accessibility rules to an element. + * @param {Element} element Element to process. + */ +function applyA11yRules(element) { + try { + for (const [selector, rule] of Object.entries(a11yRules)) { + // Apply if the element directly matches the selector + if (element.matches(selector)) { + rule(element); + } + // Apply the rule to descendants + element.querySelectorAll(selector).forEach(rule); + } + } catch (error) { + console.error('Error applying accessibility rules to element:', element, error); + } +} + +function setAccessibilityObserver() { + // Apply for existing elements + applyA11yRules(document.body); + + // Setup observer for dynamic content + const observer = new MutationObserver((mutationsList) => { + for (const mutation of mutationsList) { + if (mutation.type === 'childList') { + for (const addedNode of mutation.addedNodes) { + if (addedNode instanceof Element && addedNode.nodeType === Node.ELEMENT_NODE) { + applyA11yRules(addedNode); + } + } + } + } + }); + + observer.observe(document.body, { + childList: true, + subtree: true, + }); +} + +export function initAccessibility() { + setAccessibilityObserver(); +} diff --git a/public/scripts/login.js b/public/scripts/login.js index 6ef26abf7..9bb4434c2 100644 --- a/public/scripts/login.js +++ b/public/scripts/login.js @@ -1,3 +1,5 @@ +import { initAccessibility } from './a11y.js'; + /** * CRSF token for requests. */ @@ -265,6 +267,8 @@ function configureDiscreetLogin() { } (async function () { + initAccessibility(); + csrfToken = await getCsrfToken(); const userList = await getUserList();