lots of things
This commit is contained in:
@@ -1,7 +1,12 @@
|
||||
<template>
|
||||
<v-app>
|
||||
<Login v-if="!ircStore.connected" />
|
||||
<Chat v-else />
|
||||
<router-view />
|
||||
|
||||
<!-- <Login v-if="!ircStore.connected" /> -->
|
||||
<!-- <Chat v-else /> -->
|
||||
<!-- <v-dialog max-width="450px" v-model="showRegistration"> -->
|
||||
<!-- <Register @success="showRegistration = false" /> -->
|
||||
<!-- </v-dialog> -->
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
@@ -9,6 +14,10 @@
|
||||
import { useIRCStore } from "@/stores/irc";
|
||||
import Chat from "@/components/Chat.vue";
|
||||
import Login from "@/components/Login.vue";
|
||||
import Register from "@/components/Register.vue";
|
||||
import { useAccountStore } from "./stores/accountStore";
|
||||
|
||||
const ircStore = useIRCStore();
|
||||
const accountStore = useAccountStore();
|
||||
const { showRegistration } = storeToRefs(accountStore);
|
||||
</script>
|
||||
|
||||
@@ -10,6 +10,7 @@ const accountStore = useAccountStore();
|
||||
|
||||
<template>
|
||||
<div class="d-flex flex-row" style="height: 100vh">
|
||||
<router-view />
|
||||
<v-sheet border class="buffers">
|
||||
<UserCard />
|
||||
<v-divider />
|
||||
@@ -27,16 +28,10 @@ const accountStore = useAccountStore();
|
||||
:me="accountStore.account.nick"
|
||||
/>
|
||||
<v-sheet>
|
||||
<!-- <v-text-field -->
|
||||
<!-- variant="outlined" -->
|
||||
<!-- :placeholder="`Message ${store.activeBufferName}`" -->
|
||||
<!-- v-model="inputBuffer" -->
|
||||
<!-- hide-details -->
|
||||
<!-- class="ma-2" -->
|
||||
<!-- @keydown.enter.exact.prevent="send" -->
|
||||
<!-- /> -->
|
||||
|
||||
<InputBuffer @send="ircStore.sendActiveBuffer" />
|
||||
<InputBuffer
|
||||
@raw="(txt: string) => ircStore.client.raw(txt)"
|
||||
@send="ircStore.sendActiveBuffer"
|
||||
/>
|
||||
</v-sheet>
|
||||
</div>
|
||||
<v-sheet class="user-list h-100" border>
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { useIRCStore } from "@/stores/irc";
|
||||
import { useBufferStore } from "@/stores/bufferStore";
|
||||
const emit = defineEmits(["send"]);
|
||||
import { useAccountStore } from "@/stores/accountStore";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
const emit = defineEmits(["send", "raw"]);
|
||||
|
||||
const store = useIRCStore();
|
||||
const accountStore = useAccountStore();
|
||||
const { showRegistration } = storeToRefs(accountStore);
|
||||
const router = useRouter();
|
||||
const bufferStore = useBufferStore();
|
||||
const text = ref();
|
||||
const menu = ref({
|
||||
@@ -15,7 +21,7 @@ const menu = ref({
|
||||
const menuList = ref({
|
||||
density: "compact",
|
||||
slim: true,
|
||||
items: [],
|
||||
items: [] as any[],
|
||||
itemTitle: "title",
|
||||
itemValue: "value",
|
||||
selected: [],
|
||||
@@ -30,18 +36,26 @@ const completionPos = ref(0);
|
||||
function clickItem() {}
|
||||
function send() {
|
||||
if (!text.value) return;
|
||||
emit("send", text.value);
|
||||
|
||||
if (text.value.slice(0, 2) === "//") {
|
||||
emit("raw", text.value.substring(2));
|
||||
} else if (text.value[0] === "/") {
|
||||
router.push({ path: "/register" });
|
||||
} else {
|
||||
emit("send", text.value);
|
||||
}
|
||||
|
||||
menu.value.open = false;
|
||||
text.value = "";
|
||||
}
|
||||
|
||||
function filterUsers(s) {
|
||||
function filterUsers(s: string) {
|
||||
if (store.activeBuffer) {
|
||||
return store.activeBuffer.users.filter((u) => u.nick.startsWith(s));
|
||||
}
|
||||
}
|
||||
|
||||
function trigger(ev) {
|
||||
function trigger(ev: KeyboardEvent) {
|
||||
const input = ev.target;
|
||||
const cursor = input.selectionStart;
|
||||
cursorPos.value = cursor;
|
||||
@@ -49,7 +63,18 @@ function trigger(ev) {
|
||||
|
||||
const textBefore = text.slice(0, cursor);
|
||||
const mentionMatch = textBefore.match(/@(\w*)$/);
|
||||
if (mentionMatch) {
|
||||
if (text[0] === "/") {
|
||||
menu.value.open = true;
|
||||
menuList.value.items = [
|
||||
{
|
||||
title: "register",
|
||||
value: "register",
|
||||
cmd: () => {
|
||||
showRegistration.value = true;
|
||||
},
|
||||
},
|
||||
];
|
||||
} else if (mentionMatch) {
|
||||
menu.value.open = true;
|
||||
menuList.value.items = filterUsers(mentionMatch[1]);
|
||||
menuList.value.itemTitle = "nick";
|
||||
|
||||
@@ -2,15 +2,21 @@
|
||||
import { useAccountStore } from "@/stores/accountStore";
|
||||
import { useIRCStore } from "@/stores/irc";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { useRouter } from "vue-router";
|
||||
const accountStore = useAccountStore();
|
||||
const ircStore = useIRCStore();
|
||||
|
||||
const { account } = storeToRefs(accountStore);
|
||||
const withAccount = ref(false);
|
||||
const form = ref(false);
|
||||
const connecting = ref(false);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
function login() {
|
||||
ircStore.connect();
|
||||
connecting.value = true;
|
||||
router.push({ name: "Chat" });
|
||||
}
|
||||
|
||||
function required(v: any) {
|
||||
@@ -48,7 +54,13 @@ function required(v: any) {
|
||||
<v-checkbox v-model="withAccount" label="Login with an account" />
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-btn type="submit" color="success" :disabled="!form">Connect</v-btn>
|
||||
<v-btn
|
||||
:loading="connecting"
|
||||
type="submit"
|
||||
color="success"
|
||||
:disabled="!form"
|
||||
>Connect</v-btn
|
||||
>
|
||||
</v-card-actions>
|
||||
</v-form>
|
||||
</v-card>
|
||||
|
||||
@@ -51,6 +51,15 @@ watch(
|
||||
<span class="message-time" v-if="!!msg.time">{{
|
||||
formatTime(msg.time)
|
||||
}}</span>
|
||||
<v-chip
|
||||
class="ml-2"
|
||||
v-bind="props"
|
||||
label
|
||||
color="purple-lighten-2"
|
||||
v-if="msg.kind === 'notice'"
|
||||
size="x-small"
|
||||
><v-icon class="mr-2">mdi-eye</v-icon>Only visible to you
|
||||
</v-chip>
|
||||
</v-list-item-title>
|
||||
{{ msg.message }}
|
||||
</v-list-item></template
|
||||
|
||||
88
app/frontend/src/components/Register.vue
Normal file
88
app/frontend/src/components/Register.vue
Normal file
@@ -0,0 +1,88 @@
|
||||
<script lang="ts" setup>
|
||||
import { HookStatus, useIRCStore } from "@/stores/irc";
|
||||
import { onBeforeMount, onBeforeUnmount } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
const router = useRouter();
|
||||
const ircStore = useIRCStore();
|
||||
const emit = defineEmits(["success"]);
|
||||
|
||||
const RegistrationSuccess = new RegExp(/Account created/);
|
||||
function interceptNickServMessage(message: { nick: string; message: string }) {
|
||||
if (message.nick !== "NickServ") {
|
||||
return HookStatus.HOOK_OK;
|
||||
}
|
||||
|
||||
registering.value = false;
|
||||
|
||||
if (message.message.match(RegistrationSuccess)) {
|
||||
emit("success");
|
||||
}
|
||||
|
||||
return HookStatus.HOOK_EAT;
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
ircStore.registerHook("message", interceptNickServMessage);
|
||||
ircStore.registerHook("notice", interceptNickServMessage);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
ircStore.unregisterHook("message", interceptNickServMessage);
|
||||
ircStore.unregisterHook("notice", interceptNickServMessage);
|
||||
});
|
||||
|
||||
const newAccount = ref({
|
||||
email: "",
|
||||
username: "",
|
||||
password: "",
|
||||
});
|
||||
|
||||
const form = ref(false);
|
||||
const registering = ref(false);
|
||||
|
||||
function register() {
|
||||
if (!form.value) return;
|
||||
|
||||
const { password, email } = newAccount.value;
|
||||
registering.value = true;
|
||||
ircStore.client.say("NickServ", `REGISTER ${password} ${email}`);
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
router.back();
|
||||
}
|
||||
const show = ref(true);
|
||||
</script>
|
||||
<template>
|
||||
<v-dialog v-model="show" max-width="400px" persistent>
|
||||
<v-card title="Account Registration">
|
||||
<v-form v-model="form" @submit.prevent="register">
|
||||
<v-card-text>
|
||||
<v-text-field
|
||||
:rules="[(v) => !!v || 'Email required']"
|
||||
v-model="newAccount.email"
|
||||
label="Email"
|
||||
role="email"
|
||||
/>
|
||||
<v-text-field
|
||||
:rules="[(v) => !!v || 'Password required']"
|
||||
v-model="newAccount.password"
|
||||
label="Password"
|
||||
role="password"
|
||||
type="password"
|
||||
/>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-btn
|
||||
:disabled="!form"
|
||||
type="submit"
|
||||
:loading="registering"
|
||||
text="Register"
|
||||
color="success"
|
||||
/>
|
||||
<v-btn @click="cancel">Cancel</v-btn>
|
||||
</v-card-actions>
|
||||
</v-form>
|
||||
</v-card></v-dialog
|
||||
>
|
||||
</template>
|
||||
@@ -1,8 +1,9 @@
|
||||
import vuetify from "./vuetify";
|
||||
import pinia from "@/stores";
|
||||
import router from "./router.ts";
|
||||
|
||||
import type { App } from "vue";
|
||||
|
||||
export function registerPlugins(app: App) {
|
||||
app.use(vuetify).use(pinia);
|
||||
app.use(vuetify).use(router).use(pinia);
|
||||
}
|
||||
|
||||
37
app/frontend/src/plugins/router.ts
Normal file
37
app/frontend/src/plugins/router.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { createMemoryHistory, createRouter } from "vue-router";
|
||||
import Chat from "@/components/Chat.vue";
|
||||
import { useIRCStore } from "@/stores/irc";
|
||||
const routes = [
|
||||
{
|
||||
path: "/",
|
||||
name: "Chat",
|
||||
component: Chat,
|
||||
children: [
|
||||
{
|
||||
path: "register",
|
||||
name: "Register",
|
||||
component: () => import("@/components/Register.vue"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/login",
|
||||
name: "Login",
|
||||
component: () => import("@/components/Login.vue"),
|
||||
},
|
||||
];
|
||||
|
||||
const router = createRouter({
|
||||
history: createMemoryHistory(),
|
||||
routes,
|
||||
});
|
||||
|
||||
router.beforeEach(async (to, from) => {
|
||||
if (!useIRCStore().connected && to.name !== "Login") {
|
||||
return { name: "Login" };
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
export default router;
|
||||
@@ -3,6 +3,7 @@ import { ref } from "vue";
|
||||
|
||||
export const useAccountStore = defineStore("accountStore", () => {
|
||||
const authenticated = ref(false);
|
||||
const showRegistration = ref(false);
|
||||
const account = ref({
|
||||
nick: "",
|
||||
account: "",
|
||||
@@ -21,5 +22,13 @@ export const useAccountStore = defineStore("accountStore", () => {
|
||||
account.value.nick = v;
|
||||
}
|
||||
|
||||
return { account, authError, authenticated, setAuthenticated, setNick };
|
||||
return {
|
||||
account,
|
||||
authError,
|
||||
authenticated,
|
||||
showRegistration,
|
||||
setAuthenticated,
|
||||
setNick,
|
||||
showRegistration,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,15 +1,50 @@
|
||||
import { defineStore, storeToRefs } from "pinia";
|
||||
import { useRouter } from "vue-router";
|
||||
import { Client } from "irc-framework";
|
||||
import { useBufferStore } from "./bufferStore";
|
||||
import { ref } from "vue";
|
||||
import { useAccountStore } from "./accountStore";
|
||||
|
||||
export type HookFunction = (event: any) => HookStatus;
|
||||
|
||||
export enum HookStatus {
|
||||
HOOK_OK,
|
||||
HOOK_EAT,
|
||||
}
|
||||
|
||||
export const useIRCStore = defineStore("ircStore", () => {
|
||||
const bufferStore = useBufferStore();
|
||||
const accountStore = useAccountStore();
|
||||
const connected = ref(false);
|
||||
const { authError } = storeToRefs(accountStore);
|
||||
|
||||
const hooks = {} as Record<string, HookFunction[]>;
|
||||
|
||||
function registerHook(event: string, f: HookFunction) {
|
||||
if (hooks[event]) hooks[event].push(f);
|
||||
else hooks[event] = [f];
|
||||
}
|
||||
|
||||
function runHook(eventName: string, eventArgs: any): HookStatus {
|
||||
if (!hooks[eventName]) return HookStatus.HOOK_OK;
|
||||
let lastRetVal = HookStatus.HOOK_OK;
|
||||
|
||||
for (const hookFunction of hooks[eventName]) {
|
||||
const retVal = hookFunction(eventArgs);
|
||||
if (retVal === HookStatus.HOOK_EAT) return retVal;
|
||||
lastRetVal = retVal;
|
||||
}
|
||||
|
||||
return lastRetVal;
|
||||
}
|
||||
|
||||
function unregisterHook(eventName: string, f: HookFunction) {
|
||||
if (!hooks[eventName]) return;
|
||||
const idx = hooks[eventName].findIndex((item) => item === f);
|
||||
if (idx === -1) return;
|
||||
hooks[eventName].splice(idx, 1);
|
||||
}
|
||||
|
||||
const selfAvatar = ref("https://placekittens.com/128/128");
|
||||
const bio = ref();
|
||||
|
||||
@@ -85,7 +120,6 @@ export const useIRCStore = defineStore("ircStore", () => {
|
||||
nick: accountStore.account.nick,
|
||||
};
|
||||
|
||||
console.log(connectParams);
|
||||
client.connect(connectParams);
|
||||
}
|
||||
|
||||
@@ -93,11 +127,12 @@ export const useIRCStore = defineStore("ircStore", () => {
|
||||
if (!bufferStore.activeBuffer) {
|
||||
return;
|
||||
}
|
||||
bufferStore.activeBuffer.channel.say(message);
|
||||
if (bufferStore.activeBuffer.channel)
|
||||
bufferStore.activeBuffer.channel.say(message);
|
||||
else client.say(bufferStore.activeBuffer.name, message);
|
||||
}
|
||||
|
||||
function isMe(target: string) {
|
||||
console.log(client.user.nick);
|
||||
return target === client.user.nick;
|
||||
}
|
||||
|
||||
@@ -129,8 +164,10 @@ export const useIRCStore = defineStore("ircStore", () => {
|
||||
},
|
||||
);
|
||||
|
||||
const router = useRouter();
|
||||
client.on("registered", function () {
|
||||
connected.value = true;
|
||||
router.push({ name: "Chat" });
|
||||
client.list();
|
||||
client.raw("METADATA * SUB avatar");
|
||||
client.raw("METADATA * SUB bio");
|
||||
@@ -207,14 +244,22 @@ export const useIRCStore = defineStore("ircStore", () => {
|
||||
|
||||
client.on("message", function (message: { nick: string; target: string }) {
|
||||
let buffer;
|
||||
|
||||
const retVal = runHook("message", message);
|
||||
if (retVal === HookStatus.HOOK_EAT) return;
|
||||
|
||||
if (message.nick === "HistServ") return;
|
||||
if (isMe(message.target)) {
|
||||
buffer = bufferStore.getBuffer(message.nick);
|
||||
} else {
|
||||
buffer = bufferStore.getBuffer(message.target);
|
||||
}
|
||||
|
||||
if (!buffer) {
|
||||
return;
|
||||
buffer = bufferStore.addBuffer(message.nick, {
|
||||
name: message.nick,
|
||||
channel: null,
|
||||
});
|
||||
}
|
||||
|
||||
buffer.messages.push(message);
|
||||
@@ -227,6 +272,14 @@ export const useIRCStore = defineStore("ircStore", () => {
|
||||
}
|
||||
});
|
||||
|
||||
client.on("notice", function (message) {
|
||||
const retVal = runHook("notice", message);
|
||||
if (retVal === HookStatus.HOOK_EAT) return;
|
||||
if (bufferStore.activeBuffer) {
|
||||
bufferStore.activeBuffer.messages.push({ ...message, kind: "notice" });
|
||||
}
|
||||
});
|
||||
|
||||
client.on("join", ({ nick, channel }: { nick: string; channel: string }) => {
|
||||
if (isMe(nick)) {
|
||||
bufferStore.addBuffer(channel, {
|
||||
@@ -294,5 +347,7 @@ export const useIRCStore = defineStore("ircStore", () => {
|
||||
setBio,
|
||||
bio,
|
||||
connected,
|
||||
registerHook,
|
||||
unregisterHook,
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user