Skip to content

Commit ceb2c84

Browse files
committed
Add message pending status for sent messages
- Optimistically display sent messages with `addPendingMessage` - Enable `labeled-response` capability for tracking message sent status - Add `addSystemMessage` for local-only messages, such as `/help` responses - Fix reactivity for the active channel and grouped messages - Updated tsconfig to target ES2024 since we can support polyfills as needed
1 parent 587269b commit ceb2c84

12 files changed

Lines changed: 227 additions & 61 deletions

File tree

src/app/types/modules/irc-framework/irc-framework.d.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,11 @@ declare module 'irc-framework' {
6262

6363
changeNick(nick: string): void;
6464

65-
sendMessage(commandName: string, target: string, message: string): void;
65+
sendMessage(commandName: string, target: string, message: string, tags?: Tags): void;
6666

67-
say(target: string, message: string): void;
68-
69-
notice(target: string, message: string): void;
67+
say(target: string, message: string, tags?: Tags): void;
68+
notice(target: string, message: string, tags?: Tags): void;
69+
tagmsg(target: string, tags?: Tags): void;
7070

7171
join(channel: string, key?: string): void;
7272

@@ -90,9 +90,9 @@ declare module 'irc-framework' {
9090

9191
setTopic(channel: string, newTopic: string): void;
9292

93-
ctcpRequest(target: string, type: string /* , ...params: Array<any> */): void;
93+
ctcpRequest(target: string, type: string, ...params: string[]): void;
9494

95-
ctcpResponse(target: string, type: string /* , params: Array<any> */): void;
95+
ctcpResponse(target: string, type: string, ...params: string[]): void;
9696

9797
action(target: string, message: string): string[];
9898

@@ -265,9 +265,15 @@ declare module 'irc-framework' {
265265
}
266266

267267
export type Tags = Partial<{
268+
/** See https://ircv3.net/specs/extensions/account-tag */
268269
account: string;
270+
/** See https://ircv3.net/specs/extensions/batch */
269271
batch: string;
272+
/** See https://ircv3.net/specs/extensions/labeled-response */
273+
label: string;
274+
/** See https://ircv3.net/specs/extensions/message-ids */
270275
msgid: string;
276+
/** See https://ircv3.net/specs/extensions/server-time */
271277
time: string;
272278
[tagName: string]: string;
273279
}>;

src/components/ChatApp.vue

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,39 @@
11
<template>
22
<DraggableWindow>
3+
<template v-slot:header>
4+
<span>{{ channel.activeChannelName }}</span>
5+
</template>
36
<template v-slot:main>
47
<OnlineUsers />
5-
<Transition name="fade"> </Transition>
8+
<ChannelMessages />
9+
<ChatInput />
610
</template>
711
</DraggableWindow>
812
</template>
913

1014
<script setup lang="ts">
11-
import DraggableWindow from './DraggableWindow.vue';
12-
import OnlineUsers from './user/OnlineUsers.vue';
13-
import { useChannelStore, useIrcStore } from '#stores';
1415
import { AUTO_JOIN_CHANNELS } from '#constants';
16+
import { useChannelStore, useIrcStore } from '#stores';
1517
import { ref, watch } from 'vue';
18+
import ChannelMessages from './channel/ChannelMessages.vue';
19+
import DraggableWindow from './layout/DraggableWindow.vue';
20+
import OnlineUsers from './user/OnlineUsers.vue';
21+
import ChatInput from './chat/ChatInput.vue';
1622
// TODO: Re-implement resizing tracking
1723
1824
const irc = useIrcStore();
19-
const channels = useChannelStore();
25+
const channel = useChannelStore();
2026
const joined = ref(false);
2127
2228
watch([joined, () => irc.connectionStatus], ([hasJoined, connectionStatus]) => {
2329
if (hasJoined || connectionStatus !== 'connected') {
2430
return;
2531
}
2632
27-
for (const channel of AUTO_JOIN_CHANNELS) {
28-
channels.joinChannel(channel);
33+
for (const channelName of AUTO_JOIN_CHANNELS) {
34+
channel.joinChannel(channelName);
2935
}
30-
channels.changeActiveChannel(AUTO_JOIN_CHANNELS[0]);
36+
channel.changeActiveChannel(AUTO_JOIN_CHANNELS[0]);
3137
});
3238
</script>
3339

src/components/channel/ChannelMessages.vue

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,10 @@
55
<script setup lang="ts">
66
import { useMessageGroups } from '#composables/useMessageGroups.ts';
77
import { useChannelStore } from '#stores';
8-
import { computed } from 'vue';
98
import MessageGroup from './MessageGroup.vue';
109
11-
const { activeChannel } = useChannelStore();
12-
const messages = computed(() => activeChannel.messages);
13-
const { messageGroups } = useMessageGroups(messages);
10+
const channel = useChannelStore();
11+
const { messageGroups } = useMessageGroups(() => channel.activeMessages);
1412
</script>
1513

1614
<style scoped></style>

src/components/chat/ChatInput.vue

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<template>
2+
<div class="chat-input-container p3 border-top bg-light">
3+
<BForm @submit.prevent="onSubmit">
4+
<BInputGroup size="lg">
5+
<BFormInput
6+
v-model="inputBuffer"
7+
autofocus
8+
autocomplete="off"
9+
:placeholder="placeholder"
10+
aria-label="Chat message"
11+
/>
12+
<BButton
13+
type="submit"
14+
variant="primary"
15+
class="px-4"
16+
:disabled="!inputBuffer.trim()"
17+
></BButton>
18+
</BInputGroup>
19+
</BForm>
20+
</div>
21+
</template>
22+
23+
<script setup lang="ts">
24+
import { useChannelStore, useChatStore } from '#stores';
25+
import { BButton, BForm, BFormInput, BInputGroup } from 'bootstrap-vue-next';
26+
import { computed, ref } from 'vue';
27+
28+
const channel = useChannelStore();
29+
const chat = useChatStore();
30+
const inputBuffer = ref('');
31+
const placeholder = computed(() => `Message ${channel.activeChannelName}`);
32+
33+
function onSubmit() {
34+
const text = inputBuffer.value.trim();
35+
if (!text) {
36+
return;
37+
}
38+
39+
const isCommand = text.startsWith('/');
40+
41+
if (isCommand) {
42+
// TODO: Add command handler
43+
} else {
44+
chat.say(text);
45+
}
46+
47+
inputBuffer.value = '';
48+
}
49+
</script>
50+
51+
<style scoped>
52+
.chat-input-container {
53+
min-height: 5em;
54+
}
55+
</style>

src/composables/useMessageGroups.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import type { Message } from '#models';
2-
import { parseNick } from '#utils';
32
import { computed, toValue, type MaybeRefOrGetter, type DeepReadonly } from 'vue';
43

5-
export interface MessageGroup extends Pick<Message, 'nick' | 'type'> {
4+
export interface MessageGroup extends Pick<Message, 'nick' | 'username' | 'type'> {
65
id: string;
76
messages: Message[];
87
}
@@ -21,13 +20,14 @@ export function useMessageGroups(messages: MaybeRefOrGetter<Message[] | DeepRead
2120
!prevGroup ||
2221
message.type !== 'privmsg' ||
2322
prevGroup.type !== 'privmsg' ||
24-
parseNick(prevGroup.nick) !== parseNick(message.nick)
23+
prevGroup.username !== message.username
2524
) {
2625
groups.push({
27-
id: `group-${message.time}-${message.nick}`,
26+
id: `group-${message.username}-${message.time}`,
2827
type: message.type,
2928
messages: [message],
3029
nick: message.nick,
30+
username: message.username,
3131
});
3232
} else {
3333
prevGroup.messages.push(message);

src/models/message.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
import type { Tags } from 'irc-framework';
22

33
export type MessageType = 'privmsg' | 'action' | 'notice' | 'system';
4+
export type MessageStatus = 'pending' | 'sent' | 'error';
45

56
export interface Message {
67
id: string;
78
time: number;
89
starred: boolean;
910
message: string;
1011
target: string;
12+
username: string;
1113
nick: string;
1214
type: MessageType;
1315
tags: Tags;
16+
17+
status?: MessageStatus;
18+
pendingId?: string;
1419
}

0 commit comments

Comments
 (0)