Files

211 lines
7.6 KiB
HTML

{% extends "base.htm" %}
{% block title %}NanoShare - Edit file{% endblock %}
{% block meta %}
<meta name="description" content="NanoShare file editing page.">
<meta name="robots" content="noindex, nofollow" />
{% endblock %}
{% block head %}
<style>
.file-edit-page { margin: clamp(16px, 3vw, 32px) auto; }
.info-line { margin-top: 12px; color: var(--muted); font-size: .95rem; }
.save-row { margin-top: 18px; display: flex; justify-content: space-evenly; align-items: center; gap: 10px; flex-wrap: wrap; }
.status-row { margin-top: 8px; text-align: center; }
.status { color: var(--muted); font-size: .92rem; }
.status.ok { color: #8ee28e; }
.status.err { color: #f08f8f; }
.btn-danger { border-color: #8f3b3b; color: #ffd4d4; }
.btn-danger:hover { background: #4d1f1f; }
.meta-table-wrap { margin-top: 10px; border: 1px solid var(--border); border-radius: 12px; overflow: hidden; }
.meta-table { width: 100%; border-collapse: collapse; }
.meta-table th, .meta-table td { padding: 10px 12px; border-bottom: 1px solid var(--border); text-align: left; vertical-align: top; }
.meta-table tr:last-child th, .meta-table tr:last-child td { border-bottom: 0; }
.meta-table th { width: 160px; color: var(--muted); font-weight: 700; }
.danger-zone { margin-top: 20px; border: 1px solid #6a2c2c; border-radius: 12px; padding: 12px; background: rgba(120, 32, 32, 0.14); }
.danger-zone h2 { margin: 0 0 6px; font-size: 1rem; }
.danger-zone p { margin: 0 0 10px; color: var(--muted); font-size: .92rem; }
</style>
{% endblock %}
{% block content %}
<main class="file-edit-page">
<section class="card" aria-labelledby="edit-title">
<h1 id="edit-title" class="page-title">Edit file</h1>
<p class="subtle">Update filename, note and expiration date.</p>
<div class="meta-table-wrap" role="region" aria-label="File metadata">
<table class="meta-table">
<tbody>
<tr>
<th scope="row">URL</th>
<td><a href="{{ share_url }}">{{ share_url }}</a></td>
</tr>
<tr>
<th scope="row">File ID</th>
<td><code>{{ file_id }}</code></td>
</tr>
<tr>
<th scope="row">Uploaded at</th>
<td><time class="local-time" data-ts="{{ file.uploaded_at }}"></time></td>
</tr>
<tr>
<th scope="row">File size</th>
<td><span class="badge">{{ file.file_size }}</span></td>
</tr>
</tbody>
</table>
</div>
<div class="field">
<label for="fileName" class="label">Filename</label>
<input id="fileName" class="input" type="text" maxlength="255" value="{{ file.file_name }}" />
</div>
<div class="field">
<label for="note" class="label">Note</label>
<input id="note" class="input" type="text" maxlength="500" value="{{ file.note or '' }}" />
</div>
<div class="row">
<div class="field">
<label for="expiresMode" class="label">Expiration</label>
<select id="expiresMode" class="select">
<option value="1h">1 hour</option>
<option value="24h">24 hours</option>
<option value="7d">7 days</option>
<option value="30d">30 days</option>
<option value="never">Never</option>
<option value="custom">Custom date</option>
</select>
</div>
<div class="field" id="customWrap" style="display:none;">
<label for="customExpire" class="label">Custom expiration</label>
<input type="datetime-local" id="customExpire" class="datetime" />
</div>
</div>
<div class="save-row">
<button id="saveBtn" class="btn" type="button">Save changes</button>
<a class="btn btn-ghost" href="{{ url_for('side_main.file_info', file_id=file_id) }}">View info</a>
<a class="btn btn-ghost" href="{{ url_for('side_main.files_list') }}">Back to files</a>
</div>
<div class="status-row"><span id="status" class="status"></span></div>
<section class="danger-zone" aria-labelledby="danger-title">
<h2 id="danger-title">Danger zone</h2>
<p>Deleting a file is permanent and cannot be undone.</p>
<button id="deleteBtn" class="btn btn-ghost btn-danger" type="button">Delete file</button>
</section>
</section>
</main>
<script>
const fileId = '{{ file_id }}';
const initialExpires = '{{ file.expires_at }}';
const expiresMode = document.getElementById('expiresMode');
const customWrap = document.getElementById('customWrap');
const customExpire = document.getElementById('customExpire');
const statusEl = document.getElementById('status');
function formatDate(value) {
if (!value) return 'Never';
const date = new Date(Number.parseInt(String(value), 10));
if (Number.isNaN(date.getTime())) return 'Invalid date';
return date.toLocaleString(undefined, {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
hour12: false,
});
}
function toLocalInputValue(timestampMs) {
const d = new Date(Number.parseInt(String(timestampMs), 10));
if (Number.isNaN(d.getTime())) return '';
const local = new Date(d.getTime() - d.getTimezoneOffset() * 60000);
return local.toISOString().slice(0, 16);
}
function setStatus(msg, type) {
statusEl.textContent = msg;
statusEl.className = 'status';
if (type) statusEl.classList.add(type);
}
function syncExpireUI() {
customWrap.style.display = expiresMode.value === 'custom' ? 'block' : 'none';
}
if (initialExpires) {
expiresMode.value = 'custom';
customExpire.value = toLocalInputValue(initialExpires);
} else {
expiresMode.value = 'never';
}
syncExpireUI();
expiresMode.addEventListener('change', syncExpireUI);
document.querySelectorAll('time.local-time').forEach((el) => {
el.textContent = formatDate(el.dataset.ts || '');
});
document.getElementById('saveBtn').addEventListener('click', async () => {
const fileName = document.getElementById('fileName').value.trim();
const note = document.getElementById('note').value.trim();
if (!fileName) {
setStatus('Filename is required.', 'err');
return;
}
let expires = expiresMode.value;
if (expiresMode.value === 'custom') {
if (!customExpire.value) {
setStatus('Pick a custom date.', 'err');
return;
}
expires = new Date(customExpire.value).toISOString();
}
setStatus('Saving...');
try {
const response = await fetch(`/api/file/${encodeURIComponent(fileId)}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ file_name: fileName, note, expires }),
});
const data = await response.json();
if (!response.ok || !data.ok) {
throw new Error(data.error || 'Could not update file');
}
setStatus('Saved.', 'ok');
} catch (error) {
setStatus(error.message || 'Could not update file.', 'err');
}
});
document.getElementById('deleteBtn').addEventListener('click', async () => {
const ok = window.confirm('Delete this file? This cannot be undone.');
if (!ok) return;
setStatus('Deleting...');
try {
const response = await fetch(`/api/file/${encodeURIComponent(fileId)}`, {
method: 'DELETE',
});
const data = await response.json();
if (!response.ok || !data.ok) {
throw new Error(data.error || 'Could not delete file');
}
window.location.href = '{{ url_for("side_main.files_list") }}';
} catch (error) {
setStatus(error.message || 'Could not delete file.', 'err');
}
});
</script>
{% endblock %}