Attic is a cozy space with lofty ambitions. attic.social

fix pointer dialog

dbushell.com 22a88c4b 2ca2aaa2

verified
+94 -51
-1
src/app.html
··· 10 10 </head> 11 11 <body data-sveltekit-preload-data="hover"> 12 12 <attic-app>%sveltekit.body%</attic-app> 13 - <div class="pointer"></div> 14 13 </body> 15 14 </html>
+1 -1
src/css/components/bookmark.css
··· 70 70 & > code { 71 71 align-self: baseline; 72 72 font-size: var(--font-size-1); 73 - grid-template-columns: 2; 73 + grid-column: 2; 74 74 text-overflow: ellipsis; 75 75 opacity: 0.8; 76 76 overflow: hidden;
+5 -1
src/css/main.css
··· 20 20 font-weight: 700; 21 21 } 22 22 23 - .pointer { 23 + #pointer { 24 24 --size: 50px; 25 25 background: url("/images/pointer.svg") center / 100% auto no-repeat; 26 26 block-size: var(--size); ··· 35 35 position: fixed; 36 36 user-select: none; 37 37 z-index: 999; 38 + 39 + :where(body:not(:has(:is(a[href], button):hover, :focus-visible))) & { 40 + display: none; 41 + } 38 42 39 43 @supports not (inset: anchor(start)) { 40 44 display: none;
+32
src/lib/dialog.svelte.ts
··· 1 + export let pointer: { ref: HTMLElement | null } = $state({ ref: null }); 2 + 3 + export const openDialog = (dialog?: HTMLDialogElement) => { 4 + if (dialog === undefined) { 5 + return; 6 + } 7 + 8 + dialog.showModal(); 9 + 10 + dialog.addEventListener("close", () => { 11 + if (pointer.ref) { 12 + document.querySelector("attic-app")?.append(pointer.ref); 13 + pointer.ref.showPopover(); 14 + } 15 + }, { once: true }); 16 + 17 + if (pointer.ref) { 18 + dialog.append(pointer.ref); 19 + pointer.ref.showPopover(); 20 + } 21 + }; 22 + 23 + export const closeDialog = (dialog?: HTMLDialogElement) => { 24 + if (dialog === undefined) { 25 + return; 26 + } 27 + dialog.close(); 28 + // if (pointer.ref) { 29 + // document.querySelector("attic-app")?.append(pointer.ref); 30 + // pointer.ref.showPopover(); 31 + // } 32 + };
+10
src/routes/+layout.svelte
··· 1 1 <script lang="ts"> 2 2 import "$css/main.css"; 3 + import { pointer } from "$lib/dialog.svelte.js"; 4 + import { onMount } from "svelte"; 5 + 6 + onMount(() => { 7 + if (pointer.ref) { 8 + pointer.ref.showPopover(); 9 + } 10 + }); 3 11 4 12 let { data, children } = $props(); 5 13 </script> ··· 35 43 </small> 36 44 </p> 37 45 </footer> 46 + 47 + <div bind:this={pointer.ref} id="pointer" popover="manual"></div>
+46 -48
src/routes/bookmarks/[did=did]/+page.svelte
··· 1 1 <script lang="ts"> 2 + import { closeDialog, openDialog } from "$lib/dialog.svelte.js"; 2 3 import type { EventHandler } from "svelte/elements"; 3 4 import type { PageProps } from "./$types"; 4 - 5 5 let { data, form, params }: PageProps = $props(); 6 6 7 7 const isSelf = $derived(data.user && params.did === data.user.did); ··· 53 53 $effect(() => { 54 54 if (form?.action === "editBookmark" && "error" in form) { 55 55 if (editDialog?.open === false) { 56 - editDialog?.showModal(); 56 + openDialog(editDialog); 57 57 } 58 58 } 59 59 }); ··· 61 61 $effect(() => { 62 62 if (form?.action === "createBookmark" && "error" in form) { 63 63 if (createDialog?.open === false) { 64 - createDialog?.showModal(); 64 + openDialog(createDialog); 65 65 } 66 66 } 67 67 }); ··· 91 91 commandfor={dialog?.id} 92 92 onclick={(ev) => { 93 93 ev.preventDefault(); 94 - dialog?.close(); 94 + closeDialog(dialog); 95 95 }} 96 96 > 97 97 <span class="visually-hidden">close</span> ··· 189 189 </dialog> 190 190 {/if} 191 191 192 - {#if data.bookmarks.length} 193 - <div class="Bookmarks"> 194 - <div class="Bookmarks-header"> 195 - <h2>Bookmarks</h2> 192 + <div class="Bookmarks"> 193 + <div class="Bookmarks-header"> 194 + <h2>Bookmarks</h2> 195 + {#if isSelf} 196 + <button type="button" onclick={() => openDialog(createDialog)}> 197 + New 198 + </button> 199 + {/if} 200 + </div> 201 + {#each data.bookmarks as entry (entry.cid)} 202 + <article id={entry.cid} class="Bookmark"> 203 + <h3> 204 + <a href={entry.url} rel="noopener noreferrer" target="_blank"> 205 + <img 206 + alt="" 207 + width="16" 208 + height="16" 209 + src="/bookmarks/favicon/{new URL(entry.url).hostname}" 210 + /> 211 + {entry.title} 212 + </a> 213 + </h3> 214 + <time datetime={entry.createdAt}> 215 + {dateFormat.format(new Date(entry.createdAt))} 216 + </time> 217 + <code aria-hidden="true">{entry.url}</code> 196 218 {#if isSelf} 197 - <button type="button" onclick={() => createDialog?.showModal()}> 198 - New 199 - </button> 219 + <div class="flex flex-wrap"> 220 + <button 221 + type="button" 222 + onclick={(ev) => { 223 + ev.preventDefault(); 224 + form = null; 225 + editData = entry; 226 + openDialog(editDialog); 227 + }} 228 + > 229 + Edit 230 + </button> 231 + </div> 200 232 {/if} 201 - </div> 202 - {#each data.bookmarks as entry (entry.cid)} 203 - <article id={entry.cid} class="Bookmark"> 204 - <h3> 205 - <a href={entry.url} rel="noopener noreferrer" target="_blank"> 206 - <img 207 - alt="" 208 - width="16" 209 - height="16" 210 - src="/bookmarks/favicon/{new URL(entry.url).hostname}" 211 - /> 212 - {entry.title} 213 - </a> 214 - </h3> 215 - <time datetime={entry.createdAt}> 216 - {dateFormat.format(new Date(entry.createdAt))} 217 - </time> 218 - <code aria-hidden="true">{entry.url}</code> 219 - {#if isSelf} 220 - <div class="flex flex-wrap"> 221 - <button 222 - type="button" 223 - onclick={(ev) => { 224 - ev.preventDefault(); 225 - form = null; 226 - editData = entry; 227 - editDialog?.showModal(); 228 - }} 229 - > 230 - Edit 231 - </button> 232 - </div> 233 - {/if} 234 - </article> 235 - {/each} 236 - </div> 237 - {/if} 233 + </article> 234 + {/each} 235 + </div>