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
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.
This commit is contained in:
35
src/main.go
35
src/main.go
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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>
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user