Compare commits
3 Commits
318748c984
...
cd996d851a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd996d851a | ||
|
|
0f063deff9 | ||
|
|
614f7d9802 |
@ -1,15 +1,54 @@
|
|||||||
import statusCode from "../utils/statusCodes";
|
import statusCode from "../utils/statusCodes";
|
||||||
import { Response } from "express";
|
import { Response } from "express";
|
||||||
|
import { getMessageUid, getUserOfMailbox } from "../db/utils/mail";
|
||||||
|
import emailManager from "../mails/EmailManager";
|
||||||
|
|
||||||
export default class Message {
|
export default class Message {
|
||||||
|
|
||||||
static async addFlag(body, res: Response) {
|
static async addFlag(body, res: Response) {
|
||||||
console.log("hit")
|
const { mailboxId, messageId, flag } = body;
|
||||||
res.status(statusCode.OK).send();
|
const uid = (await getMessageUid(messageId))[0]?.uid;
|
||||||
|
if (!uid) {
|
||||||
|
res.status(statusCode.NOT_FOUND).send({ error: "Message uid not found." });
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = (await getUserOfMailbox(mailboxId))[0]?.user;
|
||||||
|
if (!user) {
|
||||||
|
res.status(statusCode.NOT_FOUND).send({ error: "Not account for this mailbox." });
|
||||||
|
}
|
||||||
|
emailManager
|
||||||
|
.getImap(user)
|
||||||
|
.getMailbox(mailboxId)
|
||||||
|
.addFlag(uid.toString(), [flag])
|
||||||
|
.then(() => {
|
||||||
|
res.status(statusCode.OK).send();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
res.status(statusCode.METHOD_FAILURE).send({ error: err });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static async removeFlag(body, res: Response) {
|
static async removeFlag(body, res: Response) {
|
||||||
console.log("hit")
|
const { mailboxId, messageId, flag } = body;
|
||||||
res.status(statusCode.OK).send();
|
const uid = (await getMessageUid(messageId))[0]?.uid;
|
||||||
|
if (!uid) {
|
||||||
|
res.status(statusCode.NOT_FOUND).send({ error: "Message uid not found." });
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = (await getUserOfMailbox(mailboxId))[0]?.user;
|
||||||
|
if (!user) {
|
||||||
|
res.status(statusCode.NOT_FOUND).send({ error: "Not account for this mailbox." });
|
||||||
|
}
|
||||||
|
emailManager
|
||||||
|
.getImap(user)
|
||||||
|
.getMailbox(mailboxId)
|
||||||
|
.removeFlag(uid.toString(), [flag])
|
||||||
|
.then(() => {
|
||||||
|
res.status(statusCode.OK).send();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
res.status(statusCode.METHOD_FAILURE).send({ error: err });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -122,7 +122,7 @@ export async function getMessages(roomId: number) {
|
|||||||
|
|
||||||
WHERE msg.room_id = ?
|
WHERE msg.room_id = ?
|
||||||
GROUP BY msg.message_id
|
GROUP BY msg.message_id
|
||||||
ORDER BY message.idate DESC;
|
ORDER BY message.idate ASC;
|
||||||
`;
|
`;
|
||||||
const values = [roomId];
|
const values = [roomId];
|
||||||
return await execQueryAsync(query, values);
|
return await execQueryAsync(query, values);
|
||||||
|
|||||||
@ -16,26 +16,38 @@ export async function getAllAccounts() {
|
|||||||
return await execQueryAsync(query, values);
|
return await execQueryAsync(query, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAllMailboxes(accountId) {
|
export async function getAllMailboxes(accountId: number) {
|
||||||
const query = 'SELECT * FROM mailbox WHERE mailbox.account_id = ?';
|
const query = "SELECT * FROM mailbox WHERE mailbox.account_id = ?";
|
||||||
const values = [accountId];
|
const values = [accountId];
|
||||||
return await execQueryAsync(query, values)
|
return await execQueryAsync(query, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function registerMailbox(accountId, mailboxName) {
|
export async function registerMailbox(accountId: number, mailboxName: string) {
|
||||||
const query = `INSERT INTO mailbox (account_id, mailbox_name) VALUES (?, ?)`;
|
const query = `INSERT INTO mailbox (account_id, mailbox_name) VALUES (?, ?)`;
|
||||||
const values = [accountId, mailboxName];
|
const values = [accountId, mailboxName];
|
||||||
return await execQueryAsyncWithId(query, values);
|
return await execQueryAsyncWithId(query, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getMailbox(mailboxId) {
|
export async function getMailbox(mailboxId: number) {
|
||||||
const query = `SELECT * FROM mailbox WHERE mailbox_id = ?`;
|
const query = `SELECT * FROM mailbox WHERE mailbox_id = ?`;
|
||||||
const values = [mailboxId];
|
const values = [mailboxId];
|
||||||
return await execQueryAsync(query, values);
|
return await execQueryAsync(query, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateMailbox(mailboxId, uidnext) {
|
export function updateMailbox(mailboxId: number, uidnext: number) {
|
||||||
const query = `UPDATE mailbox SET uidnext = ? WHERE mailbox_id = ?`;
|
const query = `UPDATE mailbox SET uidnext = ? WHERE mailbox_id = ?`;
|
||||||
const values = [uidnext, mailboxId];
|
const values = [uidnext, mailboxId];
|
||||||
execQuery(query, values);
|
execQuery(query, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function updateMailboxModseq(mailboxId: number, modseq: number) {
|
||||||
|
const query = `UPDATE mailbox SET nextmodseq = ? WHERE mailbox_id = ?`;
|
||||||
|
const values = [modseq, mailboxId];
|
||||||
|
return await execQueryAsync(query, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getMailboxModseq(mailboxId: number): Promise<{ modseq: number }[]> {
|
||||||
|
const query = `SELECT nextmodseq AS modseq FROM mailbox WHERE mailbox_id = ?`;
|
||||||
|
const values = [mailboxId];
|
||||||
|
return await execQueryAsync(query, values);
|
||||||
|
}
|
||||||
|
|||||||
@ -34,6 +34,12 @@ export async function getMessageIdOnUid(uid: number): Promise<{ message_id: numb
|
|||||||
return await execQueryAsync(query, values);
|
return await execQueryAsync(query, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getMessageUid(messageId: number): Promise<{uid: number}[]> {
|
||||||
|
const query = `SELECT uid FROM mailbox_message WHERE message_id = ?`;
|
||||||
|
const values = [messageId];
|
||||||
|
return await execQueryAsync(query, values);
|
||||||
|
}
|
||||||
|
|
||||||
export async function findRoomByOwner(ownerId: number): Promise<{ room_id: number }[]> {
|
export async function findRoomByOwner(ownerId: number): Promise<{ room_id: number }[]> {
|
||||||
const query = `SELECT room_id FROM app_room WHERE owner_id = ?`;
|
const query = `SELECT room_id FROM app_room WHERE owner_id = ?`;
|
||||||
const values = [ownerId];
|
const values = [ownerId];
|
||||||
@ -50,3 +56,15 @@ export async function getUserIdOfMailbox(boxId: number): Promise<{ user_id: numb
|
|||||||
const values = [boxId];
|
const values = [boxId];
|
||||||
return await execQueryAsync(query, values);
|
return await execQueryAsync(query, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getUserOfMailbox(mailboxId: number): Promise<{ user: string }[]> {
|
||||||
|
const query = `
|
||||||
|
SELECT address.email AS user
|
||||||
|
FROM mailbox
|
||||||
|
INNER JOIN app_account ON app_account.account_id = mailbox.account_id
|
||||||
|
INNER JOIN address on address.address_id = app_account.user_id
|
||||||
|
WHERE mailbox.mailbox_id = ?
|
||||||
|
`;
|
||||||
|
const values = [mailboxId];
|
||||||
|
return await execQueryAsync(query, values);
|
||||||
|
}
|
||||||
|
|||||||
@ -42,7 +42,11 @@ class EmailManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getSmtp(email: string): SmtpInstance | undefined {
|
getSmtp(email: string): SmtpInstance | undefined {
|
||||||
return this.smtpInstances.find((instance) => instance.user == email);
|
return this.smtpInstances.find((instance) => instance.user === email);
|
||||||
|
}
|
||||||
|
|
||||||
|
getImap(email: string): ImapInstance | undefined {
|
||||||
|
return this.imapInstances.find((instance) => instance.account.user === email);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -23,7 +23,7 @@ export class ImapInstance {
|
|||||||
this.boxes = [];
|
this.boxes = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IMAP
|
* IMAP init
|
||||||
*/
|
*/
|
||||||
this.imap.once("ready", () => {
|
this.imap.once("ready", () => {
|
||||||
logger.log("Imap connected for " + this.account.user);
|
logger.log("Imap connected for " + this.account.user);
|
||||||
@ -59,7 +59,7 @@ export class ImapInstance {
|
|||||||
|
|
||||||
getAllBox(boxes) {
|
getAllBox(boxes) {
|
||||||
// ideally we should get the all box to get all messages
|
// ideally we should get the all box to get all messages
|
||||||
let allBox = '';
|
let allBox = "";
|
||||||
Object.keys(boxes).forEach((key) => {
|
Object.keys(boxes).forEach((key) => {
|
||||||
if (key === "INBOX") return;
|
if (key === "INBOX") return;
|
||||||
if (allBox.includes("/")) return; // already found
|
if (allBox.includes("/")) return; // already found
|
||||||
@ -74,4 +74,8 @@ export class ImapInstance {
|
|||||||
if (!allBox.includes("/")) logger.warn("Did not find 'All' mailbox");
|
if (!allBox.includes("/")) logger.warn("Did not find 'All' mailbox");
|
||||||
return allBox;
|
return allBox;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getMailbox(mailboxId: number): Mailbox {
|
||||||
|
return this.boxes.find((box) => box.id === mailboxId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import Imap, { ImapMessageAttributes, Box } from "imap";
|
import Imap, { ImapMessageAttributes, Box } from "imap";
|
||||||
import { getMailbox, updateMailbox } from "../../db/imap/imap-db";
|
import { getMailbox, getMailboxModseq, updateMailbox, updateMailboxModseq } from "../../db/imap/imap-db";
|
||||||
import { Attrs, AttrsWithEnvelope } from "../../interfaces/mail/attrs.interface";
|
import { Attrs, AttrsWithEnvelope } from "../../interfaces/mail/attrs.interface";
|
||||||
import logger from "../../system/Logger";
|
import logger from "../../system/Logger";
|
||||||
import RegisterMessageInApp from "../message/saveMessage";
|
import RegisterMessageInApp from "../message/saveMessage";
|
||||||
@ -20,7 +20,7 @@ export default class Mailbox {
|
|||||||
msgToSync: number;
|
msgToSync: number;
|
||||||
syncing: boolean;
|
syncing: boolean;
|
||||||
|
|
||||||
constructor(_imap, _boxId, _boxName) {
|
constructor(_imap: Imap, _boxId: number, _boxName: string) {
|
||||||
this.imap = _imap;
|
this.imap = _imap;
|
||||||
this.boxName = _boxName;
|
this.boxName = _boxName;
|
||||||
this.id = _boxId;
|
this.id = _boxId;
|
||||||
@ -33,37 +33,70 @@ export default class Mailbox {
|
|||||||
async init() {
|
async init() {
|
||||||
// get mailbox from the database
|
// get mailbox from the database
|
||||||
this.box = (await getMailbox(this.id))[0];
|
this.box = (await getMailbox(this.id))[0];
|
||||||
|
const isReadOnly = false;
|
||||||
const readOnly = true;
|
this.imap.openBox(this.boxName, isReadOnly, (err, box) => {
|
||||||
this.imap.openBox(this.boxName, readOnly, (err, box) => {
|
|
||||||
if (err) logger.err(err);
|
if (err) logger.err(err);
|
||||||
|
|
||||||
// sync only if has new messages
|
// sync messages and flags
|
||||||
if (this.box.uidnext < box.uidnext) {
|
this.initSync(box);
|
||||||
this.sync(this.box.uidnext, box.uidnext);
|
|
||||||
} else {
|
|
||||||
logger.log("Already up to date")
|
|
||||||
}
|
|
||||||
|
|
||||||
// wait for new mails
|
// wait for new mails
|
||||||
this.imap.on("mail", (numNewMsgs: number) => {
|
this.imap.on("mail", (numNewMsgs: number) => {
|
||||||
if (!this.syncing) {
|
if (!this.syncing) {
|
||||||
// if not syncing restart a sync
|
// if not syncing restart a sync
|
||||||
this.sync(this.box.uidnext, this.box.uidnext + numNewMsgs);
|
this.syncMail(this.box.uidnext, this.box.uidnext + numNewMsgs);
|
||||||
} else {
|
} else {
|
||||||
// else save number of message to sync latter
|
// else save number of message to sync latter
|
||||||
this.msgToSync += numNewMsgs;
|
this.msgToSync += numNewMsgs;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// wait for flags update
|
||||||
this.imap.on("update", (seqno: number, info: ImapInfo) => {
|
this.imap.on("update", (seqno: number, info: ImapInfo) => {
|
||||||
|
logger.log(`Update message ${info.uid} with ${info.flags}`);
|
||||||
const updateMsg = new updateMessage(info.uid, info.flags);
|
const updateMsg = new updateMessage(info.uid, info.flags);
|
||||||
updateMsg.updateFlags();
|
updateMsg.updateFlags();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async sync(savedUid: number, currentUid: number) {
|
async updateModseq(newModseq: number) {
|
||||||
|
updateMailboxModseq(this.id, newModseq).then(() => {
|
||||||
|
this.box.highestmodseq = newModseq;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async initSync(box: Box) {
|
||||||
|
// sync mail only if has new messages
|
||||||
|
if (this.box.uidnext < box.uidnext) {
|
||||||
|
this.syncMail(this.box.uidnext, box.uidnext);
|
||||||
|
} else {
|
||||||
|
logger.log("Mail already up to date");
|
||||||
|
}
|
||||||
|
|
||||||
|
// sync flags
|
||||||
|
const lastModseq = (await getMailboxModseq(this.id))[0]?.modseq ?? 0;
|
||||||
|
if (box.highestmodseq > lastModseq) {
|
||||||
|
const fetchStream = this.imap.fetch("1:*", { bodies: "", modifiers: { changedsince: lastModseq } });
|
||||||
|
fetchStream.on("message", (message) => {
|
||||||
|
message.once("attributes", (attrs) => {
|
||||||
|
const updateMsg = new updateMessage(attrs.uid, attrs.flags);
|
||||||
|
updateMsg.updateFlags();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
fetchStream.once("error", function (err) {
|
||||||
|
logger.err("Fetch error when syncing flags: " + err);
|
||||||
|
});
|
||||||
|
fetchStream.once("end", function () {
|
||||||
|
logger.log("Done fetching new flags");
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
logger.log("Flags already up to date")
|
||||||
|
}
|
||||||
|
this.updateModseq(box.highestmodseq);
|
||||||
|
}
|
||||||
|
|
||||||
|
async syncMail(savedUid: number, currentUid: number) {
|
||||||
this.syncing = true;
|
this.syncing = true;
|
||||||
const promises: Promise<unknown>[] = [];
|
const promises: Promise<unknown>[] = [];
|
||||||
const mails: Attrs[] = [];
|
const mails: Attrs[] = [];
|
||||||
@ -75,7 +108,6 @@ export default class Mailbox {
|
|||||||
|
|
||||||
f.on("message", (msg, seqno) => {
|
f.on("message", (msg, seqno) => {
|
||||||
msg.once("attributes", (attrs: AttrsWithEnvelope) => {
|
msg.once("attributes", (attrs: AttrsWithEnvelope) => {
|
||||||
console.log(attrs.envelope);
|
|
||||||
mails.push(attrs);
|
mails.push(attrs);
|
||||||
promises.push(saveMessage(attrs, this.id, this.imap));
|
promises.push(saveMessage(attrs, this.id, this.imap));
|
||||||
});
|
});
|
||||||
@ -113,8 +145,32 @@ export default class Mailbox {
|
|||||||
this.box.uidnext += this.msgToSync;
|
this.box.uidnext += this.msgToSync;
|
||||||
// reset value to allow to detect new incoming message while syncing
|
// reset value to allow to detect new incoming message while syncing
|
||||||
this.msgToSync = 0;
|
this.msgToSync = 0;
|
||||||
this.sync(currentUid, this.box.uidnext);
|
this.syncMail(currentUid, this.box.uidnext);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addFlag(source: string, flags: string[]): Promise<void> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.imap.addFlags(source, flags, (err) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
removeFlag(source: string, flags: string[]): Promise<void> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.imap.delFlags(source, flags, (err) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,7 +12,8 @@ export default class updateMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async updateFlags() {
|
async updateFlags() {
|
||||||
const messageId = (await getMessageIdOnUid(this.uid))[0].message_id;
|
const messageId = (await getMessageIdOnUid(this.uid))[0]?.message_id;
|
||||||
|
if (!messageId) return;
|
||||||
const currentFlags = await getFlags(this.uid);
|
const currentFlags = await getFlags(this.uid);
|
||||||
|
|
||||||
const flagsToAdd = this.flags.filter((flag) => !currentFlags.find((f) => flag == f.flag_name));
|
const flagsToAdd = this.flags.filter((flag) => !currentFlags.find((f) => flag == f.flag_name));
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
import express from "express";
|
import express from "express";
|
||||||
import cors from "cors";
|
import cors from "cors";
|
||||||
const app = express();
|
const app = express();
|
||||||
import ImapSync from "./mails/EmailManager";
|
|
||||||
import { execQueryAsync, execQuery } from "./db/db";
|
import { execQueryAsync, execQuery } from "./db/db";
|
||||||
|
import mailRouter from "./routes/mail";
|
||||||
|
import emailManager from "./mails/EmailManager";
|
||||||
|
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
app.use(
|
app.use(
|
||||||
@ -13,8 +14,6 @@ app.use(
|
|||||||
app.use(cors());
|
app.use(cors());
|
||||||
app.listen(process.env.PORT || 5500);
|
app.listen(process.env.PORT || 5500);
|
||||||
|
|
||||||
import mailRouter from "./routes/mail";
|
|
||||||
import emailManager from "./mails/EmailManager";
|
|
||||||
app.use("/api/mail", mailRouter);
|
app.use("/api/mail", mailRouter);
|
||||||
|
|
||||||
emailManager.init();
|
emailManager.init();
|
||||||
|
|||||||
@ -235,6 +235,7 @@ function sendMessage() {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
padding: 0 10px 10px 10px;
|
padding: 0 10px 10px 10px;
|
||||||
|
max-height: 500px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { Message } from "@/store/models/model";
|
|||||||
import API from "@/services/imapAPI";
|
import API from "@/services/imapAPI";
|
||||||
import store from "@/store/store";
|
import store from "@/store/store";
|
||||||
import { isSeenFc } from "@/utils/flagsUtils";
|
import { isSeenFc } from "@/utils/flagsUtils";
|
||||||
|
import SvgLoader from "@/components/utils/SvgLoader.vue";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
msg: Object as PropType<Message>,
|
msg: Object as PropType<Message>,
|
||||||
@ -33,7 +34,7 @@ const setFlag = (flag: string) => {
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div id="main">
|
||||||
<div class="button" @click="setFlag('\\Seen')">
|
<div class="button" @click="setFlag('\\Seen')">
|
||||||
{{ isSeenFc(props.msg?.flags) ? "Mark as not read" : "Mark as read" }}
|
{{ isSeenFc(props.msg?.flags) ? "Mark as not read" : "Mark as read" }}
|
||||||
</div>
|
</div>
|
||||||
@ -44,14 +45,30 @@ const setFlag = (flag: string) => {
|
|||||||
<div>transfer</div>
|
<div>transfer</div>
|
||||||
<div>see source</div>
|
<div>see source</div>
|
||||||
<div>{{ props.msg?.flags }}</div>
|
<div>{{ props.msg?.flags }}</div>
|
||||||
|
<div class="icons">
|
||||||
|
<SvgLoader svg="flag-line" />
|
||||||
|
<SvgLoader svg="reply-line" />
|
||||||
|
<SvgLoader svg="delete-bin-4-line" />
|
||||||
|
<SvgLoader svg="delete-bin-6-line" />
|
||||||
|
<SvgLoader svg="share-forward-line" />
|
||||||
|
<SvgLoader svg="reply-all-line" />
|
||||||
|
<SvgLoader svg="mail-check-line" />
|
||||||
|
<SvgLoader svg="mail-unread-line" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
div {
|
#main {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icons {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
border: solid 1px;
|
border: solid 1px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
|
|||||||
@ -77,5 +77,6 @@ provide("room", room);
|
|||||||
|
|
||||||
.messages {
|
.messages {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
overflow: auto;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user