mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-09 21:07:46 +00:00
[examples] Update todo functionality in the "sveltekit" example (#7506)
This commit is contained in:
670
examples/sveltekit/package-lock.json
generated
670
examples/sveltekit/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "sveltekit",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"dev": "svelte-kit dev",
|
||||
"build": "svelte-kit build",
|
||||
@@ -9,7 +11,7 @@
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "next",
|
||||
"@sveltejs/kit": "next",
|
||||
"svelte": "^3.44.0"
|
||||
"svelte": "^3.46.0"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
@@ -8,6 +8,6 @@
|
||||
%svelte.head%
|
||||
</head>
|
||||
<body>
|
||||
<div id="svelte">%svelte.body%</div>
|
||||
<div>%svelte.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
1
examples/sveltekit/src/global.d.ts
vendored
1
examples/sveltekit/src/global.d.ts
vendored
@@ -1 +0,0 @@
|
||||
/// <reference types="@sveltejs/kit" />
|
||||
@@ -1,19 +1,22 @@
|
||||
import cookie from 'cookie';
|
||||
import { v4 as uuid } from '@lukeed/uuid';
|
||||
|
||||
export const handle = async ({ request, resolve }) => {
|
||||
const cookies = cookie.parse(request.headers.cookie || '');
|
||||
request.locals.userid = cookies.userid || uuid();
|
||||
export const handle = async ({ event, resolve }) => {
|
||||
const cookies = cookie.parse(event.request.headers.get('cookie') || '');
|
||||
event.locals.userid = cookies.userid || uuid();
|
||||
|
||||
const response = await resolve(request);
|
||||
const response = await resolve(event);
|
||||
|
||||
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'] = cookie.serialize('userid', request.locals.userid, {
|
||||
path: '/',
|
||||
httpOnly: true
|
||||
});
|
||||
response.headers.set(
|
||||
'set-cookie',
|
||||
cookie.serialize('userid', event.locals.userid, {
|
||||
path: '/',
|
||||
httpOnly: true
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return response;
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
<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 class="hidden" aria-hidden="true">{Math.floor($displayed_count + 1)}</strong>
|
||||
<strong>{Math.floor($displayed_count)}</strong>
|
||||
</div>
|
||||
</div>
|
||||
@@ -94,4 +94,9 @@
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
top: -100%;
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { invalidate } from '$app/navigation';
|
||||
|
||||
// this action (https://svelte.dev/tutorial/actions) allows us to
|
||||
// progressively enhance a <form> that already works without JS
|
||||
export function enhance(form, { pending, error, result }) {
|
||||
export function enhance(form, { pending, error, result } = {}) {
|
||||
let current_token;
|
||||
|
||||
async function handle_submit(e) {
|
||||
@@ -8,31 +10,35 @@ export function enhance(form, { pending, error, result }) {
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
const body = new FormData(form);
|
||||
const data = new FormData(form);
|
||||
|
||||
if (pending) pending(body, form);
|
||||
if (pending) pending({ data, form });
|
||||
|
||||
try {
|
||||
const res = await fetch(form.action, {
|
||||
const response = await fetch(form.action, {
|
||||
method: form.method,
|
||||
headers: {
|
||||
accept: 'application/json'
|
||||
},
|
||||
body
|
||||
body: data
|
||||
});
|
||||
|
||||
if (token !== current_token) return;
|
||||
|
||||
if (res.ok) {
|
||||
result(res, form);
|
||||
if (response.ok) {
|
||||
if (result) result({ data, form, response });
|
||||
|
||||
const url = new URL(form.action);
|
||||
url.search = url.hash = '';
|
||||
invalidate(url.href);
|
||||
} else if (error) {
|
||||
error(res, null, form);
|
||||
error({ data, form, error: null, response });
|
||||
} else {
|
||||
console.error(await res.text());
|
||||
console.error(await response.text());
|
||||
}
|
||||
} catch (e) {
|
||||
if (error) {
|
||||
error(null, e, form);
|
||||
error({ data, form, error: e, response: null });
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import { api } from './_api';
|
||||
|
||||
// PATCH /todos/:uid.json
|
||||
export const patch = 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 = async (request) => {
|
||||
return api(request, `todos/${request.locals.userid}/${request.params.uid}`);
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
This module is used by the /todos.json and /todos/[uid].json
|
||||
endpoints to make calls to api.svelte.dev, which stores todos
|
||||
This module is used by the /todos endpoint 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.
|
||||
@@ -11,35 +11,12 @@
|
||||
|
||||
const base = 'https://api.svelte.dev';
|
||||
|
||||
export async function api(request, resource, data) {
|
||||
// user must have a cookie set
|
||||
if (!request.locals.userid) {
|
||||
return { status: 401 };
|
||||
}
|
||||
|
||||
const res = await fetch(`${base}/${resource}`, {
|
||||
method: request.method,
|
||||
export function api(method, resource, data) {
|
||||
return fetch(`${base}/${resource}`, {
|
||||
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()
|
||||
};
|
||||
}
|
||||
|
||||
66
examples/sveltekit/src/routes/todos/index.js
Normal file
66
examples/sveltekit/src/routes/todos/index.js
Normal file
@@ -0,0 +1,66 @@
|
||||
import { api } from './_api';
|
||||
|
||||
export const get = async ({ locals }) => {
|
||||
// locals.userid comes from src/hooks.js
|
||||
const response = await api('get', `todos/${locals.userid}`);
|
||||
|
||||
if (response.status === 404) {
|
||||
// user hasn't created a todo list.
|
||||
// start with an empty array
|
||||
return {
|
||||
body: {
|
||||
todos: []
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (response.status === 200) {
|
||||
return {
|
||||
body: {
|
||||
todos: await response.json()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: response.status
|
||||
};
|
||||
};
|
||||
|
||||
export const post = async ({ request, locals }) => {
|
||||
const form = await request.formData();
|
||||
|
||||
await api('post', `todos/${locals.userid}`, {
|
||||
text: form.get('text')
|
||||
});
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
// If the user has JavaScript disabled, the URL will change to
|
||||
// include the method override unless we redirect back to /todos
|
||||
const redirect = {
|
||||
status: 303,
|
||||
headers: {
|
||||
location: '/todos'
|
||||
}
|
||||
};
|
||||
|
||||
export const patch = async ({ request, locals }) => {
|
||||
const form = await request.formData();
|
||||
|
||||
await api('patch', `todos/${locals.userid}/${form.get('uid')}`, {
|
||||
text: form.has('text') ? form.get('text') : undefined,
|
||||
done: form.has('done') ? !!form.get('done') : undefined
|
||||
});
|
||||
|
||||
return redirect;
|
||||
};
|
||||
|
||||
export const del = async ({ request, locals }) => {
|
||||
const form = await request.formData();
|
||||
|
||||
await api('delete', `todos/${locals.userid}/${form.get('uid')}`);
|
||||
|
||||
return redirect;
|
||||
};
|
||||
@@ -1,28 +0,0 @@
|
||||
import { api } from './_api';
|
||||
|
||||
// GET /todos.json
|
||||
export const get = 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 = async (request) => {
|
||||
const response = await api(request, `todos/${request.locals.userid}`, {
|
||||
// because index.svelte posts a FormData object,
|
||||
// request.body is _also_ a (readonly) FormData
|
||||
// object, which allows us to get form data
|
||||
// with the `body.get(key)` method
|
||||
text: request.body.get('text')
|
||||
});
|
||||
|
||||
return response;
|
||||
};
|
||||
@@ -1,40 +1,9 @@
|
||||
<script context="module">
|
||||
import { enhance } from '$lib/form';
|
||||
|
||||
// see https://kit.svelte.dev/docs#loading
|
||||
export const 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>
|
||||
import { enhance } from '$lib/form';
|
||||
import { scale } from 'svelte/transition';
|
||||
import { flip } from 'svelte/animate';
|
||||
|
||||
export let todos;
|
||||
|
||||
async function patch(res) {
|
||||
const todo = await res.json();
|
||||
|
||||
todos = todos.map((t) => {
|
||||
if (t.uid === todo.uid) return todo;
|
||||
return t;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -46,13 +15,10 @@
|
||||
|
||||
<form
|
||||
class="new"
|
||||
action="/todos.json"
|
||||
action="/todos"
|
||||
method="post"
|
||||
use:enhance={{
|
||||
result: async (res, form) => {
|
||||
const created = await res.json();
|
||||
todos = [...todos, created];
|
||||
|
||||
result: async ({ form }) => {
|
||||
form.reset();
|
||||
}
|
||||
}}
|
||||
@@ -68,41 +34,33 @@
|
||||
animate:flip={{ duration: 200 }}
|
||||
>
|
||||
<form
|
||||
action="/todos/{todo.uid}.json?_method=PATCH"
|
||||
action="/todos?_method=PATCH"
|
||||
method="post"
|
||||
use:enhance={{
|
||||
pending: (data) => {
|
||||
pending: ({ data }) => {
|
||||
todo.done = !!data.get('done');
|
||||
},
|
||||
result: patch
|
||||
}
|
||||
}}
|
||||
>
|
||||
<input type="hidden" name="uid" value={todo.uid} />
|
||||
<input type="hidden" name="done" value={todo.done ? '' : 'true'} />
|
||||
<button class="toggle" aria-label="Mark todo as {todo.done ? 'not done' : 'done'}" />
|
||||
</form>
|
||||
|
||||
<form
|
||||
class="text"
|
||||
action="/todos/{todo.uid}.json?_method=PATCH"
|
||||
method="post"
|
||||
use:enhance={{
|
||||
result: patch
|
||||
}}
|
||||
>
|
||||
<form class="text" action="/todos?_method=PATCH" method="post" use:enhance>
|
||||
<input type="hidden" name="uid" value={todo.uid} />
|
||||
<input aria-label="Edit todo" type="text" name="text" value={todo.text} />
|
||||
<button class="save" aria-label="Save todo" />
|
||||
</form>
|
||||
|
||||
<form
|
||||
action="/todos/{todo.uid}.json?_method=DELETE"
|
||||
action="/todos?_method=DELETE"
|
||||
method="post"
|
||||
use:enhance={{
|
||||
pending: () => (todo.pending_delete = true),
|
||||
result: () => {
|
||||
todos = todos.filter((t) => t.uid !== todo.uid);
|
||||
}
|
||||
pending: () => (todo.pending_delete = true)
|
||||
}}
|
||||
>
|
||||
<input type="hidden" name="uid" value={todo.uid} />
|
||||
<button class="delete" aria-label="Delete todo" disabled={todo.pending_delete} />
|
||||
</form>
|
||||
</div>
|
||||
@@ -158,7 +116,7 @@
|
||||
.done {
|
||||
transform: none;
|
||||
opacity: 0.4;
|
||||
filter: drop-shadow(0px 0px 1px rgba(0, 0, 0, 0.1));
|
||||
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.1));
|
||||
}
|
||||
|
||||
form.text {
|
||||
|
||||
@@ -5,8 +5,10 @@ const config = {
|
||||
kit: {
|
||||
adapter: adapter(),
|
||||
|
||||
// hydrate the <div id="svelte"> element in src/app.html
|
||||
target: '#svelte'
|
||||
// Override http methods in the Todo forms
|
||||
methodOverride: {
|
||||
allowed: ['PATCH', 'DELETE']
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user