From d36efbb1378ca1d6bf3b3c20391d711c00da4761 Mon Sep 17 00:00:00 2001 From: Kit La Touche Date: Fri, 29 Nov 2024 14:29:12 -0500 Subject: Rename and modify channels store I tried to have a custom class for Channel objects, but Svelte's automatic proxy logic works only on bare objects, as far as I could tell. So that broke everything. I resorted to a function that would build the bare objects, but we still lack methods that I think would make life easier ("touch last read" etc). --- ui/lib/store.js | 2 +- ui/lib/store/channels.js | 36 ------------------------ ui/lib/store/channels.svelte.js | 62 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 37 deletions(-) delete mode 100644 ui/lib/store/channels.js create mode 100644 ui/lib/store/channels.svelte.js (limited to 'ui/lib') diff --git a/ui/lib/store.js b/ui/lib/store.js index 3b20e05..47ebbc2 100644 --- a/ui/lib/store.js +++ b/ui/lib/store.js @@ -1,5 +1,5 @@ import { writable } from 'svelte/store'; -import { Channels } from '$lib/store/channels'; +import { Channels } from '$lib/store/channels.svelte.js'; import { Messages } from '$lib/store/messages.svelte.js'; import { Logins } from '$lib/store/logins'; diff --git a/ui/lib/store/channels.js b/ui/lib/store/channels.js deleted file mode 100644 index 37dc673..0000000 --- a/ui/lib/store/channels.js +++ /dev/null @@ -1,36 +0,0 @@ -export class Channels { - constructor() { - this.channels = []; - } - - setChannels(channels) { - this.channels = [...channels]; - 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; - } - - 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/channels.svelte.js b/ui/lib/store/channels.svelte.js new file mode 100644 index 0000000..8919be0 --- /dev/null +++ b/ui/lib/store/channels.svelte.js @@ -0,0 +1,62 @@ +import { DateTime } from 'luxon'; +const EPOCH_STRING = "1970-01-01T00:00:00Z"; + +// For reasons unclear to me, a straight up class definition with a constructor +// doesn't seem to work, reactively. So we resort to this. +// Owen suggests that this sentence in the Svelte docs should make the reason +// clear: +// > If $state is used with an array or a simple object, the result is a deeply +// > reactive state proxy. +// Emphasis on "simple object". +// --Kit +function makeChannelObject({ id, name, draft = '', lastReadAt = null, scrollPosition = null }) { + return { + id, + name, + lastReadAt: lastReadAt || DateTime.fromISO(EPOCH_STRING), + draft, + scrollPosition, + }; +} + +export class Channels { + channels = $state([]); + + getChannel(channelId) { + return this.channels.filter((ch) => ch.id === channelId)[0] || null; + } + + setChannels(channels) { + this.channels = channels.map(makeChannelObject); + this.sort(); + return this; + } + + addChannel(id, name) { + this.channels = [ + ...this.channels, + makeChannelObject({ 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; + } + + sort() { + this.channels.sort((a, b) => { + if (a.name < b.name) { + return -1; + } else if (a.name > b.name) { + return 1; + } + return 0; + }); + } +} -- cgit v1.2.3 From b85dcad22f89de7ec8d07ab1776fa2f51a08ae24 Mon Sep 17 00:00:00 2001 From: Kit La Touche Date: Fri, 29 Nov 2024 14:32:00 -0500 Subject: Use Luxon dates on Message store and component This includes jamming the "at" of a message into a data- attribute on the Message component, so that it can later be used by parent components via Plain Old Javascript and the .dataset attribute of an HTML node. --- ui/lib/components/Message.svelte | 12 ++++++++++-- ui/lib/store/messages.svelte.js | 7 ++++--- 2 files changed, 14 insertions(+), 5 deletions(-) (limited to 'ui/lib') diff --git a/ui/lib/components/Message.svelte b/ui/lib/components/Message.svelte index 1663696..5673248 100644 --- a/ui/lib/components/Message.svelte +++ b/ui/lib/components/Message.svelte @@ -1,4 +1,5 @@ -
+
- {at} + {atFormatted} {#if editable} {/if} diff --git a/ui/lib/store/messages.svelte.js b/ui/lib/store/messages.svelte.js index 0ceba54..ba4c895 100644 --- a/ui/lib/store/messages.svelte.js +++ b/ui/lib/store/messages.svelte.js @@ -1,17 +1,18 @@ +import { DateTime } from 'luxon'; import { marked } from 'marked'; import DOMPurify from 'dompurify'; const RUN_COALESCE_MAX_INTERVAL = 10 /* min */ * 60 /* sec */ * 1000; /* ms */ export class Messages { - channels = $state({}); + channels = $state({}); // Mapping inChannel(channel) { - return this.channels[channel]; + return this.channels[channel] || []; } addMessage(channel, id, { at, sender, body }) { - let parsedAt = new Date(at); + let parsedAt = DateTime.fromISO(at); let renderedBody = DOMPurify.sanitize(marked.parse(body, { breaks: true })); const message = { id, at: parsedAt, body, renderedBody }; -- cgit v1.2.3 From 9c4f37d3853dbc114c305dbbb45f2cb23cf8f4e0 Mon Sep 17 00:00:00 2001 From: Kit La Touche Date: Fri, 29 Nov 2024 14:33:13 -0500 Subject: Style Channels differently when they have unreads I dunno, I like the fleuron. Maybe it's too twee? --- ui/lib/components/Channel.svelte | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'ui/lib') diff --git a/ui/lib/components/Channel.svelte b/ui/lib/components/Channel.svelte index e84c6d0..01b1c87 100644 --- a/ui/lib/components/Channel.svelte +++ b/ui/lib/components/Channel.svelte @@ -1,10 +1,14 @@
  • - # + {#if hasUnreads} + + {:else} + + {/if} {name}
  • -- cgit v1.2.3 From f8ecf97e66b999882bfa8750af111e9cf73ea89c Mon Sep 17 00:00:00 2001 From: Kit La Touche Date: Fri, 29 Nov 2024 14:33:52 -0500 Subject: Prefer camelCase to snake_case in argument names Even when they get mapped to snake_case searchParams. --- ui/lib/apiServer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'ui/lib') diff --git a/ui/lib/apiServer.js b/ui/lib/apiServer.js index fee1a81..e52daff 100644 --- a/ui/lib/apiServer.js +++ b/ui/lib/apiServer.js @@ -54,9 +54,9 @@ export async function acceptInvite(inviteId, username, password) { return apiServer.post(`/invite/${inviteId}`, data); } -export function subscribeToEvents(resume_point) { +export function subscribeToEvents(resumePoint) { const eventsUrl = new URL('/api/events', window.location); - eventsUrl.searchParams.append('resume_point', resume_point); + eventsUrl.searchParams.append('resume_point', resumePoint); 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 -- cgit v1.2.3