diff options
| -rw-r--r-- | eslint.config.js | 24 | ||||
| -rw-r--r-- | ui/lib/components/ActiveChannel.svelte | 27 | ||||
| -rw-r--r-- | ui/lib/store.js | 2 | ||||
| -rw-r--r-- | ui/lib/store/messages.js | 40 | ||||
| -rw-r--r-- | ui/lib/store/messages.svelte.js | 59 | ||||
| -rw-r--r-- | ui/routes/(app)/ch/[channel]/+page.svelte | 4 |
6 files changed, 67 insertions, 89 deletions
diff --git a/eslint.config.js b/eslint.config.js index 2eeebcc..7d23626 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,23 +1,3 @@ -import js from '@eslint/js'; -import svelte from 'eslint-plugin-svelte'; import prettier from 'eslint-config-prettier'; -import globals from 'globals'; - -/** @type {import('eslint').Linter.Config[]} */ -export default [ - js.configs.recommended, - ...svelte.configs['flat/recommended'], - prettier, - ...svelte.configs['flat/prettier'], - { - languageOptions: { - globals: { - ...globals.browser, - ...globals.node - } - } - }, - { - ignores: ['build/', '.svelte-kit/', 'dist/'] - } -]; +import svelte from 'eslint-plugin-svelte'; +export default [prettier, ...svelte.configs['flat/prettier']]; diff --git a/ui/lib/components/ActiveChannel.svelte b/ui/lib/components/ActiveChannel.svelte index a4ccd24..f939dbd 100644 --- a/ui/lib/components/ActiveChannel.svelte +++ b/ui/lib/components/ActiveChannel.svelte @@ -1,34 +1,11 @@ <script> - import { messages } from '$lib/store'; import MessageRun from './MessageRun.svelte'; - 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]; - } - - chunk = [x]; - key = newKey; - } else { - chunk.push(x); - } - } - if (chunk !== undefined) { - yield [key, chunk]; - } - } + let { messageRuns } = $props(); </script> <div class="container"> - {#each chunkBy(messageList, (msg) => msg.sender) as [sender, messages]} + {#each messageRuns as { sender, messages }} <div> <MessageRun {sender} {messages} /> </div> diff --git a/ui/lib/store.js b/ui/lib/store.js index ae17ffa..3b20e05 100644 --- a/ui/lib/store.js +++ b/ui/lib/store.js @@ -1,6 +1,6 @@ import { writable } from 'svelte/store'; import { Channels } from '$lib/store/channels'; -import { Messages } from '$lib/store/messages'; +import { Messages } from '$lib/store/messages.svelte.js'; import { Logins } from '$lib/store/logins'; export const currentUser = writable(null); diff --git a/ui/lib/store/messages.js b/ui/lib/store/messages.js deleted file mode 100644 index 62c567a..0000000 --- a/ui/lib/store/messages.js +++ /dev/null @@ -1,40 +0,0 @@ -export class Messages { - constructor() { - this.channels = {}; - } - - 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; - } - - 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; - } - - 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; - } -} diff --git a/ui/lib/store/messages.svelte.js b/ui/lib/store/messages.svelte.js new file mode 100644 index 0000000..1c59599 --- /dev/null +++ b/ui/lib/store/messages.svelte.js @@ -0,0 +1,59 @@ +const RUN_COALESCE_MAX_INTERVAL = 10 /* min */ * 60 /* sec */ * 1000; /* ms */ + +export class Messages { + channels = $state({}); + + inChannel(channel) { + return this.channels[channel]; + } + + addMessage(channel, id, at, sender, body) { + let parsedAt = new Date(at); + const message = { id, at: parsedAt, body }; + + let runs = (this.channels[channel] ||= []); + let currentRun = runs.slice(-1)[0]; + if (currentRun === undefined) { + currentRun = { sender, messages: [message] }; + runs.push(currentRun); + } else { + let lastMessage = currentRun.messages.slice(-1)[0]; + let newRun = + currentRun.sender !== sender || parsedAt - lastMessage.at > RUN_COALESCE_MAX_INTERVAL; + + if (newRun) { + currentRun = { sender, messages: [message] }; + runs.push(currentRun); + } else { + currentRun.messages.push(message); + } + } + + return this; + } + + setMessages(messages) { + this.channels = {}; + for (let { channel, id, at, sender, body } of messages) { + this.addMessage(channel, id, at, sender, body); + } + return this; + } + + deleteMessage(messageId) { + for (let channel in this.channels) { + this.channels[channel] = this.channels[channel] + .map(({ sender, messages }) => ({ + sender, + messages: messages.filter(({ id }) => id != messageId) + })) + .filter(({ messages }) => messages.length > 0); + } + return this; + } + + deleteChannel(id) { + delete this.channels[id]; + return this; + } +} diff --git a/ui/routes/(app)/ch/[channel]/+page.svelte b/ui/routes/(app)/ch/[channel]/+page.svelte index 49c1c29..0961665 100644 --- a/ui/routes/(app)/ch/[channel]/+page.svelte +++ b/ui/routes/(app)/ch/[channel]/+page.svelte @@ -2,12 +2,14 @@ import { page } from '$app/stores'; import ActiveChannel from '$lib/components/ActiveChannel.svelte'; import MessageInput from '$lib/components/MessageInput.svelte'; + import { messages } from '$lib/store'; let channel = $derived($page.params.channel); + let messageRuns = $derived($messages.inChannel(channel)); </script> <div class="active-channel"> - <ActiveChannel {channel} /> + <ActiveChannel {messageRuns} /> </div> <div class="create-message max-h-full"> <MessageInput {channel} /> |
