svelte/site/src/routes/guide/_sections.js

249 lines
5.8 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import fs from 'fs';
import path from 'path';
import * as fleece from 'golden-fleece';
import process_markdown from '../../utils/_process_markdown.js';
import marked from 'marked';
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',
};
// map lang to prism-language-attr
const prismLang = {
bash: 'bash',
html: 'markup',
js: 'javascript',
css: 'css',
};
function btoa(str) {
return new Buffer(str).toString('base64');
}
const escaped = {
'"': '"',
"'": ''',
'&': '&',
'<': '&lt;',
'>': '&gt;',
};
const unescaped = Object.keys(escaped).reduce(
(unescaped, key) => ((unescaped[escaped[key]] = key), unescaped),
{}
);
function unescape(str) {
return String(str).replace(/&.+?;/g, match => unescaped[match] || match);
}
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;
}
}
// https://github.com/darkskyapp/string-hash/blob/master/index.js
function getHash(str) {
let hash = 5381;
let i = str.length;
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 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,
};
});
}