Add tailwind and Monaco
This commit is contained in:
+9
-3
@@ -12,13 +12,18 @@
|
||||
"@sveltejs/kit": "next",
|
||||
"@typescript-eslint/eslint-plugin": "^4.19.0",
|
||||
"@typescript-eslint/parser": "^4.19.0",
|
||||
"autoprefixer": "^10.2.5",
|
||||
"cssnano": "^5.0.1",
|
||||
"eslint": "^7.22.0",
|
||||
"eslint-config-prettier": "^8.1.0",
|
||||
"eslint-plugin-svelte3": "^3.2.0",
|
||||
"postcss": "^8.2.10",
|
||||
"postcss-load-config": "^3.0.1",
|
||||
"prettier": "~2.2.1",
|
||||
"prettier-plugin-svelte": "^2.2.0",
|
||||
"svelte": "^3.34.0",
|
||||
"svelte-preprocess": "^4.0.0",
|
||||
"svelte-preprocess": "^4.7.1",
|
||||
"tailwindcss": "^2.1.1",
|
||||
"tslib": "^2.0.0",
|
||||
"typescript": "^4.0.0"
|
||||
},
|
||||
@@ -26,6 +31,7 @@
|
||||
"dependencies": {
|
||||
"@fontsource/fira-mono": "^4.2.2",
|
||||
"@lukeed/uuid": "^2.0.0",
|
||||
"cookie": "^0.4.1"
|
||||
"cookie": "^0.4.1",
|
||||
"monaco-editor": "^0.23.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
const tailwindcss = require("tailwindcss");
|
||||
const autoprefixer = require("autoprefixer");
|
||||
const cssnano = require("cssnano");
|
||||
|
||||
const mode = process.env.NODE_ENV;
|
||||
const dev = mode === "development";
|
||||
|
||||
module.exports = {
|
||||
plugins: [
|
||||
// Some plugins, like postcss-nested, need to run before Tailwind
|
||||
|
||||
tailwindcss,
|
||||
|
||||
// But others, like autoprefixer, need to run after
|
||||
|
||||
autoprefixer,
|
||||
|
||||
!dev && cssnano({
|
||||
preset: "default",
|
||||
}),
|
||||
],
|
||||
};
|
||||
@@ -1,4 +1,7 @@
|
||||
@import '@fontsource/fira-mono';
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
:root {
|
||||
font-family: Arial, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
|
||||
@@ -17,7 +17,6 @@
|
||||
<ul>
|
||||
<li class:active={$page.path === '/'}><a sveltekit:prefetch href="/">Home</a></li>
|
||||
<li class:active={$page.path === '/about'}><a sveltekit:prefetch href="/about">About</a></li>
|
||||
<li class:active={$page.path === '/todos'}><a sveltekit:prefetch href="/todos">Todos</a></li>
|
||||
</ul>
|
||||
<svg viewBox="0 0 2 3" aria-hidden="true">
|
||||
<path d="M0,0 L0,3 C0.5,3 0.5,3 1,2 L2,0 Z" />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import Header from '$lib/Header/index.svelte';
|
||||
import '../app.css';
|
||||
import '../app.postcss';
|
||||
</script>
|
||||
|
||||
<Header />
|
||||
|
||||
+67
-24
@@ -1,18 +1,4 @@
|
||||
<script context="module">
|
||||
import { browser, dev } from '$app/env';
|
||||
|
||||
// we don't need any JS on this page, though we'll load
|
||||
// it in dev so that we get hot module replacement...
|
||||
export const hydrate = dev;
|
||||
|
||||
// ...but if the client-side router is already loaded
|
||||
// (i.e. we came here from elsewhere in the app), use it
|
||||
export const router = browser;
|
||||
|
||||
// since there's no dynamic data here, we can prerender
|
||||
// it so that it gets served as a static asset in prod
|
||||
export const prerender = true;
|
||||
</script>
|
||||
<!-- <script context="module" ✂prettier:content✂="CglpbXBvcnQgeyBicm93c2VyLCBkZXYgfSBmcm9tICckYXBwL2Vudic7CgoJLy8gd2UgZG9uJ3QgbmVlZCBhbnkgSlMgb24gdGhpcyBwYWdlLCB0aG91Z2ggd2UnbGwgbG9hZAoJLy8gaXQgaW4gZGV2IHNvIHRoYXQgd2UgZ2V0IGhvdCBtb2R1bGUgcmVwbGFjZW1lbnQuLi4KCWV4cG9ydCBjb25zdCBoeWRyYXRlID0gZGV2OwoKCS8vIC4uLmJ1dCBpZiB0aGUgY2xpZW50LXNpZGUgcm91dGVyIGlzIGFscmVhZHkgbG9hZGVkCgkvLyAoaS5lLiB3ZSBjYW1lIGhlcmUgZnJvbSBlbHNld2hlcmUgaW4gdGhlIGFwcCksIHVzZSBpdAoJZXhwb3J0IGNvbnN0IHJvdXRlciA9IGJyb3dzZXI7CgoJLy8gc2luY2UgdGhlcmUncyBubyBkeW5hbWljIGRhdGEgaGVyZSwgd2UgY2FuIHByZXJlbmRlcgoJLy8gaXQgc28gdGhhdCBpdCBnZXRzIHNlcnZlZCBhcyBhIHN0YXRpYyBhc3NldCBpbiBwcm9kCglleHBvcnQgY29uc3QgcHJlcmVuZGVyID0gdHJ1ZTsK">{}</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>About</title>
|
||||
@@ -26,10 +12,9 @@
|
||||
following into your command line and following the prompts:
|
||||
</p>
|
||||
|
||||
<!-- TODO lose the @next! -->
|
||||
<pre>npm init svelte@next</pre>
|
||||
|
||||
<p>
|
||||
<p class="text-red-600 ">
|
||||
The page you're looking at is purely static HTML, with no client-side interactivity needed.
|
||||
Because of that, we don't need to load any JavaScript. Try viewing the page's source, or opening
|
||||
the devtools network panel and reloading.
|
||||
@@ -41,10 +26,68 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.content {
|
||||
width: 100%;
|
||||
max-width: var(--column-width);
|
||||
margin: var(--column-margin-top) auto 0 auto;
|
||||
}
|
||||
</style>
|
||||
<style style lang="postcss" ✂prettier:content✂="CgkuY29udGVudCB7CgkJd2lkdGg6IDEwMCU7CgkJbWF4LXdpZHRoOiB2YXIoLS1jb2x1bW4td2lkdGgpOwoJCW1hcmdpbjogdmFyKC0tY29sdW1uLW1hcmdpbi10b3ApIGF1dG8gMCBhdXRvOwoJfQo="></style> -->
|
||||
<script lang="ts">
|
||||
import { browser, dev } from '$app/env';
|
||||
|
||||
import type monaco from 'monaco-editor';
|
||||
import { onMount } from 'svelte';
|
||||
import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
|
||||
import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
|
||||
import cssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker';
|
||||
import htmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker';
|
||||
import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker';
|
||||
|
||||
// we don't need any JS on this page, though we'll load
|
||||
// it in dev so that we get hot module replacement...
|
||||
export const hydrate = dev;
|
||||
|
||||
// ...but if the client-side router is already loaded
|
||||
// (i.e. we came here from elsewhere in the app), use it
|
||||
export const router = browser;
|
||||
|
||||
// since there's no dynamic data here, we can prerender
|
||||
// it so that it gets served as a static asset in prod
|
||||
export const prerender = true;
|
||||
|
||||
let divEl: HTMLDivElement = null;
|
||||
let editor: monaco.editor.IStandaloneCodeEditor;
|
||||
let Monaco;
|
||||
|
||||
onMount(async () => {
|
||||
// @ts-ignore
|
||||
self.MonacoEnvironment = {
|
||||
getWorker: function (_moduleId: any, label: string) {
|
||||
if (label === 'json') {
|
||||
return new jsonWorker();
|
||||
}
|
||||
if (label === 'css' || label === 'scss' || label === 'less') {
|
||||
return new cssWorker();
|
||||
}
|
||||
if (label === 'html' || label === 'handlebars' || label === 'razor') {
|
||||
return new htmlWorker();
|
||||
}
|
||||
if (label === 'typescript' || label === 'javascript') {
|
||||
return new tsWorker();
|
||||
}
|
||||
return new editorWorker();
|
||||
}
|
||||
};
|
||||
|
||||
Monaco = await import('monaco-editor');
|
||||
editor = Monaco.editor.create(divEl, {
|
||||
value: ['function x() {', '\tconsole.log("Hello world!");', '}'].join('\n'),
|
||||
language: 'javascript'
|
||||
});
|
||||
|
||||
return () => {
|
||||
editor.dispose();
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>About</title>
|
||||
</svelte:head>
|
||||
|
||||
<div bind:this={divEl} class="h-screen" />
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<Counter />
|
||||
</section>
|
||||
|
||||
<style>
|
||||
<style style lang="postcss">
|
||||
section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import { api } from './_api';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import type { Locals } from '$lib/types';
|
||||
|
||||
// PATCH /todos/:uid.json
|
||||
export const patch: RequestHandler<Locals, FormData> = async (request) => {
|
||||
return api(request, `todos/${request.locals.userid}/${request.params.uid}`, {
|
||||
text: request.body.get('text'),
|
||||
done: request.body.has('done') ? !!request.body.get('done') : undefined
|
||||
});
|
||||
};
|
||||
|
||||
// DELETE /todos/:uid.json
|
||||
export const del: RequestHandler<Locals> = async (request) => {
|
||||
return api(request, `todos/${request.locals.userid}/${request.params.uid}`);
|
||||
};
|
||||
@@ -1,48 +0,0 @@
|
||||
import type { Request } from '@sveltejs/kit';
|
||||
import type { Locals } from '$lib/types';
|
||||
|
||||
/*
|
||||
This module is used by the /todos.json and /todos/[uid].json
|
||||
endpoints to make calls to api.svelte.dev, which stores todos
|
||||
for each user. The leading underscore indicates that this is
|
||||
a private module, _not_ an endpoint — visiting /todos/_api
|
||||
will net you a 404 response.
|
||||
|
||||
(The data on the todo app will expire periodically; no
|
||||
guarantees are made. Don't use it to organise your life.)
|
||||
*/
|
||||
|
||||
const base = 'https://api.svelte.dev';
|
||||
|
||||
export async function api(request: Request<Locals>, resource: string, data?: {}) {
|
||||
// user must have a cookie set
|
||||
if (!request.locals.userid) {
|
||||
return { status: 401 };
|
||||
}
|
||||
|
||||
const res = await fetch(`${base}/${resource}`, {
|
||||
method: request.method,
|
||||
headers: {
|
||||
'content-type': 'application/json'
|
||||
},
|
||||
body: data && JSON.stringify(data)
|
||||
});
|
||||
|
||||
// if the request came from a <form> submission, the browser's default
|
||||
// behaviour is to show the URL corresponding to the form's "action"
|
||||
// attribute. in those cases, we want to redirect them back to the
|
||||
// /todos page, rather than showing the response
|
||||
if (res.ok && request.method !== 'GET' && request.headers.accept !== 'application/json') {
|
||||
return {
|
||||
status: 303,
|
||||
headers: {
|
||||
location: '/todos'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: res.status,
|
||||
body: await res.json()
|
||||
};
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import { api } from './_api';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import type { Locals } from '$lib/types';
|
||||
|
||||
// GET /todos.json
|
||||
export const get: RequestHandler<Locals> = async (request) => {
|
||||
// request.locals.userid comes from src/hooks.js
|
||||
const response = await api(request, `todos/${request.locals.userid}`);
|
||||
|
||||
if (response.status === 404) {
|
||||
// user hasn't created a todo list.
|
||||
// start with an empty array
|
||||
return { body: [] };
|
||||
}
|
||||
|
||||
return response;
|
||||
};
|
||||
|
||||
// POST /todos.json
|
||||
export const post: RequestHandler<Locals, FormData> = async (request) => {
|
||||
const response = await api(request, `todos/${request.locals.userid}`, {
|
||||
// because index.svelte posts a FormData object,
|
||||
// request.body is _also_ a (readonly) FormData
|
||||
// object, which allows us to get form data
|
||||
// with the `body.get(key)` method
|
||||
text: request.body.get('text')
|
||||
});
|
||||
|
||||
return response;
|
||||
};
|
||||
@@ -1,227 +0,0 @@
|
||||
<script context="module" lang="ts">
|
||||
import { enhance } from '$lib/form';
|
||||
import type { Load } from '@sveltejs/kit';
|
||||
|
||||
// see https://kit.svelte.dev/docs#loading
|
||||
export const load: Load = async ({ fetch }) => {
|
||||
const res = await fetch('/todos.json');
|
||||
|
||||
if (res.ok) {
|
||||
const todos = await res.json();
|
||||
|
||||
return {
|
||||
props: { todos }
|
||||
};
|
||||
}
|
||||
|
||||
const { message } = await res.json();
|
||||
|
||||
return {
|
||||
error: new Error(message)
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { scale } from 'svelte/transition';
|
||||
import { flip } from 'svelte/animate';
|
||||
|
||||
type Todo = {
|
||||
uid: string;
|
||||
created_at: Date;
|
||||
text: string;
|
||||
done: boolean;
|
||||
};
|
||||
|
||||
export let todos: Todo[];
|
||||
|
||||
async function patch(res: Response) {
|
||||
const todo = await res.json();
|
||||
|
||||
todos = todos.map((t) => {
|
||||
if (t.uid === todo.uid) return todo;
|
||||
return t;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Todos</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="todos">
|
||||
<h1>Todos</h1>
|
||||
|
||||
<form
|
||||
class="new"
|
||||
action="/todos.json"
|
||||
method="post"
|
||||
use:enhance={{
|
||||
result: async (res, form) => {
|
||||
const created = await res.json();
|
||||
todos = [...todos, created];
|
||||
|
||||
form.reset();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<input name="text" aria-label="Add todo" placeholder="+ tap to add a todo" />
|
||||
</form>
|
||||
|
||||
{#each todos as todo (todo.uid)}
|
||||
<div
|
||||
class="todo"
|
||||
class:done={todo.done}
|
||||
transition:scale|local={{ start: 0.7 }}
|
||||
animate:flip={{ duration: 200 }}
|
||||
>
|
||||
<form
|
||||
action="/todos/{todo.uid}.json?_method=patch"
|
||||
method="post"
|
||||
use:enhance={{
|
||||
pending: (data) => {
|
||||
todo.done = !!data.get('done');
|
||||
},
|
||||
result: patch
|
||||
}}
|
||||
>
|
||||
<input type="hidden" name="done" value={todo.done ? '' : 'true'} />
|
||||
<button class="toggle" aria-label="Mark todo as {todo.done ? 'not done' : 'done'}" />
|
||||
</form>
|
||||
|
||||
<form
|
||||
class="text"
|
||||
action="/todos/{todo.uid}.json?_method=patch"
|
||||
method="post"
|
||||
use:enhance={{
|
||||
result: patch
|
||||
}}
|
||||
>
|
||||
<input aria-label="Edit todo" type="text" name="text" value={todo.text} />
|
||||
<button class="save" aria-label="Save todo" />
|
||||
</form>
|
||||
|
||||
<form
|
||||
action="/todos/{todo.uid}.json?_method=delete"
|
||||
method="post"
|
||||
use:enhance={{
|
||||
result: () => {
|
||||
todos = todos.filter((t) => t.uid !== todo.uid);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<button class="delete" aria-label="Delete todo" />
|
||||
</form>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.todos {
|
||||
width: 100%;
|
||||
max-width: var(--column-width);
|
||||
margin: var(--column-margin-top) auto 0 auto;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.new {
|
||||
margin: 0 0 0.5rem 0;
|
||||
}
|
||||
|
||||
input {
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
input:focus-visible {
|
||||
box-shadow: inset 1px 1px 6px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid #ff3e00 !important;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.new input {
|
||||
font-size: 28px;
|
||||
width: 100%;
|
||||
padding: 0.5em 1em 0.3em 1em;
|
||||
box-sizing: border-box;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.todo {
|
||||
display: grid;
|
||||
grid-template-columns: 2rem 1fr 2rem;
|
||||
grid-gap: 0.5rem;
|
||||
align-items: center;
|
||||
margin: 0 0 0.5rem 0;
|
||||
padding: 0.5rem;
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
filter: drop-shadow(2px 4px 6px rgba(0, 0, 0, 0.1));
|
||||
transform: translate(-1px, -1px);
|
||||
transition: filter 0.2s, transform 0.2s;
|
||||
}
|
||||
|
||||
.done {
|
||||
transform: none;
|
||||
opacity: 0.4;
|
||||
filter: drop-shadow(0px 0px 1px rgba(0, 0, 0, 0.1));
|
||||
}
|
||||
|
||||
form.text {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.todo input {
|
||||
flex: 1;
|
||||
padding: 0.5em 2em 0.5em 0.8em;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.todo button {
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
background-position: 50% 50%;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
button.toggle {
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
border-radius: 50%;
|
||||
box-sizing: border-box;
|
||||
background-size: 1em auto;
|
||||
}
|
||||
|
||||
.done .toggle {
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='22' height='16' viewBox='0 0 22 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 1.5L7.4375 14.5L1.5 8.5909' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.delete {
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4.5 5V22H19.5V5H4.5Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M10 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M14 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M2 5H22' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M8 5L9.6445 2H14.3885L16 5H8Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3C/svg%3E%0A");
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.delete:hover,
|
||||
.delete:focus {
|
||||
transition: opacity 0.2s;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.save {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
opacity: 0;
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 2H3.5C2.67158 2 2 2.67157 2 3.5V20.5C2 21.3284 2.67158 22 3.5 22H20.5C21.3284 22 22 21.3284 22 20.5V3.5C22 2.67157 21.3284 2 20.5 2Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M17 2V11H7.5V2H17Z' fill='white' stroke='white' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M13.5 5.5V7.5' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3Cpath d='M5.99844 2H18.4992' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E%0A");
|
||||
}
|
||||
|
||||
.todo input:focus + .save,
|
||||
.save:focus {
|
||||
transition: opacity 0.2s;
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
+5
-1
@@ -4,7 +4,11 @@ import preprocess from 'svelte-preprocess';
|
||||
const config = {
|
||||
// Consult https://github.com/sveltejs/svelte-preprocess
|
||||
// for more information about preprocessors
|
||||
preprocess: preprocess(),
|
||||
preprocess: [
|
||||
preprocess({
|
||||
postcss: true
|
||||
}),
|
||||
],
|
||||
|
||||
kit: {
|
||||
// hydrate the <div id="svelte"> element in src/app.html
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
const { tailwindExtractor } = require("tailwindcss/lib/lib/purgeUnusedStyles");
|
||||
|
||||
module.exports = {
|
||||
mode: "aot",
|
||||
purge: {
|
||||
content: [
|
||||
"./src/**/*.{html,js,svelte,ts}",
|
||||
],
|
||||
options: {
|
||||
defaultExtractor: (content) => [
|
||||
// If this stops working, please open an issue at https://github.com/svelte-add/tailwindcss/issues rather than bothering Tailwind Labs about it
|
||||
...tailwindExtractor(content),
|
||||
// Match Svelte class: directives (https://github.com/tailwindlabs/tailwindcss/discussions/1731)
|
||||
...[...content.matchAll(/(?:class:)*([\w\d-/:%.]+)/gm)].map(([_match, group, ..._rest]) => group),
|
||||
],
|
||||
},
|
||||
safelist: [/^svelte-[\d\w]+$/],
|
||||
},
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
variants: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
Reference in New Issue
Block a user