load message in front
This commit is contained in:
parent
926dc60920
commit
4d4ef54bcb
@ -4,7 +4,7 @@ const { getMessages } = require("../db/api.js");
|
||||
async function messages(body, res) {
|
||||
const { roomId } = body;
|
||||
getMessages(roomId).then((messages) => {
|
||||
res.status(statusCode.OK).json(messages.data);
|
||||
res.status(statusCode.OK).json(messages);
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
res.status(statusCode.INTERNAL_SERVER_ERROR);
|
||||
|
@ -12,9 +12,11 @@ async function registerMailbox(userId, pwd, xoauth, xoauth2, host, port, tls) {
|
||||
|
||||
async function getMailboxes() {
|
||||
const query = `
|
||||
SELECT app_account.account_id AS id, address.email
|
||||
FROM app_account INNER JOIN address
|
||||
WHERE address.address_id = app_account.user_id
|
||||
SELECT
|
||||
app_account.account_id AS id,
|
||||
address.email
|
||||
FROM app_account INNER JOIN address
|
||||
WHERE address.address_id = app_account.user_id
|
||||
`;
|
||||
const values = [];
|
||||
return await execQueryAsync(query, values);
|
||||
@ -46,31 +48,68 @@ async function getRooms(mailboxId) {
|
||||
|
||||
async function getMessages(roomId) {
|
||||
// todo attachements name
|
||||
// todo html, text, textAsHtml
|
||||
// todo datetime
|
||||
const query = `
|
||||
SELECT
|
||||
address_field.address_id,
|
||||
bodypart.text,
|
||||
header_field.value
|
||||
FROM bodypart
|
||||
INNER JOIN header_field
|
||||
INNER JOIN address_field
|
||||
msg.message_id AS id,
|
||||
GROUP_CONCAT(fromT.address_id) AS fromA,
|
||||
GROUP_CONCAT(toT.address_id) AS toA,
|
||||
GROUP_CONCAT(ccT.address_id) AS ccA,
|
||||
subjectT.value AS subject,
|
||||
content.text AS content,
|
||||
message.idate AS date
|
||||
FROM app_room_message msg
|
||||
|
||||
LEFT JOIN (
|
||||
SELECT address_field.address_id, address_field.message_id
|
||||
FROM address_field
|
||||
INNER JOIN field_name
|
||||
WHERE
|
||||
(
|
||||
header_field.field_id = field_name.field_id OR
|
||||
address_field.field_id = field_name.field_id
|
||||
) AND
|
||||
(
|
||||
field_name.field_name = 'html' OR
|
||||
field_name.field_name = 'text' OR
|
||||
field_name.field_name = 'textAsHtml' OR
|
||||
field_name.field_name = 'to' OR
|
||||
field_name.field_name = 'cc' OR
|
||||
field_name.field_name = 'subject'
|
||||
)
|
||||
`;
|
||||
// todo roomID
|
||||
const values = [];
|
||||
field_name.field_id = address_field.field_id AND
|
||||
field_name.field_name = 'from'
|
||||
) fromT ON msg.message_id = fromT.message_id
|
||||
LEFT JOIN (
|
||||
SELECT address_field.address_id, address_field.message_id
|
||||
FROM address_field
|
||||
INNER JOIN field_name
|
||||
WHERE
|
||||
field_name.field_id = address_field.field_id AND
|
||||
field_name.field_name = 'to'
|
||||
) toT ON msg.message_id = toT.message_id
|
||||
LEFT JOIN (
|
||||
SELECT address_field.address_id, address_field.message_id
|
||||
FROM address_field
|
||||
INNER JOIN field_name
|
||||
WHERE
|
||||
field_name.field_id = address_field.field_id AND
|
||||
field_name.field_name = 'cc'
|
||||
) ccT ON msg.message_id = ccT.message_id
|
||||
|
||||
LEFT JOIN (
|
||||
SELECT header_field.message_id, header_field.value
|
||||
FROM header_field
|
||||
INNER JOIN field_name
|
||||
WHERE
|
||||
field_name.field_id = header_field.field_id AND
|
||||
field_name.field_name = 'subject'
|
||||
) subjectT ON msg.message_id = subjectT.message_id
|
||||
|
||||
LEFT JOIN (
|
||||
SELECT bodypart.text, header_field.message_id FROM bodypart
|
||||
INNER JOIN header_field
|
||||
INNER JOIN field_name
|
||||
WHERE
|
||||
field_name.field_id = header_field.field_id AND
|
||||
field_name.field_name = 'html' AND
|
||||
bodypart.bodypart_id = header_field.bodypart_id
|
||||
) content ON msg.message_id = content.message_id
|
||||
|
||||
INNER JOIN message ON message.message_id = msg.message_id
|
||||
WHERE msg.room_id = ?
|
||||
GROUP BY msg.message_id;
|
||||
`;
|
||||
const values = [roomId];
|
||||
return await execQueryAsync(query, values);
|
||||
}
|
||||
|
||||
|
@ -14,10 +14,10 @@ async function registerMessage(timestamp, rfc822size, messageId) {
|
||||
function registerMailbox_message(mailboxId, uid, messageId, modseq, seen, deleted) {
|
||||
const query = `
|
||||
INSERT IGNORE INTO mailbox_message
|
||||
(mailbox_id, uid, message_id, modseq, seen, deleted) VALUES (1, 19, 10, '12450', 0, 0)
|
||||
(mailbox_id, uid, message_id, modseq, seen, deleted) VALUES (?, ?, ?, ?, ?, ?)
|
||||
`;
|
||||
// todo
|
||||
const values = [mailboxId, uid, messageId, modseq, seen, deleted];
|
||||
console.log(values)
|
||||
execQuery(query, values);
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@ async function createRoom(roomName, ownerId, messageId) {
|
||||
}
|
||||
|
||||
async function registerMessageInRoom(messageId, roomId, isSeen) {
|
||||
const query = `INSERT INTO app_space_message (message_id, room_id) VALUES (?, ?)`;
|
||||
const query = `INSERT IGNORE INTO app_room_message (message_id, room_id) VALUES (?, ?)`;
|
||||
const values = [messageId, roomId];
|
||||
await execQueryAsync(query, values);
|
||||
|
||||
@ -68,8 +68,8 @@ async function isRoomGroup(roomId) {
|
||||
});
|
||||
}
|
||||
|
||||
async function findSpacesFromMessage(messageId) {
|
||||
const query = `SELECT room_id, thread_id FROM app_space_message WHERE message_id = '${messageId}'`;
|
||||
async function findRoomsFromMessage(messageId) {
|
||||
const query = `SELECT room_id FROM app_room_message WHERE message_id = '${messageId}'`;
|
||||
return await execQueryAsync(query);
|
||||
}
|
||||
|
||||
@ -85,5 +85,5 @@ module.exports = {
|
||||
createThread,
|
||||
registerMessageInThread,
|
||||
isRoomGroup,
|
||||
findSpacesFromMessage,
|
||||
findRoomsFromMessage,
|
||||
};
|
||||
|
@ -131,25 +131,24 @@ CREATE TABLE app_room (
|
||||
|
||||
-- 12
|
||||
CREATE TABLE app_thread (
|
||||
thread_id INT AUTO_INCREMENT,
|
||||
room_id INT NOT NULL,
|
||||
thread_name VARCHAR(255) NOT NULL,
|
||||
notSeen INT NOT NULL DEFAULT 0,
|
||||
lastUpdate TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP(),
|
||||
parent_room_id INT,
|
||||
root_room_id INT,
|
||||
isDm BOOLEAN NOT NULL DEFAULT false,
|
||||
PRIMARY KEY (thread_id),
|
||||
FOREIGN KEY (room_id) REFERENCES app_room(room_id) ON DELETE CASCADE
|
||||
PRIMARY KEY (room_id),
|
||||
UNIQUE KEY (room_id, parent_room_id, root_room_id),
|
||||
FOREIGN KEY (room_id) REFERENCES app_room(room_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (parent_room_id) REFERENCES app_room(room_id) ON DELETE SET NULL,
|
||||
FOREIGN KEY (root_room_id) REFERENCES app_room(room_id) ON DELETE SET NULL
|
||||
);
|
||||
|
||||
-- 13
|
||||
CREATE TABLE app_space_message (
|
||||
CREATE TABLE app_room_message (
|
||||
message_id INT NOT NULL,
|
||||
room_id INT,
|
||||
thread_id INT,
|
||||
UNIQUE KEY (message_id, room_id, thread_id),
|
||||
UNIQUE KEY (message_id, room_id),
|
||||
FOREIGN KEY (message_id) REFERENCES message(message_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (room_id) REFERENCES app_room(room_id) ON DELETE SET NULL,
|
||||
FOREIGN KEY (thread_id) REFERENCES app_thread(thread_id) ON DELETE SET NULL
|
||||
FOREIGN KEY (room_id) REFERENCES app_room(room_id) ON DELETE SET NULL
|
||||
);
|
||||
|
||||
-- 14
|
||||
@ -159,11 +158,3 @@ CREATE TABLE app_room_member (
|
||||
FOREIGN KEY (room_id) REFERENCES app_room(room_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (member_id) REFERENCES address(address_id)
|
||||
);
|
||||
|
||||
-- 14
|
||||
CREATE TABLE app_thread_member (
|
||||
thread_id INT NOT NULL,
|
||||
member_id INT NOT NULL,
|
||||
FOREIGN KEY (thread_id) REFERENCES app_thread(thread_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (member_id) REFERENCES address(address_id)
|
||||
);
|
||||
|
@ -28,6 +28,7 @@ if (shouldReset) {
|
||||
execQuery("SET FOREIGN_KEY_CHECKS=0");
|
||||
results.map((table) => {
|
||||
execQuery("DELETE FROM " + table.table_name);
|
||||
// execQuery("DROP TABLE " + table.table_name);
|
||||
});
|
||||
});
|
||||
return;
|
||||
|
@ -19,6 +19,7 @@ const validate_mailbox = ajv.compile(schema_mailbox);
|
||||
*/
|
||||
router.get("/mailboxes", (req, res) => {
|
||||
getMailboxes().then((data) => {
|
||||
data[0].id = 1; // todo debug
|
||||
res.status(statusCodes.OK).json(data)
|
||||
});
|
||||
});
|
||||
@ -49,9 +50,10 @@ router.get("/:roomId/messages", async (req, res) => {
|
||||
* Register a new mailbox inside the app
|
||||
*/
|
||||
router.post("/mailbox", async (req, res) => {
|
||||
console.log(req.body)
|
||||
const valid = validate_mailbox(req.body);
|
||||
if (!valid) {
|
||||
res.status(statusCodes.NOT_ACCEPTABLE).send(validate_mailbox.errors)
|
||||
res.status(statusCodes.NOT_ACCEPTABLE).send({ error: validate_mailbox.errors });
|
||||
} else {
|
||||
await addMailbox(req.body, res);
|
||||
}
|
||||
|
13
front/src/store/models/Room.js
Normal file
13
front/src/store/models/Room.js
Normal file
@ -0,0 +1,13 @@
|
||||
export default class Room {
|
||||
constructor(id, user, userId, roomName, mailboxId) {
|
||||
this.id = id;
|
||||
this.user = user;
|
||||
this.userId = userId;
|
||||
this.roomName = roomName;
|
||||
this.mailboxId = mailboxId;
|
||||
|
||||
this.messages = [];
|
||||
this.messagesFetched = false;
|
||||
this.threads = [];
|
||||
}
|
||||
}
|
@ -1,42 +1,19 @@
|
||||
import API from "@/services/imapAPI";
|
||||
import { createStore } from "vuex";
|
||||
import Room from "./models/Room";
|
||||
|
||||
const store = createStore({
|
||||
state() {
|
||||
return {
|
||||
rooms: [
|
||||
{
|
||||
id: 0,
|
||||
user: "clemnce",
|
||||
userId: 0,
|
||||
roomName: "Lorem magna minim cillum labore ex eiusmod proident excepteur sint irure ipsum.",
|
||||
mailboxId: 1,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
user: "juliette",
|
||||
roomName: "Lorem magna minim cillum labore ex eiusmod proident excepteur sint irure ipsum.",
|
||||
mailboxId: 1,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
user: "jean",
|
||||
roomName: "Lorem magna minim cillum labore ex eiusmod proident excepteur sint irure ipsum.",
|
||||
mailboxId: 2,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
user: "luc",
|
||||
roomName: "Lorem magna minim cillum labore ex eiusmod proident excepteur sint irure ipsum.",
|
||||
mailboxId: 2,
|
||||
},
|
||||
],
|
||||
messages: [
|
||||
|
||||
new Room(1, "clemence", 0, "Est ex adipisicing non ipsum voluptate duis enim adipisicing labore.", 1),
|
||||
new Room(2, "laurance", 0, "Est ex adipisicing non ipsum voluptate duis enim adipisicing labore.", 2),
|
||||
new Room(3, "mathilde", 0, "Est ex adipisicing non ipsum voluptate duis enim adipisicing labore.", 1),
|
||||
],
|
||||
messages: [],
|
||||
mailboxes: [],
|
||||
activeMailbox: 0,
|
||||
activeRoom: 0
|
||||
activeRoom: 0,
|
||||
};
|
||||
},
|
||||
mutations: {
|
||||
@ -48,16 +25,18 @@ const store = createStore({
|
||||
const mailbox = state.mailboxes.find((mailbox) => mailbox.id == payload);
|
||||
// todo fetched mailbox all
|
||||
if (mailbox?.fetched == false) {
|
||||
API.getRooms(payload).then((res) => {
|
||||
// todo add if not exist
|
||||
mailbox.fetched = true;
|
||||
res.data.forEach((room) => {
|
||||
room.fetched = false;
|
||||
state.rooms.push(room);
|
||||
API.getRooms(payload)
|
||||
.then((res) => {
|
||||
// todo add if not exist
|
||||
console.log(res.data)
|
||||
mailbox.fetched = true;
|
||||
res.data.forEach((room) => {
|
||||
state.rooms.push(new Room(room.id, room.user, room.userId, room.roomName, room.mailboxId));
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
});
|
||||
}
|
||||
},
|
||||
setActiveRoom(state, payload) {
|
||||
@ -66,24 +45,35 @@ const store = createStore({
|
||||
// fetch messages for this room if not already fetched
|
||||
const room = state.rooms.find((room) => room.id == payload);
|
||||
if (!room || room?.fetched == false) {
|
||||
console.log("add messages")
|
||||
API.getMessages(payload).then((res) => {
|
||||
// todo add if not exist
|
||||
room.fetched = true;
|
||||
res.data.forEach((msg) => {
|
||||
state.messages.push(msg);
|
||||
console.log("add messages");
|
||||
API.getMessages(payload)
|
||||
.then((res) => {
|
||||
// todo add if not exist
|
||||
room.fetched = true;
|
||||
res.data.forEach((msg) => {
|
||||
state.messages.push(msg);
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
});
|
||||
}
|
||||
},
|
||||
addMailboxes(state, payload) {
|
||||
console.log(payload)
|
||||
payload.forEach((mailbox) => {
|
||||
mailbox.fetched = false;
|
||||
state.mailboxes.push(mailbox);
|
||||
});
|
||||
},
|
||||
addMessages(state, payload) {
|
||||
const room = state.rooms.find((room) => room.id == payload.roomId);
|
||||
payload.messages.forEach((message) => {
|
||||
room.messages.push(message);
|
||||
});
|
||||
room.messagesFetched = true;
|
||||
console.log(room.messages)
|
||||
},
|
||||
},
|
||||
getters: {
|
||||
rooms: (state) => () => {
|
||||
@ -91,16 +81,16 @@ const store = createStore({
|
||||
return state.rooms.filter((room) => room.mailboxId == state.activeMailbox);
|
||||
},
|
||||
messages: (state) => (roomId) => {
|
||||
const room = state.rooms.find((room) => room.id === roomId);
|
||||
if (room?.fetched === false) {
|
||||
console.log("ok")
|
||||
const room = state.rooms.find((room) => room.id == roomId);
|
||||
if (!room.messagesFetched) {
|
||||
console.log("fetched Messages")
|
||||
store.dispatch("fetchMessages", { roomId: room.id });
|
||||
}
|
||||
return [1, 2];
|
||||
}
|
||||
return room.messages;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
async fetchMailboxes(context) {
|
||||
console.log("add mailboxes");
|
||||
fetchMailboxes: async (context) => {
|
||||
API.getMailboxes()
|
||||
.then((res) => {
|
||||
context.commit("addMailboxes", res.data);
|
||||
@ -109,8 +99,15 @@ const store = createStore({
|
||||
console.log(err);
|
||||
});
|
||||
},
|
||||
async fetchMessages(context) {
|
||||
console.log(context)
|
||||
fetchMessages: async (context, data) => {
|
||||
API.getMessages(data.roomId)
|
||||
.then((res) => {
|
||||
console.log(res.data)
|
||||
context.commit("addMessages", { messages: res.data, roomId: data.roomId });
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -19,7 +19,7 @@ export default {
|
||||
|
||||
<style scoped>
|
||||
#main {
|
||||
background-color: blue;
|
||||
border-bottom: 1px solid #505050;
|
||||
width: 100%;
|
||||
height: 51px;
|
||||
}
|
||||
|
@ -1,30 +1,72 @@
|
||||
<script setup>
|
||||
const data = {
|
||||
html: "<div dir=\"ltr\">new content<br><div><br><div class=\"gmail_quote\"><div dir=\"ltr\" class=\"gmail_attr\">---------- Forwarded message ---------<br>De : <b class=\"gmail_sendername\" dir=\"auto\">Ulysse Carlier</b> <span dir=\"auto\"><<a href=\"mailto:carlierulysse@gmail.com\">carlierulysse@gmail.com</a>></span><br>Date: ven. 10 mars 2023 à 14:52<br>Subject: message to transfer<br>To: Hugueprime M <<a href=\"mailto:hugueprime@gmail.com\">hugueprime@gmail.com</a>><br></div><br><br><div dir=\"ltr\">content to transfer<br></div>\n</div></div></div>\n",
|
||||
text: "new content\n\n---------- Forwarded message ---------\nDe : Ulysse Carlier <carlierulysse@gmail.com>\nDate: ven. 10 mars 2023 à 14:52\nSubject: message to transfer\nTo: Hugueprime M <hugueprime@gmail.com>\n\n\ncontent to transfer\n",
|
||||
textAsHtml: "<p>new content</p><p>---------- Forwarded message ---------<br/>De : Ulysse Carlier <<a href=\"mailto:carlierulysse@gmail.com\">carlierulysse@gmail.com</a>><br/>Date: ven. 10 mars 2023 à 14:52<br/>Subject: message to transfer<br/>To: Hugueprime M <<a href=\"mailto:hugueprime@gmail.com\">hugueprime@gmail.com</a>></p><p>content to transfer</p>",
|
||||
subject: "Fwd: message to transfer",
|
||||
references: "<CAAGJdR0i=4w1i2Nb9=zx5bCPqkR+2eUCs=_qbXTSzugZu4EXFQ@mail.gmail.com>",
|
||||
date: "2023-03-10T13:52:21.000Z",
|
||||
}
|
||||
import { defineProps } from "vue";
|
||||
|
||||
const props = defineProps({ data: Object });
|
||||
console.log(props.data.date);
|
||||
const date = new Date(props.data.date);
|
||||
console.log(date);
|
||||
</script>
|
||||
<!-- to if to is more than me
|
||||
<!-- to if to is more than me
|
||||
cc -->
|
||||
<!-- object (channel only)
|
||||
<!-- object (channel only)
|
||||
content
|
||||
attachments -->
|
||||
<template>
|
||||
<div class="message">
|
||||
<!-- <div v-html="data.html"></div> -->
|
||||
<!-- <div v-html="data.text"></div> -->
|
||||
<div></div>
|
||||
<div v-html="data.textAsHtml"></div>
|
||||
<div id="context">
|
||||
<div class="left" id="profile">Carrefour@emailing .carrefor.fr "carrefour"</div>
|
||||
<div class="middle">{{ props.data.subject }}</div>
|
||||
<div class="right" id="date">
|
||||
{{
|
||||
date.toLocaleString("en-GB", {
|
||||
weekday: "short",
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
timezone: "UTC+1",
|
||||
})
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<div id="content">
|
||||
<div v-html="props.data.content"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.message {
|
||||
width: 100%;
|
||||
width: auto;
|
||||
border: white 1px solid;
|
||||
padding: 10px;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
#context {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#content {
|
||||
overflow: auto;
|
||||
max-height: 300px;
|
||||
width: 750px; /* template width being 600px to 640px up to 750px (experiment and test) */
|
||||
}
|
||||
|
||||
.left,
|
||||
.right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.middle {
|
||||
margin: 0 10px;
|
||||
flex: 1;
|
||||
align-self: center;
|
||||
display: contents;
|
||||
}
|
||||
</style>
|
||||
|
@ -14,7 +14,8 @@ onBeforeMount(async () => {
|
||||
console.log(store.state.rooms.find((room) => room.id === id)?.fetched);
|
||||
let room = store.state.rooms.find((room) => room.id === id);
|
||||
if (!room || room?.fetched === false) {
|
||||
await store.dispatch("fetchMessages");
|
||||
// todo
|
||||
// await store.dispatch("fetchMessages", );
|
||||
}
|
||||
store.commit("setActiveRoom", id);
|
||||
});
|
||||
@ -28,20 +29,49 @@ onBeforeRouteUpdate(async (to, from) => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div id="main">
|
||||
<Header></Header>
|
||||
<div>
|
||||
<Message v-for="(message, index) in store.getters.messages(id)" :key="index" :data="message" />
|
||||
<b>{{ id }}</b>
|
||||
{{ messages.length }}
|
||||
<div id="RoomViewBody">
|
||||
<div class="content">
|
||||
<Message v-for="(message, index) in store.getters.messages(id)" :key="index" :data="message" />
|
||||
<b>{{ id }}</b>
|
||||
{{ messages.length }}
|
||||
</div>
|
||||
<div id="composer">COMPOSER</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
div {
|
||||
#main {
|
||||
background-color: #1d1d23;
|
||||
color: white;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#RoomViewBody {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#composer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
padding-top: 10px;
|
||||
|
||||
/* todo composer */
|
||||
height: 35px;
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
margin-bottom: 100px;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
Loading…
Reference in New Issue
Block a user