refactor(views): split file views into summary, detail, process, and annotations pages

- /files/<id> → new summary page with navigation cards
- /files/<id>/detail → document detail with metadata, preview, text
- /files/<id>/process → processing pipeline status and history
- /files/<id>/annotations → comments & annotations with EmbedPDF viewer
- /files/<id>/comments → redirects to /annotations
- Added embed-pdf-viewer as git submodule for PDF annotation viewer
- Updated all navigation links across templates
- Updated all tests to use new URL structure

Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
Agent-Logs-Url: https://github.com/christianlouis/DocuElevate/sessions/12276514-bd3d-4e3e-84d9-5977d1f82b19
This commit is contained in:
copilot-swe-agent[bot]
2026-03-22 11:48:57 +00:00
parent 5a3ddcc1f0
commit f852ba9783
14 changed files with 1213 additions and 725 deletions
+746
View File
@@ -0,0 +1,746 @@
{% extends "base.html" %}
{% block title %}Comments & Annotations - {{ file.original_filename or 'Document' }} - DocuElevate{% endblock %}
{% block head_extra %}
<script src="/static/js/common.js"></script>
<style>
.annotations-container { max-width: 1200px; margin: 0 auto; }
/* ── PDF viewer ── */
.pdf-viewer-card {
background: white; border-radius: 0.5rem;
box-shadow: 0 1px 3px rgba(0,0,0,.08);
margin-bottom: 1.25rem; overflow: hidden;
}
.dark .pdf-viewer-card { background: #1f2937; box-shadow: 0 1px 3px rgba(0,0,0,.3); }
.pdf-viewer-card-header {
display: flex; align-items: center; gap: 0.5rem;
padding: 1rem 1.5rem; border-bottom: 1px solid #e5e7eb;
font-weight: 700; font-size: 0.95rem; color: #374151;
}
.dark .pdf-viewer-card-header { border-bottom-color: #374151; color: #d1d5db; }
#embedpdf-viewer { width: 100%; height: 600px; }
/* ── header ── */
.annotations-header {
display: flex; align-items: flex-start; justify-content: space-between;
gap: 1rem; margin-bottom: 1.5rem; flex-wrap: wrap;
}
.annotations-title { font-size: 1.5rem; font-weight: 700; color: #1f2937; line-height: 1.25; }
.annotations-subtitle { font-size: 0.85rem; color: #6b7280; margin-top: 0.25rem; }
.dark .annotations-title { color: #f3f4f6; }
.dark .annotations-subtitle { color: #9ca3af; }
/* ── error ── */
.error-box { background: #fee2e2; border: 1px solid #f87171; color: #b91c1c; padding: 1rem; border-radius: 0.375rem; margin-bottom: 1rem; }
/* ── card ── */
.collab-card {
background: white; border-radius: 0.5rem;
box-shadow: 0 1px 3px rgba(0,0,0,.08);
padding: 1.5rem; margin-bottom: 1.25rem;
}
.dark .collab-card { background: #1f2937; box-shadow: 0 1px 3px rgba(0,0,0,.3); }
/* ── Comments panel ───────────────────────────────────────────────────── */
.comments-panel, .annotations-panel {
margin-top: 0;
}
.panel-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 1rem;
}
.panel-header h3 {
margin-bottom: 0;
}
.comments-empty, .annotations-empty {
text-align: center;
padding: 2rem;
color: #718096;
}
.comments-loading, .annotations-loading {
text-align: center;
padding: 1.5rem;
color: #718096;
}
.comments-error {
text-align: center;
padding: 1rem;
color: #991B1B;
}
/* Individual comment */
.comment-item {
background-color: #f7fafc;
border-radius: 0.5rem;
padding: 1rem;
margin-bottom: 0.75rem;
border-left: 3px solid #4299e1;
}
.dark .comment-item {
background-color: #2d3748;
border-left-color: #63b3ed;
}
.comment-item.comment-reply {
margin-left: 1.5rem;
border-left-color: #a0aec0;
background-color: #edf2f7;
}
.dark .comment-item.comment-reply {
background-color: #1a202c;
border-left-color: #4a5568;
}
.comment-item.comment-resolved {
opacity: 0.75;
border-left-color: #48bb78;
}
.comment-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.5rem;
flex-wrap: wrap;
}
.comment-author {
font-weight: 600;
color: #2d3748;
font-size: 0.875rem;
}
.dark .comment-author {
color: #e2e8f0;
}
.comment-time {
font-size: 0.75rem;
color: #718096;
}
.comment-resolved-badge {
font-size: 0.75rem;
color: #065F46;
background-color: #D1FAE5;
padding: 0.125rem 0.5rem;
border-radius: 9999px;
font-weight: 600;
}
.dark .comment-resolved-badge {
background-color: #065F46;
color: #D1FAE5;
}
.comment-body {
color: #4a5568;
font-size: 0.9375rem;
line-height: 1.5;
margin-bottom: 0.5rem;
word-break: break-word;
}
.dark .comment-body {
color: #cbd5e0;
}
.comment-mention {
color: #3182ce;
font-weight: 600;
}
.dark .comment-mention {
color: #63b3ed;
}
.comment-actions {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.comment-action-btn {
background: none;
border: 1px solid #e2e8f0;
color: #718096;
font-size: 0.75rem;
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 0.25rem;
min-height: 28px;
}
.comment-action-btn:hover {
background-color: #edf2f7;
color: #2d3748;
}
.dark .comment-action-btn {
border-color: #4a5568;
color: #a0aec0;
}
.dark .comment-action-btn:hover {
background-color: #4a5568;
color: #e2e8f0;
}
.comment-action-btn--danger:hover {
background-color: #FEE2E2;
color: #991B1B;
border-color: #f56565;
}
.dark .comment-action-btn--danger:hover {
background-color: #742a2a;
color: #feb2b2;
border-color: #f56565;
}
.comment-replies {
margin-top: 0.75rem;
}
/* Comment form */
.comment-form-wrapper {
margin-top: 1rem;
}
.comment-textarea {
width: 100%;
border: 1px solid #e2e8f0;
border-radius: 0.375rem;
padding: 0.75rem;
font-size: 0.875rem;
resize: vertical;
min-height: 60px;
font-family: inherit;
}
.dark .comment-textarea {
background-color: #2d3748;
border-color: #4a5568;
color: #e2e8f0;
}
.comment-textarea:focus {
outline: none;
border-color: #4299e1;
box-shadow: 0 0 0 2px rgba(66, 153, 225, 0.3);
}
.comment-submit-btn {
background-color: #4299e1;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 0.375rem;
font-size: 0.875rem;
cursor: pointer;
font-weight: 600;
margin-top: 0.5rem;
min-height: 36px;
}
.comment-submit-btn:hover {
background-color: #3182ce;
}
.comment-cancel-btn {
background-color: #e2e8f0;
color: #4a5568;
border: none;
padding: 0.5rem 1rem;
border-radius: 0.375rem;
font-size: 0.875rem;
cursor: pointer;
margin-top: 0.5rem;
margin-left: 0.5rem;
min-height: 36px;
}
.dark .comment-cancel-btn {
background-color: #4a5568;
color: #e2e8f0;
}
.comment-edit-btns, .annotation-edit-btns {
display: flex;
gap: 0.5rem;
margin-top: 0.5rem;
}
.comment-reply-form {
margin-top: 0.75rem;
padding-top: 0.75rem;
border-top: 1px solid #e2e8f0;
}
.dark .comment-reply-form {
border-top-color: #4a5568;
}
/* @mention dropdown */
.mention-dropdown-wrapper {
position: relative;
}
#mention-dropdown {
position: absolute;
bottom: 100%;
left: 0;
width: 280px;
max-height: 200px;
overflow-y: auto;
background-color: white;
border: 1px solid #e2e8f0;
border-radius: 0.375rem;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
z-index: 50;
margin-bottom: 0.25rem;
}
.dark #mention-dropdown {
background-color: #2d3748;
border-color: #4a5568;
}
.mention-item {
display: flex;
flex-direction: column;
width: 100%;
text-align: left;
padding: 0.5rem 0.75rem;
border: none;
background: none;
cursor: pointer;
font-size: 0.875rem;
min-height: 44px;
justify-content: center;
}
.mention-item:hover, .mention-item:focus {
background-color: #edf2f7;
outline: none;
}
.dark .mention-item:hover, .dark .mention-item:focus {
background-color: #4a5568;
}
.mention-user-id {
font-weight: 600;
color: #2d3748;
}
.dark .mention-user-id {
color: #e2e8f0;
}
.mention-display-name {
font-size: 0.75rem;
color: #718096;
}
/* ── Annotations panel ────────────────────────────────────────────────── */
.annotation-item {
background-color: #f7fafc;
border-radius: 0.5rem;
padding: 1rem;
margin-bottom: 0.75rem;
border-left: 3px solid #ecc94b;
}
.dark .annotation-item {
background-color: #2d3748;
border-left-color: #d69e2e;
}
.annotation-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.5rem;
flex-wrap: wrap;
}
.annotation-type {
font-size: 0.75rem;
font-weight: 600;
padding: 0.125rem 0.5rem;
border-radius: 9999px;
text-transform: capitalize;
}
.annotation-type--note {
background-color: #DBEAFE;
color: #1E3A8A;
}
.annotation-type--highlight {
background-color: #FEF3C7;
color: #92400E;
}
.annotation-type--underline {
background-color: #D1FAE5;
color: #065F46;
}
.annotation-type--strikethrough {
background-color: #FEE2E2;
color: #991B1B;
}
.dark .annotation-type--note {
background-color: #1E3A8A;
color: #DBEAFE;
}
.dark .annotation-type--highlight {
background-color: #92400E;
color: #FEF3C7;
}
.dark .annotation-type--underline {
background-color: #065F46;
color: #D1FAE5;
}
.dark .annotation-type--strikethrough {
background-color: #991B1B;
color: #FEE2E2;
}
.annotation-color-dot {
display: inline-block;
width: 12px;
height: 12px;
border-radius: 50%;
border: 1px solid rgba(0, 0, 0, 0.2);
}
.annotation-page {
font-size: 0.75rem;
color: #718096;
}
.annotation-content {
color: #4a5568;
font-size: 0.9375rem;
line-height: 1.5;
margin-bottom: 0.5rem;
word-break: break-word;
}
.dark .annotation-content {
color: #cbd5e0;
}
.annotation-meta {
display: flex;
gap: 0.75rem;
font-size: 0.75rem;
color: #718096;
margin-bottom: 0.5rem;
}
.annotation-author {
font-weight: 600;
}
.annotation-actions {
display: flex;
gap: 0.5rem;
}
.annotation-action-btn {
background: none;
border: 1px solid #e2e8f0;
color: #718096;
font-size: 0.75rem;
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
cursor: pointer;
min-height: 28px;
}
.annotation-action-btn:hover {
background-color: #edf2f7;
color: #2d3748;
}
.dark .annotation-action-btn {
border-color: #4a5568;
color: #a0aec0;
}
.dark .annotation-action-btn:hover {
background-color: #4a5568;
color: #e2e8f0;
}
.annotation-action-btn--danger:hover {
background-color: #FEE2E2;
color: #991B1B;
border-color: #f56565;
}
.dark .annotation-action-btn--danger:hover {
background-color: #742a2a;
color: #feb2b2;
}
.annotation-textarea {
width: 100%;
border: 1px solid #e2e8f0;
border-radius: 0.375rem;
padding: 0.75rem;
font-size: 0.875rem;
resize: vertical;
min-height: 60px;
font-family: inherit;
}
.dark .annotation-textarea {
background-color: #2d3748;
border-color: #4a5568;
color: #e2e8f0;
}
.annotation-textarea:focus {
outline: none;
border-color: #ecc94b;
box-shadow: 0 0 0 2px rgba(236, 201, 75, 0.3);
}
.annotation-select {
border: 1px solid #e2e8f0;
border-radius: 0.375rem;
padding: 0.5rem;
font-size: 0.875rem;
margin-top: 0.5rem;
width: 100%;
}
.dark .annotation-select {
background-color: #2d3748;
border-color: #4a5568;
color: #e2e8f0;
}
.annotation-submit-btn {
background-color: #ecc94b;
color: #744210;
border: none;
padding: 0.5rem 1rem;
border-radius: 0.375rem;
font-size: 0.875rem;
cursor: pointer;
font-weight: 600;
margin-top: 0.5rem;
min-height: 36px;
}
.annotation-submit-btn:hover {
background-color: #d69e2e;
}
.annotation-cancel-btn {
background-color: #e2e8f0;
color: #4a5568;
border: none;
padding: 0.5rem 1rem;
border-radius: 0.375rem;
font-size: 0.875rem;
cursor: pointer;
margin-top: 0.5rem;
margin-left: 0.5rem;
min-height: 36px;
}
.dark .annotation-cancel-btn {
background-color: #4a5568;
color: #e2e8f0;
}
/* Annotation form layout */
.annotation-form-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.75rem;
margin-top: 0.75rem;
}
.annotation-form-grid .form-group {
display: flex;
flex-direction: column;
}
.annotation-form-grid label {
font-size: 0.75rem;
font-weight: 600;
color: #4a5568;
margin-bottom: 0.25rem;
}
.dark .annotation-form-grid label {
color: #a0aec0;
}
.annotation-form-grid input,
.annotation-form-grid select {
border: 1px solid #e2e8f0;
border-radius: 0.375rem;
padding: 0.5rem;
font-size: 0.875rem;
}
.dark .annotation-form-grid input,
.dark .annotation-form-grid select {
background-color: #2d3748;
border-color: #4a5568;
color: #e2e8f0;
}
/* ── Collaboration panels grid ────────────────────────────────────────── */
.collab-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1.5rem;
}
@media (max-width: 768px) {
.collab-grid {
grid-template-columns: 1fr;
}
}
</style>
{% endblock %}
{% block content %}
<div class="annotations-container">
{% if error %}
<div class="error-box"><strong>Error:</strong> {{ error }}</div>
{% elif file %}
<!-- ── Back + header ── -->
<div style="display:flex;align-items:center;gap:1rem;margin-bottom:1.5rem;flex-wrap:wrap;">
<a href="/files/{{ file.id }}" style="display:inline-flex;align-items:center;gap:0.4rem;color:#3b82f6;font-weight:600;text-decoration:none;" aria-label="Back to File Summary">
<i class="fas fa-arrow-left" aria-hidden="true"></i> Back to File
</a>
</div>
<div class="annotations-header">
<div>
<div class="annotations-title">
<i class="fas fa-comments" aria-hidden="true" style="color:#10b981;margin-right:0.4rem;"></i>
Comments &amp; Annotations
</div>
<div class="annotations-subtitle">{{ file.original_filename }}</div>
</div>
</div>
<!-- ── PDF Viewer (EmbedPDF) ───────────────────────────────────────────── -->
{% if is_pdf and (processed_file_exists or original_file_exists) %}
<div class="pdf-viewer-card">
<div class="pdf-viewer-card-header">
<i class="fas fa-file-pdf" aria-hidden="true" style="color:#ef4444;"></i>
Document Viewer
</div>
<div id="embedpdf-viewer" aria-label="PDF document viewer"></div>
</div>
{% endif %}
<!-- ── Comments & Annotations ──────────────────────────────────────────── -->
<div class="collab-card">
<div class="collab-grid">
<!-- Comments Panel -->
<section class="comments-panel" aria-label="{{ _('comments.heading') }}">
<div class="panel-header">
<h3><i class="fas fa-comments" aria-hidden="true"></i> {{ _("comments.heading") }}</h3>
</div>
<div id="comments-list" aria-live="polite"></div>
<!-- New comment form -->
<div class="comment-form-wrapper">
<form id="comment-form" aria-label="{{ _('comments.add_comment') }}">
<div class="mention-dropdown-wrapper">
<div id="mention-dropdown" class="hidden" role="listbox" aria-label="{{ _('comments.mention_users') }}"></div>
<textarea
id="comment-input"
class="comment-textarea"
placeholder="{{ _('comments.body_placeholder') }}"
rows="3"
aria-label="{{ _('comments.body_placeholder') }}"
maxlength="10000"
></textarea>
</div>
<button type="submit" class="comment-submit-btn">
<i class="fas fa-paper-plane" aria-hidden="true"></i> {{ _("comments.add_comment") }}
</button>
</form>
</div>
</section>
<!-- Annotations Panel -->
<section class="annotations-panel" aria-label="{{ _('annotations.heading') }}">
<div class="panel-header">
<h3><i class="fas fa-sticky-note" aria-hidden="true"></i> {{ _("annotations.heading") }}</h3>
</div>
<div id="annotations-list" aria-live="polite"></div>
<!-- New annotation form -->
<div class="comment-form-wrapper">
<form id="annotation-form" aria-label="{{ _('annotations.add') }}">
<textarea
id="annotation-content-input"
class="annotation-textarea"
placeholder="{{ _('annotations.content_placeholder') }}"
rows="2"
aria-label="{{ _('annotations.content_placeholder') }}"
maxlength="5000"
></textarea>
<div class="annotation-form-grid">
<div class="form-group">
<label for="annotation-page-input">{{ _("annotations.page") }}</label>
<input type="number" id="annotation-page-input" min="1" value="1" aria-label="{{ _('annotations.page') }}">
</div>
<div class="form-group">
<label for="annotation-type-input">{{ _("annotations.type") }}</label>
<select id="annotation-type-input" aria-label="Annotation type">
<option value="note">{{ _("annotations.type_note") }}</option>
<option value="highlight">{{ _("annotations.type_highlight") }}</option>
<option value="underline">{{ _("annotations.type_underline") }}</option>
<option value="strikethrough">{{ _("annotations.type_strikethrough") }}</option>
</select>
</div>
<div class="form-group">
<label for="annotation-color-input">{{ _("annotations.color") }}</label>
<input type="color" id="annotation-color-input" value="#ffff00" aria-label="{{ _('annotations.color') }}">
</div>
</div>
<button type="submit" class="annotation-submit-btn">
<i class="fas fa-plus" aria-hidden="true"></i> {{ _("annotations.add") }}
</button>
</form>
</div>
</section>
</div>
</div>
{% else %}
<!-- file is None without error -->
<div class="error-box">Document not found.</div>
{% endif %}
</div>
<!-- Comments & Annotations JS -->
<script src="{{ url_for('static', path='js/comments.js') }}" defer></script>
<script src="{{ url_for('static', path='js/annotations.js') }}" defer></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
{% if file %}
var fileId = {{ file.id | tojson }};
// Detect current user from whoami endpoint
fetch('/api/auth/whoami')
.then(function (r) { return r.json(); })
.then(function (data) {
var userId = (data && (data.email || data.preferred_username)) || '';
var commentsI18n = {
empty: {{ _("comments.empty") | tojson }},
add_comment: {{ _("comments.add_comment") | tojson }},
add_reply: {{ _("comments.add_reply") | tojson }},
edit: {{ _("comments.edit") | tojson }},
save: {{ _("comments.save") | tojson }},
resolve: {{ _("comments.resolve") | tojson }},
resolved: {{ _("comments.resolved") | tojson }},
unresolve: {{ _("comments.unresolve") | tojson }},
delete_confirm: {{ _("comments.delete_confirm") | tojson }},
reply_placeholder: {{ _("comments.reply_placeholder") | tojson }},
body_placeholder: {{ _("comments.body_placeholder") | tojson }},
mention_users: {{ _("comments.mention_users") | tojson }},
cancel: {{ _("common.cancel") | tojson }}
};
var annotationsI18n = {
empty: {{ _("annotations.empty") | tojson }},
add: {{ _("annotations.add") | tojson }},
save: {{ _("annotations.save") | tojson }},
delete_confirm: {{ _("annotations.delete_confirm") | tojson }},
page: {{ _("annotations.page") | tojson }},
color: {{ _("annotations.color") | tojson }},
type_note: {{ _("annotations.type_note") | tojson }},
type_highlight: {{ _("annotations.type_highlight") | tojson }},
type_underline: {{ _("annotations.type_underline") | tojson }},
type_strikethrough: {{ _("annotations.type_strikethrough") | tojson }},
cancel: {{ _("common.cancel") | tojson }}
};
if (typeof initComments === 'function') {
initComments(fileId, userId, commentsI18n);
}
if (typeof initAnnotations === 'function') {
initAnnotations(fileId, userId, annotationsI18n);
}
})
.catch(function () {
// Auth disabled — initialise with empty user
if (typeof initComments === 'function') initComments(fileId, '', {});
if (typeof initAnnotations === 'function') initAnnotations(fileId, '', {});
});
{% endif %}
});
</script>
<!-- ── EmbedPDF Viewer init ── -->
{% if file and is_pdf and (processed_file_exists or original_file_exists) %}
<script async type="module">
import EmbedPDF from 'https://cdn.jsdelivr.net/npm/@embedpdf/snippet@2/dist/embedpdf.js';
const viewerEl = document.getElementById('embedpdf-viewer');
if (viewerEl) {
{% if processed_file_exists %}
const pdfUrl = '/api/files/{{ file.id }}/preview?version=processed';
{% else %}
const pdfUrl = '/api/files/{{ file.id }}/preview?version=original';
{% endif %}
EmbedPDF.init({
type: 'container',
target: viewerEl,
src: pdfUrl,
});
}
</script>
{% endif %}
{% endblock %}
+5 -5
View File
@@ -1056,7 +1056,7 @@
html += `
<div role="listitem">
<a href="/files/${doc.file_id}/detail" aria-label="${title}${scorePercent}% similarity (${scoreLabel})" style="text-decoration: none; color: inherit; display: block;">
<a href="/files/${doc.file_id}" aria-label="${title}${scorePercent}% similarity (${scoreLabel})" style="text-decoration: none; color: inherit; display: block;">
<div style="display: flex; align-items: center; gap: 1rem; padding: 0.75rem 1rem; background-color: #f7fafc; border-radius: 0.5rem; border: 1px solid #e2e8f0; transition: border-color 0.2s; cursor: pointer;" onmouseover="this.style.borderColor='#4299e1'" onmouseout="this.style.borderColor='#e2e8f0'">
<div style="flex-shrink: 0; width: 48px; height: 48px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 0.875rem; color: white; background-color: ${scorePercent >= 80 ? '#48bb78' : scorePercent >= 50 ? '#ecc94b' : '#718096'};" aria-hidden="true">
${scorePercent}%
@@ -1097,14 +1097,14 @@
{% block content %}
<div class="detail-container">
<div style="display:flex;align-items:center;gap:1rem;margin-bottom:1.5rem;flex-wrap:wrap;">
<a href="/files" class="back-button" style="margin-bottom:0;" aria-label="Back to File List">
<a href="/files/{{ file.id }}" class="back-button" style="margin-bottom:0;" aria-label="Back to File Summary">
<i class="fas fa-arrow-left" aria-hidden="true"></i>
Back to File List
Back to File
</a>
{% if file %}
<a href="/files/{{ file.id }}" class="back-button" style="margin-bottom:0;" aria-label="View document for {{ file.original_filename }}">
<a href="/files/{{ file.id }}/detail" class="back-button" style="margin-bottom:0;" aria-label="View document detail for {{ file.original_filename }}">
<i class="fas fa-eye" aria-hidden="true"></i>
View Document
Document Detail
</a>
{% endif %}
</div>
+196
View File
@@ -0,0 +1,196 @@
{% extends "base.html" %}
{% block title %}{{ file.document_title or file.original_filename or 'Document' }} - DocuElevate{% endblock %}
{% block head_extra %}
<script src="/static/js/common.js"></script>
<style>
.summary-container { max-width: 900px; margin: 0 auto; }
/* ── header ── */
.summary-header {
display: flex; align-items: flex-start; justify-content: space-between;
gap: 1rem; margin-bottom: 1.5rem; flex-wrap: wrap;
}
.summary-title { font-size: 1.75rem; font-weight: 700; color: #1f2937; line-height: 1.25; }
.summary-subtitle { font-size: 0.9rem; color: #6b7280; margin-top: 0.25rem; font-family: monospace; }
.dark .summary-title { color: #f3f4f6; }
.dark .summary-subtitle { color: #9ca3af; }
/* ── status pill ── */
.status-pill {
display: inline-flex; align-items: center; gap: 0.4rem;
padding: 0.35rem 0.85rem; border-radius: 9999px;
font-size: 0.8rem; font-weight: 600; white-space: nowrap;
}
.pill-completed { background: #d1fae5; color: #065f46; }
.pill-failed { background: #fee2e2; color: #991b1b; }
.pill-processing { background: #dbeafe; color: #1e3a8a; }
.pill-pending { background: #fef3c7; color: #92400e; }
.pill-duplicate { background: #e5e7eb; color: #374151; }
/* ── cards ── */
.summary-card {
background: white; border-radius: 0.5rem;
box-shadow: 0 1px 3px rgba(0,0,0,.08);
padding: 1.5rem; margin-bottom: 1.25rem;
}
.dark .summary-card { background: #1f2937; box-shadow: 0 1px 3px rgba(0,0,0,.3); }
.summary-card-title {
font-size: 1rem; font-weight: 700; color: #374151;
text-transform: uppercase; letter-spacing: 0.05em;
margin-bottom: 1rem;
}
.dark .summary-card-title { color: #d1d5db; }
/* ── info rows ── */
.info-row { display: flex; justify-content: space-between; padding: 0.4rem 0; border-bottom: 1px solid #f3f4f6; font-size: 0.875rem; }
.info-row:last-child { border-bottom: none; }
.info-key { color: #6b7280; }
.info-val { color: #1f2937; font-family: monospace; text-align: right; word-break: break-all; max-width: 60%; }
.dark .info-row { border-bottom-color: #374151; }
.dark .info-key { color: #9ca3af; }
.dark .info-val { color: #e5e7eb; }
/* ── nav cards ── */
.nav-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; margin-bottom: 1.25rem; }
@media (max-width: 768px) { .nav-grid { grid-template-columns: 1fr; } }
.nav-card {
display: flex; flex-direction: column; align-items: center; justify-content: center;
background: white; border-radius: 0.5rem; box-shadow: 0 1px 3px rgba(0,0,0,.08);
padding: 1.5rem; text-decoration: none; color: #374151;
transition: box-shadow 0.15s, transform 0.15s; min-height: 120px;
}
.nav-card:hover { box-shadow: 0 4px 12px rgba(0,0,0,.15); transform: translateY(-2px); }
.dark .nav-card { background: #1f2937; color: #e5e7eb; }
.dark .nav-card:hover { box-shadow: 0 4px 12px rgba(0,0,0,.4); }
.nav-card i { font-size: 1.5rem; margin-bottom: 0.75rem; }
.nav-card-title { font-weight: 700; font-size: 0.95rem; margin-bottom: 0.25rem; }
.nav-card-desc { font-size: 0.8rem; color: #6b7280; text-align: center; }
.dark .nav-card-desc { color: #9ca3af; }
/* ── action buttons ── */
.action-btn {
display: inline-flex; align-items: center; gap: 0.4rem;
padding: 0.5rem 1.1rem; border-radius: 0.375rem;
font-size: 0.875rem; font-weight: 600; text-decoration: none;
cursor: pointer; border: none; transition: filter 0.1s;
}
.action-btn:hover { filter: brightness(0.92); }
.btn-primary { background: #3b82f6; color: white; }
.btn-secondary { background: #f3f4f6; color: #374151; }
/* ── error ── */
.error-box { background: #fee2e2; border: 1px solid #f87171; color: #b91c1c; padding: 1rem; border-radius: 0.375rem; margin-bottom: 1rem; }
</style>
{% endblock %}
{% block content %}
<div class="summary-container">
{% if error %}
<div class="error-box"><strong>Error:</strong> {{ error }}</div>
{% elif file %}
<!-- ── Back + header ── -->
<a href="/files" style="display:inline-flex;align-items:center;gap:0.4rem;color:#3b82f6;font-weight:600;text-decoration:none;margin-bottom:1.25rem;" aria-label="Back to Files">
<i class="fas fa-arrow-left" aria-hidden="true"></i> Back to Files
</a>
<div class="summary-header">
<div>
<div class="summary-title">
{% if gpt_metadata and gpt_metadata.filename %}
{{ gpt_metadata.filename | replace('.pdf','') | replace('_',' ') }}
{% elif file.document_title %}
{{ file.document_title }}
{% else %}
{{ file.original_filename or '(untitled)' }}
{% endif %}
</div>
<div class="summary-subtitle">{{ file.original_filename }}</div>
</div>
<div style="display:flex;align-items:center;gap:0.75rem;flex-wrap:wrap;">
<!-- Status pill -->
{% if file.is_duplicate %}
<span class="status-pill pill-duplicate"><i class="fas fa-copy" aria-hidden="true"></i> Duplicate</span>
{% elif step_summary %}
{% set main_completed = step_summary.main.success + step_summary.main.skipped %}
{% if step_summary.main.failure > 0 or step_summary.uploads.failure > 0 %}
<span class="status-pill pill-failed"><i class="fas fa-times-circle" aria-hidden="true"></i> Failed</span>
{% elif step_summary.main["in_progress"] > 0 or step_summary.uploads["in_progress"] > 0 %}
<span class="status-pill pill-processing"><i class="fas fa-circle-notch fa-spin" aria-hidden="true"></i> Processing</span>
{% elif step_summary.total_main_steps > 0 and main_completed == step_summary.total_main_steps and step_summary.main.failure == 0 %}
<span class="status-pill pill-completed"><i class="fas fa-check-circle" aria-hidden="true"></i> Completed</span>
{% else %}
<span class="status-pill pill-pending"><i class="fas fa-pause-circle" aria-hidden="true"></i> Pending</span>
{% endif %}
{% endif %}
</div>
</div>
<!-- ── Navigation cards ── -->
<nav class="nav-grid" aria-label="File sections">
<a href="/files/{{ file.id }}/detail" class="nav-card">
<i class="fas fa-file-alt" aria-hidden="true" style="color:#3b82f6;"></i>
<div class="nav-card-title">Document Detail</div>
<div class="nav-card-desc">Metadata, preview &amp; extracted text</div>
</a>
<a href="/files/{{ file.id }}/process" class="nav-card">
<i class="fas fa-cogs" aria-hidden="true" style="color:#6366f1;"></i>
<div class="nav-card-title">Processing</div>
<div class="nav-card-desc">Pipeline status &amp; processing history</div>
</a>
<a href="/files/{{ file.id }}/annotations" class="nav-card">
<i class="fas fa-comments" aria-hidden="true" style="color:#10b981;"></i>
<div class="nav-card-title">Comments &amp; Annotations</div>
<div class="nav-card-desc">Discussion &amp; document annotations</div>
</a>
</nav>
<!-- ── File info card ── -->
<div class="summary-card">
<div class="summary-card-title"><i class="fas fa-info-circle" aria-hidden="true" style="color:#6366f1;margin-right:0.4rem;"></i>File Information</div>
<div class="info-row"><span class="info-key">File ID</span><span class="info-val">{{ file.id }}</span></div>
<div class="info-row"><span class="info-key">Filename</span><span class="info-val">{{ file.original_filename }}</span></div>
<div class="info-row"><span class="info-key">Size</span><span class="info-val">{{ (file.file_size / 1024) | round(1) }} KB</span></div>
<div class="info-row"><span class="info-key">MIME Type</span><span class="info-val">{{ file.mime_type or 'unknown' }}</span></div>
<div class="info-row"><span class="info-key">Created</span><span class="info-val">{{ file.created_at.strftime('%Y-%m-%d %H:%M') if file.created_at else 'N/A' }}</span></div>
{% if file.detected_language %}
<div class="info-row"><span class="info-key">Language</span><span class="info-val">{{ file.detected_language }}</span></div>
{% endif %}
{% if pipeline_info %}
<div class="info-row"><span class="info-key">Pipeline</span><span class="info-val">{{ pipeline_info.name }}</span></div>
{% endif %}
{% if file.document_title %}
<div class="info-row"><span class="info-key">Document Title</span><span class="info-val">{{ file.document_title }}</span></div>
{% endif %}
</div>
<!-- ── Quick actions ── -->
<div class="summary-card">
<div class="summary-card-title"><i class="fas fa-bolt" aria-hidden="true" style="color:#f59e0b;margin-right:0.4rem;"></i>Quick Actions</div>
<div style="display:flex;gap:0.75rem;flex-wrap:wrap;">
{% if processed_file_exists %}
<a href="/api/files/{{ file.id }}/download?version=processed" class="action-btn btn-primary">
<i class="fas fa-download" aria-hidden="true"></i> Download Processed
</a>
{% endif %}
{% if original_file_exists %}
<a href="/api/files/{{ file.id }}/download?version=original" class="action-btn btn-secondary">
<i class="fas fa-download" aria-hidden="true"></i> Download Original
</a>
{% endif %}
<a href="/files/{{ file.id }}/detail" class="action-btn btn-secondary">
<i class="fas fa-eye" aria-hidden="true"></i> View Detail
</a>
</div>
</div>
{% else %}
<!-- file is None without error -->
<div class="error-box">Document not found.</div>
{% endif %}
</div>
{% endblock %}
+4 -636
View File
@@ -148,510 +148,6 @@
/* ── error ── */
.error-box { background: #fee2e2; border: 1px solid #f87171; color: #b91c1c; padding: 1rem; border-radius: 0.375rem; margin-bottom: 1rem; }
/* ── Comments panel ───────────────────────────────────────────────────── */
.comments-panel, .annotations-panel {
margin-top: 0;
}
.panel-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 1rem;
}
.panel-header h3 {
margin-bottom: 0;
}
.comments-empty, .annotations-empty {
text-align: center;
padding: 2rem;
color: #718096;
}
.comments-loading, .annotations-loading {
text-align: center;
padding: 1.5rem;
color: #718096;
}
.comments-error {
text-align: center;
padding: 1rem;
color: #991B1B;
}
/* Individual comment */
.comment-item {
background-color: #f7fafc;
border-radius: 0.5rem;
padding: 1rem;
margin-bottom: 0.75rem;
border-left: 3px solid #4299e1;
}
.dark .comment-item {
background-color: #2d3748;
border-left-color: #63b3ed;
}
.comment-item.comment-reply {
margin-left: 1.5rem;
border-left-color: #a0aec0;
background-color: #edf2f7;
}
.dark .comment-item.comment-reply {
background-color: #1a202c;
border-left-color: #4a5568;
}
.comment-item.comment-resolved {
opacity: 0.75;
border-left-color: #48bb78;
}
.comment-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.5rem;
flex-wrap: wrap;
}
.comment-author {
font-weight: 600;
color: #2d3748;
font-size: 0.875rem;
}
.dark .comment-author {
color: #e2e8f0;
}
.comment-time {
font-size: 0.75rem;
color: #718096;
}
.comment-resolved-badge {
font-size: 0.75rem;
color: #065F46;
background-color: #D1FAE5;
padding: 0.125rem 0.5rem;
border-radius: 9999px;
font-weight: 600;
}
.dark .comment-resolved-badge {
background-color: #065F46;
color: #D1FAE5;
}
.comment-body {
color: #4a5568;
font-size: 0.9375rem;
line-height: 1.5;
margin-bottom: 0.5rem;
word-break: break-word;
}
.dark .comment-body {
color: #cbd5e0;
}
.comment-mention {
color: #3182ce;
font-weight: 600;
}
.dark .comment-mention {
color: #63b3ed;
}
.comment-actions {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.comment-action-btn {
background: none;
border: 1px solid #e2e8f0;
color: #718096;
font-size: 0.75rem;
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 0.25rem;
min-height: 28px;
}
.comment-action-btn:hover {
background-color: #edf2f7;
color: #2d3748;
}
.dark .comment-action-btn {
border-color: #4a5568;
color: #a0aec0;
}
.dark .comment-action-btn:hover {
background-color: #4a5568;
color: #e2e8f0;
}
.comment-action-btn--danger:hover {
background-color: #FEE2E2;
color: #991B1B;
border-color: #f56565;
}
.dark .comment-action-btn--danger:hover {
background-color: #742a2a;
color: #feb2b2;
border-color: #f56565;
}
.comment-replies {
margin-top: 0.75rem;
}
/* Comment form */
.comment-form-wrapper {
margin-top: 1rem;
}
.comment-textarea {
width: 100%;
border: 1px solid #e2e8f0;
border-radius: 0.375rem;
padding: 0.75rem;
font-size: 0.875rem;
resize: vertical;
min-height: 60px;
font-family: inherit;
}
.dark .comment-textarea {
background-color: #2d3748;
border-color: #4a5568;
color: #e2e8f0;
}
.comment-textarea:focus {
outline: none;
border-color: #4299e1;
box-shadow: 0 0 0 2px rgba(66, 153, 225, 0.3);
}
.comment-submit-btn {
background-color: #4299e1;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 0.375rem;
font-size: 0.875rem;
cursor: pointer;
font-weight: 600;
margin-top: 0.5rem;
min-height: 36px;
}
.comment-submit-btn:hover {
background-color: #3182ce;
}
.comment-cancel-btn {
background-color: #e2e8f0;
color: #4a5568;
border: none;
padding: 0.5rem 1rem;
border-radius: 0.375rem;
font-size: 0.875rem;
cursor: pointer;
margin-top: 0.5rem;
margin-left: 0.5rem;
min-height: 36px;
}
.dark .comment-cancel-btn {
background-color: #4a5568;
color: #e2e8f0;
}
.comment-edit-btns, .annotation-edit-btns {
display: flex;
gap: 0.5rem;
margin-top: 0.5rem;
}
.comment-reply-form {
margin-top: 0.75rem;
padding-top: 0.75rem;
border-top: 1px solid #e2e8f0;
}
.dark .comment-reply-form {
border-top-color: #4a5568;
}
/* @mention dropdown */
.mention-dropdown-wrapper {
position: relative;
}
#mention-dropdown {
position: absolute;
bottom: 100%;
left: 0;
width: 280px;
max-height: 200px;
overflow-y: auto;
background-color: white;
border: 1px solid #e2e8f0;
border-radius: 0.375rem;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
z-index: 50;
margin-bottom: 0.25rem;
}
.dark #mention-dropdown {
background-color: #2d3748;
border-color: #4a5568;
}
.mention-item {
display: flex;
flex-direction: column;
width: 100%;
text-align: left;
padding: 0.5rem 0.75rem;
border: none;
background: none;
cursor: pointer;
font-size: 0.875rem;
min-height: 44px;
justify-content: center;
}
.mention-item:hover, .mention-item:focus {
background-color: #edf2f7;
outline: none;
}
.dark .mention-item:hover, .dark .mention-item:focus {
background-color: #4a5568;
}
.mention-user-id {
font-weight: 600;
color: #2d3748;
}
.dark .mention-user-id {
color: #e2e8f0;
}
.mention-display-name {
font-size: 0.75rem;
color: #718096;
}
/* ── Annotations panel ────────────────────────────────────────────────── */
.annotation-item {
background-color: #f7fafc;
border-radius: 0.5rem;
padding: 1rem;
margin-bottom: 0.75rem;
border-left: 3px solid #ecc94b;
}
.dark .annotation-item {
background-color: #2d3748;
border-left-color: #d69e2e;
}
.annotation-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.5rem;
flex-wrap: wrap;
}
.annotation-type {
font-size: 0.75rem;
font-weight: 600;
padding: 0.125rem 0.5rem;
border-radius: 9999px;
text-transform: capitalize;
}
.annotation-type--note {
background-color: #DBEAFE;
color: #1E3A8A;
}
.annotation-type--highlight {
background-color: #FEF3C7;
color: #92400E;
}
.annotation-type--underline {
background-color: #D1FAE5;
color: #065F46;
}
.annotation-type--strikethrough {
background-color: #FEE2E2;
color: #991B1B;
}
.dark .annotation-type--note {
background-color: #1E3A8A;
color: #DBEAFE;
}
.dark .annotation-type--highlight {
background-color: #92400E;
color: #FEF3C7;
}
.dark .annotation-type--underline {
background-color: #065F46;
color: #D1FAE5;
}
.dark .annotation-type--strikethrough {
background-color: #991B1B;
color: #FEE2E2;
}
.annotation-color-dot {
display: inline-block;
width: 12px;
height: 12px;
border-radius: 50%;
border: 1px solid rgba(0, 0, 0, 0.2);
}
.annotation-page {
font-size: 0.75rem;
color: #718096;
}
.annotation-content {
color: #4a5568;
font-size: 0.9375rem;
line-height: 1.5;
margin-bottom: 0.5rem;
word-break: break-word;
}
.dark .annotation-content {
color: #cbd5e0;
}
.annotation-meta {
display: flex;
gap: 0.75rem;
font-size: 0.75rem;
color: #718096;
margin-bottom: 0.5rem;
}
.annotation-author {
font-weight: 600;
}
.annotation-actions {
display: flex;
gap: 0.5rem;
}
.annotation-action-btn {
background: none;
border: 1px solid #e2e8f0;
color: #718096;
font-size: 0.75rem;
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
cursor: pointer;
min-height: 28px;
}
.annotation-action-btn:hover {
background-color: #edf2f7;
color: #2d3748;
}
.dark .annotation-action-btn {
border-color: #4a5568;
color: #a0aec0;
}
.dark .annotation-action-btn:hover {
background-color: #4a5568;
color: #e2e8f0;
}
.annotation-action-btn--danger:hover {
background-color: #FEE2E2;
color: #991B1B;
border-color: #f56565;
}
.dark .annotation-action-btn--danger:hover {
background-color: #742a2a;
color: #feb2b2;
}
.annotation-textarea {
width: 100%;
border: 1px solid #e2e8f0;
border-radius: 0.375rem;
padding: 0.75rem;
font-size: 0.875rem;
resize: vertical;
min-height: 60px;
font-family: inherit;
}
.dark .annotation-textarea {
background-color: #2d3748;
border-color: #4a5568;
color: #e2e8f0;
}
.annotation-textarea:focus {
outline: none;
border-color: #ecc94b;
box-shadow: 0 0 0 2px rgba(236, 201, 75, 0.3);
}
.annotation-select {
border: 1px solid #e2e8f0;
border-radius: 0.375rem;
padding: 0.5rem;
font-size: 0.875rem;
margin-top: 0.5rem;
width: 100%;
}
.dark .annotation-select {
background-color: #2d3748;
border-color: #4a5568;
color: #e2e8f0;
}
.annotation-submit-btn {
background-color: #ecc94b;
color: #744210;
border: none;
padding: 0.5rem 1rem;
border-radius: 0.375rem;
font-size: 0.875rem;
cursor: pointer;
font-weight: 600;
margin-top: 0.5rem;
min-height: 36px;
}
.annotation-submit-btn:hover {
background-color: #d69e2e;
}
.annotation-cancel-btn {
background-color: #e2e8f0;
color: #4a5568;
border: none;
padding: 0.5rem 1rem;
border-radius: 0.375rem;
font-size: 0.875rem;
cursor: pointer;
margin-top: 0.5rem;
margin-left: 0.5rem;
min-height: 36px;
}
.dark .annotation-cancel-btn {
background-color: #4a5568;
color: #e2e8f0;
}
/* Annotation form layout */
.annotation-form-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.75rem;
margin-top: 0.75rem;
}
.annotation-form-grid .form-group {
display: flex;
flex-direction: column;
}
.annotation-form-grid label {
font-size: 0.75rem;
font-weight: 600;
color: #4a5568;
margin-bottom: 0.25rem;
}
.dark .annotation-form-grid label {
color: #a0aec0;
}
.annotation-form-grid input,
.annotation-form-grid select {
border: 1px solid #e2e8f0;
border-radius: 0.375rem;
padding: 0.5rem;
font-size: 0.875rem;
}
.dark .annotation-form-grid input,
.dark .annotation-form-grid select {
background-color: #2d3748;
border-color: #4a5568;
color: #e2e8f0;
}
/* ── Collaboration panels grid ────────────────────────────────────────── */
.collab-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1.5rem;
}
@media (max-width: 768px) {
.collab-grid {
grid-template-columns: 1fr;
}
}
</style>
{% endblock %}
@@ -663,8 +159,8 @@
{% elif file %}
<!-- ── Back + header ── -->
<a href="/files" style="display:inline-flex;align-items:center;gap:0.4rem;color:#3b82f6;font-weight:600;text-decoration:none;margin-bottom:1.25rem;" aria-label="Back to Files">
<i class="fas fa-arrow-left" aria-hidden="true"></i> Back to Files
<a href="/files/{{ file.id }}" style="display:inline-flex;align-items:center;gap:0.4rem;color:#3b82f6;font-weight:600;text-decoration:none;margin-bottom:1.25rem;" aria-label="Back to File Summary">
<i class="fas fa-arrow-left" aria-hidden="true"></i> Back to File
</a>
<div class="doc-header">
@@ -699,8 +195,8 @@
{% endif %}
<!-- Process detail link -->
<a href="/files/{{ file.id }}/detail" class="action-btn btn-process">
<i class="fas fa-cogs" aria-hidden="true"></i> Processing Details
<a href="/files/{{ file.id }}/process" class="action-btn btn-process">
<i class="fas fa-cogs" aria-hidden="true"></i> Processing
</a>
</div>
</div>
@@ -1054,83 +550,6 @@
</div>
{% endif %}
<!-- ── Comments & Annotations ──────────────────────────────────────────── -->
<div class="doc-card">
<div class="collab-grid">
<!-- Comments Panel -->
<section class="comments-panel" aria-label="{{ _('comments.heading') }}">
<div class="panel-header">
<h3><i class="fas fa-comments" aria-hidden="true"></i> {{ _("comments.heading") }}</h3>
</div>
<div id="comments-list" aria-live="polite"></div>
<!-- New comment form -->
<div class="comment-form-wrapper">
<form id="comment-form" aria-label="{{ _('comments.add_comment') }}">
<div class="mention-dropdown-wrapper">
<div id="mention-dropdown" class="hidden" role="listbox" aria-label="{{ _('comments.mention_users') }}"></div>
<textarea
id="comment-input"
class="comment-textarea"
placeholder="{{ _('comments.body_placeholder') }}"
rows="3"
aria-label="{{ _('comments.body_placeholder') }}"
maxlength="10000"
></textarea>
</div>
<button type="submit" class="comment-submit-btn">
<i class="fas fa-paper-plane" aria-hidden="true"></i> {{ _("comments.add_comment") }}
</button>
</form>
</div>
</section>
<!-- Annotations Panel -->
<section class="annotations-panel" aria-label="{{ _('annotations.heading') }}">
<div class="panel-header">
<h3><i class="fas fa-sticky-note" aria-hidden="true"></i> {{ _("annotations.heading") }}</h3>
</div>
<div id="annotations-list" aria-live="polite"></div>
<!-- New annotation form -->
<div class="comment-form-wrapper">
<form id="annotation-form" aria-label="{{ _('annotations.add') }}">
<textarea
id="annotation-content-input"
class="annotation-textarea"
placeholder="{{ _('annotations.content_placeholder') }}"
rows="2"
aria-label="{{ _('annotations.content_placeholder') }}"
maxlength="5000"
></textarea>
<div class="annotation-form-grid">
<div class="form-group">
<label for="annotation-page-input">{{ _("annotations.page") }}</label>
<input type="number" id="annotation-page-input" min="1" value="1" aria-label="{{ _('annotations.page') }}">
</div>
<div class="form-group">
<label for="annotation-type-input">{{ _("annotations.type") }}</label>
<select id="annotation-type-input" aria-label="Annotation type">
<option value="note">{{ _("annotations.type_note") }}</option>
<option value="highlight">{{ _("annotations.type_highlight") }}</option>
<option value="underline">{{ _("annotations.type_underline") }}</option>
<option value="strikethrough">{{ _("annotations.type_strikethrough") }}</option>
</select>
</div>
<div class="form-group">
<label for="annotation-color-input">{{ _("annotations.color") }}</label>
<input type="color" id="annotation-color-input" value="#ffff00" aria-label="{{ _('annotations.color') }}">
</div>
</div>
<button type="submit" class="annotation-submit-btn">
<i class="fas fa-plus" aria-hidden="true"></i> {{ _("annotations.add") }}
</button>
</form>
</div>
</section>
</div>
</div>
{% else %}
<!-- file is None without error -->
<div class="error-box">Document not found.</div>
@@ -1138,9 +557,6 @@
</div>
<!-- Comments & Annotations JS -->
<script src="{{ url_for('static', path='js/comments.js') }}" defer></script>
<script src="{{ url_for('static', path='js/annotations.js') }}" defer></script>
<script>
// ── Toggle inline OCR text visibility ──
function toggleOcrText() {
@@ -1521,54 +937,6 @@
pdfInit('/api/files/{{ file.id }}/preview?version={{ pv }}');
{% endif %}
{% endif %}
{% if file %}
// ── Initialise comments & annotations ──
var fileId = {{ file.id | tojson }};
fetch('/api/auth/whoami')
.then(function (r) { return r.json(); })
.then(function (data) {
var userId = (data && (data.email || data.preferred_username)) || '';
var commentsI18n = {
empty: {{ _("comments.empty") | tojson }},
add_comment: {{ _("comments.add_comment") | tojson }},
add_reply: {{ _("comments.add_reply") | tojson }},
edit: {{ _("comments.edit") | tojson }},
save: {{ _("comments.save") | tojson }},
resolve: {{ _("comments.resolve") | tojson }},
resolved: {{ _("comments.resolved") | tojson }},
unresolve: {{ _("comments.unresolve") | tojson }},
delete_confirm: {{ _("comments.delete_confirm") | tojson }},
reply_placeholder: {{ _("comments.reply_placeholder") | tojson }},
body_placeholder: {{ _("comments.body_placeholder") | tojson }},
mention_users: {{ _("comments.mention_users") | tojson }},
cancel: {{ _("common.cancel") | tojson }}
};
var annotationsI18n = {
empty: {{ _("annotations.empty") | tojson }},
add: {{ _("annotations.add") | tojson }},
save: {{ _("annotations.save") | tojson }},
delete_confirm: {{ _("annotations.delete_confirm") | tojson }},
page: {{ _("annotations.page") | tojson }},
color: {{ _("annotations.color") | tojson }},
type_note: {{ _("annotations.type_note") | tojson }},
type_highlight: {{ _("annotations.type_highlight") | tojson }},
type_underline: {{ _("annotations.type_underline") | tojson }},
type_strikethrough: {{ _("annotations.type_strikethrough") | tojson }},
cancel: {{ _("common.cancel") | tojson }}
};
if (typeof initComments === 'function') {
initComments(fileId, userId, commentsI18n);
}
if (typeof initAnnotations === 'function') {
initAnnotations(fileId, userId, annotationsI18n);
}
})
.catch(function () {
if (typeof initComments === 'function') initComments(fileId, '', {});
if (typeof initAnnotations === 'function') initAnnotations(fileId, '', {});
});
{% endif %}
});
</script>
{% endblock %}
+1 -1
View File
@@ -904,7 +904,7 @@
function viewFileDetail(fileId, event) {
if (event) event.stopPropagation();
window.location.href = `/files/${fileId}/detail`;
window.location.href = `/files/${fileId}`;
}
// ── Preview modal ──