Add web UI for WebDAV files and CardDAV contacts
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>
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
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>
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user