all up to date
@ -0,0 +1,10 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
@ -0,0 +1,22 @@
|
||||
FROM node:18
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# COPY package.json and package-lock.json files
|
||||
COPY package*.json ./
|
||||
|
||||
COPY vite.config.js ./
|
||||
|
||||
# COPY tsconfig.json file
|
||||
COPY jsconfig.json ./
|
||||
|
||||
# COPY
|
||||
COPY . .
|
||||
|
||||
RUN npm install
|
||||
|
||||
RUN npm run build
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD [ "node", "/app/build" ]
|
@ -0,0 +1,57 @@
|
||||
# Carteles de DMD
|
||||
|
||||
Aplicación para que los grupos creen sus propios carteles.
|
||||
|
||||
## TODO
|
||||
|
||||
- [x] Separar fecha y hora en campos distintos.
|
||||
- [x] Quitar el 'de' de la fecha
|
||||
- [x] Generar texto de boletín junto al cartel.
|
||||
- [ ] Guardar una copia de todos los carteles que se creen y crear una página 'admin' que los muestre con fecha, permita borrarlos y tal. Se pueden guardar todos en Sqlite y listo.
|
||||
|
||||
### Verde
|
||||
|
||||
- [x] Cambiar color de 'dirección' de rojo a verde
|
||||
- [x] Cambiar la plantilla por modelo nuevo
|
||||
|
||||
### Azul
|
||||
|
||||
- [x] Fecha y hora juntas en la esquina superior derecha
|
||||
|
||||
### Corazón
|
||||
|
||||
- [x] Incluir todos los elementos
|
||||
- [ ] Confirmar con Esther
|
||||
|
||||
### Nueva plantilla
|
||||
|
||||
- [x] Incluir
|
||||
|
||||
# Carteles de DMD
|
||||
|
||||
Aplicación para que los grupos creen sus propios carteles.
|
||||
|
||||
## TODO
|
||||
|
||||
- [x] Separar fecha y hora en campos distintos.
|
||||
- [x] Quitar el 'de' de la fecha
|
||||
- [x] Generar texto de boletín junto al cartel.
|
||||
- [ ] Guardar una copia de todos los carteles que se creen y crear una página 'admin' que los muestre con fecha, permita borrarlos y tal. Se pueden guardar todos en Sqlite y listo.
|
||||
|
||||
### Verde
|
||||
|
||||
- [x] Cambiar color de 'dirección' de rojo a verde
|
||||
- [x] Cambiar la plantilla por modelo nuevo
|
||||
|
||||
### Azul
|
||||
|
||||
- [x] Fecha y hora juntas en la esquina superior derecha
|
||||
|
||||
### Corazón
|
||||
|
||||
- [x] Incluir todos los elementos
|
||||
- [ ] Confirmar con Esther
|
||||
|
||||
### Nueva plantilla
|
||||
|
||||
- [x] Incluir
|
@ -0,0 +1,4 @@
|
||||
{
|
||||
"schemaVersion": 2,
|
||||
"dockerfilePath": "./Dockerfile"
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
{
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true
|
||||
}
|
||||
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias and https://kit.svelte.dev/docs/configuration#files
|
||||
//
|
||||
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
||||
// from the referenced tsconfig.json - TypeScript does not merge them in
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "carteles2",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "^1.0.0",
|
||||
"@sveltejs/kit": "^1.0.0",
|
||||
"svelte": "^3.54.0",
|
||||
"svelte-check": "^2.9.2",
|
||||
"typescript": "^4.9.3",
|
||||
"vite": "^4.0.0"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"html2canvas": "^1.4.1",
|
||||
"quill": "^1.3.7"
|
||||
}
|
||||
}
|
@ -0,0 +1,975 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Alegreya+Sans+SC:wght@300;400;700&display=swap');
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 14px;
|
||||
font-family: 'Arial', sans-serif;
|
||||
}
|
||||
|
||||
html {
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
}
|
||||
|
||||
:root {
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
ul,ol {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
|
||||
/* Quill css */
|
||||
|
||||
/*!
|
||||
* * Quill Editor v1.3.6
|
||||
* * https://quilljs.com/
|
||||
* * Copyright (c) 2014, Jason Chen
|
||||
* * Copyright (c) 2013, salesforce.com
|
||||
* */
|
||||
.ql-container {
|
||||
box-sizing: border-box;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
font-size: 13px;
|
||||
height: 100%;
|
||||
margin: 0px;
|
||||
position: relative;
|
||||
}
|
||||
.ql-container.ql-disabled .ql-tooltip {
|
||||
visibility: hidden;
|
||||
}
|
||||
.ql-container.ql-disabled .ql-editor ul[data-checked] > li::before {
|
||||
pointer-events: none;
|
||||
}
|
||||
.ql-clipboard {
|
||||
left: -100000px;
|
||||
height: 1px;
|
||||
overflow-y: hidden;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
}
|
||||
.ql-clipboard p {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.ql-editor {
|
||||
min-height: 8rem;
|
||||
box-sizing: border-box;
|
||||
line-height: 1.42;
|
||||
height: 100%;
|
||||
outline: none;
|
||||
overflow-y: auto;
|
||||
padding: 12px 15px;
|
||||
tab-size: 4;
|
||||
-moz-tab-size: 4;
|
||||
text-align: left;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.ql-editor > * {
|
||||
cursor: text;
|
||||
}
|
||||
.ql-editor p,
|
||||
.ql-editor ol,
|
||||
.ql-editor ul,
|
||||
.ql-editor pre,
|
||||
.ql-editor blockquote,
|
||||
.ql-editor h1,
|
||||
.ql-editor h2,
|
||||
.ql-editor h3,
|
||||
.ql-editor h4,
|
||||
.ql-editor h5,
|
||||
.ql-editor h6 {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
counter-reset: list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9;
|
||||
}
|
||||
.ql-editor ol,
|
||||
.ql-editor ul {
|
||||
padding-left: 1.5em;
|
||||
}
|
||||
.ql-editor ol > li,
|
||||
.ql-editor ul > li {
|
||||
list-style-type: none;
|
||||
}
|
||||
.ql-editor ul > li::before {
|
||||
content: '\2022';
|
||||
}
|
||||
.ql-editor ul[data-checked=true],
|
||||
.ql-editor ul[data-checked=false] {
|
||||
pointer-events: none;
|
||||
}
|
||||
.ql-editor ul[data-checked=true] > li *,
|
||||
.ql-editor ul[data-checked=false] > li * {
|
||||
pointer-events: all;
|
||||
}
|
||||
.ql-editor ul[data-checked=true] > li::before,
|
||||
.ql-editor ul[data-checked=false] > li::before {
|
||||
color: #777;
|
||||
cursor: pointer;
|
||||
pointer-events: all;
|
||||
}
|
||||
.ql-editor ul[data-checked=true] > li::before {
|
||||
content: '\2611';
|
||||
}
|
||||
.ql-editor ul[data-checked=false] > li::before {
|
||||
content: '\2610';
|
||||
}
|
||||
.ql-editor li::before {
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
width: 1.2em;
|
||||
}
|
||||
.ql-editor li:not(.ql-direction-rtl)::before {
|
||||
margin-left: -1.5em;
|
||||
margin-right: 0.3em;
|
||||
text-align: right;
|
||||
}
|
||||
.ql-editor li.ql-direction-rtl::before {
|
||||
margin-left: 0.3em;
|
||||
margin-right: -1.5em;
|
||||
}
|
||||
.ql-editor ol li:not(.ql-direction-rtl),
|
||||
.ql-editor ul li:not(.ql-direction-rtl) {
|
||||
padding-left: 1.5em;
|
||||
}
|
||||
.ql-editor ol li.ql-direction-rtl,
|
||||
.ql-editor ul li.ql-direction-rtl {
|
||||
padding-right: 1.5em;
|
||||
}
|
||||
.ql-editor ol li {
|
||||
counter-reset: list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9;
|
||||
counter-increment: list-0;
|
||||
}
|
||||
.ql-editor ol li:before {
|
||||
content: counter(list-0, decimal) '. ';
|
||||
}
|
||||
.ql-editor ol li.ql-indent-1 {
|
||||
counter-increment: list-1;
|
||||
}
|
||||
.ql-editor ol li.ql-indent-1:before {
|
||||
content: counter(list-1, lower-alpha) '. ';
|
||||
}
|
||||
.ql-editor ol li.ql-indent-1 {
|
||||
counter-reset: list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9;
|
||||
}
|
||||
.ql-editor ol li.ql-indent-2 {
|
||||
counter-increment: list-2;
|
||||
}
|
||||
.ql-editor ol li.ql-indent-2:before {
|
||||
content: counter(list-2, lower-roman) '. ';
|
||||
}
|
||||
.ql-editor ol li.ql-indent-2 {
|
||||
counter-reset: list-3 list-4 list-5 list-6 list-7 list-8 list-9;
|
||||
}
|
||||
.ql-editor ol li.ql-indent-3 {
|
||||
counter-increment: list-3;
|
||||
}
|
||||
.ql-editor ol li.ql-indent-3:before {
|
||||
content: counter(list-3, decimal) '. ';
|
||||
}
|
||||
.ql-editor ol li.ql-indent-3 {
|
||||
counter-reset: list-4 list-5 list-6 list-7 list-8 list-9;
|
||||
}
|
||||
.ql-editor ol li.ql-indent-4 {
|
||||
counter-increment: list-4;
|
||||
}
|
||||
.ql-editor ol li.ql-indent-4:before {
|
||||
content: counter(list-4, lower-alpha) '. ';
|
||||
}
|
||||
.ql-editor ol li.ql-indent-4 {
|
||||
counter-reset: list-5 list-6 list-7 list-8 list-9;
|
||||
}
|
||||
.ql-editor ol li.ql-indent-5 {
|
||||
counter-increment: list-5;
|
||||
}
|
||||
.ql-editor ol li.ql-indent-5:before {
|
||||
content: counter(list-5, lower-roman) '. ';
|
||||
}
|
||||
.ql-editor ol li.ql-indent-5 {
|
||||
counter-reset: list-6 list-7 list-8 list-9;
|
||||
}
|
||||
.ql-editor ol li.ql-indent-6 {
|
||||
counter-increment: list-6;
|
||||
}
|
||||
.ql-editor ol li.ql-indent-6:before {
|
||||
content: counter(list-6, decimal) '. ';
|
||||
}
|
||||
.ql-editor ol li.ql-indent-6 {
|
||||
counter-reset: list-7 list-8 list-9;
|
||||
}
|
||||
.ql-editor ol li.ql-indent-7 {
|
||||
counter-increment: list-7;
|
||||
}
|
||||
.ql-editor ol li.ql-indent-7:before {
|
||||
content: counter(list-7, lower-alpha) '. ';
|
||||
}
|
||||
.ql-editor ol li.ql-indent-7 {
|
||||
counter-reset: list-8 list-9;
|
||||
}
|
||||
.ql-editor ol li.ql-indent-8 {
|
||||
counter-increment: list-8;
|
||||
}
|
||||
.ql-editor ol li.ql-indent-8:before {
|
||||
content: counter(list-8, lower-roman) '. ';
|
||||
}
|
||||
.ql-editor ol li.ql-indent-8 {
|
||||
counter-reset: list-9;
|
||||
}
|
||||
.ql-editor ol li.ql-indent-9 {
|
||||
counter-increment: list-9;
|
||||
}
|
||||
.ql-editor ol li.ql-indent-9:before {
|
||||
content: counter(list-9, decimal) '. ';
|
||||
}
|
||||
.ql-editor .ql-indent-1:not(.ql-direction-rtl) {
|
||||
padding-left: 3em;
|
||||
}
|
||||
.ql-editor li.ql-indent-1:not(.ql-direction-rtl) {
|
||||
padding-left: 4.5em;
|
||||
}
|
||||
.ql-editor .ql-indent-1.ql-direction-rtl.ql-align-right {
|
||||
padding-right: 3em;
|
||||
}
|
||||
.ql-editor li.ql-indent-1.ql-direction-rtl.ql-align-right {
|
||||
padding-right: 4.5em;
|
||||
}
|
||||
.ql-editor .ql-indent-2:not(.ql-direction-rtl) {
|
||||
padding-left: 6em;
|
||||
}
|
||||
.ql-editor li.ql-indent-2:not(.ql-direction-rtl) {
|
||||
padding-left: 7.5em;
|
||||
}
|
||||
.ql-editor .ql-indent-2.ql-direction-rtl.ql-align-right {
|
||||
padding-right: 6em;
|
||||
}
|
||||
.ql-editor li.ql-indent-2.ql-direction-rtl.ql-align-right {
|
||||
padding-right: 7.5em;
|
||||
}
|
||||
.ql-editor .ql-indent-3:not(.ql-direction-rtl) {
|
||||
padding-left: 9em;
|
||||
}
|
||||
.ql-editor li.ql-indent-3:not(.ql-direction-rtl) {
|
||||
padding-left: 10.5em;
|
||||
}
|
||||
.ql-editor .ql-indent-3.ql-direction-rtl.ql-align-right {
|
||||
padding-right: 9em;
|
||||
}
|
||||
.ql-editor li.ql-indent-3.ql-direction-rtl.ql-align-right {
|
||||
padding-right: 10.5em;
|
||||
}
|
||||
.ql-editor .ql-indent-4:not(.ql-direction-rtl) {
|
||||
padding-left: 12em;
|
||||
}
|
||||
.ql-editor li.ql-indent-4:not(.ql-direction-rtl) {
|
||||
padding-left: 13.5em;
|
||||
}
|
||||
.ql-editor .ql-indent-4.ql-direction-rtl.ql-align-right {
|
||||
padding-right: 12em;
|
||||
}
|
||||
.ql-editor li.ql-indent-4.ql-direction-rtl.ql-align-right {
|
||||
padding-right: 13.5em;
|
||||
}
|
||||
.ql-editor .ql-indent-5:not(.ql-direction-rtl) {
|
||||
padding-left: 15em;
|
||||
}
|
||||
.ql-editor li.ql-indent-5:not(.ql-direction-rtl) {
|
||||
padding-left: 16.5em;
|
||||
}
|
||||
.ql-editor .ql-indent-5.ql-direction-rtl.ql-align-right {
|
||||
padding-right: 15em;
|
||||
}
|
||||
.ql-editor li.ql-indent-5.ql-direction-rtl.ql-align-right {
|
||||
padding-right: 16.5em;
|
||||
}
|
||||
.ql-editor .ql-indent-6:not(.ql-direction-rtl) {
|
||||
padding-left: 18em;
|
||||
}
|
||||
.ql-editor li.ql-indent-6:not(.ql-direction-rtl) {
|
||||
padding-left: 19.5em;
|
||||
}
|
||||
.ql-editor .ql-indent-6.ql-direction-rtl.ql-align-right {
|
||||
padding-right: 18em;
|
||||
}
|
||||
.ql-editor li.ql-indent-6.ql-direction-rtl.ql-align-right {
|
||||
padding-right: 19.5em;
|
||||
}
|
||||
.ql-editor .ql-indent-7:not(.ql-direction-rtl) {
|
||||
padding-left: 21em;
|
||||
}
|
||||
.ql-editor li.ql-indent-7:not(.ql-direction-rtl) {
|
||||
padding-left: 22.5em;
|
||||
}
|
||||
.ql-editor .ql-indent-7.ql-direction-rtl.ql-align-right {
|
||||
padding-right: 21em;
|
||||
}
|
||||
.ql-editor li.ql-indent-7.ql-direction-rtl.ql-align-right {
|
||||
padding-right: 22.5em;
|
||||
}
|
||||
.ql-editor .ql-indent-8:not(.ql-direction-rtl) {
|
||||
padding-left: 24em;
|
||||
}
|
||||
.ql-editor li.ql-indent-8:not(.ql-direction-rtl) {
|
||||
padding-left: 25.5em;
|
||||
}
|
||||
.ql-editor .ql-indent-8.ql-direction-rtl.ql-align-right {
|
||||
padding-right: 24em;
|
||||
}
|
||||
.ql-editor li.ql-indent-8.ql-direction-rtl.ql-align-right {
|
||||
padding-right: 25.5em;
|
||||
}
|
||||
.ql-editor .ql-indent-9:not(.ql-direction-rtl) {
|
||||
padding-left: 27em;
|
||||
}
|
||||
.ql-editor li.ql-indent-9:not(.ql-direction-rtl) {
|
||||
padding-left: 28.5em;
|
||||
}
|
||||
.ql-editor .ql-indent-9.ql-direction-rtl.ql-align-right {
|
||||
padding-right: 27em;
|
||||
}
|
||||
.ql-editor li.ql-indent-9.ql-direction-rtl.ql-align-right {
|
||||
padding-right: 28.5em;
|
||||
}
|
||||
.ql-editor .ql-video {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
}
|
||||
.ql-editor .ql-video.ql-align-center {
|
||||
margin: 0 auto;
|
||||
}
|
||||
.ql-editor .ql-video.ql-align-right {
|
||||
margin: 0 0 0 auto;
|
||||
}
|
||||
.ql-editor .ql-bg-black {
|
||||
background-color: #000;
|
||||
}
|
||||
.ql-editor .ql-bg-red {
|
||||
background-color: #e60000;
|
||||
}
|
||||
.ql-editor .ql-bg-orange {
|
||||
background-color: #f90;
|
||||
}
|
||||
.ql-editor .ql-bg-yellow {
|
||||
background-color: #ff0;
|
||||
}
|
||||
.ql-editor .ql-bg-green {
|
||||
background-color: #008a00;
|
||||
}
|
||||
.ql-editor .ql-bg-blue {
|
||||
background-color: #06c;
|
||||
}
|
||||
.ql-editor .ql-bg-purple {
|
||||
background-color: #93f;
|
||||
}
|
||||
.ql-editor .ql-color-white {
|
||||
color: #fff;
|
||||
}
|
||||
.ql-editor .ql-color-red {
|
||||
color: #e60000;
|
||||
}
|
||||
.ql-editor .ql-color-orange {
|
||||
color: #f90;
|
||||
}
|
||||
.ql-editor .ql-color-yellow {
|
||||
color: #ff0;
|
||||
}
|
||||
.ql-editor .ql-color-green {
|
||||
color: #008a00;
|
||||
}
|
||||
.ql-editor .ql-color-blue {
|
||||
color: #06c;
|
||||
}
|
||||
.ql-editor .ql-color-purple {
|
||||
color: #93f;
|
||||
}
|
||||
.ql-editor .ql-font-serif {
|
||||
font-family: Georgia, Times New Roman, serif;
|
||||
}
|
||||
.ql-editor .ql-font-monospace {
|
||||
font-family: Monaco, Courier New, monospace;
|
||||
}
|
||||
.ql-editor .ql-size-small {
|
||||
font-size: 0.75em;
|
||||
}
|
||||
.ql-editor .ql-size-large {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
.ql-editor .ql-size-huge {
|
||||
font-size: 2.5em;
|
||||
}
|
||||
.ql-editor .ql-direction-rtl {
|
||||
direction: rtl;
|
||||
text-align: inherit;
|
||||
}
|
||||
.ql-editor .ql-align-center {
|
||||
text-align: center;
|
||||
}
|
||||
.ql-editor .ql-align-justify {
|
||||
text-align: justify;
|
||||
}
|
||||
.ql-editor .ql-align-right {
|
||||
text-align: right;
|
||||
}
|
||||
.ql-editor.ql-blank::before {
|
||||
color: rgba(0,0,0,0.6);
|
||||
content: attr(data-placeholder);
|
||||
font-style: italic;
|
||||
left: 15px;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
}
|
||||
.ql-snow.ql-toolbar:after,
|
||||
.ql-snow .ql-toolbar:after {
|
||||
clear: both;
|
||||
content: '';
|
||||
display: table;
|
||||
}
|
||||
.ql-snow.ql-toolbar button,
|
||||
.ql-snow .ql-toolbar button {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
float: left;
|
||||
height: 24px;
|
||||
padding: 3px 5px;
|
||||
width: 28px;
|
||||
}
|
||||
.ql-snow.ql-toolbar button svg,
|
||||
.ql-snow .ql-toolbar button svg {
|
||||
float: left;
|
||||
height: 100%;
|
||||
}
|
||||
.ql-snow.ql-toolbar button:active:hover,
|
||||
.ql-snow .ql-toolbar button:active:hover {
|
||||
outline: none;
|
||||
}
|
||||
.ql-snow.ql-toolbar input.ql-image[type=file],
|
||||
.ql-snow .ql-toolbar input.ql-image[type=file] {
|
||||
display: none;
|
||||
}
|
||||
.ql-snow.ql-toolbar button:hover,
|
||||
.ql-snow .ql-toolbar button:hover,
|
||||
.ql-snow.ql-toolbar button:focus,
|
||||
.ql-snow .ql-toolbar button:focus,
|
||||
.ql-snow.ql-toolbar button.ql-active,
|
||||
.ql-snow .ql-toolbar button.ql-active,
|
||||
.ql-snow.ql-toolbar .ql-picker-label:hover,
|
||||
.ql-snow .ql-toolbar .ql-picker-label:hover,
|
||||
.ql-snow.ql-toolbar .ql-picker-label.ql-active,
|
||||
.ql-snow .ql-toolbar .ql-picker-label.ql-active,
|
||||
.ql-snow.ql-toolbar .ql-picker-item:hover,
|
||||
.ql-snow .ql-toolbar .ql-picker-item:hover,
|
||||
.ql-snow.ql-toolbar .ql-picker-item.ql-selected,
|
||||
.ql-snow .ql-toolbar .ql-picker-item.ql-selected {
|
||||
color: #06c;
|
||||
}
|
||||
.ql-snow.ql-toolbar button:hover .ql-fill,
|
||||
.ql-snow .ql-toolbar button:hover .ql-fill,
|
||||
.ql-snow.ql-toolbar button:focus .ql-fill,
|
||||
.ql-snow .ql-toolbar button:focus .ql-fill,
|
||||
.ql-snow.ql-toolbar button.ql-active .ql-fill,
|
||||
.ql-snow .ql-toolbar button.ql-active .ql-fill,
|
||||
.ql-snow.ql-toolbar .ql-picker-label:hover .ql-fill,
|
||||
.ql-snow .ql-toolbar .ql-picker-label:hover .ql-fill,
|
||||
.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-fill,
|
||||
.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-fill,
|
||||
.ql-snow.ql-toolbar .ql-picker-item:hover .ql-fill,
|
||||
.ql-snow .ql-toolbar .ql-picker-item:hover .ql-fill,
|
||||
.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-fill,
|
||||
.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-fill,
|
||||
.ql-snow.ql-toolbar button:hover .ql-stroke.ql-fill,
|
||||
.ql-snow .ql-toolbar button:hover .ql-stroke.ql-fill,
|
||||
.ql-snow.ql-toolbar button:focus .ql-stroke.ql-fill,
|
||||
.ql-snow .ql-toolbar button:focus .ql-stroke.ql-fill,
|
||||
.ql-snow.ql-toolbar button.ql-active .ql-stroke.ql-fill,
|
||||
.ql-snow .ql-toolbar button.ql-active .ql-stroke.ql-fill,
|
||||
.ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke.ql-fill,
|
||||
.ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke.ql-fill,
|
||||
.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke.ql-fill,
|
||||
.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke.ql-fill,
|
||||
.ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke.ql-fill,
|
||||
.ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke.ql-fill,
|
||||
.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke.ql-fill,
|
||||
.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke.ql-fill {
|
||||
fill: #06c;
|
||||
}
|
||||
.ql-snow.ql-toolbar button:hover .ql-stroke,
|
||||
.ql-snow .ql-toolbar button:hover .ql-stroke,
|
||||
.ql-snow.ql-toolbar button:focus .ql-stroke,
|
||||
.ql-snow .ql-toolbar button:focus .ql-stroke,
|
||||
.ql-snow.ql-toolbar button.ql-active .ql-stroke,
|
||||
.ql-snow .ql-toolbar button.ql-active .ql-stroke,
|
||||
.ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke,
|
||||
.ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke,
|
||||
.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke,
|
||||
.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke,
|
||||
.ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke,
|
||||
.ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke,
|
||||
.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke,
|
||||
.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke,
|
||||
.ql-snow.ql-toolbar button:hover .ql-stroke-miter,
|
||||
.ql-snow .ql-toolbar button:hover .ql-stroke-miter,
|
||||
.ql-snow.ql-toolbar button:focus .ql-stroke-miter,
|
||||
.ql-snow .ql-toolbar button:focus .ql-stroke-miter,
|
||||
.ql-snow.ql-toolbar button.ql-active .ql-stroke-miter,
|
||||
.ql-snow .ql-toolbar button.ql-active .ql-stroke-miter,
|
||||
.ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke-miter,
|
||||
.ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke-miter,
|
||||
.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke-miter,
|
||||
.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke-miter,
|
||||
.ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke-miter,
|
||||
.ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke-miter,
|
||||
.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke-miter,
|
||||
.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke-miter {
|
||||
stroke: #06c;
|
||||
}
|
||||
@media (pointer: coarse) {
|
||||
.ql-snow.ql-toolbar button:hover:not(.ql-active),
|
||||
.ql-snow .ql-toolbar button:hover:not(.ql-active) {
|
||||
color: #444;
|
||||
}
|
||||
.ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-fill,
|
||||
.ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-fill,
|
||||
.ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke.ql-fill,
|
||||
.ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-stroke.ql-fill {
|
||||
fill: #444;
|
||||
}
|
||||
.ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke,
|
||||
.ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-stroke,
|
||||
.ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke-miter,
|
||||
.ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-stroke-miter {
|
||||
stroke: #444;
|
||||
}
|
||||
}
|
||||
.ql-snow {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.ql-snow * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.ql-snow .ql-hidden {
|
||||
display: none;
|
||||
}
|
||||
.ql-snow .ql-out-bottom,
|
||||
.ql-snow .ql-out-top {
|
||||
visibility: hidden;
|
||||
}
|
||||
.ql-snow .ql-tooltip {
|
||||
position: absolute;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
.ql-snow .ql-tooltip a {
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
.ql-snow .ql-tooltip.ql-flip {
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
.ql-snow .ql-formats {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.ql-snow .ql-formats:after {
|
||||
clear: both;
|
||||
content: '';
|
||||
display: table;
|
||||
}
|
||||
.ql-snow .ql-stroke {
|
||||
fill: none;
|
||||
stroke: #444;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
stroke-width: 2;
|
||||
}
|
||||
.ql-snow .ql-stroke-miter {
|
||||
fill: none;
|
||||
stroke: #444;
|
||||
stroke-miterlimit: 10;
|
||||
stroke-width: 2;
|
||||
}
|
||||
.ql-snow .ql-fill,
|
||||
.ql-snow .ql-stroke.ql-fill {
|
||||
fill: #444;
|
||||
}
|
||||
.ql-snow .ql-empty {
|
||||
fill: none;
|
||||
}
|
||||
.ql-snow .ql-even {
|
||||
fill-rule: evenodd;
|
||||
}
|
||||
.ql-snow .ql-thin,
|
||||
.ql-snow .ql-stroke.ql-thin {
|
||||
stroke-width: 1;
|
||||
}
|
||||
.ql-snow .ql-transparent {
|
||||
opacity: 0.4;
|
||||
}
|
||||
.ql-snow .ql-direction svg:last-child {
|
||||
display: none;
|
||||
}
|
||||
.ql-snow .ql-direction.ql-active svg:last-child {
|
||||
display: inline;
|
||||
}
|
||||
.ql-snow .ql-direction.ql-active svg:first-child {
|
||||
display: none;
|
||||
}
|
||||
.ql-snow .ql-editor h1 {
|
||||
font-size: 2em;
|
||||
}
|
||||
.ql-snow .ql-editor h2 {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
.ql-snow .ql-editor h3 {
|
||||
font-size: 1.17em;
|
||||
}
|
||||
.ql-snow .ql-editor h4 {
|
||||
font-size: 1em;
|
||||
}
|
||||
.ql-snow .ql-editor h5 {
|
||||
font-size: 0.83em;
|
||||
}
|
||||
.ql-snow .ql-editor h6 {
|
||||
font-size: 0.67em;
|
||||
}
|
||||
.ql-snow .ql-editor a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.ql-snow .ql-editor blockquote {
|
||||
border-left: 4px solid #ccc;
|
||||
margin-bottom: 5px;
|
||||
margin-top: 5px;
|
||||
padding-left: 16px;
|
||||
}
|
||||
.ql-snow .ql-editor code,
|
||||
.ql-snow .ql-editor pre {
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.ql-snow .ql-editor pre {
|
||||
white-space: pre-wrap;
|
||||
margin-bottom: 5px;
|
||||
margin-top: 5px;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
.ql-snow .ql-editor code {
|
||||
font-size: 85%;
|
||||
padding: 2px 4px;
|
||||
}
|
||||
.ql-snow .ql-editor pre.ql-syntax {
|
||||
background-color: #23241f;
|
||||
color: #f8f8f2;
|
||||
overflow: visible;
|
||||
}
|
||||
.ql-snow .ql-editor img {
|
||||
max-width: 100%;
|
||||
}
|
||||
.ql-snow .ql-picker {
|
||||
color: #444;
|
||||
display: block;
|
||||
float: left;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
height: 24px;
|
||||
position: relative;
|
||||
}
|
||||
.ql-snow .ql-picker-label {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
padding-left: 8px;
|
||||
padding-right: 2px;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
.ql-snow .ql-picker-label::before {
|
||||
display: inline-block;
|
||||
line-height: 22px;
|
||||
}
|
||||
.ql-snow .ql-picker-options {
|
||||
background-color: #fff;
|
||||
display: none;
|
||||
min-width: 100%;
|
||||
padding: 4px 8px;
|
||||
position: absolute;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.ql-snow .ql-picker-options .ql-picker-item {
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
padding-bottom: 5px;
|
||||
padding-top: 5px;
|
||||
}
|
||||
.ql-snow .ql-picker.ql-expanded .ql-picker-label {
|
||||
color: #ccc;
|
||||
z-index: 2;
|
||||
}
|
||||
.ql-snow .ql-picker.ql-expanded .ql-picker-label .ql-fill {
|
||||
fill: #ccc;
|
||||
}
|
||||
.ql-snow .ql-picker.ql-expanded .ql-picker-label .ql-stroke {
|
||||
stroke: #ccc;
|
||||
}
|
||||
.ql-snow .ql-picker.ql-expanded .ql-picker-options {
|
||||
display: block;
|
||||
margin-top: -1px;
|
||||
top: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
.ql-snow .ql-color-picker,
|
||||
.ql-snow .ql-icon-picker {
|
||||
width: 28px;
|
||||
}
|
||||
.ql-snow .ql-color-picker .ql-picker-label,
|
||||
.ql-snow .ql-icon-picker .ql-picker-label {
|
||||
padding: 2px 4px;
|
||||
}
|
||||
.ql-snow .ql-color-picker .ql-picker-label svg,
|
||||
.ql-snow .ql-icon-picker .ql-picker-label svg {
|
||||
right: 4px;
|
||||
}
|
||||
.ql-snow .ql-icon-picker .ql-picker-options {
|
||||
padding: 4px 0px;
|
||||
}
|
||||
.ql-snow .ql-icon-picker .ql-picker-item {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
padding: 2px 4px;
|
||||
}
|
||||
.ql-snow .ql-color-picker .ql-picker-options {
|
||||
padding: 3px 5px;
|
||||
width: 152px;
|
||||
}
|
||||
.ql-snow .ql-color-picker .ql-picker-item {
|
||||
border: 1px solid transparent;
|
||||
float: left;
|
||||
height: 16px;
|
||||
margin: 2px;
|
||||
padding: 0px;
|
||||
width: 16px;
|
||||
}
|
||||
.ql-snow .ql-picker:not(.ql-color-picker):not(.ql-icon-picker) svg {
|
||||
position: absolute;
|
||||
margin-top: -9px;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
width: 18px;
|
||||
}
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-label]:not([data-label=''])::before,
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-label[data-label]:not([data-label=''])::before,
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-label[data-label]:not([data-label=''])::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-label]:not([data-label=''])::before,
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-item[data-label]:not([data-label=''])::before,
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item[data-label]:not([data-label=''])::before {
|
||||
content: attr(data-label);
|
||||
}
|
||||
.ql-snow .ql-picker.ql-header {
|
||||
width: 98px;
|
||||
}
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item::before {
|
||||
content: 'Normal';
|
||||
}
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {
|
||||
content: 'Heading 1';
|
||||
}
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {
|
||||
content: 'Heading 2';
|
||||
}
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {
|
||||
content: 'Heading 3';
|
||||
}
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before {
|
||||
content: 'Heading 4';
|
||||
}
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {
|
||||
content: 'Heading 5';
|
||||
}
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {
|
||||
content: 'Heading 6';
|
||||
}
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {
|
||||
font-size: 2em;
|
||||
}
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {
|
||||
font-size: 1.17em;
|
||||
}
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before {
|
||||
font-size: 1em;
|
||||
}
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {
|
||||
font-size: 0.83em;
|
||||
}
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {
|
||||
font-size: 0.67em;
|
||||
}
|
||||
.ql-snow .ql-picker.ql-font {
|
||||
width: 108px;
|
||||
}
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-label::before,
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-item::before {
|
||||
content: 'Sans Serif';
|
||||
}
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=serif]::before,
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=serif]::before {
|
||||
content: 'Serif';
|
||||
}
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=monospace]::before,
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=monospace]::before {
|
||||
content: 'Monospace';
|
||||
}
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=serif]::before {
|
||||
font-family: Georgia, Times New Roman, serif;
|
||||
}
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=monospace]::before {
|
||||
font-family: Monaco, Courier New, monospace;
|
||||
}
|
||||
.ql-snow .ql-picker.ql-size {
|
||||
width: 98px;
|
||||
}
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-label::before,
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item::before {
|
||||
content: 'Normal';
|
||||
}
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value=small]::before,
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=small]::before {
|
||||
content: 'Small';
|
||||
}
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value=large]::before,
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=large]::before {
|
||||
content: 'Large';
|
||||
}
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value=huge]::before,
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=huge]::before {
|
||||
content: 'Huge';
|
||||
}
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=small]::before {
|
||||
font-size: 10px;
|
||||
}
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=large]::before {
|
||||
font-size: 18px;
|
||||
}
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=huge]::before {
|
||||
font-size: 32px;
|
||||
}
|
||||
.ql-snow .ql-color-picker.ql-background .ql-picker-item {
|
||||
background-color: #fff;
|
||||
}
|
||||
.ql-snow .ql-color-picker.ql-color .ql-picker-item {
|
||||
background-color: #000;
|
||||
}
|
||||
.ql-toolbar.ql-snow {
|
||||
border: 1px solid #ccc;
|
||||
box-sizing: border-box;
|
||||
font-family: 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;
|
||||
padding: 8px;
|
||||
}
|
||||
.ql-toolbar.ql-snow .ql-formats {
|
||||
margin-right: 15px;
|
||||
}
|
||||
.ql-toolbar.ql-snow .ql-picker-label {
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
.ql-toolbar.ql-snow .ql-picker-options {
|
||||
border: 1px solid transparent;
|
||||
box-shadow: rgba(0,0,0,0.2) 0 2px 8px;
|
||||
}
|
||||
.ql-toolbar.ql-snow .ql-picker.ql-expanded .ql-picker-label {
|
||||
border-color: #ccc;
|
||||
}
|
||||
.ql-toolbar.ql-snow .ql-picker.ql-expanded .ql-picker-options {
|
||||
border-color: #ccc;
|
||||
}
|
||||
.ql-toolbar.ql-snow .ql-color-picker .ql-picker-item.ql-selected,
|
||||
.ql-toolbar.ql-snow .ql-color-picker .ql-picker-item:hover {
|
||||
border-color: #000;
|
||||
}
|
||||
.ql-toolbar.ql-snow + .ql-container.ql-snow {
|
||||
border-top: 0px;
|
||||
}
|
||||
.ql-snow .ql-tooltip {
|
||||
background-color: #fff;
|
||||
border: 1px solid #ccc;
|
||||
box-shadow: 0px 0px 5px #ddd;
|
||||
color: #444;
|
||||
padding: 5px 12px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.ql-snow .ql-tooltip::before {
|
||||
content: "Visit URL:";
|
||||
line-height: 26px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
.ql-snow .ql-tooltip input[type=text] {
|
||||
display: none;
|
||||
border: 1px solid #ccc;
|
||||
font-size: 13px;
|
||||
height: 26px;
|
||||
margin: 0px;
|
||||
padding: 3px 5px;
|
||||
width: 170px;
|
||||
}
|
||||
.ql-snow .ql-tooltip a.ql-preview {
|
||||
display: inline-block;
|
||||
max-width: 200px;
|
||||
overflow-x: hidden;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: top;
|
||||
}
|
||||
.ql-snow .ql-tooltip a.ql-action::after {
|
||||
border-right: 1px solid #ccc;
|
||||
content: 'Edit';
|
||||
margin-left: 16px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
.ql-snow .ql-tooltip a.ql-remove::before {
|
||||
content: 'Remove';
|
||||
margin-left: 8px;
|
||||
}
|
||||
.ql-snow .ql-tooltip a {
|
||||
line-height: 26px;
|
||||
}
|
||||
.ql-snow .ql-tooltip.ql-editing a.ql-preview,
|
||||
.ql-snow .ql-tooltip.ql-editing a.ql-remove {
|
||||
display: none;
|
||||
}
|
||||
.ql-snow .ql-tooltip.ql-editing input[type=text] {
|
||||
display: inline-block;
|
||||
}
|
||||
.ql-snow .ql-tooltip.ql-editing a.ql-action::after {
|
||||
border-right: 0px;
|
||||
content: 'Save';
|
||||
padding-right: 0px;
|
||||
}
|
||||
.ql-snow .ql-tooltip[data-mode=link]::before {
|
||||
content: "Enter link:";
|
||||
}
|
||||
.ql-snow .ql-tooltip[data-mode=formula]::before {
|
||||
content: "Enter formula:";
|
||||
}
|
||||
.ql-snow .ql-tooltip[data-mode=video]::before {
|
||||
content: "Enter video:";
|
||||
}
|
||||
.ql-snow a {
|
||||
color: #06c;
|
||||
}
|
||||
.ql-container.ql-snow {
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
@ -0,0 +1,9 @@
|
||||
// See https://kit.svelte.dev/docs/types#app
|
||||
// for information about these interfaces
|
||||
// and what to do when importing types
|
||||
declare namespace App {
|
||||
// interface Error {}
|
||||
// interface Locals {}
|
||||
// interface PageData {}
|
||||
// interface Platform {}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,14 @@
|
||||
/**
|
||||
* @async
|
||||
* @function send
|
||||
* @param {HTMLFormElement} form
|
||||
* @returns {Promise<JSON>}
|
||||
*/
|
||||
export const send = async (form) => {
|
||||
const response = await fetch(form.action, {
|
||||
method: form.method,
|
||||
body: new FormData(form),
|
||||
headers: { accept: "application/json" },
|
||||
});
|
||||
return await response.json();
|
||||
};
|
@ -0,0 +1,38 @@
|
||||
<script>
|
||||
import { templates } from '$lib/templates/templates';
|
||||
|
||||
/** @type number */ export let templateIndex;
|
||||
import { address, multiplier } from '$lib/stores/store';
|
||||
</script>
|
||||
|
||||
{#if templates[templateIndex] && $address}
|
||||
<div
|
||||
class="address"
|
||||
style="
|
||||
top: {templates[templateIndex].address.top}px;
|
||||
height: {templates[templateIndex].address.height}px;
|
||||
left: {templates[templateIndex].address.left}px;
|
||||
right: {templates[templateIndex].address.right}px;
|
||||
color: {templates[templateIndex].address.color};
|
||||
font-size: {templates[templateIndex].address.fontSize * $multiplier}px;
|
||||
font-family: {templates[templateIndex].address.fontFamily};
|
||||
line-height: {templates[templateIndex].address.lineHeight};
|
||||
text-align: {templates[templateIndex].address.textAlign};
|
||||
font-weight: {templates[templateIndex].address.fontWeight};
|
||||
line-height: {templates[templateIndex].address.lineHeight}px;
|
||||
"
|
||||
>
|
||||
{$address}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.address {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,35 @@
|
||||
<script>
|
||||
import { templates } from '$lib/templates/templates';
|
||||
|
||||
/** @type number */ export let templateIndex;
|
||||
import { city, multiplier } from '$lib/stores/store';
|
||||
</script>
|
||||
|
||||
{#if templates[templateIndex] && $city}
|
||||
<div
|
||||
class="city"
|
||||
style="
|
||||
top: {templates[templateIndex].city.top}px;
|
||||
height: {templates[templateIndex].city.height}px;
|
||||
left: {templates[templateIndex].city.left}px;
|
||||
right: {templates[templateIndex].city.right}px;
|
||||
color: {templates[templateIndex].city.color};
|
||||
font-size: {templates[templateIndex].city.fontSize * $multiplier}px;
|
||||
font-family: {templates[templateIndex].city.fontFamily};
|
||||
line-height: {templates[templateIndex].city.lineHeight};
|
||||
text-align: {templates[templateIndex].city.textAlign};
|
||||
font-weight: {templates[templateIndex].city.fontWeight};
|
||||
line-height: {templates[templateIndex].city.lineHeight}px;
|
||||
"
|
||||
>
|
||||
{$city}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.city {
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,36 @@
|
||||
<script>
|
||||
import { templates } from '$lib/templates/templates';
|
||||
|
||||
/** @type number */ export let templateIndex;
|
||||
import { content, multiplier } from '$lib/stores/store';
|
||||
</script>
|
||||
|
||||
{#if templates[templateIndex] && $content}
|
||||
<div
|
||||
class="content"
|
||||
style="
|
||||
top: {templates[templateIndex].content.top}px;
|
||||
height: {templates[templateIndex].content.height}px;
|
||||
left: {templates[templateIndex].content.left}px;
|
||||
right: {templates[templateIndex].content.right}px;
|
||||
color: {templates[templateIndex].content.color};
|
||||
font-size: {templates[templateIndex].content.fontSize * $multiplier}px;
|
||||
font-family: {templates[templateIndex].content.fontFamily};
|
||||
text-align: {templates[templateIndex].content.textAlign};
|
||||
font-weight: {templates[templateIndex].content.fontWeight};
|
||||
line-height: {templates[templateIndex].content.lineHeight}px;
|
||||
"
|
||||
>
|
||||
{@html $content}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.content {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,41 @@
|
||||
<script>
|
||||
import { templates } from '$lib/templates/templates';
|
||||
import { date, newdate, multiplier } from '$lib/stores/store';
|
||||
import { convertDate } from '$lib/convertDate';
|
||||
/** @type number */ export let templateIndex;
|
||||
|
||||
// /** @type {string} */ let newdate;
|
||||
|
||||
$: if ($date !== '') {
|
||||
$newdate = convertDate($date);
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if templates[templateIndex] && newdate}
|
||||
<div
|
||||
class="date"
|
||||
style="
|
||||
top: {templates[templateIndex].date.top}px;
|
||||
height: {templates[templateIndex].date.height}px;
|
||||
left: {templates[templateIndex].date.left}px;
|
||||
right: {templates[templateIndex].date.right}px;
|
||||
color: {templates[templateIndex].date.color};
|
||||
font-size: {templates[templateIndex].date.fontSize * $multiplier}px;
|
||||
font-family: {templates[templateIndex].date.fontFamily};
|
||||
text-align: {templates[templateIndex].date.textAlign};
|
||||
font-weight: {templates[templateIndex].date.fontWeight};
|
||||
line-height: {templates[templateIndex].date.lineHeight}px;
|
||||
"
|
||||
>
|
||||
{$newdate}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.date {
|
||||
position: absolute;
|
||||
text-transform: uppercase;
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,34 @@
|
||||
<script>
|
||||
import { templates } from '$lib/templates/templates';
|
||||
|
||||
/** @type number */ export let templateIndex;
|
||||
import { heading, multiplier } from '$lib/stores/store';
|
||||
</script>
|
||||
|
||||
{#if $heading && templates[templateIndex].heading}
|
||||
<div
|
||||
class="heading"
|
||||
style="
|
||||
top: {templates[templateIndex].heading.top}px;
|
||||
height: {templates[templateIndex].heading.height}px;
|
||||
left: {templates[templateIndex].heading.left}px;
|
||||
right: {templates[templateIndex].heading.right}px;
|
||||
color: {templates[templateIndex].heading.color};
|
||||
font-size: {templates[templateIndex].heading.fontSize * $multiplier}px;
|
||||
font-family: {templates[templateIndex].heading.fontFamily};
|
||||
text-align: {templates[templateIndex].heading.textAlign};
|
||||
font-weight: {templates[templateIndex].heading.fontWeight};
|
||||
line-height: {templates[templateIndex].heading.lineHeight}px;
|
||||
"
|
||||
>
|
||||
{$heading}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.heading {
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,39 @@
|
||||
<script>
|
||||
import { templates } from '$lib/templates/templates';
|
||||
|
||||
/** @type number */ export let templateIndex;
|
||||
import { subtitle, multiplier } from '$lib/stores/store';
|
||||
console.log($multiplier);
|
||||
</script>
|
||||
|
||||
{#if templates[templateIndex] && $subtitle}
|
||||
<div
|
||||
class="subtitle"
|
||||
style="
|
||||
top: {templates[templateIndex].subtitle.top}px;
|
||||
height: {templates[templateIndex].subtitle.height}px;
|
||||
left: {templates[templateIndex].subtitle.left}px;
|
||||
right: {templates[templateIndex].subtitle.right}px;
|
||||
color: {templates[templateIndex].subtitle.color};
|
||||
font-size: {templates[templateIndex].subtitle.fontSize * $multiplier}px;
|
||||
font-family: {templates[templateIndex].subtitle.fontFamily};
|
||||
text-align: {templates[templateIndex].subtitle.textAlign};
|
||||
font-weight: {templates[templateIndex].subtitle.fontWeight};
|
||||
line-height: {templates[templateIndex].subtitle.lineHeight}px;
|
||||
"
|
||||
>
|
||||
{$subtitle}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.subtitle {
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
/* flex-direction: column; */
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,70 @@
|
||||
<script>
|
||||
import {
|
||||
heading,
|
||||
title,
|
||||
subtitle,
|
||||
content,
|
||||
date,
|
||||
newdate,
|
||||
time,
|
||||
weekday,
|
||||
address,
|
||||
city
|
||||
} from '$lib/stores/store';
|
||||
|
||||
import { convertDate } from '$lib/convertDate';
|
||||
|
||||
/** @type {string} */ let textDate = '';
|
||||
/** @type {boolean} */ let checked = false;
|
||||
|
||||
$: if ($date !== '') {
|
||||
textDate = convertDate($date);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="text-container">
|
||||
<input type="checkbox" bind:checked />
|
||||
<label for="checked">Mostrar texto</label>
|
||||
|
||||
{#if checked}
|
||||
<div class="text" contenteditable="true">
|
||||
<p>Estimada/o amiga/o:</p>
|
||||
<p>
|
||||
El {$weekday.toLowerCase()}, {textDate}, celebramos el acto <strong>'{$title}'</strong> en {$city}.
|
||||
</p>
|
||||
<p>Como siempre, la entrada es gratuita hasta completar aforo.</p>
|
||||
<ul>
|
||||
<li>
|
||||
<p style="white-space: pre-wrap;">
|
||||
<strong>Lugar</strong>:<br />{$address}<br /><em>{$city}</em>
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><strong>Fecha</strong>:<br />{$newdate}<br />{$time}</p>
|
||||
</li>
|
||||
</ul>
|
||||
<p>{@html $content}</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.text-container {
|
||||
margin: 1rem auto;
|
||||
}
|
||||
.text {
|
||||
margin: 1rem auto;
|
||||
padding: 1rem;
|
||||
border: 1px solid lightgrey;
|
||||
background-color: #fff;
|
||||
}
|
||||
p {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
label {
|
||||
color: #333;
|
||||
}
|
||||
li {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,35 @@
|
||||
<script>
|
||||
import { templates } from '$lib/templates/templates';
|
||||
|
||||
/** @type number */ export let templateIndex;
|
||||
import { time, multiplier } from '$lib/stores/store';
|
||||
</script>
|
||||
|
||||
{#if templates[templateIndex] && $time !== ''}
|
||||
<div
|
||||
class="time"
|
||||
style="
|
||||
top: {templates[templateIndex].time.top}px;
|
||||
height: {templates[templateIndex].time.height}px;
|
||||
left: {templates[templateIndex].time.left}px;
|
||||
right: {templates[templateIndex].time.right}px;
|
||||
color: {templates[templateIndex].time.color};
|
||||
font-size: {templates[templateIndex].time.fontSize * $multiplier}px;
|
||||
font-family: {templates[templateIndex].time.fontFamily};
|
||||
text-align: {templates[templateIndex].time.textAlign};
|
||||
font-weight: {templates[templateIndex].time.fontWeight};
|
||||
line-height: {templates[templateIndex].time.lineHeight}px;
|
||||
"
|
||||
>
|
||||
{$time}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.time {
|
||||
position: absolute;
|
||||
text-transform: uppercase;
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,38 @@
|
||||
<script>
|
||||
import { templates } from '$lib/templates/templates';
|
||||
|
||||
/** @type number */ export let templateIndex;
|
||||
import { title, multiplier } from '$lib/stores/store';
|
||||
</script>
|
||||
|
||||
{#if templates[templateIndex] && $title}
|
||||
<div
|
||||
class="title"
|
||||
style="
|
||||
top: {templates[templateIndex].title.top}px;
|
||||
height: {templates[templateIndex].title.height}px;
|
||||
left: {templates[templateIndex].title.left}px;
|
||||
right: {templates[templateIndex].title.right}px;
|
||||
color: {templates[templateIndex].title.color};
|
||||
font-size: {templates[templateIndex].title.fontSize * $multiplier}px;
|
||||
font-family: {templates[templateIndex].title.fontFamily};
|
||||
text-align: {templates[templateIndex].title.textAlign};
|
||||
font-weight: {templates[templateIndex].title.fontWeight};
|
||||
line-height: {templates[templateIndex].title.lineHeight}px;
|
||||
|
||||
"
|
||||
>
|
||||
{$title}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.title {
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,34 @@
|
||||
<script>
|
||||
import { templates } from '$lib/templates/templates';
|
||||
|
||||
/** @type number */ export let templateIndex;
|
||||
import { weekday, multiplier } from '$lib/stores/store';
|
||||
</script>
|
||||
|
||||
{#if templates[templateIndex] && $weekday}
|
||||
<div
|
||||
class="weekday"
|
||||
style="
|
||||
top: {templates[templateIndex].weekday.top}px;
|
||||
height: {templates[templateIndex].weekday.height}px;
|
||||
left: {templates[templateIndex].weekday.left}px;
|
||||
right: {templates[templateIndex].weekday.right}px;
|
||||
color: {templates[templateIndex].weekday.color};
|
||||
font-size: {templates[templateIndex].weekday.fontSize * $multiplier}px;
|
||||
font-family: {templates[templateIndex].weekday.fontFamily};
|
||||
text-align: {templates[templateIndex].weekday.textAlign};
|
||||
font-weight: {templates[templateIndex].weekday.fontWeight};
|
||||
line-height: {templates[templateIndex].weekday.lineHeight}px;
|
||||
"
|
||||
>
|
||||
{$weekday}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.weekday {
|
||||
position: absolute;
|
||||
text-transform: uppercase;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,23 @@
|
||||
import { getMonthName } from '$lib/monthName';
|
||||
/**
|
||||
* @param {string} date - Fecha del evento
|
||||
* @returns {string} - Fecha en dia (número) mes (letras)
|
||||
*/
|
||||
export const convertDate = (date) => {
|
||||
if (date == undefined) {
|
||||
return '';
|
||||
}
|
||||
/** @type {string} */ const monthNumber = date.split('-')[1];
|
||||
/** @type {string} */ let day = date.split('-')[2].split('T')[0];
|
||||
/** @type {number} */ const dayNumber = Number(day);
|
||||
/** @type {string} */ const month = getMonthName(monthNumber);
|
||||
|
||||
/** Removes 0 on single digit days */
|
||||
if (dayNumber < 10) {
|
||||
day = day.charAt(1);
|
||||
}
|
||||
|
||||
/** @type {string} */ const res = `${day} de ${month}`;
|
||||
return res;
|
||||
};
|
||||
|
@ -0,0 +1,38 @@
|
||||
/** @typedef {typeof import('better-sqlite3')} better-sqlite3 */
|
||||
import Database from 'better-sqlite3';
|
||||
|
||||
export const db = new Database('./src/lib/db/carteles_dev.sqlite', { verbose: console.log });
|
||||
db.pragma("journal_mode = WAL");
|
||||
db.pragma("synchronous = normal");
|
||||
db.pragma("temp_store = memory");
|
||||
|
||||
const createUserTable = db.prepare(`CREATE TABLE IF NOT EXISTS Users (
|
||||
email TEXT PRIMARY KEY UNIQUE NOT NULL,
|
||||
password TEXT,
|
||||
isAdmin INTEGER
|
||||
);`);
|
||||
|
||||
const createPostersTable = db.prepare(`CREATE TABLE IF NOT EXISTS Posters (
|
||||
id INTEGER PRIMARY KEY UNIQUE NOT NULL,
|
||||
createdAt TEXT NOT NULL,
|
||||
image BLOB NOT NULL,
|
||||
content TEXT
|
||||
);`);
|
||||
|
||||
createUserTable.run();
|
||||
createPostersTable.run();
|
||||
|
||||
/**
|
||||
* @typedef {Object} User
|
||||
* @property {string} [email]
|
||||
* @property {string} [password]
|
||||
* @property {boolean} [isAdmin]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} Poster
|
||||
* @property {number} [id]
|
||||
* @property {string} [createdAt] Date
|
||||
* @property {Blob} [image]
|
||||
* @property {string} [content] Stringified contents
|
||||
*/
|
@ -0,0 +1,95 @@
|
||||
import { db } from '$lib/db/db.js';
|
||||
/**
|
||||
* Return of functions that change change DB, either 'success' or 'failure' with explanation
|
||||
* @typedef {Object} ChangeDBResult
|
||||
* @property {string} [error]
|
||||
* @property {string} [success]
|
||||
* @property {string} [id]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @function addUserToDB Adds user to the db
|
||||
* @param {string} email
|
||||
* @param {string} passwordHash
|
||||
* @param {boolean} isAdmin
|
||||
* @returns {ChangeDBResult} return
|
||||
*/
|
||||
export const addUserToDB = (email, passwordHash, isAdmin) => {
|
||||
if (!email || !passwordHash || !isAdmin) {
|
||||
return {
|
||||
error: "Either email, password or isAdmin are missing"
|
||||
};
|
||||
}
|
||||
const addUser = db.prepare(`INSERT INTO Users(email,password,isAdmin) VALUES(?,?,?);`)
|
||||
const result = addUser.run(email, passwordHash, isAdmin);
|
||||
if (result.changes === 1) {
|
||||
return {
|
||||
success: `User '${email}' added to DB`
|
||||
}
|
||||
}
|
||||
return { error: `Could not add user '${email}' to DB`, };
|
||||
}
|
||||
|
||||
/** Removes user from db
|
||||
* @param {string} email
|
||||
* @returns {ChangeDBResult} return
|
||||
*/
|
||||
export const delUserFromDB = (email) => {
|
||||
if (!email) {
|
||||
return {
|
||||
error: "Email not provided"
|
||||
}
|
||||
}
|
||||
const delUser = db.prepare(`DELETE FROM Users WHERE email=${email};`);
|
||||
const result = delUser.run();
|
||||
if (result.changes === 1) {
|
||||
return {
|
||||
success: `User '${email}' removed successfully`
|
||||
}
|
||||
}
|
||||
return { error: `Could not remove user '${email}' from DB` }
|
||||
}
|
||||
|
||||
/**
|
||||
* @function addPosterToDB
|
||||
* @param {any} image
|
||||
* @param {string} content
|
||||
* @returns {ChangeDBResult} return
|
||||
*/
|
||||
export const addPosterToDB = (image, content) => {
|
||||
|
||||
const id = crypto.randomUUID(); // Create random ID for Poster
|
||||
console.log("New id is: ", id);
|
||||
|
||||
const createdAt = Date.now().toString;
|
||||
|
||||
if (!image) { // Return error if mandatory info is missing
|
||||
return {
|
||||
error: "Image missing"
|
||||
}
|
||||
}
|
||||
|
||||
const addPoster = db.prepare(`INSERT INTO Posters(id,createdAt,image,content) VALUES ($id,$createdAt,$image,$content);`);
|
||||
const result = addPoster.run({
|
||||
id: id,
|
||||
createdAt: createdAt,
|
||||
image: image,
|
||||
content: content
|
||||
});
|
||||
|
||||
if (result.changes === 1) {
|
||||
return {
|
||||
success: `Poster added to DB with id ${id}`,
|
||||
id
|
||||
}
|
||||
}
|
||||
return { // Default return just in case
|
||||
error: `Could not save poster '${id}' to DB. Something went wrong`
|
||||
}
|
||||
}
|
||||
|
||||
export const getAllPostersFromDB = () => {
|
||||
const getPosters = db.prepare(`SELECT * FROM Posters;`);
|
||||
const result = getPosters.all();
|
||||
return result;
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
<script>
|
||||
import { colabs } from '$lib/stores/store';
|
||||
|
||||
/** @type {HTMLInputElement} */ let fileinput;
|
||||
|
||||
const onFileSelected = (/** @type {Event} */ e) => {
|
||||
// @ts-ignore
|
||||
let image = e.target.files[0];
|
||||
let reader = new FileReader();
|
||||
reader.readAsDataURL(image);
|
||||
reader.onload = (e) => {
|
||||
// @ts-ignore
|
||||
$colabs = [...$colabs, { image: e.target.result, text: '' }];
|
||||
};
|
||||
};
|
||||
|
||||
const removecolabs = (/** @type {number} */ index) => {
|
||||
if (index > -1) {
|
||||
$colabs.splice(index, 1);
|
||||
$colabs = $colabs;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="colabs">
|
||||
{#if $colabs[0]}
|
||||
{#each $colabs as organization, i}
|
||||
<div class="organization">
|
||||
<div
|
||||
class="remove"
|
||||
on:click|preventDefault={() => {
|
||||
removecolabs(i);
|
||||
}}
|
||||
>
|
||||
x
|
||||
</div>
|
||||
<img class="organization-logo" src={organization.image} alt={organization.text} />
|
||||
<input
|
||||
class="logo-title"
|
||||
type="text"
|
||||
bind:value={organization.text}
|
||||
placeholder="Nombre(opcional)"
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<button
|
||||
on:click|preventDefault={() => {
|
||||
fileinput.click();
|
||||
}}>Añadir logo</button
|
||||
>
|
||||
<input
|
||||
style="display:none;"
|
||||
type="file"
|
||||
accept=".jpg, .jpeg, .png"
|
||||
on:change={(e) => onFileSelected(e)}
|
||||
bind:this={fileinput}
|
||||
/>
|
||||
|
||||
<style>
|
||||
.colabs {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, min-content);
|
||||
align-items: center;
|
||||
justify-content: start;
|
||||
}
|
||||
|
||||
.organization {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-rows: min-content min-content;
|
||||
grid-gap: 0.5rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0.25rem 1rem;
|
||||
background-color: #ddd;
|
||||
padding: 0.25rem;
|
||||
}
|
||||
|
||||
.organization-logo {
|
||||
max-height: 72px;
|
||||
max-width: 144px;
|
||||
margin: 0 auto;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.remove {
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
right: -0.25rem;
|
||||
color: red;
|
||||
cursor: pointer;
|
||||
background-color: white;
|
||||
padding: 0 0.25rem;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
button {
|
||||
display: block;
|
||||
padding: 0.5rem;
|
||||
cursor: pointer;
|
||||
margin: 1rem;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,220 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import html2canvas from 'html2canvas';
|
||||
import Organized from '$lib/form/Organized.svelte';
|
||||
import Colabs from './Colabs.svelte';
|
||||
import {
|
||||
title,
|
||||
heading,
|
||||
subtitle,
|
||||
content,
|
||||
date,
|
||||
time,
|
||||
weekday,
|
||||
address,
|
||||
city,
|
||||
canvas
|
||||
} from '$lib/stores/store';
|
||||
|
||||
/** @type {HTMLDivElement} */ let editor;
|
||||
|
||||
const handleSubmit = async () => {
|
||||
console.log('submit');
|
||||
};
|
||||
|
||||
const getContent = () => {
|
||||
if (!editor) {
|
||||
console.log('no hay editor');
|
||||
return;
|
||||
}
|
||||
if (editor && editor.firstChild !== null) {
|
||||
// @ts-ignore
|
||||
$content = editor.firstChild.innerHTML;
|
||||
}
|
||||
};
|
||||
const saveCanvas = async () => {
|
||||
if ($canvas !== undefined) {
|
||||
const img = await html2canvas($canvas, { scale: 2 });
|
||||
const image = img.toDataURL('image/png');
|
||||
const data = new FormData();
|
||||
data.append('image', image);
|
||||
data.append(
|
||||
'content',
|
||||
JSON.stringify({
|
||||
title: $title,
|
||||
heading: $heading,
|
||||
subtitle: $subtitle,
|
||||
content: $content,
|
||||
date: $date,
|
||||
time: $time,
|
||||
weekday: $weekday,
|
||||
address: $address,
|
||||
city: $city
|
||||
})
|
||||
);
|
||||
|
||||
const req = await fetch('/admin', {
|
||||
method: 'POST',
|
||||
body: data
|
||||
});
|
||||
console.log(req);
|
||||
}
|
||||
};
|
||||
|
||||
const downloadCanvas = async () => {
|
||||
if ($canvas !== undefined) {
|
||||
const res = await html2canvas($canvas, { scale: 2 });
|
||||
const image = res.toDataURL('image/png');
|
||||
const link = document.createElement('a');
|
||||
link.download = `${$date.split('-')[0]}-${$date.split('-')[1]}-${
|
||||
$date.split('-')[2].split('T')[0]
|
||||
}-${$city.replace(' ', '_')}-cartel.png`;
|
||||
link.href = image;
|
||||
link.click();
|
||||
}
|
||||
};
|
||||
|
||||
const quillOptions = {
|
||||
modules: {
|
||||
toolbar: [['bold', 'italic']]
|
||||
},
|
||||
theme: 'snow'
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
const { default: Quill } = await import('quill');
|
||||
|
||||
let quill = new Quill(editor, {
|
||||
modules: {
|
||||
toolbar: quillOptions
|
||||
},
|
||||
theme: 'snow',
|
||||
placeholder:
|
||||
'Participan:\nFernando Marín (DMD)\nPresenta:\nFernanda del Castillo (DMD Asturias)'
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<form on:submit|preventDefault={handleSubmit}>
|
||||
<div class="form-group">
|
||||
<label for="heading">Tipo de actividad</label>
|
||||
<input
|
||||
bind:value={$heading}
|
||||
type="text"
|
||||
name="heading"
|
||||
placeholder="Ej: Taller, charla, coloquio, debate, curso"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div clasS="form-group">
|
||||
<label for="title">Título</label>
|
||||
<input bind:value={$title} type="text" name="title" placeholder="Título" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="subtitle">Subtítulo</label>
|
||||
<input bind:value={$subtitle} type="text" name="subtitle" placeholder="Subtítulo" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="content">Contenido</label>
|
||||
<div id="editor" on:click={getContent}>
|
||||
<div on:click={getContent} on:keyup={getContent} bind:this={editor} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group when">
|
||||
<div class="daymonth">
|
||||
<label for="date">Fecha</label>
|
||||
<input bind:value={$date} type="date" name="date" />
|
||||
</div>
|
||||
<div class="time">
|
||||
<label for="time">Hora</label>
|
||||
<input bind:value={$time} type="time" name="time" />
|
||||
</div>
|
||||
<div class="dayname">
|
||||
<label for="weekday">Día de la semana</label>
|
||||
<input bind:value={$weekday} type="text" name="weekday" placeholder="Ej. Lunes" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="address">Dirección</label>
|
||||
<textarea
|
||||
bind:value={$address}
|
||||
type="text"
|
||||
name="address"
|
||||
placeholder="Ej. Calle del Abeto, 4"
|
||||
/>
|
||||
<label for="city">Ciudad</label>
|
||||
<input bind:value={$city} type="text" name="" placeholder="Ej. Cádiz" />
|
||||
</div>
|
||||
|
||||
<div class="form-group ">
|
||||
<label for="organizedBy">Organiza:</label>
|
||||
<Organized />
|
||||
<label for="colabs">Colabora:</label>
|
||||
<Colabs />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<button on:click|preventDefault={downloadCanvas}>Descargar</button>
|
||||
<button on:click|preventDefault={saveCanvas}>Guardar</button>
|
||||
|
||||
<style>
|
||||
.form-group {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
form {
|
||||
margin: 1rem;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
display: block;
|
||||
}
|
||||
|
||||
input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
font-size: 0.8rem;
|
||||
font-family: sans-serif;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
textarea {
|
||||
display: block;
|
||||
width: 100%;
|
||||
font-size: 0.8rem;
|
||||
font-family: sans-serif;
|
||||
padding: 0.5rem;
|
||||
min-height: 3rem;
|
||||
line-height: 1.3rem;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
font-size: 0.8rem;
|
||||
font-family: monospace;
|
||||
margin: 0.25rem 0;
|
||||
}
|
||||
|
||||
#editor {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
button {
|
||||
margin: 1rem;
|
||||
padding: 0.5rem;
|
||||
font-size: 1.1rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.form-group.when {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
grid-gap: 0.25rem;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,108 @@
|
||||
<script>
|
||||
import { organizedBy } from '$lib/stores/store';
|
||||
|
||||
/** @type {HTMLInputElement} */ let fileinput;
|
||||
|
||||
const onFileSelected = (/** @type {Event} */ e) => {
|
||||
// @ts-ignore
|
||||
let image = e.target.files[0];
|
||||
let reader = new FileReader();
|
||||
reader.readAsDataURL(image);
|
||||
reader.onload = (e) => {
|
||||
// @ts-ignore
|
||||
$organizedBy = [...$organizedBy, { image: e.target.result, text: '' }];
|
||||
};
|
||||
};
|
||||
|
||||
const removeOrganizer = (/** @type {number} */ index) => {
|
||||
if (index > -1) {
|
||||
$organizedBy.splice(index, 1);
|
||||
$organizedBy = $organizedBy;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="form-organized-by">
|
||||
{#if $organizedBy[0]}
|
||||
{#each $organizedBy as organization, i}
|
||||
<div class="organization">
|
||||
<div
|
||||
class="remove"
|
||||
on:click|preventDefault={() => {
|
||||
removeOrganizer(i);
|
||||
}}
|
||||
>
|
||||
x
|
||||
</div>
|
||||
<img class="organized-logo" src={organization.image} alt={organization.text} />
|
||||
<input
|
||||
class="logo-title"
|
||||
type="text"
|
||||
bind:value={organization.text}
|
||||
placeholder="Nombre (opcional)"
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<button
|
||||
on:click|preventDefault={() => {
|
||||
fileinput.click();
|
||||
}}>Añadir logo</button
|
||||
>
|
||||
<input
|
||||
style="display:none;"
|
||||
type="file"
|
||||
accept=".jpg, .jpeg, .png"
|
||||
on:change={(e) => onFileSelected(e)}
|
||||
bind:this={fileinput}
|
||||
/>
|
||||
|
||||
<style>
|
||||
.form-organized-by {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, min-content);
|
||||
align-items: center;
|
||||
justify-content: start;
|
||||
}
|
||||
.organization {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-rows: min-content min-content;
|
||||
grid-gap: 0.5rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0.25rem 1rem;
|
||||
background-color: #ddd;
|
||||
padding: 0.25rem;
|
||||
}
|
||||
.organized-logo {
|
||||
max-height: 72px;
|
||||
max-width: 128px;
|
||||
margin: 0 auto;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.remove {
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
right: -0.25rem;
|
||||
color: red;
|
||||
cursor: pointer;
|
||||
background-color: white;
|
||||
padding: 0 0.25rem;
|
||||
}
|
||||
|
||||
input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
button {
|
||||
display: inline-block;
|
||||
padding: 0.5rem;
|
||||
cursor: pointer;
|
||||
margin: 1rem;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,23 @@
|
||||
<header>
|
||||
<h1><a href="/">Carteles dmd</a></h1>
|
||||
</header>
|
||||
|
||||
<style>
|
||||
header {
|
||||
max-height: 4rem;
|
||||
margin: 0;
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
h1 {
|
||||
text-align: left;
|
||||
font-weight: 700;
|
||||
color: firebrick;
|
||||
font-family: 'Alegreya Sans SC', sans-serif;
|
||||
}
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
font-size: 2rem;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,45 @@
|
||||
/** @return {string} */
|
||||
export const getMonthName = (/** @type {string} */ month) => {
|
||||
let res
|
||||
switch (month) {
|
||||
case "01":
|
||||
res = "enero";
|
||||
break;
|
||||
case "02":
|
||||
res = "febrero";
|
||||
break;
|
||||
case "03":
|
||||
res = "marzo";
|
||||
break;
|
||||
case "04":
|
||||
res = "abril";
|
||||
break;
|
||||
case "05":
|
||||
res = "mayo";
|
||||
break;
|
||||
case "06":
|
||||
res = "junio";
|
||||
break;
|
||||
case "07":
|
||||
res = "julio";
|
||||
break;
|
||||
case "08":
|
||||
res = "agosto";
|
||||
break;
|
||||
case "09":
|
||||
res = "septiembre";
|
||||
break;
|
||||
case "10":
|
||||
res = "octubre";
|
||||
break;
|
||||
case "11":
|
||||
res = "noviembre";
|
||||
break;
|
||||
case "12":
|
||||
res = "diciembre";
|
||||
break;
|
||||
default:
|
||||
res = "";
|
||||
}
|
||||
return res;
|
||||
}
|
@ -0,0 +1,193 @@
|
||||
<script>
|
||||
/** @type {number} */ export let templateIndex;
|
||||
/** @type {string} */ export let templateImage;
|
||||
|
||||
import { organizedBy, colabs, canvas } from '$lib/stores/store';
|
||||
|
||||
import Heading from '$lib/components/Heading.svelte';
|
||||
import Title from '$lib/components/Title.svelte';
|
||||
import Subtitle from '$lib/components/Subtitle.svelte';
|
||||
import Content from '$lib/components/Content.svelte';
|
||||
import Time from '$lib/components/Time.svelte';
|
||||
import Date from '$lib/components/Date.svelte';
|
||||
import Weekday from '$lib/components/Weekday.svelte';
|
||||
import Address from '$lib/components/Address.svelte';
|
||||
import Text from '$lib/components/Text.svelte';
|
||||
import City from '$lib/components/City.svelte';
|
||||
</script>
|
||||
|
||||
<div class="preview">
|
||||
<div class="result" bind:this={$canvas} style="background-image: url('/{templateImage}');">
|
||||
{#if templateIndex >= 0}
|
||||
<Heading {templateIndex} />
|
||||
<Title {templateIndex} />
|
||||
<Subtitle {templateIndex} />
|
||||
<Content {templateIndex} />
|
||||
<Date {templateIndex} />
|
||||
<Time {templateIndex} />
|
||||
<Weekday {templateIndex} />
|
||||
<Address {templateIndex} />
|
||||
<City {templateIndex} />
|
||||
{/if}
|
||||
|
||||
{#if $organizedBy[0] && $colabs[0]}
|
||||
<div class="orgcolab">
|
||||
<div class="org">Organiza:</div>
|
||||
<div class="colab">Colabora:</div>
|
||||
<div class="orgmixed">
|
||||
{#each $organizedBy as organization}
|
||||
<div class="organization">
|
||||
<img src={organization.image} alt={organization.text} />
|
||||
<div class="caption">{organization.text}</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="colabmixed">
|
||||
{#each $colabs as organization}
|
||||
<div class="organization">
|
||||
<img src={organization.image} alt={organization.text} />
|
||||
<div class="caption">{organization.text}</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $organizedBy[0] && !$colabs[0]}
|
||||
<div class="imagetitle">Organiza:</div>
|
||||
<div class="organized-by">
|
||||
{#each $organizedBy as organization}
|
||||
<div class="organization">
|
||||
<img src={organization.image} alt={organization.text} />
|
||||
<div class="caption">{organization.text}</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $colabs[0] && !$organizedBy[0]}
|
||||
<div class="imagetitle">Colabora:</div>
|
||||
<div class="organized-by">
|
||||
{#each $colabs as organization}
|
||||
<div class="organization">
|
||||
<img src={organization.image} alt={organization.text} />
|
||||
<div class="caption">{organization.text}</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="text">
|
||||
<Text />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.preview {
|
||||
padding: 8px;
|
||||
/* display: grid; */
|
||||
/* justify-content: center; */
|
||||
/* grid-template-columns: 1fr; */
|
||||
/* align-items: start; */
|
||||
}
|
||||
|
||||
.result {
|
||||
display: block;
|
||||
width: 600px;
|
||||
/* height: 8px; */
|
||||
aspect-ratio: 1/1.4142;
|
||||
/* min-height: 842px; */
|
||||
/* align-self: center; */
|
||||
/* width: auto; */
|
||||
background-size: cover;
|
||||
position: relative;
|
||||
/* padding: 1rem; */
|
||||
}
|
||||
|
||||
.imagetitle {
|
||||
position: absolute;
|
||||
top: 725px;
|
||||
left: 16px;
|
||||
font-size: 0.8rem;
|
||||
color: #444;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.organized-by {
|
||||
position: absolute;
|
||||
top: 745px;
|
||||
left: 1rem;
|
||||
right: 1rem;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(min(100%/3, max(64px, 100%/5)), 1fr));
|
||||
grid-template-rows: min-content;
|
||||
grid-auto-rows: auto;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.organization {
|
||||
margin: 0 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.organization img {
|
||||
max-height: 64px;
|
||||
max-width: 128px;
|
||||
margin: 0 auto;
|
||||
opacity: 100%;
|
||||
}
|
||||
|
||||
.caption {
|
||||
margin: 0.5rem 0;
|
||||
font-size: 0.7rem;
|
||||
font-family: sans-serif;
|
||||
text-transform: uppercase;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.orgcolab {
|
||||
position: absolute;
|
||||
top: 725px;
|
||||
left: 16px;
|
||||
height: 7rem;
|
||||
overflow: hidden;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-rows: min-content 1fr;
|
||||
grid-gap: 0.25rem;
|
||||
justify-content: start;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.org {
|
||||
grid-row: 1/2;
|
||||
grid-column: 1/2;
|
||||
font-size: 0.8rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.colab {
|
||||
font-size: 0.8rem;
|
||||
grid-row: 1/2;
|
||||
grid-column: 2/3;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.orgmixed {
|
||||
grid-row: 2/3;
|
||||
grid-column: 1/2;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.colabmixed {
|
||||
grid-row: 2/3;
|
||||
grid-column: 2/3;
|
||||
display: flex;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,16 @@
|
||||
import { writable } from 'svelte/store'
|
||||
|
||||
export const title = writable("");
|
||||
export const heading = writable("");
|
||||
export const subtitle = writable("");
|
||||
export const content = writable("");
|
||||
export const date = writable("");
|
||||
export const newdate = writable("");
|
||||
export const time = writable("");
|
||||
export const weekday = writable("");
|
||||
export const address = writable("");
|
||||
export const city = writable("");
|
||||
export const organizedBy = writable([]);
|
||||
export const colabs = writable([]);
|
||||
export const canvas = writable();
|
||||
export const multiplier = writable(1);
|
@ -0,0 +1,117 @@
|
||||
import '$lib/templates/templates'
|
||||
|
||||
/** @type {import('$lib/templates/templates').Template} */
|
||||
export const azul = {
|
||||
name: "azul",
|
||||
description: "Azul",
|
||||
image: "plantilla_azul.png",
|
||||
heading: {
|
||||
top: 415,
|
||||
height: 32,
|
||||
left: 420,
|
||||
right: 8,
|
||||
fontSize: 20,
|
||||
color: "white",
|
||||
fontFamily: "'Alegreya Sans SC', sans-serif",
|
||||
lineHeight: 32,
|
||||
textAlign: "left",
|
||||
fontWeight: 700
|
||||
},
|
||||
title: {
|
||||
top: 445,
|
||||
height: 120,
|
||||
left: 16,
|
||||
right: 16,
|
||||
fontSize: 40,
|
||||
color: "firebrick",
|
||||
fontFamily: "'Alegreya Sans SC', sans-serif",
|
||||
lineHeight: 40,
|
||||
textAlign: "center",
|
||||
fontWeight: 700
|
||||
|
||||
},
|
||||
subtitle: {
|
||||
top: 575,
|
||||
height: 32,
|
||||
left: 16,
|
||||
right: 16,
|
||||
fontSize: 24,
|
||||
color: "white",
|
||||
fontFamily: "'Alegreya Sans SC', sans-serif",
|
||||
lineHeight: 32,
|
||||
textAlign: "center",
|
||||
fontWeight: 400
|
||||
},
|
||||
content: {
|
||||
top: 630,
|
||||
height: 78,
|
||||
left: 8,
|
||||
right: 8,
|
||||
fontSize: 20,
|
||||
color: "white",
|
||||
fontFamily: "sans-serif",
|
||||
lineHeight: 21,
|
||||
textAlign: "center",
|
||||
fontWeight: 400
|
||||
},
|
||||
date: {
|
||||
top: 40,
|
||||
height: 32,
|
||||
left: 350,
|
||||
right: 30,
|
||||
fontSize: 28,
|
||||
color: "#fff",
|
||||
fontFamily: "'Alegreya Sans SC', sans-serif",
|
||||
lineHeight: 25,
|
||||
textAlign: "right",
|
||||
fontWeight: 700
|
||||
},
|
||||
time: {
|
||||
top: 70,
|
||||
height: 32,
|
||||
left: 350,
|
||||
right: 30,
|
||||
fontSize: 30,
|
||||
color: "#fff",
|
||||
fontFamily: "sans-serif",
|
||||
lineHeight: 32,
|
||||
textAlign: "right",
|
||||
fontWeight: 700
|
||||
},
|
||||
weekday: {
|
||||
top: 12,
|
||||
height: 32,
|
||||
left: 350,
|
||||
right: 30,
|
||||
fontSize: 20,
|
||||
color: "#fff",
|
||||
fontFamily: "'Alegreya Sans SC', sans-serif",
|
||||
lineHeight: 32,
|
||||
textAlign: "right",
|
||||
fontWeight: 700
|
||||
},
|
||||
address: {
|
||||
top: 12,
|
||||
height: 78,
|
||||
left: 30,
|
||||
right: 350,
|
||||
fontSize: 16,
|
||||
color: "white",
|
||||
fontFamily: "sans-serif",
|
||||
lineHeight: 20,
|
||||
textAlign: "left",
|
||||
fontWeight: 400
|
||||
},
|
||||
city: {
|
||||
top: 85,
|
||||
height: 70,
|
||||
left: 30,
|
||||
right: 410,
|
||||
fontSize: 18,
|
||||
color: "white",
|
||||
fontFamily: "sans-serif",
|
||||
lineHeight: 22,
|
||||
textAlign: "left",
|
||||
fontWeight: 400
|
||||
},
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
import '$lib/templates/templates'
|
||||
|
||||
/** @type {import('$lib/templates/templates').Template} */
|
||||
export const corazon = {
|
||||
name: "corazon",
|
||||
image: "corazon.png",
|
||||
heading: {
|
||||
top: 8,
|
||||
height: 32,
|
||||
left: 8,
|
||||
right: 100,
|
||||
fontSize: 32,
|
||||
color: "white",
|
||||
fontFamily: "'Alegreya Sans SC', sans-serif",
|
||||
lineHeight: 32,
|
||||
textAlign: "left",
|
||||
fontWeight: 700
|
||||
},
|
||||
title: {
|
||||
top: 88,
|
||||
height: 64,
|
||||
left: 8,
|
||||
right: 8,
|
||||
fontSize: 32,
|
||||
color: "white",
|
||||
fontFamily: "'Alegreya Sans SC', sans-serif",
|
||||
lineHeight: 32,
|
||||
textAlign: "center",
|
||||
fontWeight: 300
|
||||
|
||||
},
|
||||
subtitle: {
|
||||
top: 160,
|
||||
height: 48,
|
||||
left: 8,
|
||||
right: 8,
|
||||
fontSize: 48,
|
||||
color: "white",
|
||||
fontFamily: "'Alegreya Sans SC', sans-serif",
|
||||
lineHeight: 48,
|
||||
textAlign: "center",
|
||||
fontWeight: 700
|
||||
},
|
||||
content: {
|
||||
top: 480,
|
||||
height: 8.5,
|
||||
left: 1.5,
|
||||
right: 17,
|
||||
fontSize: 0,
|
||||
color: "#222",
|
||||
fontFamily: "sans-serif",
|
||||
lineHeight: 1.1,
|
||||
textAlign: "left",
|
||||
fontWeight: 700
|
||||
},
|
||||
date: {
|
||||
top: 540,
|
||||
height: 32,
|
||||
left: 128,
|
||||
right: 8,
|
||||
fontSize: 32,
|
||||
color: "#fff",
|
||||
fontFamily: "'Arial', sans-serif",
|
||||
lineHeight: 32,
|
||||
textAlign: "left",
|
||||
fontWeight: 700
|
||||
},
|
||||
time: {
|
||||
top: 600,
|
||||
height: 32,
|
||||
left: 128,
|
||||
right: 8,
|
||||
fontSize: 32,
|
||||
color: "#fff",
|
||||
fontFamily: "'Arial', sans-serif",
|
||||
lineHeight: 32,
|
||||
textAlign: "left",
|
||||
fontWeight: 700
|
||||
},
|
||||
weekday: {
|
||||
top: 8,
|
||||
height: 32,
|
||||
left: 8,
|
||||
right: 80,
|
||||
fontSize: 0,
|
||||
color: "#fff",
|
||||
fontFamily: "sans-serif",
|
||||
lineHeight: 1.8,
|
||||
textAlign: "left",
|
||||
fontWeight: 700
|
||||
},
|
||||
address: {
|
||||
top: 650,
|
||||
height: 48,
|
||||
left: 128,
|
||||
right: 8,
|
||||
fontSize: 24,
|
||||
color: "white",
|
||||
fontFamily: "'Alegreya Sans SC', sans-serif",
|
||||
lineHeight: 24,
|
||||
textAlign: "left",
|
||||
fontWeight: 400
|
||||
},
|
||||
city: {
|
||||
top: 8,
|
||||
height: 32,
|
||||
left: 80,
|
||||
right: 8,
|
||||
fontSize: 32,
|
||||
color: "white",
|
||||
fontFamily: "'Alegreya Sans SC', sans-serif",
|
||||
lineHeight: 36,
|
||||
textAlign: "right",
|
||||
fontWeight: 700
|
||||
},
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
import '$lib/templates/templates'
|
||||
|
||||
/** @type {import('$lib/templates/templates').Template} */
|
||||
export const posits = {
|
||||
name: "posits",
|
||||
image: "plantilla_posits.jpg",
|
||||
heading: {
|
||||
top: 100,
|
||||
height: 1.2,
|
||||
left: 8,
|
||||
right: 1,
|
||||
fontSize: 0,
|
||||
color: "white",
|
||||
fontFamily: "sans-serif",
|
||||
lineHeight: 1.1,
|
||||
textAlign: "left",
|
||||
fontWeight: 400
|
||||
},
|
||||
title: {
|
||||
top: 450,
|
||||
height: 32,
|
||||
left: 80,
|
||||
right: 80,
|
||||
fontSize: 32,
|
||||
color: "#000",
|
||||
fontFamily: "'Alegreya Sans SC', sans-serif",
|
||||
lineHeight: 38,
|
||||
textAlign: "center",
|
||||
fontWeight: 700
|
||||
},
|
||||
subtitle: {
|
||||
top: 300,
|
||||
height: 3,
|
||||
left: 8,
|
||||
right: 2,
|
||||
fontSize: 0,
|
||||
color: "firebrick",
|
||||
fontFamily: "'Arial', sans-serif",
|
||||
lineHeight: 1.5,
|
||||
textAlign: "center",
|
||||
fontWeight: 400
|
||||
},
|
||||
content: {
|
||||
top: 480,
|
||||
height: 8.5,
|
||||
left: 1.5,
|
||||
right: 17,
|
||||
fontSize: 0,
|
||||
color: "#222",
|
||||
fontFamily: "sans-serif",
|
||||
lineHeight: 1.1,
|
||||
textAlign: "left",
|
||||
fontWeight: 400
|
||||
},
|
||||
date: {
|
||||
top: 600,
|
||||
height: 48,
|
||||
left: 428,
|
||||
right: 32,
|
||||
fontSize: 20,
|
||||
color: "firebrick",
|
||||
fontFamily: "sans-serif",
|
||||
lineHeight: 22,
|
||||
textAlign: "center",
|
||||
fontWeight: 700
|
||||
},
|
||||
time: {
|
||||
top: 655,
|
||||
height: 32,
|
||||
left: 428,
|
||||
right: 32,
|
||||
fontSize: 32,
|
||||
color: "firebrick",
|
||||
fontFamily: "sans-serif",
|
||||
lineHeight: 32,
|
||||
textAlign: "center",
|
||||
fontWeight: 700
|
||||
},
|
||||
weekday: {
|
||||
top: 600,
|
||||
height: 5,
|
||||
left: 18.5,
|
||||
right: 5,
|
||||
fontSize: 20,
|
||||
color: "#fff",
|
||||
fontFamily: "sans-serif",
|
||||
lineHeight: 28,
|
||||
textAlign: "center",
|
||||
fontWeight: 700
|
||||
},
|
||||
address: {
|
||||
top: 555,
|
||||
height: 96,
|
||||
left: 32,
|
||||
right: 385,
|
||||
fontSize: 18,
|
||||
color: "#000",
|
||||
fontFamily: "'Arial', sans-serif",
|
||||
lineHeight: 24,
|
||||
textAlign: "center",
|
||||
fontWeight: 400
|
||||
},
|
||||
city: {
|
||||
top: 660,
|
||||
height: 48,
|
||||
left: 32,
|
||||
right: 385,
|
||||
fontSize: 24,
|
||||
color: "#000",
|
||||
fontFamily: "'Arial', sans-serif",
|
||||
lineHeight: 24,
|
||||
textAlign: "center",
|
||||
fontWeight: 700
|
||||
},
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
import { verde } from '$lib/templates/verde';
|
||||
import { azul } from '$lib/templates/azul';
|
||||
import { corazon } from '$lib/templates/corazon';
|
||||
import { posits } from '$lib/templates/posits';
|
||||
/**
|
||||
* @typedef {{
|
||||
* top: number;
|
||||
* height: number;
|
||||
* right: number;
|
||||
* left: number;
|
||||
* color: string;
|
||||
* fontSize: number;
|
||||
* fontFamily: string;
|
||||
* lineHeight: number;
|
||||
* textAlign: string;
|
||||
* fontWeight: number;
|
||||
* }} Element
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* name: string;
|
||||
* description: string;
|
||||
* image: string;
|
||||
* heading: Element;
|
||||
* title: Element;
|
||||
* subtitle: Element;
|
||||
* date: Element;
|
||||
* time: Element;
|
||||
* weekday: Element;
|
||||
* content: Element;
|
||||
* address: Element;
|
||||
* city: Element;
|
||||
* }} Template
|
||||
*/
|
||||
|
||||
/** @type Array<Template> */
|
||||
export const templates = [verde, azul, corazon, posits];
|
@ -0,0 +1,117 @@
|
||||
import '$lib/templates/templates'
|
||||
|
||||
/** @type {import('$lib/templates/templates').Template} */
|
||||
export const verde = {
|
||||
name: "verde",
|
||||
description: "Una plantilla con todos los elementos",
|
||||
image: "plantilla_verde.png",
|
||||
heading: {
|
||||
top: 100,
|
||||
height: 32,
|
||||
left: 140,
|
||||
right: 20,
|
||||
fontSize: 24,
|
||||
color: "white",
|
||||
fontFamily: "'Alegreya Sans SC', sans-serif",
|
||||
lineHeight: 32,
|
||||
textAlign: "left",
|
||||
fontWeight: 700
|
||||
},
|
||||
title: {
|
||||
top: 150,
|
||||
height: 86,
|
||||
left: 132,
|
||||
right: 16,
|
||||
fontSize: 40,
|
||||
color: "white",
|
||||
fontFamily: "'Alegreya Sans SC', sans-serif",
|
||||
lineHeight: 40,
|
||||
textAlign: "center",
|
||||
fontWeight: 700
|
||||
},
|
||||
subtitle: {
|
||||
top: 295,
|
||||
height: 82,
|
||||
left: 132,
|
||||
right: 16,
|
||||
fontSize: 18,
|
||||
color: "firebrick",
|
||||
fontFamily: "'Alegreya Sans SC', sans-serif",
|
||||
lineHeight: 20,
|
||||
textAlign: "center",
|
||||
fontWeight: 400
|
||||
},
|
||||
content: {
|
||||
top: 430,
|
||||
height: 164,
|
||||
left: 32,
|
||||
right: 320,
|
||||
fontSize: 14,
|
||||
color: "#222",
|
||||
fontFamily: "'Helvetica', sans-serif",
|
||||
lineHeight: 18,
|
||||
textAlign: "left",
|
||||
fontWeight: 400
|
||||
},
|
||||
date: {
|
||||
top: 628,
|
||||
height: 32,
|
||||
left: 275,
|
||||
right: 32,
|
||||
fontSize: 18,
|
||||
color: "#fff",
|
||||
fontFamily: "sans-serif",
|
||||
lineHeight: 32,
|
||||
textAlign: "center",
|
||||
fontWeight: 700
|
||||
},
|
||||
time: {
|
||||
top: 650,
|
||||
height: 32,
|
||||
left: 275,
|
||||
right: 32,
|
||||
fontSize: 25
|
||||
,
|
||||
color: "#fff",
|
||||
fontFamily: "sans-serif",
|
||||
lineHeight: 32,
|
||||
textAlign: "center",
|
||||
fontWeight: 700
|
||||
},
|
||||
weekday: {
|
||||
top: 605,
|
||||
height: 32,
|
||||
left: 245,
|
||||
right: 32,
|
||||
fontSize: 18,
|
||||
color: "#fff",
|
||||
fontFamily: "sans-serif",
|
||||
lineHeight: 32,
|
||||
textAlign: "center",
|
||||
fontWeight: 700
|
||||
},
|
||||
address: {
|
||||
top: 610,
|
||||
height: 70,
|
||||
left: 32,
|
||||
right: 300,
|
||||
fontSize: 16,
|
||||
color: "#91b756",
|
||||
fontFamily: "sans-serif",
|
||||
lineHeight: 22,
|
||||
textAlign: "left",
|
||||
fontWeight: 700
|
||||
},
|
||||
city: {
|
||||
top: 685,
|
||||
height: 24,
|
||||
left: 32,
|
||||
right: 300,
|
||||
fontSize: 24,
|
||||
color: "#91b756",
|
||||
fontFamily: "sans-serif",
|
||||
lineHeight: 24,
|
||||
textAlign: "left",
|
||||
fontWeight: 700
|
||||
},
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
<script>
|
||||
// import { multiplier } from '$lib/stores/store';
|
||||
import Header from '$lib/header/Header.svelte';
|
||||
import '../app.css';
|
||||
// import { browser } from '$app/environment';
|
||||
|
||||
// let browserType = '';
|
||||
|
||||
// if (browser) {
|
||||
// const agent = navigator.userAgent;
|
||||
// if (agent.match(/chrome|chromium|crios/i)) {
|
||||
// browserType = 'chrome';
|
||||
// $multiplier = 1;
|
||||
// } else if (agent.match(/firefox|fxios/i)) {
|
||||
// browserType = 'firefox';
|
||||
// $multiplier = 1;
|
||||
// } else if (agent.match(/safari/i)) {
|
||||
// browserType = 'safari';
|
||||
// } else if (agent.match(/opr\//i)) {
|
||||
// browserType = 'opera';
|
||||
// } else if (agent.match(/edg/i)) {
|
||||
// browserType = 'edge';
|
||||
// } else {
|
||||
// browserType = 'No browser detection';
|
||||
// }
|
||||
// }
|
||||
|
||||
// console.log('Browser is: ', browserType);
|
||||
</script>
|
||||
|
||||
<Header />
|
||||
<main>
|
||||
<slot />
|
||||
</main>
|
||||
|
||||
<style>
|
||||
main {
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin: 0 auto;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
@ -0,0 +1 @@
|
||||
export const prerender = true;
|
@ -0,0 +1,85 @@
|
||||
<script>
|
||||
import { templates } from "$lib/templates/templates";
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Carteles DMD</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Aplicación para la generación automática de carteles de actividades de DMD"
|
||||
/>
|
||||
</svelte:head>
|
||||
<section>
|
||||
<p>Elige plantilla</p>
|
||||
<div class="main">
|
||||
<ul>
|
||||
{#each templates as template}
|
||||
<li>
|
||||
<a href="/{template.name}" data-sveltekit-prefetch
|
||||
><img
|
||||
src="/{template.image}"
|
||||
alt="Plantilla en blanco estilo {template.name}"
|
||||
/>
|
||||
{#if template.description}
|
||||
<div class="caption">{@html template.description}</div>
|
||||
{:else}
|
||||
<div class="caption">{template.name}</div>
|
||||
{/if}
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
section {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
display: grid;
|
||||
min-height: 90vh;
|
||||
}
|
||||
|
||||
.main {
|
||||
display: grid;
|
||||
grid-auto-columns: 1fr;
|
||||
grid-auto-rows: 1fr;
|
||||
grid-gap: 1rem;
|
||||
padding: 1rem 0;
|
||||
}
|
||||
p {
|
||||
font-size: 1.5rem;
|
||||
color: #333;
|
||||
}
|
||||
ul {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
}
|
||||
li {
|
||||
line-height: 2rem;
|
||||
display: inline-block;
|
||||
padding: 0.25rem;
|
||||
background-color: rebeccapurple;
|
||||
box-shadow: 0 0 0.5rem 0 rgba(0, 0, 0, 0.7);
|
||||
margin: 0 0.5rem;
|
||||
border-radius: 0.5rem;
|
||||
align-self: center;
|
||||
}
|
||||
a {
|
||||
text-transform: capitalize;
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
font-weight: 400;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
img {
|
||||
max-height: 200px;
|
||||
border-radius: 0.25rem 0.25rem 0 0;
|
||||
}
|
||||
.caption {
|
||||
text-align: left;
|
||||
padding: 0.25rem;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,14 @@
|
||||
<script>
|
||||
import { page } from '$app/stores';
|
||||
</script>
|
||||
|
||||
{#if $page.error}
|
||||
<div class="error-message"><h1>{$page.status}: {$page.error.message}</h1></div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.error-message {
|
||||
max-width: 480px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,21 @@
|
||||
import { error } from '@sveltejs/kit';
|
||||
import { templates } from '$lib/templates/templates';
|
||||
|
||||
/** @type {import('./$types').PageLoad} */
|
||||
export function load(event) {
|
||||
|
||||
/** @type {string} */
|
||||
const slug = event.params.slug;
|
||||
|
||||
/** @type {number} */ const templateIndex = templates.map((e) => e.name).indexOf(slug);
|
||||
/** @type {string} */ const templateImage = templates[templateIndex].image;
|
||||
if (templateIndex !== -1) {
|
||||
return {
|
||||
slug,
|
||||
templateIndex,
|
||||
templateImage
|
||||
}
|
||||
}
|
||||
|
||||
throw error(404, "Not found");
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
<script>
|
||||
/**
|
||||
* Un objeto con los datos que llegan de la funcion load de +page.js
|
||||
* @typedef {{
|
||||
* slug: string;
|
||||
* templateIndex: number;
|
||||
* templateImage: string;
|
||||
* }}
|
||||
* LoadFunctionData
|
||||
*/
|
||||
|
||||
/** @type LoadFunctionData */ export let data;
|
||||
|
||||
import Form from '$lib/form/Form.svelte';
|
||||
import Preview from '$lib/preview/Preview.svelte';
|
||||
|
||||
const templateIndex = data.templateIndex;
|
||||
const templateImage = data.templateImage;
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Carteles DMD - Plantilla {data.slug}</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="main">
|
||||
<section class="form"><Form /></section>
|
||||
|
||||
{#if templateIndex !== undefined}
|
||||
<section class="preview">
|
||||
<Preview {templateIndex} {templateImage} />
|
||||
</section>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.main {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 616px;
|
||||
grid-gap: 0.5rem;
|
||||
min-height: calc(100vh - 5rem);
|
||||
}
|
||||
|
||||
.form {
|
||||
grid-column: 1/2;
|
||||
background-color: rgb(236, 240, 240);
|
||||
}
|
||||
|
||||
.preview {
|
||||
grid-column: 2/3;
|
||||
background-color: rgb(240, 240, 240);
|
||||
}
|
||||
</style>
|
@ -0,0 +1,33 @@
|
||||
import { error } from '@sveltejs/kit';
|
||||
import fs from 'fs';
|
||||
import stream from 'stream';
|
||||
// import { canvas } from '$lib/stores/store';
|
||||
import { getAllPostersFromDB, addPosterToDB } from '$lib/db/utils';
|
||||
|
||||
/** @type {import('./$types').PageServerLoad} */
|
||||
export const load = () => {
|
||||
const posters = getAllPostersFromDB();
|
||||
if (posters !== undefined) {
|
||||
return {
|
||||
posters
|
||||
}
|
||||
}
|
||||
throw error(404, "error");
|
||||
};
|
||||
|
||||
/** @type {import('./$types').Actions} */
|
||||
export const actions = {
|
||||
default: async ({ request }) => {
|
||||
console.log("Estoy aquí");
|
||||
const path = crypto.randomUUID();
|
||||
const data = await request.formData();
|
||||
const image = data.get("image")?.toString();
|
||||
const content = data.get("content");
|
||||
fs.writeFileSync(`./static/${path}.png`, image, 'base64url');
|
||||
// const savetoDB = addPosterToDB(image, content);
|
||||
return {
|
||||
success: true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
<script>
|
||||
/** @type {import('./$types').PageData} */
|
||||
export let data;
|
||||
|
||||
$: posters = data.posters;
|
||||
|
||||
/** @type {Blob} */ let image = new Blob();
|
||||
/** @type {string} */ let template = '';
|
||||
/** @type {string} */ let content = '';
|
||||
</script>
|
||||
|
||||
<h1>Test</h1>
|
||||
<ul>
|
||||
{#each posters as poster}
|
||||
<li>{poster.name}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
<form method="POST">
|
||||
<input name="template" bind:value={template} placeholder="Plantilla" type="text" />
|
||||
<input name="content" bind:value={content} placeholder="Contenido" type="text" />
|
||||
<input name="image" bind:value={image} type="file" />
|
||||
<button>Send</button>
|
||||
</form>
|
||||
|
||||
<style>
|
||||
input {
|
||||
display: block;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,66 @@
|
||||
import { error } from '@sveltejs/kit';
|
||||
import { api } from './api';
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* uid: string;
|
||||
* created_at: Date;
|
||||
* text: string;
|
||||
* done: boolean;
|
||||
* pending_delete: boolean;
|
||||
* }} Todo
|
||||
*/
|
||||
|
||||
/** @type {import('./$types').PageServerLoad} */
|
||||
export const load = async ({ locals }) => {
|
||||
// locals.userid comes from src/hooks.js
|
||||
const response = await api('GET', `todos/${locals.userid}`);
|
||||
|
||||
if (response.status === 404) {
|
||||
// user hasn't created a todo list.
|
||||
// start with an empty array
|
||||
return {
|
||||
/** @type {Todo[]} */
|
||||
todos: []
|
||||
};
|
||||
}
|
||||
|
||||
if (response.status === 200) {
|
||||
return {
|
||||
/** @type {Todo[]} */
|
||||
todos: await response.json()
|
||||
};
|
||||
}
|
||||
|
||||
throw error(response.status);
|
||||
};
|
||||
|
||||
/** @type {import('./$types').Actions} */
|
||||
export const actions = {
|
||||
add: async ({ request, locals }) => {
|
||||
const form = await request.formData();
|
||||
|
||||
await api('POST', `todos/${locals.userid}`, {
|
||||
text: form.get('text')
|
||||
});
|
||||
},
|
||||
edit: async ({ request, locals }) => {
|
||||
const form = await request.formData();
|
||||
|
||||
await api('PATCH', `todos/${locals.userid}/${form.get('uid')}`, {
|
||||
text: form.get('text')
|
||||
});
|
||||
},
|
||||
toggle: async ({ request, locals }) => {
|
||||
const form = await request.formData();
|
||||
|
||||
await api('PATCH', `todos/${locals.userid}/${form.get('uid')}`, {
|
||||
done: !!form.get('done')
|
||||
});
|
||||
},
|
||||
delete: async ({ request, locals }) => {
|
||||
const form = await request.formData();
|
||||
|
||||
await api('DELETE', `todos/${locals.userid}/${form.get('uid')}`);
|
||||
}
|
||||
};
|
@ -0,0 +1,182 @@
|
||||
<script>
|
||||
import { enhance } from '$app/forms';
|
||||
import { invalidateAll } from '$app/navigation';
|
||||
import { scale } from 'svelte/transition';
|
||||
import { flip } from 'svelte/animate';
|
||||
|
||||
/** @type {import('./$types').PageData} */
|
||||
export let data;
|
||||
$: todos = data.todos;
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Todos</title>
|
||||
<meta name="description" content="A todo list app" />
|
||||
</svelte:head>
|
||||
|
||||
<div class="todos">
|
||||
<h1>Todos</h1>
|
||||
|
||||
<form
|
||||
class="new"
|
||||
action="/todos?/add"
|
||||
method="post"
|
||||
use:enhance={() => {
|
||||
return ({ form, result }) => {
|
||||
if (result.type === 'success') {
|
||||
form.reset();
|
||||
invalidateAll();
|
||||
}
|
||||
};
|
||||
}}
|
||||
>
|
||||
<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?/toggle"
|
||||
method="post"
|
||||
use:enhance={({ data }) => {
|
||||
todo.done = !!data.get('done');
|
||||
}}
|
||||
>
|
||||
<input type="hidden" name="uid" value={todo.uid} />
|
||||
<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?/edit" method="post" use:enhance>
|
||||
<input type="hidden" name="uid" value={todo.uid} />
|
||||
<input aria-label="Edit todo" type="text" name="text" value={todo.text} />
|
||||
<button class="save" aria-label="Save todo" />
|
||||
</form>
|
||||
|
||||
<form
|
||||
action="/todos?/delete"
|
||||
method="post"
|
||||
use:enhance={() => {
|
||||
todo.pending_delete = true;
|
||||
}}
|
||||
>
|
||||
<input type="hidden" name="uid" value={todo.uid} />
|
||||
<button class="delete" aria-label="Delete todo" disabled={todo.pending_delete} />
|
||||
</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 {
|
||||
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(0 0 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>
|
@ -0,0 +1,25 @@
|
||||
/*
|
||||
This module is used by the /todos endpoint to
|
||||
make calls to api.svelte.dev, which stores todos
|
||||
for each user.
|
||||
|
||||
(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';
|
||||
|
||||
/**
|
||||
* @param {string} method
|
||||
* @param {string} resource
|
||||
* @param {Record<string, unknown>} [data]
|
||||
*/
|
||||
export function api(method, resource, data) {
|
||||
return fetch(`${base}/${resource}`, {
|
||||
method,
|
||||
headers: {
|
||||
'content-type': 'application/json'
|
||||
},
|
||||
body: data && JSON.stringify(data)
|
||||
});
|
||||
}
|
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 38 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 264 KiB |
After Width: | Height: | Size: 183 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 769 B |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 890 KiB |
After Width: | Height: | Size: 109 KiB |
After Width: | Height: | Size: 260 KiB |
After Width: | Height: | Size: 3.3 MiB |
@ -0,0 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
@ -0,0 +1 @@
|
||||
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
|
@ -0,0 +1,10 @@
|
||||
import adapter from '@sveltejs/adapter-auto';
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
const config = {
|
||||
kit: {
|
||||
adapter: adapter()
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
@ -0,0 +1,8 @@
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
|
||||
/** @type {import('vite').UserConfig} */
|
||||
const config = {
|
||||
plugins: [sveltekit()]
|
||||
};
|
||||
|
||||
export default config;
|