feat(ui): fix accessibility in upload, search, files, and login templates
- upload.html: Add role/tabindex/aria-label to drop zone, aria-live to status messages, aria-label to file inputs - search.html: Add role="search", sr-only label, aria-live for results, input type="search" - files.html: Add table aria-label, scope="col" to headers, aria-sort on sortable columns, aria-labels to action buttons, dialog roles to modals, aria-live to status areas, pagination nav with aria-labels - login.html: Wrap form in main landmark, aria-hidden on decorative icons Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
@@ -345,25 +345,25 @@
|
||||
|
||||
{% block content %}
|
||||
<!-- Drop overlay -->
|
||||
<div id="dropOverlay" class="drop-overlay">
|
||||
<div id="dropOverlay" class="drop-overlay" aria-hidden="true">
|
||||
<div class="drop-message">
|
||||
<i class="fas fa-cloud-upload-alt"></i>
|
||||
<i class="fas fa-cloud-upload-alt" aria-hidden="true"></i>
|
||||
<p>Drop files or folders anywhere to upload</p>
|
||||
<div class="drop-hint">Supports PDF, Office docs, images, HTML, Markdown, and more – folders are processed recursively</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Upload progress modal -->
|
||||
<div id="uploadModal" class="upload-modal">
|
||||
<div id="uploadModal" class="upload-modal" role="dialog" aria-modal="true" aria-label="Upload progress">
|
||||
<div class="upload-modal-header">
|
||||
<span><i class="fas fa-upload"></i> Uploading Files</span>
|
||||
<button class="close-modal-btn" onclick="closeUploadModal()">
|
||||
<i class="fas fa-times"></i>
|
||||
<span><i class="fas fa-upload" aria-hidden="true"></i> Uploading Files</span>
|
||||
<button class="close-modal-btn" onclick="closeUploadModal()" aria-label="Close upload progress">
|
||||
<i class="fas fa-times" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="upload-modal-body">
|
||||
<div id="uploadStatusMessage" class="text-sm text-gray-700 mb-2"></div>
|
||||
<div id="uploadProgressContainer" class="space-y-2"></div>
|
||||
<div id="uploadStatusMessage" class="text-sm text-gray-700 mb-2" aria-live="polite"></div>
|
||||
<div id="uploadProgressContainer" class="space-y-2" role="status" aria-live="polite"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -374,7 +374,7 @@
|
||||
<div id="queueBanner" class="hidden bg-blue-50 border-l-4 border-blue-400 text-blue-800 p-4 mb-6 rounded-r-lg" role="status">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<i class="fas fa-spinner fa-spin mr-3 text-blue-500"></i>
|
||||
<i class="fas fa-spinner fa-spin mr-3 text-blue-500" aria-hidden="true"></i>
|
||||
<span>
|
||||
<strong id="queueBannerCount">0</strong> item(s) are currently queued or being processed.
|
||||
Files will appear here once processing completes.
|
||||
@@ -382,7 +382,7 @@
|
||||
</div>
|
||||
<a href="/admin/queue" class="text-blue-600 hover:text-blue-800 text-sm font-medium whitespace-nowrap ml-4"
|
||||
id="queueBannerLink" style="display:none;">
|
||||
<i class="fas fa-external-link-alt mr-1"></i>View Queue
|
||||
<i class="fas fa-external-link-alt mr-1" aria-hidden="true"></i>View Queue
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -445,7 +445,7 @@
|
||||
<div class="filters-section" style="margin-top: 0.75rem;">
|
||||
<div style="width: 100%;">
|
||||
<label for="fulltext-search" style="font-weight: 600; display: block; margin-bottom: 0.4rem;">
|
||||
<i class="fas fa-search"></i> Full-Text Search (OCR text, metadata, tags)
|
||||
<i class="fas fa-search" aria-hidden="true"></i> Full-Text Search (OCR text, metadata, tags)
|
||||
</label>
|
||||
<div style="display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap;">
|
||||
<input
|
||||
@@ -490,10 +490,10 @@
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button type="button" onclick="bulkReprocess()" style="padding: 0.5rem 1rem; background-color: #3182ce; color: white; border: none; border-radius: 0.25rem; cursor: pointer; font-weight: 600; min-height: 44px;">
|
||||
<i class="fas fa-sync"></i> Reprocess Selected
|
||||
<i class="fas fa-sync" aria-hidden="true"></i> Reprocess Selected
|
||||
</button>
|
||||
<button type="button" onclick="bulkDelete()" style="padding: 0.5rem 1rem; background-color: #e53e3e; color: white; border: none; border-radius: 0.25rem; cursor: pointer; font-weight: 600; min-height: 44px;">
|
||||
<i class="fas fa-trash"></i> Delete Selected
|
||||
<i class="fas fa-trash" aria-hidden="true"></i> Delete Selected
|
||||
</button>
|
||||
<button type="button" onclick="clearSelection()" style="padding: 0.5rem 1rem; background-color: #718096; color: white; border: none; border-radius: 0.25rem; cursor: pointer; font-weight: 600; min-height: 44px;">
|
||||
Clear Selection
|
||||
@@ -504,13 +504,13 @@
|
||||
|
||||
<!-- File table (desktop) -->
|
||||
<div class="hidden md:block overflow-x-auto">
|
||||
<table class="file-table" id="fileTable">
|
||||
<table class="file-table" id="fileTable" aria-label="File records">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 40px;">
|
||||
<input type="checkbox" id="selectAll" onclick="toggleSelectAll()" title="Select all files on this page">
|
||||
<th scope="col" style="width: 40px;">
|
||||
<input type="checkbox" id="selectAll" onclick="toggleSelectAll()" title="Select all files on this page" aria-label="Select all files on this page">
|
||||
</th>
|
||||
<th class="sortable" onclick="sortTable('id')">
|
||||
<th scope="col" class="sortable" onclick="sortTable('id')" role="columnheader" aria-sort="{% if sort_by == 'id' %}{% if sort_order == 'asc' %}ascending{% else %}descending{% endif %}{% else %}none{% endif %}">
|
||||
ID
|
||||
<span class="sort-indicator {% if sort_by == 'id' %}active{% endif %}">
|
||||
{% if sort_by == 'id' %}
|
||||
@@ -520,7 +520,7 @@
|
||||
{% endif %}
|
||||
</span>
|
||||
</th>
|
||||
<th class="sortable" onclick="sortTable('original_filename')">
|
||||
<th scope="col" class="sortable" onclick="sortTable('original_filename')">
|
||||
Original Filename
|
||||
<span class="sort-indicator {% if sort_by == 'original_filename' %}active{% endif %}">
|
||||
{% if sort_by == 'original_filename' %}
|
||||
@@ -530,7 +530,7 @@
|
||||
{% endif %}
|
||||
</span>
|
||||
</th>
|
||||
<th class="sortable" onclick="sortTable('file_size')">
|
||||
<th scope="col" class="sortable" onclick="sortTable('file_size')">
|
||||
File Size
|
||||
<span class="sort-indicator {% if sort_by == 'file_size' %}active{% endif %}">
|
||||
{% if sort_by == 'file_size' %}
|
||||
@@ -540,7 +540,7 @@
|
||||
{% endif %}
|
||||
</span>
|
||||
</th>
|
||||
<th class="sortable" onclick="sortTable('mime_type')">
|
||||
<th scope="col" class="sortable" onclick="sortTable('mime_type')">
|
||||
MIME Type
|
||||
<span class="sort-indicator {% if sort_by == 'mime_type' %}active{% endif %}">
|
||||
{% if sort_by == 'mime_type' %}
|
||||
@@ -550,8 +550,8 @@
|
||||
{% endif %}
|
||||
</span>
|
||||
</th>
|
||||
<th>Status</th>
|
||||
<th class="sortable" onclick="sortTable('created_at')">
|
||||
<th scope="col">Status</th>
|
||||
<th scope="col" class="sortable" onclick="sortTable('created_at')">
|
||||
Created At
|
||||
<span class="sort-indicator {% if sort_by == 'created_at' %}active{% endif %}">
|
||||
{% if sort_by == 'created_at' %}
|
||||
@@ -561,7 +561,7 @@
|
||||
{% endif %}
|
||||
</span>
|
||||
</th>
|
||||
<th>Actions</th>
|
||||
<th scope="col">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -582,11 +582,11 @@
|
||||
<td>{{ file.created_at.strftime('%Y-%m-%d %H:%M:%S') if file.created_at else 'N/A' }}</td>
|
||||
<td>
|
||||
<div style="display: flex; align-items: center;">
|
||||
<button onclick="viewFileDetail({{ file.id }}, event)" class="action-btn" title="View details" style="min-height:44px;min-width:44px;">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
<button onclick="viewFileDetail({{ file.id }}, event)" class="action-btn" title="View details" aria-label="View details for {{ file.original_filename }}" style="min-height:44px;min-width:44px;">
|
||||
<i class="fas fa-info-circle" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button onclick="showDeleteModal({{ file.id }}, event)" class="action-btn delete" title="Delete file" style="min-height:44px;min-width:44px;">
|
||||
<i class="fas fa-trash"></i>
|
||||
<button onclick="showDeleteModal({{ file.id }}, event)" class="action-btn delete" title="Delete file" aria-label="Delete {{ file.original_filename }}" style="min-height:44px;min-width:44px;">
|
||||
<i class="fas fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
@@ -621,11 +621,11 @@
|
||||
<div class="flex flex-col items-end gap-2 flex-shrink-0">
|
||||
<span class="status-badge status-{{ file.processing_status }}">{{ file.processing_status | title }}</span>
|
||||
<div class="flex gap-1" role="group" aria-label="File actions" onclick="event.stopPropagation();" onkeydown="event.stopPropagation();">
|
||||
<button onclick="viewFileDetail({{ file.id }}, event)" class="action-btn" title="View details" style="min-height:44px;min-width:44px;">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
<button onclick="viewFileDetail({{ file.id }}, event)" class="action-btn" title="View details" aria-label="View details for {{ file.original_filename }}" style="min-height:44px;min-width:44px;">
|
||||
<i class="fas fa-info-circle" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button onclick="showDeleteModal({{ file.id }}, event)" class="action-btn delete" title="Delete file" style="min-height:44px;min-width:44px;">
|
||||
<i class="fas fa-trash"></i>
|
||||
<button onclick="showDeleteModal({{ file.id }}, event)" class="action-btn delete" title="Delete file" aria-label="Delete {{ file.original_filename }}" style="min-height:44px;min-width:44px;">
|
||||
<i class="fas fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -638,36 +638,36 @@
|
||||
|
||||
<!-- Pagination -->
|
||||
{% if pagination.total_pages > 1 %}
|
||||
<div class="pagination flex-wrap gap-2">
|
||||
<div class="pagination-info">
|
||||
<nav class="pagination flex-wrap gap-2" aria-label="File list pagination">
|
||||
<div class="pagination-info" aria-live="polite">
|
||||
Showing {{ ((pagination.page - 1) * pagination.per_page + 1) }} -
|
||||
{{ min(pagination.page * pagination.per_page, pagination.total_items) }}
|
||||
of {{ pagination.total_items }} files
|
||||
</div>
|
||||
<div class="pagination-buttons flex-wrap">
|
||||
{% if pagination.page > 1 %}
|
||||
<button class="pagination-button" onclick="goToPage(1)" style="min-height:44px;">First</button>
|
||||
<button class="pagination-button" onclick="goToPage({{ pagination.page - 1 }})" style="min-height:44px;">Previous</button>
|
||||
<button class="pagination-button" onclick="goToPage(1)" aria-label="Go to first page" style="min-height:44px;">First</button>
|
||||
<button class="pagination-button" onclick="goToPage({{ pagination.page - 1 }})" aria-label="Go to previous page" style="min-height:44px;">Previous</button>
|
||||
{% endif %}
|
||||
|
||||
{% for p in range(max(1, pagination.page - 2), min(pagination.total_pages + 1, pagination.page + 3)) %}
|
||||
<button class="pagination-button {% if p == pagination.page %}active{% endif %}" onclick="goToPage({{ p }})" style="min-height:44px;">
|
||||
<button class="pagination-button {% if p == pagination.page %}active{% endif %}" onclick="goToPage({{ p }})" aria-label="Page {{ p }}" {% if p == pagination.page %}aria-current="page"{% endif %} style="min-height:44px;">
|
||||
{{ p }}
|
||||
</button>
|
||||
{% endfor %}
|
||||
|
||||
{% if pagination.page < pagination.total_pages %}
|
||||
<button class="pagination-button" onclick="goToPage({{ pagination.page + 1 }})" style="min-height:44px;">Next</button>
|
||||
<button class="pagination-button" onclick="goToPage({{ pagination.total_pages }})" style="min-height:44px;">Last</button>
|
||||
<button class="pagination-button" onclick="goToPage({{ pagination.page + 1 }})" aria-label="Go to next page" style="min-height:44px;">Next</button>
|
||||
<button class="pagination-button" onclick="goToPage({{ pagination.total_pages }})" aria-label="Go to last page" style="min-height:44px;">Last</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
{% endif %}
|
||||
|
||||
<!-- Delete confirmation modal -->
|
||||
<div id="deleteModal" class="modal">
|
||||
<div id="deleteModal" class="modal" role="dialog" aria-modal="true" aria-labelledby="deleteModalTitle">
|
||||
<div class="modal-content">
|
||||
<div class="modal-title">Confirm Deletion</div>
|
||||
<div class="modal-title" id="deleteModalTitle">Confirm Deletion</div>
|
||||
<p>Are you sure you want to delete this file?</p>
|
||||
<div class="modal-buttons">
|
||||
<button id="cancelDelete" class="modal-btn modal-btn-cancel" style="min-height:44px;min-width:80px;">Cancel</button>
|
||||
|
||||
Reference in New Issue
Block a user