e.which === 13 && add(e.target)}
+ on:keydown={e => e.key === 'Enter' && add(e.target)}
>
@@ -155,4 +155,4 @@
label:hover button {
opacity: 1;
}
-
\ No newline at end of file
+
diff --git a/site/package-lock.json b/site/package-lock.json
index 646ca0ce53..b3d12343f1 100644
--- a/site/package-lock.json
+++ b/site/package-lock.json
@@ -1281,9 +1281,9 @@
}
},
"@sveltejs/site-kit": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/@sveltejs/site-kit/-/site-kit-1.1.4.tgz",
- "integrity": "sha512-PsFUX1C/fhV0ODdCJaEQ8OwzgmaPJVmdefiSYA+i6zttBeV19d/ow+l7SPMXxBkux+vUIl5can4BwValCukCsw==",
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@sveltejs/site-kit/-/site-kit-1.2.0.tgz",
+ "integrity": "sha512-C7puq+1so3fKPPZAnQJQlKfyCG6FsnSSFSS2LRIhWD8VK2FL5j8Eq7DIKSxUvWbGw1AsxnzO3dIHJWPB7fwjKg==",
"dev": true,
"requires": {
"@sindresorhus/slugify": "^0.9.1",
@@ -1291,13 +1291,14 @@
}
},
"@sveltejs/svelte-repl": {
- "version": "0.1.17",
- "resolved": "https://registry.npmjs.org/@sveltejs/svelte-repl/-/svelte-repl-0.1.17.tgz",
- "integrity": "sha512-rM0DC+pZnqwH6PiuxXUmFRwYZ9XNkexxTNt+prR91Qs7ssxGgf0QkH6kGivSNLbrOtOvcgJbt1nUDybWra5HKA==",
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/@sveltejs/svelte-repl/-/svelte-repl-0.2.0.tgz",
+ "integrity": "sha512-2vLQnOVrsmn2d2K4a6urGm8OulGGSPhZCGNySSb1H8nOPsgKrdcTt5qoaxNYXgcyVp55Yow2SvXYXsyJKd4KEQ==",
"dev": true,
"requires": {
"codemirror": "^5.49.2",
"estree-walker": "^0.9.0",
+ "marked": "^0.8.2",
"sourcemap-codec": "^1.4.6",
"svelte-json-tree": "0.0.5",
"yootils": "0.0.16"
@@ -1309,10 +1310,16 @@
"integrity": "sha512-12U47o7XHUX329+x3FzNVjCx3SHEzMF0nkDv7r/HnBzX/xNTKxajBk6gyygaxrAFtLj39219oMfbtxv4KpaOiA==",
"dev": true
},
+ "marked": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/marked/-/marked-0.8.2.tgz",
+ "integrity": "sha512-EGwzEeCcLniFX51DhTpmTom+dSA/MG/OBUDjnWtHbEnjAH180VzUeAw+oE4+Zv+CoYBWyRlYOTR0N8SO9R1PVw==",
+ "dev": true
+ },
"sourcemap-codec": {
- "version": "1.4.6",
- "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.6.tgz",
- "integrity": "sha512-1ZooVLYFxC448piVLBbtOxFcXwnymH9oUF8nRd3CuYDVvkRBxRl6pB4Mtas5a4drtL+E8LDgFkQNcgIw6tc8Hg==",
+ "version": "1.4.8",
+ "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
+ "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
"dev": true
}
}
@@ -1358,9 +1365,9 @@
}
},
"acorn": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.0.0.tgz",
- "integrity": "sha512-PaF/MduxijYYt7unVGRuds1vBC9bFxbNf+VWqhOClfdgy7RlVkQqt610ig1/yxTgsDIfW1cWDel5EBbOy3jdtQ==",
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz",
+ "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==",
"dev": true
},
"ansi-colors": {
@@ -1594,9 +1601,9 @@
"dev": true
},
"codemirror": {
- "version": "5.49.2",
- "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.49.2.tgz",
- "integrity": "sha512-dwJ2HRPHm8w51WB5YTF9J7m6Z5dtkqbU9ntMZ1dqXyFB9IpjoUFDj80ahRVEoVanfIp6pfASJbOlbWdEf8FOzQ==",
+ "version": "5.55.0",
+ "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.55.0.tgz",
+ "integrity": "sha512-TumikSANlwiGkdF/Blnu/rqovZ0Y3Jh8yy9TqrPbSM0xxSucq3RgnpVDQ+mD9q6JERJEIT2FMuF/fBGfkhIR/g==",
"dev": true
},
"color-convert": {
@@ -3157,6 +3164,11 @@
"xtend": "^4.0.0"
}
},
+ "prism-svelte": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/prism-svelte/-/prism-svelte-0.4.3.tgz",
+ "integrity": "sha512-plS7uY0WWiTBwWZs9KM3M88ZxHWKbrbMUDf52CPum6BqAxiLmKROmaTnmhXtv0krQ0l0HRLcFS8JDwOFyPt/OQ=="
+ },
"prismjs": {
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.17.1.tgz",
@@ -3483,9 +3495,9 @@
}
},
"sapper": {
- "version": "0.27.8",
- "resolved": "https://registry.npmjs.org/sapper/-/sapper-0.27.8.tgz",
- "integrity": "sha512-78K+56yu9nGOEU0B0XjBvNchRuPEv4aHbAKK4D874S4aoapMAkHCT0bHtPK12S3P7JPxvvT8GzHaq/8NetMmbg==",
+ "version": "0.27.11",
+ "resolved": "https://registry.npmjs.org/sapper/-/sapper-0.27.11.tgz",
+ "integrity": "sha512-5EaPZhlc8OnyN3UCI6dRSM1Gz5sxyzLZG/1z5nMvZhg9Ng+rSvEvJx/rW/tSHcnQPa8or7+YcbfvQHKS5oPHiw==",
"dev": true,
"requires": {
"html-minifier": "^4.0.0",
diff --git a/site/package.json b/site/package.json
index a94d264217..30075c9f37 100644
--- a/site/package.json
+++ b/site/package.json
@@ -24,6 +24,7 @@
"marked": "^0.7.0",
"pg": "^7.12.1",
"polka": "^1.0.0-next.9",
+ "prism-svelte": "^0.4.3",
"prismjs": "^1.17.1",
"sirv": "^1.0.0-next.2",
"yootils": "0.0.16"
@@ -35,8 +36,8 @@
"@babel/preset-env": "^7.6.0",
"@babel/runtime": "^7.6.0",
"@sindresorhus/slugify": "^0.9.1",
- "@sveltejs/site-kit": "^1.1.4",
- "@sveltejs/svelte-repl": "^0.1.17",
+ "@sveltejs/site-kit": "^1.2.0",
+ "@sveltejs/svelte-repl": "^0.2.0",
"degit": "^2.1.4",
"dotenv": "^8.1.0",
"esm": "^3.2.25",
@@ -53,7 +54,7 @@
"rollup-plugin-replace": "^2.2.0",
"rollup-plugin-svelte": "^5.1.0",
"rollup-plugin-terser": "^5.1.1",
- "sapper": "^0.27.8",
+ "sapper": "^0.27.11",
"shelljs": "^0.8.3",
"svelte": "^3.12.0"
},
diff --git a/site/rollup.config.js b/site/rollup.config.js
index 9cb963a87d..d4efb4eb29 100644
--- a/site/rollup.config.js
+++ b/site/rollup.config.js
@@ -13,6 +13,10 @@ 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/');
diff --git a/site/scripts/get-example-thumbnails/index.js b/site/scripts/get-example-thumbnails/index.js
index e5b977c6d4..770fca852f 100644
--- a/site/scripts/get-example-thumbnails/index.js
+++ b/site/scripts/get-example-thumbnails/index.js
@@ -45,7 +45,7 @@ async function main() {
image.autocrop();
// image.scale(0.25);
- if (image.bitmap.width > 200 || image.bitmap.width > 200) {
+ if (image.bitmap.width > 200 || image.bitmap.height > 200) {
const scale = Math.min(
200 / image.bitmap.width,
200 / image.bitmap.height
diff --git a/site/src/routes/blog/[slug].svelte b/site/src/routes/blog/[slug].svelte
index 610fb6506b..d5fd6c0676 100644
--- a/site/src/routes/blog/[slug].svelte
+++ b/site/src/routes/blog/[slug].svelte
@@ -133,44 +133,14 @@
border: 0.8rem solid var(--second);
}
- /* headers anchors */
-
- .post :global(.offset-anchor) {
- position: relative;
- display: block;
- top: calc(-1 * (var(--nav-h) + var(--top-offset) - 1rem));
- width: 0;
- height: 0;
- }
-
.post :global(.anchor) {
- position: absolute;
- display: block;
- background: url(/icons/link.svg) 0 50% no-repeat;
- background-size: 1em 1em;
- width: 1.4em;
- height: 1em;
top: calc((var(--h3) - 24px) / 2);
- left: -1.4em;
- opacity: 0;
- transition: opacity 0.2s;
- border: none !important; /* TODO get rid of linkify */
}
- .post :global(h2):hover :global(.anchor),
- .post :global(h3):hover :global(.anchor),
- .post :global(h4):hover :global(.anchor),
- .post :global(h5):hover :global(.anchor),
- .post :global(h6):hover :global(.anchor) {
- opacity: 1;
- }
-
-
@media (max-width: 768px) {
.post :global(.anchor) {
transform: scale(0.6);
opacity: 1;
- top: calc((1em - 0.6 * 24px) / 2);
left: -1.0em;
}
}
diff --git a/site/src/routes/docs/_sections.js b/site/src/routes/docs/_sections.js
index bb081a050b..3657ba85ac 100644
--- a/site/src/routes/docs/_sections.js
+++ b/site/src/routes/docs/_sections.js
@@ -85,7 +85,7 @@ export default function() {
renderer.heading = (text, level, rawtext) => {
let slug;
- const match = /
(.+)<\/a>/.exec(text);
+ const match = /]*>(.+)<\/a>/.exec(text);
if (match) {
slug = match[1];
text = match[2];
diff --git a/site/src/routes/faq.js b/site/src/routes/faq.js
deleted file mode 100644
index 6263494a4c..0000000000
--- a/site/src/routes/faq.js
+++ /dev/null
@@ -1,4 +0,0 @@
-export function get(req, res) {
- res.writeHead(302, { Location: 'https://github.com/sveltejs/svelte/wiki/FAQ' });
- res.end();
-}
\ No newline at end of file
diff --git a/site/src/routes/faq/_faqs.js b/site/src/routes/faq/_faqs.js
new file mode 100644
index 0000000000..fa48e02282
--- /dev/null
+++ b/site/src/routes/faq/_faqs.js
@@ -0,0 +1,58 @@
+import fs from 'fs';
+import path from 'path';
+import { extract_frontmatter, link_renderer } from '@sveltejs/site-kit/utils/markdown.js';
+import marked from 'marked';
+import { makeSlugProcessor } from '../../utils/slug';
+import { highlight } from '../../utils/highlight';
+import { SLUG_PRESERVE_UNICODE } from '../../../config';
+
+const makeSlug = makeSlugProcessor(SLUG_PRESERVE_UNICODE);
+
+export default function get_faqs() {
+ return fs
+ .readdirSync('content/faq')
+ .map(file => {
+ if (path.extname(file) !== '.md') return;
+
+ const match = /^([0-9]+)-(.+)\.md$/.exec(file);
+ if (!match) throw new Error(`Invalid filename '${file}'`);
+
+ const [, order, slug] = match;
+
+ const markdown = fs.readFileSync(`content/faq/${file}`, 'utf-8');
+
+ const { content, metadata } = extract_frontmatter(markdown);
+
+ const renderer = new marked.Renderer();
+
+ renderer.link = link_renderer;
+
+ renderer.code = highlight;
+
+ renderer.heading = (text, level, rawtext) => {
+ const fragment = makeSlug(rawtext);
+
+ return `
+
+
+
+ ${text}
+ `;
+ };
+
+ const answer = marked(
+ content.replace(/^\t+/gm, match => match.split('\t').join(' ')),
+ { renderer }
+ );
+
+ const fragment = makeSlug(slug);
+
+ return {
+ fragment,
+ order,
+ answer,
+ metadata
+ };
+ })
+ .sort((a, b) => a.order - b.order);
+}
diff --git a/site/src/routes/faq/index.json.js b/site/src/routes/faq/index.json.js
new file mode 100644
index 0000000000..b6810a984e
--- /dev/null
+++ b/site/src/routes/faq/index.json.js
@@ -0,0 +1,24 @@
+import send from '@polka/send';
+import get_faqs from './_faqs.js';
+
+let json;
+
+export function get(req, res) {
+ if (!json || process.env.NODE_ENV !== 'production') {
+ const faqs = get_faqs()
+ .map(faq => {
+ return {
+ fragment: faq.fragment,
+ answer: faq.answer,
+ metadata: faq.metadata
+ };
+ });
+
+ json = JSON.stringify(faqs);
+ }
+
+ send(res, 200, json, {
+ 'Content-Type': 'application/json',
+ 'Cache-Control': `max-age=${5 * 60 * 1e3}` // 5 minutes
+ });
+}
diff --git a/site/src/routes/faq/index.svelte b/site/src/routes/faq/index.svelte
new file mode 100644
index 0000000000..c426f5bf3d
--- /dev/null
+++ b/site/src/routes/faq/index.svelte
@@ -0,0 +1,89 @@
+
+
+
+
+
+ Frequently Asked Questions • Svelte
+
+
+
+
+
+
+
+
Frequently Asked Questions
+ {#each faqs as faq}
+
+
+
+
+
+ {faq.metadata.question}
+
+ {@html faq.answer}
+
+ {/each}
+
+
+
diff --git a/site/src/routes/repl/[id]/_components/AppControls/index.svelte b/site/src/routes/repl/[id]/_components/AppControls/index.svelte
index 007dba3461..10639e91c0 100644
--- a/site/src/routes/repl/[id]/_components/AppControls/index.svelte
+++ b/site/src/routes/repl/[id]/_components/AppControls/index.svelte
@@ -30,7 +30,7 @@
$: canSave = $session.user && gist && gist.owner === $session.user.uid;
function handleKeydown(event) {
- if (event.which === 83 && (isMac ? event.metaKey : event.ctrlKey)) {
+ if (event.key === 's' && (isMac ? event.metaKey : event.ctrlKey)) {
event.preventDefault();
save();
}
diff --git a/site/src/routes/tutorial/[slug]/_TableOfContents.svelte b/site/src/routes/tutorial/[slug]/_TableOfContents.svelte
index 7b0d4b5de0..97a298bcca 100644
--- a/site/src/routes/tutorial/[slug]/_TableOfContents.svelte
+++ b/site/src/routes/tutorial/[slug]/_TableOfContents.svelte
@@ -64,7 +64,7 @@
diff --git a/site/src/template.html b/site/src/template.html
index 344684d766..28ea1910a5 100644
--- a/site/src/template.html
+++ b/site/src/template.html
@@ -7,6 +7,7 @@
%sapper.base%
+
diff --git a/site/src/utils/highlight.js b/site/src/utils/highlight.js
index d8be46b3a6..d9e4fe5ec8 100644
--- a/site/src/utils/highlight.js
+++ b/site/src/utils/highlight.js
@@ -1,6 +1,7 @@
import { langs } from '@sveltejs/site-kit/utils/markdown.js';
import PrismJS from 'prismjs';
import 'prismjs/components/prism-bash';
+import 'prism-svelte';
export function highlight(source, lang) {
const plang = langs[lang] || '';
diff --git a/site/static/examples/thumbnails/dom-event-forwarding.jpg b/site/static/examples/thumbnails/dom-event-forwarding.jpg
index 0992aef1fd..4c23217bba 100644
Binary files a/site/static/examples/thumbnails/dom-event-forwarding.jpg and b/site/static/examples/thumbnails/dom-event-forwarding.jpg differ
diff --git a/site/static/favicon.png b/site/static/favicon.png
index ddbebf3b4f..21bc302b68 100644
Binary files a/site/static/favicon.png and b/site/static/favicon.png differ
diff --git a/site/static/global.css b/site/static/global.css
index 14e4e0a0c0..7aa0715a98 100644
--- a/site/static/global.css
+++ b/site/static/global.css
@@ -1,500 +1,31 @@
-/*
------------------------------------------------
- vars – css custom-properties
+/* headers anchors */
- NOTE
- - some vars change inside media-queries!
- - under normal conditions, there's no need to touch these
------------------------------------------------
-*/
-:root {
- --nav-h: 6rem;
- --top-offset: 6rem;
- --sidebar-w: 30rem;
- --sidebar-mid-w: 36rem;
- --sidebar-large-w: 48rem;
- --main-width: 80rem;
- --code-w: 72em;
- --side-nav: 3.2rem;
- --side-page: var(--side-nav);
-
- /* easings */
- --in-cubic: cubic-bezier(0.55, 0.055, 0.675, 0.19);
- --out-cubic: cubic-bezier(0.215, 0.61, 0.355, 1);
- --inout-cubic: cubic-bezier(0.645, 0.045, 0.355, 1);
-
- --in-back: cubic-bezier(0.6, -0.28, 0.735, 0.045);
- --out-back: cubic-bezier(0.175, 0.885, 0.32, 1.275);
- --inout-back: cubic-bezier(0.68, -0.55, 0.265, 1.55);
-}
-
-@media screen and (min-width: 768px) {
- :root {
- --side-page: 14vw;
- --top-offset: 10rem;
- --side-nav: 4.8rem;
- }
-}
-
-/* theme vars */
-.theme-default {
- --back: #ffffff;
- --back-light: #f6fafd;
- --back-api: #eff8ff;
- --prime: #ff3e00;
- --second: #676778;
- --flash: #40b3ff;
- --heading: var(--second);
- --text: #444;
- --sidebar-text: rgba(255, 255, 255, .75);
- --border-w: .3rem; /* border-width */
- --border-r: .4rem; /* border-radius */
-}
-
-/* typo vars */
-.typo-default {
- --unit: .8rem;
- --code-fs: 1.3rem;
- --h6: 1.4rem;
- --h5: 1.6rem;
- --h4: 1.8rem; /* default font-size */
- --h3: 2.6rem;
- --h2: 3rem;
- --h1: 3.2rem;
- --linemax: 42em; /* max line-length */
- --lh: 1.5; /* base line-height */
-}
-
-body {
- --font: 'Overpass', sans-serif;
- --font-mono: 'Fira Mono', monospace;
- --font-ui: var(--font-mono);
- --font-system: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;
-}
-
-
-/* fonts ---------------------------------- */
-/* overpass-300normal - latin */
-@font-face {
- font-family: 'Overpass';
- font-style: normal;
- font-weight: 300;
- font-display: fallback;
- src:
- local('Overpass Light '),
- local('Overpass-Light'),
- url('fonts/overpass/overpass-latin-300.woff2') format('woff2');
-}
-
-/* overpass-600normal - latin */
-@font-face {
- font-family: 'Overpass';
- font-style: normal;
- font-weight: 600;
- font-display: fallback;
- src:
- local('Overpass Bold '),
- local('Overpass-Bold'),
- url('fonts/overpass/overpass-latin-600.woff2') format('woff2');
-}
-
-/* fira-mono-400normal - latin */
-@font-face {
- font-family: 'Fira Mono';
- font-style: normal;
- font-weight: 400;
- font-display: fallback;
- src:
- local('Fira Mono Regular '),
- local('Fira Mono-Regular'),
- url('fonts/fira-mono/fira-mono-latin-400.woff2') format('woff2');
-}
-
-/* base reset ----------------------------- */
-html {
- font-size: 62.5%;
- -ms-text-size-adjust: 62.5%;
- -webkit-text-size-adjust: 62.5%;
- -ms-overflow-style: -ms-autohiding-scrollbar;
- box-sizing: border-box;
- border-collapse: collapse;
-}
-
-html,
-body,
-#sapper {
- width: 100%;
- height: 100%;
-}
-
-* {
- box-sizing: inherit;
- margin: 0;
- padding: 0;
-}
-
-/* link reset ----------------------------- */
-a {
- text-decoration: none;
- cursor: pointer;
- color: inherit;
-}
-
-a:hover, a:active { color: var(--flash) }
-a:focus { outline: none }
-
-/*
------------------------------------------------
- global styles
------------------------------------------------
-*/
-
-/* typography ----------------------------- */
-body {
- font: 300 var(--h4)/var(--lh) var(--font);
- background-color: var(--back);
- color: var(--text);
-
- /* default spacing of Overpass is a bit too airy */
- /* letter-spacing: -.013em; */
-}
-
-h1, h2, h3, h4, h5, h6, blockquote {
- position: relative;
- margin: 0;
- color: var(--heading);
-}
-
-/* h1, h2, h3, h4, h5, h6 { font-weight: 600 } */
-h6 { font-size: var(--h6) }
-h5 { font-size: var(--h5) }
-h4 { font-size: var(--h4) }
-h3 { font-size: var(--h3) }
-h2 { font-size: var(--h2) }
-h1 { font-size: var(--h1) }
-
-h1, h2 {
- font-family: var(--font);
- line-height: 1.25;
-}
-
-h3 { font-weight: 300 }
-
-p, ol, ul {
- margin: 0 0 1em 0;
-}
-
-.b, b, strong { font-weight: 600 }
-
-tt, code, kbd, samp {
- font: 400 var(--code-fs)/1.7 var(--font-mono);
-}
-
-code {
- position: relative;
- border-radius: .3em;
- white-space: nowrap;
- color: #444;
- -webkit-font-smoothing: initial;
-}
-
-pre code {
- top: 0;
- white-space: inherit;
- background-color: none;
-}
-
-/* sync CodeMirror with prism */
-.CodeMirror {
- font-size: var(--code-fs) !important;
-}
-
-::selection {
- background: var(--flash);
- color: white;
-}
-
-/* opinionated styles --------------------- */
-
-li:not(.white) > h2 {
- color: var(--second)
-}
-
-blockquote {
- position: relative;
- margin: 1.6rem 0 2.4rem;
- padding: 2rem 2.4rem 1.8rem 2.4rem;
- border-radius: var(--border-r);
- font-family: var(--font);
- max-width: var(--linemax);
-}
-
-blockquote p {
- font-size: var(--h5);
-}
-
-blockquote :last-child {
- margin: 0;
-}
-
-/* buttons -------------------------------- */
-button {
- font-family: inherit;
- font-size: inherit;
- background-color: transparent;
- border: none;
- color: currentColor;
- cursor: pointer;
-}
-
-button:focus,
-.btn:focus { outline: 0 }
-
-button[disabled],
-.btn[disabled],
-.btn:hover[disabled] {
- opacity: .55;
- pointer-events: none;
-}
-
-button > svg,
-.btn > svg {
- position: relative;
- top: -.1rem;
- width: 2rem !important;
- height: 2rem !important;
- stroke: currentColor !important;
-}
-
-/* reset ------- */
-.btn {
- --btn-h: 4rem;
- --btn-outline: .2rem;
- --btn-font: var(--font);
- --btn-calc-h: calc(var(--btn-h) - var(--btn-outline) * 2);
- --btn-hover: linear-gradient(to top, rgba(0,0,0,.07), rgba(0,0,0,.07));
-
- position: relative;
- margin: 0 .8rem .8rem 0;
- vertical-align: middle;
- white-space: nowrap;
- display: inline-block;
- zoom: 1;
- border: none transparent;
- font: var(--h4) var(--btn-font);
- border-radius: var(--border-r);
- color: currentColor;
- cursor: pointer;
-}
-
-/* default */
-.btn {
- line-height: var(--btn-h);
- height: var(--btn-h);
- padding: 0 1.6rem;
- transition: all .1s;
-}
-
-.btn:hover {
- transform: scale(.98);
- mix-blend-mode: multiply;
- background-image: var(--btn-hover);
-}
-
-/* optional */
-.btn[outline] {
- line-height: var(--btn-calc-h);
- height: var(--btn-calc-h);
- border: var(--btn-outline) solid currentColor;
- background-color: white;
- color: currentColor;
-}
-
-/* links ------------------------------------- */
-a {
- position: relative;
- padding: 0 0 1px 0;
- border-bottom: 1px solid currentColor;
- user-select: none;
- color: var(--prime);
- transition: color .2s, border .2s, padding .2s;
-}
-
-a:hover {
- color: var(--flash);
-}
-
-a:hover {
- padding: 0;
- border-bottom: 2px solid currentColor;
-}
-
-a.no-underline {
- border-bottom: none;
- padding: 0;
-}
-
-/* a:hover:not(.disabled) > .icon { stroke: var(--flash) } */
-
-/* lists ---------------------------------- */
-.listify ol,
-.listify ul {
- --list-padding: 2.9rem;
-
- list-style: none;
- color: currentColor;
- margin-left: var(--list-padding);
-}
-
-.listify ol > li,
-.listify ul > li {
- max-width: calc(var(--linemax) - var(--list-padding));
- line-height: 1.5;
- margin: 0 0 0.4rem 0;
-}
-
-.listify ul > li:before {
- content: '';
- position: absolute;
- margin-top: 1.1rem;
- margin-left: -1.8rem;
- background-color: var(--second);
- width: .6rem;
- height: .6rem;
- border-radius: 2px;
- opacity: 0.7;
-}
-
-.listify ol { list-style: decimal }
-
-/* tables --------------------------------- */
-table {
- width: 100%;
- font-size: var(--h5);
-}
-
-td, th {
- text-align: left;
- border-bottom: 1px solid #eee;
- padding: 0.4rem 0.8rem 0.4rem 0;
-}
-
-table code, table span {
- white-space: pre;
-}
-
-/* grid ----------------------------------- */
-.grid, .grid.half {
- display: grid;
- grid-gap: 2.4rem;
- grid-template-columns: 1fr;
- align-items: center;
-}
-
-.grid.stretch { align-items: stretch }
-
-.grid > .cols-2,
-.grid > .cols-3 { grid-column: span 1 }
-
-@media screen and (min-width: 840px) {
- .grid.half,
- .grid { grid-template-columns: repeat(2, 1fr) }
- .grid > .cols-2,
- .grid > .cols-3 { grid-column: span 2 }
-}
-
-@media screen and (min-width: 1100px) {
- .grid { grid-template-columns: repeat(3, 1fr) }
- .grid > .cols-2 { grid-column: span 2 }
- .grid > .cols-3 { grid-column: span 3 }
-}
-
-/* helper styles -------------------------- */
-.flex-auto { flex: 1 0 auto }
-
-.py0 {
- padding-top: 0 !important;
- padding-bottom: 0 !important;
-}
-
-.legend, figcaption, .post aside {
- max-width: none;
- margin: 0 auto;
- padding: 1.6rem 0 0 .8rem;
- font: 1.2rem/1.6 var(--font-ui);
-}
-
-.filename {
- display: inline-block;
- padding: 1.6rem 0 0 1rem;
- font: var(--h6) var(--font-ui);
-}
-
-.box {
- padding: 2.4rem 3.2rem;
- border-radius: var(--border-r);
-}
-
-/* theme colors --------------------------- */
-.prime { color: var(--prime) !important }
-.second { color: var(--second) !important }
-.flash { color: var(--flash) !important }
-.black { color: black !important }
-.white { color: white !important }
-
-.back { background-color: var(--back) !important }
-.back-light { background-color: var(--back-light) !important }
-.bg-prime { background-color: var(--prime) !important }
-.bg-second { background-color: var(--second) !important }
-.bg-flash { background-color: var(--flash) !important }
-
-/* inputs --------------------------------- */
-input[type="checkbox"] {
- /* display: block; */
+.offset-anchor {
position: relative;
- height: 1em;
- width: calc(100% - 0.6em);
- max-width: 2em;
- top: -2px;
- border-radius: 0.5em;
- -webkit-appearance: none;
- outline: none;
- margin: 0 0.6em 0 0;
-}
-
-input[type="checkbox"]::before {
- content: "";
- position: absolute;
display: block;
- height: 100%;
- width: 100%;
- padding: 2px;
- border-radius: 1em;
- top: 0;
- left: 0;
- background: var(--second);
- /* box-sizing: border-box; */
- box-sizing: content-box;
+ top: calc(-1 * (var(--nav-h) + var(--top-offset)) + 11rem);
+ width: 0;
+ height: 0;
}
-input[type="checkbox"]:checked::before {
- background: var(--prime);
-}
-
-input[type="checkbox"]::after {
- content: "";
+.anchor {
position: absolute;
display: block;
+ background: url(/icons/link.svg) 0 50% no-repeat;
+ background-size: 1em 1em;
+ width: 1.4em;
height: 1em;
- width: 1em;
- top: 2px;
- left: 2px;
- border-radius: 1em;
- background: white;
- box-shadow: 0 0px 1px rgba(0,0,0,.4), 0 4px 2px rgba(0,0,0,.1);
- -webkit-transition: background .2s ease-out, left .2s ease-out;
-}
-
-input[type="checkbox"]:checked::after {
- left: calc(100% - 9px);
+ top: calc(((var(--h3) - 24px) / 2) + 0.6em);
+ left: -1.4em;
+ opacity: 0;
+ transition: opacity 0.2s;
+ border: none !important; /* TODO get rid of linkify */
+}
+
+h2:hover .anchor,
+h3:hover .anchor,
+h4:hover .anchor,
+h5:hover .anchor,
+h6:hover .anchor {
+ opacity: 1;
}
diff --git a/site/static/icons/arrow-right.svg b/site/static/icons/arrow-right.svg
index 5229286a59..57bb51ed28 100644
--- a/site/static/icons/arrow-right.svg
+++ b/site/static/icons/arrow-right.svg
@@ -1,7 +1 @@
-
-
\ No newline at end of file
diff --git a/site/static/icons/check.svg b/site/static/icons/check.svg
index 5679642c7f..c3e46a42b6 100644
--- a/site/static/icons/check.svg
+++ b/site/static/icons/check.svg
@@ -1,4 +1 @@
-
-
\ No newline at end of file
diff --git a/site/static/icons/chevron.svg b/site/static/icons/chevron.svg
index 3706f53278..9d582fa803 100644
--- a/site/static/icons/chevron.svg
+++ b/site/static/icons/chevron.svg
@@ -1,4 +1 @@
-
-
\ No newline at end of file
diff --git a/site/static/icons/collapse.svg b/site/static/icons/collapse.svg
index 13ff9dee82..795939aec8 100644
--- a/site/static/icons/collapse.svg
+++ b/site/static/icons/collapse.svg
@@ -1,4 +1 @@
-
-
\ No newline at end of file
diff --git a/site/static/icons/download.svg b/site/static/icons/download.svg
index 8d255ff74b..b286a882fe 100644
--- a/site/static/icons/download.svg
+++ b/site/static/icons/download.svg
@@ -1,4 +1 @@
-
-
\ No newline at end of file
diff --git a/site/static/icons/dropdown.svg b/site/static/icons/dropdown.svg
index b3fcdbe795..69435856b4 100644
--- a/site/static/icons/dropdown.svg
+++ b/site/static/icons/dropdown.svg
@@ -1,4 +1 @@
-
-
\ No newline at end of file
diff --git a/site/static/icons/edit.svg b/site/static/icons/edit.svg
index 5e7b182086..760321cd2f 100644
--- a/site/static/icons/edit.svg
+++ b/site/static/icons/edit.svg
@@ -1,7 +1 @@
-
-
\ No newline at end of file
diff --git a/site/static/icons/expand.svg b/site/static/icons/expand.svg
index b429cb53c8..a026808da3 100644
--- a/site/static/icons/expand.svg
+++ b/site/static/icons/expand.svg
@@ -1,4 +1 @@
-
-
\ No newline at end of file
diff --git a/site/static/icons/flip.svg b/site/static/icons/flip.svg
index 09326b1e6b..3a18d5ffa1 100644
--- a/site/static/icons/flip.svg
+++ b/site/static/icons/flip.svg
@@ -1,4 +1 @@
-
-
\ No newline at end of file
diff --git a/site/static/icons/fork.svg b/site/static/icons/fork.svg
index 57ad61bb2f..3e21885495 100644
--- a/site/static/icons/fork.svg
+++ b/site/static/icons/fork.svg
@@ -1,4 +1 @@
-
-
\ No newline at end of file
diff --git a/site/static/icons/link.svg b/site/static/icons/link.svg
index 3b36b03cab..3345bdd0ba 100644
--- a/site/static/icons/link.svg
+++ b/site/static/icons/link.svg
@@ -1,8 +1 @@
-
-
\ No newline at end of file
diff --git a/site/static/icons/save.svg b/site/static/icons/save.svg
index 4bde5edde5..a8a5fa1618 100644
--- a/site/static/icons/save.svg
+++ b/site/static/icons/save.svg
@@ -1,4 +1 @@
-
-
\ No newline at end of file
diff --git a/site/static/images/svelte-apple-touch-icon.png b/site/static/images/svelte-apple-touch-icon.png
index 94c9f12f03..5600445b1c 100644
Binary files a/site/static/images/svelte-apple-touch-icon.png and b/site/static/images/svelte-apple-touch-icon.png differ
diff --git a/site/static/images/twitter-card.png b/site/static/images/twitter-card.png
index 1e8271f655..7a66e94003 100644
Binary files a/site/static/images/twitter-card.png and b/site/static/images/twitter-card.png differ
diff --git a/site/static/svelte-logo-mask.svg b/site/static/svelte-logo-mask.svg
index 9e5b40598f..8572d3da6c 100644
--- a/site/static/svelte-logo-mask.svg
+++ b/site/static/svelte-logo-mask.svg
@@ -1,16 +1 @@
-
-
-
\ No newline at end of file
diff --git a/site/static/svelte-logo-outline.svg b/site/static/svelte-logo-outline.svg
index 9aac72c957..d93d71c593 100644
--- a/site/static/svelte-logo-outline.svg
+++ b/site/static/svelte-logo-outline.svg
@@ -1,20 +1 @@
-
-
-
\ No newline at end of file
diff --git a/site/static/svelte-logo-vertical.svg b/site/static/svelte-logo-vertical.svg
index b019147f35..7e2888234d 100644
--- a/site/static/svelte-logo-vertical.svg
+++ b/site/static/svelte-logo-vertical.svg
@@ -1,4 +1 @@
-
\ No newline at end of file
diff --git a/site/static/svelte-logo.svg b/site/static/svelte-logo.svg
index 4bf279659a..d8b477bee1 100644
--- a/site/static/svelte-logo.svg
+++ b/site/static/svelte-logo.svg
@@ -1,20 +1 @@
-
-
-
\ No newline at end of file
diff --git a/site/static/tutorial/icons/email.svg b/site/static/tutorial/icons/email.svg
index db656054ff..70d92ca430 100644
--- a/site/static/tutorial/icons/email.svg
+++ b/site/static/tutorial/icons/email.svg
@@ -1,4 +1 @@
-
-
\ No newline at end of file
diff --git a/site/static/tutorial/icons/folder-open.svg b/site/static/tutorial/icons/folder-open.svg
index 9dac0ef9e9..b2a58e7432 100644
--- a/site/static/tutorial/icons/folder-open.svg
+++ b/site/static/tutorial/icons/folder-open.svg
@@ -1,4 +1 @@
-
-
\ No newline at end of file
diff --git a/site/static/tutorial/icons/folder.svg b/site/static/tutorial/icons/folder.svg
index 7d4a03f1d2..2e1246a7f9 100644
--- a/site/static/tutorial/icons/folder.svg
+++ b/site/static/tutorial/icons/folder.svg
@@ -1,4 +1 @@
-
-
\ No newline at end of file
diff --git a/site/static/tutorial/icons/gif.svg b/site/static/tutorial/icons/gif.svg
index 315860a247..4b28abcecd 100644
--- a/site/static/tutorial/icons/gif.svg
+++ b/site/static/tutorial/icons/gif.svg
@@ -1,4 +1 @@
-
-
\ No newline at end of file
diff --git a/site/static/tutorial/icons/map-marker.svg b/site/static/tutorial/icons/map-marker.svg
index 4e9466c7bd..40a702db9d 100644
--- a/site/static/tutorial/icons/map-marker.svg
+++ b/site/static/tutorial/icons/map-marker.svg
@@ -1,4 +1 @@
-
-
\ No newline at end of file
diff --git a/site/static/tutorial/icons/md.svg b/site/static/tutorial/icons/md.svg
index 60bc36f4a7..582204d3b7 100644
--- a/site/static/tutorial/icons/md.svg
+++ b/site/static/tutorial/icons/md.svg
@@ -1,4 +1 @@
-
-
\ No newline at end of file
diff --git a/site/static/tutorial/icons/xlsx.svg b/site/static/tutorial/icons/xlsx.svg
index 6ee32e943d..1a51093a97 100644
--- a/site/static/tutorial/icons/xlsx.svg
+++ b/site/static/tutorial/icons/xlsx.svg
@@ -1,4 +1 @@
-
-
\ No newline at end of file
diff --git a/site/static/tutorial/kitten.png b/site/static/tutorial/kitten.png
index 6b0cedc945..f1a0ecca19 100644
Binary files a/site/static/tutorial/kitten.png and b/site/static/tutorial/kitten.png differ
diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts
index 9822529ece..88fe197993 100644
--- a/src/compiler/compile/Component.ts
+++ b/src/compiler/compile/Component.ts
@@ -28,6 +28,7 @@ import { Node, ImportDeclaration, Identifier, Program, ExpressionStatement, Assi
import add_to_set from './utils/add_to_set';
import check_graph_for_cycles from './utils/check_graph_for_cycles';
import { print, x, b } from 'code-red';
+import { is_reserved_keyword } from './utils/reserved_keywords';
interface ComponentOptions {
namespace?: string;
@@ -185,7 +186,7 @@ export default class Component {
if (variable) {
variable.referenced = true;
- } else if (name === '$$props') {
+ } else if (is_reserved_keyword(name)) {
this.add_var({
name,
injected: true,
@@ -204,7 +205,7 @@ export default class Component {
const variable = this.var_lookup.get(subscribable_name);
if (variable) {
- variable.referenced = true;
+ variable.referenced = true;
variable.subscribable = true;
}
} else {
@@ -239,7 +240,7 @@ export default class Component {
const program: any = { type: 'Program', body: result.js };
walk(program, {
- enter: (node, parent, key) => {
+ enter: (node: Node, parent: Node, key) => {
if (node.type === 'Identifier') {
if (node.name[0] === '@') {
if (node.name[1] === '_') {
@@ -526,7 +527,7 @@ export default class Component {
if (!script) return;
walk(script.content, {
- enter(node) {
+ enter(node: Node) {
if (node.type === 'LabeledStatement' && node.label.name === '$') {
component.warn(node as any, {
code: 'module-script-reactive-declaration',
@@ -631,7 +632,6 @@ export default class Component {
this.add_var({
name,
initialised: instance_scope.initialised_declarations.has(name),
- hoistable: /^Import/.test(node.type),
writable
});
@@ -649,7 +649,7 @@ export default class Component {
reassigned: true,
initialised: true,
});
- } else if (name === '$$props') {
+ } else if (is_reserved_keyword(name)) {
this.add_var({
name,
injected: true,
@@ -714,8 +714,14 @@ export default class Component {
};
let scope_updated = false;
+ let generator_count = 0;
+
walk(content, {
- enter(node, parent, prop, index) {
+ enter(node: Node, parent, prop, index) {
+ if ((node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression') && node.generator === true) {
+ generator_count++;
+ }
+
if (map.has(node)) {
scope = map.get(node);
}
@@ -741,9 +747,13 @@ export default class Component {
component.warn_on_undefined_store_value_references(node, parent, scope);
},
- leave(node) {
+ leave(node: Node) {
+ if ((node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression') && node.generator === true) {
+ generator_count--;
+ }
+
// do it on leave, to prevent infinite loop
- if (component.compile_options.dev && component.compile_options.loopGuardTimeout > 0) {
+ if (component.compile_options.dev && component.compile_options.loopGuardTimeout > 0 && generator_count <= 0) {
const to_replace_for_loop_protect = component.loop_protect(node, scope, component.compile_options.loopGuardTimeout);
if (to_replace_for_loop_protect) {
this.replace(to_replace_for_loop_protect);
@@ -780,12 +790,12 @@ export default class Component {
const component = this;
const { content } = script;
- const { instance_scope, instance_scope_map: map } = this;
+ const { instance_scope, module_scope, instance_scope_map: map } = this;
let scope = instance_scope;
walk(content, {
- enter(node, parent) {
+ enter(node: Node, parent: Node) {
if (map.has(node)) {
scope = map.get(node);
}
@@ -797,7 +807,12 @@ export default class Component {
const deep = assignee.type === 'MemberExpression';
names.forEach(name => {
- if (scope.find_owner(name) === instance_scope) {
+ const scope_owner = scope.find_owner(name);
+ if (
+ scope_owner !== null
+ ? scope_owner === instance_scope
+ : module_scope && module_scope.has(name)
+ ) {
const variable = component.var_lookup.get(name);
variable[deep ? 'mutated' : 'reassigned'] = true;
}
@@ -813,7 +828,7 @@ export default class Component {
}
},
- leave(node) {
+ leave(node: Node) {
if (map.has(node)) {
scope = scope.parent;
}
@@ -821,7 +836,7 @@ export default class Component {
});
}
- warn_on_undefined_store_value_references(node, parent, scope) {
+ warn_on_undefined_store_value_references(node, parent, scope: Scope) {
if (
node.type === 'LabeledStatement' &&
node.label.name === '$' &&
@@ -837,8 +852,17 @@ export default class Component {
const object = get_object(node);
const { name } = object;
- if (name[0] === '$' && !scope.has(name)) {
- this.warn_if_undefined(name, object, null);
+ if (name[0] === '$') {
+ if (!scope.has(name)) {
+ this.warn_if_undefined(name, object, null);
+ }
+
+ if (name[1] !== '$' && scope.has(name.slice(1)) && scope.find_owner(name.slice(1)) !== this.instance_scope) {
+ this.error(node, {
+ code: `contextual-store`,
+ message: `Stores must be declared at the top level of the component (this may change in a future version of Svelte)`
+ });
+ }
}
}
}
@@ -881,7 +905,7 @@ export default class Component {
let scope = instance_scope;
walk(this.ast.instance.content, {
- enter(node, parent, key, index) {
+ enter(node: Node, parent, key, index) {
if (/Function/.test(node.type)) {
return this.skip();
}
@@ -958,7 +982,7 @@ export default class Component {
}
},
- leave(node, parent, _key, index) {
+ leave(node: Node, parent, _key, index) {
if (map.has(node)) {
scope = scope.parent;
}
@@ -980,6 +1004,7 @@ export default class Component {
hoistable_nodes,
var_lookup,
injected_reactive_declaration_vars,
+ imports,
} = this;
const top_level_function_declarations = new Map();
@@ -1059,7 +1084,7 @@ export default class Component {
walking.add(fn_declaration);
walk(fn_declaration, {
- enter(node, parent) {
+ enter(node: Node, parent) {
if (!hoistable) return this.skip();
if (map.has(node)) {
@@ -1107,7 +1132,7 @@ export default class Component {
}
},
- leave(node) {
+ leave(node: Node) {
if (map.has(node)) {
scope = scope.parent;
}
@@ -1131,6 +1156,16 @@ export default class Component {
this.fully_hoisted.push(node);
}
}
+
+ for (const { specifiers } of imports) {
+ for (const specifier of specifiers) {
+ const variable = var_lookup.get(specifier.local.name);
+
+ if (!variable.mutated || variable.subscribable) {
+ variable.hoistable = true;
+ }
+ }
+ }
}
extract_reactive_declarations() {
@@ -1150,7 +1185,7 @@ export default class Component {
const map = this.instance_scope_map;
walk(node.body, {
- enter(node, parent) {
+ enter(node: Node, parent) {
if (map.has(node)) {
scope = map.get(node);
}
@@ -1190,7 +1225,7 @@ export default class Component {
}
},
- leave(node) {
+ leave(node: Node) {
if (map.has(node)) {
scope = scope.parent;
}
@@ -1210,7 +1245,6 @@ export default class Component {
});
const lookup = new Map();
- let seen;
unsorted_reactive_declarations.forEach(declaration => {
declaration.assignees.forEach(name => {
@@ -1245,33 +1279,24 @@ export default class Component {
}
const add_declaration = declaration => {
- if (this.reactive_declarations.indexOf(declaration) !== -1) {
- return;
- }
-
- seen.add(declaration);
+ if (this.reactive_declarations.includes(declaration)) return;
declaration.dependencies.forEach(name => {
if (declaration.assignees.has(name)) return;
const earlier_declarations = lookup.get(name);
if (earlier_declarations)
- earlier_declarations.forEach(declaration => {
- add_declaration(declaration);
- });
+ earlier_declarations.forEach(add_declaration);
});
this.reactive_declarations.push(declaration);
};
- unsorted_reactive_declarations.forEach(declaration => {
- seen = new Set();
- add_declaration(declaration);
- });
+ unsorted_reactive_declarations.forEach(add_declaration);
}
warn_if_undefined(name: string, node, template_scope: TemplateScope) {
if (name[0] === '$') {
- if (name === '$' || name[1] === '$' && name !== '$$props') {
+ if (name === '$' || name[1] === '$' && !is_reserved_keyword(name)) {
this.error(node, {
code: 'illegal-global',
message: `${name} is an illegal variable name`
@@ -1280,7 +1305,7 @@ export default class Component {
this.has_reactive_assignments = true; // TODO does this belong here?
- if (name === '$$props') return;
+ if (is_reserved_keyword(name)) return;
name = name.slice(1);
}
@@ -1450,4 +1475,4 @@ function get_relative_path(from: string, to: string) {
}
return from_parts.concat(to_parts).join('/');
-}
\ No newline at end of file
+}
diff --git a/src/compiler/compile/nodes/AwaitBlock.ts b/src/compiler/compile/nodes/AwaitBlock.ts
index cd57750d29..66bc15364c 100644
--- a/src/compiler/compile/nodes/AwaitBlock.ts
+++ b/src/compiler/compile/nodes/AwaitBlock.ts
@@ -3,24 +3,43 @@ import PendingBlock from './PendingBlock';
import ThenBlock from './ThenBlock';
import CatchBlock from './CatchBlock';
import Expression from './shared/Expression';
+import Component from '../Component';
+import TemplateScope from './shared/TemplateScope';
+import { TemplateNode } from '../../interfaces';
+import { Context, unpack_destructuring } from './shared/Context';
+import { Node as ESTreeNode } from 'estree';
export default class AwaitBlock extends Node {
type: 'AwaitBlock';
expression: Expression;
- value: string;
- error: string;
+
+ then_contexts: Context[];
+ catch_contexts: Context[];
+
+ then_node: ESTreeNode | null;
+ catch_node: ESTreeNode | null;
pending: PendingBlock;
then: ThenBlock;
catch: CatchBlock;
- constructor(component, parent, scope, info) {
+ constructor(component: Component, parent, scope: TemplateScope, info: TemplateNode) {
super(component, parent, scope, info);
this.expression = new Expression(component, this, scope, info.expression);
- this.value = info.value;
- this.error = info.error;
+ this.then_node = info.value;
+ this.catch_node = info.error;
+
+ if (this.then_node) {
+ this.then_contexts = [];
+ unpack_destructuring(this.then_contexts, info.value, node => node);
+ }
+
+ if (this.catch_node) {
+ this.catch_contexts = [];
+ unpack_destructuring(this.catch_contexts, info.error, node => node);
+ }
this.pending = new PendingBlock(component, this, scope, info.pending);
this.then = new ThenBlock(component, this, scope, info.then);
diff --git a/src/compiler/compile/nodes/Binding.ts b/src/compiler/compile/nodes/Binding.ts
index 7d6fad0a81..bcb07280b0 100644
--- a/src/compiler/compile/nodes/Binding.ts
+++ b/src/compiler/compile/nodes/Binding.ts
@@ -60,7 +60,7 @@ export default class Binding extends Node {
scope.dependencies_for_name.get(name).forEach(name => {
const variable = component.var_lookup.get(name);
if (variable) {
- variable[this.expression.node.type === 'MemberExpression' ? 'mutated' : 'reassigned'] = true;
+ variable.mutated = true;
}
});
} else {
@@ -72,6 +72,11 @@ export default class Binding extends Node {
});
variable[this.expression.node.type === 'MemberExpression' ? 'mutated' : 'reassigned'] = true;
+
+ if (info.expression.type === 'Identifier' && !variable.writable) component.error(this.expression.node, {
+ code: 'invalid-binding',
+ message: 'Cannot bind to a variable which is not writable',
+ });
}
const type = parent.get_static_attribute_value('type');
diff --git a/src/compiler/compile/nodes/CatchBlock.ts b/src/compiler/compile/nodes/CatchBlock.ts
index 0edc0f76d8..1a92f617bb 100644
--- a/src/compiler/compile/nodes/CatchBlock.ts
+++ b/src/compiler/compile/nodes/CatchBlock.ts
@@ -1,16 +1,23 @@
import map_children from './shared/map_children';
import TemplateScope from './shared/TemplateScope';
import AbstractBlock from './shared/AbstractBlock';
+import AwaitBlock from './AwaitBlock';
+import Component from '../Component';
+import { TemplateNode } from '../../interfaces';
export default class CatchBlock extends AbstractBlock {
type: 'CatchBlock';
scope: TemplateScope;
- constructor(component, parent, scope, info) {
+ constructor(component: Component, parent: AwaitBlock, scope: TemplateScope, info: TemplateNode) {
super(component, parent, scope, info);
this.scope = scope.child();
- this.scope.add(parent.error, parent.expression.dependencies, this);
+ if (parent.catch_node) {
+ parent.catch_contexts.forEach(context => {
+ this.scope.add(context.key.name, parent.expression.dependencies, this);
+ });
+ }
this.children = map_children(component, parent, this.scope, info.children);
if (!info.skip) {
diff --git a/src/compiler/compile/nodes/EachBlock.ts b/src/compiler/compile/nodes/EachBlock.ts
index 31850f8745..30306745bd 100644
--- a/src/compiler/compile/nodes/EachBlock.ts
+++ b/src/compiler/compile/nodes/EachBlock.ts
@@ -4,56 +4,8 @@ import map_children from './shared/map_children';
import TemplateScope from './shared/TemplateScope';
import AbstractBlock from './shared/AbstractBlock';
import Element from './Element';
-import { x } from 'code-red';
-import { Node, Identifier, RestElement } from 'estree';
-
-interface Context {
- key: Identifier;
- name?: string;
- modifier: (node: Node) => Node;
-}
-
-function unpack_destructuring(contexts: Context[], node: Node, modifier: (node: Node) => Node) {
- if (!node) return;
-
- if (node.type === 'Identifier' || (node as any).type === 'RestIdentifier') { // TODO is this right? not RestElement?
- contexts.push({
- key: node as Identifier,
- modifier
- });
- } else if (node.type === 'ArrayPattern') {
- node.elements.forEach((element, i) => {
- if (element && (element as any).type === 'RestIdentifier') {
- unpack_destructuring(contexts, element, node => x`${modifier(node)}.slice(${i})` as Node);
- } else {
- unpack_destructuring(contexts, element, node => x`${modifier(node)}[${i}]` as Node);
- }
- });
- } else if (node.type === 'ObjectPattern') {
- const used_properties = [];
-
- node.properties.forEach((property, i) => {
- if ((property as any).kind === 'rest') { // TODO is this right?
- const replacement: RestElement = {
- type: 'RestElement',
- argument: property.key as Identifier
- };
-
- node.properties[i] = replacement as any;
-
- unpack_destructuring(
- contexts,
- property.value,
- node => x`@object_without_properties(${modifier(node)}, [${used_properties}])` as Node
- );
- } else {
- used_properties.push(x`"${(property.key as Identifier).name}"`);
-
- unpack_destructuring(contexts, property.value, node => x`${modifier(node)}.${(property.key as Identifier).name}` as Node);
- }
- });
- }
-}
+import { Context, unpack_destructuring } from './shared/Context';
+import { Node } from 'estree';
export default class EachBlock extends AbstractBlock {
type: 'EachBlock';
@@ -69,6 +21,7 @@ export default class EachBlock extends AbstractBlock {
contexts: Context[];
has_animation: boolean;
has_binding = false;
+ has_index_binding = false;
else?: ElseBlock;
diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts
index e8108858c5..7a70e603a7 100644
--- a/src/compiler/compile/nodes/Element.ts
+++ b/src/compiler/compile/nodes/Element.ts
@@ -19,10 +19,10 @@ import { INode } from './interfaces';
const svg = /^(?:altGlyph|altGlyphDef|altGlyphItem|animate|animateColor|animateMotion|animateTransform|circle|clipPath|color-profile|cursor|defs|desc|discard|ellipse|feBlend|feColorMatrix|feComponentTransfer|feComposite|feConvolveMatrix|feDiffuseLighting|feDisplacementMap|feDistantLight|feDropShadow|feFlood|feFuncA|feFuncB|feFuncG|feFuncR|feGaussianBlur|feImage|feMerge|feMergeNode|feMorphology|feOffset|fePointLight|feSpecularLighting|feSpotLight|feTile|feTurbulence|filter|font|font-face|font-face-format|font-face-name|font-face-src|font-face-uri|foreignObject|g|glyph|glyphRef|hatch|hatchpath|hkern|image|line|linearGradient|marker|mask|mesh|meshgradient|meshpatch|meshrow|metadata|missing-glyph|mpath|path|pattern|polygon|polyline|radialGradient|rect|set|solidcolor|stop|svg|switch|symbol|text|textPath|tref|tspan|unknown|use|view|vkern)$/;
-const aria_attributes = 'activedescendant atomic autocomplete busy checked colindex controls current describedby details disabled dropeffect errormessage expanded flowto grabbed haspopup hidden invalid keyshortcuts label labelledby level live modal multiline multiselectable orientation owns placeholder posinset pressed readonly relevant required roledescription rowindex selected setsize sort valuemax valuemin valuenow valuetext'.split(' ');
+const aria_attributes = 'activedescendant atomic autocomplete busy checked colcount colindex colspan controls current describedby details disabled dropeffect errormessage expanded flowto grabbed haspopup hidden invalid keyshortcuts label labelledby level live modal multiline multiselectable orientation owns placeholder posinset pressed readonly relevant required roledescription rowcount rowindex rowspan selected setsize sort valuemax valuemin valuenow valuetext'.split(' ');
const aria_attribute_set = new Set(aria_attributes);
-const aria_roles = 'alert alertdialog application article banner button cell checkbox columnheader combobox command complementary composite contentinfo definition dialog directory document feed figure form grid gridcell group heading img input landmark link list listbox listitem log main marquee math menu menubar menuitem menuitemcheckbox menuitemradio navigation none note option presentation progressbar radio radiogroup range region roletype row rowgroup rowheader scrollbar search searchbox section sectionhead select separator slider spinbutton status structure switch tab table tablist tabpanel term textbox timer toolbar tooltip tree treegrid treeitem widget window'.split(' ');
+const aria_roles = 'alert alertdialog application article banner blockquote button caption cell checkbox code columnheader combobox complementary contentinfo definition deletion dialog directory document emphasis feed figure form generic grid gridcell group heading img link list listbox listitem log main marquee math meter menu menubar menuitem menuitemcheckbox menuitemradio navigation none note option paragraph presentation progressbar radio radiogroup region row rowgroup rowheader scrollbar search searchbox separator slider spinbutton status strong subscript superscript switch tab table tablist tabpanel term textbox time timer toolbar tooltip tree treegrid treeitem'.split(' ');
const aria_role_set = new Set(aria_roles);
const a11y_required_attributes = {
@@ -56,6 +56,11 @@ const a11y_required_content = new Set([
'h6'
]);
+const a11y_no_onchange = new Set([
+ 'select',
+ 'option'
+]);
+
const invisible_elements = new Set(['meta', 'html', 'script', 'style']);
const valid_modifiers = new Set([
@@ -272,13 +277,14 @@ export default class Element extends Node {
}
this.validate_attributes();
+ this.validate_special_cases();
this.validate_bindings();
this.validate_content();
this.validate_event_handlers();
}
validate_attributes() {
- const { component } = this;
+ const { component, parent } = this;
const attribute_map = new Map();
@@ -378,6 +384,14 @@ export default class Element extends Node {
}
}
+
+ if (/(^[0-9-.])|[\^$@%?!|()[\]{}^*+~;]/.test(name)) {
+ component.error(attribute, {
+ code: `illegal-attribute`,
+ message: `'${name}' is not a valid attribute name`,
+ });
+ }
+
if (name === 'slot') {
if (!attribute.is_static) {
component.error(attribute, {
@@ -395,26 +409,10 @@ export default class Element extends Node {
component.slot_outlets.add(name);
}
- let ancestor = this.parent;
- do {
- if (ancestor.type === 'InlineComponent') break;
- if (ancestor.type === 'Element' && /-/.test(ancestor.name)) break;
-
- if (ancestor.type === 'IfBlock' || ancestor.type === 'EachBlock') {
- const type = ancestor.type === 'IfBlock' ? 'if' : 'each';
- const message = `Cannot place slotted elements inside an ${type}-block`;
-
- component.error(attribute, {
- code: `invalid-slotted-content`,
- message
- });
- }
- } while (ancestor = ancestor.parent);
-
- if (!ancestor) {
+ if (!(parent.type === 'InlineComponent' || within_custom_element(parent))) {
component.error(attribute, {
code: `invalid-slotted-content`,
- message: `Element with a slot='...' attribute must be a descendant of a component or custom element`
+ message: `Element with a slot='...' attribute must be a child of a component or a descendant of a custom element`,
});
}
}
@@ -428,29 +426,47 @@ export default class Element extends Node {
attribute_map.set(attribute.name, attribute);
});
+ }
+
+ validate_special_cases() {
+ const { component, attributes, handlers } = this;
+ const attribute_map = new Map();
+ const handlers_map = new Map();
+
+ attributes.forEach(attribute => (
+ attribute_map.set(attribute.name, attribute)
+ ));
+
+ handlers.forEach(handler => (
+ handlers_map.set(handler.name, handler)
+ ));
- // handle special cases
if (this.name === 'a') {
- const attribute = attribute_map.get('href') || attribute_map.get('xlink:href');
+ const href_attribute = attribute_map.get('href') || attribute_map.get('xlink:href');
+ const id_attribute = attribute_map.get('id');
+ const name_attribute = attribute_map.get('name');
- if (attribute) {
- const value = attribute.get_static_value();
+ if (href_attribute) {
+ const href_value = href_attribute.get_static_value();
- if (value === '' || value === '#') {
- component.warn(attribute, {
+ if (href_value === '' || href_value === '#' || /^\W*javascript:/i.test(href_value)) {
+ component.warn(href_attribute, {
code: `a11y-invalid-attribute`,
- message: `A11y: '${value}' is not a valid ${attribute.name} attribute`
+ message: `A11y: '${href_value}' is not a valid ${href_attribute.name} attribute`
});
}
} else {
- component.warn(this, {
- code: `a11y-missing-attribute`,
- message: `A11y: