julien-c HF staff commited on
Commit
9d177b4
1 Parent(s): 40c9339

npm init svelte@next (w/o the README)

Browse files
.eslintrc.cjs ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ module.exports = {
2
+ root: true,
3
+ parser: '@typescript-eslint/parser',
4
+ extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
5
+ plugins: ['svelte3', '@typescript-eslint'],
6
+ ignorePatterns: ['*.cjs'],
7
+ overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }],
8
+ settings: {
9
+ 'svelte3/typescript': () => require('typescript')
10
+ },
11
+ parserOptions: {
12
+ sourceType: 'module',
13
+ ecmaVersion: 2020
14
+ },
15
+ env: {
16
+ browser: true,
17
+ es2017: true,
18
+ node: true
19
+ }
20
+ };
.gitignore ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ .DS_Store
2
+ node_modules
3
+ /build
4
+ /.svelte-kit
5
+ /package
6
+ .env
7
+ .env.*
8
+ !.env.example
9
+ .vercel
10
+ .output
.npmrc ADDED
@@ -0,0 +1 @@
 
 
1
+ engine-strict=true
.prettierrc ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ {
2
+ "useTabs": true,
3
+ "singleQuote": true,
4
+ "trailingComma": "none",
5
+ "printWidth": 100
6
+ }
package.json ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "sveltekit-demo",
3
+ "version": "0.0.1",
4
+ "scripts": {
5
+ "dev": "svelte-kit dev",
6
+ "build": "svelte-kit build",
7
+ "package": "svelte-kit package",
8
+ "preview": "svelte-kit preview",
9
+ "check": "svelte-check --tsconfig ./tsconfig.json",
10
+ "check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
11
+ "lint": "prettier --ignore-path .gitignore --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .",
12
+ "format": "prettier --ignore-path .gitignore --write --plugin-search-dir=. ."
13
+ },
14
+ "devDependencies": {
15
+ "@sveltejs/adapter-auto": "next",
16
+ "@sveltejs/kit": "next",
17
+ "@types/cookie": "^0.4.1",
18
+ "@typescript-eslint/eslint-plugin": "^4.31.1",
19
+ "@typescript-eslint/parser": "^4.31.1",
20
+ "eslint": "^7.32.0",
21
+ "eslint-config-prettier": "^8.3.0",
22
+ "eslint-plugin-svelte3": "^3.2.1",
23
+ "prettier": "^2.4.1",
24
+ "prettier-plugin-svelte": "^2.4.0",
25
+ "svelte": "^3.44.0",
26
+ "svelte-check": "^2.2.6",
27
+ "svelte-preprocess": "^4.9.4",
28
+ "tslib": "^2.3.1",
29
+ "typescript": "^4.4.3"
30
+ },
31
+ "type": "module",
32
+ "dependencies": {
33
+ "@fontsource/fira-mono": "^4.5.0",
34
+ "@lukeed/uuid": "^2.0.0",
35
+ "cookie": "^0.4.1"
36
+ }
37
+ }
src/app.css ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import '@fontsource/fira-mono';
2
+
3
+ :root {
4
+ font-family: Arial, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
5
+ Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
6
+ --font-mono: 'Fira Mono', monospace;
7
+ --pure-white: #ffffff;
8
+ --primary-color: #b9c6d2;
9
+ --secondary-color: #d0dde9;
10
+ --tertiary-color: #edf0f8;
11
+ --accent-color: #ff3e00;
12
+ --heading-color: rgba(0, 0, 0, 0.7);
13
+ --text-color: #444444;
14
+ --background-without-opacity: rgba(255, 255, 255, 0.7);
15
+ --column-width: 42rem;
16
+ --column-margin-top: 4rem;
17
+ }
18
+
19
+ body {
20
+ min-height: 100vh;
21
+ margin: 0;
22
+ background-color: var(--primary-color);
23
+ background: linear-gradient(
24
+ 180deg,
25
+ var(--primary-color) 0%,
26
+ var(--secondary-color) 10.45%,
27
+ var(--tertiary-color) 41.35%
28
+ );
29
+ }
30
+
31
+ body::before {
32
+ content: '';
33
+ width: 80vw;
34
+ height: 100vh;
35
+ position: absolute;
36
+ top: 0;
37
+ left: 10vw;
38
+ z-index: -1;
39
+ background: radial-gradient(
40
+ 50% 50% at 50% 50%,
41
+ var(--pure-white) 0%,
42
+ rgba(255, 255, 255, 0) 100%
43
+ );
44
+ opacity: 0.05;
45
+ }
46
+
47
+ #svelte {
48
+ min-height: 100vh;
49
+ display: flex;
50
+ flex-direction: column;
51
+ }
52
+
53
+ h1,
54
+ h2,
55
+ p {
56
+ font-weight: 400;
57
+ color: var(--heading-color);
58
+ }
59
+
60
+ p {
61
+ line-height: 1.5;
62
+ }
63
+
64
+ a {
65
+ color: var(--accent-color);
66
+ text-decoration: none;
67
+ }
68
+
69
+ a:hover {
70
+ text-decoration: underline;
71
+ }
72
+
73
+ h1 {
74
+ font-size: 2rem;
75
+ text-align: center;
76
+ }
77
+
78
+ h2 {
79
+ font-size: 1rem;
80
+ }
81
+
82
+ pre {
83
+ font-size: 16px;
84
+ font-family: var(--font-mono);
85
+ background-color: rgba(255, 255, 255, 0.45);
86
+ border-radius: 3px;
87
+ box-shadow: 2px 2px 6px rgb(255 255 255 / 25%);
88
+ padding: 0.5em;
89
+ overflow-x: auto;
90
+ color: var(--text-color);
91
+ }
92
+
93
+ input,
94
+ button {
95
+ font-size: inherit;
96
+ font-family: inherit;
97
+ }
98
+
99
+ button:focus:not(:focus-visible) {
100
+ outline: none;
101
+ }
102
+
103
+ @media (min-width: 720px) {
104
+ h1 {
105
+ font-size: 2.4rem;
106
+ }
107
+ }
src/app.html ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="description" content="Svelte demo app" />
6
+ <link rel="icon" href="/favicon.png" />
7
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
8
+ %svelte.head%
9
+ </head>
10
+ <body>
11
+ <div id="svelte">%svelte.body%</div>
12
+ </body>
13
+ </html>
src/global.d.ts ADDED
@@ -0,0 +1 @@
 
 
1
+ /// <reference types="@sveltejs/kit" />
src/hooks.ts ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cookie from 'cookie';
2
+ import { v4 as uuid } from '@lukeed/uuid';
3
+ import type { Handle } from '@sveltejs/kit';
4
+
5
+ export const handle: Handle = async ({ request, resolve }) => {
6
+ const cookies = cookie.parse(request.headers.cookie || '');
7
+ request.locals.userid = cookies.userid || uuid();
8
+
9
+ // TODO https://github.com/sveltejs/kit/issues/1046
10
+ const method = request.url.searchParams.get('_method');
11
+ if (method) {
12
+ request.method = method.toUpperCase();
13
+ }
14
+
15
+ const response = await resolve(request);
16
+
17
+ if (!cookies.userid) {
18
+ // if this is the first time the user has visited this app,
19
+ // set a cookie so that we recognise them when they return
20
+ response.headers['set-cookie'] = cookie.serialize('userid', request.locals.userid, {
21
+ path: '/',
22
+ httpOnly: true
23
+ });
24
+ }
25
+
26
+ return response;
27
+ };
src/lib/Counter.svelte ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { spring } from 'svelte/motion';
3
+
4
+ let count = 0;
5
+
6
+ const displayed_count = spring();
7
+ $: displayed_count.set(count);
8
+ $: offset = modulo($displayed_count, 1);
9
+
10
+ function modulo(n: number, m: number) {
11
+ // handle negative numbers
12
+ return ((n % m) + m) % m;
13
+ }
14
+ </script>
15
+
16
+ <div class="counter">
17
+ <button on:click={() => (count -= 1)} aria-label="Decrease the counter by one">
18
+ <svg aria-hidden="true" viewBox="0 0 1 1">
19
+ <path d="M0,0.5 L1,0.5" />
20
+ </svg>
21
+ </button>
22
+
23
+ <div class="counter-viewport">
24
+ <div class="counter-digits" style="transform: translate(0, {100 * offset}%)">
25
+ <strong style="top: -100%" aria-hidden="true">{Math.floor($displayed_count + 1)}</strong>
26
+ <strong>{Math.floor($displayed_count)}</strong>
27
+ </div>
28
+ </div>
29
+
30
+ <button on:click={() => (count += 1)} aria-label="Increase the counter by one">
31
+ <svg aria-hidden="true" viewBox="0 0 1 1">
32
+ <path d="M0,0.5 L1,0.5 M0.5,0 L0.5,1" />
33
+ </svg>
34
+ </button>
35
+ </div>
36
+
37
+ <style>
38
+ .counter {
39
+ display: flex;
40
+ border-top: 1px solid rgba(0, 0, 0, 0.1);
41
+ border-bottom: 1px solid rgba(0, 0, 0, 0.1);
42
+ margin: 1rem 0;
43
+ }
44
+
45
+ .counter button {
46
+ width: 2em;
47
+ padding: 0;
48
+ display: flex;
49
+ align-items: center;
50
+ justify-content: center;
51
+ border: 0;
52
+ background-color: transparent;
53
+ color: var(--text-color);
54
+ font-size: 2rem;
55
+ }
56
+
57
+ .counter button:hover {
58
+ background-color: var(--secondary-color);
59
+ }
60
+
61
+ svg {
62
+ width: 25%;
63
+ height: 25%;
64
+ }
65
+
66
+ path {
67
+ vector-effect: non-scaling-stroke;
68
+ stroke-width: 2px;
69
+ stroke: var(--text-color);
70
+ }
71
+
72
+ .counter-viewport {
73
+ width: 8em;
74
+ height: 4em;
75
+ overflow: hidden;
76
+ text-align: center;
77
+ position: relative;
78
+ }
79
+
80
+ .counter-viewport strong {
81
+ position: absolute;
82
+ display: flex;
83
+ width: 100%;
84
+ height: 100%;
85
+ font-weight: 400;
86
+ color: var(--accent-color);
87
+ font-size: 4rem;
88
+ align-items: center;
89
+ justify-content: center;
90
+ }
91
+
92
+ .counter-digits {
93
+ position: absolute;
94
+ width: 100%;
95
+ height: 100%;
96
+ }
97
+ </style>
src/lib/form.ts ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // this action (https://svelte.dev/tutorial/actions) allows us to
2
+ // progressively enhance a <form> that already works without JS
3
+ export function enhance(
4
+ form: HTMLFormElement,
5
+ {
6
+ pending,
7
+ error,
8
+ result
9
+ }: {
10
+ pending?: (data: FormData, form: HTMLFormElement) => void;
11
+ error?: (res: Response | null, error: Error | null, form: HTMLFormElement) => void;
12
+ result: (res: Response, form: HTMLFormElement) => void;
13
+ }
14
+ ): { destroy: () => void } {
15
+ let current_token: unknown;
16
+
17
+ async function handle_submit(e: Event) {
18
+ const token = (current_token = {});
19
+
20
+ e.preventDefault();
21
+
22
+ const body = new FormData(form);
23
+
24
+ if (pending) pending(body, form);
25
+
26
+ try {
27
+ const res = await fetch(form.action, {
28
+ method: form.method,
29
+ headers: {
30
+ accept: 'application/json'
31
+ },
32
+ body
33
+ });
34
+
35
+ if (token !== current_token) return;
36
+
37
+ if (res.ok) {
38
+ result(res, form);
39
+ } else if (error) {
40
+ error(res, null, form);
41
+ } else {
42
+ console.error(await res.text());
43
+ }
44
+ } catch (e: any) {
45
+ if (error) {
46
+ error(null, e, form);
47
+ } else {
48
+ throw e;
49
+ }
50
+ }
51
+ }
52
+
53
+ form.addEventListener('submit', handle_submit);
54
+
55
+ return {
56
+ destroy() {
57
+ form.removeEventListener('submit', handle_submit);
58
+ }
59
+ };
60
+ }
src/lib/header/Header.svelte ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { page } from '$app/stores';
3
+ import logo from './svelte-logo.svg';
4
+ </script>
5
+
6
+ <header>
7
+ <div class="corner">
8
+ <a href="https://kit.svelte.dev">
9
+ <img src={logo} alt="SvelteKit" />
10
+ </a>
11
+ </div>
12
+
13
+ <nav>
14
+ <svg viewBox="0 0 2 3" aria-hidden="true">
15
+ <path d="M0,0 L1,2 C1.5,3 1.5,3 2,3 L2,0 Z" />
16
+ </svg>
17
+ <ul>
18
+ <li class:active={$page.url.pathname === '/'}><a sveltekit:prefetch href="/">Home</a></li>
19
+ <li class:active={$page.url.pathname === '/about'}>
20
+ <a sveltekit:prefetch href="/about">About</a>
21
+ </li>
22
+ <li class:active={$page.url.pathname === '/todos'}>
23
+ <a sveltekit:prefetch href="/todos">Todos</a>
24
+ </li>
25
+ </ul>
26
+ <svg viewBox="0 0 2 3" aria-hidden="true">
27
+ <path d="M0,0 L0,3 C0.5,3 0.5,3 1,2 L2,0 Z" />
28
+ </svg>
29
+ </nav>
30
+
31
+ <div class="corner">
32
+ <!-- TODO put something else here? github link? -->
33
+ </div>
34
+ </header>
35
+
36
+ <style>
37
+ header {
38
+ display: flex;
39
+ justify-content: space-between;
40
+ }
41
+
42
+ .corner {
43
+ width: 3em;
44
+ height: 3em;
45
+ }
46
+
47
+ .corner a {
48
+ display: flex;
49
+ align-items: center;
50
+ justify-content: center;
51
+ width: 100%;
52
+ height: 100%;
53
+ }
54
+
55
+ .corner img {
56
+ width: 2em;
57
+ height: 2em;
58
+ object-fit: contain;
59
+ }
60
+
61
+ nav {
62
+ display: flex;
63
+ justify-content: center;
64
+ --background: rgba(255, 255, 255, 0.7);
65
+ }
66
+
67
+ svg {
68
+ width: 2em;
69
+ height: 3em;
70
+ display: block;
71
+ }
72
+
73
+ path {
74
+ fill: var(--background);
75
+ }
76
+
77
+ ul {
78
+ position: relative;
79
+ padding: 0;
80
+ margin: 0;
81
+ height: 3em;
82
+ display: flex;
83
+ justify-content: center;
84
+ align-items: center;
85
+ list-style: none;
86
+ background: var(--background);
87
+ background-size: contain;
88
+ }
89
+
90
+ li {
91
+ position: relative;
92
+ height: 100%;
93
+ }
94
+
95
+ li.active::before {
96
+ --size: 6px;
97
+ content: '';
98
+ width: 0;
99
+ height: 0;
100
+ position: absolute;
101
+ top: 0;
102
+ left: calc(50% - var(--size));
103
+ border: var(--size) solid transparent;
104
+ border-top: var(--size) solid var(--accent-color);
105
+ }
106
+
107
+ nav a {
108
+ display: flex;
109
+ height: 100%;
110
+ align-items: center;
111
+ padding: 0 1em;
112
+ color: var(--heading-color);
113
+ font-weight: 700;
114
+ font-size: 0.8rem;
115
+ text-transform: uppercase;
116
+ letter-spacing: 0.1em;
117
+ text-decoration: none;
118
+ transition: color 0.2s linear;
119
+ }
120
+
121
+ a:hover {
122
+ color: var(--accent-color);
123
+ }
124
+ </style>
src/lib/header/svelte-logo.svg ADDED
src/lib/types.d.ts ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Can be made globally available by placing this
3
+ * inside `global.d.ts` and removing `export` keyword
4
+ */
5
+ export interface Locals {
6
+ userid: string;
7
+ }
src/routes/__layout.svelte ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import Header from '$lib/header/Header.svelte';
3
+ import '../app.css';
4
+ </script>
5
+
6
+ <Header />
7
+
8
+ <main>
9
+ <slot />
10
+ </main>
11
+
12
+ <footer>
13
+ <p>visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to learn SvelteKit</p>
14
+ </footer>
15
+
16
+ <style>
17
+ main {
18
+ flex: 1;
19
+ display: flex;
20
+ flex-direction: column;
21
+ padding: 1rem;
22
+ width: 100%;
23
+ max-width: 1024px;
24
+ margin: 0 auto;
25
+ box-sizing: border-box;
26
+ }
27
+
28
+ footer {
29
+ display: flex;
30
+ flex-direction: column;
31
+ justify-content: center;
32
+ align-items: center;
33
+ padding: 40px;
34
+ }
35
+
36
+ footer a {
37
+ font-weight: bold;
38
+ }
39
+
40
+ @media (min-width: 480px) {
41
+ footer {
42
+ padding: 40px 0;
43
+ }
44
+ }
45
+ </style>
src/routes/about.svelte ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script context="module">
2
+ import { browser, dev } from '$app/env';
3
+
4
+ // we don't need any JS on this page, though we'll load
5
+ // it in dev so that we get hot module replacement...
6
+ export const hydrate = dev;
7
+
8
+ // ...but if the client-side router is already loaded
9
+ // (i.e. we came here from elsewhere in the app), use it
10
+ export const router = browser;
11
+
12
+ // since there's no dynamic data here, we can prerender
13
+ // it so that it gets served as a static asset in prod
14
+ export const prerender = true;
15
+ </script>
16
+
17
+ <svelte:head>
18
+ <title>About</title>
19
+ </svelte:head>
20
+
21
+ <div class="content">
22
+ <h1>About this app</h1>
23
+
24
+ <p>
25
+ This is a <a href="https://kit.svelte.dev">SvelteKit</a> app. You can make your own by typing the
26
+ following into your command line and following the prompts:
27
+ </p>
28
+
29
+ <!-- TODO lose the @next! -->
30
+ <pre>npm init svelte@next</pre>
31
+
32
+ <p>
33
+ The page you're looking at is purely static HTML, with no client-side interactivity needed.
34
+ Because of that, we don't need to load any JavaScript. Try viewing the page's source, or opening
35
+ the devtools network panel and reloading.
36
+ </p>
37
+
38
+ <p>
39
+ The <a href="/todos">TODOs</a> page illustrates SvelteKit's data loading and form handling. Try using
40
+ it with JavaScript disabled!
41
+ </p>
42
+ </div>
43
+
44
+ <style>
45
+ .content {
46
+ width: 100%;
47
+ max-width: var(--column-width);
48
+ margin: var(--column-margin-top) auto 0 auto;
49
+ }
50
+ </style>
src/routes/index.svelte ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script context="module" lang="ts">
2
+ export const prerender = true;
3
+ </script>
4
+
5
+ <script lang="ts">
6
+ import Counter from '$lib/Counter.svelte';
7
+ </script>
8
+
9
+ <svelte:head>
10
+ <title>Home</title>
11
+ </svelte:head>
12
+
13
+ <section>
14
+ <h1>
15
+ <div class="welcome">
16
+ <picture>
17
+ <source srcset="svelte-welcome.webp" type="image/webp" />
18
+ <img src="svelte-welcome.png" alt="Welcome" />
19
+ </picture>
20
+ </div>
21
+
22
+ to your new<br />SvelteKit app
23
+ </h1>
24
+
25
+ <h2>
26
+ try editing <strong>src/routes/index.svelte</strong>
27
+ </h2>
28
+
29
+ <Counter />
30
+ </section>
31
+
32
+ <style>
33
+ section {
34
+ display: flex;
35
+ flex-direction: column;
36
+ justify-content: center;
37
+ align-items: center;
38
+ flex: 1;
39
+ }
40
+
41
+ h1 {
42
+ width: 100%;
43
+ }
44
+
45
+ .welcome {
46
+ position: relative;
47
+ width: 100%;
48
+ height: 0;
49
+ padding: 0 0 calc(100% * 495 / 2048) 0;
50
+ }
51
+
52
+ .welcome img {
53
+ position: absolute;
54
+ width: 100%;
55
+ height: 100%;
56
+ top: 0;
57
+ display: block;
58
+ }
59
+ </style>
src/routes/todos/[uid].json.ts ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { api } from './_api';
2
+ import type { RequestHandler } from '@sveltejs/kit';
3
+ import type { Locals } from '$lib/types';
4
+
5
+ // PATCH /todos/:uid.json
6
+ export const patch: RequestHandler<Locals, FormData> = async (request) => {
7
+ return api(request, `todos/${request.locals.userid}/${request.params.uid}`, {
8
+ text: request.body.get('text'),
9
+ done: request.body.has('done') ? !!request.body.get('done') : undefined
10
+ });
11
+ };
12
+
13
+ // DELETE /todos/:uid.json
14
+ export const del: RequestHandler<Locals> = async (request) => {
15
+ return api(request, `todos/${request.locals.userid}/${request.params.uid}`);
16
+ };
src/routes/todos/_api.ts ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { EndpointOutput, Request } from '@sveltejs/kit';
2
+ import type { Locals } from '$lib/types';
3
+
4
+ /*
5
+ This module is used by the /todos.json and /todos/[uid].json
6
+ endpoints to make calls to api.svelte.dev, which stores todos
7
+ for each user. The leading underscore indicates that this is
8
+ a private module, _not_ an endpoint — visiting /todos/_api
9
+ will net you a 404 response.
10
+
11
+ (The data on the todo app will expire periodically; no
12
+ guarantees are made. Don't use it to organise your life.)
13
+ */
14
+
15
+ const base = 'https://api.svelte.dev';
16
+
17
+ export async function api(
18
+ request: Request<Locals>,
19
+ resource: string,
20
+ data?: Record<string, unknown>
21
+ ): Promise<EndpointOutput> {
22
+ // user must have a cookie set
23
+ if (!request.locals.userid) {
24
+ return { status: 401 };
25
+ }
26
+
27
+ const res = await fetch(`${base}/${resource}`, {
28
+ method: request.method,
29
+ headers: {
30
+ 'content-type': 'application/json'
31
+ },
32
+ body: data && JSON.stringify(data)
33
+ });
34
+
35
+ // if the request came from a <form> submission, the browser's default
36
+ // behaviour is to show the URL corresponding to the form's "action"
37
+ // attribute. in those cases, we want to redirect them back to the
38
+ // /todos page, rather than showing the response
39
+ if (res.ok && request.method !== 'GET' && request.headers.accept !== 'application/json') {
40
+ return {
41
+ status: 303,
42
+ headers: {
43
+ location: '/todos'
44
+ }
45
+ };
46
+ }
47
+
48
+ return {
49
+ status: res.status,
50
+ body: await res.json()
51
+ };
52
+ }
src/routes/todos/index.json.ts ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { api } from './_api';
2
+ import type { RequestHandler } from '@sveltejs/kit';
3
+ import type { Locals } from '$lib/types';
4
+
5
+ // GET /todos.json
6
+ export const get: RequestHandler<Locals> = async (request) => {
7
+ // request.locals.userid comes from src/hooks.js
8
+ const response = await api(request, `todos/${request.locals.userid}`);
9
+
10
+ if (response.status === 404) {
11
+ // user hasn't created a todo list.
12
+ // start with an empty array
13
+ return { body: [] };
14
+ }
15
+
16
+ return response;
17
+ };
18
+
19
+ // POST /todos.json
20
+ export const post: RequestHandler<Locals, FormData> = async (request) => {
21
+ const response = await api(request, `todos/${request.locals.userid}`, {
22
+ // because index.svelte posts a FormData object,
23
+ // request.body is _also_ a (readonly) FormData
24
+ // object, which allows us to get form data
25
+ // with the `body.get(key)` method
26
+ text: request.body.get('text')
27
+ });
28
+
29
+ return response;
30
+ };
src/routes/todos/index.svelte ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script context="module" lang="ts">
2
+ import { enhance } from '$lib/form';
3
+ import type { Load } from '@sveltejs/kit';
4
+
5
+ // see https://kit.svelte.dev/docs#loading
6
+ export const load: Load = async ({ fetch }) => {
7
+ const res = await fetch('/todos.json');
8
+
9
+ if (res.ok) {
10
+ const todos = await res.json();
11
+
12
+ return {
13
+ props: { todos }
14
+ };
15
+ }
16
+
17
+ const { message } = await res.json();
18
+
19
+ return {
20
+ error: new Error(message)
21
+ };
22
+ };
23
+ </script>
24
+
25
+ <script lang="ts">
26
+ import { scale } from 'svelte/transition';
27
+ import { flip } from 'svelte/animate';
28
+
29
+ type Todo = {
30
+ uid: string;
31
+ created_at: Date;
32
+ text: string;
33
+ done: boolean;
34
+ pending_delete: boolean;
35
+ };
36
+
37
+ export let todos: Todo[];
38
+
39
+ async function patch(res: Response) {
40
+ const todo = await res.json();
41
+
42
+ todos = todos.map((t) => {
43
+ if (t.uid === todo.uid) return todo;
44
+ return t;
45
+ });
46
+ }
47
+ </script>
48
+
49
+ <svelte:head>
50
+ <title>Todos</title>
51
+ </svelte:head>
52
+
53
+ <div class="todos">
54
+ <h1>Todos</h1>
55
+
56
+ <form
57
+ class="new"
58
+ action="/todos.json"
59
+ method="post"
60
+ use:enhance={{
61
+ result: async (res, form) => {
62
+ const created = await res.json();
63
+ todos = [...todos, created];
64
+
65
+ form.reset();
66
+ }
67
+ }}
68
+ >
69
+ <input name="text" aria-label="Add todo" placeholder="+ tap to add a todo" />
70
+ </form>
71
+
72
+ {#each todos as todo (todo.uid)}
73
+ <div
74
+ class="todo"
75
+ class:done={todo.done}
76
+ transition:scale|local={{ start: 0.7 }}
77
+ animate:flip={{ duration: 200 }}
78
+ >
79
+ <form
80
+ action="/todos/{todo.uid}.json?_method=patch"
81
+ method="post"
82
+ use:enhance={{
83
+ pending: (data) => {
84
+ todo.done = !!data.get('done');
85
+ },
86
+ result: patch
87
+ }}
88
+ >
89
+ <input type="hidden" name="done" value={todo.done ? '' : 'true'} />
90
+ <button class="toggle" aria-label="Mark todo as {todo.done ? 'not done' : 'done'}" />
91
+ </form>
92
+
93
+ <form
94
+ class="text"
95
+ action="/todos/{todo.uid}.json?_method=patch"
96
+ method="post"
97
+ use:enhance={{
98
+ result: patch
99
+ }}
100
+ >
101
+ <input aria-label="Edit todo" type="text" name="text" value={todo.text} />
102
+ <button class="save" aria-label="Save todo" />
103
+ </form>
104
+
105
+ <form
106
+ action="/todos/{todo.uid}.json?_method=delete"
107
+ method="post"
108
+ use:enhance={{
109
+ pending: () => (todo.pending_delete = true),
110
+ result: () => {
111
+ todos = todos.filter((t) => t.uid !== todo.uid);
112
+ }
113
+ }}
114
+ >
115
+ <button class="delete" aria-label="Delete todo" disabled={todo.pending_delete} />
116
+ </form>
117
+ </div>
118
+ {/each}
119
+ </div>
120
+
121
+ <style>
122
+ .todos {
123
+ width: 100%;
124
+ max-width: var(--column-width);
125
+ margin: var(--column-margin-top) auto 0 auto;
126
+ line-height: 1;
127
+ }
128
+
129
+ .new {
130
+ margin: 0 0 0.5rem 0;
131
+ }
132
+
133
+ input {
134
+ border: 1px solid transparent;
135
+ }
136
+
137
+ input:focus-visible {
138
+ box-shadow: inset 1px 1px 6px rgba(0, 0, 0, 0.1);
139
+ border: 1px solid #ff3e00 !important;
140
+ outline: none;
141
+ }
142
+
143
+ .new input {
144
+ font-size: 28px;
145
+ width: 100%;
146
+ padding: 0.5em 1em 0.3em 1em;
147
+ box-sizing: border-box;
148
+ background: rgba(255, 255, 255, 0.05);
149
+ border-radius: 8px;
150
+ text-align: center;
151
+ }
152
+
153
+ .todo {
154
+ display: grid;
155
+ grid-template-columns: 2rem 1fr 2rem;
156
+ grid-gap: 0.5rem;
157
+ align-items: center;
158
+ margin: 0 0 0.5rem 0;
159
+ padding: 0.5rem;
160
+ background-color: white;
161
+ border-radius: 8px;
162
+ filter: drop-shadow(2px 4px 6px rgba(0, 0, 0, 0.1));
163
+ transform: translate(-1px, -1px);
164
+ transition: filter 0.2s, transform 0.2s;
165
+ }
166
+
167
+ .done {
168
+ transform: none;
169
+ opacity: 0.4;
170
+ filter: drop-shadow(0px 0px 1px rgba(0, 0, 0, 0.1));
171
+ }
172
+
173
+ form.text {
174
+ position: relative;
175
+ display: flex;
176
+ align-items: center;
177
+ flex: 1;
178
+ }
179
+
180
+ .todo input {
181
+ flex: 1;
182
+ padding: 0.5em 2em 0.5em 0.8em;
183
+ border-radius: 3px;
184
+ }
185
+
186
+ .todo button {
187
+ width: 2em;
188
+ height: 2em;
189
+ border: none;
190
+ background-color: transparent;
191
+ background-position: 50% 50%;
192
+ background-repeat: no-repeat;
193
+ }
194
+
195
+ button.toggle {
196
+ border: 1px solid rgba(0, 0, 0, 0.2);
197
+ border-radius: 50%;
198
+ box-sizing: border-box;
199
+ background-size: 1em auto;
200
+ }
201
+
202
+ .done .toggle {
203
+ 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");
204
+ }
205
+
206
+ .delete {
207
+ 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");
208
+ opacity: 0.2;
209
+ }
210
+
211
+ .delete:hover,
212
+ .delete:focus {
213
+ transition: opacity 0.2s;
214
+ opacity: 1;
215
+ }
216
+
217
+ .save {
218
+ position: absolute;
219
+ right: 0;
220
+ opacity: 0;
221
+ 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");
222
+ }
223
+
224
+ .todo input:focus + .save,
225
+ .save:focus {
226
+ transition: opacity 0.2s;
227
+ opacity: 1;
228
+ }
229
+ </style>
static/favicon.png ADDED
static/robots.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ # https://www.robotstxt.org/robotstxt.html
2
+ User-agent: *
3
+ Disallow:
static/svelte-welcome.png ADDED
static/svelte-welcome.webp ADDED
svelte.config.js ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import adapter from '@sveltejs/adapter-auto';
2
+ import preprocess from 'svelte-preprocess';
3
+
4
+ /** @type {import('@sveltejs/kit').Config} */
5
+ const config = {
6
+ // Consult https://github.com/sveltejs/svelte-preprocess
7
+ // for more information about preprocessors
8
+ preprocess: preprocess(),
9
+
10
+ kit: {
11
+ adapter: adapter(),
12
+
13
+ // hydrate the <div id="svelte"> element in src/app.html
14
+ target: '#svelte'
15
+ }
16
+ };
17
+
18
+ export default config;
tsconfig.json ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "moduleResolution": "node",
4
+ "module": "es2020",
5
+ "lib": ["es2020", "DOM"],
6
+ "target": "es2020",
7
+ /**
8
+ svelte-preprocess cannot figure out whether you have a value or a type, so tell TypeScript
9
+ to enforce using \`import type\` instead of \`import\` for Types.
10
+ */
11
+ "importsNotUsedAsValues": "error",
12
+ "isolatedModules": true,
13
+ "resolveJsonModule": true,
14
+ /**
15
+ To have warnings/errors of the Svelte compiler at the correct position,
16
+ enable source maps by default.
17
+ */
18
+ "sourceMap": true,
19
+ "esModuleInterop": true,
20
+ "skipLibCheck": true,
21
+ "forceConsistentCasingInFileNames": true,
22
+ "baseUrl": ".",
23
+ "allowJs": true,
24
+ "checkJs": true,
25
+ "paths": {
26
+ "$lib": ["src/lib"],
27
+ "$lib/*": ["src/lib/*"]
28
+ }
29
+ },
30
+ "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.ts", "src/**/*.svelte"]
31
+ }