@ -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 = {
'"' : '"' ,
"'" : ''' ,
'&' : '&' ,
'<' : '<' ,
'>' : '>' ,
'"' : '"' ,
"'" : ''' ,
'&' : '&' ,
'<' : '<' ,
'>' : '>' ,
} ;
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 ) {
/ * p r e t t i e r - i g n o r e
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
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 '' ;
/ * p r e t t i e r - i g n o r e
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
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 ) {
/ * p r e t t i e r - i g n o r e
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
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 '' ;
/ * p r e t t i e r - i g n o r e
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
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" > < / s p a n >
< a href = "guide#${slug}" class = "anchor" aria - hidden = "true" > < / a >
$ { text }
< / h $ { l e v e l } > ` ;
}
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 ,
} ;
} ) ;
}