victor HF Staff commited on
Commit
832409f
·
0 Parent(s):
.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
+ }
README.md ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # create-svelte
2
+
3
+ Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte);
4
+
5
+ ## Creating a project
6
+
7
+ If you're seeing this, you've probably already done this step. Congrats!
8
+
9
+ ```bash
10
+ # create a new project in the current directory
11
+ npm init svelte@next
12
+
13
+ # create a new project in my-app
14
+ npm init svelte@next my-app
15
+ ```
16
+
17
+ > Note: the `@next` is temporary
18
+
19
+ ## Developing
20
+
21
+ Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
22
+
23
+ ```bash
24
+ npm run dev
25
+
26
+ # or start the server and open the app in a new browser tab
27
+ npm run dev -- --open
28
+ ```
29
+
30
+ ## Building
31
+
32
+ Before creating a production version of your app, install an [adapter](https://kit.svelte.dev/docs#adapters) for your target environment. Then:
33
+
34
+ ```bash
35
+ npm run build
36
+ ```
37
+
38
+ > 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.
jsconfig.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "baseUrl": ".",
4
+ "paths": {
5
+ "$lib": ["src/lib"],
6
+ "$lib/*": ["src/lib/*"]
7
+ }
8
+ },
9
+ "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"]
10
+ }
package.json ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "sveltekit-space",
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
+ "lint": "prettier --ignore-path .gitignore --check --plugin-search-dir=. .",
10
+ "format": "prettier --ignore-path .gitignore --write --plugin-search-dir=. ."
11
+ },
12
+ "devDependencies": {
13
+ "@sveltejs/adapter-auto": "next",
14
+ "@sveltejs/kit": "next",
15
+ "prettier": "^2.4.1",
16
+ "prettier-plugin-svelte": "^2.4.0",
17
+ "svelte": "^3.44.0"
18
+ },
19
+ "type": "module",
20
+ "dependencies": {
21
+ "@fontsource/fira-mono": "^4.5.0",
22
+ "@lukeed/uuid": "^2.0.0",
23
+ "cookie": "^0.4.1"
24
+ }
25
+ }
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.js ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cookie from 'cookie';
2
+ import { v4 as uuid } from '@lukeed/uuid';
3
+
4
+ export const handle = async ({ request, resolve }) => {
5
+ const cookies = cookie.parse(request.headers.cookie || '');
6
+ request.locals.userid = cookies.userid || uuid();
7
+
8
+ // TODO https://github.com/sveltejs/kit/issues/1046
9
+ const method = request.url.searchParams.get('_method');
10
+ if (method) {
11
+ request.method = method.toUpperCase();
12
+ }
13
+
14
+ const response = await resolve(request);
15
+
16
+ if (!cookies.userid) {
17
+ // if this is the first time the user has visited this app,
18
+ // set a cookie so that we recognise them when they return
19
+ response.headers['set-cookie'] = cookie.serialize('userid', request.locals.userid, {
20
+ path: '/',
21
+ httpOnly: true
22
+ });
23
+ }
24
+
25
+ return response;
26
+ };
src/lib/Counter.svelte ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script>
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, m) {
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.js ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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(form, { pending, error, result }) {
4
+ let current_token;
5
+
6
+ async function handle_submit(e) {
7
+ const token = (current_token = {});
8
+
9
+ e.preventDefault();
10
+
11
+ const body = new FormData(form);
12
+
13
+ if (pending) pending(body, form);
14
+
15
+ try {
16
+ const res = await fetch(form.action, {
17
+ method: form.method,
18
+ headers: {
19
+ accept: 'application/json'
20
+ },
21
+ body
22
+ });
23
+
24
+ if (token !== current_token) return;
25
+
26
+ if (res.ok) {
27
+ result(res, form);
28
+ } else if (error) {
29
+ error(res, null, form);
30
+ } else {
31
+ console.error(await res.text());
32
+ }
33
+ } catch (e) {
34
+ if (error) {
35
+ error(null, e, form);
36
+ } else {
37
+ throw e;
38
+ }
39
+ }
40
+ }
41
+
42
+ form.addEventListener('submit', handle_submit);
43
+
44
+ return {
45
+ destroy() {
46
+ form.removeEventListener('submit', handle_submit);
47
+ }
48
+ };
49
+ }
src/lib/header/Header.svelte ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script>
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/routes/__layout.svelte ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script>
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">
2
+ export const prerender = true;
3
+ </script>
4
+
5
+ <script>
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.js ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { api } from './_api';
2
+
3
+ // PATCH /todos/:uid.json
4
+ export const patch = async (request) => {
5
+ return api(request, `todos/${request.locals.userid}/${request.params.uid}`, {
6
+ text: request.body.get('text'),
7
+ done: request.body.has('done') ? !!request.body.get('done') : undefined
8
+ });
9
+ };
10
+
11
+ // DELETE /todos/:uid.json
12
+ export const del = async (request) => {
13
+ return api(request, `todos/${request.locals.userid}/${request.params.uid}`);
14
+ };
src/routes/todos/_api.js ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ This module is used by the /todos.json and /todos/[uid].json
3
+ endpoints to make calls to api.svelte.dev, which stores todos
4
+ for each user. The leading underscore indicates that this is
5
+ a private module, _not_ an endpoint — visiting /todos/_api
6
+ will net you a 404 response.
7
+
8
+ (The data on the todo app will expire periodically; no
9
+ guarantees are made. Don't use it to organise your life.)
10
+ */
11
+
12
+ const base = 'https://api.svelte.dev';
13
+
14
+ export async function api(request, resource, data) {
15
+ // user must have a cookie set
16
+ if (!request.locals.userid) {
17
+ return { status: 401 };
18
+ }
19
+
20
+ const res = await fetch(`${base}/${resource}`, {
21
+ method: request.method,
22
+ headers: {
23
+ 'content-type': 'application/json'
24
+ },
25
+ body: data && JSON.stringify(data)
26
+ });
27
+
28
+ // if the request came from a <form> submission, the browser's default
29
+ // behaviour is to show the URL corresponding to the form's "action"
30
+ // attribute. in those cases, we want to redirect them back to the
31
+ // /todos page, rather than showing the response
32
+ if (res.ok && request.method !== 'GET' && request.headers.accept !== 'application/json') {
33
+ return {
34
+ status: 303,
35
+ headers: {
36
+ location: '/todos'
37
+ }
38
+ };
39
+ }
40
+
41
+ return {
42
+ status: res.status,
43
+ body: await res.json()
44
+ };
45
+ }
src/routes/todos/index.json.js ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { api } from './_api';
2
+
3
+ // GET /todos.json
4
+ export const get = async (request) => {
5
+ // request.locals.userid comes from src/hooks.js
6
+ const response = await api(request, `todos/${request.locals.userid}`);
7
+
8
+ if (response.status === 404) {
9
+ // user hasn't created a todo list.
10
+ // start with an empty array
11
+ return { body: [] };
12
+ }
13
+
14
+ return response;
15
+ };
16
+
17
+ // POST /todos.json
18
+ export const post = async (request) => {
19
+ const response = await api(request, `todos/${request.locals.userid}`, {
20
+ // because index.svelte posts a FormData object,
21
+ // request.body is _also_ a (readonly) FormData
22
+ // object, which allows us to get form data
23
+ // with the `body.get(key)` method
24
+ text: request.body.get('text')
25
+ });
26
+
27
+ return response;
28
+ };
src/routes/todos/index.svelte ADDED
@@ -0,0 +1,220 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script context="module">
2
+ import { enhance } from '$lib/form';
3
+
4
+ // see https://kit.svelte.dev/docs#loading
5
+ export const load = async ({ fetch }) => {
6
+ const res = await fetch('/todos.json');
7
+
8
+ if (res.ok) {
9
+ const todos = await res.json();
10
+
11
+ return {
12
+ props: { todos }
13
+ };
14
+ }
15
+
16
+ const { message } = await res.json();
17
+
18
+ return {
19
+ error: new Error(message)
20
+ };
21
+ };
22
+ </script>
23
+
24
+ <script>
25
+ import { scale } from 'svelte/transition';
26
+ import { flip } from 'svelte/animate';
27
+
28
+ export let todos;
29
+
30
+ async function patch(res) {
31
+ const todo = await res.json();
32
+
33
+ todos = todos.map((t) => {
34
+ if (t.uid === todo.uid) return todo;
35
+ return t;
36
+ });
37
+ }
38
+ </script>
39
+
40
+ <svelte:head>
41
+ <title>Todos</title>
42
+ </svelte:head>
43
+
44
+ <div class="todos">
45
+ <h1>Todos</h1>
46
+
47
+ <form
48
+ class="new"
49
+ action="/todos.json"
50
+ method="post"
51
+ use:enhance={{
52
+ result: async (res, form) => {
53
+ const created = await res.json();
54
+ todos = [...todos, created];
55
+
56
+ form.reset();
57
+ }
58
+ }}
59
+ >
60
+ <input name="text" aria-label="Add todo" placeholder="+ tap to add a todo" />
61
+ </form>
62
+
63
+ {#each todos as todo (todo.uid)}
64
+ <div
65
+ class="todo"
66
+ class:done={todo.done}
67
+ transition:scale|local={{ start: 0.7 }}
68
+ animate:flip={{ duration: 200 }}
69
+ >
70
+ <form
71
+ action="/todos/{todo.uid}.json?_method=patch"
72
+ method="post"
73
+ use:enhance={{
74
+ pending: (data) => {
75
+ todo.done = !!data.get('done');
76
+ },
77
+ result: patch
78
+ }}
79
+ >
80
+ <input type="hidden" name="done" value={todo.done ? '' : 'true'} />
81
+ <button class="toggle" aria-label="Mark todo as {todo.done ? 'not done' : 'done'}" />
82
+ </form>
83
+
84
+ <form
85
+ class="text"
86
+ action="/todos/{todo.uid}.json?_method=patch"
87
+ method="post"
88
+ use:enhance={{
89
+ result: patch
90
+ }}
91
+ >
92
+ <input aria-label="Edit todo" type="text" name="text" value={todo.text} />
93
+ <button class="save" aria-label="Save todo" />
94
+ </form>
95
+
96
+ <form
97
+ action="/todos/{todo.uid}.json?_method=delete"
98
+ method="post"
99
+ use:enhance={{
100
+ pending: () => (todo.pending_delete = true),
101
+ result: () => {
102
+ todos = todos.filter((t) => t.uid !== todo.uid);
103
+ }
104
+ }}
105
+ >
106
+ <button class="delete" aria-label="Delete todo" disabled={todo.pending_delete} />
107
+ </form>
108
+ </div>
109
+ {/each}
110
+ </div>
111
+
112
+ <style>
113
+ .todos {
114
+ width: 100%;
115
+ max-width: var(--column-width);
116
+ margin: var(--column-margin-top) auto 0 auto;
117
+ line-height: 1;
118
+ }
119
+
120
+ .new {
121
+ margin: 0 0 0.5rem 0;
122
+ }
123
+
124
+ input {
125
+ border: 1px solid transparent;
126
+ }
127
+
128
+ input:focus-visible {
129
+ box-shadow: inset 1px 1px 6px rgba(0, 0, 0, 0.1);
130
+ border: 1px solid #ff3e00 !important;
131
+ outline: none;
132
+ }
133
+
134
+ .new input {
135
+ font-size: 28px;
136
+ width: 100%;
137
+ padding: 0.5em 1em 0.3em 1em;
138
+ box-sizing: border-box;
139
+ background: rgba(255, 255, 255, 0.05);
140
+ border-radius: 8px;
141
+ text-align: center;
142
+ }
143
+
144
+ .todo {
145
+ display: grid;
146
+ grid-template-columns: 2rem 1fr 2rem;
147
+ grid-gap: 0.5rem;
148
+ align-items: center;
149
+ margin: 0 0 0.5rem 0;
150
+ padding: 0.5rem;
151
+ background-color: white;
152
+ border-radius: 8px;
153
+ filter: drop-shadow(2px 4px 6px rgba(0, 0, 0, 0.1));
154
+ transform: translate(-1px, -1px);
155
+ transition: filter 0.2s, transform 0.2s;
156
+ }
157
+
158
+ .done {
159
+ transform: none;
160
+ opacity: 0.4;
161
+ filter: drop-shadow(0px 0px 1px rgba(0, 0, 0, 0.1));
162
+ }
163
+
164
+ form.text {
165
+ position: relative;
166
+ display: flex;
167
+ align-items: center;
168
+ flex: 1;
169
+ }
170
+
171
+ .todo input {
172
+ flex: 1;
173
+ padding: 0.5em 2em 0.5em 0.8em;
174
+ border-radius: 3px;
175
+ }
176
+
177
+ .todo button {
178
+ width: 2em;
179
+ height: 2em;
180
+ border: none;
181
+ background-color: transparent;
182
+ background-position: 50% 50%;
183
+ background-repeat: no-repeat;
184
+ }
185
+
186
+ button.toggle {
187
+ border: 1px solid rgba(0, 0, 0, 0.2);
188
+ border-radius: 50%;
189
+ box-sizing: border-box;
190
+ background-size: 1em auto;
191
+ }
192
+
193
+ .done .toggle {
194
+ 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");
195
+ }
196
+
197
+ .delete {
198
+ 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");
199
+ opacity: 0.2;
200
+ }
201
+
202
+ .delete:hover,
203
+ .delete:focus {
204
+ transition: opacity 0.2s;
205
+ opacity: 1;
206
+ }
207
+
208
+ .save {
209
+ position: absolute;
210
+ right: 0;
211
+ opacity: 0;
212
+ 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");
213
+ }
214
+
215
+ .todo input:focus + .save,
216
+ .save:focus {
217
+ transition: opacity 0.2s;
218
+ opacity: 1;
219
+ }
220
+ </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,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import adapter from '@sveltejs/adapter-auto';
2
+
3
+ /** @type {import('@sveltejs/kit').Config} */
4
+ const config = {
5
+ kit: {
6
+ adapter: adapter(),
7
+
8
+ // hydrate the <div id="svelte"> element in src/app.html
9
+ target: '#svelte'
10
+ }
11
+ };
12
+
13
+ export default config;