From 54dcbd126e8dfdf00988ac773929935d290f7c79 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 20 Dec 2020 17:34:08 -0500 Subject: [PATCH 01/28] store user info in JWT. yeah, yeah, i know --- examples/realworld.svelte.dev/package.json | 1 + .../src/routes/auth/_respond.js | 15 ++++++++++ .../src/routes/auth/login.js | 16 ++++++----- .../src/routes/auth/logout.js | 14 +++++++--- .../src/routes/auth/register.js | 18 ++++++------ .../src/routes/auth/save.js | 28 ++++++++++++------- .../src/routes/auth/user.js | 5 ---- .../realworld.svelte.dev/src/setup/index.js | 26 +++++++++++++---- pnpm-lock.yaml | 8 ++++++ 9 files changed, 90 insertions(+), 41 deletions(-) create mode 100644 examples/realworld.svelte.dev/src/routes/auth/_respond.js delete mode 100644 examples/realworld.svelte.dev/src/routes/auth/user.js diff --git a/examples/realworld.svelte.dev/package.json b/examples/realworld.svelte.dev/package.json index 55026c6a30aa..b6fa33501ff1 100644 --- a/examples/realworld.svelte.dev/package.json +++ b/examples/realworld.svelte.dev/package.json @@ -15,6 +15,7 @@ "svelte": "^3.29.0" }, "dependencies": { + "cookie": "^0.4.1", "node-fetch": "^2.6.1" } } diff --git a/examples/realworld.svelte.dev/src/routes/auth/_respond.js b/examples/realworld.svelte.dev/src/routes/auth/_respond.js new file mode 100644 index 000000000000..7fc8702a4a4c --- /dev/null +++ b/examples/realworld.svelte.dev/src/routes/auth/_respond.js @@ -0,0 +1,15 @@ +export function respond(body) { + if (body.errors) { + return { status: 401, body }; + } + + const json = JSON.stringify(body.user); + const value = Buffer(json).toString('base64'); + + return { + headers: { + 'set-cookie': `jwt=${value}; Path=/; HttpOnly` + }, + body + }; +} diff --git a/examples/realworld.svelte.dev/src/routes/auth/login.js b/examples/realworld.svelte.dev/src/routes/auth/login.js index 025e9afa3777..b924db56471a 100644 --- a/examples/realworld.svelte.dev/src/routes/auth/login.js +++ b/examples/realworld.svelte.dev/src/routes/auth/login.js @@ -1,11 +1,13 @@ import * as api from '$common/api.js'; +import { respond } from './_respond'; -export async function post({ body }) { - const response = await api.post('users/login', { - user: body +export async function post(request) { + const body = await api.post('users/login', { + user: { + email: request.body.email, + password: request.body.password + } }); - return { - body: response - }; -} \ No newline at end of file + return respond(body); +} diff --git a/examples/realworld.svelte.dev/src/routes/auth/logout.js b/examples/realworld.svelte.dev/src/routes/auth/logout.js index 9285627c4b8c..f1c3b2fb9ae8 100644 --- a/examples/realworld.svelte.dev/src/routes/auth/logout.js +++ b/examples/realworld.svelte.dev/src/routes/auth/logout.js @@ -1,4 +1,10 @@ -export function post(req, res) { - delete req.session.user; - res.end(JSON.stringify({ ok: true })); -} \ No newline at end of file +export function post() { + return { + headers: { + 'set-cookie': 'jwt=deleted; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT' + }, + body: { + ok: true + } + }; +} diff --git a/examples/realworld.svelte.dev/src/routes/auth/register.js b/examples/realworld.svelte.dev/src/routes/auth/register.js index 9774dd81a256..a6df6659043d 100644 --- a/examples/realworld.svelte.dev/src/routes/auth/register.js +++ b/examples/realworld.svelte.dev/src/routes/auth/register.js @@ -1,15 +1,13 @@ import * as api from '$common/api.js'; +import { respond } from './_respond'; -export function post(req, res) { - const user = req.body; +export async function post(request) { + const user = request.body; - api.post('users', { user }).then(response => { - if (response.user) { - req.session.user = response.user; - } + // TODO individual properties + console.log('user', user); - res.setHeader('Content-Type', 'application/json'); + const body = await api.post('users', { user }); - res.end(JSON.stringify(response)); - }); -} \ No newline at end of file + return respond(body); +} diff --git a/examples/realworld.svelte.dev/src/routes/auth/save.js b/examples/realworld.svelte.dev/src/routes/auth/save.js index 51fe7eb4bea4..3fc529b5df49 100644 --- a/examples/realworld.svelte.dev/src/routes/auth/save.js +++ b/examples/realworld.svelte.dev/src/routes/auth/save.js @@ -1,15 +1,23 @@ import * as api from '$common/api.js'; +import { respond } from './_respond'; -export function post(req, res) { - const user = req.body; +export async function post(request, context) { + const user = request.body; - api.put('user', { user }, req.session.user && req.session.user.token).then(response => { - if (response.user) { - req.session.user = response.user; - } + if (!context.user) { + return { + status: 401 + }; + } - res.setHeader('Content-Type', 'application/json'); + const { token } = context.user; + const body = await api.put( + 'user', + { + user // TODO individual properties + }, + token + ); - res.end(JSON.stringify(response)); - }); -} \ No newline at end of file + return respond(body); +} diff --git a/examples/realworld.svelte.dev/src/routes/auth/user.js b/examples/realworld.svelte.dev/src/routes/auth/user.js deleted file mode 100644 index dcbe9cbcb79b..000000000000 --- a/examples/realworld.svelte.dev/src/routes/auth/user.js +++ /dev/null @@ -1,5 +0,0 @@ -export function get(req, res) { - res.setHeader('Content-Type', 'application/json'); - - res.end(JSON.stringify({ user: req.session.user || null })); -} \ No newline at end of file diff --git a/examples/realworld.svelte.dev/src/setup/index.js b/examples/realworld.svelte.dev/src/setup/index.js index e0b5412cbcaf..32c83810c1dd 100644 --- a/examples/realworld.svelte.dev/src/setup/index.js +++ b/examples/realworld.svelte.dev/src/setup/index.js @@ -1,12 +1,28 @@ +import * as cookie from 'cookie'; + export function prepare(headers) { - // TODO + const cookies = cookie.parse(headers.cookie); + const jwt = cookies.jwt && Buffer(cookies.jwt, 'base64').toString('utf-8'); + return { - context: {}, + context: { + user: jwt && JSON.parse(jwt) + }, headers: {} }; } export function getSession(context) { - // TODO - return context; -} \ No newline at end of file + if (context.user) { + const { email, username, bio, image } = context.user; + + return { + user: { + email, + username, + bio, + image + } + }; + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7b09f38f26c9..654236953123 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -45,6 +45,7 @@ importers: svelte: ^3.29.0 examples/realworld.svelte.dev: dependencies: + cookie: 0.4.1 node-fetch: 2.6.1 devDependencies: '@sveltejs/adapter-node': 'link:../../packages/adapter-node' @@ -56,6 +57,7 @@ importers: '@sveltejs/adapter-node': 'workspace:*' '@sveltejs/kit': 'workspace:*' '@sveltejs/snowpack-config': 'workspace:*' + cookie: ^0.4.1 marked: ^1.2.2 node-fetch: ^2.6.1 svelte: ^3.29.0 @@ -1468,6 +1470,12 @@ packages: node: '>=0.10.0' resolution: integrity: sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo= + /cookie/0.4.1: + dev: false + engines: + node: '>= 0.6' + resolution: + integrity: sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== /cosmiconfig/7.0.0: dependencies: '@types/parse-json': 4.0.0 From f99169b9b4b3deaab28a67c4b6ff520a968aeadf Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 21 Dec 2020 10:55:18 -0500 Subject: [PATCH 02/28] use prefetching promise if available, reload on query change --- packages/kit/src/runtime/internal/renderer/index.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/kit/src/runtime/internal/renderer/index.js b/packages/kit/src/runtime/internal/renderer/index.js index 49757de62fae..1c42d84a8f0b 100644 --- a/packages/kit/src/runtime/internal/renderer/index.js +++ b/packages/kit/src/runtime/internal/renderer/index.js @@ -135,7 +135,10 @@ export class Renderer { this.stores.navigating.set(true); - const hydrated = await this.hydrate(selected); + const hydrated = + this.prefetching.href === selected.href + ? await this.prefetching.promise + : await this.hydrate(selected); if (this.token === token) { // check render wasn't aborted @@ -332,7 +335,7 @@ export class Renderer { } }); - if (!this.current || state.path !== this.current.path) { + if (!this.current || changed.query || state.path !== this.current.path) { props.page = page; } } catch (error) { From c7efcb7f521cc906ebe03fa040a8fc489190156d Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 21 Dec 2020 10:55:40 -0500 Subject: [PATCH 03/28] inject credentials in server-side fetch --- packages/kit/package.json | 1 + packages/kit/src/renderer/endpoint.js | 12 ++----- packages/kit/src/renderer/index.js | 8 +++-- packages/kit/src/renderer/page.js | 47 +++++++++++++++++++++++++-- packages/kit/src/renderer/utils.js | 7 ++++ 5 files changed, 60 insertions(+), 15 deletions(-) create mode 100644 packages/kit/src/renderer/utils.js diff --git a/packages/kit/package.json b/packages/kit/package.json index a99a679d107d..51437328f9b1 100644 --- a/packages/kit/package.json +++ b/packages/kit/package.json @@ -18,6 +18,7 @@ "@types/rimraf": "^3.0.0", "@types/sade": "^1.7.2", "amphtml-validator": "^1.0.33", + "cookie": "^0.4.1", "eslint": "^7.14.0", "esm": "^3.2.25", "estree-walker": "^2.0.1", diff --git a/packages/kit/src/renderer/endpoint.js b/packages/kit/src/renderer/endpoint.js index 088ec399e645..73f2efb3daf2 100644 --- a/packages/kit/src/renderer/endpoint.js +++ b/packages/kit/src/renderer/endpoint.js @@ -1,3 +1,5 @@ +import { normalize_headers } from './utils'; + export default function render_route(request, context, options) { const route = options.manifest.endpoints.find((route) => route.pattern.test(request.path)); if (!route) return null; @@ -36,7 +38,7 @@ export default function render_route(request, context, options) { let { status = 200, body, headers = {} } = response; - headers = lowercase_keys(headers); + headers = normalize_headers(headers); if ( (typeof body === 'object' && !('content-type' in headers)) || @@ -63,11 +65,3 @@ export default function render_route(request, context, options) { } }); } - -function lowercase_keys(obj) { - const clone = {}; - for (const key in obj) { - clone[key.toLowerCase()] = obj[key]; - } - return clone; -} diff --git a/packages/kit/src/renderer/index.js b/packages/kit/src/renderer/index.js index a0bf22df0d2b..95261eb2c3c0 100644 --- a/packages/kit/src/renderer/index.js +++ b/packages/kit/src/renderer/index.js @@ -1,6 +1,7 @@ import { createHash } from 'crypto'; import render_page from './page'; import render_endpoint from './endpoint'; +import { normalize_headers } from './utils'; function md5(body) { return createHash('md5').update(body).digest('hex'); @@ -18,12 +19,13 @@ export async function render(request, options) { }; } - const { context, headers = {} } = - (await (options.setup.prepare && options.setup.prepare(request.headers))) || {}; + const prepared = (await (options.setup.prepare && options.setup.prepare(request.headers))) || {}; + const context = prepared.context; + const headers = normalize_headers(prepared.headers); try { const response = await (render_endpoint(request, context, options) || - render_page(request, context, options)); + render_page(request, context, options, headers)); if (response) { // inject ETags for 200 responses diff --git a/packages/kit/src/renderer/page.js b/packages/kit/src/renderer/page.js index b1ce8193b3c6..62eb82a9095d 100644 --- a/packages/kit/src/renderer/page.js +++ b/packages/kit/src/renderer/page.js @@ -1,10 +1,19 @@ import devalue from 'devalue'; import fetch, { Response } from 'node-fetch'; import { writable } from 'svelte/store'; +import * as cookie from 'cookie'; import { parse, resolve, URLSearchParams } from 'url'; import { render } from './index'; -async function get_response({ request, options, $session, route, status = 200, error }) { +async function get_response({ + request, + options, + response_headers, + $session, + route, + status = 200, + error +}) { const host = options.host || request.headers[options.host_header]; const dependencies = {}; @@ -64,11 +73,41 @@ async function get_response({ request, options, $session, route, status = 200, e } if (!response) { + const headers = opts.headers ? { ...opts.headers } : {}; + + if (opts.credentials !== 'omit') { + const cookies = Object.assign( + {}, + cookie.parse(request.headers.cookie || ''), + cookie.parse(headers.cookie || '') + ); + + // In some cases, a cookie might be set in `src/setup.js`, in which + // case we need to honour it if a credentialled fetch is made immediately + const set_cookie = response_headers['set-cookie']; + if (set_cookie) { + set_cookie.split(', ').forEach((s) => { + const m = /([^=]+)=([^;]+)/.exec(s); + if (m) cookies[m[1]] = m[2]; + }); + } + + const str = Object.keys(cookies) + .map((key) => `${key}=${cookies[key]}`) + .join('; '); + + headers.cookie = str; + + if (!headers.authorization && request.headers.authorization) { + headers.authorization = request.headers.authorization; + } + } + const rendered = await render( { host: request.host, method: opts.method || 'GET', - headers: opts.headers || {}, // TODO inject credentials... + headers, path: resolved, body: opts.body, query: new URLSearchParams(parsed.query || '') @@ -262,7 +301,7 @@ async function get_response({ request, options, $session, route, status = 200, e }; } -export default async function render_page(request, context, options) { +export default async function render_page(request, context, options, response_headers) { const route = options.manifest.pages.find((route) => route.pattern.test(request.path)); const $session = await (options.setup.getSession && options.setup.getSession(context)); @@ -277,6 +316,7 @@ export default async function render_page(request, context, options) { return await get_response({ request, options, + response_headers, $session, route, status: 200, @@ -291,6 +331,7 @@ export default async function render_page(request, context, options) { return await get_response({ request, options, + response_headers, $session, route, status, diff --git a/packages/kit/src/renderer/utils.js b/packages/kit/src/renderer/utils.js new file mode 100644 index 000000000000..a908a89d381d --- /dev/null +++ b/packages/kit/src/renderer/utils.js @@ -0,0 +1,7 @@ +export function normalize_headers(headers) { + const normalized = {}; + for (const key in headers) { + normalized[key.toLowerCase()] = headers[key]; + } + return normalized; +} From e003e75c9f631ab4dcaeb63217db4981bc336e51 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 21 Dec 2020 10:56:14 -0500 Subject: [PATCH 04/28] start updating realworld example --- .../realworld.svelte.dev/src/common/api.js | 13 +-- .../src/common/constants.js | 1 + .../ArticleList/ListPagination.svelte | 26 ----- .../src/components/ArticleList/index.svelte | 57 ++-------- .../src/components/Home.svelte | 47 -------- .../src/components/MainView/index.svelte | 54 ---------- .../src/components/Nav.svelte | 28 +++-- .../src/components/Pagination.svelte | 24 +++++ .../src/components/Tags.svelte | 18 ---- .../src/routes/articles.json.js | 31 ++++++ .../src/routes/auth/_respond.js | 2 +- .../src/routes/index.svelte | 100 +++++++++++++++++- .../src/routes/profile/[user]/$layout.svelte | 97 +++++++++++++++++ .../src/routes/profile/[user]/[view].svelte | 23 ---- .../src/routes/profile/[user]/_Profile.svelte | 74 ------------- .../src/routes/profile/[user]/_load.js | 22 ++++ .../routes/profile/[user]/favorites.svelte | 13 +++ .../src/routes/profile/[user]/index.svelte | 23 ++-- .../src/routes/profile/index.svelte | 2 +- .../src/routes/settings/_SettingsForm.svelte | 48 ++++++--- .../src/routes/settings/index.svelte | 20 ++-- .../src/routes/tags.json.js | 19 ++++ .../realworld.svelte.dev/src/setup/index.js | 4 +- pnpm-lock.yaml | 6 +- 24 files changed, 397 insertions(+), 355 deletions(-) create mode 100644 examples/realworld.svelte.dev/src/common/constants.js delete mode 100644 examples/realworld.svelte.dev/src/components/ArticleList/ListPagination.svelte delete mode 100644 examples/realworld.svelte.dev/src/components/Home.svelte delete mode 100644 examples/realworld.svelte.dev/src/components/MainView/index.svelte create mode 100644 examples/realworld.svelte.dev/src/components/Pagination.svelte delete mode 100644 examples/realworld.svelte.dev/src/components/Tags.svelte create mode 100644 examples/realworld.svelte.dev/src/routes/articles.json.js create mode 100644 examples/realworld.svelte.dev/src/routes/profile/[user]/$layout.svelte delete mode 100644 examples/realworld.svelte.dev/src/routes/profile/[user]/[view].svelte delete mode 100644 examples/realworld.svelte.dev/src/routes/profile/[user]/_Profile.svelte create mode 100644 examples/realworld.svelte.dev/src/routes/profile/[user]/_load.js create mode 100644 examples/realworld.svelte.dev/src/routes/profile/[user]/favorites.svelte create mode 100644 examples/realworld.svelte.dev/src/routes/tags.json.js diff --git a/examples/realworld.svelte.dev/src/common/api.js b/examples/realworld.svelte.dev/src/common/api.js index 610aaa4e4ec9..58b93be8dbd1 100644 --- a/examples/realworld.svelte.dev/src/common/api.js +++ b/examples/realworld.svelte.dev/src/common/api.js @@ -12,13 +12,14 @@ async function send({ method, path, data, token }) { opts.headers['Authorization'] = `Token ${token}`; } - const fetch = typeof window !== 'undefined' - ? window.fetch - : await import('node-fetch').then(mod => mod.default) + const fetch = + typeof window !== 'undefined' + ? window.fetch + : await import('node-fetch').then((mod) => mod.default); return fetch(`${base}/${path}`, opts) - .then(r => r.text()) - .then(json => { + .then((r) => r.text()) + .then((json) => { try { return JSON.parse(json); } catch (err) { @@ -41,4 +42,4 @@ export function post(path, data, token) { export function put(path, data, token) { return send({ method: 'PUT', path, data, token }); -} \ No newline at end of file +} diff --git a/examples/realworld.svelte.dev/src/common/constants.js b/examples/realworld.svelte.dev/src/common/constants.js new file mode 100644 index 000000000000..a666ee388147 --- /dev/null +++ b/examples/realworld.svelte.dev/src/common/constants.js @@ -0,0 +1 @@ +export const page_size = 10; diff --git a/examples/realworld.svelte.dev/src/components/ArticleList/ListPagination.svelte b/examples/realworld.svelte.dev/src/components/ArticleList/ListPagination.svelte deleted file mode 100644 index 76a6b3bd7ef8..000000000000 --- a/examples/realworld.svelte.dev/src/components/ArticleList/ListPagination.svelte +++ /dev/null @@ -1,26 +0,0 @@ - - -{#if articlesCount > 10} - -{/if} \ No newline at end of file diff --git a/examples/realworld.svelte.dev/src/components/ArticleList/index.svelte b/examples/realworld.svelte.dev/src/components/ArticleList/index.svelte index d76ddc5d0855..aca1e097520f 100644 --- a/examples/realworld.svelte.dev/src/components/ArticleList/index.svelte +++ b/examples/realworld.svelte.dev/src/components/ArticleList/index.svelte @@ -1,53 +1,16 @@ -{#if articles} - {#if articles.length === 0} -
- No articles are here... yet. -
- {:else} -
- {#each articles as article (article.slug)} - - {/each} - - -
- {/if} +{#if articles.length === 0} +
No articles are here... yet.
{:else} -
Loading...
-{/if} \ No newline at end of file +
+ {#each articles as article (article.slug)} + + {/each} +
+{/if} diff --git a/examples/realworld.svelte.dev/src/components/Home.svelte b/examples/realworld.svelte.dev/src/components/Home.svelte deleted file mode 100644 index 2b4f14a4bb11..000000000000 --- a/examples/realworld.svelte.dev/src/components/Home.svelte +++ /dev/null @@ -1,47 +0,0 @@ - - - - Conduit - - -
- - -
-
- - -
- -
-
-
-
\ No newline at end of file diff --git a/examples/realworld.svelte.dev/src/components/MainView/index.svelte b/examples/realworld.svelte.dev/src/components/MainView/index.svelte deleted file mode 100644 index df1ed7191155..000000000000 --- a/examples/realworld.svelte.dev/src/components/MainView/index.svelte +++ /dev/null @@ -1,54 +0,0 @@ - - -
-
- -
- - -
\ No newline at end of file diff --git a/examples/realworld.svelte.dev/src/components/Nav.svelte b/examples/realworld.svelte.dev/src/components/Nav.svelte index 2b74d7c184b4..b7e614a8d6da 100644 --- a/examples/realworld.svelte.dev/src/components/Nav.svelte +++ b/examples/realworld.svelte.dev/src/components/Nav.svelte @@ -4,44 +4,52 @@ \ No newline at end of file + diff --git a/examples/realworld.svelte.dev/src/components/Pagination.svelte b/examples/realworld.svelte.dev/src/components/Pagination.svelte new file mode 100644 index 000000000000..87992c84e4a7 --- /dev/null +++ b/examples/realworld.svelte.dev/src/components/Pagination.svelte @@ -0,0 +1,24 @@ + + +{#if pages > 1} + +{/if} diff --git a/examples/realworld.svelte.dev/src/components/Tags.svelte b/examples/realworld.svelte.dev/src/components/Tags.svelte deleted file mode 100644 index ca165aa85427..000000000000 --- a/examples/realworld.svelte.dev/src/components/Tags.svelte +++ /dev/null @@ -1,18 +0,0 @@ - - -{#if tags} -
- {#each tags as tag} - - {tag} - - {/each} -
-{:else} -
Loading Tags...
-{/if} \ No newline at end of file diff --git a/examples/realworld.svelte.dev/src/routes/articles.json.js b/examples/realworld.svelte.dev/src/routes/articles.json.js new file mode 100644 index 000000000000..09dba5ed48fa --- /dev/null +++ b/examples/realworld.svelte.dev/src/routes/articles.json.js @@ -0,0 +1,31 @@ +import * as api from '$common/api'; +import { page_size } from '$common/constants'; + +export async function get(request, context) { + const tab = request.query.get('tab') || 'all'; + const tag = request.query.get('tag'); + const page = +request.query.get('page') || 1; + + const endpoint = tab === 'feed' ? 'articles/feed' : 'articles'; + + const q = new URLSearchParams(); + + q.set('limit', page_size); + q.set('offset', (page - 1) * page_size); + + if (tag) { + q.set('tag', tag); + } + + const { articles, articlesCount } = await api.get( + `${endpoint}?${q}`, + context.user && context.user.token + ); + + return { + body: { + articles, + pages: Math.ceil(articlesCount / page_size) + } + }; +} diff --git a/examples/realworld.svelte.dev/src/routes/auth/_respond.js b/examples/realworld.svelte.dev/src/routes/auth/_respond.js index 7fc8702a4a4c..cf3e8187a7a0 100644 --- a/examples/realworld.svelte.dev/src/routes/auth/_respond.js +++ b/examples/realworld.svelte.dev/src/routes/auth/_respond.js @@ -4,7 +4,7 @@ export function respond(body) { } const json = JSON.stringify(body.user); - const value = Buffer(json).toString('base64'); + const value = Buffer.from(json).toString('base64'); return { headers: { diff --git a/examples/realworld.svelte.dev/src/routes/index.svelte b/examples/realworld.svelte.dev/src/routes/index.svelte index b4aa70cc9985..f5f83b5a43bb 100644 --- a/examples/realworld.svelte.dev/src/routes/index.svelte +++ b/examples/realworld.svelte.dev/src/routes/index.svelte @@ -1,5 +1,101 @@ + + - \ No newline at end of file + + Conduit + + +
+ {#if !$session.user} + + {/if} + +
+
+
+
+ +
+ + + `/?${page_link_base}&page=${p}`} /> +
+ +
+ +
+
+
+
diff --git a/examples/realworld.svelte.dev/src/routes/profile/[user]/$layout.svelte b/examples/realworld.svelte.dev/src/routes/profile/[user]/$layout.svelte new file mode 100644 index 000000000000..40c8338835c7 --- /dev/null +++ b/examples/realworld.svelte.dev/src/routes/profile/[user]/$layout.svelte @@ -0,0 +1,97 @@ + + + + + + {profile.username} • Conduit + + +
+ + +
+
+
+
+ +
+ + +
+
+
+
diff --git a/examples/realworld.svelte.dev/src/routes/profile/[user]/[view].svelte b/examples/realworld.svelte.dev/src/routes/profile/[user]/[view].svelte deleted file mode 100644 index 13d3cea66b47..000000000000 --- a/examples/realworld.svelte.dev/src/routes/profile/[user]/[view].svelte +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - {profile.username} • Conduit - - - \ No newline at end of file diff --git a/examples/realworld.svelte.dev/src/routes/profile/[user]/_Profile.svelte b/examples/realworld.svelte.dev/src/routes/profile/[user]/_Profile.svelte deleted file mode 100644 index ca372f3bc638..000000000000 --- a/examples/realworld.svelte.dev/src/routes/profile/[user]/_Profile.svelte +++ /dev/null @@ -1,74 +0,0 @@ - - - - {profile.username} • Conduit - - -
- - -
-
-
-
- -
- - -
-
-
-
\ No newline at end of file diff --git a/examples/realworld.svelte.dev/src/routes/profile/[user]/_load.js b/examples/realworld.svelte.dev/src/routes/profile/[user]/_load.js new file mode 100644 index 000000000000..be0890310616 --- /dev/null +++ b/examples/realworld.svelte.dev/src/routes/profile/[user]/_load.js @@ -0,0 +1,22 @@ +import * as api from '$common/api.js'; + +const page_size = 10; + +export function create_load(type) { + return async ({ page }) => { + const username = page.params.user.slice(1); + const p = +page.query.get('p') || 1; + + const q = new URLSearchParams([ + ['limit', page_size], + ['offset', (p - 1) * page_size], + [type, encodeURIComponent(username)] + ]); + + const { articles, articlesCount } = await api.get(`articles?${q}`); + + return { + props: { articles, articlesCount } + }; + }; +} diff --git a/examples/realworld.svelte.dev/src/routes/profile/[user]/favorites.svelte b/examples/realworld.svelte.dev/src/routes/profile/[user]/favorites.svelte new file mode 100644 index 000000000000..b61aed8e93f9 --- /dev/null +++ b/examples/realworld.svelte.dev/src/routes/profile/[user]/favorites.svelte @@ -0,0 +1,13 @@ + + + + + diff --git a/examples/realworld.svelte.dev/src/routes/profile/[user]/index.svelte b/examples/realworld.svelte.dev/src/routes/profile/[user]/index.svelte index 9474f2085655..311ee98b79d2 100644 --- a/examples/realworld.svelte.dev/src/routes/profile/[user]/index.svelte +++ b/examples/realworld.svelte.dev/src/routes/profile/[user]/index.svelte @@ -1,24 +1,13 @@ - - {profile.username} • Conduit - - - \ No newline at end of file + diff --git a/examples/realworld.svelte.dev/src/routes/profile/index.svelte b/examples/realworld.svelte.dev/src/routes/profile/index.svelte index 3d38f9467809..bf61ede95fb2 100644 --- a/examples/realworld.svelte.dev/src/routes/profile/index.svelte +++ b/examples/realworld.svelte.dev/src/routes/profile/index.svelte @@ -2,7 +2,7 @@ export function load({ session }) { return { redirect: { - to: session.user ? `/profile/@${user.username}` : '/login', + to: session.user ? `/profile/@${session.user.username}` : '/login', status: 302 } }; diff --git a/examples/realworld.svelte.dev/src/routes/settings/_SettingsForm.svelte b/examples/realworld.svelte.dev/src/routes/settings/_SettingsForm.svelte index 6a98a1c1a108..216752a5018a 100644 --- a/examples/realworld.svelte.dev/src/routes/settings/_SettingsForm.svelte +++ b/examples/realworld.svelte.dev/src/routes/settings/_SettingsForm.svelte @@ -1,38 +1,60 @@ -
+ dispatch('save', { image, username, bio, email, password })}>
- +
- +
-