Compare commits
No commits in common. "e43ab6cfe180de13faf880ab4dbe8e398e7dcd9f" and "1ab74d67caf5b4d37cb39c4ca7e7bca3c8107dd6" have entirely different histories.
e43ab6cfe1
...
1ab74d67ca
@ -1,15 +0,0 @@
|
|||||||
import statusCode from "../utils/statusCodes";
|
|
||||||
import { Response } from "express";
|
|
||||||
|
|
||||||
export default class Message {
|
|
||||||
|
|
||||||
static async addFlag(body, res: Response) {
|
|
||||||
console.log("hit")
|
|
||||||
res.status(statusCode.OK).send();
|
|
||||||
}
|
|
||||||
|
|
||||||
static async removeFlag(body, res: Response) {
|
|
||||||
console.log("hit")
|
|
||||||
res.status(statusCode.OK).send();
|
|
||||||
}
|
|
||||||
}
|
|
@ -5,12 +5,10 @@ import { Response } from "express";
|
|||||||
|
|
||||||
export async function messages(body, res: Response) {
|
export async function messages(body, res: Response) {
|
||||||
const { roomId } = body;
|
const { roomId } = body;
|
||||||
getMessages(roomId)
|
getMessages(roomId).then((messages) => {
|
||||||
.then((messages) => {
|
res.status(statusCode.OK).json(messages);
|
||||||
res.status(statusCode.OK).json(messages);
|
}).catch((err) => {
|
||||||
})
|
logger.err(err)
|
||||||
.catch((err) => {
|
res.status(statusCode.INTERNAL_SERVER_ERROR);
|
||||||
logger.err(err);
|
});
|
||||||
res.status(statusCode.INTERNAL_SERVER_ERROR);
|
}
|
||||||
});
|
|
||||||
}
|
|
@ -1,18 +1,29 @@
|
|||||||
|
import statusCodes from "../utils/statusCodes";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
import { rooms } from "../abl/rooms";
|
import { rooms } from "../abl/rooms";
|
||||||
import Message from "../abl/Messages-abl";
|
|
||||||
import { messages } from "../abl/messages";
|
import { messages } from "../abl/messages";
|
||||||
import { members } from "../abl/members";
|
import { members } from "../abl/members";
|
||||||
|
import {
|
||||||
|
validateCreateAccount,
|
||||||
|
validateGetAccounts,
|
||||||
|
validateGetMembers,
|
||||||
|
validateGetMessages,
|
||||||
|
validateGetRooms,
|
||||||
|
} from "../validator/validator";
|
||||||
import Account from "../abl/Account-abl";
|
import Account from "../abl/Account-abl";
|
||||||
import validator from "../validator/validator";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return all mailboxes and folders for an user
|
* Return all mailboxes and folders for an user
|
||||||
*/
|
*/
|
||||||
router.get("/accounts", async (req, res) => {
|
router.get("/accounts", async (req, res) => {
|
||||||
await validator.validate("getAccounts", req.params, res, Account.getAll);
|
const valid = validateGetAccounts(req.params);
|
||||||
|
if (!valid) {
|
||||||
|
res.status(statusCodes.NOT_ACCEPTABLE).send({ error: validateGetAccounts.errors });
|
||||||
|
} else {
|
||||||
|
await Account.getAll(req.params, res);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -20,36 +31,48 @@ router.get("/accounts", async (req, res) => {
|
|||||||
*/
|
*/
|
||||||
router.get("/:mailboxId/rooms", async (req, res) => {
|
router.get("/:mailboxId/rooms", async (req, res) => {
|
||||||
// todo offet limit
|
// todo offet limit
|
||||||
await validator.validate("getRooms", req.params, res, rooms);
|
const valid = validateGetRooms(req.params);
|
||||||
|
if (!valid) {
|
||||||
|
res.status(statusCodes.NOT_ACCEPTABLE).send({ error: validateGetRooms.errors });
|
||||||
|
} else {
|
||||||
|
await rooms(req.params, res);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return all messages from a room
|
* Return all messages from a room
|
||||||
*/
|
*/
|
||||||
router.get("/:roomId/messages", async (req, res) => {
|
router.get("/:roomId/messages", async (req, res) => {
|
||||||
await validator.validate("getMessages", req.params, res, messages);
|
const valid = validateGetMessages(req.params);
|
||||||
|
if (!valid) {
|
||||||
|
res.status(statusCodes.NOT_ACCEPTABLE).send({ error: validateGetMessages.errors });
|
||||||
|
} else {
|
||||||
|
await messages(req.params, res);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return all members from a room
|
* Return all members from a room
|
||||||
*/
|
*/
|
||||||
router.get("/:roomId/members", async (req, res) => {
|
router.get("/:roomId/members", async (req, res) => {
|
||||||
await validator.validate("getMembers", req.params, res, members);
|
const valid = validateGetMembers(req.params);
|
||||||
|
if (!valid) {
|
||||||
|
res.status(statusCodes.NOT_ACCEPTABLE).send({ error: validateGetMembers.errors });
|
||||||
|
} else {
|
||||||
|
await members(req.params, res);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a new mailbox inside the app
|
* Register a new mailbox inside the app
|
||||||
*/
|
*/
|
||||||
router.post("/account", async (req, res) => {
|
router.post("/account", async (req, res) => {
|
||||||
await validator.validate("createAccount", req.body, res, Account.register);
|
const valid = validateCreateAccount(req.body);
|
||||||
});
|
if (!valid) {
|
||||||
|
res.status(statusCodes.NOT_ACCEPTABLE).send({ error: validateCreateAccount.errors });
|
||||||
router.post("/addFlag", async (req, res) => {
|
} else {
|
||||||
await validator.validate("addFlag", req.body, res, Message.addFlag);
|
await Account.register(req.body, res);
|
||||||
});
|
}
|
||||||
|
|
||||||
router.post("/removeFlag", async (req, res) => {
|
|
||||||
await validator.validate("removeFlag", req.body, res, Message.removeFlag);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"mailboxId": {
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"messageId": {
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"flag": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"mailboxId",
|
|
||||||
"messageId",
|
|
||||||
"flag"
|
|
||||||
],
|
|
||||||
"additionalProperties": false
|
|
||||||
}
|
|
@ -8,55 +8,9 @@ import getAccountSchema from "./schemas/getAccounts-schema.json";
|
|||||||
import getRoomSchema from "./schemas/getRooms-schema.json";
|
import getRoomSchema from "./schemas/getRooms-schema.json";
|
||||||
import getMessagesSchema from "./schemas/getMessages-schema.json";
|
import getMessagesSchema from "./schemas/getMessages-schema.json";
|
||||||
import getMembersSchema from "./schemas/getMembers-schema.json";
|
import getMembersSchema from "./schemas/getMembers-schema.json";
|
||||||
import setFlagSchema from "./schemas/setFlag-schema.json";
|
|
||||||
import { Request, Response } from "express";
|
|
||||||
import statusCodes from "../utils/statusCodes";
|
|
||||||
import logger from "../system/Logger";
|
|
||||||
|
|
||||||
export const validateCreateAccount = ajv.compile(createAccountSchema);
|
export const validateCreateAccount = ajv.compile(createAccountSchema);
|
||||||
export const validateGetAccounts = ajv.compile(getAccountSchema);
|
export const validateGetAccounts = ajv.compile(getAccountSchema);
|
||||||
export const validateGetRooms = ajv.compile(getRoomSchema);
|
export const validateGetRooms = ajv.compile(getRoomSchema);
|
||||||
export const validateGetMessages = ajv.compile(getMessagesSchema);
|
export const validateGetMessages = ajv.compile(getMessagesSchema);
|
||||||
export const validateGetMembers = ajv.compile(getMembersSchema);
|
export const validateGetMembers = ajv.compile(getMembersSchema);
|
||||||
export const validateSetFlag = ajv.compile(setFlagSchema);
|
|
||||||
|
|
||||||
class Validator {
|
|
||||||
_getSchema(name: string): any {
|
|
||||||
switch (name) {
|
|
||||||
case "createAccount":
|
|
||||||
return validateCreateAccount;
|
|
||||||
case "getAccounts":
|
|
||||||
return validateGetAccounts;
|
|
||||||
case "getRooms":
|
|
||||||
return validateGetRooms;
|
|
||||||
case "getMessages":
|
|
||||||
return validateGetMessages;
|
|
||||||
case "getMembers":
|
|
||||||
return validateGetMembers;
|
|
||||||
case "addFlag":
|
|
||||||
case "removeFlag":
|
|
||||||
return validateSetFlag;
|
|
||||||
default:
|
|
||||||
logger.err(`Schema ${name} not found`);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async validate(
|
|
||||||
schemaName: string,
|
|
||||||
args: any,
|
|
||||||
res: Response,
|
|
||||||
callback: (body: any, res: Response) => Promise<void>,
|
|
||||||
): Promise<void> {
|
|
||||||
const validator = this._getSchema(schemaName);
|
|
||||||
const valid = validator(args);
|
|
||||||
if (!valid) {
|
|
||||||
res.status(statusCodes.NOT_ACCEPTABLE).send({ error: validator.errors });
|
|
||||||
} else {
|
|
||||||
await callback(args, res);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const validator = new Validator();
|
|
||||||
export default validator;
|
|
@ -9,6 +9,7 @@ const props = defineProps({
|
|||||||
|
|
||||||
const iframe = ref<HTMLIFrameElement>();
|
const iframe = ref<HTMLIFrameElement>();
|
||||||
|
|
||||||
|
// todo dompurify
|
||||||
// background vs color
|
// background vs color
|
||||||
const htmlDefault = (html: string) => {
|
const htmlDefault = (html: string) => {
|
||||||
return `
|
return `
|
||||||
@ -32,9 +33,7 @@ function setIframeContent(content: string | undefined) {
|
|||||||
if (!content) return;
|
if (!content) return;
|
||||||
const doc = iframe.value.contentDocument || iframe.value.contentWindow?.document;
|
const doc = iframe.value.contentDocument || iframe.value.contentWindow?.document;
|
||||||
if (!doc) return;
|
if (!doc) return;
|
||||||
// todo dompurify for image
|
const html = DOMPurify.sanitize(content);
|
||||||
const html = DOMPurify.sanitize(content, { FORBID_TAGS: ["style"] });
|
|
||||||
|
|
||||||
doc.open();
|
doc.open();
|
||||||
doc.write(htmlDefault(html));
|
doc.write(htmlDefault(html));
|
||||||
doc.close();
|
doc.close();
|
||||||
|
@ -1,17 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineProps, PropType } from "vue";
|
import { defineProps, ref, PropType } from "vue";
|
||||||
import { decodeEmojis } from "../../../utils/string";
|
import { decodeEmojis } from "../../../utils/string";
|
||||||
import { removeDuplicates } from "../../../utils/array";
|
import { removeDuplicates } from "../../../utils/array";
|
||||||
import { Address, Message } from "@/store/models/model";
|
import { Address, Message } from "@/store/models/model";
|
||||||
import Content from "./Content.vue";
|
import Content from "./Content.vue";
|
||||||
import Options from "./Options.vue";
|
|
||||||
import { isSeenFc } from "@/utils/flagsUtils";
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
msg: Object as PropType<Message>,
|
msg: Object as PropType<Message>,
|
||||||
members: Array as PropType<Address[]>,
|
members: Array as PropType<Address[]>,
|
||||||
mailboxId: Number,
|
|
||||||
roomId: Number,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const displayAddresses = (addressIds: string[] | undefined): string => {
|
const displayAddresses = (addressIds: string[] | undefined): string => {
|
||||||
@ -26,13 +22,15 @@ const displayAddresses = (addressIds: string[] | undefined): string => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const classes = (): string => {
|
const classes = (): string => {
|
||||||
const flags = props.msg?.flags;
|
const flags = props.msg?.flags?.split(",");
|
||||||
|
|
||||||
|
// not flags implies no seen flag
|
||||||
|
if (!flags) return "msg-notSeen";
|
||||||
|
|
||||||
// Important takes the priority on Seen flag
|
// Important takes the priority on Seen flag
|
||||||
if (flags?.includes("\\Important")) {
|
if (flags.includes("\\Important")) {
|
||||||
return "msg-important";
|
return "msg-important";
|
||||||
}
|
} else if (!flags.includes("\\Seen")) {
|
||||||
if (!isSeenFc(flags)) {
|
|
||||||
return "msg-notSeen";
|
return "msg-notSeen";
|
||||||
}
|
}
|
||||||
return "msg-basic";
|
return "msg-basic";
|
||||||
@ -66,7 +64,7 @@ const classes = (): string => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="content" :class="[classes()]">
|
<div class="content" :class="[classes()]">
|
||||||
<Content :content="props.msg?.content" />
|
<Content :content="props.msg?.content" />
|
||||||
<Options class="options" :mailboxId="props.mailboxId" :roomId="props.roomId" :msg="props.msg" />
|
<div class="options">options {{ props?.msg?.flags }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,49 +1,23 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineProps, PropType } from "vue";
|
import { defineProps, onMounted, ref, watch, PropType } from "vue";
|
||||||
import { Message } from "@/store/models/model";
|
import { decodeEmojis } from "../../../utils/string";
|
||||||
import API from "@/services/imapAPI";
|
import { removeDuplicates } from "../../../utils/array";
|
||||||
import store from "@/store/store";
|
import DOMPurify from "dompurify";
|
||||||
import { isSeenFc } from "@/utils/flagsUtils";
|
import { Address, Message } from "@/store/models/model";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
msg: Object as PropType<Message>,
|
msg: Object as PropType<Message>,
|
||||||
mailboxId: Number,
|
|
||||||
roomId: Number,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const setFlag = (flag: string) => {
|
|
||||||
// todo loading
|
|
||||||
if (!props.mailboxId || !props.msg) return;
|
|
||||||
let apiCall = isSeenFc(props.msg?.flags) ? API.removeFlag : API.addFlag;
|
|
||||||
apiCall({
|
|
||||||
mailboxId: props.mailboxId,
|
|
||||||
messageId: props.msg?.id,
|
|
||||||
flag: flag,
|
|
||||||
})
|
|
||||||
.then((res) => {
|
|
||||||
if (isSeenFc(props.msg?.flags)) {
|
|
||||||
store.commit("removeFlag", { roomId: props.roomId, messageId: props.msg?.id, flag: flag });
|
|
||||||
} else {
|
|
||||||
store.commit("addFlag", { roomId: props.roomId, messageId: props.msg?.id, flag: flag });
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.log(err);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="button" @click="setFlag('\\Seen')">
|
<div>mark as not read</div>
|
||||||
{{ isSeenFc(props.msg?.flags) ? "Mark as not read" : "Mark as read" }}
|
|
||||||
</div>
|
|
||||||
<div>flag favorite</div>
|
<div>flag favorite</div>
|
||||||
<div>reply</div>
|
<div>reply</div>
|
||||||
<div>delete from all</div>
|
<div>delete from all</div>
|
||||||
<div>delete from remote</div>
|
<div>delete from remote</div>
|
||||||
<div>transfer</div>
|
<div>transfer</div>
|
||||||
<div>see source</div>
|
<div>see source</div>
|
||||||
<div>{{ props.msg?.flags }}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -51,16 +25,4 @@ const setFlag = (flag: string) => {
|
|||||||
div {
|
div {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button {
|
|
||||||
border: solid 1px;
|
|
||||||
border-radius: 6px;
|
|
||||||
display: initial;
|
|
||||||
padding: 1px 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button:hover {
|
|
||||||
background-color: var(--selected);
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -16,10 +16,4 @@ export default {
|
|||||||
getMembers(roomId: number) {
|
getMembers(roomId: number) {
|
||||||
return API().get(`/mail/${roomId}/members`);
|
return API().get(`/mail/${roomId}/members`);
|
||||||
},
|
},
|
||||||
addFlag(data: { mailboxId: number; messageId: number; flag: string }) {
|
|
||||||
return API().post(`/mail/addFlag`, data);
|
|
||||||
},
|
|
||||||
removeFlag(data: { mailboxId: number; messageId: number; flag: string }) {
|
|
||||||
return API().post(`/mail/removeFlag`, data);
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
@ -14,7 +14,7 @@ export interface Message {
|
|||||||
subject: string;
|
subject: string;
|
||||||
content: string;
|
content: string;
|
||||||
date: string;
|
date: string;
|
||||||
flags: string[];
|
flags: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum LoadingState {
|
export enum LoadingState {
|
||||||
@ -23,7 +23,6 @@ export enum LoadingState {
|
|||||||
loaded = 2,
|
loaded = 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo store messages outside of the room
|
|
||||||
export interface Room {
|
export interface Room {
|
||||||
id: number;
|
id: number;
|
||||||
roomName: string;
|
roomName: string;
|
||||||
|
@ -46,8 +46,6 @@ export interface State {
|
|||||||
activeRoom: number;
|
activeRoom: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const roomOnId = (state: State, roomId: number) => state.rooms.find((room: Room) => room.id == roomId);
|
|
||||||
|
|
||||||
// // define injection key todo
|
// // define injection key todo
|
||||||
// export const key: InjectionKey<Store<State>> = Symbol()
|
// export const key: InjectionKey<Store<State>> = Symbol()
|
||||||
|
|
||||||
@ -96,34 +94,20 @@ const store = createStore<State>({
|
|||||||
},
|
},
|
||||||
addMessages(state, payload) {
|
addMessages(state, payload) {
|
||||||
// todo add if not exist
|
// todo add if not exist
|
||||||
const room = roomOnId(state, payload.roomId);
|
const room = state.rooms.find((room) => room.id == payload.roomId);
|
||||||
if (!room) return;
|
if (!room) return;
|
||||||
payload.messages.forEach((message: any) => {
|
payload.messages.forEach((message: Message) => {
|
||||||
message.flags = message.flags?.split(",") ?? [];
|
|
||||||
room.messages.push(message);
|
room.messages.push(message);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
addAddress(state, payload) {
|
addAddress(state, payload) {
|
||||||
// todo add if not exist
|
// todo add if not exist
|
||||||
const room = roomOnId(state, payload.roomId);
|
const room = state.rooms.find((room) => room.id == payload.roomId);
|
||||||
if (!room) return;
|
if (!room) return;
|
||||||
payload.addresses.forEach((address: Address) => {
|
payload.addresses.forEach((address: Address) => {
|
||||||
room.members.push(address);
|
room.members.push(address);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
addFlag(state, payload) {
|
|
||||||
// todo if seen notif
|
|
||||||
const msg = roomOnId(state, payload.roomId)?.messages.find((msg) => msg.id == payload.messageId);
|
|
||||||
if (msg) {
|
|
||||||
msg.flags.push(payload.flag);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
removeFlag(state, payload) {
|
|
||||||
const msg = roomOnId(state, payload.roomId)?.messages.find((msg) => msg.id == payload.messageId);
|
|
||||||
if (msg) {
|
|
||||||
msg.flags?.splice(msg.flags?.indexOf(payload.flag), 1);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
rooms: (state) => (): Room[] => {
|
rooms: (state) => (): Room[] => {
|
||||||
@ -135,19 +119,20 @@ const store = createStore<State>({
|
|||||||
room:
|
room:
|
||||||
(state) =>
|
(state) =>
|
||||||
(roomId: number): Room | undefined => {
|
(roomId: number): Room | undefined => {
|
||||||
return roomOnId(state, roomId);
|
const room = state.rooms.find((room) => room.id == roomId);
|
||||||
|
return room;
|
||||||
},
|
},
|
||||||
address:
|
address:
|
||||||
(state) =>
|
(state) =>
|
||||||
(roomId: number, addressId: number): Address | undefined => {
|
(roomId: number, addressId: number): Address | undefined => {
|
||||||
const room = roomOnId(state, roomId);
|
const room = state.rooms.find((room) => room.id == roomId);
|
||||||
const address = room?.members.find((address) => address.id == addressId);
|
const address = room?.members.find((address) => address.id == addressId);
|
||||||
return address;
|
return address;
|
||||||
},
|
},
|
||||||
messages:
|
messages:
|
||||||
(state) =>
|
(state) =>
|
||||||
(roomId: number): Message[] => {
|
(roomId: number): Message[] => {
|
||||||
const room = roomOnId(state, roomId);
|
const room = state.rooms.find((room) => room.id == roomId);
|
||||||
if (!room) return [];
|
if (!room) return [];
|
||||||
if (room.messageLoading === LoadingState.notLoaded) {
|
if (room.messageLoading === LoadingState.notLoaded) {
|
||||||
store.dispatch("fetchMessages", { roomId: room.id, room: room });
|
store.dispatch("fetchMessages", { roomId: room.id, room: room });
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
export function isSeenFc(flags: string[] | undefined): boolean {
|
|
||||||
return flags?.includes("\\Seen") ?? false;
|
|
||||||
}
|
|
@ -47,8 +47,6 @@ function openMessageView(id) {
|
|||||||
:key="index"
|
:key="index"
|
||||||
:msg="message"
|
:msg="message"
|
||||||
:members="room?.members"
|
:members="room?.members"
|
||||||
:mailboxId="room.mailboxId"
|
|
||||||
:roomId="room.id"
|
|
||||||
@open-message-view="(id) => openMessageView(id)"
|
@open-message-view="(id) => openMessageView(id)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user