diff options
| -rw-r--r-- | ui/app.html | 1 | ||||
| -rw-r--r-- | ui/lib/apiServer.js | 4 | ||||
| -rw-r--r-- | ui/lib/assets/logo.png | bin | 137101 -> 92469 bytes | |||
| -rw-r--r-- | ui/lib/components/Message.svelte | 32 | ||||
| -rw-r--r-- | ui/lib/components/MessageRun.svelte | 4 | ||||
| -rw-r--r-- | ui/routes/(login)/invite/[invite]/+page.svelte | 6 | ||||
| -rw-r--r-- | ui/service-worker.js | 54 | ||||
| -rw-r--r-- | ui/static/apple-touch-icon-precomposed.png | bin | 0 -> 92469 bytes | |||
| -rw-r--r-- | ui/static/apple-touch-icon.png | bin | 0 -> 92469 bytes | |||
| -rw-r--r-- | ui/static/favicon.ico | bin | 0 -> 92469 bytes | |||
| -rw-r--r-- | ui/static/favicon.png | bin | 137101 -> 92469 bytes | |||
| -rw-r--r-- | ui/static/manifest.json | 51 |
12 files changed, 142 insertions, 10 deletions
diff --git a/ui/app.html b/ui/app.html index 10525fe..5e7d92b 100644 --- a/ui/app.html +++ b/ui/app.html @@ -4,6 +4,7 @@ <meta charset="utf-8" /> <link rel="icon" href="%sveltekit.assets%/favicon.png" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> + <link rel="manifest" href="%sveltekit.assets%/manifest.json" /> %sveltekit.head% </head> <body data-sveltekit-preload-data="hover" data-theme="skeleton"> diff --git a/ui/lib/apiServer.js b/ui/lib/apiServer.js index 6ada0f7..fee1a81 100644 --- a/ui/lib/apiServer.js +++ b/ui/lib/apiServer.js @@ -34,6 +34,10 @@ export async function postToChannel(channelId, body) { return apiServer.post(`/channels/${channelId}`, { body }); } +export async function deleteMessage(messageId) { + return apiServer.delete(`/messages/${messageId}`, {}); +} + export async function createInvite() { return apiServer.post(`/invite`, {}); } diff --git a/ui/lib/assets/logo.png b/ui/lib/assets/logo.png Binary files differindex 5df6b4e..4b35d9b 100644 --- a/ui/lib/assets/logo.png +++ b/ui/lib/assets/logo.png diff --git a/ui/lib/components/Message.svelte b/ui/lib/components/Message.svelte index 68c5c91..0c8eeec 100644 --- a/ui/lib/components/Message.svelte +++ b/ui/lib/components/Message.svelte @@ -2,16 +2,38 @@ import { marked } from 'marked'; import DOMPurify from 'dompurify'; + import { deleteMessage } from '$lib/apiServer'; + function scroll(message) { message.scrollIntoView(); } - let { at, body } = $props(); + let { id, at, body, editable = false } = $props(); let renderedBody = $derived(DOMPurify.sanitize(marked.parse(body, { breaks: true }))); + let deleteArmed = $state(false); + + function onDelete(event) { + event.preventDefault(); + if (deleteArmed) { + deleteArmed = false; + deleteMessage(id); + } else { + deleteArmed = true; + } + } + + function onmouseleave() { + deleteArmed = false; + } </script> -<div class="message relative"> - <span class="timestamp chip variant-soft absolute top-0 right-0">{at}</span> +<div class="message relative" class:bg-warning-800={deleteArmed} {onmouseleave}> + <div class="handle chip bg-surface-700 absolute -top-6 right-0"> + {at} + {#if editable} + <button onclick={onDelete}>🗑️</button> + {/if} + </div> <section use:scroll class="py-1 message-body"> <!-- eslint-disable-next-line svelte/no-at-html-tags --> {@html renderedBody} @@ -19,10 +41,10 @@ </div> <style> - .message .timestamp { + .message .handle { display: none; } - .message:hover .timestamp { + .message:hover .handle { display: flex; } .message-body { diff --git a/ui/lib/components/MessageRun.svelte b/ui/lib/components/MessageRun.svelte index 2e8c613..83a82a9 100644 --- a/ui/lib/components/MessageRun.svelte +++ b/ui/lib/components/MessageRun.svelte @@ -16,7 +16,7 @@ <span class="chip variant-soft sticky top-o left-0"> @{name}: </span> - {#each messages as { at, body }} - <Message {at} {body} /> + {#each messages as { id, at, body }} + <Message {id} {at} {body} editable={ownMessage} /> {/each} </div> diff --git a/ui/routes/(login)/invite/[invite]/+page.svelte b/ui/routes/(login)/invite/[invite]/+page.svelte index 18bf437..4433bd6 100644 --- a/ui/routes/(login)/invite/[invite]/+page.svelte +++ b/ui/routes/(login)/invite/[invite]/+page.svelte @@ -11,10 +11,10 @@ let pending = false; let disabled = $derived(pending); - async function onSubmit(event) { + async function onSubmit(event, inviteId) { event.preventDefault(); pending = true; - const response = await acceptInvite(data.invite.id, username, password); + const response = await acceptInvite(inviteId, username, password); if (200 <= response.status && response.status < 300) { username = ''; password = ''; @@ -32,5 +32,5 @@ <div class="card m-4 p-4"> <p>Hi there! {invite.issuer} invites you to the conversation.</p> </div> - <LogIn {disabled} bind:username bind:password onsubmit={onSubmit} /> + <LogIn {disabled} bind:username bind:password onsubmit={(event) => onSubmit(event, invite.id)} /> {/await} diff --git a/ui/service-worker.js b/ui/service-worker.js new file mode 100644 index 0000000..9855a73 --- /dev/null +++ b/ui/service-worker.js @@ -0,0 +1,54 @@ +/// <reference types="@sveltejs/kit" /> +/// <reference no-default-lib="true"/> +/// <reference lib="esnext" /> +/// <reference lib="webworker" /> + +// Because of this line, this service worker won't run in dev mode in Firefox. +// Only Safari, Edge, Chrome can run it at the moment, because only they +// support modules in service workers. +// +// That's okay! If you run `tools/run` with PILCROW_DEV unset, you will get the +// bundled version, and can work on it. Or just use Safari. +import { build, files, version } from '$service-worker'; + +// Create a unique cache name for this deployment +const CACHE = `cache-${version}`; + +const ASSETS = [ + ...build, // the app itself + ...files // everything in `static` +]; + +self.addEventListener('install', (event) => { + // Create a new cache and add all files to it + async function addFilesToCache() { + const cache = await caches.open(CACHE); + await cache.addAll(ASSETS); + } + + event.waitUntil(addFilesToCache()); +}); + +self.addEventListener('activate', (event) => { + // Remove previous cached data from disk + async function deleteOldCaches() { + for (const key of await caches.keys()) { + if (key !== CACHE) await caches.delete(key); + } + } + + event.waitUntil(deleteOldCaches()); +}); + +// The simplest possible use of the caches above: +async function cacheFirst(request) { + const responseFromCache = await caches.match(request); + if (responseFromCache) { + return responseFromCache; + } + return fetch(request); +} + +self.addEventListener('fetch', (event) => { + event.respondWith(cacheFirst(event.request)); +}); diff --git a/ui/static/apple-touch-icon-precomposed.png b/ui/static/apple-touch-icon-precomposed.png Binary files differnew file mode 100644 index 0000000..4b35d9b --- /dev/null +++ b/ui/static/apple-touch-icon-precomposed.png diff --git a/ui/static/apple-touch-icon.png b/ui/static/apple-touch-icon.png Binary files differnew file mode 100644 index 0000000..4b35d9b --- /dev/null +++ b/ui/static/apple-touch-icon.png diff --git a/ui/static/favicon.ico b/ui/static/favicon.ico Binary files differnew file mode 100644 index 0000000..4b35d9b --- /dev/null +++ b/ui/static/favicon.ico diff --git a/ui/static/favicon.png b/ui/static/favicon.png Binary files differindex 5df6b4e..4b35d9b 100644 --- a/ui/static/favicon.png +++ b/ui/static/favicon.png diff --git a/ui/static/manifest.json b/ui/static/manifest.json new file mode 100644 index 0000000..5d735d0 --- /dev/null +++ b/ui/static/manifest.json @@ -0,0 +1,51 @@ +{ + "name": "Pilcrow", + "short_name": "Pilcrow", + "start_url": "/", + "display": "standalone", + "background_color": "#fdfdfd", + "theme_color": "#2c3656", + "orientation": "portrait-primary", + "icons": [ + { + "src": "/favicon.png", + "type": "image/png", + "sizes": "72x72" + }, + { + "src": "/favicon.png", + "type": "image/png", + "sizes": "96x96" + }, + { + "src": "/favicon.png", + "type": "image/png", + "sizes": "128x128" + }, + { + "src": "/favicon.png", + "type": "image/png", + "sizes": "144x144" + }, + { + "src": "/favicon.png", + "type": "image/png", + "sizes": "152x152" + }, + { + "src": "/favicon.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "/favicon.png", + "type": "image/png", + "sizes": "384x384" + }, + { + "src": "/favicon.png", + "type": "image/png", + "sizes": "512x512" + } + ] +} |
