summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ui/app.css6
-rw-r--r--ui/lib/apiServer.js7
-rw-r--r--ui/lib/components/ChannelList.svelte13
-rw-r--r--ui/lib/components/Conversation.svelte (renamed from ui/lib/components/Channel.svelte)0
-rw-r--r--ui/lib/components/ConversationList.svelte17
-rw-r--r--ui/lib/components/CreateConversationForm.svelte (renamed from ui/lib/components/CreateChannelForm.svelte)6
-rw-r--r--ui/lib/outbox.svelte.js24
-rw-r--r--ui/lib/session.svelte.js16
-rw-r--r--ui/lib/state/local/channels.svelte.js118
-rw-r--r--ui/lib/state/local/conversations.svelte.js119
-rw-r--r--ui/lib/state/remote/conversations.svelte.js (renamed from ui/lib/state/remote/channels.svelte.js)10
-rw-r--r--ui/lib/state/remote/state.svelte.js8
-rw-r--r--ui/routes/(app)/+layout.svelte46
-rw-r--r--ui/routes/(app)/+page.svelte4
-rw-r--r--ui/routes/(app)/c/[conversation]/+page.svelte28
-rw-r--r--ui/styles/active-conversation.css (renamed from ui/styles/active-channel.css)2
-rw-r--r--ui/styles/overscroll.css11
-rw-r--r--ui/styles/sidebar.css12
-rw-r--r--ui/styles/variables.css4
-rw-r--r--ui/tests/lib/components/CreateChannelForm.svelte.test.js18
-rw-r--r--ui/tests/lib/components/MessageInput.svelte.test.js2
21 files changed, 243 insertions, 228 deletions
diff --git a/ui/app.css b/ui/app.css
index 79f36eb..34a74b7 100644
--- a/ui/app.css
+++ b/ui/app.css
@@ -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">&#x2795;</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,