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
110
111
112
113
114
115
116
117
118
|
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;
}, {});
}
|