work in progress
This commit is contained in:
22
docker-compose.yaml
Normal file
22
docker-compose.yaml
Normal file
@@ -0,0 +1,22 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
ergo:
|
||||
init: true
|
||||
image: ghcr.io/ergochat/ergo:master
|
||||
ports:
|
||||
- "6667:6667/tcp"
|
||||
- "8097:8097"
|
||||
volumes:
|
||||
- data:/ircd
|
||||
- ./ircd.yaml:/ircd/ircd.yaml
|
||||
deploy:
|
||||
placement:
|
||||
constraints:
|
||||
- "node.role == manager"
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
replicas: 1
|
||||
|
||||
volumes:
|
||||
data:
|
||||
4
irchad-web/.browserslistrc
Normal file
4
irchad-web/.browserslistrc
Normal file
@@ -0,0 +1,4 @@
|
||||
> 1%
|
||||
last 2 versions
|
||||
not dead
|
||||
not ie 11
|
||||
5
irchad-web/.editorconfig
Normal file
5
irchad-web/.editorconfig
Normal file
@@ -0,0 +1,5 @@
|
||||
[*.{js,jsx,ts,tsx,vue}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
82
irchad-web/.eslintrc-auto-import.json
Normal file
82
irchad-web/.eslintrc-auto-import.json
Normal file
@@ -0,0 +1,82 @@
|
||||
{
|
||||
"globals": {
|
||||
"Component": true,
|
||||
"ComponentPublicInstance": true,
|
||||
"ComputedRef": true,
|
||||
"DirectiveBinding": true,
|
||||
"EffectScope": true,
|
||||
"ExtractDefaultPropTypes": true,
|
||||
"ExtractPropTypes": true,
|
||||
"ExtractPublicPropTypes": true,
|
||||
"InjectionKey": true,
|
||||
"MaybeRef": true,
|
||||
"MaybeRefOrGetter": true,
|
||||
"PropType": true,
|
||||
"Ref": true,
|
||||
"Slot": true,
|
||||
"Slots": true,
|
||||
"VNode": true,
|
||||
"WritableComputedRef": true,
|
||||
"computed": true,
|
||||
"createApp": true,
|
||||
"customRef": true,
|
||||
"defineAsyncComponent": true,
|
||||
"defineComponent": true,
|
||||
"defineStore": true,
|
||||
"effectScope": true,
|
||||
"getCurrentInstance": true,
|
||||
"getCurrentScope": true,
|
||||
"h": true,
|
||||
"inject": true,
|
||||
"isProxy": true,
|
||||
"isReactive": true,
|
||||
"isReadonly": true,
|
||||
"isRef": true,
|
||||
"markRaw": true,
|
||||
"nextTick": true,
|
||||
"onActivated": true,
|
||||
"onBeforeMount": true,
|
||||
"onBeforeRouteLeave": true,
|
||||
"onBeforeRouteUpdate": true,
|
||||
"onBeforeUnmount": true,
|
||||
"onBeforeUpdate": true,
|
||||
"onDeactivated": true,
|
||||
"onErrorCaptured": true,
|
||||
"onMounted": true,
|
||||
"onRenderTracked": true,
|
||||
"onRenderTriggered": true,
|
||||
"onScopeDispose": true,
|
||||
"onServerPrefetch": true,
|
||||
"onUnmounted": true,
|
||||
"onUpdated": true,
|
||||
"onWatcherCleanup": true,
|
||||
"provide": true,
|
||||
"reactive": true,
|
||||
"readonly": true,
|
||||
"ref": true,
|
||||
"resolveComponent": true,
|
||||
"shallowReactive": true,
|
||||
"shallowReadonly": true,
|
||||
"shallowRef": true,
|
||||
"storeToRefs": true,
|
||||
"toRaw": true,
|
||||
"toRef": true,
|
||||
"toRefs": true,
|
||||
"toValue": true,
|
||||
"triggerRef": true,
|
||||
"unref": true,
|
||||
"useAttrs": true,
|
||||
"useCssModule": true,
|
||||
"useCssVars": true,
|
||||
"useId": true,
|
||||
"useModel": true,
|
||||
"useRoute": true,
|
||||
"useRouter": true,
|
||||
"useSlots": true,
|
||||
"useTemplateRef": true,
|
||||
"watch": true,
|
||||
"watchEffect": true,
|
||||
"watchPostEffect": true,
|
||||
"watchSyncEffect": true
|
||||
}
|
||||
}
|
||||
22
irchad-web/.gitignore
vendored
Normal file
22
irchad-web/.gitignore
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
3
irchad-web/eslint.config.js
Normal file
3
irchad-web/eslint.config.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import vuetify from 'eslint-config-vuetify'
|
||||
|
||||
export default vuetify()
|
||||
13
irchad-web/index.html
Normal file
13
irchad-web/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Welcome to Vuetify 3</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
20
irchad-web/jsconfig.json
Normal file
20
irchad-web/jsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"target": "es5",
|
||||
"module": "esnext",
|
||||
"baseUrl": "./",
|
||||
"moduleResolution": "bundler",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
},
|
||||
"lib": [
|
||||
"esnext",
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"scripthost"
|
||||
]
|
||||
}
|
||||
}
|
||||
34
irchad-web/package.json
Normal file
34
irchad-web/package.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "irchad-web",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint . --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/roboto": "5.2.7",
|
||||
"@mdi/font": "7.4.47",
|
||||
"irc-framework": "^4.14.0",
|
||||
"pinia": "^3.0.3",
|
||||
"vue": "^3.5.21",
|
||||
"vue-router": "^4.5.1",
|
||||
"vuetify": "^3.10.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"eslint": "^9.35.0",
|
||||
"eslint-config-vuetify": "^4.2.0",
|
||||
"sass-embedded": "^1.92.1",
|
||||
"unplugin-auto-import": "^19.3.0",
|
||||
"unplugin-fonts": "^1.4.0",
|
||||
"unplugin-vue-components": "^29.0.0",
|
||||
"unplugin-vue-router": "^0.15.0",
|
||||
"vite": "^7.1.5",
|
||||
"vite-plugin-vue-layouts-next": "^1.0.0",
|
||||
"vite-plugin-vuetify": "^2.1.2"
|
||||
}
|
||||
}
|
||||
9
irchad-web/src/App.vue
Normal file
9
irchad-web/src/App.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<v-app>
|
||||
<router-view />
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
//
|
||||
</script>
|
||||
21
irchad-web/src/components/BufferList.vue
Normal file
21
irchad-web/src/components/BufferList.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<script setup>
|
||||
import { computed } from "vue";
|
||||
import { useIRCStore } from "@/stores/irc";
|
||||
const { buffers } = useIRCStore();
|
||||
const store = useIRCStore();
|
||||
const bufferList = computed(() => {
|
||||
return Object.keys(buffers);
|
||||
});
|
||||
|
||||
function click(bufferName) {
|
||||
store.setActiveBuffer(bufferName);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-list>
|
||||
<v-list-item v-for="buf in bufferList" @click="click(buf)">
|
||||
{{ buf }}
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</template>
|
||||
60
irchad-web/src/components/Chat.vue
Normal file
60
irchad-web/src/components/Chat.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from "vue";
|
||||
import { useIRCStore } from "@/stores/irc";
|
||||
|
||||
const store = useIRCStore();
|
||||
const inputBuffer = ref();
|
||||
|
||||
onMounted(() => {
|
||||
store.connect();
|
||||
});
|
||||
|
||||
function send() {
|
||||
store.sendActiveBuffer(inputBuffer.value);
|
||||
inputBuffer.value = "";
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="d-flex flex-row" style="height: 100vh">
|
||||
<v-sheet border class="buffers">
|
||||
<BufferList />
|
||||
</v-sheet>
|
||||
<div class="messages d-flex flex-column">
|
||||
<MessageList
|
||||
:messages="store.activeBuffer?.messages"
|
||||
:me="store.clientInfo.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"
|
||||
/>
|
||||
</v-sheet>
|
||||
</div>
|
||||
<v-sheet class="user-list h-100" border>
|
||||
<UserList :users="store.activeBuffer?.users" />
|
||||
</v-sheet>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.buffers {
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.messages {
|
||||
height: 100%;
|
||||
flex: 2;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.user-list {
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
||||
61
irchad-web/src/components/MessageList.vue
Normal file
61
irchad-web/src/components/MessageList.vue
Normal file
@@ -0,0 +1,61 @@
|
||||
<script setup>
|
||||
import { computed } from "vue";
|
||||
import { useIRCStore } from "@/stores/irc";
|
||||
const props = defineProps(["messages", "me"]);
|
||||
|
||||
const store = useIRCStore();
|
||||
const messagesReverse = computed(() => {
|
||||
if (props.messages) {
|
||||
return [...props.messages];
|
||||
}
|
||||
});
|
||||
|
||||
const timeFormatter = new Intl.DateTimeFormat("en-US", {
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
hour12: true,
|
||||
});
|
||||
function formatTime(ts) {
|
||||
const date = new Date(ts);
|
||||
return timeFormatter.format(date);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-sheet class="message-list d-flex">
|
||||
<v-list>
|
||||
<v-list-item
|
||||
v-for="msg in messagesReverse"
|
||||
density="compact"
|
||||
:prepend-avatar="store.getMetadata(msg.nick, 'avatar')"
|
||||
>
|
||||
<v-list-item-title>
|
||||
<span
|
||||
class="message-nick"
|
||||
:class="{ 'text-primary': me === msg.nick }"
|
||||
>
|
||||
{{ msg.nick }}
|
||||
</span>
|
||||
<span class="message-time" v-if="!!msg.time">{{
|
||||
formatTime(msg.time)
|
||||
}}</span>
|
||||
</v-list-item-title>
|
||||
<v-list-item-subtitle>
|
||||
{{ msg.message }}
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-sheet>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.message-list {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
.message-time {
|
||||
font-size: 0.65em;
|
||||
margin-left: 4px;
|
||||
}
|
||||
</style>
|
||||
15
irchad-web/src/components/UserList.vue
Normal file
15
irchad-web/src/components/UserList.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<script setup>
|
||||
import { useIRCStore } from "@/stores/irc";
|
||||
const props = defineProps(["users"]);
|
||||
const store = useIRCStore();
|
||||
</script>
|
||||
<template>
|
||||
<v-list density="compact">
|
||||
<v-list-item
|
||||
v-for="user in users"
|
||||
:prepend-avatar="store.getMetadata(user.nick, 'avatar')"
|
||||
>
|
||||
{{ user.nick }}
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</template>
|
||||
9
irchad-web/src/layouts/default.vue
Normal file
9
irchad-web/src/layouts/default.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<v-main>
|
||||
<router-view />
|
||||
</v-main>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
//
|
||||
</script>
|
||||
23
irchad-web/src/main.js
Normal file
23
irchad-web/src/main.js
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* main.js
|
||||
*
|
||||
* Bootstraps Vuetify and other plugins then mounts the App`
|
||||
*/
|
||||
|
||||
// Plugins
|
||||
import { registerPlugins } from '@/plugins'
|
||||
|
||||
// Components
|
||||
import App from './App.vue'
|
||||
|
||||
// Composables
|
||||
import { createApp } from 'vue'
|
||||
|
||||
// Styles
|
||||
import 'unfonts.css'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
registerPlugins(app)
|
||||
|
||||
app.mount('#app')
|
||||
7
irchad-web/src/pages/index.vue
Normal file
7
irchad-web/src/pages/index.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<Chat />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
//
|
||||
</script>
|
||||
17
irchad-web/src/plugins/index.js
Normal file
17
irchad-web/src/plugins/index.js
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* plugins/index.js
|
||||
*
|
||||
* Automatically included in `./src/main.js`
|
||||
*/
|
||||
|
||||
// Plugins
|
||||
import vuetify from './vuetify'
|
||||
import pinia from '@/stores'
|
||||
import router from '@/router'
|
||||
|
||||
export function registerPlugins (app) {
|
||||
app
|
||||
.use(vuetify)
|
||||
.use(router)
|
||||
.use(pinia)
|
||||
}
|
||||
19
irchad-web/src/plugins/vuetify.js
Normal file
19
irchad-web/src/plugins/vuetify.js
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* plugins/vuetify.js
|
||||
*
|
||||
* Framework documentation: https://vuetifyjs.com`
|
||||
*/
|
||||
|
||||
// Styles
|
||||
import '@mdi/font/css/materialdesignicons.css'
|
||||
import 'vuetify/styles'
|
||||
|
||||
// Composables
|
||||
import { createVuetify } from 'vuetify'
|
||||
|
||||
// https://vuetifyjs.com/en/introduction/why-vuetify/#feature-guides
|
||||
export default createVuetify({
|
||||
theme: {
|
||||
defaultTheme: 'system',
|
||||
},
|
||||
})
|
||||
36
irchad-web/src/router/index.js
Normal file
36
irchad-web/src/router/index.js
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* router/index.ts
|
||||
*
|
||||
* Automatic routes for `./src/pages/*.vue`
|
||||
*/
|
||||
|
||||
// Composables
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import { setupLayouts } from 'virtual:generated-layouts'
|
||||
import { routes } from 'vue-router/auto-routes'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes: setupLayouts(routes),
|
||||
})
|
||||
|
||||
// Workaround for https://github.com/vitejs/vite/issues/11804
|
||||
router.onError((err, to) => {
|
||||
if (err?.message?.includes?.('Failed to fetch dynamically imported module')) {
|
||||
if (localStorage.getItem('vuetify:dynamic-reload')) {
|
||||
console.error('Dynamic import error, reloading page did not fix it', err)
|
||||
} else {
|
||||
console.log('Reloading page to fix dynamic import error')
|
||||
localStorage.setItem('vuetify:dynamic-reload', 'true')
|
||||
location.assign(to.fullPath)
|
||||
}
|
||||
} else {
|
||||
console.error(err)
|
||||
}
|
||||
})
|
||||
|
||||
router.isReady().then(() => {
|
||||
localStorage.removeItem('vuetify:dynamic-reload')
|
||||
})
|
||||
|
||||
export default router
|
||||
8
irchad-web/src/stores/app.js
Normal file
8
irchad-web/src/stores/app.js
Normal file
@@ -0,0 +1,8 @@
|
||||
// Utilities
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useAppStore = defineStore('app', {
|
||||
state: () => ({
|
||||
//
|
||||
}),
|
||||
})
|
||||
4
irchad-web/src/stores/index.js
Normal file
4
irchad-web/src/stores/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
// Utilities
|
||||
import { createPinia } from 'pinia'
|
||||
|
||||
export default createPinia()
|
||||
178
irchad-web/src/stores/irc.js
Normal file
178
irchad-web/src/stores/irc.js
Normal file
@@ -0,0 +1,178 @@
|
||||
import { defineStore } from "pinia";
|
||||
import { Client } from "irc-framework";
|
||||
|
||||
function autoNick() {
|
||||
const discriminiator = Math.round(Math.random() * 100);
|
||||
return `chad-${discriminiator}`;
|
||||
}
|
||||
|
||||
export const useIRCStore = defineStore("irc", () => {
|
||||
const clientInfo = ref({
|
||||
nick: autoNick(),
|
||||
username: "chad",
|
||||
gecos: "IrChad",
|
||||
});
|
||||
|
||||
const buffers = ref({});
|
||||
const activeBufferName = ref();
|
||||
const metadata = ref({});
|
||||
|
||||
const activeBuffer = computed(() => {
|
||||
if (activeBufferName.value) {
|
||||
return buffers.value[activeBufferName.value];
|
||||
}
|
||||
});
|
||||
|
||||
function getMetadata(subject, key) {
|
||||
if (metadata.value[subject]) return metadata.value[subject][key];
|
||||
}
|
||||
|
||||
function setActiveBuffer(bufferName) {
|
||||
activeBufferName.value = bufferName;
|
||||
}
|
||||
|
||||
const client = new Client({
|
||||
enable_echomessage: true,
|
||||
});
|
||||
|
||||
function connect() {
|
||||
client.requestCap("draft/metadata-2");
|
||||
client.requestCap("echo-message");
|
||||
const tls = location.protocol === "https:";
|
||||
client.connect({
|
||||
...clientInfo.value,
|
||||
host: location.hostname,
|
||||
tls,
|
||||
port: location.port,
|
||||
version: "irchad irc-framework",
|
||||
path: "/ws",
|
||||
});
|
||||
}
|
||||
|
||||
function sendActiveBuffer(message) {
|
||||
client.say(activeBufferName.value, message);
|
||||
// const buffer = getBuffer(activeBufferName.value);
|
||||
//
|
||||
// buffer.messages.push({
|
||||
// nick: clientInfo.value.nick,
|
||||
// message,
|
||||
// time: Date.now(),
|
||||
// });
|
||||
}
|
||||
|
||||
function isMe(target) {
|
||||
return target === clientInfo.value.nick;
|
||||
}
|
||||
|
||||
function addBuffer(bufferName) {
|
||||
buffers.value[bufferName] = {
|
||||
messages: [],
|
||||
topic: "",
|
||||
users: [],
|
||||
};
|
||||
return buffers.value[bufferName];
|
||||
}
|
||||
|
||||
function getBuffer(bufferName) {
|
||||
return buffers.value[bufferName];
|
||||
}
|
||||
|
||||
function delBuffer(bufferName) {
|
||||
if (buffers.value[bufferName]) {
|
||||
delete buffers.value[bufferName];
|
||||
}
|
||||
}
|
||||
|
||||
client.on("registered", function () {
|
||||
client.list();
|
||||
client.raw("METADATA * SUB avatar");
|
||||
client.raw("METADATA * SET avatar https://placekittens.com/128/128");
|
||||
});
|
||||
|
||||
client.on("unknown command", function (ircCommand) {
|
||||
if (ircCommand.command === "METADATA") {
|
||||
const from = ircCommand.params[0];
|
||||
const target = ircCommand.params[2];
|
||||
const key = ircCommand.params[1];
|
||||
const value = ircCommand.params[3];
|
||||
|
||||
let subject = target;
|
||||
if (target === "*") {
|
||||
subject = from;
|
||||
}
|
||||
|
||||
if (!metadata.value[subject]) {
|
||||
metadata.value[subject] = {};
|
||||
}
|
||||
|
||||
metadata.value[subject][key] = value;
|
||||
}
|
||||
});
|
||||
|
||||
client.on("channel list", (channels) =>
|
||||
channels.map((ch) => client.join(ch.channel)),
|
||||
);
|
||||
|
||||
client.on("message", function (message) {
|
||||
let buffer;
|
||||
if (isMe(message.target)) {
|
||||
buffer = getBuffer(message.nick);
|
||||
} else {
|
||||
buffer = getBuffer(message.target);
|
||||
}
|
||||
buffer.messages.push(message);
|
||||
});
|
||||
|
||||
client.on("join", (ev) => {
|
||||
const nick = ev.nick;
|
||||
const channel = ev.channel;
|
||||
console.log(ev);
|
||||
if (isMe(nick)) {
|
||||
addBuffer(channel);
|
||||
if (!activeBufferName.value) {
|
||||
activeBufferName.value = channel;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const buffer = getBuffer(channel);
|
||||
if (!buffer) return;
|
||||
buffer.users.push({
|
||||
nick: ev.nick,
|
||||
gecos: ev.gecos,
|
||||
hostname: ev.hostname,
|
||||
ident: ev.ident,
|
||||
});
|
||||
});
|
||||
|
||||
client.on("part", ({ nick, channel }) => {
|
||||
if (isMe(nick)) {
|
||||
delBuffer(channel);
|
||||
}
|
||||
const buffer = getBuffer(channel);
|
||||
if (!buffer) return;
|
||||
const idx = buffer.users.find((u) => u.nick === nick);
|
||||
if (idx === -1) return;
|
||||
|
||||
buffer.users.splice(idx, 1);
|
||||
});
|
||||
|
||||
client.on("userlist", (ev) => {
|
||||
const buffer = getBuffer(ev.channel);
|
||||
if (!buffer) return;
|
||||
buffer.users = ev.users;
|
||||
});
|
||||
|
||||
return {
|
||||
clientInfo,
|
||||
connect,
|
||||
client,
|
||||
buffers,
|
||||
activeBufferName,
|
||||
activeBuffer,
|
||||
sendActiveBuffer,
|
||||
setActiveBuffer,
|
||||
getMetadata,
|
||||
metadata,
|
||||
};
|
||||
});
|
||||
10
irchad-web/src/styles/settings.scss
Normal file
10
irchad-web/src/styles/settings.scss
Normal file
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* src/styles/settings.scss
|
||||
*
|
||||
* Configures SASS variables and Vuetify overwrites
|
||||
*/
|
||||
|
||||
// https://vuetifyjs.com/features/sass-variables/`
|
||||
// @use 'vuetify/settings' with (
|
||||
// $color-pack: false
|
||||
// );
|
||||
80
irchad-web/vite.config.mjs
Normal file
80
irchad-web/vite.config.mjs
Normal file
@@ -0,0 +1,80 @@
|
||||
// Plugins
|
||||
import AutoImport from "unplugin-auto-import/vite";
|
||||
import Components from "unplugin-vue-components/vite";
|
||||
import Fonts from "unplugin-fonts/vite";
|
||||
import Layouts from "vite-plugin-vue-layouts-next";
|
||||
import Vue from "@vitejs/plugin-vue";
|
||||
import VueRouter from "unplugin-vue-router/vite";
|
||||
import { VueRouterAutoImports } from "unplugin-vue-router";
|
||||
import Vuetify, { transformAssetUrls } from "vite-plugin-vuetify";
|
||||
|
||||
// Utilities
|
||||
import { defineConfig } from "vite";
|
||||
import { fileURLToPath, URL } from "node:url";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
VueRouter(),
|
||||
Layouts(),
|
||||
Vue({
|
||||
template: { transformAssetUrls },
|
||||
}),
|
||||
// https://github.com/vuetifyjs/vuetify-loader/tree/master/packages/vite-plugin#readme
|
||||
Vuetify({
|
||||
autoImport: true,
|
||||
styles: {
|
||||
configFile: "src/styles/settings.scss",
|
||||
},
|
||||
}),
|
||||
Components(),
|
||||
Fonts({
|
||||
google: {
|
||||
families: [
|
||||
{
|
||||
name: "Roboto",
|
||||
styles: "wght@100;300;400;500;700;900",
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
AutoImport({
|
||||
imports: [
|
||||
"vue",
|
||||
VueRouterAutoImports,
|
||||
{
|
||||
pinia: ["defineStore", "storeToRefs"],
|
||||
},
|
||||
],
|
||||
eslintrc: {
|
||||
enabled: true,
|
||||
},
|
||||
vueTemplate: true,
|
||||
}),
|
||||
],
|
||||
optimizeDeps: {
|
||||
exclude: [
|
||||
"vuetify",
|
||||
"vue-router",
|
||||
"unplugin-vue-router/runtime",
|
||||
"unplugin-vue-router/data-loaders",
|
||||
"unplugin-vue-router/data-loaders/basic",
|
||||
],
|
||||
},
|
||||
define: { "process.env": {} },
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": fileURLToPath(new URL("src", import.meta.url)),
|
||||
},
|
||||
extensions: [".js", ".json", ".jsx", ".mjs", ".ts", ".tsx", ".vue"],
|
||||
},
|
||||
server: {
|
||||
port: 3000,
|
||||
proxy: {
|
||||
"/ws": {
|
||||
target: "ws://localhost:8097",
|
||||
ws: "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
3081
irchad-web/yarn.lock
Normal file
3081
irchad-web/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user