feat: move to svelte kit
This commit is contained in:
parent
adad65f3c9
commit
7cc339fcd1
|
@ -0,0 +1,20 @@
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
|
||||||
|
plugins: ['svelte3', '@typescript-eslint'],
|
||||||
|
ignorePatterns: ['*.cjs'],
|
||||||
|
overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }],
|
||||||
|
settings: {
|
||||||
|
'svelte3/typescript': () => require('typescript')
|
||||||
|
},
|
||||||
|
parserOptions: {
|
||||||
|
sourceType: 'module',
|
||||||
|
ecmaVersion: 2019
|
||||||
|
},
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
es2017: true,
|
||||||
|
node: true
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,15 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
env: {
|
|
||||||
browser: true,
|
|
||||||
es2021: true,
|
|
||||||
},
|
|
||||||
extends: [
|
|
||||||
'airbnb-base',
|
|
||||||
],
|
|
||||||
parserOptions: {
|
|
||||||
ecmaVersion: 12,
|
|
||||||
sourceType: 'module',
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -1,5 +1,4 @@
|
||||||
node_modules
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
dist
|
node_modules
|
||||||
dist-ssr
|
/.svelte-kit
|
||||||
*.local
|
/package
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
engine-strict=true
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"useTabs": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"printWidth": 100
|
||||||
|
}
|
|
@ -1,21 +1,38 @@
|
||||||
# Jokes Bapak2 Client
|
# create-svelte
|
||||||
|
|
||||||
Still work in progress
|
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte);
|
||||||
|
|
||||||
## Development
|
## Creating a project
|
||||||
|
|
||||||
Please install Yarn first by doing `npm i -g yarn`
|
If you're seeing this, you've probably already done this step. Congrats!
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# install dependency
|
# create a new project in the current directory
|
||||||
$ yarn install
|
npm init svelte@next
|
||||||
|
|
||||||
# run the local server
|
# create a new project in my-app
|
||||||
$ yarn dev
|
npm init svelte@next my-app
|
||||||
|
|
||||||
# build the package
|
|
||||||
$ yarn build
|
|
||||||
|
|
||||||
# preview it before deployment
|
|
||||||
$ yarn preview
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> Note: the `@next` is temporary
|
||||||
|
|
||||||
|
## Developing
|
||||||
|
|
||||||
|
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# or start the server and open the app in a new browser tab
|
||||||
|
npm run dev -- --open
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
Before creating a production version of your app, install an [adapter](https://kit.svelte.dev/docs#adapters) for your target environment. Then:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
> You can preview the built app with `npm run preview`, regardless of whether you installed an adapter. This should _not_ be used to serve your app in production.
|
||||||
|
|
|
@ -1,22 +1,46 @@
|
||||||
{
|
{
|
||||||
"name": "jokes-bapak2-api",
|
"name": "jokesbapak2-client",
|
||||||
"version": "0.0.0",
|
"version": "0.0.1",
|
||||||
|
"license": "GPL-3.0",
|
||||||
|
"contributors": [
|
||||||
|
{
|
||||||
|
"name": "Reinaldy Rafli",
|
||||||
|
"email": "aldy505@tutanota.com",
|
||||||
|
"url": "https://github.com/aldy505"
|
||||||
|
}
|
||||||
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "svelte-kit dev",
|
||||||
"build": "vite build",
|
"build": "svelte-kit build",
|
||||||
"serve": "vite preview",
|
"preview": "svelte-kit preview",
|
||||||
"lint": "eslint --fix --ext .js --ignore-path .gitignore ."
|
"check": "svelte-check --tsconfig ./tsconfig.json",
|
||||||
},
|
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
|
||||||
"dependencies": {
|
"lint": "eslint --fix --ext .svelte,.js,.ts --ignore-path .gitignore .",
|
||||||
"@rollup/plugin-eslint": "^8.0.1",
|
"format": "prettier --write --ignore-path .gitignore --plugin-search-dir=. \"./**/*.(ts|json|js|svelte)\""
|
||||||
"tailwindcss": "^2.1.2",
|
|
||||||
"vite": "^2.2.3"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"autoprefixer": "^10.2.5",
|
"@sveltejs/kit": "next",
|
||||||
"eslint": "^7.25.0",
|
"@types/cookie": "^0.4.0",
|
||||||
"eslint-config-airbnb-base": "^14.2.1",
|
"@typescript-eslint/eslint-plugin": "^4.19.0",
|
||||||
"eslint-plugin-import": "^2.22.1",
|
"@typescript-eslint/parser": "^4.19.0",
|
||||||
"postcss": "^8.2.13"
|
"cssnano": "^5.0.6",
|
||||||
|
"eslint": "^7.22.0",
|
||||||
|
"eslint-config-prettier": "^8.1.0",
|
||||||
|
"eslint-plugin-svelte3": "^3.2.0",
|
||||||
|
"prettier": "~2.2.1",
|
||||||
|
"prettier-plugin-svelte": "^2.2.0",
|
||||||
|
"svelte": "^3.34.0",
|
||||||
|
"svelte-check": "^2.0.0",
|
||||||
|
"svelte-preprocess": "^4.7.3",
|
||||||
|
"svelte-windicss-preprocess": "^4.0.12",
|
||||||
|
"tailwindcss": "^2.2.4",
|
||||||
|
"tslib": "^2.0.0",
|
||||||
|
"typescript": "^4.0.0"
|
||||||
|
},
|
||||||
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"@fontsource/fira-mono": "^4.2.2",
|
||||||
|
"@lukeed/uuid": "^2.0.0",
|
||||||
|
"cookie": "^0.4.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
const tailwindcss = require("tailwindcss");
|
||||||
|
const autoprefixer = require("autoprefixer");
|
||||||
|
const cssnano = require("cssnano");
|
||||||
|
|
||||||
|
const mode = process.env.NODE_ENV;
|
||||||
|
const dev = mode === "development";
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
plugins: [
|
||||||
|
//Some plugins, like postcss-nested, need to run before Tailwind,
|
||||||
|
tailwindcss(),
|
||||||
|
//But others, like autoprefixer, need to run after,
|
||||||
|
autoprefixer(),
|
||||||
|
!dev && cssnano({
|
||||||
|
preset: "default",
|
||||||
|
})
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = config;
|
|
@ -1,6 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
plugins: {
|
|
||||||
tailwindcss: {},
|
|
||||||
autoprefixer: {},
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -1,15 +0,0 @@
|
||||||
<svg width="410" height="404" viewBox="0 0 410 404" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M399.641 59.5246L215.643 388.545C211.844 395.338 202.084 395.378 198.228 388.618L10.5817 59.5563C6.38087 52.1896 12.6802 43.2665 21.0281 44.7586L205.223 77.6824C206.398 77.8924 207.601 77.8904 208.776 77.6763L389.119 44.8058C397.439 43.2894 403.768 52.1434 399.641 59.5246Z" fill="url(#paint0_linear)"/>
|
|
||||||
<path d="M292.965 1.5744L156.801 28.2552C154.563 28.6937 152.906 30.5903 152.771 32.8664L144.395 174.33C144.198 177.662 147.258 180.248 150.51 179.498L188.42 170.749C191.967 169.931 195.172 173.055 194.443 176.622L183.18 231.775C182.422 235.487 185.907 238.661 189.532 237.56L212.947 230.446C216.577 229.344 220.065 232.527 219.297 236.242L201.398 322.875C200.278 328.294 207.486 331.249 210.492 326.603L212.5 323.5L323.454 102.072C325.312 98.3645 322.108 94.137 318.036 94.9228L279.014 102.454C275.347 103.161 272.227 99.746 273.262 96.1583L298.731 7.86689C299.767 4.27314 296.636 0.855181 292.965 1.5744Z" fill="url(#paint1_linear)"/>
|
|
||||||
<defs>
|
|
||||||
<linearGradient id="paint0_linear" x1="6.00017" y1="32.9999" x2="235" y2="344" gradientUnits="userSpaceOnUse">
|
|
||||||
<stop stop-color="#41D1FF"/>
|
|
||||||
<stop offset="1" stop-color="#BD34FE"/>
|
|
||||||
</linearGradient>
|
|
||||||
<linearGradient id="paint1_linear" x1="194.651" y1="8.81818" x2="236.076" y2="292.989" gradientUnits="userSpaceOnUse">
|
|
||||||
<stop stop-color="#FFEA83"/>
|
|
||||||
<stop offset="0.0833333" stop-color="#FFDD35"/>
|
|
||||||
<stop offset="1" stop-color="#FFA800"/>
|
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 1.5 KiB |
|
@ -0,0 +1,13 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="icon" href="/favicon.png" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
|
||||||
|
%svelte.head%
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="svelte">%svelte.body%</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,110 @@
|
||||||
|
/* Write your global styles here, in PostCSS syntax */
|
||||||
|
|
||||||
|
@import '@fontsource/fira-mono';
|
||||||
|
|
||||||
|
:root {
|
||||||
|
font-family: Arial, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
|
||||||
|
Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||||
|
--font-mono: 'Fira Mono', monospace;
|
||||||
|
--pure-white: #ffffff;
|
||||||
|
--primary-color: #b9c6d2;
|
||||||
|
--secondary-color: #d0dde9;
|
||||||
|
--tertiary-color: #edf0f8;
|
||||||
|
--accent-color: #ff3e00;
|
||||||
|
--heading-color: rgba(0, 0, 0, 0.7);
|
||||||
|
--text-color: #444444;
|
||||||
|
--background-without-opacity: rgba(255, 255, 255, 0.7);
|
||||||
|
--column-width: 42rem;
|
||||||
|
--column-margin-top: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
min-height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
background: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
var(--primary-color) 0%,
|
||||||
|
var(--secondary-color) 10.45%,
|
||||||
|
var(--tertiary-color) 41.35%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
body::before {
|
||||||
|
content: '';
|
||||||
|
width: 80vw;
|
||||||
|
height: 100vh;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 10vw;
|
||||||
|
z-index: -1;
|
||||||
|
background: radial-gradient(
|
||||||
|
50% 50% at 50% 50%,
|
||||||
|
var(--pure-white) 0%,
|
||||||
|
rgba(255, 255, 255, 0) 100%
|
||||||
|
);
|
||||||
|
opacity: 0.05;
|
||||||
|
}
|
||||||
|
|
||||||
|
#svelte {
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
p {
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--heading-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--accent-color);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2rem;
|
||||||
|
margin-bottom: 0 0 1em 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
font-size: 16px;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
background-color: rgba(255, 255, 255, 0.45);
|
||||||
|
border-radius: 3px;
|
||||||
|
box-shadow: 2px 2px 6px rgb(255 255 255 / 25%);
|
||||||
|
padding: 0.5em;
|
||||||
|
overflow-x: auto;
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
button {
|
||||||
|
font-size: inherit;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:focus:not(:focus-visible) {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 720px) {
|
||||||
|
h1 {
|
||||||
|
font-size: 2.4rem;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
/// <reference types="@sveltejs/kit" />
|
|
@ -0,0 +1,23 @@
|
||||||
|
import cookie from 'cookie';
|
||||||
|
import { v4 as uuid } from '@lukeed/uuid';
|
||||||
|
import type { Handle } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
export const handle: Handle = async ({ request, resolve }) => {
|
||||||
|
const cookies = cookie.parse(request.headers.cookie || '');
|
||||||
|
request.locals.userid = cookies.userid || uuid();
|
||||||
|
|
||||||
|
// TODO https://github.com/sveltejs/kit/issues/1046
|
||||||
|
if (request.query.has('_method')) {
|
||||||
|
request.method = request.query.get('_method').toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await resolve(request);
|
||||||
|
|
||||||
|
if (!cookies.userid) {
|
||||||
|
// if this is the first time the user has visited this app,
|
||||||
|
// set a cookie so that we recognise them when they return
|
||||||
|
response.headers['set-cookie'] = `userid=${request.locals.userid}; Path=/; HttpOnly`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
};
|
|
@ -1,13 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<link rel="icon" type="image/svg+xml" href="favicon.svg" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>Vite App</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="app" class="container mx-auto"></div>
|
|
||||||
<script type="module" src="/main.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { spring } from 'svelte/motion';
|
||||||
|
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
const displayed_count = spring();
|
||||||
|
$: displayed_count.set(count);
|
||||||
|
$: offset = modulo($displayed_count, 1);
|
||||||
|
|
||||||
|
function modulo(n: number, m: number) {
|
||||||
|
// handle negative numbers
|
||||||
|
return ((n % m) + m) % m;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="counter">
|
||||||
|
<button on:click={() => (count -= 1)} aria-label="Decrease the counter by one">
|
||||||
|
<svg aria-hidden="true" viewBox="0 0 1 1">
|
||||||
|
<path d="M0,0.5 L1,0.5" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="counter-viewport">
|
||||||
|
<div class="counter-digits" style="transform: translate(0, {100 * offset}%)">
|
||||||
|
<strong style="top: -100%" aria-hidden="true">{Math.floor($displayed_count + 1)}</strong>
|
||||||
|
<strong>{Math.floor($displayed_count)}</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button on:click={() => (count += 1)} aria-label="Increase the counter by one">
|
||||||
|
<svg aria-hidden="true" viewBox="0 0 1 1">
|
||||||
|
<path d="M0,0.5 L1,0.5 M0.5,0 L0.5,1" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.counter {
|
||||||
|
display: flex;
|
||||||
|
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.counter button {
|
||||||
|
width: 2em;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: 0;
|
||||||
|
background-color: transparent;
|
||||||
|
color: var(--text-color);
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.counter button:hover {
|
||||||
|
background-color: var(--secondary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 25%;
|
||||||
|
height: 25%;
|
||||||
|
}
|
||||||
|
|
||||||
|
path {
|
||||||
|
vector-effect: non-scaling-stroke;
|
||||||
|
stroke-width: 2px;
|
||||||
|
stroke: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.counter-viewport {
|
||||||
|
width: 8em;
|
||||||
|
height: 4em;
|
||||||
|
overflow: hidden;
|
||||||
|
text-align: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.counter-viewport strong {
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--accent-color);
|
||||||
|
font-size: 4rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.counter-digits {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,120 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
import logo from './svelte-logo.svg';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<header>
|
||||||
|
<div class="corner">
|
||||||
|
<a href="https://kit.svelte.dev">
|
||||||
|
<img src={logo} alt="SvelteKit" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<nav>
|
||||||
|
<svg viewBox="0 0 2 3" aria-hidden="true">
|
||||||
|
<path d="M0,0 L1,2 C1.5,3 1.5,3 2,3 L2,0 Z" />
|
||||||
|
</svg>
|
||||||
|
<ul>
|
||||||
|
<li class:active={$page.path === '/'}><a sveltekit:prefetch href="/">Home</a></li>
|
||||||
|
<li class:active={$page.path === '/about'}><a sveltekit:prefetch href="/about">About</a></li>
|
||||||
|
<li class:active={$page.path === '/todos'}><a sveltekit:prefetch href="/todos">Todos</a></li>
|
||||||
|
</ul>
|
||||||
|
<svg viewBox="0 0 2 3" aria-hidden="true">
|
||||||
|
<path d="M0,0 L0,3 C0.5,3 0.5,3 1,2 L2,0 Z" />
|
||||||
|
</svg>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="corner">
|
||||||
|
<!-- TODO put something else here? github link? -->
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.corner {
|
||||||
|
width: 3em;
|
||||||
|
height: 3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.corner a {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.corner img {
|
||||||
|
width: 2em;
|
||||||
|
height: 2em;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
--background: rgba(255, 255, 255, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 2em;
|
||||||
|
height: 3em;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
path {
|
||||||
|
fill: var(--background);
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
position: relative;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
height: 3em;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
list-style: none;
|
||||||
|
background: var(--background);
|
||||||
|
background-size: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
li.active::before {
|
||||||
|
--size: 6px;
|
||||||
|
content: '';
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: calc(50% - var(--size));
|
||||||
|
border: var(--size) solid transparent;
|
||||||
|
border-top: var(--size) solid var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
nav a {
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 1em;
|
||||||
|
color: var(--heading-color);
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 10%;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color 0.2s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: var(--accent-color);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.1566,22.8189c-10.4-14.8851-30.94-19.2971-45.7914-9.8348L22.2825,29.6078A29.9234,29.9234,0,0,0,8.7639,49.6506a31.5136,31.5136,0,0,0,3.1076,20.2318A30.0061,30.0061,0,0,0,7.3953,81.0653a31.8886,31.8886,0,0,0,5.4473,24.1157c10.4022,14.8865,30.9423,19.2966,45.7914,9.8348L84.7167,98.3921A29.9177,29.9177,0,0,0,98.2353,78.3493,31.5263,31.5263,0,0,0,95.13,58.117a30,30,0,0,0,4.4743-11.1824,31.88,31.88,0,0,0-5.4473-24.1157" style="fill:#ff3e00"/><path d="M45.8171,106.5815A20.7182,20.7182,0,0,1,23.58,98.3389a19.1739,19.1739,0,0,1-3.2766-14.5025,18.1886,18.1886,0,0,1,.6233-2.4357l.4912-1.4978,1.3363.9815a33.6443,33.6443,0,0,0,10.203,5.0978l.9694.2941-.0893.9675a5.8474,5.8474,0,0,0,1.052,3.8781,6.2389,6.2389,0,0,0,6.6952,2.485,5.7449,5.7449,0,0,0,1.6021-.7041L69.27,76.281a5.4306,5.4306,0,0,0,2.4506-3.631,5.7948,5.7948,0,0,0-.9875-4.3712,6.2436,6.2436,0,0,0-6.6978-2.4864,5.7427,5.7427,0,0,0-1.6.7036l-9.9532,6.3449a19.0329,19.0329,0,0,1-5.2965,2.3259,20.7181,20.7181,0,0,1-22.2368-8.2427,19.1725,19.1725,0,0,1-3.2766-14.5024,17.9885,17.9885,0,0,1,8.13-12.0513L55.8833,23.7472a19.0038,19.0038,0,0,1,5.3-2.3287A20.7182,20.7182,0,0,1,83.42,29.6611a19.1739,19.1739,0,0,1,3.2766,14.5025,18.4,18.4,0,0,1-.6233,2.4357l-.4912,1.4978-1.3356-.98a33.6175,33.6175,0,0,0-10.2037-5.1l-.9694-.2942.0893-.9675a5.8588,5.8588,0,0,0-1.052-3.878,6.2389,6.2389,0,0,0-6.6952-2.485,5.7449,5.7449,0,0,0-1.6021.7041L37.73,51.719a5.4218,5.4218,0,0,0-2.4487,3.63,5.7862,5.7862,0,0,0,.9856,4.3717,6.2437,6.2437,0,0,0,6.6978,2.4864,5.7652,5.7652,0,0,0,1.602-.7041l9.9519-6.3425a18.978,18.978,0,0,1,5.2959-2.3278,20.7181,20.7181,0,0,1,22.2368,8.2427,19.1725,19.1725,0,0,1,3.2766,14.5024,17.9977,17.9977,0,0,1-8.13,12.0532L51.1167,104.2528a19.0038,19.0038,0,0,1-5.3,2.3287" style="fill:#fff"/></svg>
|
After Width: | Height: | Size: 1.8 KiB |
|
@ -0,0 +1,60 @@
|
||||||
|
// this action (https://svelte.dev/tutorial/actions) allows us to
|
||||||
|
// progressively enhance a <form> that already works without JS
|
||||||
|
export function enhance(
|
||||||
|
form: HTMLFormElement,
|
||||||
|
{
|
||||||
|
pending,
|
||||||
|
error,
|
||||||
|
result
|
||||||
|
}: {
|
||||||
|
pending?: (data: FormData, form: HTMLFormElement) => void;
|
||||||
|
error?: (res: Response, error: Error, form: HTMLFormElement) => void;
|
||||||
|
result: (res: Response, form: HTMLFormElement) => void;
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
let current_token: {};
|
||||||
|
|
||||||
|
async function handle_submit(e: Event) {
|
||||||
|
const token = (current_token = {});
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const body = new FormData(form);
|
||||||
|
|
||||||
|
if (pending) pending(body, form);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(form.action, {
|
||||||
|
method: form.method,
|
||||||
|
headers: {
|
||||||
|
accept: 'application/json'
|
||||||
|
},
|
||||||
|
body
|
||||||
|
});
|
||||||
|
|
||||||
|
if (token !== current_token) return;
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
result(res, form);
|
||||||
|
} else if (error) {
|
||||||
|
error(res, null, form);
|
||||||
|
} else {
|
||||||
|
console.error(await res.text());
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (error) {
|
||||||
|
error(null, e, form);
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
form.addEventListener('submit', handle_submit);
|
||||||
|
|
||||||
|
return {
|
||||||
|
destroy() {
|
||||||
|
form.removeEventListener('submit', handle_submit);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
/**
|
||||||
|
* Can be made globally available by placing this
|
||||||
|
* inside `global.d.ts` and removing `export` keyword
|
||||||
|
*/
|
||||||
|
export interface Locals {
|
||||||
|
userid: string;
|
||||||
|
}
|
|
@ -1,6 +0,0 @@
|
||||||
import './style.css';
|
|
||||||
|
|
||||||
document.querySelector('#app').innerHTML = `
|
|
||||||
<h1 class="text-3xl">Hello Vite!</h1>
|
|
||||||
<a href="https://vitejs.dev/guide/features.html" target="_blank">Documentation</a>
|
|
||||||
`;
|
|
|
@ -1,40 +0,0 @@
|
||||||
function socialTags({
|
|
||||||
title, description, url, image, keywords, author,
|
|
||||||
}) {
|
|
||||||
const meta = [];
|
|
||||||
if (title) {
|
|
||||||
meta.push({ property: 'og:title', content: title });
|
|
||||||
meta.push({ name: 'twitter:title', content: title });
|
|
||||||
}
|
|
||||||
if (description) {
|
|
||||||
meta.push({ property: 'og:description', content: description });
|
|
||||||
meta.push({ name: 'twitter:description', content: description });
|
|
||||||
}
|
|
||||||
if (url) {
|
|
||||||
meta.push({ property: 'og:url', content: url });
|
|
||||||
meta.push({ name: 'twitter:url', content: url });
|
|
||||||
}
|
|
||||||
if (image) {
|
|
||||||
meta.push({ property: 'og:image', content: image });
|
|
||||||
meta.push({ name: 'twitter:image', content: image });
|
|
||||||
}
|
|
||||||
if (keywords) {
|
|
||||||
meta.push({ name: 'keywords', content: keywords });
|
|
||||||
}
|
|
||||||
if (author) {
|
|
||||||
meta.push({ property: 'og:author', content: author });
|
|
||||||
meta.push({ name: 'twitter:author', content: author });
|
|
||||||
}
|
|
||||||
return meta;
|
|
||||||
}
|
|
||||||
|
|
||||||
function injectToHead(document, headItems) {
|
|
||||||
const { head } = document;
|
|
||||||
for (let i = 0; i < headItems.length; i += 1) {
|
|
||||||
const meta = document.createElement('meta');
|
|
||||||
meta.push(headItems[i]);
|
|
||||||
head.appendChild(meta);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default { injectToHead, socialTags };
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import Header from '$lib/Header/index.svelte';
|
||||||
|
import '../app.postcss';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Header>
|
||||||
|
<main>
|
||||||
|
<slot />
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<p>visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to learn SvelteKit</p>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<style windi:preflights:global windi:safelist:global>
|
||||||
|
main {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 1rem;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 1024px;
|
||||||
|
margin: 0 auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 480px) {
|
||||||
|
footer {
|
||||||
|
padding: 40px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</Header>
|
||||||
|
<slot />
|
|
@ -0,0 +1,50 @@
|
||||||
|
<script context="module">
|
||||||
|
import { browser, dev } from '$app/env';
|
||||||
|
|
||||||
|
// we don't need any JS on this page, though we'll load
|
||||||
|
// it in dev so that we get hot module replacement...
|
||||||
|
export const hydrate = dev;
|
||||||
|
|
||||||
|
// ...but if the client-side router is already loaded
|
||||||
|
// (i.e. we came here from elsewhere in the app), use it
|
||||||
|
export const router = browser;
|
||||||
|
|
||||||
|
// since there's no dynamic data here, we can prerender
|
||||||
|
// it so that it gets served as a static asset in prod
|
||||||
|
export const prerender = true;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>About</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<h1>About this app</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
This is a <a href="https://kit.svelte.dev">SvelteKit</a> app. You can make your own by typing the
|
||||||
|
following into your command line and following the prompts:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- TODO lose the @next! -->
|
||||||
|
<pre>npm init svelte@next</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The page you're looking at is purely static HTML, with no client-side interactivity needed.
|
||||||
|
Because of that, we don't need to load any JavaScript. Try viewing the page's source, or opening
|
||||||
|
the devtools network panel and reloading.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The <a href="/todos">TODOs</a> page illustrates SvelteKit's data loading and form handling. Try using
|
||||||
|
it with JavaScript disabled!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.content {
|
||||||
|
width: 100%;
|
||||||
|
max-width: var(--column-width);
|
||||||
|
margin: var(--column-margin-top) auto 0 auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,59 @@
|
||||||
|
<script context="module" lang="ts">
|
||||||
|
export const prerender = true;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Counter from '$lib/Counter/index.svelte';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>Home</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h1>
|
||||||
|
<div class="welcome">
|
||||||
|
<picture>
|
||||||
|
<source srcset="svelte-welcome.webp" type="image/webp" />
|
||||||
|
<img src="svelte-welcome.png" alt="Welcome" />
|
||||||
|
</picture>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
to your new<br />SvelteKit app
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<h2>
|
||||||
|
try editing <strong>src/routes/index.svelte</strong>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<Counter />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 0;
|
||||||
|
padding: 0 0 calc(100% * 495 / 2048) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome img {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { api } from './_api';
|
||||||
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
|
import type { Locals } from '$lib/types';
|
||||||
|
|
||||||
|
// PATCH /todos/:uid.json
|
||||||
|
export const patch: RequestHandler<Locals, FormData> = async (request) => {
|
||||||
|
return api(request, `todos/${request.locals.userid}/${request.params.uid}`, {
|
||||||
|
text: request.body.get('text'),
|
||||||
|
done: request.body.has('done') ? !!request.body.get('done') : undefined
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// DELETE /todos/:uid.json
|
||||||
|
export const del: RequestHandler<Locals> = async (request) => {
|
||||||
|
return api(request, `todos/${request.locals.userid}/${request.params.uid}`);
|
||||||
|
};
|
|
@ -0,0 +1,48 @@
|
||||||
|
import type { Request } from '@sveltejs/kit';
|
||||||
|
import type { Locals } from '$lib/types';
|
||||||
|
|
||||||
|
/*
|
||||||
|
This module is used by the /todos.json and /todos/[uid].json
|
||||||
|
endpoints to make calls to api.svelte.dev, which stores todos
|
||||||
|
for each user. The leading underscore indicates that this is
|
||||||
|
a private module, _not_ an endpoint — visiting /todos/_api
|
||||||
|
will net you a 404 response.
|
||||||
|
|
||||||
|
(The data on the todo app will expire periodically; no
|
||||||
|
guarantees are made. Don't use it to organise your life.)
|
||||||
|
*/
|
||||||
|
|
||||||
|
const base = 'https://api.svelte.dev';
|
||||||
|
|
||||||
|
export async function api(request: Request<Locals>, resource: string, data?: {}) {
|
||||||
|
// user must have a cookie set
|
||||||
|
if (!request.locals.userid) {
|
||||||
|
return { status: 401 };
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await fetch(`${base}/${resource}`, {
|
||||||
|
method: request.method,
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/json'
|
||||||
|
},
|
||||||
|
body: data && JSON.stringify(data)
|
||||||
|
});
|
||||||
|
|
||||||
|
// if the request came from a <form> submission, the browser's default
|
||||||
|
// behaviour is to show the URL corresponding to the form's "action"
|
||||||
|
// attribute. in those cases, we want to redirect them back to the
|
||||||
|
// /todos page, rather than showing the response
|
||||||
|
if (res.ok && request.method !== 'GET' && request.headers.accept !== 'application/json') {
|
||||||
|
return {
|
||||||
|
status: 303,
|
||||||
|
headers: {
|
||||||
|
location: '/todos'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: res.status,
|
||||||
|
body: await res.json()
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { api } from './_api';
|
||||||
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
|
import type { Locals } from '$lib/types';
|
||||||
|
|
||||||
|
// GET /todos.json
|
||||||
|
export const get: RequestHandler<Locals> = async (request) => {
|
||||||
|
// request.locals.userid comes from src/hooks.js
|
||||||
|
const response = await api(request, `todos/${request.locals.userid}`);
|
||||||
|
|
||||||
|
if (response.status === 404) {
|
||||||
|
// user hasn't created a todo list.
|
||||||
|
// start with an empty array
|
||||||
|
return { body: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
};
|
||||||
|
|
||||||
|
// POST /todos.json
|
||||||
|
export const post: RequestHandler<Locals, FormData> = async (request) => {
|
||||||
|
const response = await api(request, `todos/${request.locals.userid}`, {
|
||||||
|
// because index.svelte posts a FormData object,
|
||||||
|
// request.body is _also_ a (readonly) FormData
|
||||||
|
// object, which allows us to get form data
|
||||||
|
// with the `body.get(key)` method
|
||||||
|
text: request.body.get('text')
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
};
|
|
@ -0,0 +1,227 @@
|
||||||
|
<script context="module" lang="ts">
|
||||||
|
import { enhance } from '$lib/form';
|
||||||
|
import type { Load } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
// see https://kit.svelte.dev/docs#loading
|
||||||
|
export const load: Load = async ({ fetch }) => {
|
||||||
|
const res = await fetch('/todos.json');
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
const todos = await res.json();
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: { todos }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const { message } = await res.json();
|
||||||
|
|
||||||
|
return {
|
||||||
|
error: new Error(message)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { scale } from 'svelte/transition';
|
||||||
|
import { flip } from 'svelte/animate';
|
||||||
|
|
||||||
|
type Todo = {
|
||||||
|
uid: string;
|
||||||
|
created_at: Date;
|
||||||
|
text: string;
|
||||||
|
done: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export let todos: Todo[];
|
||||||
|
|
||||||
|
async function patch(res: Response) {
|
||||||
|
const todo = await res.json();
|
||||||
|
|
||||||
|
todos = todos.map((t) => {
|
||||||
|
if (t.uid === todo.uid) return todo;
|
||||||
|
return t;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>Todos</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<div class="todos">
|
||||||
|
<h1>Todos</h1>
|
||||||
|
|
||||||
|
<form
|
||||||
|
class="new"
|
||||||
|
action="/todos.json"
|
||||||
|
method="post"
|
||||||
|
use:enhance={{
|
||||||
|
result: async (res, form) => {
|
||||||
|
const created = await res.json();
|
||||||
|
todos = [...todos, created];
|
||||||
|
|
||||||
|
form.reset();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<input name="text" aria-label="Add todo" placeholder="+ tap to add a todo" />
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{#each todos as todo (todo.uid)}
|
||||||
|
<div
|
||||||
|
class="todo"
|
||||||
|
class:done={todo.done}
|
||||||
|
transition:scale|local={{ start: 0.7 }}
|
||||||
|
animate:flip={{ duration: 200 }}
|
||||||
|
>
|
||||||
|
<form
|
||||||
|
action="/todos/{todo.uid}.json?_method=patch"
|
||||||
|
method="post"
|
||||||
|
use:enhance={{
|
||||||
|
pending: (data) => {
|
||||||
|
todo.done = !!data.get('done');
|
||||||
|
},
|
||||||
|
result: patch
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<input type="hidden" name="done" value={todo.done ? '' : 'true'} />
|
||||||
|
<button class="toggle" aria-label="Mark todo as {todo.done ? 'not done' : 'done'}" />
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form
|
||||||
|
class="text"
|
||||||
|
action="/todos/{todo.uid}.json?_method=patch"
|
||||||
|
method="post"
|
||||||
|
use:enhance={{
|
||||||
|
result: patch
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<input aria-label="Edit todo" type="text" name="text" value={todo.text} />
|
||||||
|
<button class="save" aria-label="Save todo" />
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form
|
||||||
|
action="/todos/{todo.uid}.json?_method=delete"
|
||||||
|
method="post"
|
||||||
|
use:enhance={{
|
||||||
|
result: () => {
|
||||||
|
todos = todos.filter((t) => t.uid !== todo.uid);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button class="delete" aria-label="Delete todo" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.todos {
|
||||||
|
width: 100%;
|
||||||
|
max-width: var(--column-width);
|
||||||
|
margin: var(--column-margin-top) auto 0 auto;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new {
|
||||||
|
margin: 0 0 0.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
border: 1px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus-visible {
|
||||||
|
box-shadow: inset 1px 1px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
border: 1px solid #ff3e00 !important;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new input {
|
||||||
|
font-size: 28px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.5em 1em 0.3em 1em;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 2rem 1fr 2rem;
|
||||||
|
grid-gap: 0.5rem;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0 0 0.5rem 0;
|
||||||
|
padding: 0.5rem;
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
filter: drop-shadow(2px 4px 6px rgba(0, 0, 0, 0.1));
|
||||||
|
transform: translate(-1px, -1px);
|
||||||
|
transition: filter 0.2s, transform 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.done {
|
||||||
|
transform: none;
|
||||||
|
opacity: 0.4;
|
||||||
|
filter: drop-shadow(0px 0px 1px rgba(0, 0, 0, 0.1));
|
||||||
|
}
|
||||||
|
|
||||||
|
form.text {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo input {
|
||||||
|
flex: 1;
|
||||||
|
padding: 0.5em 2em 0.5em 0.8em;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo button {
|
||||||
|
width: 2em;
|
||||||
|
height: 2em;
|
||||||
|
border: none;
|
||||||
|
background-color: transparent;
|
||||||
|
background-position: 50% 50%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.toggle {
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||||
|
border-radius: 50%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-size: 1em auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.done .toggle {
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg width='22' height='16' viewBox='0 0 22 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 1.5L7.4375 14.5L1.5 8.5909' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete {
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4.5 5V22H19.5V5H4.5Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M10 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M14 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M2 5H22' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M8 5L9.6445 2H14.3885L16 5H8Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3C/svg%3E%0A");
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete:hover,
|
||||||
|
.delete:focus {
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
opacity: 0;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 2H3.5C2.67158 2 2 2.67157 2 3.5V20.5C2 21.3284 2.67158 22 3.5 22H20.5C21.3284 22 22 21.3284 22 20.5V3.5C22 2.67157 21.3284 2 20.5 2Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M17 2V11H7.5V2H17Z' fill='white' stroke='white' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M13.5 5.5V7.5' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3Cpath d='M5.99844 2H18.4992' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E%0A");
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo input:focus + .save,
|
||||||
|
.save:focus {
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,3 +0,0 @@
|
||||||
@tailwind base;
|
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
|
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
|
@ -0,0 +1,3 @@
|
||||||
|
# https://www.robotstxt.org/robotstxt.html
|
||||||
|
User-agent: *
|
||||||
|
Disallow:
|
Binary file not shown.
After Width: | Height: | Size: 352 KiB |
Binary file not shown.
After Width: | Height: | Size: 113 KiB |
|
@ -0,0 +1,20 @@
|
||||||
|
import preprocess from 'svelte-preprocess';
|
||||||
|
import { windi } from 'svelte-windicss-preprocess';
|
||||||
|
|
||||||
|
/** @type {import('@sveltejs/kit').Config} */
|
||||||
|
const config = {
|
||||||
|
// Consult https://github.com/sveltejs/svelte-preprocess
|
||||||
|
// for more information about preprocessors
|
||||||
|
preprocess: [windi(), preprocess({ postcss: true })],
|
||||||
|
|
||||||
|
kit: {
|
||||||
|
// hydrate the <div id="svelte"> element in src/app.html
|
||||||
|
target: '#svelte'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
|
// Workaround until SvelteKit uses Vite 2.3.8 (and it's confirmed to fix the Tailwind JIT problem)
|
||||||
|
const mode = process.env.NODE_ENV;
|
||||||
|
const dev = mode === 'development';
|
||||||
|
process.env.TAILWIND_MODE = dev ? 'watch' : 'build';
|
|
@ -1,16 +0,0 @@
|
||||||
const colors = require('tailwindcss/colors');
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
purge: [],
|
|
||||||
darkMode: 'media', // or 'media' or 'class'
|
|
||||||
theme: {
|
|
||||||
colors: {
|
|
||||||
...colors,
|
|
||||||
},
|
|
||||||
extend: {},
|
|
||||||
},
|
|
||||||
variants: {
|
|
||||||
extend: {},
|
|
||||||
},
|
|
||||||
plugins: [],
|
|
||||||
};
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"module": "es2020",
|
||||||
|
"lib": ["es2020"],
|
||||||
|
"target": "es2019",
|
||||||
|
/**
|
||||||
|
svelte-preprocess cannot figure out whether you have a value or a type, so tell TypeScript
|
||||||
|
to enforce using \`import type\` instead of \`import\` for Types.
|
||||||
|
*/
|
||||||
|
"importsNotUsedAsValues": "error",
|
||||||
|
"isolatedModules": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
/**
|
||||||
|
To have warnings/errors of the Svelte compiler at the correct position,
|
||||||
|
enable source maps by default.
|
||||||
|
*/
|
||||||
|
"sourceMap": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"allowJs": true,
|
||||||
|
"checkJs": true,
|
||||||
|
"paths": {
|
||||||
|
"$lib/*": ["src/lib/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.ts", "src/**/*.svelte"]
|
||||||
|
}
|
|
@ -1,24 +0,0 @@
|
||||||
import { defineConfig } from 'vite';
|
|
||||||
import { resolve } from 'path';
|
|
||||||
import eslint from '@rollup/plugin-eslint';
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
publicDir: 'public',
|
|
||||||
root: './src',
|
|
||||||
server: {
|
|
||||||
port: 8000,
|
|
||||||
cors: false,
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
eslint({
|
|
||||||
fix: true,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
build: {
|
|
||||||
rollupOptions: {
|
|
||||||
input: {
|
|
||||||
main: resolve(__dirname, 'src/index.html'),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
1852
client/yarn.lock
1852
client/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue