V0.3.3: Even more optimization
Some checks failed
Build and Push Docker Image to GHCR / build-and-push (push) Has been cancelled

In this realease, I have further optimized Passport. The css that
Passport now uses is entirely handrolled and build via postcss (sadly).
Several bugs have also been fixed in this release, as well as a few
performance improvements relating to the admin UI.
This commit is contained in:
Zoe
2025-10-04 22:02:15 -05:00
parent f6ffc90ec2
commit b52d9541a3
25 changed files with 1764 additions and 640 deletions

View File

@@ -1,208 +1,132 @@
<div id="blur-target"
class="transition-[filter] motion-reduce:transition-none ease-[cubic-bezier(0.45,0,0.55,1)] duration-300">
<header class="flex w-full p-3">
<a href="/"
class="flex items-center flex-row gap-2 text-white border-b hover:border-transparent justify-center">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20"
viewBox="0 0 24 24"><!-- Icon from Tabler Icons by Paweł Kuna - https://github.com/tabler/tabler-icons/blob/master/LICENSE -->
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
<path d="m9 14l-4-4l4-4" />
<path d="M5 10h11a4 4 0 1 1 0 8h-1" />
</g>
</svg>
Return to home
</a>
</header>
<!DOCTYPE html>
<html lang="en">
<section class="flex justify-center w-full">
<div class="w-full sm:w-4/5 p-2.5">
{{#each Categories}}
<div class="flex items-center category-header" id="{{this.ID}}_category">
<div class="category-img" data-img-container>
<img width="32" height="32" draggable="false" alt="{{this.Name}}" src="{{this.Icon}}" />
</div>
<h2 data-placeholder="Enter title...">{{~ this.Name ~}}</h2>
<div class="pl-2" data-edit-actions>
<div class="flex flex-row gap-2" data-primary-actions>
<button aria-label="Edit category" onclick="editCategory(this)" class="action-button">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
viewBox="0 0 24 24"><!-- Icon from Tabler Icons by Paweł Kuna - https://github.com/tabler/tabler-icons/blob/master/LICENSE -->
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2">
<path d="M7 7H6a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2-2v-1" />
<path d="M20.385 6.585a2.1 2.1 0 0 0-2.97-2.97L9 12v3h3zM16 5l3 3" />
</g>
</svg>
</button>
<button aria-label="Delete category" onclick="deleteCategory(this)"
class="text-error action-button">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
viewBox="0 0 24 24"><!-- Icon from Tabler Icons by Paweł Kuna - https://github.com/tabler/tabler-icons/blob/master/LICENSE -->
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2"
d="M4 7h16m-10 4v6m4-6v6M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2l1-12M9 7V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v3" />
</svg>
</button>
</div>
</div>
<head>
<title>Passport</title>
<link rel="favicon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preload" as="font" type="font/woff2" crossorigin="anonymous"
href="/assets/fonts/InstrumentSans-VariableFont_wdth,wght.woff2" />
{{{embedFile "assets/styles/adminUi.css"}}}
</head>
<body>
<div id="blur-target">
<header class="flex w-full p-3">
<a href="/"
class="flex items-center flex-row gap-2 text-white border-b hover:border-transparent justify-center">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20"
viewBox="0 0 24 24"><!-- Icon from Tabler Icons by Paweł Kuna - https://github.com/tabler/tabler-icons/blob/master/LICENSE -->
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2">
<path d="m9 14l-4-4l4-4" />
<path d="M5 10h11a4 4 0 1 1 0 8h-1" />
</g>
</svg>
Return to home
</a>
</header>
{{> 'partials/category-grid' }}
</div>
<input type="file" id="icon-upload" accept="image/*" style="display: none;" />
<div id="modal-container" role="dialog" aria-modal="true" class="modal-bg">
<div class="modal">
{{> 'partials/modals/category-form' }}
{{> 'partials/modals/link-form' }}
{{> 'partials/modals/delete-link' }}
{{> 'partials/modals/delete-category' }}
</div>
</div>
<!-- store a blank link card so that if we add a new link we can clone it to make the editing experience easier -->
<div id="template-link-card" class="hidden">
<div>
<img width="64" height="64" draggable="false" />
</div>
<div>
<h3></h3>
<!-- add 2 to the height to account for the border -->
<p></p>
</div>
</div>
<div id="template-category" class="hidden">
<div class="category-header">
<div>
<img width="32" height="32" draggable="false" />
</div>
<div class="link-grid">
{{#each this.Links}}
<div id="{{this.ID}}_link" class="link-card relative admin">
<div class="relative" data-img-container>
<img width="64" height="64" draggable="false" src="{{this.Icon}}" alt="{{this.Name}}" />
</div>
<div data-text-container>
<h3 class="border border-transparent" data-placeholder="Enter title...">
{{~ this.Name ~}}
</h3>
<!-- add 2 to the height to account for the border -->
<p data-placeholder="Enter description...">
{{~ this.Description ~}}
</p>
</div>
<div class="absolute right-1 top-1" data-edit-actions>
<div class="flex flex-row gap-2" data-primary-actions>
<button aria-label="Edit link" onclick="editLink(this)" class="action-button">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
viewBox="0 0 24 24"><!-- Icon from Tabler Icons by Paweł Kuna - https://github.com/tabler/tabler-icons/blob/master/LICENSE -->
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2">
<path d="M7 7H6a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2-2v-1" />
<path d="M20.385 6.585a2.1 2.1 0 0 0-2.97-2.97L9 12v3h3zM16 5l3 3" />
</g>
</svg>
</button>
<button aria-label="Delete link" onclick="deleteLink(this)"
class="text-error action-button">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
viewBox="0 0 24 24"><!-- Icon from Tabler Icons by Paweł Kuna - https://github.com/tabler/tabler-icons/blob/master/LICENSE -->
<path fill="none" stroke="currentColor" stroke-linecap="round"
stroke-linejoin="round" stroke-width="2"
d="M4 7h16m-10 4v6m4-6v6M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2l1-12M9 7V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v3" />
</svg>
</button>
</div>
</div>
</div>
{{/each}}
<div onclick="openModal('link', {{this.ID}})" class="new-link-card">
<svg class="mr-2" xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 24 24">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" d="M12 5v14m-7-7h14" />
</svg>
<div>
<h3>Add a link</h3>
</div>
</div>
</div>
{{/each}}
<div class="flex items-center" id="add-category-button">
<svg class="mr-2" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24">
<h2></h2>
</div>
<div class="link-grid">
<div class="new-link-card link-card admin">
<svg class="mr-2" xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 24 24">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" d="M12 5v14m-7-7h14" />
</svg>
<h2 onclick="openModal('category')" class="text-subtle underline decoration-dashed cursor-pointer">
Add a new category
</h2>
</div>
</div>
</section>
</div>
<input type="file" id="icon-upload" accept="image/*" style="display: none;" />
<div id="modal-container" role="dialog" aria-modal="true"
class="modal-bg fixed top-0 left-0 bottom-0 right-0 bg-black/45 justify-center items-center hidden">
<div class="bg-overlay rounded-xl overflow-hidden w-full p-4 modal max-w-sm">
{{> 'partials/modals/category-form' }}
{{> 'partials/modals/link-form' }}
{{> 'partials/modals/delete-link' }}
{{> 'partials/modals/delete-category' }}
</div>
</div>
<!-- store a blank link card so that if we add a new link we can clone it to make the editing experience easier -->
<div id="template-link-card" class="hidden">
<div class="relative" data-img-container>
<img width="64" height="64" draggable="false" />
</div>
<div class="flex-grow flex flex-col gap-y-px overflow-hidden" data-text-container>
<h3 class="border border-transparent"></h3>
<!-- add 2 to the height to account for the border -->
<p class="min-h-[22px] border border-transparent"></p>
</div>
</div>
<div id="template-category" class="hidden">
<div class="flex items-center category-header">
<div class="category-img" data-img-container>
<img width="32" height="32" draggable="false" />
</div>
<h2></h2>
</div>
<div class="link-grid">
<div class="new-link-card">
<svg class="mr-2" xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 24 24">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 5v14m-7-7h14" />
</svg>
<div>
<h3>Add a link</h3>
<div>
<h3>Add a link</h3>
</div>
</div>
</div>
</div>
</div>
<div id="template-edit-actions" class="hidden" data-edit-actions>
<div class="flex flex-row gap-2" data-primary-actions>
<button class="action-button">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
viewBox="0 0 24 24"><!-- Icon from Tabler Icons by Paweł Kuna - https://github.com/tabler/tabler-icons/blob/master/LICENSE -->
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
<path d="M7 7H6a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2-2v-1" />
<path d="M20.385 6.585a2.1 2.1 0 0 0-2.97-2.97L9 12v3h3zM16 5l3 3" />
</g>
</svg>
</button>
<button class="text-error action-button">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
viewBox="0 0 24 24"><!-- Icon from Tabler Icons by Paweł Kuna - https://github.com/tabler/tabler-icons/blob/master/LICENSE -->
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M4 7h16m-10 4v6m4-6v6M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2l1-12M9 7V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v3" />
</svg>
</button>
<div id="template-edit-actions" class="hidden">
<div class="flex flex-row gap-2">
<button class="action-button">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
viewBox="0 0 24 24"><!-- Icon from Tabler Icons by Paweł Kuna - https://github.com/tabler/tabler-icons/blob/master/LICENSE -->
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2">
<path d="M7 7H6a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2-2v-1" />
<path d="M20.385 6.585a2.1 2.1 0 0 0-2.97-2.97L9 12v3h3zM16 5l3 3" />
</g>
</svg>
</button>
<button class="text-error action-button">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
viewBox="0 0 24 24"><!-- Icon from Tabler Icons by Paweł Kuna - https://github.com/tabler/tabler-icons/blob/master/LICENSE -->
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2"
d="M4 7h16m-10 4v6m4-6v6M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2l1-12M9 7V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v3" />
</svg>
</button>
</div>
</div>
</div>
<div id="teleport-storage" class="absolute -top-full -left-full hidden">
<!-- These are the elements that appear when the user enters edit mode, they allow for the cancelation/confirmation of the edit -->
<div class="flex flex-row gap-2" data-confirm-actions id="confirm-actions">
<button class="action-button text-success" onclick="confirmEdit()">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
<div id="teleport-storage" class="absolute -top-full -left-full hidden">
<!-- These are the elements that appear when the user enters edit mode, they allow for the cancelation/confirmation of the edit -->
<div class="action-container" id="confirm-actions">
<button class="action-button text-success" onclick="confirmEdit()">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
viewBox="0 0 24 24"><!-- Icon from Tabler Icons by Paweł Kuna - https://github.com/tabler/tabler-icons/blob/master/LICENSE -->
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" d="m5 12l5 5L20 7" />
</svg>
</button>
<button class="action-button text-error" onclick="cancelEdit()">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
viewBox="0 0 24 24"><!-- Icon from Tabler Icons by Paweł Kuna - https://github.com/tabler/tabler-icons/blob/master/LICENSE -->
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" d="M3 12a9 9 0 1 0 18 0a9 9 0 1 0-18 0m15.364-6.364L5.636 18.364" />
</svg>
</button>
</div>
<!-- This is the element that appears on top of the icon when the user is editing it that allows for changing the icon -->
<button id="select-icon-button" onclick="selectIcon()" class="select-icon-button" draggable="false">
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28"
viewBox="0 0 24 24"><!-- Icon from Tabler Icons by Paweł Kuna - https://github.com/tabler/tabler-icons/blob/master/LICENSE -->
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="m5 12l5 5L20 7" />
</svg>
</button>
<button class="action-button text-error" onclick="cancelEdit()">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
viewBox="0 0 24 24"><!-- Icon from Tabler Icons by Paweł Kuna - https://github.com/tabler/tabler-icons/blob/master/LICENSE -->
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M3 12a9 9 0 1 0 18 0a9 9 0 1 0-18 0m15.364-6.364L5.636 18.364" />
d="M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2M7 9l5-5l5 5m-5-5v12" />
</svg>
</button>
</div>
<!-- This is the element that appears on top of the icon when the user is editing it that allows for changing the icon -->
<button id="select-icon-button" onclick="selectIcon()"
class="flex absolute inset-0 bg-highlight/80 rounded-md text-base items-center justify-center"
draggable="false">
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28"
viewBox="0 0 24 24"><!-- Icon from Tabler Icons by Paweł Kuna - https://github.com/tabler/tabler-icons/blob/master/LICENSE -->
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2M7 9l5-5l5 5m-5-5v12" />
</svg>
</button>
</div>
{{{embedFile "scripts/admin.js"}}}
</body>
{{{embedFile "scripts/admin.js"}}}
{{{devContent}}}
</html>

View File

@@ -1,20 +1,31 @@
<main class="flex justify-center items-center h-screen relative bg-base">
<div class="flex bg-surface rounded-xl overflow-hidden">
<img src="/assets/leaves.webp" class="h-96 w-64 object-cover" />
<div class="flex flex-col p-4 text-center">
<h2 class="text-2xl">
<!DOCTYPE html>
<html lang="en">
<head>
<title>Passport</title>
<link rel="favicon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preload" as="font" type="font/woff2" crossorigin="anonymous"
href="/assets/fonts/InstrumentSans-VariableFont_wdth,wght.woff2" />
{{{embedFile "assets/styles/login.css"}}}
</head>
<body>
<main class="login-container">
<img src="/assets/leaves.webp" />
<div>
<h2>
Login
</h2>
<form action="/admin/login" method="post" class="flex flex-col gap-y-3 my-2">
<form action="/admin/login" method="post" class="login-form">
<input type="text" name="username" placeholder="Username" />
<input type="password" name="password" placeholder="Password" />
<button class="px-4 py-2 rounded-md w-full bg-accent text-white border-0" type="submit">Login</button>
</form>
<span id="message"></span>
</div>
</div>
</main>
</main>
</body>
<script>
let message = document.getElementById("message");
let form = document.querySelector("form");
@@ -42,4 +53,7 @@
message.innerText = (await res.json()).message;
});
</script>
</script>
{{{devContent}}}
</html>