1 Commits

Author SHA1 Message Date
Zoe
01a147d2d3 v0.3.1: More admin UI improvements and bug fixes
All checks were successful
Build and Push Docker Image to GHCR / build-and-push (push) Successful in 29s
This commit further refines the admin UI, and introduces a very SPA-like
creating process for links and categories. In-place editing has also
been improved, the styling is more correct and better formatted, as well
as having some cleaner code.

This PR also fixes a few bugs:
- Image uploads not being URL encoded, so special characters would break
  images
- If an image has exif, but no orientation tag, the image would be
  wrongfully rejected
- In-place editing forms were not correctly sized, and title inputs
  would not break with line breaks in the titles

This PR also greatly improves performance on the admin UI.
2025-09-30 19:45:58 -05:00
5 changed files with 573 additions and 315 deletions

View File

@@ -17,6 +17,7 @@ import (
"log/slog" "log/slog"
"mime/multipart" "mime/multipart"
"net/http" "net/http"
"net/url"
"os" "os"
"os/signal" "os/signal"
"path/filepath" "path/filepath"
@@ -312,25 +313,23 @@ func UploadFile(file *multipart.FileHeader, fileName, contentType string, c fibe
// if there *is* exif, parse it // if there *is* exif, parse it
if err == nil { if err == nil {
tag, err := x.Get(exif.Orientation) tag, err := x.Get(exif.Orientation)
if err != nil { if err == nil {
return "", fmt.Errorf("failed to get orientation: %v", err) if tag.Count == 1 && tag.Format() == tiff.IntVal {
} orientation, err := tag.Int(0)
if err != nil {
return "", fmt.Errorf("failed to get orientation: %v", err)
}
if tag.Count == 1 && tag.Format() == tiff.IntVal { slog.Debug("Orientation tag found", "orientation", orientation)
orientation, err := tag.Int(0)
if err != nil {
return "", fmt.Errorf("failed to get orientation: %v", err)
}
slog.Debug("Orientation tag found", "orientation", orientation) switch orientation {
case 3:
switch orientation { img = imaging.Rotate180(img)
case 3: case 6:
img = imaging.Rotate180(img) img = imaging.Rotate270(img)
case 6: case 8:
img = imaging.Rotate270(img) img = imaging.Rotate90(img)
case 8: }
img = imaging.Rotate90(img)
} }
} }
} }
@@ -381,7 +380,7 @@ func UploadFile(file *multipart.FileHeader, fileName, contentType string, c fibe
} }
} }
iconPath = "/uploads/" + fileName iconPath = "/uploads/" + url.PathEscape(fileName)
return iconPath, nil return iconPath, nil
} }

View File

@@ -84,6 +84,7 @@ input:not(.search) {
transition-duration: 150ms; transition-duration: 150ms;
transition-timing-function: cubic-bezier(0.45, 0, 0.55, 1); transition-timing-function: cubic-bezier(0.45, 0, 0.55, 1);
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
contain: layout style paint;
&:not(.admin) { &:not(.admin) {
&:hover { &:hover {
@@ -113,12 +114,12 @@ input:not(.search) {
} }
/* Div that holds the image */ /* Div that holds the image */
.link-card div:has(img):first-child { .link-card div[data-img-container] {
flex-shrink: 0; flex-shrink: 0;
margin-right: 0.5rem; margin-right: 0.5rem;
} }
.link-card div:first-child img { .link-card div[data-img-container] img {
user-select: none; user-select: none;
border-radius: 0.375rem; border-radius: 0.375rem;
aspect-ratio: 1/1; aspect-ratio: 1/1;
@@ -126,10 +127,33 @@ input:not(.search) {
} }
/* Div that holds the text */ /* Div that holds the text */
.link-card div:nth-child(2) { .link-card div[data-text-container] {
word-break: break-all; word-break: break-all;
} }
.link-card div:nth-child(2) p { .link-card div[data-text-container] p {
color: var(--color-subtle); color: var(--color-subtle);
}
.categoy-header {
display: flex;
align-items: center;
}
.category-header div[data-img-container] {
@apply shrink-0 relative mr-2 h-full flex items-center justify-center size-8;
}
.categoy-header div[data-img-container] img {
user-select: none;
object-fit: cover;
aspect-ratio: 1/1;
}
.category-header h2 {
text-transform: capitalize;
word-break: break-all;
border-width: 1px;
border-color: #0000;
} }

File diff suppressed because it is too large Load Diff

View File

@@ -76,17 +76,17 @@
<div class="w-full sm:w-4/5 p-2.5"> <div class="w-full sm:w-4/5 p-2.5">
{{#each Categories}} {{#each Categories}}
<div class="flex items-center mt-2 first:mt-0"> <div class="flex items-center mt-2 first:mt-0">
<img class="object-contain mr-2 select-none" width="32" height="32" draggable="false" alt="{{this.Name}}" <img class="object-contain mr-2 select-none size-8" width="32" height="32" draggable="false"
src="{{this.Icon}}" /> alt="{{this.Name}}" src="{{this.Icon}}" />
<h2 class="capitalize break-all">{{this.Name}}</h2> <h2 class="capitalize break-all">{{this.Name}}</h2>
</div> </div>
<div class="p-2.5 grid grid-cols-[repeat(auto-fill,_minmax(min(330px,_100%),_1fr))] gap-2"> <div class="p-2.5 grid grid-cols-[repeat(auto-fill,_minmax(min(330px,_100%),_1fr))] gap-2">
{{#each this.Links}} <a href="{{this.URL}}" class="link-card" draggable="false" target="_blank" {{#each this.Links}} <a href="{{this.URL}}" class="link-card" draggable="false" target="_blank"
rel="noopener noreferrer"> rel="noopener noreferrer">
<div> <div data-img-container>
<img width="64" height="64" draggable="false" src="{{this.Icon}}" alt="{{this.Name}}" /> <img width="64" height="64" draggable="false" src="{{this.Icon}}" alt="{{this.Name}}" />
</div> </div>
<div> <div data-text-container>
<h3>{{this.Name}}</h3> <h3>{{this.Name}}</h3>
<p class="min-h-5">{{this.Description}}</p> <p class="min-h-5">{{this.Description}}</p>
</div> </div>

View File

@@ -1,6 +1,6 @@
{ {
"name": "passport", "name": "passport",
"version": "0.3.0", "version": "0.3.1",
"description": "Passport is a simple, lightweight, and fast dashboard/new tab page for your browser.", "description": "Passport is a simple, lightweight, and fast dashboard/new tab page for your browser.",
"author": "juls0730", "author": "juls0730",
"license": "BSL-1.0", "license": "BSL-1.0",