Sync from dev: tsvector search, type/tag filters, pagination, dropdown folder picker. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
156 lines
9.1 KiB
HTML
156 lines
9.1 KiB
HTML
{% extends "base.html" %}
|
|
{% block content %}
|
|
<div class="page-header">
|
|
<h1 class="page-title">Files<span class="page-count">{{ total_files }}</span></h1>
|
|
<div class="flex gap-2">
|
|
<form action="/files/sync" method="post" style="display:inline">
|
|
<button type="submit" class="btn btn-secondary">Sync Files</button>
|
|
</form>
|
|
<a href="/files/upload{{ '?folder=' ~ current_folder if current_folder }}{{ '?context_type=' ~ context_type ~ '&context_id=' ~ context_id if context_type }}" class="btn btn-primary">+ Upload File</a>
|
|
</div>
|
|
</div>
|
|
|
|
{% if sync_result and (sync_result.added > 0 or sync_result.removed > 0) %}
|
|
<div class="flash-message" style="background: var(--accent-soft); border: 1px solid var(--accent); border-radius: var(--radius); padding: 8px 12px; margin-bottom: 16px; color: var(--text); font-size: 0.85rem;">
|
|
Synced: {{ sync_result.added }} file{{ 's' if sync_result.added != 1 }} added, {{ sync_result.removed }} removed
|
|
</div>
|
|
{% endif %}
|
|
|
|
<form class="filters-bar" method="get" action="/files" style="display: flex; gap: 8px; flex-wrap: wrap; align-items: center; margin-bottom: 16px;">
|
|
<input type="text" name="q" value="{{ current_q }}" class="form-input" placeholder="Search files..." style="max-width: 220px; padding: 6px 10px; font-size: 0.85rem;">
|
|
|
|
<select name="folder" class="form-input" style="max-width: 200px; padding: 6px 10px; font-size: 0.85rem;" onchange="this.form.submit()">
|
|
<option value="" {{ 'selected' if current_folder is none }}>All folders</option>
|
|
<option value=" " {{ 'selected' if current_folder is not none and current_folder == '' }}>/ (root)</option>
|
|
{% for f in folders %}
|
|
<option value="{{ f }}" {{ 'selected' if current_folder == f }}>{% if '/' in f %} {{ f.split('/')[-1] }}{% else %}{{ f }}{% endif %}</option>
|
|
{% endfor %}
|
|
</select>
|
|
|
|
<select name="file_type" class="form-input" style="max-width: 160px; padding: 6px 10px; font-size: 0.85rem;" onchange="this.form.submit()">
|
|
<option value="">All types</option>
|
|
<option value="image" {{ 'selected' if current_type == 'image' }}>Images</option>
|
|
<option value="document" {{ 'selected' if current_type == 'document' }}>Documents</option>
|
|
<option value="text" {{ 'selected' if current_type == 'text' }}>Text</option>
|
|
<option value="spreadsheet" {{ 'selected' if current_type == 'spreadsheet' }}>Spreadsheets</option>
|
|
<option value="archive" {{ 'selected' if current_type == 'archive' }}>Archives</option>
|
|
</select>
|
|
|
|
{% if all_tags %}
|
|
<select name="tag" class="form-input" style="max-width: 160px; padding: 6px 10px; font-size: 0.85rem;" onchange="this.form.submit()">
|
|
<option value="">All tags</option>
|
|
{% for t in all_tags %}
|
|
<option value="{{ t }}" {{ 'selected' if current_tag == t }}>{{ t }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
{% endif %}
|
|
|
|
{% if current_q or current_type or current_tag or current_folder is not none %}
|
|
<input type="hidden" name="sort" value="{{ current_sort }}">
|
|
<button type="submit" class="btn btn-primary btn-xs">Search</button>
|
|
<a href="/files" class="btn btn-ghost btn-xs">Clear</a>
|
|
{% else %}
|
|
<button type="submit" class="btn btn-primary btn-xs">Search</button>
|
|
{% endif %}
|
|
</form>
|
|
|
|
{% set qp = [] %}
|
|
{% if current_folder is not none %}{% if current_folder == '' %}{{ qp.append('folder= ') or '' }}{% else %}{{ qp.append('folder=' ~ current_folder) or '' }}{% endif %}{% endif %}
|
|
{% if current_q %}{{ qp.append('q=' ~ current_q) or '' }}{% endif %}
|
|
{% if current_type %}{{ qp.append('file_type=' ~ current_type) or '' }}{% endif %}
|
|
{% if current_tag %}{{ qp.append('tag=' ~ current_tag) or '' }}{% endif %}
|
|
{% set filter_qs = qp | join('&') %}
|
|
{% set sort_base = '/files?' ~ (filter_qs ~ '&' if filter_qs else '') %}
|
|
|
|
{% if items %}
|
|
<div class="card" style="overflow-x: auto;">
|
|
<table class="data-table" style="width: 100%; border-collapse: collapse;">
|
|
<thead>
|
|
<tr style="border-bottom: 1px solid var(--border); text-align: left;">
|
|
<th style="padding: 10px 12px; font-weight: 600; font-size: 0.8rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em;">
|
|
<a href="{{ sort_base }}sort={{ 'path_desc' if current_sort == 'path' else 'path' }}" style="color: var(--muted); text-decoration: none;">
|
|
Path {{ '▲' if current_sort == 'path' else ('▼' if current_sort == 'path_desc' else '') }}
|
|
</a>
|
|
</th>
|
|
<th style="padding: 10px 12px; font-weight: 600; font-size: 0.8rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em;">
|
|
<a href="{{ sort_base }}sort={{ 'name_desc' if current_sort == 'name' else 'name' }}" style="color: var(--muted); text-decoration: none;">
|
|
Name {{ '▲' if current_sort == 'name' else ('▼' if current_sort == 'name_desc' else '') }}
|
|
</a>
|
|
</th>
|
|
<th style="padding: 10px 12px; font-weight: 600; font-size: 0.8rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em;">Type</th>
|
|
<th style="padding: 10px 12px; font-weight: 600; font-size: 0.8rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em;">Size</th>
|
|
<th style="padding: 10px 12px; font-weight: 600; font-size: 0.8rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em;">
|
|
<a href="{{ sort_base }}sort={{ 'date_asc' if current_sort == 'date' else 'date' }}" style="color: var(--muted); text-decoration: none;">
|
|
Date {{ '▼' if current_sort == 'date' else ('▲' if current_sort == 'date_asc' else '') }}
|
|
</a>
|
|
</th>
|
|
<th style="padding: 10px 12px;"></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for item in items %}
|
|
<tr style="border-bottom: 1px solid var(--border);">
|
|
<td style="padding: 10px 12px; color: var(--muted); font-size: 0.85rem;">{{ item.folder }}</td>
|
|
<td style="padding: 10px 12px;">
|
|
<a href="/files/{{ item.id }}/preview" style="color: var(--accent);">{{ item.original_filename }}</a>
|
|
</td>
|
|
<td style="padding: 10px 12px;">
|
|
{% if item.mime_type %}<span class="row-tag">{{ item.mime_type.split('/')|last }}</span>{% endif %}
|
|
</td>
|
|
<td style="padding: 10px 12px; color: var(--muted); font-size: 0.85rem; white-space: nowrap;">
|
|
{% if item.size_bytes %}{{ "%.1f"|format(item.size_bytes / 1024) }} KB{% endif %}
|
|
</td>
|
|
<td style="padding: 10px 12px; color: var(--muted); font-size: 0.85rem; white-space: nowrap;">
|
|
{{ item.created_at.strftime('%Y-%m-%d') if item.created_at else '' }}
|
|
</td>
|
|
<td style="padding: 10px 12px; text-align: right; white-space: nowrap;">
|
|
<a href="/files/{{ item.id }}/download" class="btn btn-ghost btn-xs">Download</a>
|
|
<form action="/files/{{ item.id }}/delete" method="post" data-confirm="Delete this file?" style="display:inline">
|
|
<button type="submit" class="btn btn-ghost btn-xs" style="color: var(--red)">Del</button>
|
|
</form>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{% if total_pages > 1 %}
|
|
{% set page_base = sort_base ~ 'sort=' ~ current_sort ~ '&' %}
|
|
<div style="display: flex; justify-content: center; align-items: center; gap: 8px; margin-top: 16px;">
|
|
{% if current_page > 1 %}
|
|
<a href="{{ page_base }}page={{ current_page - 1 }}" class="btn btn-ghost btn-xs">Prev</a>
|
|
{% endif %}
|
|
|
|
{% for p in range(1, total_pages + 1) %}
|
|
{% if p == current_page %}
|
|
<span class="btn btn-primary btn-xs" style="pointer-events: none;">{{ p }}</span>
|
|
{% elif p <= 3 or p >= total_pages - 2 or (p >= current_page - 1 and p <= current_page + 1) %}
|
|
<a href="{{ page_base }}page={{ p }}" class="btn btn-ghost btn-xs">{{ p }}</a>
|
|
{% elif p == 4 and current_page > 5 %}
|
|
<span style="color: var(--muted);">...</span>
|
|
{% elif p == total_pages - 3 and current_page < total_pages - 4 %}
|
|
<span style="color: var(--muted);">...</span>
|
|
{% endif %}
|
|
{% endfor %}
|
|
|
|
{% if current_page < total_pages %}
|
|
<a href="{{ page_base }}page={{ current_page + 1 }}" class="btn btn-ghost btn-xs">Next</a>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% else %}
|
|
<div class="empty-state">
|
|
<div class="empty-state-icon">📁</div>
|
|
{% if current_q or current_type or current_tag %}
|
|
<div class="empty-state-text">No files match your filters</div>
|
|
<a href="/files" class="btn btn-secondary">Clear Filters</a>
|
|
{% else %}
|
|
<div class="empty-state-text">No files{{ ' in this folder' if current_folder is not none else ' uploaded yet' }}</div>
|
|
<a href="/files/upload" class="btn btn-primary">Upload First File</a>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
{% endblock %}
|