Files browser (web/files): - Alphabetical listing grouped folders-then-files - Upload via button or drag-and-drop, including whole folders - Folder creation - Fixed layout for long filenames without spaces (table-fixed + break-all) Contacts (web/contacts): - Full CRUD for CardDAV contacts (create/edit/delete) - VCF import (multi-card files) and export (single/all) - Photo upload with preview, birthday field - TYPE labels (private/business) for phone, email, address - Repeatable multi-input rows for phones/emails/addresses instead of textareas - Sanitizes a known malformed TYPE parameter pattern from some vCard exporters (e.g. Nextcloud Contacts) that otherwise caused phone numbers to be silently dropped on import Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
99 lines
3.6 KiB
Templ
99 lines
3.6 KiB
Templ
package templates
|
|
|
|
// Breadcrumb is one segment of the current directory path shown above the
|
|
// file listing, e.g. "docs" linking to "/web/files/docs/".
|
|
type Breadcrumb struct {
|
|
Name string
|
|
Path string // relative path (no leading slash) used to build the link
|
|
}
|
|
|
|
// FileEntry is a single row (directory or file) in the file browser.
|
|
type FileEntry struct {
|
|
Name string
|
|
IsDir bool
|
|
Size string // human-readable, empty for directories
|
|
ModTime string
|
|
RelPath string // relative path (no leading slash) used to build the link
|
|
}
|
|
|
|
templ FilesPage(username string, breadcrumbs []Breadcrumb, entries []FileEntry, currentPath string) {
|
|
@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">
|
|
<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
|
|
</button>
|
|
<label class="cursor-pointer bg-indigo-600 text-white rounded-md px-3 py-1.5 text-sm font-medium hover:bg-indigo-700">
|
|
Upload files
|
|
<input id="upload-files-input" type="file" multiple class="hidden"/>
|
|
</label>
|
|
<label class="cursor-pointer bg-indigo-600 text-white rounded-md px-3 py-1.5 text-sm font-medium hover:bg-indigo-700">
|
|
Upload folder
|
|
<input id="upload-folder-input" type="file" webkitdirectory multiple class="hidden"/>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<nav class="text-sm text-gray-500 mb-4 flex flex-wrap items-center gap-1">
|
|
<a href="/web/files/" class="hover:underline text-indigo-600">home</a>
|
|
for _, bc := range breadcrumbs {
|
|
<span>/</span>
|
|
<a href={ templ.URL("/web/files/" + bc.Path + "/") } class="hover:underline text-indigo-600">{ bc.Name }</a>
|
|
}
|
|
</nav>
|
|
|
|
<div
|
|
id="file-drop-zone"
|
|
data-current-path={ currentPath }
|
|
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">
|
|
<colgroup>
|
|
<col class="w-auto"/>
|
|
<col class="w-20"/>
|
|
<col class="w-36"/>
|
|
</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>
|
|
</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>
|
|
}
|
|
</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>
|
|
</tr>
|
|
}
|
|
if len(entries) == 0 {
|
|
<tr>
|
|
<td colspan="3" 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>
|
|
</div>
|
|
<p id="upload-status" class="mt-3 text-sm text-gray-500"></p>
|
|
<script type="module" src="/web/static/files.js"></script>
|
|
}
|
|
}
|
|
|