Compare commits
39 Commits
cd996d851a
...
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 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -32,3 +32,4 @@ tmp
|
||||
test.*
|
||||
*.png
|
||||
!*/schemas/*
|
||||
todo
|
||||
31
README.md
Normal file
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,54 +1,120 @@
|
||||
import statusCode from "../utils/statusCodes";
|
||||
import { Response } from "express";
|
||||
import { getMessageUid, getUserOfMailbox } from "../db/utils/mail";
|
||||
import emailManager from "../mails/EmailManager";
|
||||
import logger from "../system/Logger";
|
||||
import Message from "../mails/message/Message";
|
||||
import Room from "../mails/room/Room";
|
||||
|
||||
export default class Message {
|
||||
static async addFlag(body, res: Response) {
|
||||
export default class MessageAbl {
|
||||
static async changeFlag(body, res: Response, isDelete: boolean) {
|
||||
const { mailboxId, messageId, flag } = body;
|
||||
const uid = (await getMessageUid(messageId))[0]?.uid;
|
||||
if (!uid) {
|
||||
const message = new Message().setMessageId(messageId);
|
||||
|
||||
try {
|
||||
await message.useUid();
|
||||
} catch (err) {
|
||||
res.status(statusCode.NOT_FOUND).send({ error: "Message uid not found." });
|
||||
}
|
||||
|
||||
const user = (await getUserOfMailbox(mailboxId))[0]?.user;
|
||||
if (!user) {
|
||||
try {
|
||||
await message.useMailbox(mailboxId);
|
||||
} catch (err) {
|
||||
res.status(statusCode.NOT_FOUND).send({ error: "Not account for this mailbox." });
|
||||
}
|
||||
emailManager
|
||||
.getImap(user)
|
||||
.getMailbox(mailboxId)
|
||||
.addFlag(uid.toString(), [flag])
|
||||
.then(() => {
|
||||
res.status(statusCode.OK).send();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
|
||||
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) {
|
||||
await MessageAbl.changeFlag(body, res, false);
|
||||
}
|
||||
|
||||
static async removeFlag(body, res: Response) {
|
||||
const { mailboxId, messageId, flag } = body;
|
||||
const uid = (await getMessageUid(messageId))[0]?.uid;
|
||||
if (!uid) {
|
||||
res.status(statusCode.NOT_FOUND).send({ error: "Message uid not found." });
|
||||
await MessageAbl.changeFlag(body, res, true);
|
||||
}
|
||||
|
||||
const user = (await getUserOfMailbox(mailboxId))[0]?.user;
|
||||
if (!user) {
|
||||
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;
|
||||
}
|
||||
emailManager
|
||||
.getImap(user)
|
||||
.getMailbox(mailboxId)
|
||||
.removeFlag(uid.toString(), [flag])
|
||||
.then(() => {
|
||||
res.status(statusCode.OK).send();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
res.status(statusCode.METHOD_FAILURE).send({ error: err });
|
||||
|
||||
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
|
||||
@@ -106,7 +109,8 @@ export async function getMessages(roomId: number) {
|
||||
) 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,6 +124,13 @@ 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 ASC;
|
||||
|
||||
@@ -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
|
||||
`;
|
||||
|
||||
23
back/db/message/message-db.ts
Normal file
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);
|
||||
}
|
||||
@@ -40,6 +40,12 @@ export async function getMessageUid(messageId: number): Promise<{uid: number}[]>
|
||||
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];
|
||||
|
||||
@@ -14,9 +14,9 @@ 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;
|
||||
@@ -41,38 +41,45 @@ 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 = "";
|
||||
getMailboxName(boxToFound: string): Promise<string> {
|
||||
return new Promise((resolve, rejects) => {
|
||||
let matchBox = "";
|
||||
this.imap.getBoxes("", (err, boxes) => {
|
||||
Object.keys(boxes).forEach((key) => {
|
||||
if (key === "INBOX") return;
|
||||
if (allBox.includes("/")) return; // already found
|
||||
if (matchBox.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;
|
||||
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 {
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import Imap, { ImapMessageAttributes, Box } from "imap";
|
||||
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,14 +21,16 @@ export default class Mailbox {
|
||||
box: Box;
|
||||
msgToSync: number;
|
||||
syncing: boolean;
|
||||
imapInstance: ImapInstance;
|
||||
|
||||
constructor(_imap: Imap, _boxId: number, _boxName: string) {
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -44,7 +48,7 @@ export default class Mailbox {
|
||||
this.imap.on("mail", (numNewMsgs: number) => {
|
||||
if (!this.syncing) {
|
||||
// if not syncing restart a sync
|
||||
this.syncMail(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;
|
||||
@@ -57,26 +61,32 @@ export default class Mailbox {
|
||||
const updateMsg = new updateMessage(info.uid, info.flags);
|
||||
updateMsg.updateFlags();
|
||||
});
|
||||
|
||||
// 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.");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async updateModseq(newModseq: number) {
|
||||
updateMailboxModseq(this.id, newModseq).then(() => {
|
||||
this.box.highestmodseq = newModseq;
|
||||
this.box.highestmodseq = newModseq.toString();
|
||||
});
|
||||
}
|
||||
|
||||
async initSync(box: Box) {
|
||||
// sync mail only if has new messages
|
||||
if (this.box.uidnext < box.uidnext) {
|
||||
this.syncMail(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 (box.highestmodseq > lastModseq) {
|
||||
if (parseInt(box.highestmodseq) > lastModseq) {
|
||||
const fetchStream = this.imap.fetch("1:*", { bodies: "", modifiers: { changedsince: lastModseq } });
|
||||
fetchStream.on("message", (message) => {
|
||||
message.once("attributes", (attrs) => {
|
||||
@@ -91,53 +101,44 @@ export default class Mailbox {
|
||||
logger.log("Done fetching new flags");
|
||||
});
|
||||
} else {
|
||||
logger.log("Flags already up to date")
|
||||
logger.log("Flags already up to date");
|
||||
}
|
||||
this.updateModseq(box.highestmodseq);
|
||||
this.updateModseq(parseInt(box.highestmodseq));
|
||||
}
|
||||
|
||||
async syncMail(savedUid: number, currentUid: number) {
|
||||
syncManager = async (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,
|
||||
});
|
||||
logger.log(`Fetching from ${savedUid} to ${currentUid} uid`);
|
||||
const nbMessageToSync = currentUid - savedUid;
|
||||
let STEP = nbMessageToSync > 200 ? Math.floor(nbMessageToSync / 7) : nbMessageToSync;
|
||||
let mails: AttrsWithEnvelope[] = [];
|
||||
|
||||
f.on("message", (msg, seqno) => {
|
||||
msg.once("attributes", (attrs: AttrsWithEnvelope) => {
|
||||
mails.push(attrs);
|
||||
promises.push(saveMessage(attrs, this.id, this.imap));
|
||||
});
|
||||
});
|
||||
|
||||
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);
|
||||
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();
|
||||
resolve("");
|
||||
})
|
||||
.catch((err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
logger.err("Failed to save a message: " + error);
|
||||
}
|
||||
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;
|
||||
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) {
|
||||
@@ -145,8 +146,33 @@ export default class Mailbox {
|
||||
this.box.uidnext += this.msgToSync;
|
||||
// reset value to allow to detect new incoming message while syncing
|
||||
this.msgToSync = 0;
|
||||
this.syncMail(currentUid, this.box.uidnext);
|
||||
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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -173,4 +199,13 @@ export default class Mailbox {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
46
back/mails/room/Room.ts
Normal file
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
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
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
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;
|
||||
@@ -2,8 +2,10 @@ import express from "express";
|
||||
import cors from "cors";
|
||||
const app = express();
|
||||
import { execQueryAsync, execQuery } from "./db/db";
|
||||
import mailRouter from "./routes/mail";
|
||||
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(
|
||||
@@ -14,8 +16,11 @@ app.use(
|
||||
app.use(cors());
|
||||
app.listen(process.env.PORT || 5500);
|
||||
|
||||
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
|
||||
@@ -30,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
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
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
1813
front/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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,9 +19,14 @@
|
||||
|
||||
--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
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
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
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/pushpin-2-line.svg
Normal file
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
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 |
40
front/src/components/basic/Button.vue
Normal file
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
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>
|
||||
<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 -->
|
||||
<div>
|
||||
<button :disabled="error != ''" @click="addAccountRequest">Add</button>
|
||||
<template v-slot:actions>
|
||||
<Button :disabled="error != ''" :onClick="addAccountRequest" text="Add" />
|
||||
{{ error }}
|
||||
</div>
|
||||
</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 {
|
||||
.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
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">
|
||||
<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;
|
||||
|
||||
@@ -84,6 +84,19 @@ function sendMessage() {
|
||||
});
|
||||
}
|
||||
|
||||
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...
|
||||
// Font selection: choose the font family, size, color, and style
|
||||
// Images: insert pictures and graphics into your email
|
||||
@@ -95,21 +108,56 @@ function sendMessage() {
|
||||
<template>
|
||||
<div v-if="editor">
|
||||
<div id="options" v-if="showOptions">
|
||||
<span class="category">
|
||||
<SvgLoader
|
||||
svg="bold"
|
||||
@click="editor.chain().focus().toggleBold().run()"
|
||||
:classes="getClasses(editor.isActive('bold'))"
|
||||
v-tooltip="{ text: 'Bold', shortcut: ['Ctrl', 'B'] }"
|
||||
/>
|
||||
<SvgLoader
|
||||
svg="italic"
|
||||
@click="editor.chain().focus().toggleItalic().run()"
|
||||
:classes="getClasses(editor.isActive('italic'))"
|
||||
v-tooltip="{ text: 'Italic', shortcut: ['Ctrl', 'I'] }"
|
||||
/>
|
||||
<SvgLoader
|
||||
svg="strikethrough"
|
||||
@click="editor.chain().focus().toggleStrike().run()"
|
||||
:classes="getClasses(editor.isActive('strike'))"
|
||||
v-tooltip="{ text: 'Strike', shortcut: ['Ctrl', 'Shift', 'X'] }"
|
||||
/>
|
||||
<SvgLoader
|
||||
svg="underline"
|
||||
@click="editor.chain().focus().toggleUnderline().run()"
|
||||
:classes="getClasses(editor.isActive('underline'))"
|
||||
v-tooltip="{ text: 'Underline', shortcut: ['Ctrl', 'U'] }"
|
||||
/>
|
||||
<SvgLoader
|
||||
svg="font-color"
|
||||
@click="editor.commands.toggleHighlight({ color: '#ffcc00' })"
|
||||
:classes="getClasses(editor.isActive('highlight'))"
|
||||
v-tooltip="{ text: 'Highlight', shortcut: ['Ctrl', 'Shift', 'H'] }"
|
||||
/>
|
||||
</span>
|
||||
<span class="category">
|
||||
<SvgLoader
|
||||
svg="h-2"
|
||||
@click="editor.chain().focus().toggleHeading({ level: 2 }).run()"
|
||||
:class="[{ 'is-active': editor.isActive({ level: 2 }) }, 'editorOption']"
|
||||
: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()"
|
||||
:class="[{ 'is-active': editor.isActive({ level: 3 }) }, 'editorOption']"
|
||||
: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()"
|
||||
:class="[{ 'is-active': editor.isActive({ level: 4 }) }, 'editorOption']"
|
||||
:classes="getClasses(editor.isActive({ level: 4 }))"
|
||||
v-tooltip="{ text: 'Heading 4', shortcut: ['Ctrl', 'Alt', '4'] }"
|
||||
/>
|
||||
</span>
|
||||
<!-- <SvgLoader
|
||||
@@ -121,34 +169,40 @@ function sendMessage() {
|
||||
<SvgLoader
|
||||
svg="list-ordered"
|
||||
@click="editor.chain().focus().toggleOrderedList().run()"
|
||||
:class="[{ 'is-active': editor.isActive('orderedList') }, 'editorOption']"
|
||||
: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') }, 'editorOption']"
|
||||
: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()"
|
||||
:class="[{ 'is-active': editor.isActive({ textAlign: 'left' }) }, 'editorOption']"
|
||||
: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()"
|
||||
:class="[{ 'is-active': editor.isActive({ textAlign: 'center' }) }, 'editorOption']"
|
||||
: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()"
|
||||
:class="[{ 'is-active': editor.isActive({ textAlign: 'right' }) }, 'editorOption']"
|
||||
: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()"
|
||||
:class="[{ 'is-active': editor.isActive({ textAlign: 'justify' }) }, 'editorOption']"
|
||||
:classes="getClasses(editor.isActive({ textAlign: 'justify' }))"
|
||||
v-tooltip="{ text: 'Justify', shortcut: ['Ctrl', 'Shift', 'J'] }"
|
||||
/>
|
||||
</span>
|
||||
|
||||
@@ -166,14 +220,16 @@ function sendMessage() {
|
||||
<SvgLoader
|
||||
svg="indent-increase"
|
||||
@click="!$event.disabled ? editor.chain().focus().sinkListItem('listItem').run() : ''"
|
||||
:class="[{ disabled: !editor.can().sinkListItem('listItem') }, 'editorOption']"
|
||||
: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() : ''"
|
||||
:class="[{ disabled: !editor.can().liftListItem('listItem') }, 'editorOption']"
|
||||
:classes="getClasses(false, !editor.can().liftListItem('listItem'))"
|
||||
:isDisabled="!editor.can().liftListItem('listItem')"
|
||||
v-tooltip="{ text: 'Lift Item', shortcut: ['Shift', 'Tab'] }"
|
||||
/>
|
||||
</span>
|
||||
<!-- <SvgLoader
|
||||
@@ -182,7 +238,11 @@ function sendMessage() {
|
||||
:class="[{ 'is-active': editor.isActive('bold') }, 'editorOption']"
|
||||
/> -->
|
||||
</div>
|
||||
<bubble-menu class="bubble-menu" :tippy-options="{ duration: 100 }" :editor="editor">
|
||||
<bubble-menu
|
||||
:class="[showOptions ? 'hide' : '', 'bubble-menu']"
|
||||
:tippy-options="{ duration: 100 }"
|
||||
:editor="editor"
|
||||
>
|
||||
<SvgLoader
|
||||
svg="bold"
|
||||
@click="editor.chain().focus().toggleBold().run()"
|
||||
@@ -191,8 +251,8 @@ function sendMessage() {
|
||||
<SvgLoader
|
||||
svg="italic"
|
||||
@click="editor.chain().focus().toggleItalic().run()"
|
||||
:class="[{ 'is-active': editor.isActive('italic') }, 'editorOption']"
|
||||
v-tooltip="'Italic'"
|
||||
:class="[{ 'is-active,selectable': editor.isActive('italic') }, 'editorOption']"
|
||||
:classes="[{ 'is-active,selectable': editor.isActive('italic') }, 'selectable'].join()"
|
||||
/>
|
||||
<SvgLoader
|
||||
svg="strikethrough"
|
||||
@@ -235,10 +295,15 @@ function sendMessage() {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
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;
|
||||
@@ -246,6 +311,7 @@ function sendMessage() {
|
||||
border-radius: 10px;
|
||||
padding: 0 10px;
|
||||
overflow: auto;
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
.bubble-menu,
|
||||
@@ -269,20 +335,6 @@ function sendMessage() {
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.editorOption {
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover,
|
||||
&.is-active {
|
||||
background-color: var(--selected);
|
||||
}
|
||||
}
|
||||
|
||||
.disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -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,9 @@
|
||||
<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({
|
||||
@@ -12,53 +12,128 @@ 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 id="main">
|
||||
<div class="button" @click="setFlag('\\Seen')">
|
||||
{{ isSeenFc(props.msg?.flags) ? "Mark as not read" : "Mark as read" }}
|
||||
</div>
|
||||
<div>flag favorite</div>
|
||||
<div>reply</div>
|
||||
<div>delete from all</div>
|
||||
<div>delete from remote</div>
|
||||
<div>transfer</div>
|
||||
<div>see source</div>
|
||||
<div>{{ props.msg?.flags }}</div>
|
||||
<div class="icons">
|
||||
<SvgLoader svg="flag-line" />
|
||||
<SvgLoader svg="reply-line" />
|
||||
<SvgLoader svg="delete-bin-4-line" />
|
||||
<SvgLoader svg="delete-bin-6-line" />
|
||||
<SvgLoader svg="share-forward-line" />
|
||||
<SvgLoader svg="reply-all-line" />
|
||||
<SvgLoader svg="mail-check-line" />
|
||||
<SvgLoader svg="mail-unread-line" />
|
||||
<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>see source</div> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
<style lang="scss" scoped>
|
||||
#main {
|
||||
text-align: center;
|
||||
}
|
||||
@@ -67,17 +142,6 @@ const setFlag = (flag: string) => {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.button {
|
||||
border: solid 1px;
|
||||
border-radius: 6px;
|
||||
display: initial;
|
||||
padding: 1px 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background-color: var(--selected);
|
||||
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
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>
|
||||
@@ -4,21 +4,74 @@ 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`);
|
||||
console.log(props.isDisabled);
|
||||
const classes = () => props.classes?.split(",") ?? "";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<img :disabled="props.isDisabled" :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 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 {
|
||||
filter: var(--svg-primary-text);
|
||||
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;
|
||||
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
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 class="members" v-if="room?.roomType != RoomType.DM">
|
||||
</div>
|
||||
<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 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>
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
580
front/yarn.lock
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"
|
||||
|
||||
Reference in New Issue
Block a user