various guide improvements

pull/1913/head
Rich Harris 6 years ago
parent 3c06379398
commit 7aa612f61d

@ -3,14 +3,12 @@ title: Transitions
--- ---
### Transitions
Transitions allow elements to enter and leave the DOM gracefully, rather than suddenly appearing and disappearing. Transitions allow elements to enter and leave the DOM gracefully, rather than suddenly appearing and disappearing.
```html ```html
<!-- { title: 'Transitions' } --> <!-- { title: 'Transitions' } -->
<script> <script>
import { fade } from 'svelte/transition.js'; import { fade } from 'svelte/transition';
let visible = false; let visible = false;
</script> </script>
@ -21,12 +19,12 @@ Transitions allow elements to enter and leave the DOM gracefully, rather than su
{/if} {/if}
``` ```
Transitions can have parameters — typically `delay` and `duration`, but often others, depending on the transition in question. For example, here's the `fly` transition from the [svelte-transitions](https://github.com/sveltejs/svelte-transitions) package: Transitions can have parameters — typically `delay` and `duration`, but often others, depending on the transition in question. For example, here's the `fly` transition:
```html ```html
<!-- { title: 'Transition with parameters' } --> <!-- { title: 'Transition with parameters' } -->
<script> <script>
import { fly } from 'svelte-transitions'; import { fly } from 'svelte/transition';
let visible = false; let visible = false;
</script> </script>
@ -37,6 +35,9 @@ Transitions can have parameters — typically `delay` and `duration`, but often
{/if} {/if}
``` ```
### In and out
An element can have separate `in` and `out` transitions: An element can have separate `in` and `out` transitions:
```html ```html
@ -53,7 +54,27 @@ An element can have separate `in` and `out` transitions:
{/if} {/if}
``` ```
Transitions are simple functions that take a `node` and any provided `parameters` and return an object with the following properties:
### Built-in transitions
Svelte comes with a handful of ready-to-use transitions:
```html
<!-- { repl: false } -->
<script>
import {
fade,
fly,
slide,
draw
} from 'svelte/transition';
</script>
```
### Custom transitions
You can also make your own. Transitions are simple functions that take a `node` and any provided `parameters` and return an object with the following properties:
* `duration` — how long the transition takes in milliseconds * `duration` — how long the transition takes in milliseconds
* `delay` — milliseconds before the transition starts * `delay` — milliseconds before the transition starts

@ -104,37 +104,8 @@
</symbol> </symbol>
<symbol id="link" class="icon" viewBox="0 0 24 24"> <symbol id="link" class="icon" viewBox="0 0 24 24">
<!-- <path d=" <path d="M9,7L6,7A2 2 0 0 0 6,17L9,17"/>
M10.5,13.5 <path d="M15,7L18,7A2 2 0 0 1 18,17L15,17"/>
C8.5,11.5 9.5,11.5 9.5,10.5 <path d="M7,12L17,12"/>
C8,9 13,3.5, 16,3
C19,3 21,6 21,8
C21,9,20,9,19,11"/>
<path d="
M13.5,10.5
C15.5,13.5 14.5,13.5 14.5,13.5
C16,15 11,21.5, 8,21
C5,21 3,18 3,16
C3,15,4,15,5,13"/> -->
<path d="
M9,7
L6,7
A2 2 0 0 0 6,17
L9,17
"/>
<path d="
M15,7
L18,7
A2 2 0 0 1 18,17
L15,17
"/>
<path d="
M7,12
L17,12
"/>
</symbol> </symbol>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

@ -1,5 +1,5 @@
<script> <script>
import { onMount } from 'svelte'; import { onMount, afterUpdate } from 'svelte';
import Icon from '../../components/Icon.html'; import Icon from '../../components/Icon.html';
export let sections = []; export let sections = [];
@ -36,24 +36,57 @@
window.removeEventListener('hashchange', onhashchange, false); window.removeEventListener('hashchange', onhashchange, false);
}; };
}); });
let ul;
afterUpdate(() => {
const active = ul.querySelector('.active');
if (active) {
const { top, bottom } = active.getBoundingClientRect();
const min = 200;
const max = window.innerHeight - 200;
if (top > max) {
ul.parentNode.scrollBy({
top: top - max,
left: 0,
behavior: 'smooth'
});
} else if (bottom < min) {
ul.parentNode.scrollBy({
top: bottom - min,
left: 0,
behavior: 'smooth'
});
}
}
});
</script> </script>
<ul class='guide-toc'> <ul bind:this={ul} class="guide-toc">
{#each sections as section} {#each sections as section}
<li> <li>
<a class='section {section.slug === active_section ? "active": ""}' href='guide#{section.slug}'> <a class="section" class:active="{section.slug === active_section}" href="guide#{section.slug}">
{section.metadata.title} {#if section.slug === active_section} {section.metadata.title}
<Icon name='arrow-right' /> {/if}
</a> {#if section.slug === active_section}
<Icon name="arrow-right" />
{#each section.subsections as subsection} {/if}
<!-- see <script> below: on:click='scrollTo(event, subsection.slug)' --> </a>
<a class='subsection {subsection.slug === active_section ? "active": ""}' href='guide#{subsection.slug}'>
{subsection.title} {#if subsection.slug === active_section} {#each section.subsections as subsection}
<Icon name='arrow-right' /> {/if} <!-- see <script> below: on:click='scrollTo(event, subsection.slug)' -->
</a> <a class="subsection" class:active="{subsection.slug === active_section}" href="guide#{subsection.slug}">
{/each} {subsection.title}
</li>
{#if subsection.slug === active_section}
<Icon name="arrow-right" />
{/if}
</a>
{/each}
</li>
{/each} {/each}
</ul> </ul>

@ -4,216 +4,245 @@ import * as fleece from 'golden-fleece';
import process_markdown from '../../utils/_process_markdown.js'; import process_markdown from '../../utils/_process_markdown.js';
import marked from 'marked'; import marked from 'marked';
import prismjs from 'prismjs'; // prism-highlighter smaller footprint [hljs: 192.5k] import Prism from 'prismjs'; // prism-highlighter smaller footprint [hljs: 192.5k]
require('prismjs/components/prism-bash'); require('prismjs/components/prism-bash');
const langs = { const langs = {
'hidden-data': 'json', 'hidden-data': 'json',
'html-no-repl': 'html', 'html-no-repl': 'html',
}; };
// map lang to prism-language-attr // map lang to prism-language-attr
const prismLang = { const prismLang = {
bash: 'bash', bash: 'bash',
html: 'markup', html: 'markup',
js: 'javascript', js: 'javascript',
css: 'css', css: 'css',
}; };
function btoa(str) { function btoa(str) {
return new Buffer(str).toString('base64'); return new Buffer(str).toString('base64');
} }
const escaped = { const escaped = {
'"': '&quot;', '"': '&quot;',
"'": '&#39;', "'": '&#39;',
'&': '&amp;', '&': '&amp;',
'<': '&lt;', '<': '&lt;',
'>': '&gt;', '>': '&gt;',
}; };
const unescaped = Object.keys(escaped).reduce( const unescaped = Object.keys(escaped).reduce(
(unescaped, key) => ((unescaped[escaped[key]] = key), unescaped), (unescaped, key) => ((unescaped[escaped[key]] = key), unescaped),
{}, {}
); );
function unescape(str) { function unescape(str) {
return String(str).replace(/&.+?;/g, match => unescaped[match] || match); return String(str).replace(/&.+?;/g, match => unescaped[match] || match);
} }
const blockTypes = 'blockquote html heading hr list listitem paragraph table tablerow tablecell'.split( const blockTypes = [
' ', 'blockquote',
); 'html',
'heading',
'hr',
'list',
'listitem',
'paragraph',
'table',
'tablerow',
'tablecell'
];
function extractMeta(line, lang) { function extractMeta(line, lang) {
try { try {
if (lang === 'html' && line.startsWith('<!--') && line.endsWith('-->')) { if (lang === 'html' && line.startsWith('<!--') && line.endsWith('-->')) {
return fleece.evaluate(line.slice(4, -3).trim()); return fleece.evaluate(line.slice(4, -3).trim());
} }
if ( if (
lang === 'js' || lang === 'js' ||
(lang === 'json' && line.startsWith('/*') && line.endsWith('*/')) (lang === 'json' && line.startsWith('/*') && line.endsWith('*/'))
) { ) {
return fleece.evaluate(line.slice(2, -2).trim()); return fleece.evaluate(line.slice(2, -2).trim());
} }
} catch (err) { } catch (err) {
// TODO report these errors, don't just squelch them // TODO report these errors, don't just squelch them
return null; return null;
} }
} }
// https://github.com/darkskyapp/string-hash/blob/master/index.js // https://github.com/darkskyapp/string-hash/blob/master/index.js
function getHash(str) { function getHash(str) {
let hash = 5381; let hash = 5381;
let i = str.length; let i = str.length;
while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i); while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i);
return (hash >>> 0).toString(36); return (hash >>> 0).toString(36);
} }
export const demos = new Map(); export const demos = new Map();
export default function() { export default function() {
return fs return fs
.readdirSync(`content/guide`) .readdirSync(`content/guide`)
.filter(file => file[0] !== '.' && path.extname(file) === '.md') .filter(file => file[0] !== '.' && path.extname(file) === '.md')
.map(file => { .map(file => {
const markdown = fs.readFileSync(`content/guide/${file}`, 'utf-8'); const markdown = fs.readFileSync(`content/guide/${file}`, 'utf-8');
const { content, metadata } = process_markdown(markdown); const { content, metadata } = process_markdown(markdown);
const groups = []; const subsections = [];
let group = null; const groups = [];
let uid = 1; let group = null;
let uid = 1;
const renderer = new marked.Renderer();
const renderer = new marked.Renderer();
renderer.code = (source, lang) => {
source = source.replace(/^ +/gm, match => renderer.code = (source, lang) => {
match.split(' ').join('\t'), source = source.replace(/^ +/gm, match =>
); match.split(' ').join('\t')
);
const lines = source.split('\n');
const lines = source.split('\n');
const meta = extractMeta(lines[0], lang);
const meta = extractMeta(lines[0], lang);
let prefix = '';
let className = 'code-block'; let prefix = '';
let className = 'code-block';
if (lang === 'html' && !group) {
/* prettier-ignore if (lang === 'html' && !group) {
----------------------------------------------- /* prettier-ignore
edit vedam -----------------------------------------------
don't know how to access components here edit vedam
so i hardcoded icon here don't know how to access components here
----------------------------------------------- so i hardcoded icon here
*/ -----------------------------------------------
if (!meta || meta.repl !== false) { */
prefix = `<a class='open-in-repl' href='repl?demo=@@${uid}' title='open in REPL'><svg class='icon'><use xlink:href='#maximize-2' /></svg></a>`; if (!meta || meta.repl !== false) {
} prefix = `<a class='open-in-repl' href='repl?demo=@@${uid}' title='open in REPL'><svg class='icon'><use xlink:href='#maximize-2' /></svg></a>`;
}
group = { id: uid++, blocks: [] };
groups.push(group); group = { id: uid++, blocks: [] };
} groups.push(group);
}
if (meta) {
source = lines.slice(1).join('\n'); if (meta) {
const filename = meta.filename || (lang === 'html' && 'App.html'); source = lines.slice(1).join('\n');
if (filename) { const filename = meta.filename || (lang === 'html' && 'App.html');
prefix = `<span class='filename'>${prefix} ${filename}</span>`; if (filename) {
className += ' named'; prefix = `<span class='filename'>${prefix} ${filename}</span>`;
} className += ' named';
} }
}
if (group) group.blocks.push({ meta: meta || {}, lang, source });
if (group) group.blocks.push({ meta: meta || {}, lang, source });
if (meta && meta.hidden) return '';
if (meta && meta.hidden) return '';
/* prettier-ignore
----------------------------------------------- /* prettier-ignore
design-edit vedam -----------------------------------------------
insert prism-highlighter design-edit vedam
----------------------------------------------- insert prism-highlighter
*/ -----------------------------------------------
let plang = prismLang[lang]; */
const highlighted = Prism.highlight( let plang = prismLang[lang];
source, const highlighted = Prism.highlight(
Prism.languages[plang], source,
lang, Prism.languages[plang],
); lang
);
return `<div class='${className}'>${prefix}<pre class='language-${plang}'><code>${highlighted}</code></pre></div>`;
}; return `<div class='${className}'>${prefix}<pre class='language-${plang}'><code>${highlighted}</code></pre></div>`;
};
blockTypes.forEach(type => {
const fn = renderer[type]; const seen = new Set();
renderer[type] = function() {
group = null; renderer.heading = (text, level, rawtext) => {
return fn.apply(this, arguments); if (level <= 3) {
}; const slug = rawtext
}); .toLowerCase()
.replace(/[^a-zA-Z0-9]+/g, '-')
const html = marked(content, { renderer }); .replace(/^-/, '')
.replace(/-$/, '');
const hashes = {};
if (seen.has(slug)) throw new Error(`Duplicate slug ${slug}`);
groups.forEach(group => { seen.add(slug);
const main = group.blocks[0];
if (main.meta.repl === false) return; if (level === 3) {
const title = unescape(
const hash = getHash(group.blocks.map(block => block.source).join('')); text
hashes[group.id] = hash; .replace(/<\/?code>/g, '')
.replace(/\.(\w+)(\((.+)?\))?/, (m, $1, $2, $3) => {
const json5 = group.blocks.find(block => block.lang === 'json'); if ($3) return `.${$1}(...)`;
if ($2) return `.${$1}()`;
const title = main.meta.title; return `.${$1}`;
if (!title) console.error(`Missing title for demo in ${file}`); })
);
demos.set(
hash, subsections.push({ slug, title });
JSON.stringify({ }
title: title || 'Example from guide',
components: group.blocks return `
.filter(block => block.lang === 'html' || block.lang === 'js') <h${level}>
.map(block => { <span id="${slug}" class="offset-anchor"></span>
const [name, type] = (block.meta.filename || '').split('.'); <a href="guide#${slug}" class="anchor" aria-hidden="true"></a>
return { ${text}
name: name || 'App', </h${level}>`;
type: type || 'html', }
source: block.source,
}; return `<h${level}>${text}</h${level}>`;
}), };
json5: json5 && json5.source,
}), blockTypes.forEach(type => {
); const fn = renderer[type];
}); renderer[type] = function() {
group = null;
const subsections = []; return fn.apply(this, arguments);
const pattern = /<h3 id="(.+?)">(.+?)<\/h3>/g; };
let match; });
while ((match = pattern.exec(html))) { const html = marked(content, { renderer });
const slug = match[1];
const title = unescape( const hashes = {};
match[2]
.replace(/<\/?code>/g, '') groups.forEach(group => {
.replace(/\.(\w+)(\((.+)?\))?/, (m, $1, $2, $3) => { const main = group.blocks[0];
if ($3) return `.${$1}(...)`; if (main.meta.repl === false) return;
if ($2) return `.${$1}()`;
return `.${$1}`; const hash = getHash(group.blocks.map(block => block.source).join(''));
}), hashes[group.id] = hash;
);
const json5 = group.blocks.find(block => block.lang === 'json');
subsections.push({ slug, title });
} const title = main.meta.title;
if (!title) console.error(`Missing title for demo in ${file}`);
return {
html: html.replace(/@@(\d+)/g, (m, id) => hashes[id] || m), demos.set(
metadata, hash,
subsections, JSON.stringify({
slug: file.replace(/^\d+-/, '').replace(/\.md$/, ''), title: title || 'Example from guide',
file, components: group.blocks
}; .filter(block => block.lang === 'html' || block.lang === 'js')
}); .map(block => {
const [name, type] = (block.meta.filename || '').split('.');
return {
name: name || 'App',
type: type || 'html',
source: block.source,
};
}),
json5: json5 && json5.source,
})
);
});
return {
html: html.replace(/@@(\d+)/g, (m, id) => hashes[id] || m),
metadata,
subsections,
slug: file.replace(/^\d+-/, '').replace(/\.md$/, ''),
file,
};
});
} }

@ -6,7 +6,7 @@
</script> </script>
<script> <script>
import { onMount } from 'svelte'; import { onMount, afterUpdate } from 'svelte';
import GuideContents from './_GuideContents.html'; import GuideContents from './_GuideContents.html';
import Icon from '../../components/Icon.html'; import Icon from '../../components/Icon.html';
@ -18,6 +18,7 @@
let active_section; let active_section;
let container; let container;
let aside;
onMount(() => { onMount(() => {
const anchors = container.querySelectorAll('[id]'); const anchors = container.querySelectorAll('[id]');
@ -148,21 +149,54 @@
margin-top: 0; margin-top: 0;
} }
.content :global(.offset-anchor) {
position: relative;
display: block;
top: calc(-1 * (var(--nav-h) + var(--top-offset) - 1rem));
width: 0;
height: 0;
}
.content :global(.anchor) {
position: absolute;
display: block;
background: url(/icons/link.svg) 0 50% no-repeat;
background-size: 1em 1em;
width: 1.4em;
height: 1em;
left: -1.3em;
opacity: 0;
transition: opacity 0.2s;
border: none !important; /* TODO get rid of linkify */
}
.content :global(h2):hover :global(.anchor),
.content :global(h3):hover :global(.anchor) {
opacity: 1;
}
.content :global(h3), .content :global(h3),
.content :global(h3 > code) { .content :global(h3 > code) {
pointer-events: none;
font-weight: 700; font-weight: 700;
font-size: var(--h3); font-size: var(--h3);
color: var(--second); color: var(--second);
padding: 6.4rem 0 1.6rem 0; margin: 6.4rem 0 1.6rem 0;
background: transparent; background: transparent;
line-height: 1;
} }
.content :global(code) { .content :global(code) {
padding: .3rem .8rem .3rem; padding: .3rem .8rem .3rem;
margin: 0 0.2rem; margin: 0 0.2rem;
top: -.1rem; top: -.1rem;
background: #f9f9f9; background: #f4f4f4;
}
.content :global(pre) :global(code) {
padding: 0;
margin: 0;
top: 0;
background: transparent;
} }
.content :global(.icon) { .content :global(.icon) {
@ -194,11 +228,16 @@
section :global(blockquote) { section :global(blockquote) {
color: var(--flash); color: hsl(204, 100%, 50%);
border: 2px solid currentColor; border: 2px solid var(--flash);
padding-left: 8.8rem; padding-left: 8.8rem;
} }
section :global(blockquote) :global(code) {
background: hsl(204, 100%, 95%) !important;
color: hsl(204, 100%, 50%);
}
section :global(blockquote::before) { section :global(blockquote::before) {
content: ' '; content: ' ';
position: absolute; position: absolute;
@ -217,8 +256,11 @@
<div bind:this={container} class='content linkify listify'> <div bind:this={container} class='content linkify listify'>
{#each sections as section} {#each sections as section}
<section id='{section.slug}'> <section data-id={section.slug}>
<h2> <h2>
<span class="offset-anchor" id={section.slug}></span>
<a href="#{section.slug}" class="anchor" aria-hidden></a>
{section.metadata.title} {section.metadata.title}
<small> <small>
<a href='https://github.com/sveltejs/svelte/edit/master/site/content/guide/{section.file}' title='edit this section'> <a href='https://github.com/sveltejs/svelte/edit/master/site/content/guide/{section.file}' title='edit this section'>
@ -230,8 +272,8 @@
{/each} {/each}
</div> </div>
<aside class="sidebar-container"> <aside bind:this={aside} class="sidebar-container">
<div class="sidebar"> <div class="sidebar">
<GuideContents {sections} /> <GuideContents {sections} {active_section} />
</div> </div>
</aside> </aside>

@ -219,6 +219,7 @@ body {
h1, h2, h3, h4, h5, h6, blockquote { h1, h2, h3, h4, h5, h6, blockquote {
margin: 0; margin: 0;
color: var(--heading); color: var(--heading);
position: relative;
} }
h1, h2, h3, h4, h5, h6 { font-weight: 700 } h1, h2, h3, h4, h5, h6 { font-weight: 700 }
@ -257,6 +258,7 @@ code {
pre code { pre code {
top: 0; top: 0;
white-space: inherit; white-space: inherit;
background-color: none;
} }
::selection { ::selection {
@ -268,7 +270,6 @@ pre code {
h1, h2 { h1, h2 {
font-family: var(--font); font-family: var(--font);
line-height: 1.2; line-height: 1.2;
pointer-events: none;
} }
li:not(.white) > h2 { li:not(.white) > h2 {
@ -407,6 +408,7 @@ a:hover > .icon { stroke: var(--flash) }
width: .8rem; width: .8rem;
height: 0.8rem; height: 0.8rem;
border-radius: 2px; border-radius: 2px;
opacity: 0.7;
} }
.listify ol { list-style: decimal } .listify ol { list-style: decimal }

@ -1,4 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" viewBox="0 0 24 24"> <svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" viewBox="0 0 24 24">
<path fill="#aa1e1e" d="M16,6H13V7.9H16C18.26,7.9 20.1,9.73 20.1,12A4.1,4.1 0 0,1 16,16.1H13V18H16A6,6 0 0,0 22,12C22,8.68 19.31,6 16,6M3.9,12C3.9,9.73 5.74,7.9 8,7.9H11V6H8A6,6 0 0,0 2,12A6,6 0 0,0 8,18H11V16.1H8C5.74,16.1 3.9,14.26 3.9,12M8,13H16V11H8V13Z" /> <g fill="none" stroke="#333">
<path d="M9,7L6,7A2 2 0 0 0 6,17L9,17"/>
<path d="M15,7L18,7A2 2 0 0 1 18,17L15,17"/>
<path d="M7,12L17,12"/>
</g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 403 B

After

Width:  |  Height:  |  Size: 290 B

Loading…
Cancel
Save