thelounge/client/components/Chat.vue

282 lines
6.7 KiB
Vue
Raw Normal View History

<template>
<div id="chat-container" class="window" :data-current-channel="channel.name" lang="">
2018-07-08 15:42:54 +02:00
<div
id="chat"
:class="{
2022-05-23 11:27:10 +02:00
'hide-motd': store.state.settings.motd,
'colored-nicks': store.state.settings.coloredNicks,
'time-seconds': store.state.settings.showSeconds,
'time-12h': store.state.settings.use12hClock,
}"
>
<div
:id="'chan-' + channel.id"
2019-12-04 07:58:23 +01:00
class="chat-view"
:data-type="channel.type"
:aria-label="channel.name"
role="tabpanel"
>
<div class="header">
<SidebarToggle />
<span class="title" :aria-label="'Currently open ' + channel.type">{{
channel.name
}}</span>
<StatusIcon
v-if="channel.type === 'query'"
:online="channel.isOnline"
:away="!!channel.userAway"
tooltip-dir="e"
/>
<div v-if="channel.editTopic === true" class="topic-container">
<input
2019-12-19 14:06:33 +01:00
ref="topicInput"
:value="channel.topic"
class="topic-input"
placeholder="Set channel topic"
2020-08-29 10:46:11 +02:00
enterkeyhint="done"
@keyup.enter="saveTopic"
@keyup.esc="channel.editTopic = false"
/>
<span aria-label="Save topic" class="save-topic" @click="saveTopic">
<span type="button" aria-label="Save topic"></span>
</span>
</div>
<span
v-else
:title="channel.topic"
:class="{topic: true, empty: !channel.topic}"
@dblclick="editTopic"
2019-07-17 11:33:59 +02:00
><ParsedMessage
v-if="channel.topic"
:network="network"
:text="channel.topic"
/></span>
2019-12-31 17:21:34 +01:00
<MessageSearchForm
v-if="
2022-05-23 11:27:10 +02:00
store.state.settings.searchEnabled &&
['channel', 'query'].includes(channel.type)
"
2019-12-31 17:21:34 +01:00
:network="network"
:channel="channel"
/>
<button
class="mentions"
aria-label="Open your mentions"
@click="openMentions"
/>
2019-11-09 23:21:34 +01:00
<button
class="menu"
aria-label="Open the context menu"
@click="openContextMenu"
/>
<span
v-if="channel.type === 'channel'"
class="rt-tooltip tooltipped tooltipped-w"
aria-label="Toggle user list"
>
<button
class="rt"
aria-label="Toggle user list"
2022-05-23 11:27:10 +02:00
@click="store.commit('toggleUserlist')"
/>
</span>
</div>
2019-07-17 11:33:59 +02:00
<div v-if="channel.type === 'special'" class="chat-content">
<div class="chat">
<div class="messages">
<div class="msg">
<component
:is="specialComponent"
2018-07-19 19:44:24 +02:00
:network="network"
:channel="channel"
/>
</div>
</div>
</div>
2018-07-10 11:16:24 +02:00
</div>
2019-07-17 11:33:59 +02:00
<div v-else class="chat-content">
<div
2019-07-17 11:33:59 +02:00
:class="[
'scroll-down tooltipped tooltipped-w tooltipped-no-touch',
{'scroll-down-shown': !channel.scrolledToBottom},
]"
aria-label="Jump to recent messages"
2022-05-23 11:27:10 +02:00
@click="messageList?.jumpToBottom()"
>
<div class="scroll-down-arrow" />
</div>
<ChatUserList v-if="channel.type === 'channel'" :channel="channel" />
<MessageList
ref="messageList"
:network="network"
:channel="channel"
:focused="focused"
/>
</div>
</div>
</div>
2018-08-31 12:43:21 +02:00
<div
2022-05-23 11:27:10 +02:00
v-if="store.state.currentUserVisibleError"
2018-10-16 12:21:16 +02:00
id="user-visible-error"
@click="hideUserVisibleError"
2019-07-17 11:33:59 +02:00
>
2022-05-23 11:27:10 +02:00
{{ store.state.currentUserVisibleError }}
2019-07-17 11:33:59 +02:00
</div>
<ChatInput :network="network" :channel="channel" />
</div>
</template>
2022-05-23 09:44:01 +02:00
<script lang="ts">
2019-11-09 23:21:34 +01:00
import socket from "../js/socket";
2020-03-16 18:58:40 +01:00
import eventbus from "../js/eventbus";
2018-07-12 10:41:40 +02:00
import ParsedMessage from "./ParsedMessage.vue";
import MessageList from "./MessageList.vue";
2018-07-08 15:42:54 +02:00
import ChatInput from "./ChatInput.vue";
import ChatUserList from "./ChatUserList.vue";
import SidebarToggle from "./SidebarToggle.vue";
2019-12-31 17:21:34 +01:00
import MessageSearchForm from "./MessageSearchForm.vue";
2018-07-10 11:16:24 +02:00
import ListBans from "./Special/ListBans.vue";
2019-04-14 13:44:44 +02:00
import ListInvites from "./Special/ListInvites.vue";
2018-07-10 11:37:48 +02:00
import ListChannels from "./Special/ListChannels.vue";
2018-07-11 09:54:32 +02:00
import ListIgnored from "./Special/ListIgnored.vue";
2022-05-23 11:27:10 +02:00
import {defineComponent, PropType, ref, computed, watch, nextTick, onMounted, Component} from "vue";
2022-05-23 09:44:01 +02:00
import type {ClientNetwork, ClientChan} from "../js/types";
2022-05-23 11:27:10 +02:00
import {useStore} from "../js/store";
import StatusIcon from "./StatusIcon.vue";
2022-05-23 09:44:01 +02:00
export default defineComponent({
name: "Chat",
components: {
2018-07-12 10:41:40 +02:00
ParsedMessage,
MessageList,
2018-07-08 15:42:54 +02:00
ChatInput,
ChatUserList,
SidebarToggle,
2019-12-31 17:21:34 +01:00
MessageSearchForm,
StatusIcon,
},
props: {
2022-05-23 09:44:01 +02:00
network: {type: Object as PropType<ClientNetwork>, required: true},
channel: {type: Object as PropType<ClientChan>, required: true},
focused: String,
},
emits: ["channel-changed"],
setup(props, {emit}) {
2022-05-23 11:27:10 +02:00
const store = useStore();
const messageList = ref<typeof MessageList>();
const topicInput = ref<HTMLInputElement | null>(null);
const specialComponent = computed(() => {
switch (props.channel.special) {
2019-07-17 11:33:59 +02:00
case "list_bans":
2022-05-23 11:27:10 +02:00
return ListBans as Component;
2019-07-17 11:33:59 +02:00
case "list_invites":
2022-05-23 11:27:10 +02:00
return ListInvites as Component;
2019-07-17 11:33:59 +02:00
case "list_channels":
2022-05-23 11:27:10 +02:00
return ListChannels as Component;
2019-07-17 11:33:59 +02:00
case "list_ignored":
2022-05-23 11:27:10 +02:00
return ListIgnored as Component;
2018-07-10 11:16:24 +02:00
}
2018-12-17 11:29:49 +01:00
return undefined;
2022-05-23 11:27:10 +02:00
});
2019-12-19 14:06:33 +01:00
2022-05-23 11:27:10 +02:00
const channelChanged = () => {
2019-10-17 18:56:44 +02:00
// Triggered when active channel is set or changed
emit("channel-changed", props.channel);
2019-10-17 18:56:44 +02:00
2022-05-23 11:27:10 +02:00
socket.emit("open", props.channel.id);
2019-10-17 18:56:44 +02:00
2022-05-23 11:27:10 +02:00
if (props.channel.usersOutdated) {
props.channel.usersOutdated = false;
2019-10-17 18:56:44 +02:00
socket.emit("names", {
2022-05-23 11:27:10 +02:00
target: props.channel.id,
2019-10-17 18:56:44 +02:00
});
}
2022-05-23 11:27:10 +02:00
};
const hideUserVisibleError = () => {
store.commit("currentUserVisibleError", null);
};
const editTopic = () => {
if (props.channel.type === "channel") {
props.channel.editTopic = true;
}
};
const saveTopic = () => {
props.channel.editTopic = false;
if (!topicInput.value) {
return;
}
2022-05-23 11:27:10 +02:00
const newTopic = topicInput.value.value;
if (props.channel.topic !== newTopic) {
const target = props.channel.id;
const text = `/raw TOPIC ${props.channel.name} :${newTopic}`;
socket.emit("input", {target, text});
}
2022-05-23 11:27:10 +02:00
};
const openContextMenu = (event: any) => {
2020-03-16 18:58:40 +01:00
eventbus.emit("contextmenu:channel", {
2019-11-23 15:26:20 +01:00
event: event,
2022-05-23 11:27:10 +02:00
channel: props.channel,
network: props.network,
2019-11-23 15:26:20 +01:00
});
2022-05-23 11:27:10 +02:00
};
const openMentions = (event: any) => {
2020-03-16 18:58:40 +01:00
eventbus.emit("mentions:toggle", {
event: event,
});
2022-05-23 11:27:10 +02:00
};
2022-05-31 06:15:39 +02:00
watch(
() => props.channel,
() => {
channelChanged();
}
);
2022-05-23 11:27:10 +02:00
watch(
() => props.channel.editTopic,
(newTopic) => {
if (newTopic) {
void nextTick(() => {
topicInput.value?.focus();
});
}
2022-05-23 11:27:10 +02:00
}
);
2022-05-23 11:27:10 +02:00
onMounted(() => {
channelChanged();
if (props.channel.editTopic) {
void nextTick(() => {
2022-05-23 11:27:10 +02:00
topicInput.value?.focus();
});
}
});
return {
store,
messageList,
topicInput,
specialComponent,
hideUserVisibleError,
editTopic,
saveTopic,
openContextMenu,
openMentions,
};
},
2022-05-23 09:44:01 +02:00
});
</script>