1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
|
<script>
import { getContext, onDestroy, onMount } from 'svelte';
import { browser } from '$app/environment';
import { goto, afterNavigate } from '$app/navigation';
import { page } from '$app/state';
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';
let gesture = null;
const { data, children } = $props();
const { session } = data;
onMount(session.begin.bind(session));
onDestroy(session.end.bind(session));
let pageContext = getContext('page');
let channel = $derived(page.params.channel);
let rawChannels = $derived(session.channels);
let rawChannelsMeta = $derived(session.local.all);
let rawMessages = $derived(session.messages);
function enrichChannels(channels, channelsMeta, messages) {
const enrichedChannels = [];
for (const ch of channels.values()) {
const channelMessages = messages.filter((message) => message.channel === ch.id);
const lastMessage = channelMessages.slice(-1)[0];
const lastMessageAt = lastMessage?.at;
const lastReadAt = channelsMeta.get(ch.id)?.lastReadAt;
const hasUnreads = lastReadAt === null || lastMessageAt > lastReadAt;
enrichedChannels.push({
...ch,
hasUnreads
});
}
return enrichedChannels;
}
const enrichedChannels = $derived(enrichChannels(rawChannels, rawChannelsMeta, rawMessages));
function setUpGestures() {
if (!browser) {
// Meaningless if we're not in a browser, so...
return;
}
gesture = new TinyGesture(window);
gesture.on('swiperight', () => {
pageContext.showMenu = true;
});
gesture.on('swipeleft', () => {
pageContext.showMenu = false;
});
}
onMount(setUpGestures);
onDestroy(async () => {
if (gesture !== null) {
gesture.destroy();
}
});
const STORE_KEY_LAST_ACTIVE = 'pilcrow:lastActiveChannel';
function getLastActiveChannel() {
return browser && JSON.parse(localStorage.getItem(STORE_KEY_LAST_ACTIVE));
}
function setLastActiveChannel(channelId) {
browser && localStorage.setItem(STORE_KEY_LAST_ACTIVE, JSON.stringify(channelId));
}
afterNavigate(() => {
const lastActiveChannel = getLastActiveChannel();
const inRoot = page.url.pathname === '/';
if (inRoot && lastActiveChannel) {
goto(`/ch/${lastActiveChannel}`);
} else if (channel) {
setLastActiveChannel(channel || null);
}
});
async function createChannel(name) {
await api.createChannel(name);
}
</script>
<svelte:head>
<!-- TODO: unread count? -->
<title>pilcrow</title>
</svelte:head>
<div id="interface">
<nav id="sidebar" data-expanded={pageContext.showMenu}>
<ChannelList active={channel} channels={enrichedChannels} />
<div class="create-channel">
<CreateChannelForm {createChannel} />
</div>
</nav>
<main>
{@render children?.()}
</main>
</div>
|