Add delete, inline preview, and mobile list layout to the file browser
- DELETE /files/{path} recursively removes a file/folder (root itself
can't be deleted, 404 on missing paths); wired to a new "Delete"
action in the web UI with a confirmation prompt.
- Files now open inline (Content-Disposition: inline) so browsers can
play/preview natively-supported types (video, audio, images, PDF)
directly instead of always forcing a download. A separate
"Download" link (?download=1) still forces a save-as.
- Narrow screens get a stacked card list (name, size, modified date,
download/delete actions) instead of a squeezed table, so nothing is
hidden or requires horizontal scrolling; the table layout is kept
unchanged for sm+ screens.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -20,7 +20,7 @@ templ FilesPage(username string, breadcrumbs []Breadcrumb, entries []FileEntry,
|
||||
@Layout("Files", username) {
|
||||
<div class="flex items-center justify-between mb-6 gap-4 flex-wrap">
|
||||
<h1 class="text-2xl font-semibold">Files</h1>
|
||||
<div class="flex gap-2">
|
||||
<div class="flex gap-2 flex-wrap">
|
||||
<button id="new-folder-button" type="button"
|
||||
class="bg-white border border-gray-300 rounded-md px-3 py-1.5 text-sm font-medium text-gray-700 hover:bg-gray-50">
|
||||
New folder
|
||||
@@ -50,49 +50,117 @@ templ FilesPage(username string, breadcrumbs []Breadcrumb, entries []FileEntry,
|
||||
data-upload-url={ "/web/files/" + currentPath }
|
||||
class="bg-white rounded-lg border-2 border-dashed border-gray-200 p-2 transition-colors"
|
||||
>
|
||||
<table class="w-full text-sm table-fixed">
|
||||
<!-- Table layout for wider screens (sm and up). -->
|
||||
<table class="hidden sm:table w-full text-sm table-fixed">
|
||||
<colgroup>
|
||||
<col class="w-auto"/>
|
||||
<col class="w-20"/>
|
||||
<col class="w-36"/>
|
||||
<col class="w-16"/>
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr class="text-left text-gray-400 border-b border-gray-100">
|
||||
<th class="py-2 px-3 font-medium">Name</th>
|
||||
<th class="py-2 px-3 font-medium">Size</th>
|
||||
<th class="py-2 px-3 font-medium">Modified</th>
|
||||
<th class="py-2 px-3 font-medium"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
for _, e := range entries {
|
||||
<tr class="border-b border-gray-50 hover:bg-gray-50">
|
||||
<td class="py-2 px-3 break-all">
|
||||
if e.IsDir {
|
||||
<a href={ templ.URL("/web/files/" + e.RelPath + "/") } class="flex items-center gap-2 text-indigo-600 hover:underline">
|
||||
<span aria-hidden="true">📁</span>{ e.Name }
|
||||
</a>
|
||||
} else {
|
||||
<a href={ templ.URL("/web/files/" + e.RelPath) } download class="flex items-center gap-2 text-gray-700 hover:underline">
|
||||
<span aria-hidden="true">📄</span>{ e.Name }
|
||||
</a>
|
||||
}
|
||||
@fileEntryLink(e)
|
||||
</td>
|
||||
<td class="py-2 px-3 text-gray-500 whitespace-nowrap">{ e.Size }</td>
|
||||
<td class="py-2 px-3 text-gray-500 whitespace-nowrap">{ e.ModTime }</td>
|
||||
<td class="py-2 px-3 text-right whitespace-nowrap">
|
||||
<div class="flex items-center justify-end gap-3">
|
||||
@fileActions(e)
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
if len(entries) == 0 {
|
||||
<tr>
|
||||
<td colspan="3" class="py-6 px-3 text-center text-gray-400">
|
||||
<td colspan="4" class="py-6 px-3 text-center text-gray-400">
|
||||
This folder is empty — drag & drop files or folders here, or use the upload buttons above.
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- Stacked card layout for narrow screens: same information as
|
||||
the table above (name, size, modified, delete), just
|
||||
wrapped onto its own lines instead of squeezed into
|
||||
columns, so nothing needs to be hidden or scrolled to. -->
|
||||
<ul class="sm:hidden divide-y divide-gray-100">
|
||||
for _, e := range entries {
|
||||
<li class="py-2.5 px-1">
|
||||
<div class="break-all">
|
||||
@fileEntryLink(e)
|
||||
</div>
|
||||
<div class="mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-gray-500 pl-6">
|
||||
if e.Size != "" {
|
||||
<span>{ e.Size }</span>
|
||||
}
|
||||
<span>{ e.ModTime }</span>
|
||||
<span class="ml-auto flex items-center gap-3">
|
||||
@fileActions(e)
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
}
|
||||
if len(entries) == 0 {
|
||||
<li class="py-6 px-3 text-center text-gray-400 text-sm">
|
||||
This folder is empty — drag & drop files or folders here, or use the upload buttons above.
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
<p id="upload-status" class="mt-3 text-sm text-gray-500"></p>
|
||||
<script type="module" src="/web/static/files.js"></script>
|
||||
}
|
||||
}
|
||||
|
||||
templ fileEntryLink(e FileEntry) {
|
||||
if e.IsDir {
|
||||
<a href={ templ.URL("/web/files/" + e.RelPath + "/") } class="flex items-center gap-2 text-indigo-600 hover:underline">
|
||||
<span aria-hidden="true">📁</span>{ e.Name }
|
||||
</a>
|
||||
} else {
|
||||
<a href={ templ.URL("/web/files/" + e.RelPath) } target="_blank" rel="noopener" class="flex items-center gap-2 text-gray-700 hover:underline">
|
||||
<span aria-hidden="true">📄</span>{ e.Name }
|
||||
</a>
|
||||
}
|
||||
}
|
||||
|
||||
// fileActions renders the per-entry action buttons: a Download link
|
||||
// (forces a save-as via ?download=1, since the name link above now opens
|
||||
// files inline so the browser can play/preview natively-supported types
|
||||
// like video, audio, images, or PDFs) plus the Delete button. Folders have
|
||||
// no download link since they aren't served as a single file.
|
||||
templ fileActions(e FileEntry) {
|
||||
if !e.IsDir {
|
||||
<a
|
||||
href={ templ.URL("/web/files/" + e.RelPath + "?download=1") }
|
||||
class="text-gray-500 hover:text-indigo-600 hover:underline text-xs font-medium"
|
||||
>
|
||||
Download
|
||||
</a>
|
||||
}
|
||||
@fileDeleteButton(e)
|
||||
}
|
||||
|
||||
templ fileDeleteButton(e FileEntry) {
|
||||
<button
|
||||
type="button"
|
||||
class="delete-entry-button text-red-500 hover:text-red-700 text-xs font-medium"
|
||||
data-path={ e.RelPath }
|
||||
data-name={ e.Name }
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user