feat: ui theming & improved clickOutside handler (for modals)
This commit is contained in:
parent
a950da9412
commit
b3b897be85
@ -13,7 +13,7 @@
|
|||||||
:root {
|
:root {
|
||||||
--light-background: #f5f5f5;
|
--light-background: #f5f5f5;
|
||||||
--light-foreground: #e0e0e0;
|
--light-foreground: #e0e0e0;
|
||||||
--light-accent: #303052;
|
--light-accent: #2e2e88;
|
||||||
--light-text: rgb(34, 40, 49);
|
--light-text: rgb(34, 40, 49);
|
||||||
--light-highlight-text: #c28e4a;
|
--light-highlight-text: #c28e4a;
|
||||||
--light-error-text: #4b0000;
|
--light-error-text: #4b0000;
|
||||||
@ -910,6 +910,14 @@
|
|||||||
@apply fixed inset-0 z-40 flex items-center justify-center backdrop-blur-xs;
|
@apply fixed inset-0 z-40 flex items-center justify-center backdrop-blur-xs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-exit-button {
|
||||||
|
@apply flex h-8 w-8 items-center justify-center rounded-lg border border-[var(--light-accent)] bg-transparent p-0 text-[var(--light-text)] hover:bg-[var(--light-foreground)]/80;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .modal-exit-button {
|
||||||
|
@apply hover:bg-[var(--dark-foreground)]/80;
|
||||||
|
}
|
||||||
|
|
||||||
.modal-content {
|
.modal-content {
|
||||||
@apply mx-4 max-h-[90vh] w-full max-w-[90vw] overflow-y-auto rounded-lg border-2 border-[var(--light-accent)]/10 bg-[var(--light-background)] shadow-lg;
|
@apply mx-4 max-h-[90vh] w-full max-w-[90vw] overflow-y-auto rounded-lg border-2 border-[var(--light-accent)]/10 bg-[var(--light-background)] shadow-lg;
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import type { User } from "$lib/logic/model"
|
import type { User } from "$lib/logic/model"
|
||||||
import Delete from "$lib/icons/sidebar/Delete.svelte"
|
import Delete from "$lib/icons/sidebar/Delete.svelte"
|
||||||
import { formatDateShort } from "$lib/util/contentVisual"
|
import { clickOutside, formatDateShort } from "$lib/util/contentVisual"
|
||||||
|
|
||||||
// props
|
// props
|
||||||
export let onClose: () => void
|
export let onClose: () => void
|
||||||
@ -46,14 +46,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleClickOutside = (event: MouseEvent) => {
|
|
||||||
// close the modal if user clicks outside of it
|
|
||||||
const target = event.target as HTMLElement
|
|
||||||
if (target.classList.contains("modal-backdrop")) {
|
|
||||||
onClose()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleModalContentKeydown = (_event: KeyboardEvent) => {
|
const handleModalContentKeydown = (_event: KeyboardEvent) => {
|
||||||
// accessibility compliance handler, actual handling is done by the global keydown handler
|
// accessibility compliance handler, actual handling is done by the global keydown handler
|
||||||
}
|
}
|
||||||
@ -71,71 +63,72 @@
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
class="modal-backdrop"
|
class="modal-backdrop"
|
||||||
on:click={handleClickOutside}
|
|
||||||
on:keydown={handleKeydown}
|
on:keydown={handleKeydown}
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
||||||
<div
|
<div use:clickOutside={() => onClose()}>
|
||||||
bind:this={modalContent}
|
<div
|
||||||
class="modal-content max-w-xl"
|
bind:this={modalContent}
|
||||||
on:click|stopPropagation
|
class="modal-content max-w-xl"
|
||||||
on:keydown={handleModalContentKeydown}
|
on:click|stopPropagation
|
||||||
role="document"
|
on:keydown={handleModalContentKeydown}
|
||||||
tabindex="-1"
|
role="document"
|
||||||
>
|
tabindex="-1"
|
||||||
<!-- Header (title + close button) -->
|
>
|
||||||
<div class="modal-section flex items-center justify-between" role="heading" aria-level="1">
|
<!-- Header (title + close button) -->
|
||||||
<h2 class="text-xl font-bold">Administration</h2>
|
<div class="modal-section flex items-center justify-between" role="heading" aria-level="1">
|
||||||
<button on:click={onClose} class="sidebar-button" aria-label="Close settings">
|
<h2 class="text-xl font-bold">Administration</h2>
|
||||||
<Close />
|
<button on:click={onClose} class="modal-exit-button" aria-label="Close settings">
|
||||||
</button>
|
<Close />
|
||||||
</div>
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Account information -->
|
<!-- Account information -->
|
||||||
<div class="modal-section border-b-0">
|
<div class="modal-section border-b-0">
|
||||||
<h3 class="modal-section-title">Active users</h3>
|
<h3 class="modal-section-title">Active users</h3>
|
||||||
<div class="flex flex-col justify-center">
|
<div class="flex flex-col justify-center">
|
||||||
{#if users !== null && users.length > 0}
|
{#if users !== null && users.length > 0}
|
||||||
<ul class="flex flex-col items-center space-y-1" role="listbox">
|
<ul class="flex flex-col items-center space-y-1" role="listbox">
|
||||||
{#each users as user}
|
{#each users as user}
|
||||||
<li class="modal-list-item">
|
<li class="modal-list-item">
|
||||||
<div class="flex w-full items-center justify-between">
|
<div class="flex w-full items-center justify-between">
|
||||||
<h3 class="modal-list-item-title">{user.username}</h3>
|
<h3 class="modal-list-item-title">{user.username}</h3>
|
||||||
|
|
||||||
<!-- Delete button -->
|
<!-- Delete button -->
|
||||||
{#if $currentUser !== null && user.id === $currentUser.id}
|
{#if $currentUser !== null && user.id === $currentUser.id}
|
||||||
<button
|
<button
|
||||||
disabled
|
disabled
|
||||||
class="sidebar-button sidebar-list-item-delete-button"
|
class="sidebar-button sidebar-list-item-delete-button"
|
||||||
aria-label="Delete note"
|
aria-label="Delete note"
|
||||||
>
|
>
|
||||||
<Delete classString="h-3 w-3" />
|
<Delete classString="h-3 w-3" />
|
||||||
</button>
|
</button>
|
||||||
{:else}
|
{:else}
|
||||||
<button
|
<button
|
||||||
on:click={(e) => handleDeleteUser(e, user.id, user.username)}
|
on:click={(e) => handleDeleteUser(e, user.id, user.username)}
|
||||||
class="sidebar-button sidebar-list-item-delete-button"
|
class="sidebar-button sidebar-list-item-delete-button"
|
||||||
aria-label="Delete note"
|
aria-label="Delete note"
|
||||||
>
|
>
|
||||||
<Delete classString="h-3 w-3" />
|
<Delete classString="h-3 w-3" />
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="modal-list-item-metadata">{user.id}</p>
|
<p class="modal-list-item-metadata">{user.id}</p>
|
||||||
|
|
||||||
<p class="modal-list-item-metadata">
|
<p class="modal-list-item-metadata">
|
||||||
Created: {formatDateShort(user.createdAt)}, Updated: {formatDateShort(
|
Created: {formatDateShort(user.createdAt)}, Updated: {formatDateShort(
|
||||||
user.updatedAt
|
user.updatedAt
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
{/if}
|
{/if}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
import Close from "$lib/icons/Close.svelte"
|
import Close from "$lib/icons/Close.svelte"
|
||||||
import { isPasswordValid } from "$lib/util/authValidation"
|
import { isPasswordValid } from "$lib/util/authValidation"
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import { formatDateLong } from "$lib/util/contentVisual"
|
import { clickOutside, formatDateLong } from "$lib/util/contentVisual"
|
||||||
|
|
||||||
// props
|
// props
|
||||||
export let onClose: () => void
|
export let onClose: () => void
|
||||||
@ -103,14 +103,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleClickOutside = (event: MouseEvent) => {
|
|
||||||
// close the modal if user clicks outside of it
|
|
||||||
const target = event.target as HTMLElement
|
|
||||||
if (target.classList.contains("modal-backdrop")) {
|
|
||||||
onClose()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleModalContentKeydown = (_event: KeyboardEvent) => {
|
const handleModalContentKeydown = (_event: KeyboardEvent) => {
|
||||||
// accessibility compliance handler, actual handling is done by the global keydown handler
|
// accessibility compliance handler, actual handling is done by the global keydown handler
|
||||||
}
|
}
|
||||||
@ -126,161 +118,162 @@
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
class="modal-backdrop"
|
class="modal-backdrop"
|
||||||
on:click={handleClickOutside}
|
|
||||||
on:keydown={handleKeydown}
|
on:keydown={handleKeydown}
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
||||||
<div
|
<div use:clickOutside={() => onClose()}>
|
||||||
bind:this={modalContent}
|
<div
|
||||||
class="modal-content max-w-xl"
|
bind:this={modalContent}
|
||||||
on:click|stopPropagation
|
class="modal-content max-w-xl"
|
||||||
on:keydown={handleModalContentKeydown}
|
on:click|stopPropagation
|
||||||
role="document"
|
on:keydown={handleModalContentKeydown}
|
||||||
tabindex="-1"
|
role="document"
|
||||||
>
|
tabindex="-1"
|
||||||
<!-- Header (title + close button) -->
|
>
|
||||||
<div class="modal-section flex items-center justify-between" role="heading" aria-level="1">
|
<!-- Header (title + close button) -->
|
||||||
<h2 class="text-xl font-bold">Settings</h2>
|
<div class="modal-section flex items-center justify-between" role="heading" aria-level="1">
|
||||||
<button on:click={onClose} class="sidebar-button" aria-label="Close settings">
|
<h2 class="text-xl font-bold">Settings</h2>
|
||||||
<Close />
|
<button on:click={onClose} class="modal-exit-button" aria-label="Close settings">
|
||||||
</button>
|
<Close />
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Account information -->
|
|
||||||
<div class="modal-section">
|
|
||||||
<h3 class="modal-section-title">Account Data</h3>
|
|
||||||
<table class="modal-table">
|
|
||||||
<tbody>
|
|
||||||
<tr class="modal-table-row">
|
|
||||||
<th class="modal-table-row-item modal-table-head">Username</th>
|
|
||||||
<td class="modal-table-row-item">{$currentUser?.username}</td>
|
|
||||||
</tr>
|
|
||||||
<tr class="modal-table-row">
|
|
||||||
<th class="modal-table-row-item modal-table-head">Account created</th>
|
|
||||||
<td class="modal-table-row-item"
|
|
||||||
>{$currentUser?.createdAt !== undefined
|
|
||||||
? formatDateLong($currentUser?.createdAt)
|
|
||||||
: ""}</td
|
|
||||||
>
|
|
||||||
</tr>
|
|
||||||
<tr class="modal-table-row">
|
|
||||||
<th class="modal-table-row-item modal-table-head">Password updated</th>
|
|
||||||
<td class="modal-table-row-item"
|
|
||||||
>{$currentUser?.createdAt !== undefined
|
|
||||||
? formatDateLong($currentUser?.updatedAt)
|
|
||||||
: ""}</td
|
|
||||||
>
|
|
||||||
</tr>
|
|
||||||
{#if $currentUser?.isAdmin}
|
|
||||||
<tr class="modal-table-row">
|
|
||||||
<th class="modal-table-row-item modal-table-head">Admin permissions</th>
|
|
||||||
<td class="modal-table-row-item">{$currentUser?.isAdmin}</td>
|
|
||||||
</tr>
|
|
||||||
{/if}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Account settings -->
|
|
||||||
<div class="modal-section">
|
|
||||||
<h3 class="modal-section-title">Account Settings</h3>
|
|
||||||
|
|
||||||
{#if changePasswordModalError}
|
|
||||||
<div class="error mb-4" role="alert">
|
|
||||||
{changePasswordModalError}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div class="space-y-4">
|
|
||||||
<h4 class="font-bold">Change Password</h4>
|
|
||||||
|
|
||||||
<!-- Current Password -->
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label" for="currentPassword">Current Password</label>
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
id="currentPassword"
|
|
||||||
bind:value={currentPassword}
|
|
||||||
class="auth-input-field"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- New Password -->
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label" for="newPassword">New Password</label>
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
id="newPassword"
|
|
||||||
bind:value={newPassword}
|
|
||||||
class="auth-input-field"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Confirm New Password -->
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label" for="confirmPassword">Confirm New Password</label>
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
id="confirmPassword"
|
|
||||||
bind:value={confirmPassword}
|
|
||||||
class="auth-input-field"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button on:click={changePassword} class="auth-button"> Change Password </button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- "Danger zone" -->
|
|
||||||
<div class="modal-section border-b-0">
|
|
||||||
<h3 class="modal-section-title text-red-500">Danger Zone</h3>
|
|
||||||
|
|
||||||
{#if deleteUserModalError}
|
|
||||||
<div class="error mb-4" role="alert">
|
|
||||||
{deleteUserModalError}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div class="space-y-4">
|
|
||||||
<p class="text-sm">
|
|
||||||
Deleting your account will permanently remove all your notes and account information. This
|
|
||||||
action cannot be undone.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<!-- Confirm Password -->
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label" for="deleteConfirmPassword">Your Password</label>
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
id="deleteConfirmPassword"
|
|
||||||
bind:value={deleteConfirmPassword}
|
|
||||||
class="auth-input-field"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Type DELETE to confirm -->
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label" for="deleteConfirmText">Type DELETE to confirm</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="deleteConfirmText"
|
|
||||||
bind:value={deleteConfirmText}
|
|
||||||
class="auth-input-field"
|
|
||||||
placeholder="DELETE"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
on:click={deleteAccount}
|
|
||||||
class="auth-button bg-red-500 text-white hover:bg-red-600 disabled:cursor-not-allowed disabled:opacity-50"
|
|
||||||
disabled={deleteConfirmText !== "DELETE" || !deleteConfirmPassword}
|
|
||||||
>
|
|
||||||
Delete My Account
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Account information -->
|
||||||
|
<div class="modal-section">
|
||||||
|
<h3 class="modal-section-title">Account Data</h3>
|
||||||
|
<table class="modal-table">
|
||||||
|
<tbody>
|
||||||
|
<tr class="modal-table-row">
|
||||||
|
<th class="modal-table-row-item modal-table-head">Username</th>
|
||||||
|
<td class="modal-table-row-item">{$currentUser?.username}</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="modal-table-row">
|
||||||
|
<th class="modal-table-row-item modal-table-head">Account created</th>
|
||||||
|
<td class="modal-table-row-item"
|
||||||
|
>{$currentUser?.createdAt !== undefined
|
||||||
|
? formatDateLong($currentUser?.createdAt)
|
||||||
|
: ""}</td
|
||||||
|
>
|
||||||
|
</tr>
|
||||||
|
<tr class="modal-table-row">
|
||||||
|
<th class="modal-table-row-item modal-table-head">Password updated</th>
|
||||||
|
<td class="modal-table-row-item"
|
||||||
|
>{$currentUser?.createdAt !== undefined
|
||||||
|
? formatDateLong($currentUser?.updatedAt)
|
||||||
|
: ""}</td
|
||||||
|
>
|
||||||
|
</tr>
|
||||||
|
{#if $currentUser?.isAdmin}
|
||||||
|
<tr class="modal-table-row">
|
||||||
|
<th class="modal-table-row-item modal-table-head">Admin permissions</th>
|
||||||
|
<td class="modal-table-row-item">{$currentUser?.isAdmin}</td>
|
||||||
|
</tr>
|
||||||
|
{/if}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Account settings -->
|
||||||
|
<div class="modal-section">
|
||||||
|
<h3 class="modal-section-title">Account Settings</h3>
|
||||||
|
|
||||||
|
{#if changePasswordModalError}
|
||||||
|
<div class="error mb-4" role="alert">
|
||||||
|
{changePasswordModalError}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class="space-y-4">
|
||||||
|
<h4 class="font-bold">Change Password</h4>
|
||||||
|
|
||||||
|
<!-- Current Password -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label" for="currentPassword">Current Password</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="currentPassword"
|
||||||
|
bind:value={currentPassword}
|
||||||
|
class="auth-input-field"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- New Password -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label" for="newPassword">New Password</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="newPassword"
|
||||||
|
bind:value={newPassword}
|
||||||
|
class="auth-input-field"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Confirm New Password -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label" for="confirmPassword">Confirm New Password</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="confirmPassword"
|
||||||
|
bind:value={confirmPassword}
|
||||||
|
class="auth-input-field"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button on:click={changePassword} class="auth-button"> Change Password </button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- "Danger zone" -->
|
||||||
|
<div class="modal-section border-b-0">
|
||||||
|
<h3 class="modal-section-title text-red-500">Danger Zone</h3>
|
||||||
|
|
||||||
|
{#if deleteUserModalError}
|
||||||
|
<div class="error mb-4" role="alert">
|
||||||
|
{deleteUserModalError}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class="space-y-4">
|
||||||
|
<p class="text-sm">
|
||||||
|
Deleting your account will permanently remove all your notes and account information.
|
||||||
|
This action cannot be undone.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- Confirm Password -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label" for="deleteConfirmPassword">Your Password</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="deleteConfirmPassword"
|
||||||
|
bind:value={deleteConfirmPassword}
|
||||||
|
class="auth-input-field"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Type DELETE to confirm -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label" for="deleteConfirmText">Type DELETE to confirm</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="deleteConfirmText"
|
||||||
|
bind:value={deleteConfirmText}
|
||||||
|
class="auth-input-field"
|
||||||
|
placeholder="DELETE"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
on:click={deleteAccount}
|
||||||
|
class="auth-button bg-red-500 text-white hover:bg-red-600 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
|
disabled={deleteConfirmText !== "DELETE" || !deleteConfirmPassword}
|
||||||
|
>
|
||||||
|
Delete My Account
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -12,7 +12,12 @@
|
|||||||
import AdminWrench from "$lib/icons/sidebar/AdminShield.svelte"
|
import AdminWrench from "$lib/icons/sidebar/AdminShield.svelte"
|
||||||
import Exit from "$lib/icons/sidebar/Exit.svelte"
|
import Exit from "$lib/icons/sidebar/Exit.svelte"
|
||||||
import ThemeToggle from "./ThemeToggle.svelte"
|
import ThemeToggle from "./ThemeToggle.svelte"
|
||||||
import { formatDateLong, formatDateShort, parseExpirationPrefix } from "$lib/util/contentVisual"
|
import {
|
||||||
|
clickOutside,
|
||||||
|
formatDateLong,
|
||||||
|
formatDateShort,
|
||||||
|
parseExpirationPrefix
|
||||||
|
} from "$lib/util/contentVisual"
|
||||||
import type { FullNote, NoteMetadata } from "$lib/logic/model"
|
import type { FullNote, NoteMetadata } from "$lib/logic/model"
|
||||||
|
|
||||||
// props
|
// props
|
||||||
@ -210,55 +215,63 @@
|
|||||||
|
|
||||||
<!-- User section (single button) with dropdown -->
|
<!-- User section (single button) with dropdown -->
|
||||||
<div class="relative flex flex-col px-2 py-3">
|
<div class="relative flex flex-col px-2 py-3">
|
||||||
<button
|
<div
|
||||||
on:click={toggleUserMenu}
|
use:clickOutside={() => {
|
||||||
class="sidebar-action-button flex-wrap justify-between px-4 py-4"
|
if (isUserMenuOpen) {
|
||||||
|
isUserMenuOpen = false
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div class="sidebar-user-button-content-container">
|
<button
|
||||||
<User classString="h-6 w-6 flex-shrink-0" />
|
on:click={toggleUserMenu}
|
||||||
|
class="sidebar-action-button flex-wrap justify-between px-4 py-4"
|
||||||
|
>
|
||||||
|
<div class="sidebar-user-button-content-container">
|
||||||
|
<User classString="h-6 w-6 flex-shrink-0" />
|
||||||
|
|
||||||
<div class="sidebar-user-button-username-container">
|
<div class="sidebar-user-button-username-container">
|
||||||
<span class="sidebar-user-button-username-text">Logged in as {username}</span>
|
<span class="sidebar-user-button-username-text">Logged in as {username}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if isUserMenuOpen}
|
||||||
|
<ChevronDown classString="h-6 w-6 flex-shrink-0" />
|
||||||
|
{:else}
|
||||||
|
<ChevronUp classString="h-6 w-6 flex-shrink-0" />
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
</button>
|
||||||
|
|
||||||
{#if isUserMenuOpen}
|
<!-- User actions dropdown menu -->
|
||||||
<ChevronDown classString="h-6 w-6 flex-shrink-0" />
|
{#if isUserMenuOpen}
|
||||||
{:else}
|
<div class="sidebar-user-dropdown">
|
||||||
<ChevronUp classString="h-6 w-6 flex-shrink-0" />
|
<div class="flex flex-col p-0">
|
||||||
{/if}
|
<button
|
||||||
</div>
|
on:click={toggleSettingsModal}
|
||||||
</button>
|
class="sidebar-action-button rounded-none rounded-t-lg border-0 border-b"
|
||||||
|
>
|
||||||
|
<SettingsGear classString="mr-3 h-5 w-5" />
|
||||||
|
Settings
|
||||||
|
</button>
|
||||||
|
|
||||||
<!-- User actions dropdown menu -->
|
<button
|
||||||
{#if isUserMenuOpen}
|
on:click={toggleAdminModal}
|
||||||
<div class="sidebar-user-dropdown">
|
class="sidebar-action-button rounded-none border-0 border-y"
|
||||||
<div class="flex flex-col p-0">
|
>
|
||||||
<button
|
<AdminWrench classString="mr-3 h-5 w-5" />
|
||||||
on:click={toggleSettingsModal}
|
Admin view
|
||||||
class="sidebar-action-button rounded-none rounded-t-lg border-0 border-b"
|
</button>
|
||||||
>
|
|
||||||
<SettingsGear classString="mr-3 h-5 w-5" />
|
|
||||||
Settings
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
on:click={toggleAdminModal}
|
on:click={logout}
|
||||||
class="sidebar-action-button rounded-none border-0 border-y"
|
class="sidebar-action-button rounded-none rounded-b-lg border-0 border-t"
|
||||||
>
|
>
|
||||||
<AdminWrench classString="mr-3 h-5 w-5" />
|
<Exit classString="mr-3 h-5 w-5" />
|
||||||
Admin view
|
Log out
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
<button
|
|
||||||
on:click={logout}
|
|
||||||
class="sidebar-action-button rounded-none rounded-b-lg border-0 border-t"
|
|
||||||
>
|
|
||||||
<Exit classString="mr-3 h-5 w-5" />
|
|
||||||
Log out
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{/if}
|
||||||
{/if}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
|
@ -50,3 +50,25 @@ export const formatTitleWithHighlight = (title: string): string => {
|
|||||||
|
|
||||||
return `<span class="note-title-exp-highlight">${expirationPrefix}</span> ${cleanTitle}`
|
return `<span class="note-title-exp-highlight">${expirationPrefix}</span> ${cleanTitle}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const clickOutside = (
|
||||||
|
node: HTMLElement,
|
||||||
|
callbackFunction: () => void
|
||||||
|
): {
|
||||||
|
destroy: () => void
|
||||||
|
} => {
|
||||||
|
const handleClick = (event: MouseEvent): void => {
|
||||||
|
if (node && !node.contains(event.target as Node) && !event.defaultPrevented) {
|
||||||
|
callbackFunction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// event listener with capture phase to ensure it runs before other click handlers
|
||||||
|
document.addEventListener("click", handleClick, true)
|
||||||
|
|
||||||
|
return {
|
||||||
|
destroy: () => {
|
||||||
|
document.removeEventListener("click", handleClick, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user