diff options
| author | Owen Jacobson <owen@grimoire.ca> | 2025-07-01 15:40:11 -0400 |
|---|---|---|
| committer | Owen Jacobson <owen@grimoire.ca> | 2025-07-03 22:43:44 -0400 |
| commit | 9b38cb1a62ede4900fde4ba47a7b065db329e994 (patch) | |
| tree | abf0b9d993ef03a53903aae03f375b78473952da /ui | |
| parent | 1cafeb5ec92c1dc4ad74fbed58b15a8ab2f3c0cf (diff) | |
Rename "channel" to "conversation" throughout the client.
Existing client state, stored in local storage, is migrated to new keys (that mention "conversation" instead of "channel" where appropriate) the first time the client loads.
Diffstat (limited to 'ui')
21 files changed, 243 insertions, 228 deletions
@@ -5,14 +5,14 @@ @import url('styles/app-bar.css'); @import url('styles/app-layout.css'); @import url('styles/sidebar.css'); -@import url('styles/active-channel.css'); +@import url('styles/active-conversation.css'); @import url('styles/messages.css'); @import url('styles/textarea.css'); @import url('styles/forms.css'); @import url('styles/invites.css'); body { - background-color: var(--colour-active-channel-bg); + background-color: var(--colour-active-conversation-bg); color: var(--dark-text); font-family: 'Roboto', sans-serif; } @@ -21,7 +21,7 @@ hr { width: 90%; } -.no-active-channel { +.no-active-conversation { display: table; height: 100%; width: 100%; diff --git a/ui/lib/apiServer.js b/ui/lib/apiServer.js index 1bca6f6..ac707a5 100644 --- a/ui/lib/apiServer.js +++ b/ui/lib/apiServer.js @@ -1,7 +1,6 @@ import axios from 'axios'; import * as r from './retry.js'; -import { timedDelay } from './retry.js'; export const apiServer = axios.create({ baseURL: '/api/', @@ -27,12 +26,12 @@ export async function changePassword(password, to) { return await apiServer.post('/password', { password, to }).catch(responseError); } -export async function createChannel(name) { +export async function createConversation(name) { return await apiServer.post('/conversations', { name }).catch(responseError); } -export async function postToChannel(channelId, body) { - return await apiServer.post(`/conversations/${channelId}`, { body }).catch(responseError); +export async function sendToConversation(conversationId, body) { + return await apiServer.post(`/conversations/${conversationId}`, { body }).catch(responseError); } export async function deleteMessage(messageId) { diff --git a/ui/lib/components/ChannelList.svelte b/ui/lib/components/ChannelList.svelte deleted file mode 100644 index 51dd6cf..0000000 --- a/ui/lib/components/ChannelList.svelte +++ /dev/null @@ -1,13 +0,0 @@ -<script> - import Channel from './Channel.svelte'; - - let { channels, active } = $props(); -</script> - -<nav class="list-nav"> - <ul> - {#each channels as channel} - <Channel {...channel} active={active === channel.id} /> - {/each} - </ul> -</nav> diff --git a/ui/lib/components/Channel.svelte b/ui/lib/components/Conversation.svelte index 9004e50..9004e50 100644 --- a/ui/lib/components/Channel.svelte +++ b/ui/lib/components/Conversation.svelte diff --git a/ui/lib/components/ConversationList.svelte b/ui/lib/components/ConversationList.svelte new file mode 100644 index 0000000..71332e0 --- /dev/null +++ b/ui/lib/components/ConversationList.svelte @@ -0,0 +1,17 @@ +<script> + import Conversation from './Conversation.svelte'; + + let { conversations, active } = $props(); + + function isActive(conversation) { + return active === conversation.id; + } +</script> + +<nav class="list-nav"> + <ul> + {#each conversations as conversation} + <Conversation {...conversation} active={isActive(conversation)} /> + {/each} + </ul> +</nav> diff --git a/ui/lib/components/CreateChannelForm.svelte b/ui/lib/components/CreateConversationForm.svelte index 471c2b7..e390a78 100644 --- a/ui/lib/components/CreateChannelForm.svelte +++ b/ui/lib/components/CreateConversationForm.svelte @@ -1,5 +1,5 @@ <script> - let { createChannel = async (name) => {} } = $props(); + let { createConversation = async (name) => {} } = $props(); let name = $state(''); let disabled = $state(false); @@ -8,7 +8,7 @@ event.preventDefault(); disabled = true; try { - await createChannel(name); + await createConversation(name); event.target.reset(); } finally { disabled = false; @@ -17,6 +17,6 @@ </script> <form {onsubmit}> - <input type="text" placeholder="create channel" bind:value={name} {disabled} /> + <input type="text" placeholder="start a conversation" bind:value={name} {disabled} /> <button type="submit">➕</button> </form> diff --git a/ui/lib/outbox.svelte.js b/ui/lib/outbox.svelte.js index 183f8ff..c4e2324 100644 --- a/ui/lib/outbox.svelte.js +++ b/ui/lib/outbox.svelte.js @@ -4,9 +4,9 @@ import * as msg from './state/remote/messages.svelte.js'; import * as api from './apiServer.js'; import * as md from './markdown.js'; -class PostToChannel { - constructor(channel, body) { - this.channel = channel; +class SendToConversation { + constructor(conversation, body) { + this.conversation = conversation; this.body = body; this.at = DateTime.now(); this.renderedBody = md.render(body); @@ -16,7 +16,7 @@ class PostToChannel { return { id: null, at: this.at, - channel: this.channel, + conversation: this.conversation, sender, body: this.body, renderedBody: this.renderedBody, @@ -24,7 +24,7 @@ class PostToChannel { } async send() { - return await api.retry(() => api.postToChannel(this.channel, this.body)); + return await api.retry(() => api.sendToConversation(this.conversation, this.body)); } } @@ -38,19 +38,19 @@ class DeleteMessage { } } -class CreateChannel { +class CreateConversation { constructor(name) { this.name = name; } async send() { - return await api.retry(() => api.createChannel(this.name)); + return await api.retry(() => api.createConversation(this.name)); } } export class Outbox { pending = $state([]); - messages = $derived(this.pending.filter((operation) => operation instanceof PostToChannel)); + messages = $derived(this.pending.filter((operation) => operation instanceof SendToConversation)); deleted = $derived(this.pending.filter((operation) => operation instanceof DeleteMessage)); static empty() { @@ -66,12 +66,12 @@ export class Outbox { this.start(); } - createChannel(name) { - this.enqueue(new CreateChannel(name)); + createConversation(name) { + this.enqueue(new CreateConversation(name)); } - postToChannel(channel, body) { - this.enqueue(new PostToChannel(channel, body)); + sendToConversation(conversationId, body) { + this.enqueue(new SendToConversation(conversationId, body)); } deleteMessage(messageId) { diff --git a/ui/lib/session.svelte.js b/ui/lib/session.svelte.js index 0c73e00..4430e8a 100644 --- a/ui/lib/session.svelte.js +++ b/ui/lib/session.svelte.js @@ -4,11 +4,11 @@ import { goto } from '$app/navigation'; import * as api from './apiServer.js'; import * as r from './state/remote/state.svelte.js'; -import * as l from './state/local/channels.svelte.js'; +import * as l from './state/local/conversations.svelte.js'; import { Watchdog } from './watchdog.js'; import { DateTime } from 'luxon'; -class Channel { +class Conversation { static fromRemote({ at, id, name }, messages, meta) { const sentAt = messages .filter((message) => message.conversation === id) @@ -17,7 +17,7 @@ class Channel { const lastReadAt = meta.get(id)?.lastReadAt; const hasUnreads = lastReadAt === undefined || lastEventAt > lastReadAt; - return new Channel({ at, id, name, hasUnreads }); + return new Conversation({ at, id, name, hasUnreads }); } constructor({ at, id, name, hasUnreads }) { @@ -58,9 +58,9 @@ class Session { messages = $derived( this.remote.messages.all.map((message) => Message.fromRemote(message, this.users)), ); - channels = $derived( - this.remote.channels.all.map((channel) => - Channel.fromRemote(channel, this.messages, this.local.all), + conversations = $derived( + this.remote.conversations.all.map((conversation) => + Conversation.fromRemote(conversation, this.messages, this.local.all), ), ); @@ -71,7 +71,7 @@ class Session { heartbeat, events, }); - const local = l.Channels.fromLocalStorage(); + const local = l.Conversations.fromLocalStorage(); return new Session(remote, local); } @@ -109,7 +109,7 @@ class Session { onMessage(message) { const event = JSON.parse(message.data); this.remote.onEvent(event); - this.local.retainChannels(this.remote.channels.all); + this.local.retainConversations(this.remote.conversations.all); this.watchdog.reset(this.heartbeatMillis()); } diff --git a/ui/lib/state/local/channels.svelte.js b/ui/lib/state/local/channels.svelte.js deleted file mode 100644 index 669aa1e..0000000 --- a/ui/lib/state/local/channels.svelte.js +++ /dev/null @@ -1,118 +0,0 @@ -import { DateTime } from 'luxon'; -import { SvelteMap } from 'svelte/reactivity'; - -import * as iter from '$lib/iterator.js'; - -export const STORE_KEY_CHANNELS_DATA = 'pilcrow:channelsData'; - -class Channel { - draft = $state(); - lastReadAt = $state(null); - scrollPosition = $state(null); - - static fromStored({ draft, lastReadAt, scrollPosition }) { - return new Channel({ - draft, - lastReadAt: lastReadAt == null ? null : DateTime.fromISO(lastReadAt), - scrollPosition, - }); - } - - constructor({ draft = '', lastReadAt = null, scrollPosition = null } = {}) { - this.draft = draft; - this.lastReadAt = lastReadAt; - this.scrollPosition = scrollPosition; - } - - toStored() { - const { draft, lastReadAt, scrollPosition } = this; - return { - draft, - lastReadAt: lastReadAt?.toISO(), - scrollPosition, - }; - } -} - -export class Channels { - // Store channelId -> { draft = '', lastReadAt = null, scrollPosition = null } - all = $state(); - - static fromLocalStorage() { - const stored = localStorage.getItem(STORE_KEY_CHANNELS_DATA); - if (stored !== null) { - return Channels.fromStored(JSON.parse(stored)); - } - return Channels.empty(); - } - - static fromStored(stored) { - const loaded = Object.keys(stored).map((channelId) => [ - channelId, - Channel.fromStored(stored[channelId]), - ]); - const all = new SvelteMap(loaded); - return new Channels({ all }); - } - - static empty() { - return new Channels({ all: new SvelteMap() }); - } - - constructor({ all }) { - this.all = all; - } - - channel(channelId) { - let channel = this.all.get(channelId); - if (channel === undefined) { - channel = new Channel(); - this.all.set(channelId, channel); - } - return channel; - } - - updateLastReadAt(channelId, at) { - const channel = this.channel(channelId); - // Do it this way, rather than with Math.max tricks, to avoid assignment - // when we don't need it, to minimize reactive changes: - if (channel.lastReadAt === null || at > channel.lastReadAt) { - channel.lastReadAt = at; - this.save(); - } - } - - retainChannels(channels) { - const channelIds = channels.map((channel) => channel.id); - const retain = new Set(channelIds); - for (const channelId of Array.from(this.all.keys())) { - if (!retain.has(channelId)) { - this.all.delete(channelId); - } - } - this.save(); - } - - toStored() { - return iter.reduce( - this.all.entries(), - (stored, [channelId, channel]) => ({ - ...stored, - [channelId]: channel.toStored(), - }), - {}, - ); - } - - save() { - let stored = this.toStored(); - localStorage.setItem(STORE_KEY_CHANNELS_DATA, JSON.stringify(stored)); - } -} - -function objectMap(object, mapFn) { - return Object.keys(object).reduce((result, key) => { - result[key] = mapFn(object[key]); - return result; - }, {}); -} diff --git a/ui/lib/state/local/conversations.svelte.js b/ui/lib/state/local/conversations.svelte.js new file mode 100644 index 0000000..835c237 --- /dev/null +++ b/ui/lib/state/local/conversations.svelte.js @@ -0,0 +1,119 @@ +import { DateTime } from 'luxon'; +import { SvelteMap } from 'svelte/reactivity'; + +import * as iter from '$lib/iterator.js'; + +// Conversations were called "channels" in previous iterations. Support loading +// data saved under that name to prevent the change from resetting everyone's +// unread tracking. +export const STORE_KEY_CHANNELS = 'pilcrow:channelsData'; +export const STORE_KEY_CONVERSATIONS = 'pilcrow:conversations'; + +class Conversation { + draft = $state(); + lastReadAt = $state(null); + scrollPosition = $state(null); + + static fromStored({ draft, lastReadAt, scrollPosition }) { + return new Conversation({ + draft, + lastReadAt: lastReadAt == null ? null : DateTime.fromISO(lastReadAt), + scrollPosition, + }); + } + + constructor({ draft = '', lastReadAt = null, scrollPosition = null } = {}) { + this.draft = draft; + this.lastReadAt = lastReadAt; + this.scrollPosition = scrollPosition; + } + + toStored() { + const { draft, lastReadAt, scrollPosition } = this; + return { + draft, + lastReadAt: lastReadAt?.toISO(), + scrollPosition, + }; + } +} + +export class Conversations { + // Store conversationId -> { draft = '', lastReadAt = null, scrollPosition = null } + all = $state(); + + static fromLocalStorage() { + const stored = + localStorage.getItem(STORE_KEY_CONVERSATIONS) ?? localStorage.getItem(STORE_KEY_CHANNELS); + if (stored !== null) { + return Conversations.fromStored(JSON.parse(stored)); + } + return Conversations.empty(); + } + + static fromStored(stored) { + const loaded = Object.keys(stored).map((conversationId) => [ + conversationId, + Conversation.fromStored(stored[conversationId]), + ]); + const all = new SvelteMap(loaded); + return new Conversations({ all }); + } + + static empty() { + return new Conversations({ all: new SvelteMap() }); + } + + constructor({ all }) { + this.all = all; + } + + conversation(conversationId) { + let conversation = this.all.get(conversationId); + if (conversation === undefined) { + conversation = new Conversation(); + this.all.set(conversationId, conversation); + } + return conversation; + } + + updateLastReadAt(conversationId, at) { + const conversation = this.conversation(conversationId); + // Do it this way, rather than with Math.max tricks, to avoid assignment + // when we don't need it, to minimize reactive changes: + if (conversation.lastReadAt === null || at > conversation.lastReadAt) { + conversation.lastReadAt = at; + this.save(); + } + } + + retainConversations(conversations) { + const conversationIds = conversations.map((conversation) => conversation.id); + const retain = new Set(conversationIds); + for (const conversationId of Array.from(this.all.keys())) { + if (!retain.has(conversationId)) { + this.all.delete(conversationId); + } + } + this.save(); + } + + toStored() { + return iter.reduce( + this.all.entries(), + (stored, [conversationId, conversation]) => ({ + ...stored, + [conversationId]: conversation.toStored(), + }), + {}, + ); + } + + save() { + let stored = this.toStored(); + localStorage.setItem(STORE_KEY_CONVERSATIONS, JSON.stringify(stored)); + // If we were able to save the data under `pilcrow:conversations`, then remove the old data; + // it is no longer needed and wouldn't be loaded anyways. + localStorage.removeItem(STORE_KEY_CHANNELS); + } +} diff --git a/ui/lib/state/remote/channels.svelte.js b/ui/lib/state/remote/conversations.svelte.js index 1e40075..79868f4 100644 --- a/ui/lib/state/remote/channels.svelte.js +++ b/ui/lib/state/remote/conversations.svelte.js @@ -1,8 +1,8 @@ import { DateTime } from 'luxon'; -class Channel { +class Conversation { static boot({ at, id, name }) { - return new Channel({ + return new Conversation({ at: DateTime.fromISO(at), id, name, @@ -16,14 +16,14 @@ class Channel { } } -export class Channels { +export class Conversations { all = $state([]); add({ at, id, name }) { - this.all.push(Channel.boot({ at, id, name })); + this.all.push(Conversation.boot({ at, id, name })); } remove(id) { - this.all = this.all.filter((channel) => channel.id !== id); + this.all = this.all.filter((conversation) => conversation.id !== id); } } diff --git a/ui/lib/state/remote/state.svelte.js b/ui/lib/state/remote/state.svelte.js index ffc88c6..3d65e4a 100644 --- a/ui/lib/state/remote/state.svelte.js +++ b/ui/lib/state/remote/state.svelte.js @@ -1,11 +1,11 @@ import { User, Users } from './users.svelte.js'; -import { Channels } from './channels.svelte.js'; +import { Conversations } from './conversations.svelte.js'; import { Messages } from './messages.svelte.js'; export class State { currentUser = $state(); users = $state(new Users()); - channels = $state(new Channels()); + conversations = $state(new Conversations()); messages = $state(new Messages()); static boot({ currentUser, heartbeat, resumePoint, events }) { @@ -50,12 +50,12 @@ export class State { onConversationCreated(event) { const { id, name } = event; - this.channels.add({ id, name }); + this.conversations.add({ id, name }); } onConversationDeleted(event) { const { id } = event; - this.channels.remove(id); + this.conversations.remove(id); } onUserEvent(event) { diff --git a/ui/routes/(app)/+layout.svelte b/ui/routes/(app)/+layout.svelte index e3272bc..658d966 100644 --- a/ui/routes/(app)/+layout.svelte +++ b/ui/routes/(app)/+layout.svelte @@ -7,9 +7,8 @@ import TinyGesture from 'tinygesture'; - import * as api from '$lib/apiServer.js'; - import ChannelList from '$lib/components/ChannelList.svelte'; - import CreateChannelForm from '$lib/components/CreateChannelForm.svelte'; + import ConversationList from '$lib/components/ConversationList.svelte'; + import CreateConversationForm from '$lib/components/CreateConversationForm.svelte'; let gesture = null; @@ -20,9 +19,9 @@ onDestroy(session.end.bind(session)); let pageContext = getContext('page'); - let channel = $derived(page.params.conversation); + let conversationId = $derived(page.params.conversation); - let channels = $derived(session.channels); + let conversations = $derived(session.conversations); function setUpGestures() { if (!browser) { @@ -46,28 +45,35 @@ } }); - const STORE_KEY_LAST_ACTIVE = 'pilcrow:lastActiveChannel'; + // Automatically migrate last-active-channel info now that we call them "conversations." + const STORE_KEY_LAST_ACTIVE = 'pilcrow:lastActiveConversation'; + const STORE_KEY_LAST_ACTIVE_CHANNEL = 'pilcrow:lastActiveChannel'; - function getLastActiveChannel() { - return browser && JSON.parse(localStorage.getItem(STORE_KEY_LAST_ACTIVE)); + function getLastActiveConversation() { + const stored = + localStorage.getItem(STORE_KEY_LAST_ACTIVE) ?? + localStorage.getItem(STORE_KEY_LAST_ACTIVE_CHANNEL); + return JSON.parse(stored); } - function setLastActiveChannel(channelId) { - browser && localStorage.setItem(STORE_KEY_LAST_ACTIVE, JSON.stringify(channelId)); + function setLastActiveConversation(conversationId) { + localStorage.setItem(STORE_KEY_LAST_ACTIVE, JSON.stringify(conversationId)); + // Once we've saved to the new key, we no longer need the old one. Clean it up. + localStorage.removeItem(STORE_KEY_LAST_ACTIVE_CHANNEL); } afterNavigate(() => { - const lastActiveChannel = getLastActiveChannel(); + const conversationId = getLastActiveConversation(); const inRoot = page.url.pathname === '/'; - if (inRoot && lastActiveChannel) { - goto(`/c/${lastActiveChannel}`); - } else if (channel) { - setLastActiveChannel(channel || null); + if (inRoot && conversationId) { + goto(`/c/${conversationId}`); + } else if (conversationId) { + setLastActiveConversation(conversationId || null); } }); - async function createChannel(name) { - outbox.createChannel(name); + async function createConversation(name) { + outbox.createConversation(name); } function onbeforeunload(event) { @@ -114,9 +120,9 @@ <div id="interface"> <nav id="sidebar" data-expanded={pageContext.showMenu}> - <ChannelList active={channel} {channels} /> - <div class="create-channel"> - <CreateChannelForm {createChannel} /> + <ConversationList active={conversationId} {conversations} /> + <div class="create-conversation"> + <CreateConversationForm {createConversation} /> </div> </nav> <main> diff --git a/ui/routes/(app)/+page.svelte b/ui/routes/(app)/+page.svelte index 007c5c6..1db0eb2 100644 --- a/ui/routes/(app)/+page.svelte +++ b/ui/routes/(app)/+page.svelte @@ -1,3 +1,3 @@ -<div class="no-active-channel"> - <span class="vertical-aligner"> Please select or create a channel. </span> +<div class="no-active-conversation"> + <span class="vertical-aligner">Please select or create a conversation.</span> </div> diff --git a/ui/routes/(app)/c/[conversation]/+page.svelte b/ui/routes/(app)/c/[conversation]/+page.svelte index 4d2cc86..e6cd845 100644 --- a/ui/routes/(app)/c/[conversation]/+page.svelte +++ b/ui/routes/(app)/c/[conversation]/+page.svelte @@ -8,14 +8,18 @@ const { data } = $props(); const { session, outbox } = data; - let activeChannel; + let activeConversation; - const channelId = $derived(page.params.conversation); - const channel = $derived(session.channels.find((channel) => channel.id === channelId)); + const conversationId = $derived(page.params.conversation); + const conversation = $derived( + session.conversations.find((conversation) => conversation.id === conversationId), + ); const messages = $derived( - session.messages.filter((message) => message.conversation === channelId), + session.messages.filter((message) => message.conversation === conversationId), + ); + const unsent = $derived( + outbox.messages.filter((message) => message.conversation === conversationId), ); - const unsent = $derived(outbox.messages.filter((message) => message.channel === channelId)); const deleted = $derived(outbox.deleted.map((message) => message.messageId)); const unsentSkeletons = $derived( unsent.map((message) => message.toSkeleton($state.snapshot(session.currentUser))), @@ -35,12 +39,12 @@ } function getLastVisibleMessage() { - if (activeChannel) { - const childElements = activeChannel.getElementsByClassName('message'); + if (activeConversation) { + const childElements = activeConversation.getElementsByClassName('message'); const lastInView = Array.from(childElements) .reverse() .find((el) => { - return inView(activeChannel, el); + return inView(activeConversation, el); }); return lastInView; } @@ -48,9 +52,9 @@ function setLastRead() { const lastInView = getLastVisibleMessage(); - const at = !!lastInView ? DateTime.fromISO(lastInView.dataset.at) : channel?.at; + const at = !!lastInView ? DateTime.fromISO(lastInView.dataset.at) : conversation?.at; if (!!at) { - session.local.updateLastReadAt(channelId, at); + session.local.updateLastReadAt(conversationId, at); } } @@ -79,7 +83,7 @@ } async function sendMessage(message) { - outbox.postToChannel(channelId, message); + outbox.sendToConversation(conversationId, message); } async function deleteMessage(id) { @@ -89,7 +93,7 @@ <svelte:window onkeydown={handleKeydown} /> -<div class="active-channel" {onscroll} bind:this={activeChannel}> +<div class="active-conversation" {onscroll} bind:this={activeConversation}> {#each messageRuns as { sender, ownMessage, messages }} <MessageRun {sender} diff --git a/ui/styles/active-channel.css b/ui/styles/active-conversation.css index d6a9b42..981862b 100644 --- a/ui/styles/active-channel.css +++ b/ui/styles/active-conversation.css @@ -1,4 +1,4 @@ -.active-channel { +.active-conversation { padding-left: 1rem; padding-right: 1rem; overflow: auto; diff --git a/ui/styles/overscroll.css b/ui/styles/overscroll.css index 8898f9a..a54235c 100644 --- a/ui/styles/overscroll.css +++ b/ui/styles/overscroll.css @@ -1,8 +1,9 @@ -/* This should help minimize swipe-to-go-back behaviour, enabling our -* swipe-to-reveal-channel-menu behaviour. It won't work in all cases; in iOS -* Safari, when swiping from the screen edge, the OS gets th event and -* handles it before the browser does. -*/ +/* + * This should help minimize swipe-to-go-back behaviour, enabling our + * swipe-to-reveal-conversation-menu behaviour. It won't work in all cases; in + * iOS Safari, when swiping from the screen edge, the OS gets the event and + * handles it before the browser does. + */ html, body { overscroll-behavior-x: none; diff --git a/ui/styles/sidebar.css b/ui/styles/sidebar.css index b825545..aa0d53b 100644 --- a/ui/styles/sidebar.css +++ b/ui/styles/sidebar.css @@ -1,4 +1,4 @@ -/* Sidebar and channel selector */ +/* Sidebar and conversation selector */ #sidebar { background-color: var(--colour-navbar-bg); } @@ -38,19 +38,19 @@ color: var(--colour-navbar-hover-text); } -/* create channel form */ -.create-channel { +/* create conversation form */ +.create-conversation { padding-left: 0.5rem; } -.create-channel form { +.create-conversation form { display: flex; flex-direction: row; justify-content: flex-start; align-items: stretch; } -.create-channel input { +.create-conversation input { padding: 0.5rem; border-radius: 0.5rem 0 0 0.5rem; border: 1px solid var(--colour-input-border); @@ -60,7 +60,7 @@ color: var(--colour-input-text); } -.create-channel button { +.create-conversation button { border-radius: 0 0.5rem 0.5rem 0; border: 1px solid var(--colour-input-border); background-color: var(--colour-input-bg); diff --git a/ui/styles/variables.css b/ui/styles/variables.css index 2758aa1..99705f2 100644 --- a/ui/styles/variables.css +++ b/ui/styles/variables.css @@ -40,8 +40,8 @@ --colour-input-border: color-mix(in srgb, var(--colour-input-bg) 50%, black); --colour-input-text: var(--dark-text); - /* Active channel */ - --colour-active-channel-bg: color-mix(in srgb, var(--colour-base) 25%, white); + /* Active conversation */ + --colour-active-conversation-bg: color-mix(in srgb, var(--colour-base) 25%, white); /* MessageRun */ --colour-message-run-self-bg: color-mix(in srgb, var(--colour-base) 30%, white); diff --git a/ui/tests/lib/components/CreateChannelForm.svelte.test.js b/ui/tests/lib/components/CreateChannelForm.svelte.test.js index 197cb6b..8c7b3fb 100644 --- a/ui/tests/lib/components/CreateChannelForm.svelte.test.js +++ b/ui/tests/lib/components/CreateChannelForm.svelte.test.js @@ -1,37 +1,37 @@ import { render, screen } from '@testing-library/svelte'; import userEvent from '@testing-library/user-event'; import { beforeEach, expect, test, describe, it, vi } from 'vitest'; -import CreateChannelForm from '$lib/components/CreateChannelForm.svelte'; +import CreateConversationForm from '$lib/components/CreateConversationForm.svelte'; const user = userEvent.setup(); const mocks = vi.hoisted(() => ({ - createChannel: vi.fn(), + createConversation: vi.fn(), })); -describe('CreateChannelForm', async () => { +describe('CreateConversationForm', async () => { beforeEach(async () => { - render(CreateChannelForm, { - createChannel: mocks.createChannel, + render(CreateConversationForm, { + createConversation: mocks.createConversation, }); }); - describe('creates channels', async () => { + describe('creates conversations', async () => { it('with a non-empty name', async () => { const input = screen.getByRole('textbox'); - await user.type(input, 'channel name'); + await user.type(input, 'conversation name'); const create = screen.getByRole('button'); await user.click(create); - expect(mocks.createChannel).toHaveBeenCalledExactlyOnceWith('channel name'); + expect(mocks.createConversation).toHaveBeenCalledExactlyOnceWith('conversation name'); }); it('with an empty name', async () => { const create = screen.getByRole('button'); await user.click(create); - expect(mocks.createChannel).toHaveBeenCalledExactlyOnceWith(''); + expect(mocks.createConversation).toHaveBeenCalledExactlyOnceWith(''); }); }); }); diff --git a/ui/tests/lib/components/MessageInput.svelte.test.js b/ui/tests/lib/components/MessageInput.svelte.test.js index c32ce11..b459737 100644 --- a/ui/tests/lib/components/MessageInput.svelte.test.js +++ b/ui/tests/lib/components/MessageInput.svelte.test.js @@ -9,7 +9,7 @@ const mocks = vi.hoisted(() => ({ sendMessage: vi.fn(), })); -describe('CreateChannelForm', async () => { +describe('MessageInput', async () => { beforeEach(async () => { render(MessageInput, { sendMessage: mocks.sendMessage, |
