Files
simple-nanoshare/templates/side/views/webpage/files_list.htm
T

286 lines
9.1 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% 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 }}" class="local-time"></time></td>
<td><time datetime="{{ file.expires_at }}" class="local-time"></time></td>
<td class="cell--right">
<div class="actions">
<button class="icon-btn" title="Info">
<a href="{{ url_for('side_main.file_info', file_id=file.file_id) }}"><span class="sr-only">Info</span></a>
</button>
<button class="icon-btn" title="Edit">
<a href="{{ url_for('side_main.file_edit', file_id=file.file_id) }}">✏️ <span class="sr-only">Edit</span></a>
</button>
<button class="icon-btn" title="Copy link" data-action="copy">📋 <span class="sr-only">Copy</span></button>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</section>
</main>
<script>
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.');
});
});
document.querySelectorAll("time.local-time").forEach(timeEl => {
const datetime = timeEl.getAttribute("datetime");
if (!datetime) return;
const date = new Date(datetime);
timeEl.title = date.toISOString();
timeEl.textContent = date.toLocaleString(undefined, {
year: "numeric",
month: "short",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
second: undefined,
hour12: false,
});
});
</script>
{% endblock %}