[site] migrate to SvelteKit (#6811)

Co-authored-by: Conduitry <git@chor.date>
Co-authored-by: bluwy <bjornlu.dev@gmail.com>
pull/6865/head
Ben McCann 3 years ago committed by GitHub
parent 883c47b45d
commit 42076a7502
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

10
.gitignore vendored

@ -24,15 +24,13 @@ _actual*.*
_output
/types
/site/cypress/screenshots/
/site/__sapper__/
/site/.svelte-kit/
/site/build/
/site/.env
/site/.sessions
/site/static/svelte-app.json
/site/static/contributors.jpg
/site/static/donors.jpg
/site/static/workers
/site/scripts/svelte-app
/site/scripts/community
/site/static/workers/
/site/scripts/svelte-app/
/site/src/routes/_contributors.js
/site/src/routes/_donors.js

@ -2,8 +2,6 @@
!/Dockerfile
!/package.json
!/package-lock.json
!/__sapper__
/__sapper__/*
!/__sapper__/build
!/build
!/static
!/content

@ -1,13 +1,13 @@
NODE_ENV=
PORT=
BASEURL=
PORT=3000
BASEURL=http://localhost:3000
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
MAPBOX_ACCESS_TOKEN=
VITE_MAPBOX_ACCESS_TOKEN=
PGHOST=hostname
PGPORT=port
PGHOST=localhost
PGPORT=5432
PGUSER=username
PGPASSWORD=password
PGDATABASE=database_name

@ -1,11 +0,0 @@
sudo: false
language: node_js
node_js:
- "stable"
env:
global:
- BUILD_TIMEOUT=10000
install:
- npm install
- npm install cypress

@ -1,7 +1,7 @@
# IMPORTANT: Don't use this Dockerfile in your own Sapper projects without also looking at the .dockerignore file.
# IMPORTANT: Don't use this Dockerfile in your own projects without also looking at the .dockerignore file.
# Without an appropriate .dockerignore, this Dockerfile will copy a large number of unneeded files into your image.
FROM mhart/alpine-node:12
FROM mhart/alpine-node:14
# install dependencies
WORKDIR /app
@ -12,11 +12,11 @@ RUN npm ci --production
# Only copy over the Node pieces we need
# ~> Saves 35MB
###
FROM mhart/alpine-node:slim-12
FROM mhart/alpine-node:slim-14
WORKDIR /app
COPY --from=0 /app .
COPY . .
EXPOSE 3000
CMD ["node", "__sapper__/build"]
CMD ["node", "build"]

@ -5,10 +5,10 @@ PROJECT := svelte-dev
IMAGE := gcr.io/$(PROJECT)/$(SERVICE):$(HASH)
sapper:
sveltekit:
@echo "\n~> updating template & contributors list"
@npm run update
@echo "\n~> building Sapper app"
@echo "\n~> building SvelteKit app"
@npm run build
@ -17,6 +17,6 @@ docker:
@gcloud builds submit --project $(PROJECT) -t $(IMAGE)
deploy: sapper docker
deploy: sveltekit docker
@echo "\n~> deploying $(SERVICE) to Cloud Run servers"
@gcloud run deploy $(SERVICE) --project $(PROJECT) --allow-unauthenticated --platform managed --region us-central1 --image $(IMAGE) --memory=512Mi

@ -51,7 +51,7 @@ In order for the REPL's GitHub integration to work properly when running locally
## Building the site
To build the website, run `npm run build`. The output can be found in `__sapper__/build`.
To build the website, run `npm run build`. The output can be found in `build`.
## Testing

@ -1,17 +0,0 @@
version: "{build}"
shallow_clone: true
init:
- git config --global core.autocrlf false
build: off
environment:
matrix:
# node.js
- nodejs_version: stable
install:
- ps: Install-Product node $env:nodejs_version
- npm install

@ -0,0 +1,15 @@
version: "3.0"
services:
postgres:
image: postgres:13.4
ports:
- "5432:5432"
environment:
- POSTGRES_DB=${PGDATABASE}
- POSTGRES_USER=${PGUSER}
- POSTGRES_PASSWORD=${PGPASSWORD}
adminer:
image: adminer
restart: always
ports:
- "4000:8080"

8531
site/package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -4,61 +4,40 @@
"description": "Docs and examples for Svelte",
"type": "module",
"scripts": {
"dev": "node scripts/update.js && npm run copy-workers && sapper dev",
"dev": "node scripts/update.js && npm run copy-workers && svelte-kit dev",
"copy-workers": "node scripts/copy-workers.js",
"migrate": "node-pg-migrate -r dotenv/config",
"build": "node scripts/update.js && npm run copy-workers && sapper build --legacy",
"build": "node scripts/update.js && npm run copy-workers && svelte-kit build",
"update": "node scripts/update.js --force=true",
"start": "node __sapper__/build",
"test": "mocha -r esm test/**",
"start": "node build",
"test": "uvu test",
"deploy": "make deploy"
},
"dependencies": {
"@polka/redirect": "^1.0.0-next.7",
"@polka/send": "^1.0.0-next.7",
"cookie": "^0.4.0",
"devalue": "^2.0.0",
"do-not-zip": "^1.0.0",
"flru": "^1.0.2",
"httpie": "^1.1.2",
"jsonwebtoken": "^8.5.1",
"marked": "^1.0.0",
"marked": "^3.0.5",
"pg": "^8.7.1",
"polka": "^1.0.0-next.9",
"prism-svelte": "^0.4.3",
"prismjs": "^1.25.0",
"sirv": "^1.0.0",
"yootils": "0.0.16"
"prismjs": "^1.25.0"
},
"devDependencies": {
"@babel/core": "^7.6.0",
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/plugin-transform-runtime": "^7.6.0",
"@babel/preset-env": "^7.6.0",
"@babel/runtime": "^7.6.0",
"@rollup/plugin-babel": "^5.0.0",
"@rollup/plugin-commonjs": "^17.0.0",
"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^11.0.0",
"@rollup/plugin-replace": "^2.2.0",
"@sindresorhus/slugify": "^0.9.1",
"@sveltejs/adapter-node": "next",
"@sveltejs/kit": "next",
"@sveltejs/site-kit": "^1.4.0",
"@sveltejs/svelte-repl": "^0.2.1",
"@sveltejs/svelte-repl": "^0.3.0",
"degit": "^2.1.4",
"dotenv": "^10.0.0",
"esm": "^3.2.25",
"jimp": "^0.8.0",
"mocha": "^6.2.0",
"node-fetch": "^2.6.1",
"node-pg-migrate": "^3.22.0",
"rollup": "^2.30.0",
"rollup-plugin-svelte": "^7.0.0",
"rollup-plugin-terser": "^7.0.0",
"sapper": "^0.29.3",
"node-pg-migrate": "^6.0.0",
"shelljs": "^0.8.3",
"svelte": "^3.39.0"
},
"engines": {
"node": ">=10.0.0"
"svelte": "^3.39.0",
"uvu": "^0.5.2"
}
}

@ -1,122 +0,0 @@
import 'dotenv/config';
import babel from '@rollup/plugin-babel';
import commonjs from '@rollup/plugin-commonjs';
import json from '@rollup/plugin-json';
import replace from '@rollup/plugin-replace';
import resolve from '@rollup/plugin-node-resolve';
import svelte from 'rollup-plugin-svelte';
import { terser } from 'rollup-plugin-terser';
import config from 'sapper/config/rollup.js';
import pkg from './package.json';
const mode = process.env.NODE_ENV;
const dev = mode === 'development';
const legacy = !!process.env.SAPPER_LEGACY_BUILD;
if (!dev && !process.env.MAPBOX_ACCESS_TOKEN) {
throw new Error('MAPBOX_ACCESS_TOKEN is missing. Please add the token in the .env file before generating the production build.');
}
const onwarn = (warning, onwarn) => (warning.code === 'CIRCULAR_DEPENDENCY' && /[/\\]@sapper[/\\]/.test(warning.message)) || onwarn(warning);
const dedupe = importee => importee === 'svelte' || importee.startsWith('svelte/');
export default {
client: {
input: config.client.input(),
output: config.client.output(),
plugins: [
replace({
'process.browser': true,
'process.env.NODE_ENV': JSON.stringify(mode),
'process.env.MAPBOX_ACCESS_TOKEN': JSON.stringify(process.env.MAPBOX_ACCESS_TOKEN)
}),
svelte({
compilerOptions: {
dev,
hydratable: true
}
}),
resolve({
browser: true,
dedupe
}),
commonjs(),
json(),
legacy && babel({
extensions: ['.js', '.mjs', '.html', '.svelte'],
babelHelpers: 'runtime',
exclude: ['node_modules/@babel/**'],
presets: [
['@babel/preset-env', {
targets: '> 0.25%, not dead'
}]
],
plugins: [
'@babel/plugin-syntax-dynamic-import',
['@babel/plugin-transform-runtime', {
useESModules: true
}]
]
}),
!dev && terser({
module: true
})
],
preserveEntrySignatures: false,
onwarn
},
server: {
input: config.server.input(),
output: config.server.output(),
plugins: [
replace({
'process.browser': false,
'process.env.NODE_ENV': JSON.stringify(mode)
}),
svelte({
compilerOptions: {
dev,
generate: 'ssr',
hydratable: true
},
emitCss: false
}),
resolve({
dedupe
}),
commonjs(),
json()
],
external: [
'yootils',
'codemirror',
...Object.keys(pkg.dependencies || {}).concat(
require('module').builtinModules || Object.keys(process.binding('natives'))
)
],
preserveEntrySignatures: 'strict',
onwarn
},
serviceworker: {
input: config.serviceworker.input(),
output: config.serviceworker.output(),
plugins: [
resolve(),
replace({
'process.browser': true,
'process.env.NODE_ENV': JSON.stringify(mode)
}),
commonjs(),
!dev && terser()
],
preserveEntrySignatures: false,
onwarn
}
};

@ -0,0 +1,26 @@
<!doctype html>
<html lang='en' class="theme-default typo-default">
<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width,initial-scale=1'>
<meta name='theme-color' content='#ff3e00'>
<base href='/'>
<link rel='stylesheet' href='global.css'>
<link rel='stylesheet' href='prism.css'>
<link rel='manifest' href='manifest.json'>
<link rel='icon' type='image/png' href='favicon.png'>
<meta name='twitter:card' content='summary_large_image'>
<meta name='twitter:site' content='@sveltejs'>
<meta name='twitter:creator' content='@sveltejs'>
<meta name='twitter:image' content='https://svelte.dev/images/twitter-card.png'>
<meta name='og:image' content='https://svelte.dev/images/twitter-card.png'>
%svelte.head%
</head>
<body>
<div id='svelte'>%svelte.body%</div>
</body>
</html>

@ -1,6 +0,0 @@
import '@sveltejs/site-kit/base.css';
import * as sapper from '@sapper/app';
sapper.start({
target: document.querySelector('#sapper')
});

@ -1,6 +1,7 @@
<script>
import Repl from '@sveltejs/svelte-repl';
import { onMount } from 'svelte';
import { browser } from '$app/env';
import { process_example } from '../../utils/examples';
import InputOutputToggle from './InputOutputToggle.svelte';
@ -12,7 +13,7 @@
let repl;
let name = 'loading...';
let width = process.browser
let width = browser
? window.innerWidth - 32
: 1000;
@ -73,7 +74,7 @@
$: if (embedded) document.title = `${name} • Svelte REPL`;
$: svelteUrl = process.browser && version === 'local' ?
$: svelteUrl = browser && version === 'local' ?
`${location.origin}/repl/local` :
`https://unpkg.com/svelte@${version}`;
@ -116,7 +117,7 @@
<div class="repl-outer" bind:clientWidth={width} class:mobile>
<div class="viewport" class:offset={checked}>
{#if process.browser}
{#if browser}
<Repl
bind:this={repl}
workersUrl="workers"

@ -2,4 +2,4 @@
export const svelteUrl = `https://unpkg.com/svelte@3`;
export const rollupUrl = `https://unpkg.com/rollup@1/dist/rollup.browser.js`;
export const mapbox_setup = `window.MAPBOX_ACCESS_TOKEN = process.env.MAPBOX_ACCESS_TOKEN;`;
export const mapbox_setup = `window.MAPBOX_ACCESS_TOKEN = '${import.meta.env.VITE_MAPBOX_ACCESS_TOKEN}';`;

@ -0,0 +1,31 @@
// need to do this first before importing database, etc.
import dotenv from 'dotenv';
dotenv.config();
import * as cookie from 'cookie';
import { get_user, sanitize_user } from './utils/auth';
import { query } from './utils/db';
/** @type {import('@sveltejs/kit').Handle} */
export async function handle({ request, resolve }) {
if (process.env['PGHOST']) {
// this is a convenient time to clear out expired sessions
query('delete from sessions where expiry < now()');
request.locals.cookies = cookie.parse(request.headers.cookie || '');
request.locals.user = await get_user(request.locals.cookies.sid);
}
const response = await resolve(request);
return response;
}
/** @type {import('@sveltejs/kit').GetSession} */
export function getSession(request) {
return request.locals.user
? {
user: sanitize_user(request.locals.user)
}
: {};
}

@ -1,6 +1,13 @@
<script>
const dev = process.env.NODE_ENV === 'development';
<script context="module">
/** @type {import('@sveltejs/kit').ErrorLoad} */
export function load({ error, status }) {
return {
props: { error, status }
};
}
</script>
<script>
export let status;
export let error;
@ -54,7 +61,7 @@
<p class="error">Encountered a {status} error</p>
{/if}
{#if dev && error.stack}
{#if import.meta.env.DEV && error.stack}
<pre>{error.stack}</pre>
{:else}
{#if status >= 500}

@ -1,13 +1,12 @@
<script>
import '@sveltejs/site-kit/base.css';
import { setContext } from 'svelte';
import { stores } from '@sapper/app';
import { page, navigating, session } from '$app/stores';
import { Icon, Icons, Nav, NavItem } from '@sveltejs/site-kit';
import PreloadingIndicator from '../components/PreloadingIndicator.svelte';
export let segment;
const { page, preloading, session } = stores();
setContext('app', {
login: () => {
const login_window = window.open(`${window.location.origin}/auth/login`, 'login', 'width=600,height=400');
@ -31,7 +30,7 @@
<Icons/>
{#if $preloading}
{#if $navigating && $navigating.to}
<PreloadingIndicator/>
{/if}

@ -1,26 +1,25 @@
import send from '@polka/send';
import { query } from '../../utils/db';
import { query as db_query } from '../../utils/db';
export async function get(req, res) {
if (req.user) {
export async function get({ query, locals }) {
if (locals.user) {
const page_size = 100;
const offset = req.query.offset ? parseInt(req.query.offset) : 0;
const rows = await query(`
const offset = query.get('offset') ? parseInt(query.get('offset')) : 0;
const rows = await db_query(`
select g.uid, g.name, coalesce(g.updated_at, g.created_at) as updated_at
from gists g
where g.user_id = $1
order by id desc
limit ${page_size + 1}
offset $2
`, [req.user.id, offset]);
`, [locals.user.id, offset]);
rows.forEach(row => {
row.uid = row.uid.replace(/-/g, '');
});
const more = rows.length > page_size;
send(res, 200, { apps: rows.slice(0, page_size), offset: more ? offset + page_size : null });
return { body: { apps: rows.slice(0, page_size), offset: more ? offset + page_size : null }};
} else {
send(res, 401);
return { status: 401 };
}
}

@ -1,22 +1,22 @@
<script context="module">
export async function preload(page, { user }) {
export async function load({ fetch, page, session: { user }}) {
let apps = [];
let offset = null;
if (user) {
let url = 'apps.json';
if (page.query.offset) {
url += `?offset=${encodeURIComponent(page.query.offset)}`;
if (page.query.get('offset')) {
url += `?offset=${encodeURIComponent(page.query.get('offset'))}`;
}
const r = await this.fetch(url, {
const r = await fetch(url, {
credentials: 'include'
});
if (!r.ok) return this.error(r.status, await r.text());
if (!r.ok) return { status: r.status, body: await r.text() };
({ apps, offset } = await r.json());
}
return { user, apps, offset };
return { props: { user, apps, offset }};
}
</script>

@ -1,6 +1,6 @@
export const oauth = 'https://github.com/login/oauth';
export const baseurl = process.env.BASEURL;
export const baseurl = process.env['BASEURL'];
export const secure = baseurl && baseurl.startsWith('https:');
export const client_id = process.env.GITHUB_CLIENT_ID;
export const client_secret = process.env.GITHUB_CLIENT_SECRET;
export const client_id = process.env['GITHUB_CLIENT_ID'];
export const client_secret = process.env['GITHUB_CLIENT_SECRET'];

@ -1,4 +1,3 @@
import send from '@polka/send';
import devalue from 'devalue';
import * as cookie from 'cookie';
import * as httpie from 'httpie';
@ -6,11 +5,11 @@ import { parse, stringify } from 'querystring';
import { sanitize_user, create_user, create_session } from '../../utils/auth';
import { oauth, secure, client_id, client_secret } from './_config.js';
export async function get(req, res) {
export async function get({ query }) {
try {
// Trade "code" for "access_token"
const r1 = await httpie.post(`${oauth}/access_token?` + stringify({
code: req.query.code,
code: query.get('code'),
client_id,
client_secret,
}));
@ -27,28 +26,29 @@ export async function get(req, res) {
const user = await create_user(r2.data, access_token);
const session = await create_session(user);
res.writeHead(200, {
'Set-Cookie': cookie.serialize('sid', session.uid, {
maxAge: 31536000,
path: '/',
httpOnly: true,
secure
}),
'Content-Type': 'text/html; charset=utf-8'
});
res.end(`
return {
headers: {
'Set-Cookie': cookie.serialize('sid', session.uid, {
maxAge: 31536000,
path: '/',
httpOnly: true,
secure
}),
'Content-Type': 'text/html; charset=utf-8'
},
body: `
<script>
window.opener.postMessage({
user: ${devalue(sanitize_user(user))}
}, window.location.origin);
</script>
`);
`
};
} catch (err) {
console.error('GET /auth/callback', err);
send(res, 500, err.data, {
'Content-Type': err.headers['content-type'],
'Content-Length': err.headers['content-length']
});
return {
status: 500,
body: err.data
};
}
}
}

@ -1,19 +1,28 @@
import send from '@polka/send';
import { stringify } from 'querystring';
import { oauth, baseurl, client_id } from './_config.js';
export const get = client_id
? (req, res) => {
? () => {
const Location = `${oauth}/authorize?` + stringify({
scope: 'read:user',
client_id,
redirect_uri: `${baseurl}/auth/callback`,
});
send(res, 302, Location, { Location });
return {
status: 302,
headers: {
Location
}
};
}
: (req, res) => {
send(res, 500, `
: () => {
return {
status: 500,
headers: {
'Content-Type': 'text/html; charset=utf-8'
},
body: `
<body style="font-family: sans-serif; background: rgb(255,215,215); border: 2px solid red; margin: 0; padding: 1em;">
<h1>Missing .env file</h1>
<p>In order to use GitHub authentication, you will need to <a target="_blank" href="https://github.com/settings/developers">register an OAuth application</a> and create a local .env file:</p>
@ -21,7 +30,6 @@ export const get = client_id
<p>The <code>BASEURL</code> variable should match the callback URL specified for your app.</p>
<p>See also <a target="_blank" href="https://github.com/sveltejs/svelte/tree/master/site#repl-github-integration">here</a></p>
</body>
`, {
'Content-Type': 'text/html; charset=utf-8'
});
};
`
};
};

@ -1,17 +1,18 @@
import send from '@polka/send';
import * as cookie from 'cookie';
import { secure } from './_config.js';
import { delete_session } from '../../utils/auth.js';
export async function get(req, res) {
await delete_session(req.cookies.sid);
export async function get(request) {
await delete_session(request.locals.cookies.sid);
send(res, 200, '', {
'Set-Cookie': cookie.serialize('sid', '', {
maxAge: -1,
path: '/',
httpOnly: true,
secure
})
});
}
return {
headers: {
'Set-Cookie': cookie.serialize('sid', '', {
maxAge: -1,
path: '/',
httpOnly: true,
secure
})
}
};
}

@ -1,9 +1,8 @@
import send from '@polka/send';
import get_posts from './_posts.js';
let lookup;
export function get(req, res) {
export function get({ params }) {
if (!lookup || process.env.NODE_ENV !== 'production') {
lookup = new Map();
get_posts().forEach(post => {
@ -11,12 +10,14 @@ export function get(req, res) {
});
}
const post = lookup.get(req.params.slug);
const post = lookup.get(params.slug);
if (post) {
res.setHeader('Cache-Control', `max-age=${5 * 60 * 1e3}`); // 5 minutes
send(res, 200, post);
} else {
send(res, 404, { message: 'not found' });
return {
body: post,
headers: {
'Cache-Control': `max-age=${5 * 60 * 1e3}` // 5 minutes
}
};
}
}

@ -1,7 +1,9 @@
<script context="module">
export async function preload({ params }) {
const res = await this.fetch(`blog/${params.slug}.json`);
return res.ok ? { post: await res.json() } : this.error(404, 'Not found');
export async function load({ fetch, page: { params } }) {
const res = await fetch(`/blog/${params.slug}.json`);
if (res.ok) {
return { props: { post: await res.json() }};
}
}
</script>

@ -1,9 +1,8 @@
import send from '@polka/send';
import get_posts from './_posts.js';
let json;
export function get(req, res) {
export function get() {
if (!json || process.env.NODE_ENV !== 'production') {
const posts = get_posts()
.filter(post => !post.metadata.draft)
@ -17,8 +16,11 @@ export function get(req, res) {
json = JSON.stringify(posts);
}
send(res, 200, json, {
'Content-Type': 'application/json',
'Cache-Control': `max-age=${5 * 60 * 1e3}` // 5 minutes
});
return {
body: json,
headers: {
'Content-Type': 'application/json',
'Cache-Control': `max-age=${5 * 60 * 1e3}` // 5 minutes
}
}
}

@ -1,7 +1,11 @@
<script context="module">
export async function preload() {
const posts = await this.fetch(`blog.json`).then(r => r.json());
return { posts };
export async function load({ fetch }) {
const posts = await fetch(`blog.json`).then(r => r.json());
return {
props: {
posts
}
};
}
</script>
@ -22,7 +26,7 @@
<div class='posts stretch'>
{#each posts as post}
<article class='post' data-pubdate={post.metadata.dateString}>
<a class="no-underline" sapper:prefetch href='blog/{post.slug}' title='Read the article »'>
<a class="no-underline" sveltekit:prefetch href='blog/{post.slug}' title='Read the article »'>
<h2>{post.metadata.title}</h2>
<p>{post.metadata.description}</p>
</a>

@ -1,4 +1,3 @@
import send from '@polka/send';
import get_posts from '../blog/_posts.js';
const months = ',Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'.split(',');
@ -46,9 +45,12 @@ const rss = `
</rss>
`.replace(/>[^\S]+/gm, '>').replace(/[^\S]+</gm, '<').trim();
export function get(req, res) {
send(res, 200, rss, {
'Cache-Control': `max-age=${30 * 60 * 1e3}`,
'Content-Type': 'application/rss+xml'
});
export function get() {
return {
body: rss,
headers: {
'Cache-Control': `max-age=${30 * 60 * 1e3}`,
'Content-Type': 'application/rss+xml'
}
};
}

@ -1,4 +1,6 @@
export function get(req, res) {
res.writeHead(302, { Location: 'https://discord.gg/yy75DKs' });
res.end();
}
export function get() {
return {
status: 302,
headers: { Location: 'https://discord.gg/yy75DKs' },
};
}

@ -1,12 +1,13 @@
import send from '@polka/send';
import get_sections from './_sections.js';
let json;
export function get(req, res) {
export function get() {
if (!json || process.env.NODE_ENV !== 'production') {
json = get_sections();
}
send(res, 200, json);
return {
body: json
};
}

@ -6,8 +6,8 @@
'4_Prefix_stores_with_$_to_access_their_values': 'accessing stores ($)'
};
export async function preload() {
const sections = await this.fetch(`docs.json`).then(r => r.json());
export async function load({ fetch }) {
const sections = await fetch(`docs.json`).then(r => r.json());
for (const section of sections) {
for (const subsection of section.subsections) {
const { slug } = subsection;
@ -18,7 +18,11 @@
}
}
return { sections };
return {
props: {
sections
}
};
}
</script>

@ -1,10 +1,9 @@
import send from '@polka/send';
import { get_example } from './_examples.js';
const cache = new Map();
export function get(req, res) {
const { slug } = req.params;
export function get({ params }) {
const { slug } = params;
let example = cache.get(slug);
@ -14,10 +13,8 @@ export function get(req, res) {
}
if (example) {
send(res, 200, example);
} else {
send(res, 404, {
error: 'not found'
});
return {
body: example
};
}
}

@ -1,18 +1,22 @@
import send from '@polka/send';
import { get_examples } from './_examples.js';
let cached;
export function get(req, res) {
try {
if (!cached || process.env.NODE_ENV !== 'production') {
cached = get_examples().filter(section => section.title);
}
export function get() {
if (!cached || process.env.NODE_ENV !== 'production') {
cached = get_examples().filter(section => section.title);
}
send(res, 200, cached);
} catch (e) {
send(res, e.status || 500, {
message: e.message
});
try {
return {
body: cached
};
} catch(err) {
return {
status: e.status || 500,
body: {
message: e.message
}
};
}
}

@ -1,7 +1,7 @@
<!-- FIXME sometimes it adds a trailing slash when landing -->
<script context="module">
export async function preload() {
const sections = await this.fetch(`examples.json`).then(r => r.json());
export async function load({ fetch }) {
const sections = await fetch(`examples.json`).then(r => r.json());
const title_by_slug = sections.reduce((acc, {examples}) => {
examples.forEach(({slug, title}) => {
acc[slug] = title;
@ -10,13 +10,18 @@
return acc;
}, {});
return {sections, title_by_slug};
return {
props: {
sections,
title_by_slug
}
};
}
</script>
<script>
import { onMount } from 'svelte';
import { goto } from '@sapper/app';
import { goto } from '$app/navigation';
import Repl from '@sveltejs/svelte-repl';
import ScreenToggle from '../../components/ScreenToggle.svelte';

@ -1,9 +1,8 @@
import send from '@polka/send';
import get_faqs from './_faqs.js';
let json;
export function get(req, res) {
export function get() {
if (!json || process.env.NODE_ENV !== 'production') {
const faqs = get_faqs()
.map(faq => {
@ -17,8 +16,11 @@ export function get(req, res) {
json = JSON.stringify(faqs);
}
send(res, 200, json, {
'Content-Type': 'application/json',
'Cache-Control': `max-age=${5 * 60 * 1e3}` // 5 minutes
});
return {
body: json,
headers: {
'Content-Type': 'application/json',
'Cache-Control': `max-age=${5 * 60 * 1e3}` // 5 minutes
}
};
}

@ -1,7 +1,11 @@
<script context="module">
export async function preload() {
const faqs = await this.fetch(`faq.json`).then(r => r.json());
return { faqs };
export async function load({ fetch }) {
const faqs = await fetch(`faq.json`).then(r => r.json());
return {
props: {
faqs
}
};
}
</script>

@ -87,7 +87,7 @@ npm run dev
<p style="flex: 1">See the <a href="blog/the-easiest-way-to-get-started">quickstart guide</a> for more information.</p>
<p class="cta"><a sapper:prefetch href="tutorial">Learn Svelte</a></p>
<p class="cta"><a sveltekit:prefetch href="tutorial">Learn Svelte</a></p>
</div>
</Blurb>

@ -1,7 +1,6 @@
<script>
import { getContext } from 'svelte';
import { stores } from '@sapper/app';
const { session } = stores();
import { session } from '$app/stores';
const { logout } = getContext('app');

@ -1,6 +1,6 @@
<script>
import { createEventDispatcher, getContext } from 'svelte';
import { stores } from '@sapper/app';
import { session } from '$app/stores';
import UserMenu from './UserMenu.svelte';
import { Icon } from '@sveltejs/site-kit';
import * as doNotZip from 'do-not-zip';
@ -9,7 +9,6 @@
import { isMac } from '../../../../../utils/compat.js';
const dispatch = createEventDispatcher();
const { session } = stores();
const { login } = getContext('app');
export let repl;
@ -44,6 +43,9 @@
const r = await fetch(`repl/create.json`, {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
name,
files: components.map(component => ({
@ -99,6 +101,9 @@
const r = await fetch(`repl/${gist.uid}.json`, {
method: 'PATCH',
credentials: 'include',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
name,
files: components.map(component => ({

@ -1,127 +1,146 @@
import send from '@polka/send';
import body from '../_utils/body.js';
import * as httpie from 'httpie';
import { query, find } from '../../../utils/db';
import { get_example } from '../../examples/_examples.js';
const { GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET } = process.env;
const GITHUB_CLIENT_ID = process.env['GITHUB_CLIENT_ID'];
const GITHUB_CLIENT_SECRET = process.env['GITHUB_CLIENT_SECRET'];
async function import_gist(req, res) {
const base = `https://api.github.com/gists/${req.params.id}`;
const url = `${base}?client_id=${GITHUB_CLIENT_ID}&client_secret=${GITHUB_CLIENT_SECRET}`;
try {
const { data } = await httpie.get(url, {
headers: {
'User-Agent': 'https://svelte.dev'
}
});
// create owner if necessary...
let user = await find(`select * from users where uid = $1`, [data.owner.id]);
if (!user) {
const { id, name, login, avatar_url } = data.owner;
user = await find(`
insert into users(uid, name, username, avatar)
values ($1, $2, $3, $4)
returning *
`, [id, name, login, avatar_url]);
}
delete data.files['README.md'];
delete data.files['meta.json'];
const files = Object.keys(data.files).map(key => {
const name = key.replace(/\.html$/, '.svelte');
return {
name,
source: data.files[key].content
};
});
// add gist to database...
await query(`
insert into gists(uid, user_id, name, files)
values ($1, $2, $3, $4)
`, [req.params.id, user.id, data.description, JSON.stringify(files)]);
send(res, 200, {
uid: req.params.id,
name: data.description,
files,
owner: data.owner.id
});
} catch (err) {
send(res, err.statusCode, { error: err.message });
}
}
export async function get(req, res) {
export async function get({ path, params }) {
// is this an example?
const example = get_example(req.params.id);
const example = get_example(params.id);
if (example) {
return send(res, 200, {
relaxed: true,
uid: req.params.id,
name: example.title,
files: example.files,
owner: null
});
return {
body: {
relaxed: true,
uid: params.id,
name: example.title,
files: example.files,
owner: null
}
};
}
if (process.env.NODE_ENV === 'development') {
// In dev, proxy requests to load particular REPLs to the real server.
// This avoids needing to connect to the real database server.
req.pipe(
require('https').request({ host: 'svelte.dev', path: req.url })
).once('response', res_proxy => {
res_proxy.pipe(res);
res.writeHead(res_proxy.statusCode, res_proxy.headers);
}).once('error', () => res.end());
return;
try {
const res_proxy = await httpie.get(`https://svelte.dev${path}`);
return {
body: res_proxy.data,
status: res_proxy.statusCode,
headers: res_proxy.headers
};
} catch (err) {
return {
status: err.statusCode,
body: { error: err.message }
};
}
}
const [row] = await query(`
select g.*, u.uid as owner from gists g
left join users u on g.user_id = u.id
where g.uid = $1 limit 1
`, [req.params.id]); // via filename pattern
`, [params.id]); // via filename pattern
if (!row) {
return import_gist(req, res);
const base = `https://api.github.com/gists/${params.id}`;
const url = `${base}?client_id=${GITHUB_CLIENT_ID}&client_secret=${GITHUB_CLIENT_SECRET}`;
try {
const { data } = await httpie.get(url, {
headers: {
'User-Agent': 'https://svelte.dev'
}
});
// create owner if necessary...
let user = await find(`select * from users where uid = $1`, [data.owner.id]);
if (!user) {
const { id, name, login, avatar_url } = data.owner;
user = await find(`
insert into users(uid, name, username, avatar)
values ($1, $2, $3, $4)
returning *
`, [id, name, login, avatar_url]);
}
delete data.files['README.md'];
delete data.files['meta.json'];
const files = Object.keys(data.files).map(key => {
const name = key.replace(/\.html$/, '.svelte');
return {
name,
source: data.files[key].content
};
});
// add gist to database...
await query(`
insert into gists(uid, user_id, name, files)
values ($1, $2, $3, $4)
`, [params.id, user.id, data.description, JSON.stringify(files)]);
return {
body: {
uid: params.id,
name: data.description,
files,
owner: data.owner.id
}
};
} catch (err) {
return {
status: err.statusCode,
body: { error: err.message }
};
}
}
send(res, 200, {
uid: row.uid.replace(/-/g, ''),
name: row.name,
files: row.files,
owner: row.owner
});
return {
body: {
uid: row.uid.replace(/-/g, ''),
name: row.name,
files: row.files,
owner: row.owner
}
};
}
export async function patch(req, res) {
const { user } = req;
export async function patch({ params, locals, body }) {
const { user } = locals;
if (!user) return;
let id;
const uid = req.params.id;
const uid = params.id;
try {
const [row] = await query(`select * from gists where uid = $1 limit 1`, [uid]);
if (!row) return send(res, 404, { error: 'Gist not found' });
if (row.user_id !== user.id) return send(res, 403, { error: 'Item does not belong to you' });
if (!row) {
return {
status: 404,
body: {
error: 'Gist not found'
}
};
}
if (row.user_id !== user.id) {
return { status: 403, body: { error: 'Item does not belong to you' }};
}
id = row.id;
} catch (err) {
console.error('PATCH /gists @ select', err);
return send(res, 500);
return { status: 500 };
}
try {
const obj = await body(req);
const obj = body;
obj.updated_at = 'now()';
let k;
const cols = [];
@ -136,14 +155,19 @@ export async function patch(req, res) {
const [row] = await query(`update gists ${set} where id = ${id} returning *`, vals);
send(res, 200, {
uid: row.uid.replace(/-/g, ''),
name: row.name,
files: row.files,
owner: user.uid,
});
return {
body: {
uid: row.uid.replace(/-/g, ''),
name: row.name,
files: row.files,
owner: user.uid,
}
};
} catch (err) {
console.error('PATCH /gists @ update', err);
send(res, 500, { error: err.message });
return {
status: 500,
body: { error: err.message }
};
}
}

@ -1,8 +1,10 @@
<script context="module">
export function preload({ params, query }) {
export function load({ page: { params, query }}) {
return {
version: query.version || '3',
id: params.id
props: {
version: query.get('version') || '3',
id: params.id
}
};
}
</script>
@ -10,21 +12,22 @@
<script>
import Repl from '@sveltejs/svelte-repl';
import { onMount } from 'svelte';
import { goto, stores } from '@sapper/app';
import { browser } from '$app/env';
import { goto } from '$app/navigation';
import { session } from '$app/stores';
import { mapbox_setup } from '../../../config';
import InputOutputToggle from '../../../components/Repl/InputOutputToggle.svelte';
import AppControls from './_components/AppControls/index.svelte';
export let version;
export let id;
const { session } = stores();
let repl;
let gist;
let name = 'Loading...';
let zen_mode = false;
let is_relaxed_gist = false;
let width = process.browser ? window.innerWidth : 1000;
let width = browser ? window.innerWidth : 1000;
let checked = false;
function update_query_string(version) {
@ -33,8 +36,8 @@
if (version !== 'latest') params.push(`version=${version}`);
const url = params.length > 0
? `repl/${id}?${params.join('&')}`
: `repl/${id}`;
? `/repl/${id}?${params.join('&')}`
: `/repl/${id}`;
history.replaceState({}, 'x', url);
}
@ -48,7 +51,7 @@
}
// TODO handle `relaxed` logic
fetch(`repl/${id}.json`).then(r => {
fetch(`/repl/${id}.json`).then(r => {
if (r.ok) {
r.json().then(data => {
gist = data;
@ -82,7 +85,7 @@
});
}
$: if (process.browser) fetch_gist(id);
$: if (browser) fetch_gist(id);
onMount(() => {
if (version !== 'local') {
@ -100,15 +103,12 @@
goto(`/repl/${gist.uid}?version=${version}`);
}
$: svelteUrl = process.browser && version === 'local' ?
$: svelteUrl = browser && version === 'local' ?
`${location.origin}/repl/local` :
`https://unpkg.com/svelte@${version}`;
const rollupUrl = `https://unpkg.com/rollup@1/dist/rollup.browser.js`;
// needed for context API example
const mapbox_setup = `window.MAPBOX_ACCESS_TOKEN = process.env.MAPBOX_ACCESS_TOKEN;`;
$: mobile = width < 540;
$: relaxed = is_relaxed_gist || ($session.user && gist && $session.user.uid === gist.owner);
@ -192,7 +192,7 @@
on:forked={handle_fork}
/>
{#if process.browser}
{#if browser}
<div class="viewport" class:offset={checked}>
<Repl
bind:this={repl}

@ -1,27 +1,31 @@
import send from '@polka/send';
import body from './_utils/body.js';
import { query } from '../../utils/db';
export async function post(req, res) {
const { user } = req;
export async function post({ locals, body }) {
const { user } = locals;
if (!user) return; // response already sent
try {
const { name, files } = await body(req);
const { name, files } = body;
const [row] = await query(`
insert into gists(user_id, name, files)
values ($1, $2, $3) returning *`, [user.id, name, JSON.stringify(files)]);
send(res, 201, {
uid: row.uid.replace(/-/g, ''),
name: row.name,
files: row.files,
owner: user.uid,
});
return {
status: 201,
body: {
uid: row.uid.replace(/-/g, ''),
name: row.name,
files: row.files,
owner: user.uid,
}
};
} catch (err) {
send(res, 500, {
error: err.message
});
return {
status: 500,
body: {
error: err.message
}
};
}
}

@ -1,17 +1,20 @@
<script context="module">
export function preload({ query }) {
export function load({ page: { query }}) {
return {
version: query.version,
gist: query.gist,
example: query.example
props: {
version: query.get('version') || '3',
gist: query.get('gist'),
example: query.get('example')
}
};
}
</script>
<script>
import { browser } from '$app/env';
import ReplWidget from '../../components/Repl/ReplWidget.svelte';
export let version = '3';
export let version;
export let gist;
export let example;
</script>
@ -41,7 +44,7 @@
</svelte:head>
<div class="repl-outer">
{#if process.browser}
{#if browser}
<ReplWidget {version} {gist} {example} embedded={true}/>
{/if}
</div>

@ -1,16 +1,22 @@
<script context="module">
export function preload({ query }) {
export function load({ page: { query }}) {
const { gist, example, version } = query;
// redirect to v2 REPL if appropriate
if (/^[^>]?[12]/.test(version)) {
const q = Object.keys(query).map(key => `${key}=${query[key]}`).join('&');
return this.redirect(302, `https://v2.svelte.dev/repl?${q}`);
return {
status: 302,
redirect: `https://v2.svelte.dev/repl?${q}`
};
}
const id = gist || example || 'hello-world';
const q = version ? `?version=${version}` : ``;
this.redirect(301, `repl/${id}${q}`);
return {
status: 301,
redirect: `/repl/${id}${q}`
};
}
</script>

@ -1,17 +0,0 @@
import { createReadStream } from 'fs';
export function get(req, res) {
const path = req.params.file.join('/');
if (process.env.NODE_ENV !== 'development' || ('/' + path).includes('/.')) {
res.writeHead(403);
res.end();
return;
}
createReadStream('../' + path)
.on('error', () => {
res.writeHead(403);
res.end();
})
.pipe(res);
res.writeHead(200, { 'Content-Type': 'text/javascript' });
}

@ -0,0 +1,11 @@
import { readFileSync } from 'fs';
export function get({ params: { path } }) {
if (process.env.NODE_ENV !== 'development' || ('/' + path).includes('/.')) {
return { status: 403 };
}
return {
headers: { 'Content-Type': 'text/javascript' },
body: readFileSync('../' + path)
};
}

@ -1,5 +1,5 @@
<script>
import { goto } from '@sapper/app';
import { goto } from '$app/navigation';
import { Icon } from '@sveltejs/site-kit';
export let sections;
@ -64,7 +64,7 @@
</style>
<nav>
<a sapper:prefetch aria-label="Previous tutorial step" class="no-underline" href="tutorial/{(selected.prev || selected).slug}" class:disabled={!selected.prev}>
<a sveltekit:prefetch aria-label="Previous tutorial step" class="no-underline" href="tutorial/{(selected.prev || selected).slug}" class:disabled={!selected.prev}>
<Icon name="arrow-left" />
</a>
@ -88,7 +88,7 @@
</select>
</div>
<a sapper:prefetch aria-label="Next tutorial step" class="no-underline" href="tutorial/{(selected.next || selected).slug}" class:disabled={!selected.next}>
<a sveltekit:prefetch aria-label="Next tutorial step" class="no-underline" href="tutorial/{(selected.next || selected).slug}" class:disabled={!selected.next}>
<Icon name="arrow-right" />
</a>
</nav>

@ -1,7 +1,6 @@
import * as fs from 'fs';
import * as path from 'path';
import marked from 'marked';
import send from '@polka/send';
import { extract_frontmatter, extract_metadata, link_renderer } from '@sveltejs/site-kit/utils/markdown';
import { highlight } from '../../../utils/highlight';
@ -85,8 +84,8 @@ function get_tutorial(slug) {
};
}
export function get(req, res) {
const { slug } = req.params;
export function get({ params }) {
const { slug } = params;
let tut = cache.get(slug);
if (!tut || process.env.NODE_ENV !== 'production') {
@ -95,8 +94,8 @@ export function get(req, res) {
}
if (tut) {
send(res, 200, tut);
} else {
send(res, 404, { message: 'not found' });
return {
body: tut
};
}
}

@ -1,14 +1,19 @@
<script context="module">
export async function preload({ params }) {
const res = await this.fetch(`tutorial/${params.slug}.json`);
export async function load({ fetch, page: { params }}) {
const res = await fetch(`/tutorial/${params.slug}.json`);
if (!res.ok) {
return this.redirect(301, `tutorial/basics`);
return {
status: 301,
redirect: '/tutorial/basics'
}
}
return {
slug: params.slug,
chapter: await res.json()
props: {
slug: params.slug,
chapter: await res.json()
}
};
}
</script>
@ -16,6 +21,7 @@
<script>
import Repl from '@sveltejs/svelte-repl';
import { getContext } from 'svelte';
import { browser } from '$app/env';
import ScreenToggle from '../../../components/ScreenToggle.svelte';
import TableOfContents from './_TableOfContents.svelte';
@ -36,7 +42,7 @@
let scrollable;
const lookup = new Map();
let width = process.browser ? window.innerWidth : 1000;
let width = browser ? window.innerWidth : 1000;
let offset = 0;
sections.forEach(section => {
@ -50,7 +56,7 @@
lookup.set(chapter.slug, obj);
if (process.browser) { // pending https://github.com/sveltejs/svelte/issues/2135
if (browser) { // pending https://github.com/sveltejs/svelte/issues/2135
if (prev) prev.next = obj;
prev = obj;
}

@ -0,0 +1,19 @@
<script context="module">
export async function load({ fetch }) {
const sections = await fetch(`/tutorial.json`).then(r => r.json());
return {
props: {
sections
}
};
}
</script>
<script>
import { setContext } from 'svelte';
export let sections;
setContext('tutorial', { sections });
</script>
<slot></slot>

@ -1,15 +0,0 @@
<script context="module">
export async function preload() {
const sections = await this.fetch(`tutorial.json`).then(r => r.json());
return { sections };
}
</script>
<script>
import { setContext } from 'svelte';
export let sections;
setContext('tutorial', { sections });
</script>
<slot></slot>

@ -1,5 +1,4 @@
import * as fs from 'fs';
import send from '@polka/send';
import { extract_frontmatter } from '@sveltejs/site-kit/utils/markdown';
let json;
@ -48,16 +47,21 @@ function get_sections() {
return sections;
}
export function get(req, res) {
export function get() {
try {
if (!json || process.env.NODE_ENV !== 'production') {
json = get_sections();
}
send(res, 200, json);
return {
body: json
};
} catch (err) {
send(res, 500, {
message: err.message
});
return {
status: 500,
body: {
message: err.message
}
};
}
}

@ -1,5 +1,8 @@
<script context="module">
export function preload() {
this.redirect(301, 'tutorial/basics');
export function load() {
return {
status: 301,
redirect: '/tutorial/basics'
};
}
</script>
</script>

@ -1,20 +1,23 @@
export function get(req, res) {
export async function get(req) {
let { min = '0', max = '100' } = req.query;
min = +min;
max = +max;
res.setHeader('Access-Control-Allow-Origin', '*');
// simulate a long delay
setTimeout(() => {
// fail sometimes
if (Math.random() < 0.333) {
res.statusCode = 400;
res.end(`Failed to generate random number. Please try again`);
return;
}
await new Promise((res) => setTimeout(res, 1000));
// fail sometimes
if (Math.random() < 0.333) {
return {
status: 400,
headers: { 'Access-Control-Allow-Origin': '*' },
body: `Failed to generate random number. Please try again`
};
}
const num = min + Math.round(Math.random() * (max - min));
res.end(String(num));
}, 1000);
}
const num = min + Math.round(Math.random() * (max - min));
return {
headers: { 'Access-Control-Allow-Origin': '*' },
body: String(num)
};
}

@ -1,43 +0,0 @@
import polka from 'polka';
import send from '@polka/send';
import sirv from 'sirv';
import * as sapper from '@sapper/server';
import { sanitize_user, authenticate } from './utils/auth';
if (!process.env.PORT) {
process.env.PORT = 3000;
}
const { PORT } = process.env;
const app = polka({
onError: (err, req, res) => {
const error = err.message || err;
const code = err.code || err.status || 500;
res.headersSent || send(res, code, { error }, {
'content-type': 'text/plain'
});
}
});
if (process.env.PGHOST) {
app.use(authenticate());
}
app.use(
sirv('static', {
dev: process.env.NODE_ENV === 'development',
setHeaders(res) {
res.setHeader('Access-Control-Allow-Origin', '*');
res.hasHeader('Cache-Control') || res.setHeader('Cache-Control', 'max-age=600'); // 10min default
}
}),
sapper.middleware({
session: req => ({
user: sanitize_user(req.user)
})
})
);
app.listen(PORT);

@ -1,87 +0,0 @@
import { timestamp, files, shell } from '@sapper/service-worker';
const ASSETS = `cache${timestamp}`;
// `shell` is an array of all the files generated by Rollup,
// `files` is an array of everything in the `static` directory
const to_cache = shell.concat(files.filter(file => {
const basename = file.split('/').pop();
if (basename[0] === '.') return false; // .DS_Store and friends
if (basename.endsWith('.mp3')) return false; // TODO others?
return true;
}));
const cached = new Set(to_cache);
self.addEventListener('install', event => {
event.waitUntil(
caches
.open(ASSETS)
.then(cache => cache.addAll(to_cache))
.then(() => {
self.skipWaiting();
})
);
});
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(async keys => {
// delete old caches
for (const key of keys) {
if (key !== ASSETS) await caches.delete(key);
}
self.clients.claim();
})
);
});
self.addEventListener('fetch', event => {
if (event.request.method !== 'GET' || event.request.headers.has('range')) return;
const url = new URL(event.request.url);
// don't try to handle e.g. data: URIs
if (!url.protocol.startsWith('http')) return;
// ignore dev server requests
if (url.hostname === self.location.hostname && url.port !== self.location.port) return;
// always serve static files and Rollup-generated assets from cache
if (url.host === self.location.host && cached.has(url.pathname)) {
event.respondWith(caches.match(event.request));
return;
}
// for pages, you might want to serve a shell `index.html` file,
// which Sapper has generated for you. It's not right for every
// app, but if it's right for yours then uncomment this section
/*
if (url.origin === self.origin && routes.find(route => route.pattern.test(url.pathname))) {
event.respondWith(caches.match('/index.html'));
return;
}
*/
if (event.request.cache === 'only-if-cached') return;
// for everything else, try the network first, falling back to
// cache if the user is offline. (If the pages never change, you
// might prefer a cache-first approach to a network-first one.)
event.respondWith(
caches
.open(`offline${timestamp}`)
.then(async cache => {
try {
const response = await fetch(event.request);
cache.put(event.request, response.clone());
return response;
} catch (err) {
const response = await cache.match(event.request);
if (response) return response;
throw err;
}
})
);
});

@ -1,40 +0,0 @@
<!doctype html>
<html lang='en' class="theme-default typo-default">
<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width,initial-scale=1'>
<meta name='theme-color' content='#ff3e00'>
%sapper.base%
<link href=global.css rel=stylesheet>
<link href=prism.css rel=stylesheet>
<link rel='manifest' href='manifest.json'>
<link rel='icon' type='image/png' href='favicon.png'>
<meta name='twitter:card' content='summary_large_image'>
<meta name='twitter:site' content='@sveltejs'>
<meta name='twitter:creator' content='@sveltejs'>
<meta name='twitter:image' content='https://svelte.dev/images/twitter-card.png'>
<meta name='og:image' content='https://svelte.dev/images/twitter-card.png'>
<!-- Sapper generates a <style> tag containing critical CSS
for the current page. CSS for the rest of the app is
lazily loaded when it precaches secondary pages -->
%sapper.styles%
<!-- This contains the contents of the <svelte:head> component, if
the current page has one -->
%sapper.head%
</head>
<body>
<!-- The application will be rendered inside this element,
because `app/client.js` references it -->
<div id='sapper'>%sapper.html%</div>
<!-- Sapper creates a <script> tag containing `app/client.js`
and anything else it needs to hydrate the app and
initialise the router -->
%sapper.scripts%
</body>
</html>

@ -1,4 +1,3 @@
import * as cookie from 'cookie';
import flru from 'flru';
import { find, query } from './db';
@ -37,7 +36,7 @@ export const delete_session = async sid => {
session_cache.set(sid, null);
};
const get_user = async sid => {
export const get_user = async sid => {
if (!sid) return null;
if (!session_cache.has(sid)) {
@ -51,15 +50,3 @@ const get_user = async sid => {
return session_cache.get(sid);
};
export const authenticate = () => {
// this is a convenient time to clear out expired sessions
query(`delete from sessions where expiry < now()`);
return async (req, res, next) => {
req.cookies = cookie.parse(req.headers.cookie || '');
req.user = await get_user(req.cookies.sid);
next();
};
};

@ -1,7 +1,7 @@
import pg from 'pg';
// Uses `PG*` ENV vars
export const DB = process.env.PGHOST ? new pg.Pool() : null;
export const DB = process.env['PGHOST'] ? new pg.Pool() : null;
export function query(text, values=[]) {
return DB.query(text, values).then(r => r.rows);

@ -1,7 +1,7 @@
import { langs } from '@sveltejs/site-kit/utils/markdown.js';
import PrismJS from 'prismjs';
import 'prismjs/components/prism-bash';
import 'prismjs/components/prism-diff';
import 'prismjs/components/prism-bash.js';
import 'prismjs/components/prism-diff.js';
import 'prism-svelte';
export function highlight(source, lang) {

@ -1,5 +1,5 @@
import slugify from '@sindresorhus/slugify';
import {SLUG_SEPARATOR} from '../../config';
import {SLUG_SEPARATOR} from '../../config.js';
/* url-safe processor */

@ -0,0 +1,36 @@
import adapter from '@sveltejs/adapter-node';
/** @type {import('@sveltejs/kit').Config} */
export default {
kit: {
adapter: adapter(),
target: '#svelte',
prerender: {
enabled: false
},
vite: () => ({
optimizeDeps: {
include: [
'codemirror',
'codemirror/mode/javascript/javascript.js',
'codemirror/mode/handlebars/handlebars.js',
'codemirror/mode/htmlmixed/htmlmixed.js',
'codemirror/mode/xml/xml.js',
'codemirror/mode/css/css.js',
'codemirror/mode/markdown/markdown.js',
'codemirror/addon/edit/closebrackets.js',
'codemirror/addon/edit/closetag.js',
'codemirror/addon/edit/continuelist.js',
'codemirror/addon/comment/comment.js',
'codemirror/addon/fold/foldcode.js',
'codemirror/addon/fold/foldgutter.js',
'codemirror/addon/fold/brace-fold.js',
'codemirror/addon/fold/xml-fold.js',
'codemirror/addon/fold/indent-fold.js',
'codemirror/addon/fold/markdown-fold.js',
'codemirror/addon/fold/comment-fold.js'
]
}
})
}
};

@ -1,423 +1,430 @@
import {strict as assert} from 'assert';
import {urlsafeSlugProcessor, unicodeSafeProcessor} from '../../src/utils/slug';
import {SLUG_SEPARATOR as _} from '../../config';
import * as uvu from 'uvu';
import * as assert from 'uvu/assert';
import {urlsafeSlugProcessor, unicodeSafeProcessor} from '../../src/utils/slug.js';
import {SLUG_SEPARATOR as _} from '../../config.js';
describe('slug', () => {
describe('urlsafeSlugProcessor', () => {
describe('ascii', () => {
it('space separated words', () => {
assert.equal(
urlsafeSlugProcessor('Text expressions'),
`Text${_}expressions`
);
});
it('numbered text', () => {
assert.equal(
urlsafeSlugProcessor('1. export creates'),
`1${_}export${_}creates`
);
});
it('punctuated text', () => {
assert.equal(
urlsafeSlugProcessor('svelte.VERSION'),
`svelte${_}VERSION`
);
});
it('text starting with the dollar sign', () => {
assert.equal(
urlsafeSlugProcessor('$destroy method'),
`$destroy${_}method`
);
});
it('numbered text containing the dollar sign', () => {
assert.equal(
urlsafeSlugProcessor('1. export $destroy'),
`1${_}export${_}$destroy`
);
});
it('text containing the equal char', () => {
assert.equal(
urlsafeSlugProcessor('script context=module'),
`script${_}context${_}module`
);
});
it('text containing the colon char', () => {
assert.equal(
urlsafeSlugProcessor('svelte:body'),
`svelte${_}body`
);
});
it('text containing the slash char', () => {
assert.equal(
urlsafeSlugProcessor('svelte/motion'),
`svelte${_}motion`
);
});
it('text containing the comma char', () => {
assert.equal(
urlsafeSlugProcessor('svelte, motion'),
`svelte${_}motion`
);
});
});
describe('unicode', () => {
it('should translate symbols to English', () => {
assert.equal(
urlsafeSlugProcessor('Ich ♥ Deutsch'),
`Ich${_}love${_}Deutsch`
);
});
it('should remove emoji', () => {
assert.equal(
urlsafeSlugProcessor('Ich 😍 Deutsch'),
`Ich${_}Deutsch`
);
});
});
describe('cyricllic', () => {
it('space separated words', () => {
assert.equal(
urlsafeSlugProcessor('Всплытие и перехват событий'),
`Vsplytie${_}i${_}perehvat${_}sobytij`
);
});
it('numbered text', () => {
assert.equal(
urlsafeSlugProcessor('1 Всплытие и перехват событий'),
`1${_}Vsplytie${_}i${_}perehvat${_}sobytij`
);
});
it('punctuated text', () => {
assert.equal(
urlsafeSlugProcessor('.Всплытие.и.перехват событий'),
`Vsplytie${_}i${_}perehvat${_}sobytij`
);
});
it('text starting with the dollar sign', () => {
assert.equal(
urlsafeSlugProcessor('$Всплытие $ перехват событий'),
`$Vsplytie${_}$${_}perehvat${_}sobytij`
);
});
it('text containing the dollar sign', () => {
assert.equal(
urlsafeSlugProcessor('Всплытие$перехват'),
`Vsplytie$perehvat`
);
});
it('text containing the equal char', () => {
assert.equal(
urlsafeSlugProcessor('Всплытие = перехват=событий'),
`Vsplytie${_}perehvat${_}sobytij`
);
});
it('text containing the colon char', () => {
assert.equal(
urlsafeSlugProcessor('Всплытие : перехват:событий'),
`Vsplytie${_}perehvat${_}sobytij`
);
});
it('text containing the slash char', () => {
assert.equal(
urlsafeSlugProcessor('Всплытие / перехват/событий'),
`Vsplytie${_}perehvat${_}sobytij`
);
});
it('text containing the comma char', () => {
assert.equal(
urlsafeSlugProcessor('Всплытие, перехват'),
`Vsplytie${_}perehvat`
);
});
});
describe('ascii + cyricllic', () => {
it('space separated words', () => {
assert.equal(
urlsafeSlugProcessor('Всплытие и export перехват событий'),
`Vsplytie${_}i${_}export${_}perehvat${_}sobytij`
);
});
it('ascii word concatenated to a cyricllic word', () => {
assert.equal(
urlsafeSlugProcessor('exportВсплытие'),
'exportVsplytie'
);
});
it('cyricllic word concatenated to an ascii word', () => {
assert.equal(
urlsafeSlugProcessor('Всплытиеexport'),
`Vsplytieexport`
);
});
it('numbered text', () => {
assert.equal(
urlsafeSlugProcessor('1 export Всплытие и перехват событий'),
`1${_}export${_}Vsplytie${_}i${_}perehvat${_}sobytij`
);
});
it('punctuated text', () => {
assert.equal(
urlsafeSlugProcessor('.Всплытие.export.и.перехват событий'),
`Vsplytie${_}export${_}i${_}perehvat${_}sobytij`
);
});
it('text starting with the dollar sign, followed by ascii char', () => {
assert.equal(
urlsafeSlugProcessor('$exportВсплытие перехват событий'),
`$exportVsplytie${_}perehvat${_}sobytij`
);
});
it('text starting with the dollar sign, followed by unicode char', () => {
assert.equal(
urlsafeSlugProcessor('$Всплытие export перехват событий'),
`$Vsplytie${_}export${_}perehvat${_}sobytij`
);
});
it('text containing the dollar sign, followed by ascii char', () => {
assert.equal(
urlsafeSlugProcessor('export $destroy a component prop Всплытие и перехват событий'),
`export${_}$destroy${_}a${_}component${_}prop${_}Vsplytie${_}i${_}perehvat${_}sobytij`
);
});
it('text containing the dollar sign, followed by unicode char', () => {
assert.equal(
urlsafeSlugProcessor('Всплытие export $Всплытие a component prop Всплытие и перехват событий'),
`Vsplytie${_}export${_}$Vsplytie${_}a${_}component${_}prop${_}Vsplytie${_}i${_}perehvat${_}sobytij`
);
});
it('text containing the equal char', () => {
assert.equal(
urlsafeSlugProcessor('script context=module Всплытие=и перехват событий'),
`script${_}context${_}module${_}Vsplytie${_}i${_}perehvat${_}sobytij`
);
});
it('text containing the colon char', () => {
assert.equal(
urlsafeSlugProcessor('svelte:body Всплытие и:перехват событий'),
`svelte${_}body${_}Vsplytie${_}i${_}perehvat${_}sobytij`
);
});
it('text containing the slash char', () => {
assert.equal(
urlsafeSlugProcessor('svelte/motion Всплытие и / перехват/событий'),
`svelte${_}motion${_}Vsplytie${_}i${_}perehvat${_}sobytij`
);
});
it('text containing the comma char', () => {
assert.equal(
urlsafeSlugProcessor('Всплытие, export'),
`Vsplytie${_}export`
);
});
});
function run(name, func) {
const suite = uvu.suite(name);
func(suite);
suite.run();
}
run('urlsafeSlugProcessor -> ascii', it => {
it('space separated words', () => {
assert.equal(
urlsafeSlugProcessor('Text expressions'),
`Text${_}expressions`
);
});
it('numbered text', () => {
assert.equal(
urlsafeSlugProcessor('1. export creates'),
`1${_}export${_}creates`
);
});
it('punctuated text', () => {
assert.equal(
urlsafeSlugProcessor('svelte.VERSION'),
`svelte${_}VERSION`
);
});
it('text starting with the dollar sign', () => {
assert.equal(
urlsafeSlugProcessor('$destroy method'),
`$destroy${_}method`
);
});
it('numbered text containing the dollar sign', () => {
assert.equal(
urlsafeSlugProcessor('1. export $destroy'),
`1${_}export${_}$destroy`
);
});
it('text containing the equal char', () => {
assert.equal(
urlsafeSlugProcessor('script context=module'),
`script${_}context${_}module`
);
});
it('text containing the colon char', () => {
assert.equal(
urlsafeSlugProcessor('svelte:body'),
`svelte${_}body`
);
});
it('text containing the slash char', () => {
assert.equal(
urlsafeSlugProcessor('svelte/motion'),
`svelte${_}motion`
);
});
it('text containing the comma char', () => {
assert.equal(
urlsafeSlugProcessor('svelte, motion'),
`svelte${_}motion`
);
});
});
run('urlsafeSlugProcessor - unicode', it => {
it('should translate symbols to English', () => {
assert.equal(
urlsafeSlugProcessor('Ich ♥ Deutsch'),
`Ich${_}love${_}Deutsch`
);
});
it('should remove emoji', () => {
assert.equal(
urlsafeSlugProcessor('Ich 😍 Deutsch'),
`Ich${_}Deutsch`
);
});
});
run('urlsafeSlugProcessor -> cyrillic', it => {
it('space separated words', () => {
assert.equal(
urlsafeSlugProcessor('Всплытие и перехват событий'),
`Vsplytie${_}i${_}perehvat${_}sobytij`
);
});
it('numbered text', () => {
assert.equal(
urlsafeSlugProcessor('1 Всплытие и перехват событий'),
`1${_}Vsplytie${_}i${_}perehvat${_}sobytij`
);
});
it('punctuated text', () => {
assert.equal(
urlsafeSlugProcessor('.Всплытие.и.перехват событий'),
`Vsplytie${_}i${_}perehvat${_}sobytij`
);
});
it('text starting with the dollar sign', () => {
assert.equal(
urlsafeSlugProcessor('$Всплытие $ перехват событий'),
`$Vsplytie${_}$${_}perehvat${_}sobytij`
);
});
it('text containing the dollar sign', () => {
assert.equal(
urlsafeSlugProcessor('Всплытие$перехват'),
`Vsplytie$perehvat`
);
});
it('text containing the equal char', () => {
assert.equal(
urlsafeSlugProcessor('Всплытие = перехват=событий'),
`Vsplytie${_}perehvat${_}sobytij`
);
});
it('text containing the colon char', () => {
assert.equal(
urlsafeSlugProcessor('Всплытие : перехват:событий'),
`Vsplytie${_}perehvat${_}sobytij`
);
});
it('text containing the slash char', () => {
assert.equal(
urlsafeSlugProcessor('Всплытие / перехват/событий'),
`Vsplytie${_}perehvat${_}sobytij`
);
});
it('text containing the comma char', () => {
assert.equal(
urlsafeSlugProcessor('Всплытие, перехват'),
`Vsplytie${_}perehvat`
);
});
});
run('urlsafeSlugProcessor -> ascii + cyrillic', it => {
it('space separated words', () => {
assert.equal(
urlsafeSlugProcessor('Всплытие и export перехват событий'),
`Vsplytie${_}i${_}export${_}perehvat${_}sobytij`
);
});
it('ascii word concatenated to a cyrillic word', () => {
assert.equal(
urlsafeSlugProcessor('exportВсплытие'),
'exportVsplytie'
);
});
it('cyrillic word concatenated to an ascii word', () => {
assert.equal(
urlsafeSlugProcessor('Всплытиеexport'),
`Vsplytieexport`
);
});
it('numbered text', () => {
assert.equal(
urlsafeSlugProcessor('1 export Всплытие и перехват событий'),
`1${_}export${_}Vsplytie${_}i${_}perehvat${_}sobytij`
);
});
it('punctuated text', () => {
assert.equal(
urlsafeSlugProcessor('.Всплытие.export.и.перехват событий'),
`Vsplytie${_}export${_}i${_}perehvat${_}sobytij`
);
});
it('text starting with the dollar sign, followed by ascii char', () => {
assert.equal(
urlsafeSlugProcessor('$exportВсплытие перехват событий'),
`$exportVsplytie${_}perehvat${_}sobytij`
);
});
it('text starting with the dollar sign, followed by unicode char', () => {
assert.equal(
urlsafeSlugProcessor('$Всплытие export перехват событий'),
`$Vsplytie${_}export${_}perehvat${_}sobytij`
);
});
it('text containing the dollar sign, followed by ascii char', () => {
assert.equal(
urlsafeSlugProcessor('export $destroy a component prop Всплытие и перехват событий'),
`export${_}$destroy${_}a${_}component${_}prop${_}Vsplytie${_}i${_}perehvat${_}sobytij`
);
});
it('text containing the dollar sign, followed by unicode char', () => {
assert.equal(
urlsafeSlugProcessor('Всплытие export $Всплытие a component prop Всплытие и перехват событий'),
`Vsplytie${_}export${_}$Vsplytie${_}a${_}component${_}prop${_}Vsplytie${_}i${_}perehvat${_}sobytij`
);
});
it('text containing the equal char', () => {
assert.equal(
urlsafeSlugProcessor('script context=module Всплытие=и перехват событий'),
`script${_}context${_}module${_}Vsplytie${_}i${_}perehvat${_}sobytij`
);
});
it('text containing the colon char', () => {
assert.equal(
urlsafeSlugProcessor('svelte:body Всплытие и:перехват событий'),
`svelte${_}body${_}Vsplytie${_}i${_}perehvat${_}sobytij`
);
});
it('text containing the slash char', () => {
assert.equal(
urlsafeSlugProcessor('svelte/motion Всплытие и / перехват/событий'),
`svelte${_}motion${_}Vsplytie${_}i${_}perehvat${_}sobytij`
);
});
it('text containing the comma char', () => {
assert.equal(
urlsafeSlugProcessor('Всплытие, export'),
`Vsplytie${_}export`
);
});
});
run('unicodeSafeProcessor (preserve unicode) -> ascii', it => {
it('space separated words', () => {
assert.equal(
unicodeSafeProcessor('Text expressions'),
`Text${_}expressions`
);
});
it('numbered text', () => {
assert.equal(
unicodeSafeProcessor('1. export creates'),
`1${_}export${_}creates`
);
});
it('punctuated text', () => {
assert.equal(
unicodeSafeProcessor('svelte.VERSION'),
`svelte${_}VERSION`
);
});
it('text starting with the dollar sign', () => {
assert.equal(
unicodeSafeProcessor('$destroy method'),
`$destroy${_}method`
);
});
it('numbered text containing the dollar sign', () => {
assert.equal(
unicodeSafeProcessor('1. export $destroy'),
`1${_}export${_}$destroy`
);
});
it('text containing the equal char', () => {
assert.equal(
unicodeSafeProcessor('script context=module'),
`script${_}context${_}module`
);
});
it('text containing the colon char', () => {
assert.equal(
unicodeSafeProcessor('svelte:body'),
`svelte${_}body`
);
});
it('text containing the slash char', () => {
assert.equal(
unicodeSafeProcessor('svelte/motion'),
`svelte${_}motion`
);
});
it('text containing the comma char', () => {
assert.equal(
unicodeSafeProcessor('svelte, motion'),
`svelte${_}motion`
);
});
});
run('unicodeSafeProcessor (preserve unicode) -> unicode', it => {
it('should preserve symbols', () => {
assert.equal(
unicodeSafeProcessor('Ich ♥ Deutsch'),
`Ich${_}love${_}Deutsch`
);
});
it('should remove emoji', () => {
assert.equal(
unicodeSafeProcessor('Ich 😍 Deutsch'),
`Ich${_}Deutsch`
);
});
});
describe('unicodeSafeProcessor (preserve unicode)', () => {
describe('ascii', () => {
it('space separated words', () => {
assert.equal(
unicodeSafeProcessor('Text expressions'),
`Text${_}expressions`
);
});
it('numbered text', () => {
assert.equal(
unicodeSafeProcessor('1. export creates'),
`1${_}export${_}creates`
);
});
it('punctuated text', () => {
assert.equal(
unicodeSafeProcessor('svelte.VERSION'),
`svelte${_}VERSION`
);
});
it('text starting with the dollar sign', () => {
assert.equal(
unicodeSafeProcessor('$destroy method'),
`$destroy${_}method`
);
});
it('numbered text containing the dollar sign', () => {
assert.equal(
unicodeSafeProcessor('1. export $destroy'),
`1${_}export${_}$destroy`
);
});
it('text containing the equal char', () => {
assert.equal(
unicodeSafeProcessor('script context=module'),
`script${_}context${_}module`
);
});
it('text containing the colon char', () => {
assert.equal(
unicodeSafeProcessor('svelte:body'),
`svelte${_}body`
);
});
it('text containing the slash char', () => {
assert.equal(
unicodeSafeProcessor('svelte/motion'),
`svelte${_}motion`
);
});
it('text containing the comma char', () => {
assert.equal(
unicodeSafeProcessor('svelte, motion'),
`svelte${_}motion`
);
});
});
describe('unicode', () => {
it('should preserve symbols', () => {
assert.equal(
unicodeSafeProcessor('Ich ♥ Deutsch'),
`Ich${_}love${_}Deutsch`
);
});
it('should remove emoji', () => {
assert.equal(
unicodeSafeProcessor('Ich 😍 Deutsch'),
`Ich${_}Deutsch`
);
});
});
describe('cyricllic', () => {
it('space separated words', () => {
assert.equal(
unicodeSafeProcessor('Всплытие и перехват событий'),
`Всплытие${_}и${_}перехват${_}событий`
);
});
it('numbered text', () => {
assert.equal(
unicodeSafeProcessor('1 Всплытие и перехват событий'),
`1${_}Всплытие${_}и${_}перехват${_}событий`
);
});
it('punctuated text', () => {
assert.equal(
unicodeSafeProcessor('.Всплытие.и.перехват событий'),
`Всплытие${_}и${_}перехват${_}событий`
);
});
it('text starting with the dollar sign', () => {
assert.equal(
unicodeSafeProcessor('$Всплытие $ перехват событий'),
`$${_}Всплытие${_}$${_}перехват${_}событий`
);
});
it('text containing the dollar sign', () => {
assert.equal(
unicodeSafeProcessor('Всплытие$перехват'),
`Всплытие${_}$${_}перехват`
);
});
it('text containing the equal char', () => {
assert.equal(
unicodeSafeProcessor('Всплытие = перехват=событий'),
`Всплытие${_}перехват${_}событий`
);
});
it('text containing the colon char', () => {
assert.equal(
unicodeSafeProcessor('Всплытие : перехват:событий'),
`Всплытие${_}перехват${_}событий`
);
});
it('text containing the slash char', () => {
assert.equal(
unicodeSafeProcessor('Всплытие / перехват/событий'),
`Всплытие${_}перехват${_}событий`
);
});
it('text containing the comma char', () => {
assert.equal(
unicodeSafeProcessor('Всплытие, перехват'),
`Всплытие${_}перехват`
);
});
});
describe('ascii + cyricllic', () => {
it('space separated words', () => {
assert.equal(
unicodeSafeProcessor('Всплытие и export перехват событий'),
`Всплытие${_}и${_}export${_}перехват${_}событий`
);
});
it('ascii word concatenated to a cyricllic word', () => {
assert.equal(
unicodeSafeProcessor('exportВсплытие'),
`export${_}Всплытие`
);
});
it('cyricllic word concatenated to an ascii word', () => {
assert.equal(
unicodeSafeProcessor('Всплытиеexport'),
`Всплытие${_}export`
);
});
it('numbered text', () => {
assert.equal(
unicodeSafeProcessor('1 export Всплытие и перехват событий'),
`1${_}export${_}Всплытие${_}и${_}перехват${_}событий`
);
});
it('punctuated text', () => {
assert.equal(
unicodeSafeProcessor('.Всплытие.export.и.перехват событий'),
`Всплытие${_}export${_}и${_}перехват${_}событий`
);
});
it('text starting with the dollar sign, followed by ascii char', () => {
assert.equal(
unicodeSafeProcessor('$exportВсплытие перехват событий'),
`$export${_}Всплытие${_}перехват${_}событий`
);
});
it('text starting with the dollar sign, followed by unicode char', () => {
assert.equal(
unicodeSafeProcessor('$Всплытие export перехват событий'),
`$${_}Всплытие${_}export${_}перехват${_}событий`
);
});
it('text containing the dollar sign, followed by ascii char', () => {
assert.equal(
unicodeSafeProcessor('export $destroy a component prop Всплытие и перехват событий'),
`export${_}$destroy${_}a${_}component${_}prop${_}Всплытие${_}и${_}перехват${_}событий`
);
});
it('text containing the dollar sign, followed by unicode char', () => {
assert.equal(
unicodeSafeProcessor('Всплытие export $Всплытие a component prop Всплытие и перехват событий'),
`Всплытие${_}export${_}$${_}Всплытие${_}a${_}component${_}prop${_}Всплытие${_}и${_}перехват${_}событий`
);
});
it('text containing the equal char', () => {
assert.equal(
unicodeSafeProcessor('script context=module Всплытие=и перехват событий'),
`script${_}context${_}module${_}Всплытие${_}и${_}перехват${_}событий`
);
});
it('text containing the colon char', () => {
assert.equal(
unicodeSafeProcessor('svelte:body Всплытие и:перехват событий'),
`svelte${_}body${_}Всплытие${_}и${_}перехват${_}событий`
);
});
it('text containing the slash char', () => {
assert.equal(
unicodeSafeProcessor('svelte/motion Всплытие и / перехват/событий'),
`svelte${_}motion${_}Всплытие${_}и${_}перехват${_}событий`
);
});
it('text containing the comma char', () => {
assert.equal(
unicodeSafeProcessor('Всплытие, export'),
`Всплытие${_}export`
);
});
});
run('unicodeSafeProcessor (preserve unicode) -> cyrillic', it => {
it('space separated words', () => {
assert.equal(
unicodeSafeProcessor('Всплытие и перехват событий'),
`Всплытие${_}и${_}перехват${_}событий`
);
});
it('numbered text', () => {
assert.equal(
unicodeSafeProcessor('1 Всплытие и перехват событий'),
`1${_}Всплытие${_}и${_}перехват${_}событий`
);
});
it('punctuated text', () => {
assert.equal(
unicodeSafeProcessor('.Всплытие.и.перехват событий'),
`Всплытие${_}и${_}перехват${_}событий`
);
});
it('text starting with the dollar sign', () => {
assert.equal(
unicodeSafeProcessor('$Всплытие $ перехват событий'),
`$${_}Всплытие${_}$${_}перехват${_}событий`
);
});
it('text containing the dollar sign', () => {
assert.equal(
unicodeSafeProcessor('Всплытие$перехват'),
`Всплытие${_}$${_}перехват`
);
});
it('text containing the equal char', () => {
assert.equal(
unicodeSafeProcessor('Всплытие = перехват=событий'),
`Всплытие${_}перехват${_}событий`
);
});
it('text containing the colon char', () => {
assert.equal(
unicodeSafeProcessor('Всплытие : перехват:событий'),
`Всплытие${_}перехват${_}событий`
);
});
it('text containing the slash char', () => {
assert.equal(
unicodeSafeProcessor('Всплытие / перехват/событий'),
`Всплытие${_}перехват${_}событий`
);
});
it('text containing the comma char', () => {
assert.equal(
unicodeSafeProcessor('Всплытие, перехват'),
`Всплытие${_}перехват`
);
});
});
run('unicodeSafeProcessor (preserve unicode) -> ascii + cyrillic', it => {
it('space separated words', () => {
assert.equal(
unicodeSafeProcessor('Всплытие и export перехват событий'),
`Всплытие${_}и${_}export${_}перехват${_}событий`
);
});
it('ascii word concatenated to a cyrillic word', () => {
assert.equal(
unicodeSafeProcessor('exportВсплытие'),
`export${_}Всплытие`
);
});
it('cyrillic word concatenated to an ascii word', () => {
assert.equal(
unicodeSafeProcessor('Всплытиеexport'),
`Всплытие${_}export`
);
});
it('numbered text', () => {
assert.equal(
unicodeSafeProcessor('1 export Всплытие и перехват событий'),
`1${_}export${_}Всплытие${_}и${_}перехват${_}событий`
);
});
it('punctuated text', () => {
assert.equal(
unicodeSafeProcessor('.Всплытие.export.и.перехват событий'),
`Всплытие${_}export${_}и${_}перехват${_}событий`
);
});
it('text starting with the dollar sign, followed by ascii char', () => {
assert.equal(
unicodeSafeProcessor('$exportВсплытие перехват событий'),
`$export${_}Всплытие${_}перехват${_}событий`
);
});
it('text starting with the dollar sign, followed by unicode char', () => {
assert.equal(
unicodeSafeProcessor('$Всплытие export перехват событий'),
`$${_}Всплытие${_}export${_}перехват${_}событий`
);
});
it('text containing the dollar sign, followed by ascii char', () => {
assert.equal(
unicodeSafeProcessor('export $destroy a component prop Всплытие и перехват событий'),
`export${_}$destroy${_}a${_}component${_}prop${_}Всплытие${_}и${_}перехват${_}событий`
);
});
it('text containing the dollar sign, followed by unicode char', () => {
assert.equal(
unicodeSafeProcessor('Всплытие export $Всплытие a component prop Всплытие и перехват событий'),
`Всплытие${_}export${_}$${_}Всплытие${_}a${_}component${_}prop${_}Всплытие${_}и${_}перехват${_}событий`
);
});
it('text containing the equal char', () => {
assert.equal(
unicodeSafeProcessor('script context=module Всплытие=и перехват событий'),
`script${_}context${_}module${_}Всплытие${_}и${_}перехват${_}событий`
);
});
it('text containing the colon char', () => {
assert.equal(
unicodeSafeProcessor('svelte:body Всплытие и:перехват событий'),
`svelte${_}body${_}Всплытие${_}и${_}перехват${_}событий`
);
});
it('text containing the slash char', () => {
assert.equal(
unicodeSafeProcessor('svelte/motion Всплытие и / перехват/событий'),
`svelte${_}motion${_}Всплытие${_}и${_}перехват${_}событий`
);
});
it('text containing the comma char', () => {
assert.equal(
unicodeSafeProcessor('Всплытие, export'),
`Всплытие${_}export`
);
});
});

Loading…
Cancel
Save