add pages

This commit is contained in:
2025-10-24 08:20:04 +02:00
parent 5a6b558d9c
commit 3645c60214
9 changed files with 943 additions and 0 deletions
+280
View File
@@ -0,0 +1,280 @@
{% extends "base.htm" %}
{% block title %}NanoShare - Files{% endblock %}
{% block meta %}
<meta name="description" content="NanoShare is a lightweight self-hosted file sharing service built on Python Quart.">
<meta name="keywords" content="NanoShare, file sharing, self-hosted, Python Quart, open source">
<meta name="robots" content="index, follow" />
{% endblock %}
{% block head %}
<style>
/* ====== Design Tokens ====== */
:root{
--bg: #0f1221;
--panel: #161a2f;
--panel-2: #1b2140;
--text: #e7eaf6;
--muted: #a7b0d1;
--accent: #7c9eff;
--accent-2: #8ef6ff;
--border: rgba(255,255,255,.08);
--shadow: 0 10px 30px rgba(0,0,0,.25);
--radius: 14px;
}
@media (prefers-color-scheme: light){
:root{
--bg: #f6f7fb;
--panel: #ffffff;
--panel-2: #f3f6ff;
--text: #111321;
--muted: #495273;
--accent: #385cff;
--accent-2: #00bcd4;
--border: rgba(17,19,33,.08);
--shadow: 0 12px 24px rgba(17,19,33,.08);
}
}
/* ====== Base ====== */
*{box-sizing:border-box}
html,body{height:100%}
body{
margin:0;
color:var(--text);
background:
radial-gradient(1200px 600px at 10% -10%, rgba(124,158,255,.18), transparent 60%),
radial-gradient(900px 500px at 110% 10%, rgba(142,246,255,.18), transparent 60%),
var(--bg);
font: 500 system-ui, -apple-system, Segoe UI, Roboto, Inter, "Helvetica Neue", Arial, "Noto Sans", "Apple Color Emoji","Segoe UI Emoji";
}
/* ====== Layout ====== */
.file-list{
padding: clamp(16px, 3vw, 28px);
}
.card{
background: linear-gradient(180deg, var(--panel), var(--panel-2));
border: 1px solid var(--border);
border-radius: var(--radius);
box-shadow: var(--shadow);
backdrop-filter: saturate(120%) blur(6px);
-webkit-backdrop-filter: saturate(120%) blur(6px);
}
/* ====== Header ====== */
.page-title{
margin: 0 0 18px;
font-weight: 800;
letter-spacing: .2px;
font-size: clamp(22px, 2.4vw, 32px);
background: linear-gradient(90deg, var(--accent), var(--accent-2));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.subtle{
color: var(--muted);
font-size: .95rem;
margin: 0 0 22px;
}
/* ====== Table Wrapper ====== */
.table-wrap{
overflow: auto;
border-radius: calc(var(--radius) - 2px);
border: 1px solid var(--border);
}
.table{
width: 100%;
border-collapse: separate;
border-spacing: 0;
background: transparent;
}
.table thead th{
position: sticky;
top: 0;
z-index: 1;
text-align: left;
font-size: .85rem;
font-weight: 700;
letter-spacing: .3px;
padding: 12px 14px;
color: var(--muted);
background: color-mix(in srgb, var(--panel) 86%, black 0%);
border-bottom: 1px solid var(--border);
backdrop-filter: blur(4px);
}
.table tbody td{
padding: 14px;
border-bottom: 1px dashed var(--border);
vertical-align: middle;
white-space: nowrap;
}
.table tbody tr{
transition: background .25s ease, transform .06s ease;
}
.table tbody tr:hover{
background: color-mix(in srgb, var(--panel-2) 88%, transparent);
}
.table .cell--name a{
color: var(--text);
text-decoration: none;
font-weight: 700;
}
.table .cell--name a:hover{
color: var(--accent);
text-decoration: underline;
text-underline-offset: 3px;
}
.table .cell--right{ text-align: right; }
/* Zebra for readability on dense lists */
.table tbody tr:nth-child(2n){
background: color-mix(in srgb, var(--panel) 92%, transparent);
}
.table tbody tr:hover:nth-child(2n){
background: color-mix(in srgb, var(--panel-2) 86%, transparent);
}
/* ====== Badges (e.g., file size) ====== */
.badge{
display:inline-block;
padding: .25rem .5rem;
border-radius: 999px;
font-size: .78rem;
line-height: 1;
background: color-mix(in srgb, var(--accent) 16%, transparent);
color: var(--accent);
border: 1px solid color-mix(in srgb, var(--accent) 32%, var(--border));
}
/* ====== Icon Buttons ====== */
.actions{ display:flex; justify-content:flex-end; gap:8px; }
.icon-btn{
--btn-bg: color-mix(in srgb, var(--panel-2) 70%, transparent);
--btn-br: color-mix(in srgb, var(--accent) 22%, var(--border));
display:inline-flex;
align-items:center;
justify-content:center;
gap:.4ch;
padding: .45rem .6rem;
border-radius: 10px;
border: 1px solid var(--btn-br);
background: var(--btn-bg);
color: var(--text);
font-size: .9rem;
cursor: pointer;
transition: transform .06s ease, background .2s ease, border-color .2s ease, box-shadow .2s ease;
box-shadow: 0 2px 0 rgba(0,0,0,.08);
user-select: none;
}
.icon-btn:hover{
background: color-mix(in srgb, var(--accent) 12%, var(--panel-2));
border-color: color-mix(in srgb, var(--accent) 40%, var(--border));
box-shadow: 0 6px 18px rgba(0,0,0,.18);
}
.icon-btn:active{ transform: translateY(1px) scale(.98); }
.icon-btn[data-variant="ghost"]{
background: transparent;
border-color: var(--border);
}
.icon-btn[data-variant="primary"]{
background: linear-gradient(180deg, color-mix(in srgb, var(--accent) 22%, transparent), transparent);
border-color: color-mix(in srgb, var(--accent) 50%, var(--border));
}
/* ====== Small screens: wrap long text and stack actions ====== */
@media (max-width: 720px){
.table tbody td{ white-space: normal; }
.actions{ flex-wrap: wrap; }
.badge{ margin-top: 4px; }
}
/* ====== Utility ====== */
.sr-only{
position:absolute !important; width:1px; height:1px; padding:0; margin:-1px;
overflow:hidden; clip:rect(0,0,0,0); border:0;
}
</style>
{% endblock %}
{% block content %}
<main class="file-list">
<section class="card" style="padding: clamp(18px, 2.6vw, 28px);">
<h2 class="page-title">Files</h2>
<p class="subtle">Your uploaded files at a glance. Click a filename to open, or use the actions on the right.</p>
<div class="table-wrap" role="region" aria-label="Files table" tabindex="0">
<table class="table">
<thead>
<tr>
<th scope="col">Filename</th>
<th scope="col">Note</th>
<th scope="col">Size</th>
<th scope="col">Uploaded</th>
<th scope="col">Expires</th>
<th scope="col" class="cell--right">Actions</th>
</tr>
</thead>
<tbody>
{% for file in files %}
<tr>
<td class="cell--name">
<a href="{{ url_for('side_main.serve_file', file_id=file.file_id) }}">{{ file.file_name }}</a>
</td>
<td>{{ file.note }}</td>
<td><span class="badge">{{ file.file_size }}</span></td>
<td><time datetime="{{ file.uploaded_at }}">{{ file.uploaded_at }}</time></td>
<td><time datetime="{{ file.expires_at }}">{{ file.expires_at }}</time></td>
<td class="cell--right">
<div class="actions">
<button class="icon-btn" title="Info" data-action="info" aria-label="Info for {{ file.file_name }}"><span class="sr-only">Info</span></button>
<button class="icon-btn" data-variant="primary" title="Edit" data-action="edit" aria-label="Edit {{ file.file_name }}">✏️ <span class="sr-only">Edit</span></button>
<button class="icon-btn" data-variant="ghost" title="Copy link" data-action="copy" aria-label="Copy link to {{ file.file_name }}">📋 <span class="sr-only">Copy</span></button>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</section>
</main>
<script>
// Minimal progressive enhancement for "Copy" action
document.addEventListener('click', (e) => {
const btn = e.target.closest('button[data-action="copy"]');
if(!btn) return;
const row = btn.closest('tr');
const link = row?.querySelector('.cell--name a')?.href;
if(!link) return;
navigator.clipboard.writeText(link).then(() => {
const original = btn.innerHTML;
btn.innerHTML = '✅';
setTimeout(() => btn.innerHTML = original, 1200);
}).catch(() => {
alert('Could not copy link.');
});
});
// Optional: placeholder handlers for info/edit hooks
document.addEventListener('click', (e) => {
const info = e.target.closest('button[data-action="info"]');
const edit = e.target.closest('button[data-action="edit"]');
if(info){
const name = info.closest('tr')?.querySelector('.cell--name a')?.textContent?.trim();
// Hook into your modal/toast here
console.log('Info for:', name);
}
if(edit){
const id = edit.closest('tr')?.querySelector('.cell--name a')?.getAttribute('href');
// Navigate or open editor route
if(id) window.location.href = id + '/edit';
}
});
</script>
{% endblock %}