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>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||
</head>
|
||||
<body class="bg-gray-100 h-screen flex items-center justify-center">
|
||||
<div class="bg-white rounded-lg shadow-lg p-8 max-w-md w-full">
|
||||
<main class="bg-white rounded-lg shadow-lg p-8 max-w-md w-full" role="main">
|
||||
<div class="flex justify-center mb-6">
|
||||
<img src="/static/images/logo_writing.svg" alt="DocuElevate Logo" class="h-16">
|
||||
</div>
|
||||
@@ -67,7 +67,7 @@
|
||||
<a href="/oauth-login"
|
||||
class="w-full inline-flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
||||
<span class="sr-only">Sign in with SSO</span>
|
||||
<i class="fas fa-lock mr-2"></i>
|
||||
<i class="fas fa-lock mr-2" aria-hidden="true"></i>
|
||||
{{ oauth_provider_name }}
|
||||
</a>
|
||||
</div>
|
||||
@@ -78,9 +78,7 @@
|
||||
Return to Home
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Optional footer with version info -->
|
||||
</main>
|
||||
<div class="fixed bottom-4 text-center w-full text-xs text-gray-500">
|
||||
DocuElevate {{ app_version|default('', true) }}
|
||||
</div>
|
||||
|
||||
@@ -84,27 +84,28 @@
|
||||
{% block content %}
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<div class="search-container">
|
||||
<h1 class="text-3xl font-bold mb-6"><i class="fas fa-search text-blue-500"></i> Document Search</h1>
|
||||
<h1 class="text-3xl font-bold mb-6"><i class="fas fa-search text-blue-500" aria-hidden="true"></i> Document Search</h1>
|
||||
|
||||
<!-- Search box -->
|
||||
<div class="search-box">
|
||||
<div class="search-box" role="search">
|
||||
<label for="search-input" class="sr-only">Search documents</label>
|
||||
<input
|
||||
type="text"
|
||||
type="search"
|
||||
id="search-input"
|
||||
placeholder="Search documents by content, sender, tags, type..."
|
||||
value="{{ query }}"
|
||||
autofocus
|
||||
>
|
||||
<button type="button" id="search-btn">
|
||||
<i class="fas fa-search"></i> Search
|
||||
<button type="button" id="search-btn" aria-label="Search documents">
|
||||
<i class="fas fa-search" aria-hidden="true"></i> Search
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Summary -->
|
||||
<div id="search-summary" class="search-summary" style="display:none;"></div>
|
||||
<div id="search-summary" class="search-summary" style="display:none;" aria-live="polite"></div>
|
||||
|
||||
<!-- Results -->
|
||||
<div id="search-results"></div>
|
||||
<div id="search-results" aria-live="polite" aria-relevant="additions removals"></div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div id="search-pagination" class="search-pagination" style="display:none;"></div>
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
<div
|
||||
id="dropZone"
|
||||
class="border-4 border-dashed border-gray-300 rounded-lg p-6 sm:p-8 bg-white text-center w-full"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
aria-label="File upload area. Drag and drop files here, or press Enter to browse files."
|
||||
ondragover="handleDragOver(event)"
|
||||
ondragleave="handleDragLeave(event)"
|
||||
>
|
||||
@@ -28,6 +31,7 @@
|
||||
onchange="handleFileSelect(event)"
|
||||
name="files"
|
||||
multiple
|
||||
aria-label="Select files to upload"
|
||||
accept="application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-powerpoint,application/vnd.openxmlformats-officedocument.presentationml.presentation,image/*"
|
||||
/>
|
||||
<!-- Browse Files button (all devices) -->
|
||||
@@ -37,7 +41,7 @@
|
||||
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-3 px-6 rounded-lg transition duration-200 mb-4"
|
||||
style="min-height:44px;"
|
||||
>
|
||||
<i class="fas fa-folder-open mr-2"></i> Browse Files
|
||||
<i class="fas fa-folder-open mr-2" aria-hidden="true"></i> Browse Files
|
||||
</button>
|
||||
<div class="text-sm text-gray-500 mt-2">
|
||||
<p>Allowed types: PDF, Office documents (Word, Excel, PowerPoint, etc.), Images</p>
|
||||
@@ -51,7 +55,7 @@
|
||||
class="w-full flex items-center justify-center gap-2 bg-green-500 hover:bg-green-700 text-white font-bold py-3 px-6 rounded-lg transition duration-200 cursor-pointer"
|
||||
style="min-height:44px;"
|
||||
>
|
||||
<i class="fas fa-camera"></i> Take Photo / Scan Document
|
||||
<i class="fas fa-camera" aria-hidden="true"></i> Take Photo / Scan Document
|
||||
<input
|
||||
id="cameraInput"
|
||||
type="file"
|
||||
@@ -59,6 +63,7 @@
|
||||
onchange="handleFileSelect(event)"
|
||||
accept="image/*"
|
||||
capture="environment"
|
||||
aria-label="Take photo or scan document"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
@@ -111,12 +116,12 @@
|
||||
Download and Process
|
||||
</button>
|
||||
</form>
|
||||
<div id="urlStatusMessage" class="mt-4"></div>
|
||||
<div id="urlStatusMessage" class="mt-4" aria-live="polite"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="statusMessage" class="mt-4 text-gray-700"></div>
|
||||
<div id="uploadProgress" class="mt-4 w-full max-w-2xl"></div>
|
||||
<div id="statusMessage" class="mt-4 text-gray-700" aria-live="polite"></div>
|
||||
<div id="uploadProgress" class="mt-4 w-full max-w-2xl" role="status" aria-live="polite"></div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user