Compare commits
44 Commits
2594f6042b
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
adb7a1161a | ||
|
|
3c069ed2f8 | ||
|
|
ae73326820 | ||
|
|
f40b6758de | ||
|
|
43e8cc7d3e | ||
|
|
edddf9afbf | ||
|
|
3bffd88108 | ||
|
|
0094783a4e | ||
|
|
f42d819e45 | ||
|
|
467b0eebe9 | ||
|
|
e8e8555c2f | ||
|
|
af4cc2f6a0 | ||
|
|
2cae8f12a7 | ||
|
|
7be2e84691 | ||
|
|
737a22e1f8 | ||
|
|
4ecd723cec | ||
|
|
53c79aebc4 | ||
|
|
b2b0949353 | ||
|
|
ffcfc57bbe | ||
|
|
f7c95b3a36 | ||
|
|
843659b495 | ||
|
|
1a7828b281 | ||
|
|
b821c89e20 | ||
|
|
c3374a612e | ||
|
|
686e6a4911 | ||
|
|
b137263bef | ||
|
|
2c7b4f1c78 | ||
|
|
3dab9c8db1 | ||
|
|
5aef5ab7b0 | ||
|
|
8f980748b5 | ||
|
|
22fb12e6d6 | ||
|
|
12e508c7cb | ||
|
|
29b4b7bfeb | ||
|
|
91a52a29ce | ||
|
|
6e8e3bf1f4 | ||
|
|
dd8f9210a9 | ||
|
|
10fc3fd628 | ||
|
|
dc20ccfec0 | ||
|
|
ca096dac89 | ||
|
|
cd996d851a | ||
|
|
0f063deff9 | ||
|
|
614f7d9802 | ||
|
|
318748c984 | ||
|
|
4d3fbe292e |
3
.gitignore
vendored
@@ -31,4 +31,5 @@ log*
|
||||
tmp
|
||||
test.*
|
||||
*.png
|
||||
!*/schemas/*
|
||||
!*/schemas/*
|
||||
todo
|
||||
31
README.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# TRIFORM
|
||||
|
||||
_WORK IN PROGRESS_
|
||||
|
||||
TRIFORM (Threaded Rooms Interface For Organised Relational Mails) is a mail client which sorts mails into conversations based on the members present in each mails.
|
||||
|
||||
## Features
|
||||
|
||||
- [x] Multi account (tested only with gmail)
|
||||
- [x] Live syncing of mails
|
||||
- [x] Live syncing of flags
|
||||
- [x] Mark as read/unread
|
||||
- [x] Flag important
|
||||
- [ ] Attachments
|
||||
- [ ] Send new mails
|
||||
- [ ] Respond to mails
|
||||
- [x] Delete mails
|
||||
- [ ] Delete rooms
|
||||
- [ ] Live sync with the ui
|
||||
|
||||
## Installation
|
||||
|
||||
## Definitions
|
||||
|
||||
- Room: General space where messages are grouped.
|
||||
- Channel: Space use for newsletters or other conversations when there is a low need for reply.
|
||||
- Group: Space contening several users that are part of the conversation.
|
||||
- DM: Space defining a conversation with only one other member.
|
||||
- Thread: Sub-Space generally created when changing the number of member in a space.
|
||||
|
||||
## Examples
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Response } from "express";
|
||||
import { getAccounts, registerAccount } from "../db/api-db";
|
||||
import { getAccounts, getRooms, registerAccount } from "../db/api-db";
|
||||
import { getAddressId } from "../db/utils/mail";
|
||||
import logger from "../system/Logger";
|
||||
import statusCodes from "../utils/statusCodes";
|
||||
|
||||
export default class Account {
|
||||
@@ -11,10 +12,10 @@ export default class Account {
|
||||
}
|
||||
|
||||
static async register(body, res: Response) {
|
||||
const { email, pwd, xoauth, xoauth2, host, port, tls } = body;
|
||||
const { email, pwd, xoauth, xoauth2, imapHost, smtpHost, imapPort, smtpPort, tls } = body;
|
||||
getAddressId(email).then((addressId) => {
|
||||
registerAccount(addressId, pwd, xoauth, xoauth2, host, port, tls)
|
||||
.then((mailboxId) => {
|
||||
registerAccount(addressId, pwd, xoauth, xoauth2, imapHost, smtpHost, imapPort, smtpPort, tls)
|
||||
.then((mailboxId) => {0
|
||||
res.status(statusCodes.OK).json({ id: mailboxId });
|
||||
})
|
||||
.catch(() => {
|
||||
@@ -23,4 +24,16 @@ export default class Account {
|
||||
});
|
||||
// todo change mailbox to account
|
||||
}
|
||||
|
||||
static async getRooms(body, res: Response) {
|
||||
const { mailboxId, offset, limit } = body;
|
||||
getRooms(mailboxId)
|
||||
.then((rooms) => {
|
||||
res.status(statusCodes.OK).json(rooms);
|
||||
})
|
||||
.catch((err) => {
|
||||
logger.err(err);
|
||||
res.status(statusCodes.INTERNAL_SERVER_ERROR);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,120 @@
|
||||
import statusCode from "../utils/statusCodes";
|
||||
import { Response } from "express";
|
||||
import logger from "../system/Logger";
|
||||
import Message from "../mails/message/Message";
|
||||
import Room from "../mails/room/Room";
|
||||
|
||||
export default class Message {
|
||||
export default class MessageAbl {
|
||||
static async changeFlag(body, res: Response, isDelete: boolean) {
|
||||
const { mailboxId, messageId, flag } = body;
|
||||
const message = new Message().setMessageId(messageId);
|
||||
|
||||
try {
|
||||
await message.useUid();
|
||||
} catch (err) {
|
||||
res.status(statusCode.NOT_FOUND).send({ error: "Message uid not found." });
|
||||
}
|
||||
|
||||
try {
|
||||
await message.useMailbox(mailboxId);
|
||||
} catch (err) {
|
||||
res.status(statusCode.NOT_FOUND).send({ error: "Not account for this mailbox." });
|
||||
}
|
||||
|
||||
try {
|
||||
if (isDelete) {
|
||||
await message.mailbox.removeFlag(message.uid.toString(), flag);
|
||||
} else {
|
||||
await message.mailbox.addFlag(message.uid.toString(), flag);
|
||||
}
|
||||
} catch (err) {
|
||||
res.status(statusCode.METHOD_FAILURE).send({ error: err });
|
||||
}
|
||||
res.status(statusCode.OK).send();
|
||||
}
|
||||
|
||||
static async addFlag(body, res: Response) {
|
||||
console.log("hit")
|
||||
res.status(statusCode.OK).send();
|
||||
await MessageAbl.changeFlag(body, res, false);
|
||||
}
|
||||
|
||||
static async removeFlag(body, res: Response) {
|
||||
console.log("hit")
|
||||
res.status(statusCode.OK).send();
|
||||
await MessageAbl.changeFlag(body, res, true);
|
||||
}
|
||||
|
||||
static async deleteRemoteUtil(message: Message, mailboxId: number, res, isFull: boolean): Promise<boolean> {
|
||||
try {
|
||||
await message.useUid();
|
||||
} catch (error) {
|
||||
logger.err(error);
|
||||
res.status(statusCode.NOT_FOUND).send({ error: "Message uid not found." });
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
await message.useMailbox(mailboxId);
|
||||
} catch (error) {
|
||||
res.status(statusCode.NOT_FOUND).send({ error: "Not account for this mailbox." });
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
await message.mailbox.addFlag(message.uid.toString(), ["\\Deleted"]);
|
||||
await message.mailbox.moveToTrash(message.uid.toString(), (err) => {
|
||||
if (err) {
|
||||
logger.err(err);
|
||||
}
|
||||
// throw err;
|
||||
});
|
||||
} catch (err) {
|
||||
logger.log(err);
|
||||
res.status(statusCode.METHOD_FAILURE).send({ error: err });
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isFull) {
|
||||
res.status(statusCode.OK).send();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static deleteRemoteOnly = async (body, res: Response) => {
|
||||
const { mailboxId, messageId } = body;
|
||||
const message = new Message().setMessageId(messageId);
|
||||
await MessageAbl.deleteRemoteUtil(message, mailboxId, res, true);
|
||||
};
|
||||
|
||||
static deleteEverywhere = async (body, res: Response) => {
|
||||
const { mailboxId, messageId } = body;
|
||||
const message = new Message().setMessageId(messageId);
|
||||
|
||||
try {
|
||||
await message.useFlags();
|
||||
} catch (err) {
|
||||
res.status(statusCode.METHOD_FAILURE).send({ error: err });
|
||||
logger.err(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// if message not deleted remotly, delete it
|
||||
if (!message.isDeleted) {
|
||||
const success = await MessageAbl.deleteRemoteUtil(message, mailboxId, res, false);
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const room = await new Room().setRoomIdOnMessageId(messageId);
|
||||
try {
|
||||
await message.delete();
|
||||
if (room.roomId && await room.shouldDelete()) {
|
||||
await room.delete();
|
||||
res.status(statusCode.OK).json({ deleteRoom: true }).send();
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
res.status(statusCode.METHOD_FAILURE).send({ error: err });
|
||||
return;
|
||||
}
|
||||
res.status(statusCode.OK).send();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,6 +6,9 @@ import { getLastMsgData, getRoomOwner } from "../db/Room-db";
|
||||
import emailManager from "../mails/EmailManager";
|
||||
import MailBuilder from "../mails/utils/mailBuilder";
|
||||
import { getAddresses } from "../db/utils/mail";
|
||||
import { getMembers, getMessages, getRooms } from "../db/api-db";
|
||||
import logger from "../system/Logger";
|
||||
import Room from "../mails/room/Room";
|
||||
|
||||
function rmUserFromAddrs(addresses: { email: string }[], user: string) {
|
||||
let index = addresses.findIndex((a) => a.email == user);
|
||||
@@ -13,7 +16,7 @@ function rmUserFromAddrs(addresses: { email: string }[], user: string) {
|
||||
addresses.splice(index, 1);
|
||||
}
|
||||
}
|
||||
export default class Room {
|
||||
export default class RoomAbl {
|
||||
// todo change name of reponse
|
||||
static async response(body, res: Response) {
|
||||
const { user, roomId, text, html } = body;
|
||||
@@ -25,12 +28,10 @@ export default class Room {
|
||||
const mailBuilder = new MailBuilder();
|
||||
mailBuilder.from(user).to(ownerEmail).text(text).html(html);
|
||||
|
||||
emailManager.getSmtp(user).sendMail(mailBuilder.message);
|
||||
emailManager.getSmtp(user)?.sendMail(mailBuilder.message);
|
||||
res.status(statusCode.OK).send();
|
||||
} else if (roomType === RoomType.GROUP || roomType === RoomType.THREAD) {
|
||||
const lastMsgData = (await getLastMsgData(roomId))[0];
|
||||
console.log(lastMsgData);
|
||||
|
||||
const mailBuilder = new MailBuilder();
|
||||
mailBuilder.inReplySubject(lastMsgData.subject).inReplyTo(lastMsgData.messageID).text(text).html(html);
|
||||
|
||||
@@ -52,10 +53,41 @@ export default class Room {
|
||||
.to(to.map((a) => a.email))
|
||||
.cc(cc.map((a) => a.email));
|
||||
|
||||
emailManager.getSmtp(user).sendMail(mailBuilder.message);
|
||||
emailManager.getSmtp(user)?.sendMail(mailBuilder.message);
|
||||
res.status(statusCode.OK).send();
|
||||
} else {
|
||||
res.status(statusCode.FORBIDDEN).send({ error: "Cannot add a new message in a room or a channel." });
|
||||
}
|
||||
}
|
||||
|
||||
static async getMessages(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);
|
||||
});
|
||||
}
|
||||
|
||||
static async getMembers(body, res: Response) {
|
||||
const { roomId } = body;
|
||||
getMembers(roomId)
|
||||
.then((addresses) => {
|
||||
res.status(statusCode.OK).json(addresses);
|
||||
})
|
||||
.catch((err) => {
|
||||
logger.err(err);
|
||||
res.status(statusCode.INTERNAL_SERVER_ERROR);
|
||||
});
|
||||
}
|
||||
|
||||
static async delete(body, res: Response) {
|
||||
const { roomId } = body;
|
||||
console.log("delete", roomId);
|
||||
const room = new Room().setRoomId(roomId);
|
||||
// todo
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import statusCode from "../utils/statusCodes";
|
||||
import { getMembers } from "../db/api-db";
|
||||
import logger from "../system/Logger";
|
||||
|
||||
export async function members(body, res) {
|
||||
const { roomId } = body;
|
||||
getMembers(roomId).then((addresses) => {
|
||||
res.status(statusCode.OK).json(addresses);
|
||||
}).catch((err) => {
|
||||
logger.err(err)
|
||||
res.status(statusCode.INTERNAL_SERVER_ERROR);
|
||||
});
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import statusCode from "../utils/statusCodes";
|
||||
import { getMessages } from "../db/api-db";
|
||||
import logger from "../system/Logger";
|
||||
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);
|
||||
});
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import statusCode from "../utils/statusCodes";
|
||||
import { getRooms } from "../db/api-db";
|
||||
import logger from "../system/Logger";
|
||||
import { Response } from "express";
|
||||
|
||||
export async function rooms(body, res: Response) {
|
||||
const { mailboxId, offset, limit } = body;
|
||||
getRooms(mailboxId).then((rooms) => {
|
||||
res.status(statusCode.OK).json(rooms);
|
||||
}).catch((err) => {
|
||||
logger.err(err)
|
||||
res.status(statusCode.INTERNAL_SERVER_ERROR);
|
||||
});
|
||||
}
|
||||
@@ -12,8 +12,10 @@ export async function getRoomOwner(roomId: number) {
|
||||
return await execQueryAsync(query, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* get all the data needed to reply to a message in a room
|
||||
*/
|
||||
export async function getLastMsgData(roomId: number) {
|
||||
|
||||
const query = `
|
||||
SELECT
|
||||
msg.message_id AS id,
|
||||
@@ -59,3 +61,25 @@ export async function getLastMsgData(roomId: number) {
|
||||
const values = [roomId];
|
||||
return await execQueryAsync(query, values);
|
||||
}
|
||||
|
||||
export async function getRoomOnMessageId(messageId: number) {
|
||||
const query = `SELECT room_id FROM app_room_message WHERE message_id = ?`;
|
||||
const values = [messageId];
|
||||
return await execQueryAsync(query, values);
|
||||
}
|
||||
|
||||
export async function getRoomNbMessageAndThread(roomId: number): Promise<{ nbMessage: number; nbThread: number }[]> {
|
||||
const query = `
|
||||
SELECT COUNT(arm.room_id) AS nbMessage, COUNT(app_thread.room_id) AS nbThread
|
||||
FROM app_room_message arm
|
||||
INNER JOIN app_thread ON (app_thread.root_id = arm.room_id OR app_thread.parent_id = arm.room_id)
|
||||
WHERE arm.room_id = ?`;
|
||||
const values = [roomId];
|
||||
return await execQueryAsync(query, values);
|
||||
}
|
||||
|
||||
export async function deleteRoom(roomId: number) {
|
||||
const query = `DELETE FROM app_room WHERE room_id = ?`;
|
||||
const values = [roomId];
|
||||
return await execQueryAsync(query, values);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { execQueryAsync, execQueryAsyncWithId } from "./db";
|
||||
import { queryCcId, queryToId, queryFromId } from "./utils/addressQueries";
|
||||
|
||||
export async function registerAccount(userId, pwd, xoauth, xoauth2, host, port, tls) {
|
||||
export async function registerAccount(userId, pwd, xoauth, xoauth2, imapHost, smtpHost, imapPort, smtpPort, tls) {
|
||||
const query = `
|
||||
INSERT INTO app_account
|
||||
(user_id, account_pwd, xoauth, xoauth2, host, port, tls) VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
(user_id, account_pwd, xoauth, xoauth2, imap_host, smtp_host, imap_port, smtp_port, tls) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`;
|
||||
const values = [userId, pwd, xoauth, xoauth2, host, port, tls];
|
||||
const values = [userId, pwd, xoauth, xoauth2, imapHost, smtpHost, imapPort, smtpPort, tls];
|
||||
return await execQueryAsyncWithId(query, values);
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ export async function getRooms(mailboxId: number) {
|
||||
room.room_type AS roomType,
|
||||
mailbox_message.mailbox_id AS mailboxId,
|
||||
app_thread.parent_id
|
||||
|
||||
FROM app_room room
|
||||
INNER JOIN message ON message.message_id = room.message_id
|
||||
INNER JOIN mailbox_message ON mailbox_message.message_id = message.message_id
|
||||
@@ -55,6 +56,7 @@ export async function getRooms(mailboxId: number) {
|
||||
)
|
||||
) notSeenRoom ON notSeenRoom.room_id = room.room_id
|
||||
|
||||
-- get not seen in thread
|
||||
LEFT JOIN (
|
||||
SELECT app_room_message.message_id, app_thread.parent_id
|
||||
FROM app_room
|
||||
@@ -89,7 +91,8 @@ export async function getMessages(roomId: number) {
|
||||
subjectT.value AS subject,
|
||||
content.text AS content,
|
||||
message.idate AS date,
|
||||
GROUP_CONCAT(flagT.flag_name) AS flags
|
||||
GROUP_CONCAT(flagT.flag_name) AS flags,
|
||||
thread.room_id AS thread
|
||||
FROM app_room_message msg
|
||||
|
||||
${queryFromId} fromT ON msg.message_id = fromT.message_id
|
||||
@@ -104,9 +107,10 @@ export async function getMessages(roomId: number) {
|
||||
field_name.field_id = header_field.field_id AND
|
||||
field_name.field_name = 'subject'
|
||||
) subjectT ON msg.message_id = subjectT.message_id
|
||||
|
||||
|
||||
LEFT JOIN (
|
||||
SELECT bodypart.text, header_field.message_id FROM bodypart
|
||||
SELECT bodypart.text, header_field.message_id
|
||||
FROM bodypart
|
||||
INNER JOIN header_field
|
||||
INNER JOIN field_name
|
||||
WHERE
|
||||
@@ -120,9 +124,16 @@ export async function getMessages(roomId: number) {
|
||||
|
||||
INNER JOIN message ON message.message_id = msg.message_id
|
||||
|
||||
-- get room_id of thread room with this message as origin
|
||||
LEFT JOIN app_room thread ON (
|
||||
thread.message_id = msg.message_id AND
|
||||
msg.room_id != thread.room_id AND
|
||||
thread.room_type = 4
|
||||
)
|
||||
|
||||
WHERE msg.room_id = ?
|
||||
GROUP BY msg.message_id
|
||||
ORDER BY message.idate DESC;
|
||||
ORDER BY message.idate ASC;
|
||||
`;
|
||||
const values = [roomId];
|
||||
return await execQueryAsync(query, values);
|
||||
|
||||
@@ -18,8 +18,10 @@ CREATE TABLE app_account (
|
||||
account_pwd BINARY(22),
|
||||
xoauth VARCHAR(116),
|
||||
xoauth2 VARCHAR(116),
|
||||
host VARCHAR(255) NOT NULL DEFAULT 'localhost',
|
||||
port INT(5) NOT NULL DEFAULT 143,
|
||||
imap_host VARCHAR(255) NOT NULL DEFAULT 'localhost',
|
||||
imap_port INT(5) NOT NULL DEFAULT 993,
|
||||
smtp_host VARCHAR(255) NOT NULL DEFAULT 'localhost',
|
||||
smtp_port INT(5) NOT NULL DEFAULT 465,
|
||||
tls BOOLEAN NOT NULL DEFAULT true,
|
||||
PRIMARY KEY (account_id),
|
||||
FOREIGN KEY (user_id) REFERENCES address(address_id) ON DELETE CASCADE
|
||||
@@ -121,13 +123,13 @@ CREATE TABLE app_room (
|
||||
room_id INT AUTO_INCREMENT,
|
||||
room_name VARCHAR(255) NOT NULL,
|
||||
owner_id INT NOT NULL,
|
||||
message_id INT NOT NULL,
|
||||
message_id INT,
|
||||
room_type INT NOT NULL DEFAULT 0,
|
||||
lastUpdate TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP(),
|
||||
PRIMARY KEY (room_id),
|
||||
UNIQUE KEY (owner_id, message_id, room_type),
|
||||
FOREIGN KEY (owner_id) REFERENCES address(address_id),
|
||||
FOREIGN KEY (message_id) REFERENCES message(message_id)
|
||||
FOREIGN KEY (message_id) REFERENCES message(message_id) ON DELETE SET NULL
|
||||
);
|
||||
|
||||
-- 12
|
||||
@@ -175,6 +177,5 @@ create table flag (
|
||||
flag_id INT NOT NULL,
|
||||
UNIQUE KEY (message_id, flag_id),
|
||||
FOREIGN KEY (message_id) REFERENCES message(message_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (message_id) REFERENCES message(message_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (flag_id) REFERENCES flag_name(flag_id) ON DELETE CASCADE
|
||||
);
|
||||
@@ -6,9 +6,11 @@ export async function getAllAccounts() {
|
||||
app_account.account_id AS id,
|
||||
address.email AS user,
|
||||
app_account.account_pwd AS password,
|
||||
app_account.host AS host,
|
||||
app_account.port AS port,
|
||||
app_account.tls AS tls
|
||||
app_account.imap_host,
|
||||
app_account.imap_port,
|
||||
app_account.smtp_host,
|
||||
app_account.smtp_port,
|
||||
app_account.tls
|
||||
FROM app_account INNER JOIN address
|
||||
WHERE address.address_id = app_account.user_id
|
||||
`;
|
||||
@@ -16,26 +18,38 @@ export async function getAllAccounts() {
|
||||
return await execQueryAsync(query, values);
|
||||
}
|
||||
|
||||
export async function getAllMailboxes(accountId) {
|
||||
const query = 'SELECT * FROM mailbox WHERE mailbox.account_id = ?';
|
||||
export async function getAllMailboxes(accountId: number) {
|
||||
const query = "SELECT * FROM mailbox WHERE mailbox.account_id = ?";
|
||||
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 values = [accountId, mailboxName];
|
||||
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 values = [mailboxId];
|
||||
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 values = [uidnext, mailboxId];
|
||||
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);
|
||||
}
|
||||
|
||||
23
back/db/message/message-db.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { execQueryAsync } from "../db";
|
||||
|
||||
export async function getFlagsOnUid(uid: number): Promise<{ flag_id: number; flag_name: string }[]> {
|
||||
const query = `
|
||||
SELECT flag_name FROM flag_name
|
||||
INNER JOIN flag ON flag.flag_id = flag_name.flag_id
|
||||
INNER JOIN mailbox_message ON mailbox_message.message_id = flag.message_id
|
||||
WHERE mailbox_message.uid = ?
|
||||
`;
|
||||
const values = [uid];
|
||||
return await execQueryAsync(query, values);
|
||||
}
|
||||
|
||||
export async function getFlagsOnId(messageId: number): Promise<{ flag_id: number; flag_name: string }[]> {
|
||||
const query = `
|
||||
SELECT flag_name FROM flag_name
|
||||
INNER JOIN flag ON flag.flag_id = flag_name.flag_id
|
||||
INNER JOIN mailbox_message ON mailbox_message.message_id = flag.message_id
|
||||
WHERE mailbox_message.message_id = ?
|
||||
`;
|
||||
const values = [messageId];
|
||||
return await execQueryAsync(query, values);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { execQuery, execQueryAsync, execQueryAsyncWithId } from "../db";
|
||||
|
||||
export async function getFlags(uid: number): Promise<{flag_id: number, flag_name: string}[]> {
|
||||
export async function getFlags(uid: number): Promise<{ flag_id: number; flag_name: string }[]> {
|
||||
const query = `
|
||||
SELECT * FROM flag_name
|
||||
INNER JOIN flag ON flag.flag_id = flag_name.flag_id
|
||||
@@ -19,12 +19,18 @@ export async function deleteFlag(messageId: number, flagId: number) {
|
||||
|
||||
export async function updateMailboxSeen(messageId: number, isSeen: boolean) {
|
||||
const query = `UPDATE mailbox_message SET seen = ? WHERE message_id = ?`;
|
||||
const values = [messageId, isSeen];
|
||||
const values = [isSeen, messageId];
|
||||
return await execQueryAsync(query, values);
|
||||
}
|
||||
|
||||
export async function updateMailboxDeleted(messageId: number, isDeleted: boolean) {
|
||||
const query = `UPDATE mailbox_message SET deleted = ? WHERE message_id = ?`;
|
||||
const values = [messageId, isDeleted];
|
||||
const values = [isDeleted, messageId];
|
||||
return await execQueryAsync(query, values);
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteMessage(messageId: number) {
|
||||
const query = `DELETE FROM message WHERE message_id = ?`;
|
||||
const values = [messageId];
|
||||
return await execQueryAsync(query, values);
|
||||
}
|
||||
|
||||
@@ -34,6 +34,18 @@ export async function getMessageIdOnUid(uid: number): Promise<{ message_id: numb
|
||||
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 getMessageIdOnID(messageID: string): Promise<{message_id: number}[]> {
|
||||
const query = `SELECT message_id FROM message WHERE messageID = ?`;
|
||||
const values = [messageID];
|
||||
return await execQueryAsync(query, values);
|
||||
}
|
||||
|
||||
export async function findRoomByOwner(ownerId: number): Promise<{ room_id: number }[]> {
|
||||
const query = `SELECT room_id FROM app_room WHERE owner_id = ?`;
|
||||
const values = [ownerId];
|
||||
@@ -50,3 +62,15 @@ export async function getUserIdOfMailbox(boxId: number): Promise<{ user_id: numb
|
||||
const values = [boxId];
|
||||
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 {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,16 +14,16 @@ export class ImapInstance {
|
||||
this.imap = new Imap({
|
||||
user: account.user,
|
||||
password: account.password,
|
||||
tlsOptions: { servername: account.host },
|
||||
host: account.host,
|
||||
port: account.port,
|
||||
tlsOptions: { servername: account.imap_host },
|
||||
host: account.imap_host,
|
||||
port: account.imap_port,
|
||||
tls: account.tls,
|
||||
});
|
||||
this.account = account;
|
||||
this.boxes = [];
|
||||
|
||||
/**
|
||||
* IMAP
|
||||
* IMAP init
|
||||
*/
|
||||
this.imap.once("ready", () => {
|
||||
logger.log("Imap connected for " + this.account.user);
|
||||
@@ -41,37 +41,48 @@ export class ImapInstance {
|
||||
this.imap.connect();
|
||||
}
|
||||
|
||||
imapReady() {
|
||||
imapReady = () => {
|
||||
getAllMailboxes(this.account.id).then((mailboxes) => {
|
||||
if (mailboxes.length > 0) {
|
||||
this.boxes.push(new Mailbox(this.imap, mailboxes[0].mailbox_id, mailboxes[0].mailbox_name));
|
||||
this.boxes.push(new Mailbox(mailboxes[0].mailbox_id, mailboxes[0].mailbox_name, this));
|
||||
} else {
|
||||
this.imap.getBoxes("", (err, boxes) => {
|
||||
if (err) logger.err(err);
|
||||
const allBoxName = this.getAllBox(boxes);
|
||||
this.getMailboxName("All").then((allBoxName) => {
|
||||
registerMailbox(this.account.id, allBoxName).then((mailboxId) => {
|
||||
this.boxes.push(new Mailbox(this.imap, mailboxId, allBoxName));
|
||||
this.boxes.push(new Mailbox(mailboxId, allBoxName, this));
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
getAllBox(boxes) {
|
||||
// ideally we should get the all box to get all messages
|
||||
let allBox = '';
|
||||
Object.keys(boxes).forEach((key) => {
|
||||
if (key === "INBOX") return;
|
||||
if (allBox.includes("/")) return; // already found
|
||||
if (!boxes[key].children) return; // no children
|
||||
allBox = key;
|
||||
Object.keys(boxes[key].children).forEach((childBoxes) => {
|
||||
if (boxes[key].children[childBoxes].attribs.includes("\\All")) {
|
||||
allBox += "/" + childBoxes;
|
||||
getMailboxName(boxToFound: string): Promise<string> {
|
||||
return new Promise((resolve, rejects) => {
|
||||
let matchBox = "";
|
||||
this.imap.getBoxes("", (err, boxes) => {
|
||||
Object.keys(boxes).forEach((key) => {
|
||||
if (matchBox.includes("/")) return; // already found
|
||||
if (!boxes[key].children) return; // no children
|
||||
matchBox = key;
|
||||
Object.keys(boxes[key].children).forEach((childBox) => {
|
||||
let attribs = boxes[key].children[childBox].attribs;
|
||||
for (let i = 0; i < attribs.length; i++) {
|
||||
if (attribs[i].includes(boxToFound)) {
|
||||
matchBox += "/" + childBox;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
if (!matchBox.includes("/")) {
|
||||
logger.warn(`Did not find "${boxToFound}" mailbox`);
|
||||
rejects();
|
||||
} else {
|
||||
resolve(matchBox);
|
||||
}
|
||||
});
|
||||
});
|
||||
if (!allBox.includes("/")) logger.warn("Did not find 'All' mailbox");
|
||||
return allBox;
|
||||
}
|
||||
}
|
||||
|
||||
getMailbox(mailboxId: number): Mailbox {
|
||||
return this.boxes.find((box) => box.id === mailboxId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import Imap, { ImapMessageAttributes, Box } from "imap";
|
||||
import { getMailbox, updateMailbox } from "../../db/imap/imap-db";
|
||||
import Imap, { Box } from "imap";
|
||||
import { resolve } from "path";
|
||||
import { getMailbox, getMailboxModseq, updateMailbox, updateMailboxModseq } from "../../db/imap/imap-db";
|
||||
import { Attrs, AttrsWithEnvelope } from "../../interfaces/mail/attrs.interface";
|
||||
import logger from "../../system/Logger";
|
||||
import RegisterMessageInApp from "../message/saveMessage";
|
||||
import { saveMessage } from "../message/storeMessage";
|
||||
import updateMessage from "../message/updateMessage";
|
||||
import { ImapInstance } from "./ImapInstance";
|
||||
|
||||
export interface ImapInfo {
|
||||
uid: number;
|
||||
@@ -19,102 +21,191 @@ export default class Mailbox {
|
||||
box: Box;
|
||||
msgToSync: number;
|
||||
syncing: boolean;
|
||||
imapInstance: ImapInstance;
|
||||
|
||||
constructor(_imap, _boxId, _boxName) {
|
||||
this.imap = _imap;
|
||||
constructor(_boxId: number, _boxName: string, _imapInstance: ImapInstance) {
|
||||
this.imap = _imapInstance.imap;
|
||||
this.boxName = _boxName;
|
||||
this.id = _boxId;
|
||||
this.box;
|
||||
this.msgToSync = 0;
|
||||
this.syncing = false;
|
||||
this.imapInstance = _imapInstance;
|
||||
this.init();
|
||||
}
|
||||
|
||||
async init() {
|
||||
// get mailbox from the database
|
||||
this.box = (await getMailbox(this.id))[0];
|
||||
|
||||
const readOnly = true;
|
||||
this.imap.openBox(this.boxName, readOnly, (err, box) => {
|
||||
const isReadOnly = false;
|
||||
this.imap.openBox(this.boxName, isReadOnly, (err, box) => {
|
||||
if (err) logger.err(err);
|
||||
|
||||
// sync only if has new messages
|
||||
if (this.box.uidnext < box.uidnext) {
|
||||
this.sync(this.box.uidnext, box.uidnext);
|
||||
} else {
|
||||
logger.log("Already up to date")
|
||||
}
|
||||
// sync messages and flags
|
||||
this.initSync(box);
|
||||
|
||||
// wait for new mails
|
||||
this.imap.on("mail", (numNewMsgs: number) => {
|
||||
if (!this.syncing) {
|
||||
// if not syncing restart a sync
|
||||
this.sync(this.box.uidnext, this.box.uidnext + numNewMsgs);
|
||||
this.syncManager(this.box.uidnext - 1, this.box.uidnext + numNewMsgs - 1);
|
||||
} else {
|
||||
// else save number of message to sync latter
|
||||
this.msgToSync += numNewMsgs;
|
||||
}
|
||||
});
|
||||
|
||||
// wait for flags update
|
||||
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);
|
||||
updateMsg.updateFlags();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async sync(savedUid: number, currentUid: number) {
|
||||
this.syncing = true;
|
||||
const promises: Promise<unknown>[] = [];
|
||||
const mails: Attrs[] = [];
|
||||
logger.log(`Syncing from ${savedUid} to ${currentUid} uid`);
|
||||
const f = this.imap.seq.fetch(`${savedUid}:${currentUid}`, {
|
||||
size: true,
|
||||
envelope: true,
|
||||
});
|
||||
|
||||
f.on("message", (msg, seqno) => {
|
||||
msg.once("attributes", (attrs: AttrsWithEnvelope) => {
|
||||
console.log(attrs.envelope);
|
||||
mails.push(attrs);
|
||||
promises.push(saveMessage(attrs, this.id, this.imap));
|
||||
// wait for deletion
|
||||
this.imap.on("expunge", (seqno: number) => {
|
||||
// const updateMsg = new updateMessage(info.)
|
||||
console.log("Message with sequence number " + seqno + " has been deleted from the server.");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
f.once("error", (err) => {
|
||||
logger.err("Fetch error: " + err);
|
||||
});
|
||||
|
||||
f.once("end", async () => {
|
||||
let step = 20;
|
||||
for (let i = 0; i < promises.length; i += step) {
|
||||
for (let j = i; j < (i + step && promises.length); j++) {
|
||||
await new Promise((resolve, reject) => {
|
||||
promises[j]
|
||||
.then(async (res: number) => {
|
||||
const register = new RegisterMessageInApp(res, mails[j], this.id);
|
||||
await register.save();
|
||||
resolve("");
|
||||
})
|
||||
.catch((err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
logger.log(`Saved messages ${i + step > promises.length ? promises.length : i + step}/${mails.length}`);
|
||||
updateMailbox(this.id, mails[i].uid);
|
||||
}
|
||||
updateMailbox(this.id, currentUid);
|
||||
this.syncing = false;
|
||||
|
||||
// if has receive new msg during last sync then start a new sync
|
||||
if (this.msgToSync > 0) {
|
||||
const currentUid = this.box.uidnext;
|
||||
this.box.uidnext += this.msgToSync;
|
||||
// reset value to allow to detect new incoming message while syncing
|
||||
this.msgToSync = 0;
|
||||
this.sync(currentUid, this.box.uidnext);
|
||||
}
|
||||
async updateModseq(newModseq: number) {
|
||||
updateMailboxModseq(this.id, newModseq).then(() => {
|
||||
this.box.highestmodseq = newModseq.toString();
|
||||
});
|
||||
}
|
||||
|
||||
async initSync(box: Box) {
|
||||
// sync mail only if has new messages
|
||||
if (this.box.uidnext < box.uidnext) {
|
||||
this.syncManager(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 (parseInt(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(parseInt(box.highestmodseq));
|
||||
}
|
||||
|
||||
syncManager = async (savedUid: number, currentUid: number) => {
|
||||
this.syncing = true;
|
||||
logger.log(`Fetching from ${savedUid} to ${currentUid} uid`);
|
||||
const nbMessageToSync = currentUid - savedUid;
|
||||
let STEP = nbMessageToSync > 200 ? Math.floor(nbMessageToSync / 7) : nbMessageToSync;
|
||||
let mails: AttrsWithEnvelope[] = [];
|
||||
|
||||
for (let i = 0; i < nbMessageToSync; i += STEP) {
|
||||
mails = [];
|
||||
try {
|
||||
// fetch mails
|
||||
let secondUid = savedUid + STEP < currentUid ? savedUid + STEP : currentUid;
|
||||
await this.mailFetcher(savedUid, secondUid, mails);
|
||||
logger.log(`Fetched ${STEP} uids (${mails.length} messages)`);
|
||||
// save same in the database
|
||||
for (let k = 0; k < mails.length; k++) {
|
||||
try {
|
||||
const messageId = await saveMessage(mails[k], this.id, this.imap);
|
||||
const register = new RegisterMessageInApp(messageId, mails[k], this.id);
|
||||
await register.save();
|
||||
} catch (error) {
|
||||
logger.err("Failed to save a message: " + error);
|
||||
}
|
||||
}
|
||||
savedUid = secondUid;
|
||||
this.box.uidnext += savedUid;
|
||||
|
||||
updateMailbox(this.id, savedUid);
|
||||
} catch (error) {
|
||||
logger.err("Failed to sync message " + error);
|
||||
}
|
||||
logger.log(`Saved messages in uids ${i + STEP > nbMessageToSync ? nbMessageToSync : i + STEP}/${nbMessageToSync}`);
|
||||
}
|
||||
|
||||
// if has receive new msg during last sync then start a new sync
|
||||
if (this.msgToSync > 0) {
|
||||
const currentUid = this.box.uidnext;
|
||||
this.box.uidnext += this.msgToSync;
|
||||
// reset value to allow to detect new incoming message while syncing
|
||||
this.msgToSync = 0;
|
||||
await this.syncManager(currentUid, this.box.uidnext);
|
||||
}
|
||||
this.syncing = false;
|
||||
logger.log(`Finished syncing messages`);
|
||||
};
|
||||
|
||||
async mailFetcher(startUid: number, endUid: number, mails: Attrs[]): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const f = this.imap.fetch(`${startUid}:${endUid}`, {
|
||||
size: true,
|
||||
envelope: true,
|
||||
});
|
||||
|
||||
f.on("message", (msg, seqno) => {
|
||||
msg.once("attributes", (attrs: AttrsWithEnvelope) => {
|
||||
mails.push(attrs);
|
||||
});
|
||||
});
|
||||
|
||||
f.once("error", (err) => {
|
||||
logger.err("Fetch error when fetching in uid range: " + err);
|
||||
reject(1);
|
||||
});
|
||||
|
||||
f.once("end", async () => {
|
||||
resolve(0);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
move(source: string, mailboxName: string, callback: (error: Error) => void) {
|
||||
this.imap.move(source, mailboxName, callback);
|
||||
}
|
||||
|
||||
async moveToTrash(source: string, callback: (error: Error) => void) {
|
||||
const trashName = await this.imapInstance.getMailboxName("Trash");
|
||||
this.move(source, trashName, callback);
|
||||
}
|
||||
}
|
||||
|
||||
86
back/mails/message/Message.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { getFlagsOnId, getFlagsOnUid } from "../../db/message/message-db";
|
||||
import { deleteMessage } from "../../db/message/updateMessage-db";
|
||||
import { getMessageUid, getUserOfMailbox } from "../../db/utils/mail";
|
||||
import emailManager from "../EmailManager";
|
||||
import Mailbox from "../imap/Mailbox";
|
||||
import Room from "../room/Room";
|
||||
|
||||
export default class Message {
|
||||
messageId: number;
|
||||
uid: number;
|
||||
private _flags: string[] | undefined;
|
||||
private _mailbox: Mailbox;
|
||||
|
||||
constructor() {}
|
||||
|
||||
setMessageId(messageId: number): Message {
|
||||
this.messageId = messageId;
|
||||
return this;
|
||||
}
|
||||
|
||||
async useUid(): Promise<Message> {
|
||||
if (!this.messageId) {
|
||||
throw "Define message id before trying to find uid";
|
||||
}
|
||||
this.uid = (await getMessageUid(this.messageId))[0]?.uid;
|
||||
if (!this.uid) {
|
||||
throw "Uid not found";
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
async useFlags(): Promise<Message> {
|
||||
let flags;
|
||||
if (!this._flags) {
|
||||
if (this.messageId) {
|
||||
flags = await getFlagsOnId(this.messageId);
|
||||
} else if (this.uid) {
|
||||
flags = await getFlagsOnUid(this.uid);
|
||||
} else {
|
||||
throw "Neither message id or uid are set, please do so before attempting to load flags";
|
||||
}
|
||||
this._flags = flags;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
async useMailbox(mailboxId: number, user?: string): Promise<Message> {
|
||||
if (!user) {
|
||||
user = (await getUserOfMailbox(mailboxId))[0]?.user;
|
||||
if (!user) {
|
||||
throw "Cannot find user of this mailbox";
|
||||
}
|
||||
}
|
||||
this._mailbox = emailManager.getImap(user).getMailbox(mailboxId);
|
||||
return this;
|
||||
}
|
||||
|
||||
get mailbox() {
|
||||
if (!this._mailbox) {
|
||||
throw "Call useMailbox before calling functions related to mailbox";
|
||||
}
|
||||
return this._mailbox;
|
||||
}
|
||||
|
||||
get flags(): string[] {
|
||||
console.log("called");
|
||||
console.log(this._flags);
|
||||
if (!this._flags) {
|
||||
throw "Flags not loaded, call useFlags before calling functions related to flags";
|
||||
} else {
|
||||
return this._flags;
|
||||
}
|
||||
}
|
||||
|
||||
get isDeleted(): boolean {
|
||||
return this.flags.includes("\\Deleted");
|
||||
}
|
||||
|
||||
delete = async (messageId?: number): Promise<Message> => {
|
||||
if (!(this.messageId ?? messageId)) {
|
||||
throw "Delete need to have the message id";
|
||||
}
|
||||
await deleteMessage(this.messageId ?? messageId);
|
||||
return this;
|
||||
};
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
getThreadInfoOnId,
|
||||
} from "../../db/message/saveMessage-db";
|
||||
|
||||
import { findRoomByOwner, getAddressId, getUserIdOfMailbox } from "../../db/utils/mail";
|
||||
import { findRoomByOwner, getAddressId, getMessageIdOnID, getUserIdOfMailbox } from "../../db/utils/mail";
|
||||
import { nbMembers } from "../utils/envelopeUtils";
|
||||
import logger from "../../system/Logger";
|
||||
import { Attrs, Envelope, User } from "../../interfaces/mail/attrs.interface";
|
||||
@@ -55,93 +55,6 @@ export default class RegisterMessageInApp {
|
||||
this.inReplyTo = "";
|
||||
}
|
||||
|
||||
async init() {
|
||||
if (this.envelope.from) {
|
||||
this.ownerId = await getAddressId(createAddress(this.envelope.from[0])); // todo use sender or from ?
|
||||
} else {
|
||||
throw new Error("Envelope must have a 'from' field");
|
||||
}
|
||||
}
|
||||
|
||||
isDm = () => nbMembers(this.envelope) == 2;
|
||||
|
||||
async isFromUs() {
|
||||
if (this.userId == -1) {
|
||||
await getUserIdOfMailbox(this.boxId).then((res) => {
|
||||
this.userId = res[0]?.user_id;
|
||||
});
|
||||
}
|
||||
return this.ownerId == this.userId;
|
||||
}
|
||||
|
||||
async registerMembers(roomId: number) {
|
||||
getAllMembers(this.messageId).then((res) => {
|
||||
if (res.lenght == 0) return;
|
||||
const data = res[0].id.split(",");
|
||||
data.forEach(async (memberId: number) => {
|
||||
await registerMember(roomId, memberId);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async initiateRoom(owner: number, roomType: RoomType) {
|
||||
try {
|
||||
const roomId = await createRoom(this.envelope.subject, owner, this.messageId, roomType);
|
||||
await registerMessageInRoom(this.messageId, roomId, this.envelope.date);
|
||||
await this.registerMembers(roomId);
|
||||
return roomId;
|
||||
} catch (err) {
|
||||
logger.err(err);
|
||||
}
|
||||
}
|
||||
|
||||
async createOrRegisterOnExistence(owner: number, roomType: RoomType) {
|
||||
await findRoomByOwner(owner).then(async (res) => {
|
||||
if (res.length == 0) {
|
||||
// first message with this sender
|
||||
await this.initiateRoom(owner, roomType);
|
||||
} else {
|
||||
// not a reply, add to the list of message if this sender
|
||||
await registerMessageInRoom(this.messageId, res[0].room_id, this.envelope.date);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async initiateThread() {
|
||||
await createRoom(this.envelope.subject, this.ownerId, this.messageId, RoomType.THREAD).then(
|
||||
async (threadId: number) => {
|
||||
// find parent room infos
|
||||
let roomId: number;
|
||||
let root_id: number;
|
||||
await getThreadInfo(this.inReplyTo).then(async (room) => {
|
||||
// todo room not lenght, reply to transfer ?
|
||||
roomId = room[0].room_id;
|
||||
root_id = room[0].root_id;
|
||||
if (root_id === undefined) root_id = roomId;
|
||||
await registerThread(threadId, roomId, root_id);
|
||||
});
|
||||
// impl register previous message or go back
|
||||
await registerMessageInRoom(this.messageId, threadId, this.envelope.date);
|
||||
await this.registerMembers(threadId);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async createOrRegisterOnMembers(roomId: number, isThread?: boolean) {
|
||||
const hasSameMembers = await hasSameMembersAsParent(this.messageId, this.inReplyTo);
|
||||
if (hasSameMembers) {
|
||||
await registerMessageInRoom(this.messageId, roomId, this.envelope.date);
|
||||
if (isThread) {
|
||||
await getThreadInfoOnId(roomId).then(async (res) => {
|
||||
let root_id = res[0].root_id;
|
||||
if (root_id == undefined) root_id = res[0].room_id;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
await this.initiateThread();
|
||||
}
|
||||
}
|
||||
|
||||
async save() {
|
||||
await this.init();
|
||||
if (this.envelope.inReplyTo) {
|
||||
@@ -194,4 +107,98 @@ export default class RegisterMessageInApp {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async init() {
|
||||
if (this.envelope.from) {
|
||||
this.ownerId = await getAddressId(createAddress(this.envelope.from[0])); // todo use sender or from ?
|
||||
} else {
|
||||
throw new Error("Envelope must have a 'from' field");
|
||||
}
|
||||
}
|
||||
|
||||
isDm = () => nbMembers(this.envelope) == 2;
|
||||
|
||||
async isFromUs() {
|
||||
if (this.userId == -1) {
|
||||
await getUserIdOfMailbox(this.boxId).then((res) => {
|
||||
this.userId = res[0]?.user_id;
|
||||
});
|
||||
}
|
||||
return this.ownerId == this.userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* add all members of the message to the room
|
||||
*/
|
||||
async registerMembers(roomId: number) {
|
||||
getAllMembers(this.messageId).then((res) => {
|
||||
if (res.lenght == 0) return;
|
||||
const data = res[0].id.split(",");
|
||||
data.forEach(async (memberId: number) => {
|
||||
await registerMember(roomId, memberId);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async initiateRoom(owner: number, roomType: RoomType) {
|
||||
try {
|
||||
const roomId = await createRoom(this.envelope.subject, owner, this.messageId, roomType);
|
||||
await registerMessageInRoom(this.messageId, roomId, this.envelope.date);
|
||||
await this.registerMembers(roomId);
|
||||
return roomId;
|
||||
} catch (err) {
|
||||
logger.err(err);
|
||||
}
|
||||
}
|
||||
|
||||
async createOrRegisterOnExistence(owner: number, roomType: RoomType) {
|
||||
await findRoomByOwner(owner).then(async (res) => {
|
||||
if (res.length == 0) {
|
||||
// first message with this sender
|
||||
await this.initiateRoom(owner, roomType);
|
||||
} else {
|
||||
// not a reply, add to the list of message if this sender
|
||||
await registerMessageInRoom(this.messageId, res[0].room_id, this.envelope.date);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async initiateThread() {
|
||||
const inReplyToId = (await getMessageIdOnID(this.inReplyTo))[0]?.message_id;
|
||||
await createRoom(this.envelope.subject, this.ownerId, inReplyToId, RoomType.THREAD).then(
|
||||
async (threadId: number) => {
|
||||
// find parent room infos
|
||||
let roomId: number;
|
||||
let root_id: number;
|
||||
await getThreadInfo(this.inReplyTo).then(async (room) => {
|
||||
// todo room not lenght, reply to transfer ?
|
||||
roomId = room[0].room_id;
|
||||
root_id = room[0].root_id;
|
||||
if (root_id === undefined) root_id = roomId;
|
||||
await registerThread(threadId, roomId, root_id);
|
||||
});
|
||||
|
||||
// add original message
|
||||
await registerMessageInRoom(inReplyToId, threadId, this.envelope.date);
|
||||
// add reply message
|
||||
await registerMessageInRoom(this.messageId, threadId, this.envelope.date);
|
||||
await this.registerMembers(threadId);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async createOrRegisterOnMembers(roomId: number, isThread?: boolean) {
|
||||
const hasSameMembers = await hasSameMembersAsParent(this.messageId, this.inReplyTo);
|
||||
if (hasSameMembers) {
|
||||
await registerMessageInRoom(this.messageId, roomId, this.envelope.date);
|
||||
if (isThread) {
|
||||
await getThreadInfoOnId(roomId).then(async (res) => {
|
||||
let root_id = res[0].root_id;
|
||||
if (root_id == undefined) root_id = res[0].room_id;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
await this.initiateThread();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,8 @@ export default class updateMessage {
|
||||
}
|
||||
|
||||
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 flagsToAdd = this.flags.filter((flag) => !currentFlags.find((f) => flag == f.flag_name));
|
||||
|
||||
46
back/mails/room/Room.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { deleteRoom, getRoomNbMessageAndThread, getRoomOnMessageId } from "../../db/Room-db";
|
||||
|
||||
export default class Room {
|
||||
private _roomId: number;
|
||||
|
||||
constructor() {}
|
||||
|
||||
setRoomId(roomId: number): Room {
|
||||
this._roomId = roomId;
|
||||
return this;
|
||||
}
|
||||
|
||||
get roomId(): number {
|
||||
return this._roomId;
|
||||
}
|
||||
|
||||
async setRoomIdOnMessageId(messageId: number): Promise<Room> {
|
||||
const res = await getRoomOnMessageId(messageId);
|
||||
if (res.length == 0) {
|
||||
throw "Message has no room";
|
||||
}
|
||||
this._roomId = res[0].room_id;
|
||||
return this;
|
||||
}
|
||||
|
||||
// check if the room have threads or messages
|
||||
async shouldDelete(): Promise<boolean> {
|
||||
if (!this._roomId) {
|
||||
throw "shouldDelete needs to have a roomId set.";
|
||||
}
|
||||
const res = await getRoomNbMessageAndThread(this._roomId);
|
||||
if (res.length === 0) return true;
|
||||
if (res[0].nbMessage === 0 && res[0].nbThread === 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async delete(): Promise<Room> {
|
||||
if (!this._roomId) {
|
||||
throw "shouldDelete needs to have a roomId set.";
|
||||
}
|
||||
await deleteRoom(this._roomId);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -5,12 +5,11 @@ export class SmtpInstance {
|
||||
transporter: Transporter;
|
||||
user: string;
|
||||
|
||||
constructor(account: { user: string; password: string }) {
|
||||
// todo store other data
|
||||
constructor(account: { user: string; password: string, smtp_host: string, smtp_port: number }) {
|
||||
this.user = account.user;
|
||||
this.transporter = nodemailer.createTransport({
|
||||
host: "smtp.gmail.com",
|
||||
port: 465,
|
||||
host: account.smtp_host,
|
||||
port: account.smtp_port,
|
||||
secure: true,
|
||||
auth: {
|
||||
user: account.user,
|
||||
|
||||
@@ -49,4 +49,5 @@ export default class MailBuilder {
|
||||
this.message.subject = "RE: " + originSubject;
|
||||
return this;
|
||||
}
|
||||
// https://cr.yp.to/immhf/thread.html
|
||||
}
|
||||
|
||||
28
back/routes/account.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import express from "express";
|
||||
import Account from "../abl/Account-abl";
|
||||
import validator from "../validator/validator";
|
||||
const router = express.Router();
|
||||
|
||||
/**
|
||||
* Return all mailboxes and folders for an user
|
||||
*/
|
||||
router.get("/getAll", async (req, res) => {
|
||||
await validator.validate("getAccounts", req.params, res, Account.getAll);
|
||||
});
|
||||
|
||||
/**
|
||||
* Register a new mailbox inside the app
|
||||
*/
|
||||
router.post("/register", async (req, res) => {
|
||||
await validator.validate("createAccount", req.body, res, Account.register);
|
||||
});
|
||||
|
||||
/**
|
||||
* Return all rooms from a mailbox
|
||||
*/
|
||||
router.get("/:mailboxId/rooms", async (req, res) => {
|
||||
// todo offet limit
|
||||
await validator.validate("getRooms", req.params, res, Account.getRooms);
|
||||
});
|
||||
|
||||
export default router;
|
||||
@@ -1,60 +0,0 @@
|
||||
import express from "express";
|
||||
const router = express.Router();
|
||||
|
||||
import { rooms } from "../abl/rooms";
|
||||
import Message from "../abl/Message-abl";
|
||||
import { messages } from "../abl/messages";
|
||||
import { members } from "../abl/members";
|
||||
import Account from "../abl/Account-abl";
|
||||
import validator from "../validator/validator";
|
||||
import Room from "../abl/Room-abl";
|
||||
|
||||
/**
|
||||
* Return all mailboxes and folders for an user
|
||||
*/
|
||||
router.get("/accounts", async (req, res) => {
|
||||
await validator.validate("getAccounts", req.params, res, Account.getAll);
|
||||
});
|
||||
|
||||
/**
|
||||
* Return all rooms from a mailbox
|
||||
*/
|
||||
router.get("/:mailboxId/rooms", async (req, res) => {
|
||||
// todo offet limit
|
||||
await validator.validate("getRooms", req.params, res, rooms);
|
||||
});
|
||||
|
||||
/**
|
||||
* Return all messages from a room
|
||||
*/
|
||||
router.get("/:roomId/messages", async (req, res) => {
|
||||
await validator.validate("getMessages", req.params, res, messages);
|
||||
});
|
||||
|
||||
/**
|
||||
* Return all members from a room
|
||||
*/
|
||||
router.get("/:roomId/members", async (req, res) => {
|
||||
await validator.validate("getMembers", req.params, res, members);
|
||||
});
|
||||
|
||||
/**
|
||||
* Register a new mailbox inside the app
|
||||
*/
|
||||
router.post("/account", async (req, res) => {
|
||||
await validator.validate("createAccount", req.body, res, Account.register);
|
||||
});
|
||||
|
||||
router.post("/addFlag", async (req, res) => {
|
||||
await validator.validate("addFlag", req.body, res, Message.addFlag);
|
||||
});
|
||||
|
||||
router.post("/removeFlag", async (req, res) => {
|
||||
await validator.validate("removeFlag", req.body, res, Message.removeFlag);
|
||||
});
|
||||
|
||||
router.post("/response", async (req, res) => {
|
||||
await validator.validate("response", req.body, res, Room.response);
|
||||
});
|
||||
|
||||
export default router;
|
||||
23
back/routes/message.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import express from "express";
|
||||
import MessageAbl from "../abl/Message-abl";
|
||||
import validator from "../validator/validator";
|
||||
const router = express.Router();
|
||||
|
||||
router.post("/addFlag", async (req, res) => {
|
||||
await validator.validate("addFlag", req.body, res, MessageAbl.addFlag);
|
||||
});
|
||||
|
||||
router.post("/removeFlag", async (req, res) => {
|
||||
await validator.validate("removeFlag", req.body, res, MessageAbl.removeFlag);
|
||||
});
|
||||
|
||||
router.post("/deleteRemote", async(req, res) => {
|
||||
await validator.validate("delete", req.body, res, MessageAbl.deleteRemoteOnly);
|
||||
});
|
||||
|
||||
router.post("/delete", async(req, res) => {
|
||||
await validator.validate("delete", req.body, res, MessageAbl.deleteEverywhere);
|
||||
});
|
||||
|
||||
|
||||
export default router;
|
||||
28
back/routes/room.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import express from "express";
|
||||
import RoomAbl from "../abl/Room-abl";
|
||||
import validator from "../validator/validator";
|
||||
const router = express.Router();
|
||||
|
||||
/**
|
||||
* Return all messages from a room
|
||||
*/
|
||||
router.get("/:roomId/messages", async (req, res) => {
|
||||
await validator.validate("getMessages", req.params, res, RoomAbl.getMessages);
|
||||
});
|
||||
|
||||
/**
|
||||
* Return all members from a room
|
||||
*/
|
||||
router.get("/:roomId/members", async (req, res) => {
|
||||
await validator.validate("getMembers", req.params, res, RoomAbl.getMembers);
|
||||
});
|
||||
|
||||
router.post("/response", async (req, res) => {
|
||||
await validator.validate("response", req.body, res, RoomAbl.response);
|
||||
});
|
||||
|
||||
router.post("/delete", async (req, res) => {
|
||||
await validator.validate("deleteRoom", req.body, res, RoomAbl.delete);
|
||||
});
|
||||
|
||||
export default router;
|
||||
@@ -1,8 +1,11 @@
|
||||
import express from "express";
|
||||
import cors from "cors";
|
||||
const app = express();
|
||||
import ImapSync from "./mails/EmailManager";
|
||||
import { execQueryAsync, execQuery } from "./db/db";
|
||||
import emailManager from "./mails/EmailManager";
|
||||
import accountRouter from "./routes/account";
|
||||
import roomRouter from "./routes/room";
|
||||
import messageRouter from "./routes/message";
|
||||
|
||||
app.use(express.json());
|
||||
app.use(
|
||||
@@ -13,10 +16,11 @@ app.use(
|
||||
app.use(cors());
|
||||
app.listen(process.env.PORT || 5500);
|
||||
|
||||
import mailRouter from "./routes/mail";
|
||||
import emailManager from "./mails/EmailManager";
|
||||
app.use("/api/mail", mailRouter);
|
||||
app.use("/api/account", accountRouter);
|
||||
app.use("/api/room", roomRouter);
|
||||
app.use("/api/message", messageRouter);
|
||||
|
||||
// create imap and smtp instances for each account
|
||||
emailManager.init();
|
||||
|
||||
// debug reset all tables
|
||||
@@ -31,7 +35,7 @@ if (shouldReset) {
|
||||
if (table.table_name == "mailbox") return;
|
||||
console.log(table.table_name);
|
||||
execQuery("DELETE FROM " + table.table_name, []);
|
||||
// execQuery("DROP TABLE " + table.table_name);
|
||||
// execQuery("DROP TABLE " + table.table_name, []);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,10 +5,12 @@
|
||||
"pwd": { "type": "string" },
|
||||
"xoauth": { "type": "string" },
|
||||
"xoauth2": { "type": "string" },
|
||||
"host": { "type": "string", "format": "hostname" },
|
||||
"port": { "type": "number", "maximum": 65535 },
|
||||
"imapHost": { "type": "string", "format": "hostname" },
|
||||
"smtpHost": { "type": "string", "format": "hostname" },
|
||||
"imapPort": { "type": "number", "maximum": 65535 },
|
||||
"smtpPort": { "type": "number", "maximum": 65535 },
|
||||
"tls": { "type": "boolean" }
|
||||
},
|
||||
"required": ["email", "host", "port", "tls"],
|
||||
"required": ["email", "imapHost", "smtpHost", "imapPort", "smtpPort", "tls"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
16
back/validator/schemas/delete-schema.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"mailboxId": {
|
||||
"type": "number"
|
||||
},
|
||||
"messageId": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"mailboxId",
|
||||
"messageId"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
12
back/validator/schemas/deleteRoom-schema.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"roomId": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"roomId"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -10,6 +10,8 @@ import getMessagesSchema from "./schemas/getMessages-schema.json";
|
||||
import getMembersSchema from "./schemas/getMembers-schema.json";
|
||||
import setFlagSchema from "./schemas/setFlag-schema.json";
|
||||
import responseSchema from "./schemas/response-schema.json";
|
||||
import deleteSchema from "./schemas/delete-schema.json";
|
||||
import deleteRoomSchema from "./schemas/deleteRoom-schema.json";
|
||||
import { Request, Response } from "express";
|
||||
import statusCodes from "../utils/statusCodes";
|
||||
import logger from "../system/Logger";
|
||||
@@ -22,6 +24,8 @@ class Validator {
|
||||
validateGetMembers: any;
|
||||
validateSetFlag: any;
|
||||
validateResponse: any;
|
||||
delete: any;
|
||||
deleteRoom: any;
|
||||
|
||||
constructor() {
|
||||
this.validateCreateAccount = ajv.compile(createAccountSchema);
|
||||
@@ -31,6 +35,8 @@ class Validator {
|
||||
this.validateGetMembers = ajv.compile(getMembersSchema);
|
||||
this.validateSetFlag = ajv.compile(setFlagSchema);
|
||||
this.validateResponse = ajv.compile(responseSchema);
|
||||
this.delete = ajv.compile(deleteSchema);
|
||||
this.deleteRoom = ajv.compile(deleteRoomSchema);
|
||||
}
|
||||
|
||||
_getSchema(name: string): any {
|
||||
@@ -50,6 +56,10 @@ class Validator {
|
||||
return this.validateSetFlag;
|
||||
case "response":
|
||||
return this.validateResponse;
|
||||
case "delete":
|
||||
return this.delete;
|
||||
case "deleteRoom":
|
||||
return this.deleteRoom;
|
||||
default:
|
||||
logger.err(`Schema ${name} not found`);
|
||||
break;
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
# mail
|
||||
|
||||
## Project setup
|
||||
```
|
||||
yarn install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
yarn serve
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
yarn build
|
||||
```
|
||||
|
||||
### Lints and fixes files
|
||||
```
|
||||
yarn lint
|
||||
```
|
||||
|
||||
### Customize configuration
|
||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
||||
1813
front/package-lock.json
generated
@@ -28,8 +28,10 @@
|
||||
"@tiptap/pm": "^2.0.3",
|
||||
"@tiptap/starter-kit": "^2.0.3",
|
||||
"@tiptap/vue-3": "^2.0.3",
|
||||
"popper.js": "^1.16.1",
|
||||
"vue": "^3.2.13",
|
||||
"vue-router": "^4.0.3",
|
||||
"vue-svg-loader": "^0.16.0",
|
||||
"vuex": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup>
|
||||
import { RouterView } from "vue-router";
|
||||
import Sidebar from "./views/sidebar/Sidebar";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -9,17 +10,6 @@ import { RouterView } from "vue-router";
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Sidebar from "./views/sidebar/Sidebar";
|
||||
|
||||
export default {
|
||||
name: "App",
|
||||
components: {
|
||||
Sidebar,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#app {
|
||||
display: flex;
|
||||
|
||||
@@ -7,7 +7,10 @@
|
||||
/* 1d1d23 */
|
||||
--tertiary-background: #2a2a33;
|
||||
--quaternary-background: #303a46;
|
||||
|
||||
--selected: #41474f;
|
||||
--warn: #e4b31d;
|
||||
--danger: #d74453;
|
||||
|
||||
--border-color: #505050;
|
||||
|
||||
@@ -16,6 +19,15 @@
|
||||
|
||||
--svg-selected: brightness(0) saturate(100%) invert(22%) sepia(1%) saturate(7429%) hue-rotate(175deg)
|
||||
brightness(79%) contrast(69%);
|
||||
--svg-warn: brightness(0) saturate(100%) invert(77%) sepia(81%) saturate(1010%) hue-rotate(347deg) brightness(95%)
|
||||
contrast(88%);
|
||||
--svg-danger: brightness(0) saturate(100%) invert(53%) sepia(83%) saturate(4662%) hue-rotate(327deg) brightness(87%)
|
||||
contrast(92%);
|
||||
/* 343a46 */
|
||||
}
|
||||
/* .badge-primary { */
|
||||
/* https://angel-rs.github.io/css-color-filter-generator/ */
|
||||
|
||||
.selected {
|
||||
background-color: var(--selected);
|
||||
}
|
||||
|
||||
1
front/src/assets/svg/contract-up-down-line.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M5.79297 5.20718L12.0001 11.4143L18.2072 5.20718L16.793 3.79297L12.0001 8.58586L7.20718 3.79297L5.79297 5.20718Z M18.2073 18.7928L12.0002 12.5857L5.79312 18.7928L7.20733 20.207L12.0002 15.4141L16.7931 20.207L18.2073 18.7928Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 308 B |
1
front/src/assets/svg/expand-left-fill.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M10.4143 4.58594L10.4142 11.0003L16.0003 11.0004L16.0003 13.0004L10.4142 13.0003L10.4141 19.4144L3 12.0002L10.4143 4.58594ZM18.0002 19.0002V5.00018H20.0002V19.0002H18.0002Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 256 B |
1
front/src/assets/svg/expand-up-down-line.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M18.2073 9.04304L12.0002 2.83594L5.79312 9.04304L7.20733 10.4573L12.0002 5.66436L16.7931 10.4573L18.2073 9.04304Z M5.79297 14.9574L12.0001 21.1646L18.2072 14.9574L16.793 13.5432L12.0001 18.3361L7.20718 13.5432L5.79297 14.9574Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 310 B |
1
front/src/assets/svg/h-1.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path d="M13 20H11V13H4V20H2V4H4V11H11V4H13V20ZM21.0005 8V20H19.0005L19 10.204L17 10.74V8.67L19.5005 8H21.0005Z" fill="#000"></path></svg>
|
||||
|
After Width: | Height: | Size: 221 B |
1
front/src/assets/svg/h-2.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path d="M4 4V11H11V4H13V20H11V13H4V20H2V4H4ZM18.5 8C20.5711 8 22.25 9.67893 22.25 11.75C22.25 12.6074 21.9623 13.3976 21.4781 14.0292L21.3302 14.2102L18.0343 18H22V20H15L14.9993 18.444L19.8207 12.8981C20.0881 12.5908 20.25 12.1893 20.25 11.75C20.25 10.7835 19.4665 10 18.5 10C17.5818 10 16.8288 10.7071 16.7558 11.6065L16.75 11.75H14.75C14.75 9.67893 16.4289 8 18.5 8Z" fill="#000"></path></svg>
|
||||
|
After Width: | Height: | Size: 479 B |
1
front/src/assets/svg/h-3.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path d="M22 8L21.9984 10L19.4934 12.883C21.0823 13.3184 22.25 14.7728 22.25 16.5C22.25 18.5711 20.5711 20.25 18.5 20.25C16.674 20.25 15.1528 18.9449 14.8184 17.2166L16.7821 16.8352C16.9384 17.6413 17.6481 18.25 18.5 18.25C19.4665 18.25 20.25 17.4665 20.25 16.5C20.25 15.5335 19.4665 14.75 18.5 14.75C18.214 14.75 17.944 14.8186 17.7056 14.9403L16.3992 13.3932L19.3484 10H15V8H22ZM4 4V11H11V4H13V20H11V13H4V20H2V4H4Z" fill="#000"></path></svg>
|
||||
|
After Width: | Height: | Size: 526 B |
1
front/src/assets/svg/h-4.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path d="M13 20H11V13H4V20H2V4H4V11H11V4H13V20ZM22 8V16H23.5V18H22V20H20V18H14.5V16.66L19.5 8H22ZM20 11.133L17.19 16H20V11.133Z" fill="#000"></path></svg>
|
||||
|
After Width: | Height: | Size: 237 B |
1
front/src/assets/svg/h-5.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path d="M22 8V10H17.6769L17.2126 12.6358C17.5435 12.5472 17.8912 12.5 18.25 12.5C20.4591 12.5 22.25 14.2909 22.25 16.5C22.25 18.7091 20.4591 20.5 18.25 20.5C16.4233 20.5 14.8827 19.2756 14.4039 17.6027L16.3271 17.0519C16.5667 17.8881 17.3369 18.5 18.25 18.5C19.3546 18.5 20.25 17.6046 20.25 16.5C20.25 15.3954 19.3546 14.5 18.25 14.5C17.6194 14.5 17.057 14.7918 16.6904 15.2478L14.8803 14.3439L16 8H22ZM4 4V11H11V4H13V20H11V13H4V20H2V4H4Z" fill="#000"></path></svg>
|
||||
|
After Width: | Height: | Size: 549 B |
1
front/src/assets/svg/h-6.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path d="M21.097 8L18.499 12.5C20.7091 12.5 22.5 14.2909 22.5 16.5C22.5 18.7091 20.7091 20.5 18.5 20.5C16.2909 20.5 14.5 18.7091 14.5 16.5C14.5 15.7636 14.699 15.0737 15.0461 14.4811L18.788 8H21.097ZM4 4V11H11V4H13V20H11V13H4V20H2V4H4ZM18.5 14.5C17.3954 14.5 16.5 15.3954 16.5 16.5C16.5 17.6046 17.3954 18.5 18.5 18.5C19.6046 18.5 20.5 17.6046 20.5 16.5C20.5 15.3954 19.6046 14.5 18.5 14.5Z" fill="#000"></path></svg>
|
||||
|
After Width: | Height: | Size: 500 B |
1
front/src/assets/svg/heading.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path d="M17 11V4H19V21H17V13H7V21H5V4H7V11H17Z" fill="#000"></path></svg>
|
||||
|
After Width: | Height: | Size: 157 B |
1
front/src/assets/svg/image-add-line.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path d="M21 15V18H24V20H21V23H19V20H16V18H19V15H21ZM21.0082 3C21.556 3 22 3.44495 22 3.9934V13H20V5H4V18.999L14 9L17 12V14.829L14 11.8284L6.827 19H14V21H2.9918C2.44405 21 2 20.5551 2 20.0066V3.9934C2 3.44476 2.45531 3 2.9918 3H21.0082ZM8 7C9.10457 7 10 7.89543 10 9C10 10.1046 9.10457 11 8 11C6.89543 11 6 10.1046 6 9C6 7.89543 6.89543 7 8 7Z" fill="#000"></path></svg>
|
||||
|
After Width: | Height: | Size: 453 B |
1
front/src/assets/svg/paint-fill.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path d="M19.2277 18.7321L20.9955 16.9644L22.7632 18.7321C23.7395 19.7084 23.7395 21.2914 22.7632 22.2677C21.7869 23.244 20.204 23.244 19.2277 22.2677C18.2514 21.2914 18.2514 19.7084 19.2277 18.7321ZM8.87861 1.07959L20.1923 12.3933C20.5828 12.7838 20.5828 13.417 20.1923 13.8075L11.707 22.2928C11.3165 22.6833 10.6833 22.6833 10.2928 22.2928L1.80754 13.8075C1.41702 13.417 1.41702 12.7838 1.80754 12.3933L9.58572 4.61512L7.4644 2.4938L8.87861 1.07959ZM10.9999 6.02934L3.92886 13.1004H18.071L10.9999 6.02934Z" fill="#000"></path></svg>
|
||||
|
After Width: | Height: | Size: 617 B |
1
front/src/assets/svg/pushpin-2-line.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M18 3V5H17V11L19 14V16H13V23H11V16H5V14L7 11V5H6V3H18ZM9 5V11.6056L7.4037 14H16.5963L15 11.6056V5H9Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 184 B |
1
front/src/assets/svg/pushpin-line.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13.8273 1.68994L22.3126 10.1752L20.8984 11.5894L20.1913 10.8823L15.9486 15.125L15.2415 18.6605L13.8273 20.0747L9.58466 15.8321L4.63492 20.7818L3.2207 19.3676L8.17045 14.4179L3.92781 10.1752L5.34202 8.76101L8.87756 8.0539L13.1202 3.81126L12.4131 3.10416L13.8273 1.68994ZM14.5344 5.22548L9.86358 9.89631L7.0417 10.4607L13.5418 16.9608L14.1062 14.1389L18.7771 9.46812L14.5344 5.22548Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 466 B |
1
front/src/assets/svg/task-line.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path d="M19 4H5V20H19V4ZM3 2.9918C3 2.44405 3.44749 2 3.9985 2H19.9997C20.5519 2 20.9996 2.44772 20.9997 3L21 20.9925C21 21.5489 20.5551 22 20.0066 22H3.9934C3.44476 22 3 21.5447 3 21.0082V2.9918ZM11.2929 13.1213L15.5355 8.87868L16.9497 10.2929L11.2929 15.9497L7.40381 12.0607L8.81802 10.6464L11.2929 13.1213Z" fill="#000"></path></svg>
|
||||
|
After Width: | Height: | Size: 420 B |
40
front/src/components/basic/Button.vue
Normal file
@@ -0,0 +1,40 @@
|
||||
<script setup>
|
||||
import { defineProps } from "vue";
|
||||
const props = defineProps({
|
||||
onClick: { type: Function },
|
||||
text: { type: String },
|
||||
class: { type: String },
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span>
|
||||
<button :class="props.class" @click="props.onClick">{{ props.text }}</button>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
button {
|
||||
padding: 5px;
|
||||
padding: 7px 18px;
|
||||
background-color: #09a35b;
|
||||
color: var(--primary-text);
|
||||
border-radius: 5px;
|
||||
border: none;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
transition: opacity 0.5s;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
&.danger {
|
||||
background-color: var(--danger);
|
||||
}
|
||||
|
||||
&.cancel {
|
||||
background-color: var(--quaternary-background);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
70
front/src/components/basic/Input.vue
Normal file
@@ -0,0 +1,70 @@
|
||||
<script setup lang="ts">
|
||||
import { defineProps, defineEmits, withDefaults } from "vue";
|
||||
|
||||
export interface Props {
|
||||
type: string;
|
||||
required: boolean;
|
||||
onChange: any;
|
||||
placeholder: string;
|
||||
label: string;
|
||||
vModel: string;
|
||||
modelValue: any;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
required: () => false,
|
||||
});
|
||||
|
||||
defineEmits(["update:modelValue"]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span>
|
||||
<label v-show="props.label">{{ props.label }}</label>
|
||||
<input
|
||||
:value="modelValue"
|
||||
@input="$emit('update:modelValue', $event.target.value)"
|
||||
:type="props.type"
|
||||
:required="props.required"
|
||||
@change="props.onChange"
|
||||
:placeholder="props.placeholder"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
label {
|
||||
color: var(--secondary-text);
|
||||
}
|
||||
|
||||
input {
|
||||
-webkit-box-flex: 1;
|
||||
background-color: var(--quaternary-background);
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
color: var(--primary-text);
|
||||
-ms-flex: 1;
|
||||
flex: 1;
|
||||
font-family: inherit;
|
||||
font-size: 1.4rem;
|
||||
font-weight: 400;
|
||||
min-width: 0;
|
||||
padding: 8px 9px;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
input[type="number"] {
|
||||
appearance: textfield;
|
||||
}
|
||||
|
||||
/* Chrome, Safari, Edge, Opera */
|
||||
input::-webkit-outer-spin-button,
|
||||
input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -2,6 +2,8 @@
|
||||
import { ref, computed, watchEffect } from "vue";
|
||||
import Modal from "./Modal";
|
||||
import API from "../../services/imapAPI";
|
||||
import Input from "../basic/Input.vue";
|
||||
import Button from "../basic/Button.vue";
|
||||
|
||||
const modal = ref(false);
|
||||
|
||||
@@ -9,35 +11,49 @@ const email = ref("");
|
||||
const pwd = ref("");
|
||||
const xoauth = ref("");
|
||||
const xoauth2 = ref("");
|
||||
const host = ref("");
|
||||
const port = ref(993);
|
||||
const imapHost = ref("");
|
||||
const imapPort = ref(993);
|
||||
const smtpHost = ref("");
|
||||
const smtpPort = ref(465);
|
||||
|
||||
const error = ref("");
|
||||
|
||||
const knownHosts = {
|
||||
"outlook.com": {
|
||||
host: "outlook.office365.com",
|
||||
imap: "outlook.office365.com",
|
||||
smtp: "outlook.office365.com",
|
||||
},
|
||||
"hotmail.com": {
|
||||
host: "outlook.office365.com",
|
||||
imap: "outlook.office365.com",
|
||||
smtp: "outlook.office365.com",
|
||||
},
|
||||
"live.com": {
|
||||
host: "outlook.office365.com",
|
||||
imap: "outlook.office365.com",
|
||||
smtp: "outlook.office365.com",
|
||||
},
|
||||
"zoho.com": {
|
||||
host: "imap.zoho.eu",
|
||||
imap: "imap.zoho.eu",
|
||||
smtp: "smtp.zoho.eu",
|
||||
},
|
||||
"yahoo.com": {
|
||||
host: "imap.mail.yahoo.com",
|
||||
imap: "imap.mail.yahoo.com",
|
||||
smtp: "smtp.mail.yahoo.com",
|
||||
},
|
||||
"icloud.com": {
|
||||
host: "imap.mail.me.com",
|
||||
imap: "imap.mail.me.com",
|
||||
smtp: "smtp.mail.me.com",
|
||||
},
|
||||
};
|
||||
|
||||
const refHost = computed({
|
||||
const refSmtpHost = computed({
|
||||
set: (val) => {
|
||||
host.value = val;
|
||||
smtpHost.value = val;
|
||||
},
|
||||
});
|
||||
|
||||
const refImapHost = computed({
|
||||
set: (val) => {
|
||||
imapHost.value = val;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -60,7 +76,7 @@ function checkError() {
|
||||
err.value = "You need at least one authentification method.";
|
||||
} else if ([pwd.value, xoauth.value, xoauth2.value].filter((val) => val != "").length > 1) {
|
||||
err.value = "You need only one authentification method.";
|
||||
} else if (host.value == "" || port.value == "") {
|
||||
} else if (smtpHost.value == "" || smtpPort.value == "" || imapHost.value == "" || imapPort.value == "") {
|
||||
err.value = "You need to complete the port and the host.";
|
||||
} else {
|
||||
err.value = "";
|
||||
@@ -75,8 +91,10 @@ function addAccountRequest() {
|
||||
pwd: pwd.value,
|
||||
xoauth: xoauth.value,
|
||||
xoauth2: xoauth2.value,
|
||||
host: host.value,
|
||||
port: port.value,
|
||||
imapHost: imapHost.value,
|
||||
imapPort: imapPort.value,
|
||||
smtpHost: smtpHost.value,
|
||||
smtpPort: smtpPort.value,
|
||||
tls: true,
|
||||
};
|
||||
|
||||
@@ -98,76 +116,75 @@ watchEffect(() => {
|
||||
function mailChange() {
|
||||
if (email.value.includes("@")) {
|
||||
const domain = email.value.split("@")[1];
|
||||
if (!knownHosts[domain]) {
|
||||
refHost.value = "imap." + domain;
|
||||
} else {
|
||||
refHost.value = knownHosts[domain].host;
|
||||
if (imapHost.value == "") {
|
||||
refImapHost.value = knownHosts[domain]?.imap ?? `imap.${domain}`;
|
||||
}
|
||||
if (smtpHost.value == "") {
|
||||
refSmtpHost.value = knownHosts[domain]?.smtp ?? `smtp.${domain}`;
|
||||
}
|
||||
// todo check if manual
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<button @click="modal = true">Add Account</button>
|
||||
<Button :onClick="() => (modal = true)" text="Add Account" />
|
||||
<Modal v-if="modal" title="Add new account" @close-modal="modal = false">
|
||||
<template v-slot:body>
|
||||
<div class="field">
|
||||
<label>Email: </label>
|
||||
<input @change="mailChange" v-model="email" type="email" required />
|
||||
<Input
|
||||
label="Email:"
|
||||
:onChange="mailChange"
|
||||
v-model="email"
|
||||
type="email"
|
||||
placeholder="email"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<fieldset>
|
||||
<div class="field">
|
||||
<Input label="Plain password:" v-model="pwd" type="password" />
|
||||
</div>
|
||||
<!-- <fieldset>
|
||||
<legend>Authentification method</legend>
|
||||
<div class="field">
|
||||
<label>Plain password:</label>
|
||||
<input v-model="pwd" type="password" />
|
||||
<Input label="Xoauth:" v-model="xoauth" type="text" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Xoauth:</label>
|
||||
<input v-model="xoauth" type="text" />
|
||||
<Input label="Xoauth2:" v-model="xoauth2" type="text" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Xoauth2:</label>
|
||||
<input v-model="xoauth2" type="text" />
|
||||
</div>
|
||||
</fieldset>
|
||||
</fieldset> -->
|
||||
|
||||
<div class="config">
|
||||
<input v-model="host" id="host" type="text" placeholder="imap host" />
|
||||
<input v-model="port" id="port" type="number" placeholder="port" />
|
||||
</div>
|
||||
<!-- tls -->
|
||||
<div>
|
||||
<button :disabled="error != ''" @click="addAccountRequest">Add</button>
|
||||
{{ error }}
|
||||
</div>
|
||||
<fieldset class="config">
|
||||
<legend>Imap</legend>
|
||||
<Input v-model="imapHost" class="host" type="text" placeholder="host" />
|
||||
<Input v-model="imapPort" class="port" type="number" placeholder="port" />
|
||||
</fieldset>
|
||||
<fieldset class="config">
|
||||
<legend>Smtp</legend>
|
||||
<Input v-model="smtpHost" class="host" type="text" placeholder="host" />
|
||||
<Input v-model="smtpPort" class="port" type="number" placeholder="port" />
|
||||
</fieldset>
|
||||
</template>
|
||||
<!-- tls -->
|
||||
<template v-slot:actions>
|
||||
<Button :disabled="error != ''" :onClick="addAccountRequest" text="Add" />
|
||||
{{ error }}
|
||||
</template>
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
/* Chrome, Safari, Edge, Opera */
|
||||
input::-webkit-outer-spin-button,
|
||||
input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
input[type="number"] {
|
||||
appearance: textfield;
|
||||
}
|
||||
|
||||
<style lang="scss">
|
||||
.field {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.field > input {
|
||||
margin-top: 2px;
|
||||
width: 95%;
|
||||
.field {
|
||||
input {
|
||||
margin-top: 2px;
|
||||
width: 95%;
|
||||
}
|
||||
}
|
||||
|
||||
fieldset {
|
||||
@@ -179,54 +196,16 @@ fieldset {
|
||||
}
|
||||
|
||||
.config {
|
||||
display: block;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
#host {
|
||||
.host {
|
||||
margin-right: 8px;
|
||||
width: calc(95% - 100px);
|
||||
}
|
||||
|
||||
#port {
|
||||
.port {
|
||||
display: inline-flex;
|
||||
width: 70px;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 5px;
|
||||
padding: 7px 18px;
|
||||
background-color: #09a35b;
|
||||
color: var(--primary-text);
|
||||
border-radius: 5px;
|
||||
border: none;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
transition: opacity 0.5s;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
input {
|
||||
-webkit-box-flex: 1;
|
||||
background-color: var(--quaternary-background);
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
color: var(--primary-text);
|
||||
-ms-flex: 1;
|
||||
flex: 1;
|
||||
font-family: inherit;
|
||||
font-size: 1.4rem;
|
||||
font-weight: 400;
|
||||
min-width: 0;
|
||||
padding: 8px 9px;
|
||||
}
|
||||
|
||||
label {
|
||||
color: var(--secondary-text);
|
||||
}
|
||||
|
||||
input:focus {
|
||||
outline: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
54
front/src/components/modals/ConfirmationModal.vue
Normal file
@@ -0,0 +1,54 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, defineProps, withDefaults } from "vue";
|
||||
import Modal from "./Modal.vue";
|
||||
import Button from "../basic/Button.vue";
|
||||
|
||||
const modal = ref(false);
|
||||
|
||||
export interface Props {
|
||||
title: string;
|
||||
isDanger: boolean;
|
||||
onConfirmation: any;
|
||||
onCancel?: any;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
title: () => "Confirmation",
|
||||
isDanger: () => true,
|
||||
});
|
||||
|
||||
const error = ref("");
|
||||
|
||||
const handleContinue = async () => {
|
||||
try {
|
||||
if (props.onConfirmation) {
|
||||
await props.onConfirmation();
|
||||
}
|
||||
modal.value = false;
|
||||
} catch (err: any) {
|
||||
error.value = err.message;
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = async () => {
|
||||
if (props.onCancel) {
|
||||
await props.onCancel();
|
||||
}
|
||||
modal.value = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Modal v-if="modal" :title="props.title" @close-modal="modal = false">
|
||||
<template v-slot:body> Are you sure you want to do that ? </template>
|
||||
<template v-slot:actions>
|
||||
<Button :onClick="handleCancel" class="cancel" text="Cancel" />
|
||||
<Button :onClick="handleContinue" :class="props.isDanger ? 'danger' : ''" text="Confirm" />
|
||||
{{ error }}
|
||||
</template>
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss"></style>
|
||||
@@ -1,26 +1,59 @@
|
||||
<script setup lang="ts">
|
||||
import { Message } from "@/store/models/model";
|
||||
import { ref, watch, defineProps, PropType } from "vue";
|
||||
import { Address, Message, Room } from "@/store/models/model";
|
||||
import { ref, Ref, watch, defineProps, PropType } from "vue";
|
||||
import Content from "../structure/message/Content.vue";
|
||||
import Modal from "./Modal.vue";
|
||||
import MemberList from "@/views/room/MemberList.vue";
|
||||
import { removeDuplicates } from "@/utils/array";
|
||||
import { decodeEmojis } from "@/utils/string";
|
||||
|
||||
const props = defineProps({ messageId: { type: Number, require: true }, message: Object as PropType<Message> });
|
||||
const props = defineProps({
|
||||
messageId: { type: Number, require: true },
|
||||
message: Object as PropType<Message>,
|
||||
room: Object as PropType<Room>,
|
||||
});
|
||||
|
||||
const messageId = ref(-1);
|
||||
const fromA = ref<Address[]>([]);
|
||||
const toA = ref<Address[]>([]);
|
||||
const ccA = ref<Address[]>([]);
|
||||
|
||||
const getAddr = (ids: string | undefined, addrs: Address[] | undefined, res: Ref<Address[]>) => {
|
||||
res.value = [];
|
||||
if (!ids || !addrs) return;
|
||||
let idsClean = removeDuplicates(ids.split(","));
|
||||
idsClean.forEach((id) => {
|
||||
let addr = addrs.find((member) => member.id === parseInt(id));
|
||||
if (addr) {
|
||||
res.value.push(addr);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.messageId,
|
||||
(newMessageId: number | undefined) => {
|
||||
if (!newMessageId) return;
|
||||
messageId.value = newMessageId;
|
||||
getAddr(props.message?.fromA, props.room?.members, fromA);
|
||||
getAddr(props.message?.toA, props.room?.members, toA);
|
||||
getAddr(props.message?.ccA, props.room?.members, ccA);
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="main">
|
||||
<Modal v-if="messageId != -1" @close-modal="() => $emit('close')">
|
||||
<Modal v-if="messageId != -1" @close-modal="() => $emit('close')" id="modal">
|
||||
<template v-slot:header>
|
||||
<h2 id="header">
|
||||
{{ decodeEmojis(props.message?.subject) ?? "No Object" }}
|
||||
</h2>
|
||||
</template>
|
||||
<template v-slot:body>
|
||||
<div>{{ props.message?.subject ?? "No Object" }}</div>
|
||||
<MemberList type="from" :members="fromA" />
|
||||
<MemberList type="to" :members="toA" />
|
||||
<MemberList v-if="ccA.length > 0" type="cc" :members="ccA" />
|
||||
<Content type="large" :content="props.message?.content" class="content" />
|
||||
</template>
|
||||
</Modal>
|
||||
@@ -31,6 +64,11 @@ watch(
|
||||
.main {
|
||||
min-width: 700px;
|
||||
}
|
||||
|
||||
#header {
|
||||
width: 700px;
|
||||
}
|
||||
|
||||
/* todo define size automatically */
|
||||
.content {
|
||||
width: 700px;
|
||||
|
||||
@@ -26,13 +26,18 @@ onUnmounted(() => {
|
||||
<div class="modal-wrapper">
|
||||
<div class="modal" v-on-click-outside="close">
|
||||
<header class="modal-header">
|
||||
<h2>{{ props.title }}</h2>
|
||||
<slot name="header">
|
||||
<h2>{{ props.title }}</h2>
|
||||
</slot>
|
||||
<div class="close-button" @click="close"></div>
|
||||
</header>
|
||||
|
||||
<div class="modal-body">
|
||||
<slot name="body"> This is the default body! </slot>
|
||||
</div>
|
||||
<div class="modal-actions">
|
||||
<slot name="actions"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -58,13 +63,29 @@ onUnmounted(() => {
|
||||
border-radius: 5px;
|
||||
color: var(--primary-text);
|
||||
background-color: var(--secondary-background);
|
||||
padding: 20px;
|
||||
padding: 10px;
|
||||
min-width: 220px;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.modal-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
display: inline-block;
|
||||
font-size: 2.4rem;
|
||||
|
||||
@@ -1,27 +1,29 @@
|
||||
<script setup>
|
||||
import imapAPI from "@/services/imapAPI";
|
||||
import { inject, ref } from "vue";
|
||||
import store from "@/store/store";
|
||||
import { BubbleMenu, useEditor, EditorContent, FloatingMenu } from "@tiptap/vue-3";
|
||||
import { inject } from "vue";
|
||||
import Underline from "@tiptap/extension-underline";
|
||||
import Bold from "@tiptap/extension-bold";
|
||||
import Document from "@tiptap/extension-document";
|
||||
import Paragraph from "@tiptap/extension-paragraph";
|
||||
import Text from "@tiptap/extension-text";
|
||||
import Highlight from "@tiptap/extension-highlight";
|
||||
import Italic from "@tiptap/extension-italic";
|
||||
import Link from "@tiptap/extension-link";
|
||||
import Strike from "@tiptap/extension-strike";
|
||||
import History from "@tiptap/extension-history";
|
||||
import TextAlign from "@tiptap/extension-text-align";
|
||||
import OrderedList from "@tiptap/extension-ordered-list";
|
||||
import bulletList from "@tiptap/extension-bullet-list";
|
||||
import HardBreak from "@tiptap/extension-hard-break";
|
||||
import heading from "@tiptap/extension-heading";
|
||||
import Image from "@tiptap/extension-image";
|
||||
import TaskList from "@tiptap/extension-task-list";
|
||||
import imapAPI from "@/services/imapAPI";
|
||||
import SvgLoader from "@/components/utils/SvgLoader.vue";
|
||||
|
||||
import { BubbleMenu, useEditor, EditorContent } from "@tiptap/vue-3";
|
||||
import Document from "@tiptap/extension-document";
|
||||
import Text from "@tiptap/extension-text";
|
||||
import Paragraph from "@tiptap/extension-paragraph";
|
||||
import HardBreak from "@tiptap/extension-hard-break";
|
||||
import History from "@tiptap/extension-history";
|
||||
import Bold from "@tiptap/extension-bold";
|
||||
import Italic from "@tiptap/extension-italic";
|
||||
import Strike from "@tiptap/extension-strike";
|
||||
import Underline from "@tiptap/extension-underline";
|
||||
import Highlight from "@tiptap/extension-highlight";
|
||||
import Link from "@tiptap/extension-link";
|
||||
import Heading from "@tiptap/extension-heading";
|
||||
import Image from "@tiptap/extension-image";
|
||||
import TextAlign from "@tiptap/extension-text-align";
|
||||
import ListItem from "@tiptap/extension-list-item";
|
||||
import OrderedList from "@tiptap/extension-ordered-list";
|
||||
import BulletList from "@tiptap/extension-bullet-list";
|
||||
|
||||
// todo task list
|
||||
// todo style link
|
||||
// todo link and drop cursor
|
||||
// todo mentions
|
||||
@@ -37,18 +39,25 @@ const editor = useEditor({
|
||||
depth: 10,
|
||||
}),
|
||||
// marks
|
||||
Bold,
|
||||
Italic,
|
||||
Strike,
|
||||
Underline,
|
||||
Link,
|
||||
Bold,
|
||||
Strike,
|
||||
Italic,
|
||||
// nodes
|
||||
Highlight.configure({
|
||||
multicolor: true,
|
||||
}),
|
||||
Heading.configure({
|
||||
level: [2, 3, 4],
|
||||
}),
|
||||
Image,
|
||||
TextAlign.configure({
|
||||
types: ["heading", "paragraph"],
|
||||
}),
|
||||
ListItem,
|
||||
OrderedList,
|
||||
BulletList,
|
||||
],
|
||||
content: `
|
||||
<p>
|
||||
@@ -61,8 +70,9 @@ const editor = useEditor({
|
||||
});
|
||||
|
||||
const room = inject("room");
|
||||
const showOptions = ref(true);
|
||||
|
||||
const send = () => {
|
||||
function sendMessage() {
|
||||
const htmlContent = editor.value.getHTML();
|
||||
const textContent = editor.value.getText();
|
||||
const accountUser = store.getters.accountOfRoom(room.value.id);
|
||||
@@ -72,6 +82,19 @@ const send = () => {
|
||||
text: textContent,
|
||||
html: htmlContent,
|
||||
});
|
||||
}
|
||||
|
||||
const getClasses = (isActive, disabled = false) => {
|
||||
let classes = [];
|
||||
if (isActive) {
|
||||
classes.push("is-active");
|
||||
}
|
||||
if (!disabled) {
|
||||
classes.push("selectable");
|
||||
} else {
|
||||
classes.push("disabled");
|
||||
}
|
||||
return classes.join(",");
|
||||
};
|
||||
|
||||
// todo subject input when dm of group...
|
||||
@@ -83,61 +106,181 @@ const send = () => {
|
||||
</script>
|
||||
<!-- todo tooltip -->
|
||||
<template>
|
||||
<div class="main">
|
||||
<div v-if="editor">
|
||||
<bubble-menu class="bubble-menu" :tippy-options="{ duration: 100 }" :editor="editor">
|
||||
<div v-if="editor">
|
||||
<div id="options" v-if="showOptions">
|
||||
<span class="category">
|
||||
<SvgLoader
|
||||
svg="bold"
|
||||
@click="editor.chain().focus().toggleBold().run()"
|
||||
:class="[{ 'is-active': editor.isActive('bold') }, 'editorOption']"
|
||||
:classes="getClasses(editor.isActive('bold'))"
|
||||
v-tooltip="{ text: 'Bold', shortcut: ['Ctrl', 'B'] }"
|
||||
/>
|
||||
<SvgLoader
|
||||
svg="italic"
|
||||
@click="editor.chain().focus().toggleItalic().run()"
|
||||
:class="[{ 'is-active': editor.isActive('italic') }, 'editorOption']"
|
||||
v-tooltip="'Italic'"
|
||||
:classes="getClasses(editor.isActive('italic'))"
|
||||
v-tooltip="{ text: 'Italic', shortcut: ['Ctrl', 'I'] }"
|
||||
/>
|
||||
<SvgLoader
|
||||
svg="strikethrough"
|
||||
@click="editor.chain().focus().toggleStrike().run()"
|
||||
:class="[{ 'is-active': editor.isActive('strike') }, 'editorOption']"
|
||||
:classes="getClasses(editor.isActive('strike'))"
|
||||
v-tooltip="{ text: 'Strike', shortcut: ['Ctrl', 'Shift', 'X'] }"
|
||||
/>
|
||||
<SvgLoader
|
||||
svg="underline"
|
||||
@click="editor.chain().focus().toggleUnderline().run()"
|
||||
:class="[{ 'is-active': editor.isActive('underline') }, 'editorOption']"
|
||||
:classes="getClasses(editor.isActive('underline'))"
|
||||
v-tooltip="{ text: 'Underline', shortcut: ['Ctrl', 'U'] }"
|
||||
/>
|
||||
<SvgLoader
|
||||
svg="font-color"
|
||||
@click="editor.commands.toggleHighlight({ color: '#ffcc00' })"
|
||||
:class="[{ 'is-active': editor.isActive('highlight') }, 'editorOption']"
|
||||
:classes="getClasses(editor.isActive('highlight'))"
|
||||
v-tooltip="{ text: 'Highlight', shortcut: ['Ctrl', 'Shift', 'H'] }"
|
||||
/>
|
||||
</bubble-menu>
|
||||
|
||||
<floating-menu class="floating-menu" :tippy-options="{ duration: 100 }" :editor="editor">
|
||||
<button
|
||||
@click="editor.chain().focus().toggleHeading({ level: 1 }).run()"
|
||||
:class="{ 'is-active': editor.isActive('heading', { level: 1 }) }"
|
||||
>
|
||||
H1
|
||||
</button>
|
||||
<button
|
||||
</span>
|
||||
<span class="category">
|
||||
<SvgLoader
|
||||
svg="h-2"
|
||||
@click="editor.chain().focus().toggleHeading({ level: 2 }).run()"
|
||||
:class="{ 'is-active': editor.isActive('heading', { level: 2 }) }"
|
||||
>
|
||||
H2
|
||||
</button>
|
||||
<button
|
||||
:classes="getClasses(editor.isActive({ level: 2 }))"
|
||||
v-tooltip="{ text: 'Heading 2', shortcut: ['Ctrl', 'Alt', '2'] }"
|
||||
/>
|
||||
<SvgLoader
|
||||
svg="h-3"
|
||||
@click="editor.chain().focus().toggleHeading({ level: 3 }).run()"
|
||||
:classes="getClasses(editor.isActive({ level: 3 }))"
|
||||
v-tooltip="{ text: 'Heading 3', shortcut: ['Ctrl', 'Alt', '3'] }"
|
||||
/>
|
||||
<SvgLoader
|
||||
svg="h-4"
|
||||
@click="editor.chain().focus().toggleHeading({ level: 4 }).run()"
|
||||
:classes="getClasses(editor.isActive({ level: 4 }))"
|
||||
v-tooltip="{ text: 'Heading 4', shortcut: ['Ctrl', 'Alt', '4'] }"
|
||||
/>
|
||||
</span>
|
||||
<!-- <SvgLoader
|
||||
svg="link"
|
||||
@click="editor.chain().focus().toggleBold().run()"
|
||||
:class="[{ 'is-active': editor.isActive('bold') }, 'editorOption']"
|
||||
/> -->
|
||||
<span class="category">
|
||||
<SvgLoader
|
||||
svg="list-ordered"
|
||||
@click="editor.chain().focus().toggleOrderedList().run()"
|
||||
:classes="getClasses(editor.isActive('orderedList'))"
|
||||
v-tooltip="{ text: 'Ordered List', shortcut: ['Ctrl', 'Alt', '7'] }"
|
||||
/>
|
||||
<SvgLoader
|
||||
svg="list-unordered"
|
||||
@click="editor.chain().focus().toggleBulletList().run()"
|
||||
:class="{ 'is-active': editor.isActive('bulletList') }"
|
||||
>
|
||||
Bullet List
|
||||
</button>
|
||||
</floating-menu>
|
||||
</div>
|
||||
:classes="getClasses(editor.isActive('bulletList'))"
|
||||
v-tooltip="{ text: 'Unordered List', shortcut: ['Ctrl', 'Alt', '8'] }"
|
||||
/>
|
||||
</span>
|
||||
<span class="category">
|
||||
<SvgLoader
|
||||
svg="align-left"
|
||||
@click="editor.chain().focus().setTextAlign('left').run()"
|
||||
:classes="getClasses(editor.isActive({ textAlign: 'left' }))"
|
||||
v-tooltip="{ text: 'Align Left', shortcut: ['Ctrl', 'Shift', 'L'] }"
|
||||
/>
|
||||
<SvgLoader
|
||||
svg="align-center"
|
||||
@click="editor.chain().focus().setTextAlign('center').run()"
|
||||
:classes="getClasses(editor.isActive({ textAlign: 'center' }))"
|
||||
v-tooltip="{ text: 'Align Center', shortcut: ['Ctrl', 'Shift', 'E'] }"
|
||||
/>
|
||||
<SvgLoader
|
||||
svg="align-right"
|
||||
@click="editor.chain().focus().setTextAlign('right').run()"
|
||||
:classes="getClasses(editor.isActive({ textAlign: 'right' }))"
|
||||
v-tooltip="{ text: 'Align Right', shortcut: ['Ctrl', 'Shift', 'R'] }"
|
||||
/>
|
||||
<SvgLoader
|
||||
svg="align-justify"
|
||||
@click="editor.chain().focus().setTextAlign('justify').run()"
|
||||
:classes="getClasses(editor.isActive({ textAlign: 'justify' }))"
|
||||
v-tooltip="{ text: 'Justify', shortcut: ['Ctrl', 'Shift', 'J'] }"
|
||||
/>
|
||||
</span>
|
||||
|
||||
<!-- <SvgLoader
|
||||
svg="font-color"
|
||||
@click="editor.chain().focus().toggleBold().run()"
|
||||
:class="[{ 'is-active': editor.isActive('bold') }, 'editorOption']"
|
||||
/>
|
||||
<SvgLoader
|
||||
svg="paint-fill"
|
||||
@click="editor.chain().focus().toggleBold().run()"
|
||||
:class="[{ 'is-active': editor.isActive('bold') }, 'editorOption']"
|
||||
/> -->
|
||||
<span class="category">
|
||||
<SvgLoader
|
||||
svg="indent-increase"
|
||||
@click="!$event.disabled ? editor.chain().focus().sinkListItem('listItem').run() : ''"
|
||||
:classes="getClasses(false, !editor.can().sinkListItem('listItem'))"
|
||||
:isDisabled="!editor.can().sinkListItem('listItem')"
|
||||
v-tooltip="{ text: 'Sink Item', shortcut: ['Tab'] }"
|
||||
/>
|
||||
<SvgLoader
|
||||
svg="indent-decrease"
|
||||
@click="!$event.disabled ? editor.chain().focus().liftListItem('listItem').run() : ''"
|
||||
:classes="getClasses(false, !editor.can().liftListItem('listItem'))"
|
||||
:isDisabled="!editor.can().liftListItem('listItem')"
|
||||
v-tooltip="{ text: 'Lift Item', shortcut: ['Shift', 'Tab'] }"
|
||||
/>
|
||||
</span>
|
||||
<!-- <SvgLoader
|
||||
svg="image-add-line"
|
||||
@click="editor.chain().focus().toggleBold().run()"
|
||||
:class="[{ 'is-active': editor.isActive('bold') }, 'editorOption']"
|
||||
/> -->
|
||||
</div>
|
||||
<bubble-menu
|
||||
:class="[showOptions ? 'hide' : '', 'bubble-menu']"
|
||||
:tippy-options="{ duration: 100 }"
|
||||
:editor="editor"
|
||||
>
|
||||
<SvgLoader
|
||||
svg="bold"
|
||||
@click="editor.chain().focus().toggleBold().run()"
|
||||
:class="[{ 'is-active': editor.isActive('bold') }, 'editorOption']"
|
||||
/>
|
||||
<SvgLoader
|
||||
svg="italic"
|
||||
@click="editor.chain().focus().toggleItalic().run()"
|
||||
:class="[{ 'is-active,selectable': editor.isActive('italic') }, 'editorOption']"
|
||||
:classes="[{ 'is-active,selectable': editor.isActive('italic') }, 'selectable'].join()"
|
||||
/>
|
||||
<SvgLoader
|
||||
svg="strikethrough"
|
||||
@click="editor.chain().focus().toggleStrike().run()"
|
||||
:class="[{ 'is-active': editor.isActive('strike') }, 'editorOption']"
|
||||
/>
|
||||
<SvgLoader
|
||||
svg="underline"
|
||||
@click="editor.chain().focus().toggleUnderline().run()"
|
||||
:class="[{ 'is-active': editor.isActive('underline') }, 'editorOption']"
|
||||
/>
|
||||
<SvgLoader
|
||||
svg="font-color"
|
||||
@click="editor.commands.toggleHighlight({ color: '#ffcc00' })"
|
||||
:class="[{ 'is-active': editor.isActive('highlight') }, 'editorOption']"
|
||||
/>
|
||||
</bubble-menu>
|
||||
</div>
|
||||
<div class="main">
|
||||
<editor-content class="editor" :editor="editor" aria-expanded="false" />
|
||||
<SvgLoader class="sendMessage" svg="send-plane-line" @click="send()" />
|
||||
<div class="actions">
|
||||
<SvgLoader class="sendMessage" svg="send-plane-line" @click="sendMessage()" />
|
||||
<SvgLoader
|
||||
:class="[{ selected: showOptions }, 'moreOptions']"
|
||||
svg="menu-add-line"
|
||||
@click="showOptions = !showOptions"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -151,43 +294,64 @@ const send = () => {
|
||||
.main {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 10px;
|
||||
padding: 0 10px 10px 10px;
|
||||
min-height: 90px;
|
||||
max-height: 500px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.hide {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.editor {
|
||||
background-color: var(--secondary-background);
|
||||
flex: 1;
|
||||
margin-right: 10px;
|
||||
border-radius: 10px;
|
||||
padding: 0 10px;
|
||||
overflow: auto;
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
.bubble-menu {
|
||||
.bubble-menu,
|
||||
#options,
|
||||
.category {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 2px;
|
||||
background-color: var(--primary-background);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
#options {
|
||||
margin: 2px 0 4px 11px;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.bubble-menu,
|
||||
.category {
|
||||
outline: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.editorOption {
|
||||
border-radius: 6px;
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.moreOptions,
|
||||
.sendMessage {
|
||||
border-radius: 6px;
|
||||
|
||||
::v-deep img {
|
||||
padding: 3px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover,
|
||||
&.is-active {
|
||||
background-color: var(--selected);
|
||||
}
|
||||
|
||||
::v-deep img {
|
||||
filter: var(--svg-primary-text);
|
||||
padding: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sendMessage {
|
||||
filter: var(--svg-primary-text);
|
||||
cursor: pointer;
|
||||
.moreOptions:hover {
|
||||
background-color: var(--selected);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,27 +1,25 @@
|
||||
<script setup lang="ts">
|
||||
import { defineProps, PropType } from "vue";
|
||||
import { defineProps, withDefaults, ref } 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";
|
||||
import SvgLoader from "@/components/utils/SvgLoader.vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { displayAddresses } from "@/utils/address";
|
||||
|
||||
const props = defineProps({
|
||||
msg: Object as PropType<Message>,
|
||||
members: Array as PropType<Address[]>,
|
||||
export interface Props {
|
||||
msg: Message;
|
||||
members: Address[];
|
||||
compact: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
compact: () => true,
|
||||
});
|
||||
|
||||
const displayAddresses = (addressIds: string[] | undefined): string => {
|
||||
if (!addressIds) return "";
|
||||
addressIds = removeDuplicates(addressIds);
|
||||
let res = "";
|
||||
addressIds.forEach((addressId) => {
|
||||
const address = props.members?.find((member) => member.id === parseInt(addressId));
|
||||
if (address) res += address.email;
|
||||
});
|
||||
return res;
|
||||
};
|
||||
const compact = ref(props.compact);
|
||||
|
||||
const classes = (): string => {
|
||||
const flags = props.msg?.flags;
|
||||
@@ -35,6 +33,17 @@ const classes = (): string => {
|
||||
}
|
||||
return "msg-basic";
|
||||
};
|
||||
|
||||
const style = (prop: string): string => {
|
||||
// get color of the member that send the message
|
||||
let member = props.members?.find((member) => member.id === parseInt(props.msg?.fromA?.split(",")[0]));
|
||||
if (member?.color) {
|
||||
return `${prop}: ${member.color} !important`;
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
const router = useRouter();
|
||||
</script>
|
||||
<!-- to if to is more than me
|
||||
cc -->
|
||||
@@ -43,9 +52,9 @@ const classes = (): string => {
|
||||
attachments -->
|
||||
<template>
|
||||
<div class="message" @dblclick="$emit('openMessageView', props.msg?.id)">
|
||||
<div id="context">
|
||||
<div id="context" :style="style('background-color')">
|
||||
<div class="left" id="profile">
|
||||
{{ displayAddresses(props.msg?.fromA?.split(",")) }} - {{ props.msg?.fromA }}
|
||||
{{ displayAddresses(props.msg?.fromA?.split(","), props.members) }} - {{ props.msg?.fromA }}
|
||||
</div>
|
||||
<div class="middle">{{ decodeEmojis(props.msg?.subject) }}</div>
|
||||
<div class="right" id="date">
|
||||
@@ -62,20 +71,44 @@ const classes = (): string => {
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="content" :class="[classes()]">
|
||||
<Content :content="props.msg?.content" />
|
||||
<div class="content" :class="[classes()]" :style="style('border-color')">
|
||||
<Content v-if="!compact" :content="props.msg?.content" />
|
||||
<SvgLoader
|
||||
v-if="compact"
|
||||
classes="selectable"
|
||||
class="expand-contract"
|
||||
svg="expand-up-down-line"
|
||||
@click="compact = false"
|
||||
/>
|
||||
<Options class="options" :msg="props.msg" />
|
||||
</div>
|
||||
<div id="thread-link" v-if="props.msg?.thread" @click="router.push(`/${props.msg?.thread}`)">
|
||||
<SvgLoader svg="expand-left-fill" />
|
||||
<span>Go to the full conversation.</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
<style scoped lang="scss">
|
||||
.message {
|
||||
width: auto;
|
||||
/* border: white 1px solid; */
|
||||
margin: 10px 5px 0px 5px;
|
||||
}
|
||||
|
||||
#thread-link {
|
||||
&:hover {
|
||||
background-color: var(--selected);
|
||||
}
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
background-color: var(--quaternary-background);
|
||||
padding: 3px 10px;
|
||||
border-radius: 4px 4px 0 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#context {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@@ -97,8 +130,15 @@ const classes = (): string => {
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
padding-top: 6px;
|
||||
padding: 6px;
|
||||
flex-direction: row;
|
||||
border: solid 4px var(--quaternary-background);
|
||||
border-top: 0;
|
||||
border-radius: 0 0 4px 4px;
|
||||
}
|
||||
|
||||
.expand-contract {
|
||||
margin: 0 50px;
|
||||
}
|
||||
|
||||
.msg-important {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { defineProps, inject, PropType } from "vue";
|
||||
import { defineProps, inject, PropType, Ref, ref } from "vue";
|
||||
import { Message } from "@/store/models/model";
|
||||
import API from "@/services/imapAPI";
|
||||
import store from "@/store/store";
|
||||
import { isSeenFc } from "@/utils/flagsUtils";
|
||||
import { isSeenFc, hasFlag } from "@/utils/flagsUtils";
|
||||
import SvgLoader from "@/components/utils/SvgLoader.vue";
|
||||
|
||||
const props = defineProps({
|
||||
msg: Object as PropType<Message>,
|
||||
@@ -11,56 +12,136 @@ const props = defineProps({
|
||||
|
||||
const room: any = inject("room");
|
||||
|
||||
const setFlag = (flag: string) => {
|
||||
// todo loading
|
||||
const seenLoading = ref(false);
|
||||
const flaggedLoading = ref(false);
|
||||
const deletionLoading = ref(false);
|
||||
|
||||
const setReadFlag = () => setFlag("\\Seen", seenLoading);
|
||||
const setFlaggedFlag = () => setFlag("\\Flagged", flaggedLoading);
|
||||
|
||||
const setFlag = (flag: string, loadingState: Ref<boolean>) => {
|
||||
if (loadingState.value) return;
|
||||
if (!room?.value || !props.msg) return;
|
||||
let apiCall = isSeenFc(props.msg?.flags) ? API.removeFlag : API.addFlag;
|
||||
loadingState.value = true;
|
||||
|
||||
let apiCall = hasFlag(props.msg?.flags, flag) ? API.removeFlag : API.addFlag;
|
||||
apiCall({
|
||||
mailboxId: room.value?.mailboxId,
|
||||
messageId: props.msg?.id,
|
||||
flag: flag,
|
||||
})
|
||||
.then((res) => {
|
||||
if (isSeenFc(props.msg?.flags)) {
|
||||
.then(() => {
|
||||
if (hasFlag(props.msg?.flags, flag)) {
|
||||
store.commit("removeFlag", { roomId: room.value?.id, messageId: props.msg?.id, flag: flag });
|
||||
} else {
|
||||
store.commit("addFlag", { roomId: room.value?.id, messageId: props.msg?.id, flag: flag });
|
||||
}
|
||||
loadingState.value = false;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
loadingState.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const deleteEverywhere = () => {
|
||||
if (deletionLoading.value) return;
|
||||
if (!room?.value || !props.msg) return;
|
||||
deletionLoading.value = true;
|
||||
API.deleteEverywhere({ mailboxId: room.value?.mailboxId, messageId: props.msg?.id })
|
||||
.then((res) => {
|
||||
// delete even if we delete the room after because the transition between room is not clean (todo)
|
||||
store.commit("removeMsg", { roomId: room.value?.id, messageId: props.msg?.id });
|
||||
if (res.data?.deleteRoom) {
|
||||
store.commit("removeRoom", { roomId: room.value?.id });
|
||||
}
|
||||
deletionLoading.value = false;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
deletionLoading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const deleteRemoteOnly = () => {
|
||||
if (deletionLoading.value) return;
|
||||
if (!room?.value || !props.msg) return;
|
||||
deletionLoading.value = true;
|
||||
API.deleteRemoteOnly({ mailboxId: room.value?.mailboxId, messageId: props.msg?.id })
|
||||
.then((res) => {
|
||||
if (!hasFlag(props.msg?.flags, "\\Deleted")) {
|
||||
store.commit("addFlag", { roomId: room.value?.id, messageId: props.msg?.id, flag: "\\Deleted" });
|
||||
}
|
||||
deletionLoading.value = false;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
deletionLoading.value = false;
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="button" @click="setFlag('\\Seen')">
|
||||
{{ isSeenFc(props.msg?.flags) ? "Mark as not read" : "Mark as read" }}
|
||||
<div id="main">
|
||||
<div class="icons">
|
||||
<span @click="setReadFlag()">
|
||||
<SvgLoader
|
||||
v-if="isSeenFc(props.msg?.flags)"
|
||||
svg="mail-check-line"
|
||||
classes="selectable"
|
||||
v-tooltip="'Mark unread'"
|
||||
:loading="seenLoading"
|
||||
/>
|
||||
<SvgLoader
|
||||
v-if="!isSeenFc(props.msg?.flags)"
|
||||
svg="mail-unread-line"
|
||||
classes="selectable"
|
||||
v-tooltip="'Mark as read'"
|
||||
:loading="seenLoading"
|
||||
/>
|
||||
</span>
|
||||
<span @click="setFlaggedFlag()">
|
||||
<SvgLoader
|
||||
svg="flag-line"
|
||||
:loading="flaggedLoading"
|
||||
:classes="(hasFlag(props.msg?.flags, '\\Flagged') ? 'warn' : '') + ',selectable'"
|
||||
v-tooltip="hasFlag(props.msg?.flags, '\\Flagged') ? 'Unflag' : 'Flag'"
|
||||
/>
|
||||
</span>
|
||||
<SvgLoader svg="reply-line" classes="selectable" />
|
||||
<span @click="deleteRemoteOnly()">
|
||||
<SvgLoader
|
||||
svg="delete-bin-4-line"
|
||||
:loading="deletionLoading"
|
||||
classes="danger,selectable"
|
||||
v-tooltip="'Delete from remote'"
|
||||
/>
|
||||
</span>
|
||||
<span @click="deleteEverywhere()">
|
||||
<SvgLoader
|
||||
svg="delete-bin-6-line"
|
||||
:loading="deletionLoading"
|
||||
classes="danger,selectable"
|
||||
v-tooltip="'Delete everywhere'"
|
||||
/>
|
||||
</span>
|
||||
<SvgLoader svg="share-forward-line" classes="selectable" />
|
||||
<SvgLoader svg="reply-all-line" classes="selectable" />
|
||||
<div>{{ props.msg?.flags }}</div>
|
||||
</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>see source</div> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
div {
|
||||
<style lang="scss" scoped>
|
||||
#main {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.button {
|
||||
border: solid 1px;
|
||||
border-radius: 6px;
|
||||
display: initial;
|
||||
padding: 1px 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background-color: var(--selected);
|
||||
.icons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,24 +1,21 @@
|
||||
import { DirectiveBinding, ObjectDirective, VNode } from "vue";
|
||||
import { DirectiveBinding, ObjectDirective } from "vue";
|
||||
import { createPopper } from "@popperjs/core";
|
||||
import { createApp } from "vue";
|
||||
import Tooltip from "./Tooltip.vue";
|
||||
|
||||
const VTooltip: ObjectDirective = {
|
||||
mounted(el: HTMLElement, binding: DirectiveBinding<any>) {
|
||||
const tooltipText = binding.value;
|
||||
const tooltip = document.createElement("div");
|
||||
tooltip.innerText = tooltipText;
|
||||
tooltip.classList.add("tooltip");
|
||||
tooltip.style.position = "absolute";
|
||||
el.appendChild(tooltip);
|
||||
|
||||
const tooltipText = binding.value?.text ?? binding.value;
|
||||
createApp(Tooltip, { text: tooltipText, shortcut: binding.value?.shortcut }).mount(tooltip);
|
||||
tooltip.style.display = "none";
|
||||
|
||||
const popper = createPopper(el, tooltip, {
|
||||
createPopper(el, tooltip, {
|
||||
placement: "top",
|
||||
modifiers: [
|
||||
{
|
||||
name: "offset",
|
||||
options: {
|
||||
offset: [0, 8],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
el.addEventListener("mouseenter", () => {
|
||||
44
front/src/components/tooltip/Tooltip.vue
Normal file
@@ -0,0 +1,44 @@
|
||||
<script setup>
|
||||
import { defineProps } from "vue";
|
||||
const props = defineProps({ text: String, shortcut: Array });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="tooltip">
|
||||
<div>
|
||||
<b>{{ props.text }}</b>
|
||||
</div>
|
||||
<div v-if="props.shortcut" class="shortcut">
|
||||
<span v-for="(code, index) in props.shortcut" :key="index">{{ code }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.tooltip {
|
||||
background-color: var(--primary-background);
|
||||
border: solid 1px var(--border-color);
|
||||
padding: 4px;
|
||||
text-align: center;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.shortcut {
|
||||
margin: 2px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 3px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
span {
|
||||
display: inline-block;
|
||||
border: solid 1px var(--quaternary-background);
|
||||
border-bottom-width: 3px;
|
||||
border-radius: 3px;
|
||||
color: var(--secondary-text);
|
||||
background-color: var(--secondary-background);
|
||||
font-size: 1.3rem;
|
||||
padding: 0.18rem 0.31rem;
|
||||
}
|
||||
</style>
|
||||
@@ -3,15 +3,75 @@ import { defineProps } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
svg: { type: String, required: true },
|
||||
isDisabled: Boolean,
|
||||
classes: String,
|
||||
loading: { type: Boolean },
|
||||
});
|
||||
|
||||
const pathSvg = () => require(`@/assets/svg/${props.svg}.svg`);
|
||||
const classes = () => props.classes?.split(",") ?? "";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<img class="it-has-a-tooltip" :src="pathSvg()" />
|
||||
<div class="mainSvg" :class="classes()">
|
||||
<div class="lds-dual-ring" v-if="loading"></div>
|
||||
<img v-if="!loading" :disabled="props.isDisabled" :src="pathSvg()" :class="classes()" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
<style lang="scss" scoped>
|
||||
.mainSvg {
|
||||
display: flex;
|
||||
&.selectable {
|
||||
display: inline-block;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
&:hover,
|
||||
&.is-active {
|
||||
background-color: var(--selected);
|
||||
}
|
||||
}
|
||||
.disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
padding: 1px;
|
||||
min-width: 26px;
|
||||
min-height: 26px;
|
||||
filter: var(--svg-primary-text);
|
||||
&.danger {
|
||||
filter: var(--svg-danger);
|
||||
}
|
||||
|
||||
&.warn {
|
||||
filter: var(--svg-warn);
|
||||
}
|
||||
}
|
||||
|
||||
.lds-dual-ring {
|
||||
display: flex;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
}
|
||||
.lds-dual-ring:after {
|
||||
content: " ";
|
||||
display: block;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
margin: auto;
|
||||
border-radius: 50%;
|
||||
border: 2px solid #fff;
|
||||
border-color: #fff transparent #fff transparent;
|
||||
animation: lds-dual-ring 1.4s linear infinite;
|
||||
}
|
||||
@keyframes lds-dual-ring {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,7 +3,7 @@ import router from "./router";
|
||||
import App from "./App.vue";
|
||||
import store from "./store/store";
|
||||
import "@/assets/css/main.css";
|
||||
import VTooltip from "./components/Tooltip";
|
||||
import VTooltip from "./components/tooltip/Tooltip";
|
||||
|
||||
const app = createApp(App);
|
||||
app.use(router);
|
||||
|
||||
@@ -2,28 +2,36 @@ import API from "./API";
|
||||
|
||||
export default {
|
||||
registerAccount(data: object) {
|
||||
return API().post("/mail/account", data);
|
||||
return API().post("/account/register", data);
|
||||
},
|
||||
getAccounts() {
|
||||
return API().get("/mail/accounts");
|
||||
return API().get("/account/getAll");
|
||||
},
|
||||
getRooms(mailboxId: number) {
|
||||
return API().get(`/mail/${mailboxId}/rooms`);
|
||||
return API().get(`/account/${mailboxId}/rooms`);
|
||||
},
|
||||
getMessages(roomId: number) {
|
||||
return API().get(`/mail/${roomId}/messages`);
|
||||
return API().get(`/room/${roomId}/messages`);
|
||||
},
|
||||
getMembers(roomId: number) {
|
||||
return API().get(`/mail/${roomId}/members`);
|
||||
return API().get(`/room/${roomId}/members`);
|
||||
},
|
||||
addFlag(data: { mailboxId: number; messageId: number; flag: string }) {
|
||||
return API().post(`/mail/addFlag`, data);
|
||||
return API().post(`/message/addFlag`, data);
|
||||
},
|
||||
removeFlag(data: { mailboxId: number; messageId: number; flag: string }) {
|
||||
return API().post(`/mail/removeFlag`, data);
|
||||
return API().post(`/message/removeFlag`, data);
|
||||
},
|
||||
reponseEmail(data: { user: string; roomId: number; text: string; html: string }) {
|
||||
console.log(data);
|
||||
return API().post(`/mail/response`, data);
|
||||
return API().post(`/room/response`, data);
|
||||
},
|
||||
deleteRemoteOnly(data: { mailboxId: number; messageId: number }) {
|
||||
return API().post(`/message/deleteRemote`, data);
|
||||
},
|
||||
deleteEverywhere(data: { mailboxId: number; messageId: number }) {
|
||||
return API().post(`/message/delete`, data);
|
||||
},
|
||||
deleteRoom(id: number) {
|
||||
return API().post(`/room/delete`, { roomId: id });
|
||||
},
|
||||
};
|
||||
|
||||
@@ -15,6 +15,7 @@ export interface Message {
|
||||
content: string;
|
||||
date: string;
|
||||
flags: string[];
|
||||
thread: number | null;
|
||||
}
|
||||
|
||||
export enum LoadingState {
|
||||
@@ -33,6 +34,7 @@ export interface Room {
|
||||
members: Address[];
|
||||
notSeen: number;
|
||||
threadIds: number[];
|
||||
parent_id?: number;
|
||||
}
|
||||
|
||||
export interface Account {
|
||||
@@ -45,5 +47,6 @@ export interface Address {
|
||||
id: number;
|
||||
name: string | null;
|
||||
email: string;
|
||||
color: string | undefined;
|
||||
type: string;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { decodeEmojis } from "@/utils/string";
|
||||
import { AxiosError, AxiosResponse } from "axios";
|
||||
import { createStore } from "vuex";
|
||||
import { Room, Account, Address, RoomType, Message, LoadingState } from "./models/model";
|
||||
import { removeDuplicates } from "@/utils/array";
|
||||
|
||||
interface RoomFromBack {
|
||||
id: number;
|
||||
@@ -32,6 +33,7 @@ function createRoom(options: RoomFromBack): Room {
|
||||
user: options.user,
|
||||
notSeen: options.notSeen,
|
||||
threadIds: [],
|
||||
parent_id: options.parent_id,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -76,8 +78,6 @@ function updateSeen(state: State, roomId: number, flag: string[], adding: boolea
|
||||
}
|
||||
}
|
||||
}
|
||||
// define injection key todo
|
||||
// export const key: InjectionKey<Store<State>> = Symbol()
|
||||
|
||||
const store = createStore<State>({
|
||||
state: {
|
||||
@@ -95,9 +95,6 @@ const store = createStore<State>({
|
||||
},
|
||||
setActiveRoom(state, payload) {
|
||||
state.activeRoom = payload;
|
||||
// todo load room on load page
|
||||
const room = roomOnId(state, payload);
|
||||
if (!room) return;
|
||||
let roomMessage = msgOnRoomId(state, payload);
|
||||
if (!roomMessage) {
|
||||
state.roomMessages.push({ messages: [], fetch: LoadingState.notLoaded, roomId: payload });
|
||||
@@ -114,7 +111,6 @@ const store = createStore<State>({
|
||||
});
|
||||
},
|
||||
addRooms(state, payload) {
|
||||
// todo add if not exist
|
||||
const buffer: RoomFromBack[] = [];
|
||||
payload.rooms.forEach((room: RoomFromBack) => {
|
||||
if (room.roomType == RoomType.THREAD) {
|
||||
@@ -132,19 +128,37 @@ const store = createStore<State>({
|
||||
}
|
||||
});
|
||||
},
|
||||
removeRoom(state, payload) {
|
||||
const roomMessageIndex = state.roomMessages.findIndex((roomM) => roomM.roomId === payload.roomId);
|
||||
state.roomMessages.splice(roomMessageIndex, 1);
|
||||
const roomIndex = state.rooms.findIndex((room) => room.id === payload.roomId);
|
||||
const roomToDelete = state.rooms[roomIndex];
|
||||
// todo debug parent_id to root_id
|
||||
// remove thread
|
||||
if (roomToDelete.roomType === RoomType.THREAD && roomToDelete.parent_id) {
|
||||
const parentRoom = roomOnId(state, roomToDelete.parent_id);
|
||||
if (parentRoom) {
|
||||
parentRoom.threadIds = parentRoom?.threadIds.filter((id) => id !== roomToDelete.id);
|
||||
}
|
||||
}
|
||||
// todo fix to many at the same time bug
|
||||
state.rooms.splice(roomIndex, 1);
|
||||
// state.activeRoom = state.rooms[0].id;
|
||||
// router.push(`/${state.activeRoom}`);
|
||||
},
|
||||
addMessages(state, payload) {
|
||||
// todo add if not exist
|
||||
const room = roomOnId(state, payload.roomId);
|
||||
if (!room) return;
|
||||
let roomMessage = msgOnRoomId(state, payload.roomId);
|
||||
if (!roomMessage) {
|
||||
state.roomMessages.push({ roomId: payload.roomId, messages: [], fetch: LoadingState.notLoaded });
|
||||
roomMessage = msgOnRoomId(state, payload.roomId);
|
||||
}
|
||||
if (!roomMessage) return;
|
||||
|
||||
payload.messages.forEach((message: any) => {
|
||||
message.flags = message.flags?.split(",") ?? [];
|
||||
// todo fix upstream
|
||||
// message.fromA = message.fromA ? removeDuplicates(message.fromA.split(",").join(",")) : null;
|
||||
// message.toA = message.toA ? removeDuplicates(message.toA.split(",").join(",")) : null;
|
||||
// message.ccA = message.ccA ? removeDuplicates(message.ccA.split(",").join(",")) : null;
|
||||
roomMessage?.messages.push(message);
|
||||
});
|
||||
},
|
||||
@@ -170,6 +184,19 @@ const store = createStore<State>({
|
||||
updateSeen(state, payload.roomId, payload.flag, false);
|
||||
}
|
||||
},
|
||||
removeMsg(state, payload) {
|
||||
const msgs = msgOnRoomId(state, payload.roomId);
|
||||
const msgIndex = msgs?.messages.findIndex((msg) => msg.id == payload.messageId) ?? -1;
|
||||
if (msgs && msgIndex != -1) {
|
||||
if (!isSeenFc(msgs.messages[msgIndex].flags)) {
|
||||
const room = roomOnId(state, payload.roomId);
|
||||
if (room) {
|
||||
room.notSeen = room.notSeen - 1;
|
||||
}
|
||||
}
|
||||
msgs.messages.splice(msgIndex, 1);
|
||||
}
|
||||
},
|
||||
},
|
||||
getters: {
|
||||
rooms: (state) => (): Room[] => {
|
||||
@@ -193,9 +220,14 @@ const store = createStore<State>({
|
||||
messages:
|
||||
(state) =>
|
||||
(roomId: number): Message[] => {
|
||||
if (!roomId) return [];
|
||||
const roomMessage = msgOnRoomId(state, roomId);
|
||||
if (roomMessage) return roomMessage.messages;
|
||||
state.roomMessages.push({ messages: [], roomId: roomId, fetch: LoadingState.notLoaded });
|
||||
if (roomMessage && roomMessage.fetch === LoadingState.loaded) return roomMessage.messages;
|
||||
if (!roomMessage) {
|
||||
state.roomMessages.push({ messages: [], roomId: roomId, fetch: LoadingState.notLoaded });
|
||||
} else if (roomMessage.fetch === LoadingState.loaded) {
|
||||
return roomMessage.messages;
|
||||
}
|
||||
store.dispatch("fetchMessages", { roomId: roomId, obj: msgOnRoomId(state, roomId) });
|
||||
return msgOnRoomId(state, roomId)?.messages ?? [];
|
||||
},
|
||||
|
||||
13
front/src/utils/address.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Address } from "@/store/models/model";
|
||||
import { removeDuplicates } from "./array";
|
||||
|
||||
export const displayAddresses = (addressIds: string[] | undefined, members: Address[]): string => {
|
||||
if (!addressIds) return "";
|
||||
addressIds = removeDuplicates(addressIds);
|
||||
let res = "";
|
||||
addressIds.forEach((addressId) => {
|
||||
const address = members?.find((member) => member.id === parseInt(addressId));
|
||||
if (address) res += address.email;
|
||||
});
|
||||
return res;
|
||||
};
|
||||
@@ -1,3 +1,13 @@
|
||||
export function isSeenFc(flags: string[] | undefined): boolean {
|
||||
return flags?.includes("\\Seen") ?? false;
|
||||
}
|
||||
|
||||
export function hasFlag(flags: string[] | undefined, flag: string): boolean {
|
||||
if (!flags) return false;
|
||||
for (let i = 0; i < flags.length; i++) {
|
||||
if (flags[i] == flag) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { defineProps, PropType } from "vue";
|
||||
import Badge from "@/components/Badge.vue";
|
||||
import SvgLoader from "@/components/utils/SvgLoader.vue";
|
||||
import { RoomType, Address, Room } from "@/store/models/model";
|
||||
import MemberList from "./MemberList.vue";
|
||||
import imapAPI from "@/services/imapAPI";
|
||||
|
||||
const props = defineProps({ id: Number, room: Object as PropType<Room> });
|
||||
|
||||
@@ -14,21 +16,38 @@ const roomTitle = () => {
|
||||
return props.room?.roomName;
|
||||
};
|
||||
|
||||
const handleDelete = () => {
|
||||
console.log(props.room);
|
||||
if (props.room?.id) {
|
||||
imapAPI.deleteRoom(props.room.id);
|
||||
}
|
||||
// todo loading, delete
|
||||
};
|
||||
|
||||
// todo remove us from list
|
||||
const to = () => props.room?.members.filter((member: Address) => member.type == "to");
|
||||
const cc = () => props.room?.members.filter((member: Address) => member.type == "cc");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="main">
|
||||
<div>
|
||||
<div class="context">
|
||||
<div class="infos">
|
||||
<Badge :value="RoomType[room?.roomType ?? 0]" />
|
||||
{{ roomTitle() }}
|
||||
</div>
|
||||
<div class="action">action: threads message important</div>
|
||||
<div class="action">
|
||||
<SvgLoader svg="list-unordered" classes="selectable" v-tooltip="{ text: 'Thread list' }" />
|
||||
<SvgLoader svg="pushpin-line" classes="selectable" v-tooltip="{ text: 'Important messages' }" />
|
||||
<SvgLoader
|
||||
svg="delete-bin-4-line"
|
||||
@click="handleDelete()"
|
||||
classes="danger,selectable"
|
||||
v-tooltip="{ text: 'Delete room' }"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="members" v-if="room?.roomType != RoomType.DM">
|
||||
<div v-if="room?.roomType != RoomType.DM">
|
||||
<MemberList class="members-list" v-if="to()?.length ?? 0 > 0" type="to" :members="to()" />
|
||||
<MemberList class="members-list" v-if="cc()?.length ?? 0 > 0" type="cc" :members="cc()" />
|
||||
</div>
|
||||
@@ -36,8 +55,6 @@ const cc = () => props.room?.members.filter((member: Address) => member.type ==
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.main {
|
||||
}
|
||||
.context {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@@ -52,9 +69,6 @@ const cc = () => props.room?.members.filter((member: Address) => member.type ==
|
||||
padding: 3px 5px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
.members {
|
||||
}
|
||||
|
||||
.infos {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
@@ -7,18 +7,21 @@ import Header from "./Header.vue";
|
||||
import Message from "../../components/structure/message/Message.vue";
|
||||
import MessageViewModal from "@/components/modals/MessageViewModal.vue";
|
||||
import Composer from "@/components/structure/message/Composer.vue";
|
||||
import ConfirmationModal from "@/components/modals/ConfirmationModal.vue";
|
||||
|
||||
const store = useStore();
|
||||
const route = useRoute();
|
||||
const messageIdView = ref(-1);
|
||||
const message = ref(undefined);
|
||||
const messages = ref([]);
|
||||
const id = ref(parseInt(route.params.id));
|
||||
let room = ref();
|
||||
|
||||
onBeforeMount(async () => {
|
||||
console.log(id.value);
|
||||
store.commit("setActiveRoom", id.value);
|
||||
room.value = store.getters.room(id.value);
|
||||
console.log(room.value);
|
||||
messages.value = store.getters.messages(id.value);
|
||||
});
|
||||
|
||||
onBeforeRouteUpdate(async (to, from) => {
|
||||
@@ -26,7 +29,7 @@ onBeforeRouteUpdate(async (to, from) => {
|
||||
id.value = parseInt(to.params.id);
|
||||
store.commit("setActiveRoom", id.value);
|
||||
room.value = await store.getters.room(id.value);
|
||||
console.log(room.value);
|
||||
messages.value = store.getters.messages(id.value);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -45,6 +48,13 @@ function openMessageView(messageId) {
|
||||
message.value = store.getters.message(room.value.id, messageId);
|
||||
}
|
||||
|
||||
const shouldBeCompact = (index) => {
|
||||
// show last three messages
|
||||
// todo fix not changing three displayed when deleting
|
||||
if (messages.value?.length - 4 < index) return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
provide("room", room);
|
||||
</script>
|
||||
|
||||
@@ -53,15 +63,23 @@ provide("room", room);
|
||||
<Header :id="id" :room="room"></Header>
|
||||
<div class="messages">
|
||||
<Message
|
||||
v-for="(message, index) in store.getters.messages(room?.id)"
|
||||
v-for="(message, index) in messages"
|
||||
:key="index"
|
||||
:msg="message"
|
||||
:members="room?.members"
|
||||
:compact="shouldBeCompact(index)"
|
||||
@open-message-view="(id) => openMessageView(id)"
|
||||
/>
|
||||
</div>
|
||||
<Composer class="composer" v-if="shouldDisplayComposer()" />
|
||||
<MessageViewModal :message="message" :messageId="messageIdView" @close="() => openMessageView(-1)" />
|
||||
<Composer v-if="shouldDisplayComposer() || true" />
|
||||
<MessageViewModal
|
||||
:room="room"
|
||||
:message="message"
|
||||
:messageId="messageIdView"
|
||||
@close="() => openMessageView(-1)"
|
||||
/>
|
||||
<!-- todo -->
|
||||
<!-- <ConfirmationModal /> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -77,10 +95,6 @@ provide("room", room);
|
||||
|
||||
.messages {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.composer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
<script lang="ts" setup>
|
||||
import Accounts from "./accounts/Accounts.vue";
|
||||
import Rooms from "./rooms/Rooms.vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Accounts class="accounts" />
|
||||
@@ -5,19 +10,6 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Accounts from "./accounts/Accounts";
|
||||
import Rooms from "./rooms/Rooms.vue";
|
||||
|
||||
export default {
|
||||
name: "Sidebar",
|
||||
components: {
|
||||
Accounts,
|
||||
Rooms,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
div {
|
||||
display: flex;
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
<script setup>
|
||||
import Account from "./Account";
|
||||
import AddAccountModal from "@/components/modals/AddAccountModal";
|
||||
import store from "@/store/store";
|
||||
import { onMounted } from "vue";
|
||||
import { computed } from "@vue/reactivity";
|
||||
|
||||
onMounted(() => {
|
||||
store.dispatch("fetchAccounts");
|
||||
});
|
||||
|
||||
const accounts = computed(() => store.state.accounts);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="main">
|
||||
<div id="userMenu">
|
||||
@@ -6,32 +20,10 @@
|
||||
<span class="divider"></span>
|
||||
<Account v-for="(account, index) in accounts" :key="index" :data="account" />
|
||||
<span class="divider"></span>
|
||||
|
||||
<AddAccountModal />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from "vuex";
|
||||
import Account from "./Account";
|
||||
import AddAccountModal from "@/components/modals/AddAccountModal";
|
||||
import store from "@/store/store";
|
||||
|
||||
export default {
|
||||
name: "Accounts",
|
||||
components: {
|
||||
Account,
|
||||
AddAccountModal,
|
||||
},
|
||||
computed: {
|
||||
...mapState(["accounts"]),
|
||||
},
|
||||
created() {
|
||||
store.dispatch("fetchAccounts");
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#main {
|
||||
display: flex;
|
||||
|
||||
@@ -46,10 +46,9 @@ const router = useRouter();
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.room:hover,
|
||||
.selected {
|
||||
background-color: var(--selected);
|
||||
.room:hover {
|
||||
border-radius: 8px;
|
||||
background-color: var(--selected);
|
||||
}
|
||||
|
||||
.content {
|
||||
|
||||
@@ -32,6 +32,7 @@ const router = useRouter();
|
||||
padding: 4px;
|
||||
cursor: pointer;
|
||||
color: var(--secondary-text);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.badge {
|
||||
@@ -44,10 +45,8 @@ const router = useRouter();
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.room:hover,
|
||||
.selected {
|
||||
.room:hover {
|
||||
background-color: var(--selected);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.room::before {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
const { defineConfig } = require('@vue/cli-service');
|
||||
const { defineConfig } = require("@vue/cli-service");
|
||||
module.exports = defineConfig({
|
||||
transpileDependencies: true,
|
||||
devServer: {
|
||||
proxy: {
|
||||
"/api": {
|
||||
target: "http://localhost:5500",
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
transpileDependencies: true,
|
||||
devServer: {
|
||||
proxy: {
|
||||
"/api": {
|
||||
target: "http://localhost:5500",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
580
front/yarn.lock
@@ -1916,6 +1916,11 @@
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/@types/prettier/-/prettier-2.7.2.tgz"
|
||||
integrity sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==
|
||||
|
||||
"@types/q@^1.5.1":
|
||||
version "1.5.5"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/@types/q/-/q-1.5.5.tgz"
|
||||
integrity sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ==
|
||||
|
||||
"@types/qs@*":
|
||||
version "6.9.7"
|
||||
resolved "https://registry.npmmirror.com/@types/qs/-/qs-6.9.7.tgz"
|
||||
@@ -2859,7 +2864,7 @@ ansi-regex@^6.0.1:
|
||||
|
||||
ansi-styles@^3.2.1:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-3.2.1.tgz"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/ansi-styles/-/ansi-styles-3.2.1.tgz"
|
||||
integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
|
||||
dependencies:
|
||||
color-convert "^1.9.0"
|
||||
@@ -2906,6 +2911,14 @@ argparse@^2.0.1:
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/argparse/-/argparse-2.0.1.tgz"
|
||||
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
|
||||
|
||||
array-buffer-byte-length@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz"
|
||||
integrity sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
is-array-buffer "^3.0.1"
|
||||
|
||||
array-flatten@^2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.npmmirror.com/array-flatten/-/array-flatten-2.1.2.tgz"
|
||||
@@ -2921,6 +2934,17 @@ array-union@^2.1.0:
|
||||
resolved "https://registry.npmmirror.com/array-union/-/array-union-2.1.0.tgz"
|
||||
integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
|
||||
|
||||
array.prototype.reduce@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/array.prototype.reduce/-/array.prototype.reduce-1.0.5.tgz"
|
||||
integrity sha512-kDdugMl7id9COE8R7MHF5jWk7Dqt/fs4Pv+JXoICnYwqpjjjbUurz6w5fT5IG6brLdJhv6/VoHB0H7oyIBXd+Q==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
define-properties "^1.1.4"
|
||||
es-abstract "^1.20.4"
|
||||
es-array-method-boxes-properly "^1.0.0"
|
||||
is-string "^1.0.7"
|
||||
|
||||
astral-regex@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmmirror.com/astral-regex/-/astral-regex-2.0.0.tgz"
|
||||
@@ -2955,6 +2979,11 @@ autoprefixer@^10.2.4:
|
||||
picocolors "^1.0.0"
|
||||
postcss-value-parser "^4.2.0"
|
||||
|
||||
available-typed-arrays@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz"
|
||||
integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==
|
||||
|
||||
axios@^1.3.4:
|
||||
version "1.3.4"
|
||||
resolved "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz"
|
||||
@@ -3133,7 +3162,7 @@ bonjour-service@^1.0.11:
|
||||
fast-deep-equal "^3.1.3"
|
||||
multicast-dns "^7.2.5"
|
||||
|
||||
boolbase@^1.0.0:
|
||||
boolbase@^1.0.0, boolbase@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmmirror.com/boolbase/-/boolbase-1.0.0.tgz"
|
||||
integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==
|
||||
@@ -3291,6 +3320,15 @@ chalk@^2.1.0:
|
||||
escape-string-regexp "^1.0.5"
|
||||
supports-color "^5.3.0"
|
||||
|
||||
chalk@^2.4.1:
|
||||
version "2.4.2"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/chalk/-/chalk-2.4.2.tgz"
|
||||
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
|
||||
dependencies:
|
||||
ansi-styles "^3.2.1"
|
||||
escape-string-regexp "^1.0.5"
|
||||
supports-color "^5.3.0"
|
||||
|
||||
chalk@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmmirror.com/chalk/-/chalk-3.0.0.tgz"
|
||||
@@ -3427,6 +3465,15 @@ co@^4.6.0:
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/co/-/co-4.6.0.tgz"
|
||||
integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==
|
||||
|
||||
coa@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/coa/-/coa-2.0.2.tgz"
|
||||
integrity sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==
|
||||
dependencies:
|
||||
"@types/q" "^1.5.1"
|
||||
chalk "^2.4.1"
|
||||
q "^1.1.2"
|
||||
|
||||
collect-v8-coverage@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz"
|
||||
@@ -3434,7 +3481,7 @@ collect-v8-coverage@^1.0.0:
|
||||
|
||||
color-convert@^1.9.0:
|
||||
version "1.9.3"
|
||||
resolved "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/color-convert/-/color-convert-1.9.3.tgz"
|
||||
integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
|
||||
dependencies:
|
||||
color-name "1.1.3"
|
||||
@@ -3453,7 +3500,7 @@ color-name@~1.1.4:
|
||||
|
||||
color-name@1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.npmmirror.com/color-name/-/color-name-1.1.3.tgz"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/color-name/-/color-name-1.1.3.tgz"
|
||||
integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
|
||||
|
||||
colord@^2.9.1:
|
||||
@@ -3695,6 +3742,21 @@ css-minimizer-webpack-plugin@^3.0.2:
|
||||
serialize-javascript "^6.0.0"
|
||||
source-map "^0.6.1"
|
||||
|
||||
css-select-base-adapter@^0.1.1:
|
||||
version "0.1.1"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz"
|
||||
integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==
|
||||
|
||||
css-select@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/css-select/-/css-select-2.1.0.tgz"
|
||||
integrity sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==
|
||||
dependencies:
|
||||
boolbase "^1.0.0"
|
||||
css-what "^3.2.1"
|
||||
domutils "^1.7.0"
|
||||
nth-check "^1.0.2"
|
||||
|
||||
css-select@^4.1.3:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.npmmirror.com/css-select/-/css-select-4.3.0.tgz"
|
||||
@@ -3722,6 +3784,19 @@ css-tree@^2.0.1:
|
||||
mdn-data "2.0.30"
|
||||
source-map-js "^1.0.1"
|
||||
|
||||
css-tree@1.0.0-alpha.37:
|
||||
version "1.0.0-alpha.37"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/css-tree/-/css-tree-1.0.0-alpha.37.tgz"
|
||||
integrity sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==
|
||||
dependencies:
|
||||
mdn-data "2.0.4"
|
||||
source-map "^0.6.1"
|
||||
|
||||
css-what@^3.2.1:
|
||||
version "3.4.2"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/css-what/-/css-what-3.4.2.tgz"
|
||||
integrity sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==
|
||||
|
||||
css-what@^6.0.1:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.npmmirror.com/css-what/-/css-what-6.1.0.tgz"
|
||||
@@ -3781,7 +3856,7 @@ cssnano@^5.0.0, cssnano@^5.0.6:
|
||||
lilconfig "^2.0.3"
|
||||
yaml "^1.10.2"
|
||||
|
||||
csso@^4.2.0:
|
||||
csso@^4.0.2, csso@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.npmmirror.com/csso/-/csso-4.2.0.tgz"
|
||||
integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==
|
||||
@@ -3824,6 +3899,11 @@ data-urls@^2.0.0:
|
||||
whatwg-mimetype "^2.3.0"
|
||||
whatwg-url "^8.0.0"
|
||||
|
||||
de-indent@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/de-indent/-/de-indent-1.0.2.tgz"
|
||||
integrity sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==
|
||||
|
||||
debug@^3.2.7:
|
||||
version "3.2.7"
|
||||
resolved "https://registry.npmmirror.com/debug/-/debug-3.2.7.tgz"
|
||||
@@ -3889,10 +3969,10 @@ define-lazy-prop@^2.0.0:
|
||||
resolved "https://registry.npmmirror.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz"
|
||||
integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==
|
||||
|
||||
define-properties@^1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.npmmirror.com/define-properties/-/define-properties-1.1.4.tgz"
|
||||
integrity sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==
|
||||
define-properties@^1.1.3, define-properties@^1.1.4, define-properties@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/define-properties/-/define-properties-1.2.0.tgz"
|
||||
integrity sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==
|
||||
dependencies:
|
||||
has-property-descriptors "^1.0.0"
|
||||
object-keys "^1.1.1"
|
||||
@@ -3974,11 +4054,24 @@ dom-serializer@^1.0.1:
|
||||
domhandler "^4.2.0"
|
||||
entities "^2.0.0"
|
||||
|
||||
dom-serializer@0:
|
||||
version "0.2.2"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/dom-serializer/-/dom-serializer-0.2.2.tgz"
|
||||
integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==
|
||||
dependencies:
|
||||
domelementtype "^2.0.1"
|
||||
entities "^2.0.0"
|
||||
|
||||
domelementtype@^2.0.1, domelementtype@^2.2.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.npmmirror.com/domelementtype/-/domelementtype-2.3.0.tgz"
|
||||
integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==
|
||||
|
||||
domelementtype@1:
|
||||
version "1.3.1"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/domelementtype/-/domelementtype-1.3.1.tgz"
|
||||
integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==
|
||||
|
||||
domexception@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/domexception/-/domexception-2.0.1.tgz"
|
||||
@@ -3998,6 +4091,14 @@ dompurify@^3.0.1:
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/dompurify/-/dompurify-3.0.1.tgz"
|
||||
integrity sha512-60tsgvPKwItxZZdfLmamp0MTcecCta3avOhsLgPZ0qcWt96OasFfhkeIRbJ6br5i0fQawT1/RBGB5L58/Jpwuw==
|
||||
|
||||
domutils@^1.7.0:
|
||||
version "1.7.0"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/domutils/-/domutils-1.7.0.tgz"
|
||||
integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==
|
||||
dependencies:
|
||||
dom-serializer "0"
|
||||
domelementtype "1"
|
||||
|
||||
domutils@^2.5.2, domutils@^2.8.0:
|
||||
version "2.8.0"
|
||||
resolved "https://registry.npmmirror.com/domutils/-/domutils-2.8.0.tgz"
|
||||
@@ -4138,11 +4239,74 @@ error-stack-parser@^2.0.6:
|
||||
dependencies:
|
||||
stackframe "^1.3.4"
|
||||
|
||||
es-abstract@^1.17.2, es-abstract@^1.19.0, es-abstract@^1.20.4, es-abstract@^1.21.2:
|
||||
version "1.21.2"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/es-abstract/-/es-abstract-1.21.2.tgz"
|
||||
integrity sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==
|
||||
dependencies:
|
||||
array-buffer-byte-length "^1.0.0"
|
||||
available-typed-arrays "^1.0.5"
|
||||
call-bind "^1.0.2"
|
||||
es-set-tostringtag "^2.0.1"
|
||||
es-to-primitive "^1.2.1"
|
||||
function.prototype.name "^1.1.5"
|
||||
get-intrinsic "^1.2.0"
|
||||
get-symbol-description "^1.0.0"
|
||||
globalthis "^1.0.3"
|
||||
gopd "^1.0.1"
|
||||
has "^1.0.3"
|
||||
has-property-descriptors "^1.0.0"
|
||||
has-proto "^1.0.1"
|
||||
has-symbols "^1.0.3"
|
||||
internal-slot "^1.0.5"
|
||||
is-array-buffer "^3.0.2"
|
||||
is-callable "^1.2.7"
|
||||
is-negative-zero "^2.0.2"
|
||||
is-regex "^1.1.4"
|
||||
is-shared-array-buffer "^1.0.2"
|
||||
is-string "^1.0.7"
|
||||
is-typed-array "^1.1.10"
|
||||
is-weakref "^1.0.2"
|
||||
object-inspect "^1.12.3"
|
||||
object-keys "^1.1.1"
|
||||
object.assign "^4.1.4"
|
||||
regexp.prototype.flags "^1.4.3"
|
||||
safe-regex-test "^1.0.0"
|
||||
string.prototype.trim "^1.2.7"
|
||||
string.prototype.trimend "^1.0.6"
|
||||
string.prototype.trimstart "^1.0.6"
|
||||
typed-array-length "^1.0.4"
|
||||
unbox-primitive "^1.0.2"
|
||||
which-typed-array "^1.1.9"
|
||||
|
||||
es-array-method-boxes-properly@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz"
|
||||
integrity sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==
|
||||
|
||||
es-module-lexer@^0.9.0:
|
||||
version "0.9.3"
|
||||
resolved "https://registry.npmmirror.com/es-module-lexer/-/es-module-lexer-0.9.3.tgz"
|
||||
integrity sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==
|
||||
|
||||
es-set-tostringtag@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz"
|
||||
integrity sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==
|
||||
dependencies:
|
||||
get-intrinsic "^1.1.3"
|
||||
has "^1.0.3"
|
||||
has-tostringtag "^1.0.0"
|
||||
|
||||
es-to-primitive@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/es-to-primitive/-/es-to-primitive-1.2.1.tgz"
|
||||
integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==
|
||||
dependencies:
|
||||
is-callable "^1.1.4"
|
||||
is-date-object "^1.0.1"
|
||||
is-symbol "^1.0.2"
|
||||
|
||||
escalade@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.npmmirror.com/escalade/-/escalade-3.1.1.tgz"
|
||||
@@ -4614,6 +4778,13 @@ follow-redirects@^1.0.0, follow-redirects@^1.15.0:
|
||||
resolved "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.2.tgz"
|
||||
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
|
||||
|
||||
for-each@^0.3.3:
|
||||
version "0.3.3"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/for-each/-/for-each-0.3.3.tgz"
|
||||
integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==
|
||||
dependencies:
|
||||
is-callable "^1.1.3"
|
||||
|
||||
fork-ts-checker-webpack-plugin@^6.4.0:
|
||||
version "6.5.3"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.3.tgz"
|
||||
@@ -4691,11 +4862,26 @@ function-bind@^1.1.1:
|
||||
resolved "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.1.tgz"
|
||||
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
|
||||
|
||||
function.prototype.name@^1.1.5:
|
||||
version "1.1.5"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/function.prototype.name/-/function.prototype.name-1.1.5.tgz"
|
||||
integrity sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
define-properties "^1.1.3"
|
||||
es-abstract "^1.19.0"
|
||||
functions-have-names "^1.2.2"
|
||||
|
||||
functional-red-black-tree@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npmmirror.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz"
|
||||
integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==
|
||||
|
||||
functions-have-names@^1.2.2, functions-have-names@^1.2.3:
|
||||
version "1.2.3"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/functions-have-names/-/functions-have-names-1.2.3.tgz"
|
||||
integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==
|
||||
|
||||
gensync@^1.0.0-beta.2:
|
||||
version "1.0.0-beta.2"
|
||||
resolved "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz"
|
||||
@@ -4706,7 +4892,7 @@ get-caller-file@^2.0.5:
|
||||
resolved "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz"
|
||||
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
|
||||
|
||||
get-intrinsic@^1.0.2, get-intrinsic@^1.1.1:
|
||||
get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.2.0.tgz"
|
||||
integrity sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==
|
||||
@@ -4737,6 +4923,14 @@ get-stream@^6.0.0:
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/get-stream/-/get-stream-6.0.1.tgz"
|
||||
integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==
|
||||
|
||||
get-symbol-description@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/get-symbol-description/-/get-symbol-description-1.0.0.tgz"
|
||||
integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
get-intrinsic "^1.1.1"
|
||||
|
||||
glob-parent@^5.1.2, glob-parent@~5.1.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz"
|
||||
@@ -4798,6 +4992,13 @@ globals@^13.9.0:
|
||||
dependencies:
|
||||
type-fest "^0.20.2"
|
||||
|
||||
globalthis@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/globalthis/-/globalthis-1.0.3.tgz"
|
||||
integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==
|
||||
dependencies:
|
||||
define-properties "^1.1.3"
|
||||
|
||||
globby@^11.0.2, globby@^11.0.3, globby@^11.1.0:
|
||||
version "11.1.0"
|
||||
resolved "https://registry.npmmirror.com/globby/-/globby-11.1.0.tgz"
|
||||
@@ -4810,6 +5011,13 @@ globby@^11.0.2, globby@^11.0.3, globby@^11.1.0:
|
||||
merge2 "^1.4.1"
|
||||
slash "^3.0.0"
|
||||
|
||||
gopd@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/gopd/-/gopd-1.0.1.tgz"
|
||||
integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==
|
||||
dependencies:
|
||||
get-intrinsic "^1.1.3"
|
||||
|
||||
graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9:
|
||||
version "4.2.10"
|
||||
resolved "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.10.tgz"
|
||||
@@ -4832,9 +5040,14 @@ handle-thing@^2.0.0:
|
||||
resolved "https://registry.npmmirror.com/handle-thing/-/handle-thing-2.0.1.tgz"
|
||||
integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==
|
||||
|
||||
has-bigints@^1.0.1, has-bigints@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/has-bigints/-/has-bigints-1.0.2.tgz"
|
||||
integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==
|
||||
|
||||
has-flag@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmmirror.com/has-flag/-/has-flag-3.0.0.tgz"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/has-flag/-/has-flag-3.0.0.tgz"
|
||||
integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==
|
||||
|
||||
has-flag@^4.0.0:
|
||||
@@ -4849,11 +5062,23 @@ has-property-descriptors@^1.0.0:
|
||||
dependencies:
|
||||
get-intrinsic "^1.1.1"
|
||||
|
||||
has-symbols@^1.0.3:
|
||||
has-proto@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/has-proto/-/has-proto-1.0.1.tgz"
|
||||
integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==
|
||||
|
||||
has-symbols@^1.0.1, has-symbols@^1.0.2, has-symbols@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.0.3.tgz"
|
||||
integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
|
||||
|
||||
has-tostringtag@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/has-tostringtag/-/has-tostringtag-1.0.0.tgz"
|
||||
integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==
|
||||
dependencies:
|
||||
has-symbols "^1.0.2"
|
||||
|
||||
has@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.npmmirror.com/has/-/has-1.0.3.tgz"
|
||||
@@ -5113,6 +5338,15 @@ ini@^1.3.4:
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/ini/-/ini-1.3.8.tgz"
|
||||
integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
|
||||
|
||||
internal-slot@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/internal-slot/-/internal-slot-1.0.5.tgz"
|
||||
integrity sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==
|
||||
dependencies:
|
||||
get-intrinsic "^1.2.0"
|
||||
has "^1.0.3"
|
||||
side-channel "^1.0.4"
|
||||
|
||||
ipaddr.js@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-2.0.1.tgz"
|
||||
@@ -5123,11 +5357,27 @@ ipaddr.js@1.9.1:
|
||||
resolved "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz"
|
||||
integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
|
||||
|
||||
is-array-buffer@^3.0.1, is-array-buffer@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/is-array-buffer/-/is-array-buffer-3.0.2.tgz"
|
||||
integrity sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
get-intrinsic "^1.2.0"
|
||||
is-typed-array "^1.1.10"
|
||||
|
||||
is-arrayish@^0.2.1:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.2.1.tgz"
|
||||
integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==
|
||||
|
||||
is-bigint@^1.0.1:
|
||||
version "1.0.4"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/is-bigint/-/is-bigint-1.0.4.tgz"
|
||||
integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==
|
||||
dependencies:
|
||||
has-bigints "^1.0.1"
|
||||
|
||||
is-binary-path@~2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz"
|
||||
@@ -5135,11 +5385,24 @@ is-binary-path@~2.1.0:
|
||||
dependencies:
|
||||
binary-extensions "^2.0.0"
|
||||
|
||||
is-boolean-object@^1.1.0:
|
||||
version "1.1.2"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/is-boolean-object/-/is-boolean-object-1.1.2.tgz"
|
||||
integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
has-tostringtag "^1.0.0"
|
||||
|
||||
is-buffer@^1.1.5:
|
||||
version "1.1.6"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/is-buffer/-/is-buffer-1.1.6.tgz"
|
||||
integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
|
||||
|
||||
is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7:
|
||||
version "1.2.7"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/is-callable/-/is-callable-1.2.7.tgz"
|
||||
integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==
|
||||
|
||||
is-ci@^1.0.10:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.npmmirror.com/is-ci/-/is-ci-1.2.1.tgz"
|
||||
@@ -5154,6 +5417,13 @@ is-core-module@^2.9.0:
|
||||
dependencies:
|
||||
has "^1.0.3"
|
||||
|
||||
is-date-object@^1.0.1:
|
||||
version "1.0.5"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/is-date-object/-/is-date-object-1.0.5.tgz"
|
||||
integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==
|
||||
dependencies:
|
||||
has-tostringtag "^1.0.0"
|
||||
|
||||
is-docker@^2.0.0, is-docker@^2.1.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.npmmirror.com/is-docker/-/is-docker-2.2.1.tgz"
|
||||
@@ -5210,6 +5480,18 @@ is-interactive@^1.0.0:
|
||||
resolved "https://registry.npmmirror.com/is-interactive/-/is-interactive-1.0.0.tgz"
|
||||
integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==
|
||||
|
||||
is-negative-zero@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/is-negative-zero/-/is-negative-zero-2.0.2.tgz"
|
||||
integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==
|
||||
|
||||
is-number-object@^1.0.4:
|
||||
version "1.0.7"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/is-number-object/-/is-number-object-1.0.7.tgz"
|
||||
integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==
|
||||
dependencies:
|
||||
has-tostringtag "^1.0.0"
|
||||
|
||||
is-number@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz"
|
||||
@@ -5232,6 +5514,21 @@ is-potential-custom-element-name@^1.0.1:
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz"
|
||||
integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==
|
||||
|
||||
is-regex@^1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/is-regex/-/is-regex-1.1.4.tgz"
|
||||
integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
has-tostringtag "^1.0.0"
|
||||
|
||||
is-shared-array-buffer@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz"
|
||||
integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
|
||||
is-stream@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.npmmirror.com/is-stream/-/is-stream-1.1.0.tgz"
|
||||
@@ -5242,6 +5539,31 @@ is-stream@^2.0.0:
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/is-stream/-/is-stream-2.0.1.tgz"
|
||||
integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==
|
||||
|
||||
is-string@^1.0.5, is-string@^1.0.7:
|
||||
version "1.0.7"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/is-string/-/is-string-1.0.7.tgz"
|
||||
integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==
|
||||
dependencies:
|
||||
has-tostringtag "^1.0.0"
|
||||
|
||||
is-symbol@^1.0.2, is-symbol@^1.0.3:
|
||||
version "1.0.4"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/is-symbol/-/is-symbol-1.0.4.tgz"
|
||||
integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==
|
||||
dependencies:
|
||||
has-symbols "^1.0.2"
|
||||
|
||||
is-typed-array@^1.1.10, is-typed-array@^1.1.9:
|
||||
version "1.1.10"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/is-typed-array/-/is-typed-array-1.1.10.tgz"
|
||||
integrity sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==
|
||||
dependencies:
|
||||
available-typed-arrays "^1.0.5"
|
||||
call-bind "^1.0.2"
|
||||
for-each "^0.3.3"
|
||||
gopd "^1.0.1"
|
||||
has-tostringtag "^1.0.0"
|
||||
|
||||
is-typedarray@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/is-typedarray/-/is-typedarray-1.0.0.tgz"
|
||||
@@ -5252,6 +5574,13 @@ is-unicode-supported@^0.1.0:
|
||||
resolved "https://registry.npmmirror.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz"
|
||||
integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==
|
||||
|
||||
is-weakref@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/is-weakref/-/is-weakref-1.0.2.tgz"
|
||||
integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
|
||||
is-whitespace@^0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/is-whitespace/-/is-whitespace-0.3.0.tgz"
|
||||
@@ -5264,6 +5593,11 @@ is-wsl@^2.1.1, is-wsl@^2.2.0:
|
||||
dependencies:
|
||||
is-docker "^2.0.0"
|
||||
|
||||
isarray@^2.0.5:
|
||||
version "2.0.5"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/isarray/-/isarray-2.0.5.tgz"
|
||||
integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==
|
||||
|
||||
isarray@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz"
|
||||
@@ -5920,7 +6254,7 @@ json-stable-stringify-without-jsonify@^1.0.1:
|
||||
|
||||
json5@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npmmirror.com/json5/-/json5-1.0.2.tgz"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/json5/-/json5-1.0.2.tgz"
|
||||
integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==
|
||||
dependencies:
|
||||
minimist "^1.2.0"
|
||||
@@ -6042,6 +6376,15 @@ loader-utils@^1.1.0:
|
||||
emojis-list "^3.0.0"
|
||||
json5 "^1.0.1"
|
||||
|
||||
loader-utils@^1.2.3:
|
||||
version "1.4.2"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/loader-utils/-/loader-utils-1.4.2.tgz"
|
||||
integrity sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==
|
||||
dependencies:
|
||||
big.js "^5.2.2"
|
||||
emojis-list "^3.0.0"
|
||||
json5 "^1.0.1"
|
||||
|
||||
loader-utils@^2.0.0:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.npmmirror.com/loader-utils/-/loader-utils-2.0.4.tgz"
|
||||
@@ -6196,6 +6539,11 @@ mdn-data@2.0.30:
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/mdn-data/-/mdn-data-2.0.30.tgz"
|
||||
integrity sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==
|
||||
|
||||
mdn-data@2.0.4:
|
||||
version "2.0.4"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/mdn-data/-/mdn-data-2.0.4.tgz"
|
||||
integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==
|
||||
|
||||
mdurl@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/mdurl/-/mdurl-1.0.1.tgz"
|
||||
@@ -6313,7 +6661,7 @@ minipass@^3.1.1:
|
||||
dependencies:
|
||||
yallist "^4.0.0"
|
||||
|
||||
mkdirp@^0.5.6:
|
||||
mkdirp@^0.5.6, mkdirp@~0.5.1:
|
||||
version "0.5.6"
|
||||
resolved "https://registry.npmmirror.com/mkdirp/-/mkdirp-0.5.6.tgz"
|
||||
integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==
|
||||
@@ -6473,6 +6821,13 @@ npm-run-path@^4.0.1:
|
||||
dependencies:
|
||||
path-key "^3.0.0"
|
||||
|
||||
nth-check@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/nth-check/-/nth-check-1.0.2.tgz"
|
||||
integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==
|
||||
dependencies:
|
||||
boolbase "~1.0.0"
|
||||
|
||||
nth-check@^2.0.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.npmmirror.com/nth-check/-/nth-check-2.1.1.tgz"
|
||||
@@ -6490,7 +6845,7 @@ object-assign@^4.0.1:
|
||||
resolved "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz"
|
||||
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
|
||||
|
||||
object-inspect@^1.9.0:
|
||||
object-inspect@^1.12.3, object-inspect@^1.9.0:
|
||||
version "1.12.3"
|
||||
resolved "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.12.3.tgz"
|
||||
integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==
|
||||
@@ -6500,7 +6855,7 @@ object-keys@^1.1.1:
|
||||
resolved "https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz"
|
||||
integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
|
||||
|
||||
object.assign@^4.1.0:
|
||||
object.assign@^4.1.0, object.assign@^4.1.4:
|
||||
version "4.1.4"
|
||||
resolved "https://registry.npmmirror.com/object.assign/-/object.assign-4.1.4.tgz"
|
||||
integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==
|
||||
@@ -6510,6 +6865,17 @@ object.assign@^4.1.0:
|
||||
has-symbols "^1.0.3"
|
||||
object-keys "^1.1.1"
|
||||
|
||||
object.getownpropertydescriptors@^2.1.0:
|
||||
version "2.1.6"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.6.tgz"
|
||||
integrity sha512-lq+61g26E/BgHv0ZTFgRvi7NMEPuAxLkFU7rukXjc/AlwH4Am5xXVnIXy3un1bg/JPbXHrixRkK1itUzzPiIjQ==
|
||||
dependencies:
|
||||
array.prototype.reduce "^1.0.5"
|
||||
call-bind "^1.0.2"
|
||||
define-properties "^1.2.0"
|
||||
es-abstract "^1.21.2"
|
||||
safe-array-concat "^1.0.0"
|
||||
|
||||
object.omit@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/object.omit/-/object.omit-3.0.0.tgz"
|
||||
@@ -6524,6 +6890,15 @@ object.pick@^1.3.0:
|
||||
dependencies:
|
||||
isobject "^3.0.1"
|
||||
|
||||
object.values@^1.1.0:
|
||||
version "1.1.6"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/object.values/-/object.values-1.1.6.tgz"
|
||||
integrity sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
define-properties "^1.1.4"
|
||||
es-abstract "^1.20.4"
|
||||
|
||||
obuf@^1.0.0, obuf@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.npmmirror.com/obuf/-/obuf-1.1.2.tgz"
|
||||
@@ -6786,6 +7161,11 @@ pkg-dir@^4.1.0, pkg-dir@^4.2.0:
|
||||
dependencies:
|
||||
find-up "^4.0.0"
|
||||
|
||||
popper.js@^1.16.1:
|
||||
version "1.16.1"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/popper.js/-/popper.js-1.16.1.tgz"
|
||||
integrity sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==
|
||||
|
||||
portfinder@^1.0.26:
|
||||
version "1.0.32"
|
||||
resolved "https://registry.npmmirror.com/portfinder/-/portfinder-1.0.32.tgz"
|
||||
@@ -7339,6 +7719,11 @@ punycode@^2.1.0, punycode@^2.1.1:
|
||||
resolved "https://registry.npmmirror.com/punycode/-/punycode-2.3.0.tgz"
|
||||
integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==
|
||||
|
||||
q@^1.1.2:
|
||||
version "1.5.1"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/q/-/q-1.5.1.tgz"
|
||||
integrity sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==
|
||||
|
||||
qs@6.11.0:
|
||||
version "6.11.0"
|
||||
resolved "https://registry.npmmirror.com/qs/-/qs-6.11.0.tgz"
|
||||
@@ -7460,6 +7845,15 @@ regenerator-transform@^0.15.1:
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.8.4"
|
||||
|
||||
regexp.prototype.flags@^1.4.3:
|
||||
version "1.5.0"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz"
|
||||
integrity sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
define-properties "^1.2.0"
|
||||
functions-have-names "^1.2.3"
|
||||
|
||||
regexpp@^3.1.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.npmmirror.com/regexpp/-/regexpp-3.2.0.tgz"
|
||||
@@ -7591,6 +7985,16 @@ run-parallel@^1.1.9:
|
||||
dependencies:
|
||||
queue-microtask "^1.2.2"
|
||||
|
||||
safe-array-concat@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/safe-array-concat/-/safe-array-concat-1.0.0.tgz"
|
||||
integrity sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
get-intrinsic "^1.2.0"
|
||||
has-symbols "^1.0.3"
|
||||
isarray "^2.0.5"
|
||||
|
||||
safe-buffer@^5.1.0, safe-buffer@>=5.1.0, safe-buffer@~5.2.0, safe-buffer@5.2.1:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz"
|
||||
@@ -7606,6 +8010,15 @@ safe-buffer@5.1.2:
|
||||
resolved "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz"
|
||||
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
|
||||
|
||||
safe-regex-test@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/safe-regex-test/-/safe-regex-test-1.0.0.tgz"
|
||||
integrity sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
get-intrinsic "^1.1.3"
|
||||
is-regex "^1.1.4"
|
||||
|
||||
"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0":
|
||||
version "2.1.2"
|
||||
resolved "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz"
|
||||
@@ -7628,6 +8041,11 @@ sass@^1.3.0, sass@^1.62.0:
|
||||
immutable "^4.0.0"
|
||||
source-map-js ">=0.6.2 <2.0.0"
|
||||
|
||||
sax@~1.2.4:
|
||||
version "1.2.4"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/sax/-/sax-1.2.4.tgz"
|
||||
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
|
||||
|
||||
saxes@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/saxes/-/saxes-5.0.1.tgz"
|
||||
@@ -8095,6 +8513,33 @@ string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
strip-ansi "^6.0.1"
|
||||
|
||||
string.prototype.trim@^1.2.7:
|
||||
version "1.2.7"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz"
|
||||
integrity sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
define-properties "^1.1.4"
|
||||
es-abstract "^1.20.4"
|
||||
|
||||
string.prototype.trimend@^1.0.6:
|
||||
version "1.0.6"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz"
|
||||
integrity sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
define-properties "^1.1.4"
|
||||
es-abstract "^1.20.4"
|
||||
|
||||
string.prototype.trimstart@^1.0.6:
|
||||
version "1.0.6"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz"
|
||||
integrity sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
define-properties "^1.1.4"
|
||||
es-abstract "^1.20.4"
|
||||
|
||||
strip-ansi@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-4.0.0.tgz"
|
||||
@@ -8161,7 +8606,7 @@ stylehacks@^5.1.1:
|
||||
|
||||
supports-color@^5.3.0:
|
||||
version "5.5.0"
|
||||
resolved "https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/supports-color/-/supports-color-5.5.0.tgz"
|
||||
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
|
||||
dependencies:
|
||||
has-flag "^3.0.0"
|
||||
@@ -8198,6 +8643,32 @@ svg-tags@^1.0.0:
|
||||
resolved "https://registry.npmmirror.com/svg-tags/-/svg-tags-1.0.0.tgz"
|
||||
integrity sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==
|
||||
|
||||
svg-to-vue@^0.7.0:
|
||||
version "0.7.0"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/svg-to-vue/-/svg-to-vue-0.7.0.tgz"
|
||||
integrity sha512-Tg2nMmf3BQorYCAjxbtTkYyWPVSeox5AZUFvfy4MoWK/5tuQlnA/h3LAlTjV3sEvOC5FtUNovRSj3p784l4KOA==
|
||||
dependencies:
|
||||
svgo "^1.3.2"
|
||||
|
||||
svgo@^1.3.2:
|
||||
version "1.3.2"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/svgo/-/svgo-1.3.2.tgz"
|
||||
integrity sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==
|
||||
dependencies:
|
||||
chalk "^2.4.1"
|
||||
coa "^2.0.2"
|
||||
css-select "^2.0.0"
|
||||
css-select-base-adapter "^0.1.1"
|
||||
css-tree "1.0.0-alpha.37"
|
||||
csso "^4.0.2"
|
||||
js-yaml "^3.13.1"
|
||||
mkdirp "~0.5.1"
|
||||
object.values "^1.1.0"
|
||||
sax "~1.2.4"
|
||||
stable "^0.1.8"
|
||||
unquote "~1.1.1"
|
||||
util.promisify "~1.0.0"
|
||||
|
||||
svgo@^2.7.0:
|
||||
version "2.8.0"
|
||||
resolved "https://registry.npmmirror.com/svgo/-/svgo-2.8.0.tgz"
|
||||
@@ -8479,6 +8950,15 @@ type-is@~1.6.18:
|
||||
media-typer "0.3.0"
|
||||
mime-types "~2.1.24"
|
||||
|
||||
typed-array-length@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/typed-array-length/-/typed-array-length-1.0.4.tgz"
|
||||
integrity sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
for-each "^0.3.3"
|
||||
is-typed-array "^1.1.9"
|
||||
|
||||
typedarray-to-buffer@^3.1.5:
|
||||
version "3.1.5"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz"
|
||||
@@ -8496,6 +8976,16 @@ uc.micro@^1.0.1, uc.micro@^1.0.5:
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/uc.micro/-/uc.micro-1.0.6.tgz"
|
||||
integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==
|
||||
|
||||
unbox-primitive@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/unbox-primitive/-/unbox-primitive-1.0.2.tgz"
|
||||
integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
has-bigints "^1.0.2"
|
||||
has-symbols "^1.0.3"
|
||||
which-boxed-primitive "^1.0.2"
|
||||
|
||||
unicode-canonical-property-names-ecmascript@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmmirror.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz"
|
||||
@@ -8534,6 +9024,11 @@ unpipe@~1.0.0, unpipe@1.0.0:
|
||||
resolved "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz"
|
||||
integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==
|
||||
|
||||
unquote@~1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/unquote/-/unquote-1.1.1.tgz"
|
||||
integrity sha512-vRCqFv6UhXpWxZPyGDh/F3ZpNv8/qo7w6iufLpQg9aKnQ71qM4B5KiI7Mia9COcjEhrO9LueHpMYjYzsWH3OIg==
|
||||
|
||||
update-browserslist-db@^1.0.10:
|
||||
version "1.0.10"
|
||||
resolved "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz"
|
||||
@@ -8562,6 +9057,16 @@ util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
|
||||
resolved "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz"
|
||||
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
|
||||
|
||||
util.promisify@~1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/util.promisify/-/util.promisify-1.0.1.tgz"
|
||||
integrity sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==
|
||||
dependencies:
|
||||
define-properties "^1.1.3"
|
||||
es-abstract "^1.17.2"
|
||||
has-symbols "^1.0.1"
|
||||
object.getownpropertydescriptors "^2.1.0"
|
||||
|
||||
utila@~0.4:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.npmmirror.com/utila/-/utila-0.4.0.tgz"
|
||||
@@ -8629,7 +9134,7 @@ vue-hot-reload-api@^2.3.0:
|
||||
|
||||
vue-loader@^17.0.0:
|
||||
version "17.0.1"
|
||||
resolved "https://registry.npmmirror.com/vue-loader/-/vue-loader-17.0.1.tgz"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/vue-loader/-/vue-loader-17.0.1.tgz"
|
||||
integrity sha512-/OOyugJnImKCkAKrAvdsWMuwoCqGxWT5USLsjohzWbMgOwpA5wQmzQiLMzZd7DjhIfunzAGIApTOgIylz/kwcg==
|
||||
dependencies:
|
||||
chalk "^4.1.0"
|
||||
@@ -8651,6 +9156,22 @@ vue-style-loader@^4.1.0, vue-style-loader@^4.1.3:
|
||||
hash-sum "^1.0.2"
|
||||
loader-utils "^1.0.2"
|
||||
|
||||
vue-svg-loader@^0.16.0:
|
||||
version "0.16.0"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/vue-svg-loader/-/vue-svg-loader-0.16.0.tgz"
|
||||
integrity sha512-2RtFXlTCYWm8YAEO2qAOZ2SuIF2NvLutB5muc3KDYoZq5ZeCHf8ggzSan3ksbbca7CJ/Aw57ZnDF4B7W/AkGtw==
|
||||
dependencies:
|
||||
loader-utils "^1.2.3"
|
||||
svg-to-vue "^0.7.0"
|
||||
|
||||
vue-template-compiler@*, vue-template-compiler@^2.0.0:
|
||||
version "2.7.14"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/vue-template-compiler/-/vue-template-compiler-2.7.14.tgz"
|
||||
integrity sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ==
|
||||
dependencies:
|
||||
de-indent "^1.0.2"
|
||||
he "^1.2.0"
|
||||
|
||||
vue-template-es2015-compiler@^1.9.0:
|
||||
version "1.9.1"
|
||||
resolved "https://registry.npmmirror.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz"
|
||||
@@ -8911,6 +9432,29 @@ whatwg-url@^8.5.0:
|
||||
tr46 "^2.1.0"
|
||||
webidl-conversions "^6.1.0"
|
||||
|
||||
which-boxed-primitive@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz"
|
||||
integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==
|
||||
dependencies:
|
||||
is-bigint "^1.0.1"
|
||||
is-boolean-object "^1.1.0"
|
||||
is-number-object "^1.0.4"
|
||||
is-string "^1.0.5"
|
||||
is-symbol "^1.0.3"
|
||||
|
||||
which-typed-array@^1.1.9:
|
||||
version "1.1.9"
|
||||
resolved "https://repo.plus4u.net/operatorGate/repository/public-javascript/which-typed-array/-/which-typed-array-1.1.9.tgz"
|
||||
integrity sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==
|
||||
dependencies:
|
||||
available-typed-arrays "^1.0.5"
|
||||
call-bind "^1.0.2"
|
||||
for-each "^0.3.3"
|
||||
gopd "^1.0.1"
|
||||
has-tostringtag "^1.0.0"
|
||||
is-typed-array "^1.1.10"
|
||||
|
||||
which@^1.2.9:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.npmmirror.com/which/-/which-1.3.1.tgz"
|
||||
|
||||