add button to set seen flag on front

This commit is contained in:
grimhilt 2023-04-12 19:01:40 +02:00
parent 1ab74d67ca
commit 4e79ab12dc
13 changed files with 159 additions and 31 deletions

15
back/abl/Messages-abl.ts Normal file
View File

@ -0,0 +1,15 @@
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();
}
}

View File

@ -5,10 +5,12 @@ import { Response } from "express";
export async function messages(body, res: Response) {
const { roomId } = body;
getMessages(roomId).then((messages) => {
res.status(statusCode.OK).json(messages);
}).catch((err) => {
logger.err(err)
res.status(statusCode.INTERNAL_SERVER_ERROR);
});
getMessages(roomId)
.then((messages) => {
res.status(statusCode.OK).json(messages);
})
.catch((err) => {
logger.err(err);
res.status(statusCode.INTERNAL_SERVER_ERROR);
});
}

View File

@ -3,6 +3,7 @@ import express from "express";
const router = express.Router();
import { rooms } from "../abl/rooms";
import Message from "../abl/Messages-abl";
import { messages } from "../abl/messages";
import { members } from "../abl/members";
import {
@ -11,6 +12,8 @@ import {
validateGetMembers,
validateGetMessages,
validateGetRooms,
validateAddFlag,
validateRemoveFlag,
} from "../validator/validator";
import Account from "../abl/Account-abl";
@ -75,4 +78,22 @@ router.post("/account", async (req, res) => {
}
});
router.post("/addFlag", async (req, res) => {
const valid = validateAddFlag(req.body);
if (!valid) {
res.status(statusCodes.NOT_ACCEPTABLE).send({ error: validateAddFlag.errors });
} else {
await Message.addFlag(req.body, res);
}
});
router.post("/removeFlag", async (req, res) => {
const valid = validateRemoveFlag(req.body);
if (!valid) {
res.status(statusCodes.NOT_ACCEPTABLE).send({ error: validateRemoveFlag.errors });
} else {
await Message.removeFlag(req.body, res);
}
});
export default router;

View File

@ -0,0 +1,20 @@
{
"type": "object",
"properties": {
"mailboxId": {
"type": "number"
},
"messageId": {
"type": "number"
},
"flag": {
"type": "string"
}
},
"required": [
"mailboxId",
"messageId",
"flag"
],
"additionalProperties": false
}

View File

@ -8,9 +8,12 @@ import getAccountSchema from "./schemas/getAccounts-schema.json";
import getRoomSchema from "./schemas/getRooms-schema.json";
import getMessagesSchema from "./schemas/getMessages-schema.json";
import getMembersSchema from "./schemas/getMembers-schema.json";
import setFlagSchema from "./schemas/setFlag-schema.json";
export const validateCreateAccount = ajv.compile(createAccountSchema);
export const validateGetAccounts = ajv.compile(getAccountSchema);
export const validateGetRooms = ajv.compile(getRoomSchema);
export const validateGetMessages = ajv.compile(getMessagesSchema);
export const validateGetMembers = ajv.compile(getMembersSchema);
export const validateAddFlag = ajv.compile(setFlagSchema);
export const validateRemoveFlag = ajv.compile(setFlagSchema);

View File

@ -9,7 +9,6 @@ const props = defineProps({
const iframe = ref<HTMLIFrameElement>();
// todo dompurify
// background vs color
const htmlDefault = (html: string) => {
return `
@ -33,7 +32,9 @@ function setIframeContent(content: string | undefined) {
if (!content) return;
const doc = iframe.value.contentDocument || iframe.value.contentWindow?.document;
if (!doc) return;
const html = DOMPurify.sanitize(content);
// todo dompurify for image
const html = DOMPurify.sanitize(content, { FORBID_TAGS: ["style"] });
doc.open();
doc.write(htmlDefault(html));
doc.close();

View File

@ -1,13 +1,17 @@
<script setup lang="ts">
import { defineProps, ref, PropType } from "vue";
import { defineProps, PropType } from "vue";
import { decodeEmojis } from "../../../utils/string";
import { removeDuplicates } from "../../../utils/array";
import { Address, Message } from "@/store/models/model";
import Content from "./Content.vue";
import Options from "./Options.vue";
import { isSeenFc } from "@/utils/flagsUtils";
const props = defineProps({
msg: Object as PropType<Message>,
members: Array as PropType<Address[]>,
mailboxId: Number,
roomId: Number,
});
const displayAddresses = (addressIds: string[] | undefined): string => {
@ -22,15 +26,13 @@ const displayAddresses = (addressIds: string[] | undefined): string => {
};
const classes = (): string => {
const flags = props.msg?.flags?.split(",");
// not flags implies no seen flag
if (!flags) return "msg-notSeen";
const flags = props.msg?.flags;
// Important takes the priority on Seen flag
if (flags.includes("\\Important")) {
if (flags?.includes("\\Important")) {
return "msg-important";
} else if (!flags.includes("\\Seen")) {
}
if (!isSeenFc(flags)) {
return "msg-notSeen";
}
return "msg-basic";
@ -64,7 +66,7 @@ const classes = (): string => {
</div>
<div class="content" :class="[classes()]">
<Content :content="props.msg?.content" />
<div class="options">options {{ props?.msg?.flags }}</div>
<Options class="options" :mailboxId="props.mailboxId" :roomId="props.roomId" :msg="props.msg" />
</div>
</div>
</template>

View File

@ -1,23 +1,49 @@
<script setup lang="ts">
import { defineProps, onMounted, ref, watch, PropType } from "vue";
import { decodeEmojis } from "../../../utils/string";
import { removeDuplicates } from "../../../utils/array";
import DOMPurify from "dompurify";
import { Address, Message } from "@/store/models/model";
import { defineProps, PropType } from "vue";
import { Message } from "@/store/models/model";
import API from "@/services/imapAPI";
import store from "@/store/store";
import { isSeenFc } from "@/utils/flagsUtils";
const props = defineProps({
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>
<template>
<div>
<div>mark as not read</div>
<div class="button" @click="setFlag('\\Seen')">
{{ isSeenFc(props.msg?.flags) ? "Mark as not read" : "Mark as read" }}
</div>
<div>flag favorite</div>
<div>reply</div>
<div>delete from all</div>
<div>delete from remote</div>
<div>transfer</div>
<div>see source</div>
<div>{{ props.msg?.flags }}</div>
</div>
</template>
@ -25,4 +51,16 @@ const props = defineProps({
div {
text-align: center;
}
.button {
border: solid 1px;
border-radius: 6px;
display: initial;
padding: 1px 5px;
cursor: pointer;
}
.button:hover {
background-color: var(--selected);
}
</style>

View File

@ -16,4 +16,10 @@ export default {
getMembers(roomId: number) {
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);
},
};

View File

@ -14,7 +14,7 @@ export interface Message {
subject: string;
content: string;
date: string;
flags: string;
flags: string[];
}
export enum LoadingState {
@ -23,6 +23,7 @@ export enum LoadingState {
loaded = 2,
}
// todo store messages outside of the room
export interface Room {
id: number;
roomName: string;

View File

@ -46,6 +46,8 @@ export interface State {
activeRoom: number;
}
const roomOnId = (state: State, roomId: number) => state.rooms.find((room: Room) => room.id == roomId);
// // define injection key todo
// export const key: InjectionKey<Store<State>> = Symbol()
@ -94,20 +96,33 @@ const store = createStore<State>({
},
addMessages(state, payload) {
// todo add if not exist
const room = state.rooms.find((room) => room.id == payload.roomId);
const room = roomOnId(state, payload.roomId);
if (!room) return;
payload.messages.forEach((message: Message) => {
payload.messages.forEach((message: any) => {
message.flags = message.flags?.split(",") ?? [];
room.messages.push(message);
});
},
addAddress(state, payload) {
// todo add if not exist
const room = state.rooms.find((room) => room.id == payload.roomId);
const room = roomOnId(state, payload.roomId);
if (!room) return;
payload.addresses.forEach((address: Address) => {
room.members.push(address);
});
},
addFlag(state, payload) {
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: {
rooms: (state) => (): Room[] => {
@ -119,20 +134,19 @@ const store = createStore<State>({
room:
(state) =>
(roomId: number): Room | undefined => {
const room = state.rooms.find((room) => room.id == roomId);
return room;
return roomOnId(state, roomId);
},
address:
(state) =>
(roomId: number, addressId: number): Address | undefined => {
const room = state.rooms.find((room) => room.id == roomId);
const room = roomOnId(state, roomId);
const address = room?.members.find((address) => address.id == addressId);
return address;
},
messages:
(state) =>
(roomId: number): Message[] => {
const room = state.rooms.find((room) => room.id == roomId);
const room = roomOnId(state, roomId);
if (!room) return [];
if (room.messageLoading === LoadingState.notLoaded) {
store.dispatch("fetchMessages", { roomId: room.id, room: room });

View File

@ -0,0 +1,3 @@
export function isSeenFc(flags: string[] | undefined): boolean {
return flags?.includes("\\Seen") ?? false;
}

View File

@ -47,6 +47,8 @@ function openMessageView(id) {
:key="index"
:msg="message"
:members="room?.members"
:mailboxId="room.mailboxId"
:roomId="room.id"
@open-message-view="(id) => openMessageView(id)"
/>
</div>