diff options
Diffstat (limited to 'ui/lib')
| -rw-r--r-- | ui/lib/apiServer.js | 148 | ||||
| -rw-r--r-- | ui/lib/components/ActiveChannel.svelte | 62 | ||||
| -rw-r--r-- | ui/lib/components/Channel.svelte | 10 | ||||
| -rw-r--r-- | ui/lib/components/ChannelList.svelte | 14 | ||||
| -rw-r--r-- | ui/lib/components/CreateChannelForm.svelte | 42 | ||||
| -rw-r--r-- | ui/lib/components/CurrentUser.svelte | 34 | ||||
| -rw-r--r-- | ui/lib/components/Invite.svelte | 10 | ||||
| -rw-r--r-- | ui/lib/components/Invites.svelte | 28 | ||||
| -rw-r--r-- | ui/lib/components/LogIn.svelte | 68 | ||||
| -rw-r--r-- | ui/lib/components/Message.svelte | 44 | ||||
| -rw-r--r-- | ui/lib/components/MessageInput.svelte | 60 | ||||
| -rw-r--r-- | ui/lib/components/MessageRun.svelte | 28 | ||||
| -rw-r--r-- | ui/lib/store/channels.js | 60 | ||||
| -rw-r--r-- | ui/lib/store/logins.js | 34 | ||||
| -rw-r--r-- | ui/lib/store/messages.js | 64 |
15 files changed, 353 insertions, 353 deletions
diff --git a/ui/lib/apiServer.js b/ui/lib/apiServer.js index e537abc..a6fdaa6 100644 --- a/ui/lib/apiServer.js +++ b/ui/lib/apiServer.js @@ -2,120 +2,120 @@ import axios from 'axios'; import { channelsList, logins, messages } from '$lib/store'; export const apiServer = axios.create({ - baseURL: '/api/', - validateStatus: () => true + baseURL: '/api/', + validateStatus: () => true }); export async function boot() { - return apiServer.get('/boot'); + return apiServer.get('/boot'); } export async function setup(name, password) { - return apiServer.post('/setup', { name, password }); + return apiServer.post('/setup', { name, password }); } export async function logIn(name, password) { - return apiServer.post('/auth/login', { name, password }); + return apiServer.post('/auth/login', { name, password }); } export async function logOut() { - return apiServer.post('/auth/logout', {}); + return apiServer.post('/auth/logout', {}); } export async function changePassword(password, to) { - return apiServer.post('/password', { password, to }); + return apiServer.post('/password', { password, to }); } export async function createChannel(name) { - return apiServer.post('/channels', { name }); + return apiServer.post('/channels', { name }); } export async function postToChannel(channelId, body) { - return apiServer.post(`/channels/${channelId}`, { body }); + return apiServer.post(`/channels/${channelId}`, { body }); } export async function createInvite() { - return apiServer.post(`/invite`, {}); + return apiServer.post(`/invite`, {}); } export async function getInvite(inviteId) { - return apiServer.get(`/invite/${inviteId}`); + return apiServer.get(`/invite/${inviteId}`); } export async function acceptInvite(inviteId, username, password) { - const data = { - name: username, - password - }; - return apiServer.post(`/invite/${inviteId}`, data); + const data = { + name: username, + password + }; + return apiServer.post(`/invite/${inviteId}`, data); } export function subscribeToEvents(resume_point) { - const eventsUrl = new URL('/api/events', window.location); - eventsUrl.searchParams.append('resume_point', resume_point); - const evtSource = new EventSource(eventsUrl.toString()); - // TODO: this should process all incoming events and store them. - // TODO: eventually we'll need to handle expiring old info, so as not to use - // infinite browser memory. - /* - * Known message types as of now: - * - created: a channel is created. - * - action: ignore. - * - message: a message is created. - * - action: display message in channel. - * - message_deleted: a message is deleted. - * - action: replace message with <...>. - * - deleted: a channel is deleted. - * - action: remove channel from sidebar. - */ - evtSource.onmessage = (evt) => { - const data = JSON.parse(evt.data); - - switch (data.type) { - case 'login': - onLoginEvent(data); - break; - case 'channel': - onChannelEvent(data); - break; - case 'message': - onMessageEvent(data); - break; - } - }; - - return evtSource; + const eventsUrl = new URL('/api/events', window.location); + eventsUrl.searchParams.append('resume_point', resume_point); + const evtSource = new EventSource(eventsUrl.toString()); + // TODO: this should process all incoming events and store them. + // TODO: eventually we'll need to handle expiring old info, so as not to use + // infinite browser memory. + /* + * Known message types as of now: + * - created: a channel is created. + * - action: ignore. + * - message: a message is created. + * - action: display message in channel. + * - message_deleted: a message is deleted. + * - action: replace message with <...>. + * - deleted: a channel is deleted. + * - action: remove channel from sidebar. + */ + evtSource.onmessage = (evt) => { + const data = JSON.parse(evt.data); + + switch (data.type) { + case 'login': + onLoginEvent(data); + break; + case 'channel': + onChannelEvent(data); + break; + case 'message': + onMessageEvent(data); + break; + } + }; + + return evtSource; } function onLoginEvent(data) { - switch (data.event) { - case 'created': - logins.update((value) => value.addLogin(data.id, data.name)); - break; - } + switch (data.event) { + case 'created': + logins.update((value) => value.addLogin(data.id, data.name)); + break; + } } function onChannelEvent(data) { - switch (data.event) { - case 'created': - channelsList.update((value) => value.addChannel(data.id, data.name)); - break; - case 'deleted': - channelsList.update((value) => value.deleteChannel(data.id)); - messages.update((value) => value.deleteChannel(data.id)); - break; - } + switch (data.event) { + case 'created': + channelsList.update((value) => value.addChannel(data.id, data.name)); + break; + case 'deleted': + channelsList.update((value) => value.deleteChannel(data.id)); + messages.update((value) => value.deleteChannel(data.id)); + break; + } } function onMessageEvent(data) { - switch (data.event) { - case 'sent': - messages.update((value) => - value.addMessage(data.channel, data.id, data.at, data.sender, data.body) - ); - break; - case 'deleted': - messages.update((value) => value.deleteMessage(data.id)); - break; - } + switch (data.event) { + case 'sent': + messages.update((value) => + value.addMessage(data.channel, data.id, data.at, data.sender, data.body) + ); + break; + case 'deleted': + messages.update((value) => value.deleteMessage(data.id)); + break; + } } diff --git a/ui/lib/components/ActiveChannel.svelte b/ui/lib/components/ActiveChannel.svelte index ac1a0b9..a4ccd24 100644 --- a/ui/lib/components/ActiveChannel.svelte +++ b/ui/lib/components/ActiveChannel.svelte @@ -1,42 +1,42 @@ <script> - import { messages } from '$lib/store'; - import MessageRun from './MessageRun.svelte'; + import { messages } from '$lib/store'; + import MessageRun from './MessageRun.svelte'; - let { channel } = $props(); - let messageList = $derived(channel !== null ? $messages.inChannel(channel) : []); + let { channel } = $props(); + let messageList = $derived(channel !== null ? $messages.inChannel(channel) : []); - function* chunkBy(xs, fn) { - let chunk; - let key; - for (let x of xs) { - let newKey = fn(x); - if (key !== newKey) { - if (chunk !== undefined) { - yield [key, chunk]; - } + function* chunkBy(xs, fn) { + let chunk; + let key; + for (let x of xs) { + let newKey = fn(x); + if (key !== newKey) { + if (chunk !== undefined) { + yield [key, chunk]; + } - chunk = [x]; - key = newKey; - } else { - chunk.push(x); - } - } - if (chunk !== undefined) { - yield [key, chunk]; - } - } + chunk = [x]; + key = newKey; + } else { + chunk.push(x); + } + } + if (chunk !== undefined) { + yield [key, chunk]; + } + } </script> <div class="container"> - {#each chunkBy(messageList, (msg) => msg.sender) as [sender, messages]} - <div> - <MessageRun {sender} {messages} /> - </div> - {/each} + {#each chunkBy(messageList, (msg) => msg.sender) as [sender, messages]} + <div> + <MessageRun {sender} {messages} /> + </div> + {/each} </div> <style> - .container { - overflow: auto; - } + .container { + overflow: auto; + } </style> diff --git a/ui/lib/components/Channel.svelte b/ui/lib/components/Channel.svelte index fdc5d56..e84c6d0 100644 --- a/ui/lib/components/Channel.svelte +++ b/ui/lib/components/Channel.svelte @@ -1,10 +1,10 @@ <script> - let { id, name, active } = $props(); + let { id, name, active } = $props(); </script> <li class="rounded-full" class:bg-slate-400={active}> - <a href="/ch/{id}"> - <span class="badge bg-primary-500">#</span> - <span class="flex-auto">{name}</span> - </a> + <a href="/ch/{id}"> + <span class="badge bg-primary-500">#</span> + <span class="flex-auto">{name}</span> + </a> </li> diff --git a/ui/lib/components/ChannelList.svelte b/ui/lib/components/ChannelList.svelte index 9d1227e..51dd6cf 100644 --- a/ui/lib/components/ChannelList.svelte +++ b/ui/lib/components/ChannelList.svelte @@ -1,13 +1,13 @@ <script> - import Channel from './Channel.svelte'; + import Channel from './Channel.svelte'; - let { channels, active } = $props(); + let { channels, active } = $props(); </script> <nav class="list-nav"> - <ul> - {#each channels as channel} - <Channel {...channel} active={active === channel.id} /> - {/each} - </ul> + <ul> + {#each channels as channel} + <Channel {...channel} active={active === channel.id} /> + {/each} + </ul> </nav> diff --git a/ui/lib/components/CreateChannelForm.svelte b/ui/lib/components/CreateChannelForm.svelte index ac43925..afa3f78 100644 --- a/ui/lib/components/CreateChannelForm.svelte +++ b/ui/lib/components/CreateChannelForm.svelte @@ -1,30 +1,30 @@ <script> - import { createChannel } from '$lib/apiServer'; + import { createChannel } from '$lib/apiServer'; - let name = $state(''); - let pending = false; - let disabled = $derived(pending); + let name = $state(''); + let pending = false; + let disabled = $derived(pending); - async function handleSubmit(event) { - event.preventDefault(); - pending = true; - const response = await createChannel(name); - if (200 <= response.status && response.status < 300) { - name = ''; - } - pending = false; - } + async function handleSubmit(event) { + event.preventDefault(); + pending = true; + const response = await createChannel(name); + if (200 <= response.status && response.status < 300) { + name = ''; + } + pending = false; + } </script> <form onsubmit={handleSubmit} class="form form-row flex-nowrap"> - <input - type="text" - placeholder="create channel" - bind:value={name} - {disabled} - class="input flex-auto h-6 w-9/12" - /> - <button type="submit" class="flex-none w-6 h-6">➕</button> + <input + type="text" + placeholder="create channel" + bind:value={name} + {disabled} + class="input flex-auto h-6 w-9/12" + /> + <button type="submit" class="flex-none w-6 h-6">➕</button> </form> <style> diff --git a/ui/lib/components/CurrentUser.svelte b/ui/lib/components/CurrentUser.svelte index 46c76b0..56bf915 100644 --- a/ui/lib/components/CurrentUser.svelte +++ b/ui/lib/components/CurrentUser.svelte @@ -1,25 +1,25 @@ <script> - import { goto } from '$app/navigation'; - import { logOut } from '$lib/apiServer'; - import { currentUser } from '$lib/store'; + import { goto } from '$app/navigation'; + import { logOut } from '$lib/apiServer'; + import { currentUser } from '$lib/store'; - async function handleLogout(event) { - event.preventDefault(); - const response = await logOut(); - if (200 <= response.status && response.status < 300) { - currentUser.update(() => null); - goto('/login'); - } - } + async function handleLogout(event) { + event.preventDefault(); + const response = await logOut(); + if (200 <= response.status && response.status < 300) { + currentUser.update(() => null); + goto('/login'); + } + } </script> <form onsubmit={handleLogout}> - {#if $currentUser} - <a href="/me">@{$currentUser.username}</a> - {/if} - <button class="border-slate-500 border-solid border-2 font-bold p-1 rounded" type="submit" - >log out</button - > + {#if $currentUser} + <a href="/me">@{$currentUser.username}</a> + {/if} + <button class="border-slate-500 border-solid border-2 font-bold p-1 rounded" type="submit" + >log out</button + > </form> <style> diff --git a/ui/lib/components/Invite.svelte b/ui/lib/components/Invite.svelte index 79fe087..35e00b4 100644 --- a/ui/lib/components/Invite.svelte +++ b/ui/lib/components/Invite.svelte @@ -1,12 +1,12 @@ <script> - import { clipboard } from '@skeletonlabs/skeleton'; + import { clipboard } from '@skeletonlabs/skeleton'; - let { id } = $props(); - let inviteUrl = $derived(new URL(`/invite/${id}`, document.location)); + let { id } = $props(); + let inviteUrl = $derived(new URL(`/invite/${id}`, document.location)); </script> <button - class="border-slate-500 border-solid border-2 font-bold p-1 rounded" - use:clipboard={inviteUrl}>Copy</button + class="border-slate-500 border-solid border-2 font-bold p-1 rounded" + use:clipboard={inviteUrl}>Copy</button > <span data-clipboard="inviteUrl">{inviteUrl}</span> diff --git a/ui/lib/components/Invites.svelte b/ui/lib/components/Invites.svelte index 337ee7e..cc14f3b 100644 --- a/ui/lib/components/Invites.svelte +++ b/ui/lib/components/Invites.svelte @@ -1,23 +1,23 @@ <script> - import { createInvite } from '$lib/apiServer'; - import Invite from '$lib/components/Invite.svelte'; + import { createInvite } from '$lib/apiServer'; + import Invite from '$lib/components/Invite.svelte'; - let invites = $state([]); + let invites = $state([]); - async function onSubmit(event) { - event.preventDefault(); - let response = await createInvite(); - if (response.status == 200) { - invites.push(response.data); - } - } + async function onSubmit(event) { + event.preventDefault(); + let response = await createInvite(); + if (response.status == 200) { + invites.push(response.data); + } + } </script> <ul> - {#each invites as invite} - <li><Invite id={invite.id} /></li> - {/each} + {#each invites as invite} + <li><Invite id={invite.id} /></li> + {/each} </ul> <form onsubmit={onSubmit}> - <button class="btn variant-filled" type="submit"> Create Invitation </button> + <button class="btn variant-filled" type="submit"> Create Invitation </button> </form> diff --git a/ui/lib/components/LogIn.svelte b/ui/lib/components/LogIn.svelte index 4e28abe..7fb91e8 100644 --- a/ui/lib/components/LogIn.svelte +++ b/ui/lib/components/LogIn.svelte @@ -1,39 +1,39 @@ <script> - let { - username = $bindable(), - password = $bindable(), - legend = 'sign in', - disabled, - onsubmit - } = $props(); + let { + username = $bindable(), + password = $bindable(), + legend = 'sign in', + disabled, + onsubmit + } = $props(); </script> <div class="card m-4 p-4"> - <form {onsubmit}> - <label class="label" for="username"> - username - <input - class="input" - name="username" - type="text" - placeholder="username" - bind:value={username} - {disabled} - /> - </label> - <label class="label" for="password"> - password - <input - class="input" - name="password" - type="password" - placeholder="password" - bind:value={password} - {disabled} - /> - </label> - <button class="btn variant-filled" type="submit"> - {legend} - </button> - </form> + <form {onsubmit}> + <label class="label" for="username"> + username + <input + class="input" + name="username" + type="text" + placeholder="username" + bind:value={username} + {disabled} + /> + </label> + <label class="label" for="password"> + password + <input + class="input" + name="password" + type="password" + placeholder="password" + bind:value={password} + {disabled} + /> + </label> + <button class="btn variant-filled" type="submit"> + {legend} + </button> + </form> </div> diff --git a/ui/lib/components/Message.svelte b/ui/lib/components/Message.svelte index f47e9b6..f0e7045 100644 --- a/ui/lib/components/Message.svelte +++ b/ui/lib/components/Message.svelte @@ -1,32 +1,32 @@ <script> - import { marked } from 'marked'; - import DOMPurify from 'dompurify'; + import { marked } from 'marked'; + import DOMPurify from 'dompurify'; - function scroll(message) { - message.scrollIntoView(); - } + function scroll(message) { + message.scrollIntoView(); + } - let { at, body } = $props(); - let renderedBody = $derived(DOMPurify.sanitize(marked.parse(body, { breaks: true }))); + let { at, body } = $props(); + let renderedBody = $derived(DOMPurify.sanitize(marked.parse(body, { breaks: true }))); </script> <div class="message relative"> - <span class="timestamp chip variant-soft absolute top-0 right-0">{at}</span> - <section use:scroll class="py-1 message-body"> - <!-- eslint-disable-next-line svelte/no-at-html-tags --> - {@html renderedBody} - </section> + <span class="timestamp chip variant-soft absolute top-0 right-0">{at}</span> + <section use:scroll class="py-1 message-body"> + <!-- eslint-disable-next-line svelte/no-at-html-tags --> + {@html renderedBody} + </section> </div> <style> - .message .timestamp { - display: none; - } - .message:hover .timestamp { - display: flex; - } - .message-body:empty:after { - content: '.'; - visibility: hidden; - } + .message .timestamp { + display: none; + } + .message:hover .timestamp { + display: flex; + } + .message-body:empty:after { + content: '.'; + visibility: hidden; + } </style> diff --git a/ui/lib/components/MessageInput.svelte b/ui/lib/components/MessageInput.svelte index 220ed3b..907391c 100644 --- a/ui/lib/components/MessageInput.svelte +++ b/ui/lib/components/MessageInput.svelte @@ -1,40 +1,40 @@ <script> - import { postToChannel } from '$lib/apiServer'; + import { postToChannel } from '$lib/apiServer'; - let { channel } = $props(); + let { channel } = $props(); - let form; - let value = $state(''); - let pending = false; + let form; + let value = $state(''); + let pending = false; - let disabled = $derived(pending); + let disabled = $derived(pending); - async function onSubmit(event) { - event.preventDefault(); - pending = true; - await postToChannel(channel, value); - form.reset(); - pending = false; - } + async function onSubmit(event) { + event.preventDefault(); + pending = true; + await postToChannel(channel, value); + form.reset(); + pending = false; + } - function onKeyDown(event) { - if (!event.altKey && event.key === 'Enter') { - onSubmit(event); - } - } + function onKeyDown(event) { + if (!event.altKey && event.key === 'Enter') { + onSubmit(event); + } + } </script> <form bind:this={form} onsubmit={onSubmit} class="flex flex-row flex-nowrap"> - <textarea - onkeydown={onKeyDown} - bind:value - {disabled} - type="search" - class="flex-auto h-6 input rounded-r-none" - ></textarea> - <button - color="primary variant-filled-secondary" - type="submit" - class="flex-none w-6 h-6 btn-icon variant-filled rounded-l-none">»</button - > + <textarea + onkeydown={onKeyDown} + bind:value + {disabled} + type="search" + class="flex-auto h-6 input rounded-r-none" + ></textarea> + <button + color="primary variant-filled-secondary" + type="submit" + class="flex-none w-6 h-6 btn-icon variant-filled rounded-l-none">»</button + > </form> diff --git a/ui/lib/components/MessageRun.svelte b/ui/lib/components/MessageRun.svelte index 23c2186..b3e3eee 100644 --- a/ui/lib/components/MessageRun.svelte +++ b/ui/lib/components/MessageRun.svelte @@ -1,22 +1,22 @@ <script> - import { logins, currentUser } from '$lib/store'; - import Message from '$lib/components/Message.svelte'; + import { logins, currentUser } from '$lib/store'; + import Message from '$lib/components/Message.svelte'; - let { sender, messages } = $props(); + let { sender, messages } = $props(); - let name = $derived($logins.get(sender)); - let ownMessage = $derived($currentUser !== null && $currentUser.id == sender); + let name = $derived($logins.get(sender)); + let ownMessage = $derived($currentUser !== null && $currentUser.id == sender); </script> <div - class="card card-hover m-4 px-4 py-1 relative" - class:own-message={ownMessage} - class:other-message={!ownMessage} + class="card card-hover m-4 px-4 py-1 relative" + class:own-message={ownMessage} + class:other-message={!ownMessage} > - <span class="chip variant-soft sticky top-o left-0"> - @{name}: - </span> - {#each messages as { at, body }} - <Message {at} {body} /> - {/each} + <span class="chip variant-soft sticky top-o left-0"> + @{name}: + </span> + {#each messages as { at, body }} + <Message {at} {body} /> + {/each} </div> diff --git a/ui/lib/store/channels.js b/ui/lib/store/channels.js index b57ca7e..37dc673 100644 --- a/ui/lib/store/channels.js +++ b/ui/lib/store/channels.js @@ -1,36 +1,36 @@ export class Channels { - constructor() { - this.channels = []; - } + constructor() { + this.channels = []; + } - setChannels(channels) { - this.channels = [...channels]; - this.sort(); - return this; - } + setChannels(channels) { + this.channels = [...channels]; + this.sort(); + return this; + } - addChannel(id, name) { - this.channels = [...this.channels, { id, name }]; - this.sort(); - return this; - } + addChannel(id, name) { + this.channels = [...this.channels, { id, name }]; + this.sort(); + return this; + } - deleteChannel(id) { - const channelIndex = this.channels.map((e) => e.id).indexOf(id); - if (channelIndex !== -1) { - this.channels.splice(channelIndex, 1); - } - return this; - } + deleteChannel(id) { + const channelIndex = this.channels.map((e) => e.id).indexOf(id); + if (channelIndex !== -1) { + this.channels.splice(channelIndex, 1); + } + return this; + } - sort() { - this.channels.sort((a, b) => { - if (a.name < b.name) { - return -1; - } else if (a.name > b.name) { - return 1; - } - return 0; - }); - } + sort() { + this.channels.sort((a, b) => { + if (a.name < b.name) { + return -1; + } else if (a.name > b.name) { + return 1; + } + return 0; + }); + } } diff --git a/ui/lib/store/logins.js b/ui/lib/store/logins.js index 5b45206..d449b3a 100644 --- a/ui/lib/store/logins.js +++ b/ui/lib/store/logins.js @@ -1,22 +1,22 @@ export class Logins { - constructor() { - this.logins = {}; - } + constructor() { + this.logins = {}; + } - addLogin(id, name) { - this.logins[id] = name; - return this; - } + addLogin(id, name) { + this.logins[id] = name; + return this; + } - setLogins(logins) { - this.logins = {}; - for (let { id, name } of logins) { - this.addLogin(id, name); - } - return this; - } + setLogins(logins) { + this.logins = {}; + for (let { id, name } of logins) { + this.addLogin(id, name); + } + return this; + } - get(id) { - return this.logins[id]; - } + get(id) { + return this.logins[id]; + } } diff --git a/ui/lib/store/messages.js b/ui/lib/store/messages.js index 884b296..62c567a 100644 --- a/ui/lib/store/messages.js +++ b/ui/lib/store/messages.js @@ -1,40 +1,40 @@ export class Messages { - constructor() { - this.channels = {}; - } + constructor() { + this.channels = {}; + } - inChannel(channel) { - return (this.channels[channel] = this.channels[channel] || []); - } + inChannel(channel) { + return (this.channels[channel] = this.channels[channel] || []); + } - addMessage(channel, id, at, sender, body) { - this.updateChannel(channel, (messages) => [...messages, { id, at, sender, body }]); - return this; - } + addMessage(channel, id, at, sender, body) { + this.updateChannel(channel, (messages) => [...messages, { id, at, sender, body }]); + return this; + } - setMessages(messages) { - this.channels = {}; - for (let { channel, id, at, sender, body } of messages) { - this.inChannel(channel).push({ id, at, sender, body }); - } - return this; - } + setMessages(messages) { + this.channels = {}; + for (let { channel, id, at, sender, body } of messages) { + this.inChannel(channel).push({ id, at, sender, body }); + } + return this; + } - deleteMessage(message) { - for (let channel in this.channels) { - this.updateChannel(channel, (messages) => messages.filter((msg) => msg.id != message)); - } - return this; - } + deleteMessage(message) { + for (let channel in this.channels) { + this.updateChannel(channel, (messages) => messages.filter((msg) => msg.id != message)); + } + return this; + } - deleteChannel(id) { - delete this.channels[id]; - return this; - } + deleteChannel(id) { + delete this.channels[id]; + return this; + } - updateChannel(channel, callback) { - let messages = callback(this.inChannel(channel)); - messages.sort((a, b) => a.at - b.at); - this.channels[channel] = messages; - } + updateChannel(channel, callback) { + let messages = callback(this.inChannel(channel)); + messages.sort((a, b) => a.at - b.at); + this.channels[channel] = messages; + } } |
