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.
```html
<!-- { title: 'Transitions' } -->
<script>
import { fade } from 'svelte/transition.js';
import { fade } from 'svelte/transition';
let visible = false;
</script>
@ -21,12 +19,12 @@ Transitions allow elements to enter and leave the DOM gracefully, rather than su
{/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
<!-- { title: 'Transition with parameters' } -->
<script>
import { fly } from 'svelte-transitions';
import { fly } from 'svelte/transition';
let visible = false;
</script>
@ -37,6 +35,9 @@ Transitions can have parameters — typically `delay` and `duration`, but often
{/if}
```
### In and out
An element can have separate `in` and `out` transitions:
```html
@ -53,7 +54,27 @@ An element can have separate `in` and `out` transitions:
{/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
* `delay` — milliseconds before the transition starts

@ -104,37 +104,8 @@
</symbol>
<symbol id="link" class="icon" viewBox="0 0 24 24">
<!-- <path d="
M10.5,13.5
C8.5,11.5 9.5,11.5 9.5,10.5
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
"/>
<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"/>
</symbol>
</svg>

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

@ -1,5 +1,5 @@
<script>
import { onMount } from 'svelte';
import { onMount, afterUpdate } from 'svelte';
import Icon from '../../components/Icon.html';
export let sections = [];
@ -36,24 +36,57 @@
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>
<ul class='guide-toc'>
<ul bind:this={ul} class="guide-toc">
{#each sections as section}
<li>
<a class='section {section.slug === active_section ? "active": ""}' href='guide#{section.slug}'>
{section.metadata.title} {#if section.slug === active_section}
<Icon name='arrow-right' /> {/if}
</a>
{#each section.subsections as subsection}
<!-- see <script> below: on:click='scrollTo(event, subsection.slug)' -->
<a class='subsection {subsection.slug === active_section ? "active": ""}' href='guide#{subsection.slug}'>
{subsection.title} {#if subsection.slug === active_section}
<Icon name='arrow-right' /> {/if}
</a>
{/each}
</li>
<li>
<a class="section" class:active="{section.slug === active_section}" href="guide#{section.slug}">
{section.metadata.title}
{#if section.slug === active_section}
<Icon name="arrow-right" />
{/if}
</a>
{#each section.subsections as subsection}
<!-- see <script> below: on:click='scrollTo(event, subsection.slug)' -->
<a class="subsection" class:active="{subsection.slug === active_section}" href="guide#{subsection.slug}">
{subsection.title}
{#if subsection.slug === active_section}
<Icon name="arrow-right" />
{/if}
</a>
{/each}
</li>
{/each}
</ul>

@ -4,216 +4,245 @@ import * as fleece from 'golden-fleece';
import process_markdown from '../../utils/_process_markdown.js';
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');
const langs = {
'hidden-data': 'json',
'html-no-repl': 'html',
'hidden-data': 'json',
'html-no-repl': 'html',
};
// map lang to prism-language-attr
const prismLang = {
bash: 'bash',
html: 'markup',
js: 'javascript',
css: 'css',
bash: 'bash',
html: 'markup',
js: 'javascript',
css: 'css',
};
function btoa(str) {
return new Buffer(str).toString('base64');
return new Buffer(str).toString('base64');
}
const escaped = {
'"': '&quot;',
"'": '&#39;',
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;',
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
};
const unescaped = Object.keys(escaped).reduce(
(unescaped, key) => ((unescaped[escaped[key]] = key), unescaped),
{},
(unescaped, key) => ((unescaped[escaped[key]] = key), unescaped),
{}
);
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) {
try {
if (lang === 'html' && line.startsWith('<!--') && line.endsWith('-->')) {
return fleece.evaluate(line.slice(4, -3).trim());
}
if (
lang === 'js' ||
(lang === 'json' && line.startsWith('/*') && line.endsWith('*/'))
) {
return fleece.evaluate(line.slice(2, -2).trim());
}
} catch (err) {
// TODO report these errors, don't just squelch them
return null;
}
try {
if (lang === 'html' && line.startsWith('<!--') && line.endsWith('-->')) {
return fleece.evaluate(line.slice(4, -3).trim());
}
if (
lang === 'js' ||
(lang === 'json' && line.startsWith('/*') && line.endsWith('*/'))
) {
return fleece.evaluate(line.slice(2, -2).trim());
}
} catch (err) {
// TODO report these errors, don't just squelch them
return null;
}
}
// https://github.com/darkskyapp/string-hash/blob/master/index.js
function getHash(str) {
let hash = 5381;
let i = str.length;
let hash = 5381;
let i = str.length;
while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i);
return (hash >>> 0).toString(36);
while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i);
return (hash >>> 0).toString(36);
}
export const demos = new Map();
export default function() {
return fs
.readdirSync(`content/guide`)
.filter(file => file[0] !== '.' && path.extname(file) === '.md')
.map(file => {
const markdown = fs.readFileSync(`content/guide/${file}`, 'utf-8');
const { content, metadata } = process_markdown(markdown);
const groups = [];
let group = null;
let uid = 1;
const renderer = new marked.Renderer();
renderer.code = (source, lang) => {
source = source.replace(/^ +/gm, match =>
match.split(' ').join('\t'),
);
const lines = source.split('\n');
const meta = extractMeta(lines[0], lang);
let prefix = '';
let className = 'code-block';
if (lang === 'html' && !group) {
/* prettier-ignore
-----------------------------------------------
edit vedam
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>`;
}
group = { id: uid++, blocks: [] };
groups.push(group);
}
if (meta) {
source = lines.slice(1).join('\n');
const filename = meta.filename || (lang === 'html' && 'App.html');
if (filename) {
prefix = `<span class='filename'>${prefix} ${filename}</span>`;
className += ' named';
}
}
if (group) group.blocks.push({ meta: meta || {}, lang, source });
if (meta && meta.hidden) return '';
/* prettier-ignore
-----------------------------------------------
design-edit vedam
insert prism-highlighter
-----------------------------------------------
*/
let plang = prismLang[lang];
const highlighted = Prism.highlight(
source,
Prism.languages[plang],
lang,
);
return `<div class='${className}'>${prefix}<pre class='language-${plang}'><code>${highlighted}</code></pre></div>`;
};
blockTypes.forEach(type => {
const fn = renderer[type];
renderer[type] = function() {
group = null;
return fn.apply(this, arguments);
};
});
const html = marked(content, { renderer });
const hashes = {};
groups.forEach(group => {
const main = group.blocks[0];
if (main.meta.repl === false) return;
const hash = getHash(group.blocks.map(block => block.source).join(''));
hashes[group.id] = hash;
const json5 = group.blocks.find(block => block.lang === 'json');
const title = main.meta.title;
if (!title) console.error(`Missing title for demo in ${file}`);
demos.set(
hash,
JSON.stringify({
title: title || 'Example from guide',
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,
}),
);
});
const subsections = [];
const pattern = /<h3 id="(.+?)">(.+?)<\/h3>/g;
let match;
while ((match = pattern.exec(html))) {
const slug = match[1];
const title = unescape(
match[2]
.replace(/<\/?code>/g, '')
.replace(/\.(\w+)(\((.+)?\))?/, (m, $1, $2, $3) => {
if ($3) return `.${$1}(...)`;
if ($2) return `.${$1}()`;
return `.${$1}`;
}),
);
subsections.push({ slug, title });
}
return {
html: html.replace(/@@(\d+)/g, (m, id) => hashes[id] || m),
metadata,
subsections,
slug: file.replace(/^\d+-/, '').replace(/\.md$/, ''),
file,
};
});
return fs
.readdirSync(`content/guide`)
.filter(file => file[0] !== '.' && path.extname(file) === '.md')
.map(file => {
const markdown = fs.readFileSync(`content/guide/${file}`, 'utf-8');
const { content, metadata } = process_markdown(markdown);
const subsections = [];
const groups = [];
let group = null;
let uid = 1;
const renderer = new marked.Renderer();
renderer.code = (source, lang) => {
source = source.replace(/^ +/gm, match =>
match.split(' ').join('\t')
);
const lines = source.split('\n');
const meta = extractMeta(lines[0], lang);
let prefix = '';
let className = 'code-block';
if (lang === 'html' && !group) {
/* prettier-ignore
-----------------------------------------------
edit vedam
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>`;
}
group = { id: uid++, blocks: [] };
groups.push(group);
}
if (meta) {
source = lines.slice(1).join('\n');
const filename = meta.filename || (lang === 'html' && 'App.html');
if (filename) {
prefix = `<span class='filename'>${prefix} ${filename}</span>`;
className += ' named';
}
}
if (group) group.blocks.push({ meta: meta || {}, lang, source });
if (meta && meta.hidden) return '';
/* prettier-ignore
-----------------------------------------------
design-edit vedam
insert prism-highlighter
-----------------------------------------------
*/
let plang = prismLang[lang];
const highlighted = Prism.highlight(
source,
Prism.languages[plang],
lang
);
return `<div class='${className}'>${prefix}<pre class='language-${plang}'><code>${highlighted}</code></pre></div>`;
};
const seen = new Set();
renderer.heading = (text, level, rawtext) => {
if (level <= 3) {
const slug = rawtext
.toLowerCase()
.replace(/[^a-zA-Z0-9]+/g, '-')
.replace(/^-/, '')
.replace(/-$/, '');
if (seen.has(slug)) throw new Error(`Duplicate slug ${slug}`);
seen.add(slug);
if (level === 3) {
const title = unescape(
text
.replace(/<\/?code>/g, '')
.replace(/\.(\w+)(\((.+)?\))?/, (m, $1, $2, $3) => {
if ($3) return `.${$1}(...)`;
if ($2) return `.${$1}()`;
return `.${$1}`;
})
);
subsections.push({ slug, title });
}
return `
<h${level}>
<span id="${slug}" class="offset-anchor"></span>
<a href="guide#${slug}" class="anchor" aria-hidden="true"></a>
${text}
</h${level}>`;
}
return `<h${level}>${text}</h${level}>`;
};
blockTypes.forEach(type => {
const fn = renderer[type];
renderer[type] = function() {
group = null;
return fn.apply(this, arguments);
};
});
const html = marked(content, { renderer });
const hashes = {};
groups.forEach(group => {
const main = group.blocks[0];
if (main.meta.repl === false) return;
const hash = getHash(group.blocks.map(block => block.source).join(''));
hashes[group.id] = hash;
const json5 = group.blocks.find(block => block.lang === 'json');
const title = main.meta.title;
if (!title) console.error(`Missing title for demo in ${file}`);
demos.set(
hash,
JSON.stringify({
title: title || 'Example from guide',
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>
import { onMount } from 'svelte';
import { onMount, afterUpdate } from 'svelte';
import GuideContents from './_GuideContents.html';
import Icon from '../../components/Icon.html';
@ -18,6 +18,7 @@
let active_section;
let container;
let aside;
onMount(() => {
const anchors = container.querySelectorAll('[id]');
@ -148,21 +149,54 @@
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 > code) {
pointer-events: none;
font-weight: 700;
font-size: var(--h3);
color: var(--second);
padding: 6.4rem 0 1.6rem 0;
margin: 6.4rem 0 1.6rem 0;
background: transparent;
line-height: 1;
}
.content :global(code) {
padding: .3rem .8rem .3rem;
margin: 0 0.2rem;
top: -.1rem;
background: #f9f9f9;
background: #f4f4f4;
}
.content :global(pre) :global(code) {
padding: 0;
margin: 0;
top: 0;
background: transparent;
}
.content :global(.icon) {
@ -194,11 +228,16 @@
section :global(blockquote) {
color: var(--flash);
border: 2px solid currentColor;
color: hsl(204, 100%, 50%);
border: 2px solid var(--flash);
padding-left: 8.8rem;
}
section :global(blockquote) :global(code) {
background: hsl(204, 100%, 95%) !important;
color: hsl(204, 100%, 50%);
}
section :global(blockquote::before) {
content: ' ';
position: absolute;
@ -217,8 +256,11 @@
<div bind:this={container} class='content linkify listify'>
{#each sections as section}
<section id='{section.slug}'>
<section data-id={section.slug}>
<h2>
<span class="offset-anchor" id={section.slug}></span>
<a href="#{section.slug}" class="anchor" aria-hidden></a>
{section.metadata.title}
<small>
<a href='https://github.com/sveltejs/svelte/edit/master/site/content/guide/{section.file}' title='edit this section'>
@ -230,8 +272,8 @@
{/each}
</div>
<aside class="sidebar-container">
<aside bind:this={aside} class="sidebar-container">
<div class="sidebar">
<GuideContents {sections} />
<GuideContents {sections} {active_section} />
</div>
</aside>

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

@ -1,4 +1,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">
<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>

Before

Width:  |  Height:  |  Size: 403 B

After

Width:  |  Height:  |  Size: 290 B

Loading…
Cancel
Save