база
This commit is contained in:
parent
7ddd8485b2
commit
61ef56f308
23 changed files with 5316 additions and 56 deletions
11
.prettierrc
11
.prettierrc
|
@ -3,6 +3,13 @@
|
|||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 100,
|
||||
"plugins": ["prettier-plugin-svelte"],
|
||||
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
||||
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.svelte",
|
||||
"options": {
|
||||
"parser": "svelte"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
2499
package-lock.json
generated
2499
package-lock.json
generated
File diff suppressed because it is too large
Load diff
24
package.json
24
package.json
|
@ -3,6 +3,7 @@
|
|||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build:css": "postcss styles.css -o public/build/tailwind.css",
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
|
@ -13,12 +14,31 @@
|
|||
"@sveltejs/adapter-auto": "^3.0.0",
|
||||
"@sveltejs/kit": "^2.0.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
||||
"@types/node": "^20.11.16",
|
||||
"@types/websocket": "^1.0.10",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"flowbite": "^2.2.1",
|
||||
"flowbite-svelte": "^0.44.22",
|
||||
"postcss": "^8.4.35",
|
||||
"postcss-load-config": "^5.0.2",
|
||||
"prettier": "^3.1.1",
|
||||
"prettier-plugin-svelte": "^3.1.2",
|
||||
"prettier-plugin-tailwindcss": "^0.5.9",
|
||||
"stylus": "^0.55.0",
|
||||
"svelte": "^4.2.7",
|
||||
"svelte": "^4.2.10",
|
||||
"svelte-preprocess": "^5.1.3",
|
||||
"svelte-time": "^0.8.2",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"vite": "^5.0.3"
|
||||
},
|
||||
"type": "module"
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@sveltestrap/sveltestrap": "^6.2.4",
|
||||
"@themesberg/flowbite": "^1.3.0",
|
||||
"sass": "^1.70.0",
|
||||
"socket.io": "^4.7.4",
|
||||
"socket.io-client": "^4.7.4",
|
||||
"websocket": "^1.0.34",
|
||||
"ws": "^8.16.0"
|
||||
}
|
||||
}
|
||||
|
|
2244
pnpm-lock.yaml
generated
Normal file
2244
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load diff
13
postcss.config.cjs
Normal file
13
postcss.config.cjs
Normal file
|
@ -0,0 +1,13 @@
|
|||
const tailwindcss = require('tailwindcss');
|
||||
const autoprefixer = require('autoprefixer');
|
||||
|
||||
const config = {
|
||||
plugins: [
|
||||
//Some plugins, like tailwindcss/nesting, need to run before Tailwind,
|
||||
tailwindcss(),
|
||||
//But others, like autoprefixer, need to run after,
|
||||
autoprefixer
|
||||
]
|
||||
};
|
||||
|
||||
module.exports = config;
|
6
postcss.config.js
Normal file
6
postcss.config.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
3
src/app.css
Normal file
3
src/app.css
Normal file
|
@ -0,0 +1,3 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
|
@ -8,5 +8,12 @@
|
|||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
|
||||
<style>
|
||||
html,body{
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
</html>
|
||||
|
|
4
src/app.pcss
Normal file
4
src/app.pcss
Normal file
|
@ -0,0 +1,4 @@
|
|||
/* Write your global styles here, in PostCSS syntax */
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
73
src/lib/userFunction.js
Normal file
73
src/lib/userFunction.js
Normal file
|
@ -0,0 +1,73 @@
|
|||
export async function UserCheck(){
|
||||
|
||||
const response = await fetch('http://localhost:8000/api/users/me', {
|
||||
method: 'GET',
|
||||
credentials:'include'
|
||||
|
||||
})
|
||||
|
||||
if(!response.ok)
|
||||
{
|
||||
console.log(response.status)
|
||||
}
|
||||
|
||||
if(response.ok)
|
||||
{
|
||||
const data = await response.json();
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
export async function getLastMessages(){
|
||||
|
||||
const response = await fetch('http://localhost:8000/api/chat/get_some_messages/2?messages_loaded=0&messages_to_get=14',
|
||||
{
|
||||
method:'GET',
|
||||
credentials:'include'
|
||||
})
|
||||
|
||||
if(!response.ok)
|
||||
{
|
||||
console.log(response.status)
|
||||
}
|
||||
|
||||
if(response.ok)
|
||||
{
|
||||
let msgMassive = await response.json();
|
||||
let localTime
|
||||
for(let i = 0; i< msgMassive.length;i++){
|
||||
localTime = new Date(msgMassive[i].created_at)
|
||||
|
||||
msgMassive[i].created_at = localTime
|
||||
|
||||
}
|
||||
console.log(msgMassive)
|
||||
return msgMassive
|
||||
}
|
||||
}
|
||||
|
||||
export async function MessagePicToUrl(messagePic){
|
||||
|
||||
const DataForm = new FormData();
|
||||
DataForm.append('file', messagePic)
|
||||
|
||||
const respone = await fetch('http://localhost:8000/api/images/upload_image',
|
||||
{
|
||||
method:"POST",
|
||||
headers:{
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
body:DataForm
|
||||
})
|
||||
|
||||
if(!respone.ok)
|
||||
console.log("ошибка", respone.status)
|
||||
|
||||
|
||||
if(respone.ok){
|
||||
const data = await respone.json();
|
||||
console.log("картинка принята")
|
||||
return data.image_url;
|
||||
|
||||
}
|
||||
}
|
11
src/lib/websocket.js
Normal file
11
src/lib/websocket.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
export default function createWebSocket(url, onMessageCallback) {
|
||||
const socket = new WebSocket(url);
|
||||
|
||||
socket.addEventListener('message', (event) => {
|
||||
const jsonData = JSON.parse(event.data);
|
||||
onMessageCallback(jsonData);
|
||||
//console.log(jsonData)
|
||||
});
|
||||
|
||||
return socket;
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
@import '+page.svelte'
|
||||
body
|
||||
background-color: blue;
|
|
@ -1,10 +1,80 @@
|
|||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import Header from './Header.svelte';
|
||||
|
||||
let username = "";
|
||||
let password = "";
|
||||
|
||||
async function handleLogin() {
|
||||
|
||||
const response = await fetch('http://localhost:8000/users/login', {
|
||||
method: 'POST',
|
||||
credentials:'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify
|
||||
({
|
||||
email_or_username: username,
|
||||
password: password,
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
.catch(function (err)
|
||||
{
|
||||
console.log('Ошибка:', err);
|
||||
})
|
||||
|
||||
// В этом месте вы можете обработать успешный вход в систему, например, перенаправить пользователя на другую страницу
|
||||
console.log('лох залогинен!');
|
||||
|
||||
}
|
||||
|
||||
async function handleLogout() {
|
||||
try{
|
||||
const response = await fetch('http://localhost:8000/users/logout',
|
||||
{
|
||||
method:'POST',
|
||||
credentials:'include'
|
||||
|
||||
})
|
||||
if (response.ok)
|
||||
{
|
||||
console.log("ты вышел, лох");
|
||||
} else
|
||||
{
|
||||
console.error('Не вышел, лошара');
|
||||
}
|
||||
} catch (error)
|
||||
{
|
||||
console.error('Ошибка при выполнении выхода:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<Header />
|
||||
|
||||
<body>
|
||||
|
||||
<form on:submit|preventDefault={handleLogin}>
|
||||
<label for="username">Username:</label>
|
||||
<input type="text" id="username" bind:value={username} />
|
||||
|
||||
<label for="password">Password:</label>
|
||||
<input type="password" id="password" bind:value={password} />
|
||||
|
||||
<button type="submit">Login</button>
|
||||
</form>
|
||||
|
||||
<button on:click={handleLogout}>Выход</button>
|
||||
|
||||
<style>
|
||||
*{
|
||||
font-weight: 600;
|
||||
font-family:'Segoe UI', Tahoma, Geneva, Verdana, sans-serif !important;
|
||||
}
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
|
||||
|
||||
</style>
|
|
@ -1,12 +1,142 @@
|
|||
<header>
|
||||
<p>
|
||||
ну и что, что я вор?
|
||||
</p>
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import {UserCheck} from '$lib/userFunction';
|
||||
|
||||
let Nickname
|
||||
let userImage
|
||||
|
||||
onMount(async () => {
|
||||
const UserData = await UserCheck();
|
||||
Nickname = UserData.username;
|
||||
userImage = UserData.avatar_image;
|
||||
console.log(userImage)
|
||||
})
|
||||
|
||||
let isOpen = false;
|
||||
let items1 = ['Профиль', 'Настройки'];
|
||||
let items2 = ['Выйти'];
|
||||
</script>
|
||||
<header class="headerClass">
|
||||
|
||||
<div class="divsiteAvatar">
|
||||
<img class="siteAvatar" src="./BP-NEON.png" alt="лого">
|
||||
<p class="siteAvatarBP">Black Phoenix</p>
|
||||
</div>
|
||||
<div class="divUser">
|
||||
<button class="buttonUser" on:click={() => isOpen = !isOpen}>
|
||||
<h3 class="name">{Nickname}</h3>
|
||||
<img class="userAvatar" src="{userImage}" alt="аватарка">
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#if isOpen}
|
||||
<div class="dropdown">
|
||||
{#each items1 as item (item)}
|
||||
<div class="listItem">{item}</div>
|
||||
{/each}
|
||||
<div class="ListLine"></div>
|
||||
{#each items2 as item (item)}
|
||||
<div class="listItem">{item}</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</header>
|
||||
|
||||
<style>
|
||||
p{
|
||||
color: blueviolet;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
|
||||
.buttonUser{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 0px;
|
||||
border-bottom-left-radius: 10px;
|
||||
border-bottom-right-radius: 10px;
|
||||
display: flex;
|
||||
justify-content:space-around;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ListLine{
|
||||
border: 1px solid lightgray;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: 5vw;
|
||||
top: 10vh;
|
||||
width: 200px;
|
||||
|
||||
}
|
||||
|
||||
.listItem {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
div,h3{
|
||||
font-weight: 600;
|
||||
font-family:'Segoe UI', Tahoma, Geneva, Verdana, sans-serif !important;
|
||||
}
|
||||
.headerClass > * {
|
||||
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.headerClass{
|
||||
list-style: none;
|
||||
display: grid;
|
||||
grid-template-columns: 5vw 200px repeat(5, auto) 200px 5vw;
|
||||
grid-template-rows: 9vh;
|
||||
gap: 0;
|
||||
|
||||
|
||||
}
|
||||
.siteAvatar{
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
|
||||
}
|
||||
|
||||
.siteAvatarBP{
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 20px;
|
||||
|
||||
}
|
||||
.name{
|
||||
|
||||
}
|
||||
.divsiteAvatar{
|
||||
grid-column: 2;
|
||||
display: flex;
|
||||
justify-content:space-evenly;
|
||||
align-items: center;
|
||||
border: 1px solid black;
|
||||
}
|
||||
.divUser{
|
||||
grid-column: 8;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content:space-around;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
}
|
||||
.userAvatar{
|
||||
justify-content: right;
|
||||
width: 70px;
|
||||
border-radius: 50%;
|
||||
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
|
||||
|
||||
</style>
|
5
src/routes/Settings/+page.svelte
Normal file
5
src/routes/Settings/+page.svelte
Normal file
|
@ -0,0 +1,5 @@
|
|||
<script>
|
||||
import Header from '../Header.svelte';
|
||||
</script>
|
||||
|
||||
<Header />
|
220
src/routes/chatPage/+page.svelte
Normal file
220
src/routes/chatPage/+page.svelte
Normal file
|
@ -0,0 +1,220 @@
|
|||
<script>
|
||||
import Header from '../Header.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import Time from "svelte-time";
|
||||
import { dayjs } from "svelte-time";
|
||||
import 'dayjs/locale/ru';
|
||||
import {UserCheck,getLastMessages,MessagePicToUrl} from '$lib/userFunction.js';
|
||||
import createWebSocket from '$lib/websocket';
|
||||
|
||||
dayjs.locale("ru");
|
||||
console.clear();
|
||||
|
||||
let userId
|
||||
let socket
|
||||
let messages = [];
|
||||
let messageText = '';
|
||||
let messagePic;
|
||||
|
||||
|
||||
|
||||
onMount(async() => {
|
||||
const userData = await UserCheck();
|
||||
let userId = userData.id;
|
||||
let chatId = 2
|
||||
const websocketUrl = `ws://localhost:8000/api/chat/ws/${chatId}?user_id=${userId}`;
|
||||
|
||||
messages = await getLastMessages();
|
||||
|
||||
socket = createWebSocket(websocketUrl, (message) => {
|
||||
|
||||
messages = [message, ...messages]
|
||||
});
|
||||
});
|
||||
|
||||
function sendMessage() {
|
||||
|
||||
let messageUrl = MessagePicToUrl(messagePic);
|
||||
console.log(messageUrl)
|
||||
if (messageText.trim() !== '') {
|
||||
socket.send(JSON.stringify({'message':messageText,"image_url": messageUrl}));
|
||||
messageText = '';
|
||||
}}
|
||||
|
||||
let textarea;
|
||||
|
||||
const updateTextareaSize = () => {
|
||||
// Устанавливаем высоту textarea на основе количества строк
|
||||
textarea.style.height = 'auto'; // Сбрасываем высоту на auto перед измерением
|
||||
textarea.style.height = `${textarea.scrollHeight}px`;
|
||||
console.log(textarea.style.height)
|
||||
};
|
||||
|
||||
function handleKeyPress(event) {
|
||||
if (event.key === 'Enter' && !event.shiftKey) {
|
||||
// Если нажат Enter и не нажат Shift, отправляем сообщение
|
||||
sendMessage(event);
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<body class="backgroundPic">
|
||||
|
||||
<div class="DivHeader"><Header/></div>
|
||||
<div>жопа2</div>
|
||||
<div class="divChat">
|
||||
|
||||
<div class="chatBox">
|
||||
|
||||
{#each messages as message}
|
||||
<div class="MsgAll">
|
||||
<img class="MsgAva" src="{message.avatar_image}" alt="ава">
|
||||
<div class="divMessage">
|
||||
<div class="userFiled">
|
||||
<h3 class="MsgName">{message.username}</h3>
|
||||
<Time relative timestamp="{message.created_at}" ></Time>
|
||||
</div>
|
||||
<p class="MsgMsg">{message.message}</p>
|
||||
{#if message.image_url != null}
|
||||
<img class="MsgPic" src="{message.image_url}" alt="">
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
</div>
|
||||
|
||||
<form class="chatSend" on:submit={sendMessage}>
|
||||
<textarea
|
||||
placeholder="Введите сообщение" maxlength="2000" class="chatInput"
|
||||
rows="1" bind:this={textarea} on:input={updateTextareaSize}
|
||||
type="text" bind:value={messageText} on:keypress={handleKeyPress}
|
||||
></textarea>
|
||||
<input type="file" accept="image/*" bind:value={messagePic}>
|
||||
</form>
|
||||
</div>
|
||||
<div>жопа4</div>
|
||||
<div>жопа5</div>
|
||||
<div>жопа6</div>
|
||||
<div>жопа7</div>
|
||||
|
||||
</body>
|
||||
|
||||
<style>
|
||||
|
||||
.userFiled{
|
||||
display: flex;
|
||||
justify-content:flex-start;
|
||||
}
|
||||
.MsgName{
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.MsgPic{
|
||||
margin-left: 1vw;
|
||||
margin-bottom: 1vh;
|
||||
width: 80%;
|
||||
border: 1px solid rgba(255, 255, 255, 0.5);
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.9);
|
||||
}
|
||||
|
||||
.MsgAva{
|
||||
height: 6vh;
|
||||
min-height: 50px;
|
||||
width: auto;
|
||||
border: 2px solid rgba(255, 255, 255, 0.6);
|
||||
border-radius: 10px;
|
||||
margin: 0.25vw;
|
||||
}
|
||||
|
||||
.MsgAll{
|
||||
display: flex;
|
||||
flex: 90;
|
||||
}
|
||||
|
||||
.divMessage{
|
||||
background-color:rgba(255, 255, 255, 0.3);
|
||||
border: 1px solid rgba(255, 255, 255, 0.5);
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 0px 30px rgba(0, 0, 0,0.2);
|
||||
|
||||
margin: 3px;
|
||||
transform:translate(90deg);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.chatBox{
|
||||
|
||||
display: flex;
|
||||
flex: 30;
|
||||
flex-direction:column-reverse;
|
||||
align-items:flex-start;
|
||||
|
||||
overflow-y: auto;
|
||||
width: 100%;
|
||||
|
||||
|
||||
/* From https://css.glass */
|
||||
background-color: rgba(255, 255, 255, 0.20);
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 4px 30px rgb(0,0,0,0.5);
|
||||
backdrop-filter: blur(7px);
|
||||
border: 1px solid rgba(255,255,255,0.43)
|
||||
}
|
||||
|
||||
.DivHeader{
|
||||
grid-column: 3 span;
|
||||
border: 0!important;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.chatSend{
|
||||
display: flex;
|
||||
flex: 2;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.chatInput{
|
||||
width: 100%;
|
||||
resize: none;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.divChat{
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
body{
|
||||
display: grid;
|
||||
grid-template-columns: 25vw 50vw 25vw; /* 2 колонки с равным распределением ширины */
|
||||
grid-template-rows: 10vh 80vh 10vh;
|
||||
}
|
||||
|
||||
body > *{
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
*{
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
}
|
||||
|
||||
.backgroundPic{
|
||||
grid-column: span 3;
|
||||
grid-row: span 3;
|
||||
|
||||
background-color: #563E89;
|
||||
background-image: url(./BPytka.png);
|
||||
background-repeat: no-repeat;
|
||||
background-size:50vh;
|
||||
background-position-x:50%;
|
||||
background-position-y:50%;
|
||||
}
|
||||
|
||||
</style>
|
BIN
static/BP-NEON.png
Normal file
BIN
static/BP-NEON.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 266 KiB |
BIN
static/BPytka.png
Normal file
BIN
static/BPytka.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 940 KiB |
BIN
static/image/1.jpg
Normal file
BIN
static/image/1.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
BIN
static/logo.jpg
Normal file
BIN
static/logo.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 90 KiB |
|
@ -1,3 +1,4 @@
|
|||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
||||
import adapter from '@sveltejs/adapter-auto';
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
|
@ -7,8 +8,9 @@ const config = {
|
|||
// If your environment is not supported or you settled on a specific environment, switch out the adapter.
|
||||
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
|
||||
adapter: adapter()
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
preprocess: [vitePreprocess({})]
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
12
tailwind.config.cjs
Normal file
12
tailwind.config.cjs
Normal file
|
@ -0,0 +1,12 @@
|
|||
/** @type {import('tailwindcss').Config}*/
|
||||
const config = {
|
||||
content: ['./src/**/*.{html,js,svelte,ts}'],
|
||||
|
||||
theme: {
|
||||
extend: {}
|
||||
},
|
||||
|
||||
plugins: []
|
||||
};
|
||||
|
||||
module.exports = config;
|
9
tailwind.config.js
Normal file
9
tailwind.config.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: ['./src/**/*.{html,js,svelte,ts}'],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
|
Loading…
Add table
Reference in a new issue