├── .gitignore ├── .npmignore ├── Makefile ├── Readme.md ├── bin ├── bottom.html ├── demobox ├── demobox.js ├── get-config.js ├── render-code.js ├── render-file.js ├── render-utils.js ├── render.js ├── themes.js └── top.html ├── demo.md ├── demo.png ├── index.js ├── less ├── colors.js ├── index.less └── theme.less ├── lib ├── codemirror-rx.js ├── index.js └── jsx.js ├── package.json ├── run.js ├── scripts ├── main-demo.js ├── make-themes.js ├── make-themes.py ├── md-gen.js ├── md-top.md └── theme-demo.js └── www ├── demo.css └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | node_modules 3 | .module-cache 4 | www/demo.html 5 | www/react-demobox.js 6 | /build 7 | /pages 8 | scripts/tmp 9 | themes.md 10 | *.swo 11 | /css 12 | /lib-compiled 13 | /demobox*.tgz 14 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.swo 3 | .module-cache 4 | themes.md 5 | demo.md 6 | /demobox-*.tgz 7 | /build 8 | /node_modules 9 | /pages 10 | /scripts 11 | /www 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | ARGS=-t [ reactify --es6 --everything --visitors jstransform/visitors/es6-destructuring-visitors ] 3 | 4 | js: 5 | browserify ${ARGS} -d run.js -x react -x react/addons -o build/demobox.js 6 | 7 | precompile: less-dist 8 | jsx --harmony lib lib-compiled 9 | 10 | react: 11 | browserify -r react -r react/addons -o build/react.js 12 | 13 | less-dist: 14 | mkdir -p css 15 | lessc less/theme.less css/theme.css 16 | lessc less/index.less css/demobox.css 17 | 18 | less: 19 | lessc less/theme.less build/theme.css 20 | lessc less/index.less build/demobox.css 21 | 22 | colors: 23 | mkdir -p build/themes 24 | cd less && node colors.js 25 | 26 | pages: 27 | mkdir -p pages 28 | rsync build/* -r pages 29 | 30 | gh-pages: pages 31 | cd pages && git add . && git commit -am'update pages' && git push 32 | 33 | # depends on slimerjs 34 | gen-themes: 35 | mkdir -p scripts/tmp 36 | rsync build/* scripts/tmp 37 | node scripts/make-themes.js 38 | python scripts/make-themes.py 39 | mv scripts/tmp/*.png pages/theme_pics 40 | node scripts/md-gen.js 41 | 42 | md-gen: 43 | node scripts/md-gen.js 44 | 45 | gen-demo: 46 | slimerjs scripts/main-demo.js 47 | 48 | index: 49 | ./bin/demobox -i Readme.md -o pages/index.html --no-cdn 50 | 51 | themes: 52 | ./bin/demobox --no-cdn -i themes.md -o pages/themes.html 53 | 54 | demo: 55 | ./bin/demobox --no-cdn -o pages/demo.html 56 | 57 | .PHONY: less js pages css precompile 58 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | 19 | 20 | ### Demobox is a quick & stylish way of getting a demo page up and running for your new react component. 21 | 22 | There are three ways to use demobox, for varying simplicity and flexibility. 23 | 24 | 1. [demo page generator](#demo-page-generator) -- markdown to demo page converter 25 | 2. [drop-in script](#the-demoboxjs-drop-in-script) -- turn text boxes into live editors 26 | 3. [react component](#as-a-react-component) -- integrate a live editor into your project 27 | 28 | Take a look at the [FAQ](#faq) at the bottom, or head over to 29 | [github](https://github.com/jaredly/demobox/issues) to file an issue or ask a 30 | question. 31 | 32 | # Demo Page Generator 33 | 34 | The `demobox` cli tool will turn a regular markdown file with annotated code 35 | snippets into a stylish demo page with editable examples. You can look at the 36 | markdown source for this page 37 | [here](https://github.com/jaredly/demobox/blob/master/Readme.md) as an 38 | example. Also the source for the demo page 39 | ([demo.md](https://github.com/jaredly/demobox/blob/master/demo.md)) 40 | showcases a number of features. 41 | 42 | ```bash 43 | $ npm install -g demobox 44 | $ demobox -i demo.md -o demo.html 45 | ``` 46 | 47 | ## `demo.md` (||) 48 | 49 | ```markdown 50 | 51 | --- 52 | title: Demobox Demos 53 | subtitle: Getting rather meta 54 | fontPair: Open Sans 55 | colors: light-green 56 | links: 57 | Home: index.html 58 | Demos: demos.html 59 | Themes: themes.html 60 | Github: https://github.com/jaredly/demobox 61 | --- 62 | 63 | # First example 64 | 65 | ``ˋjavascript 66 | // @demobox height=150px 67 | var first = 'javascript code' 68 | , second = `You can evaluate ${first} with es6 goodness.`; 69 | // the last line must be an expression that results in a react 70 | // element. 71 |

72 | {second}
73 | JSX is just fine 74 |

75 | ``ˋ 76 | 77 | ... etc. 78 | ``` 79 | 80 | ## `demo.html` rendered page (||) 81 | 82 | [![demo page](./demo.png)](demo.html) 83 | 84 | ## Configuration 85 | 86 | These go in the yaml frontmatter (similar to jekyll) at the top of the 87 | markdown file. 88 | 89 | - **title:** the title of the page (default: Demo Page) 90 | - **subtitle:** the subtitle (default: none) 91 | - **links:** a map of title:href for links displayed in the header 92 | - **styles:** a list of css files to include on the page 93 | - **scripts:** a list of js files to include 94 | - **extraHead:** a list of html files to inject at the end of the head 95 | - **bodyTop:** a list of html files to inject at the top of the body 96 | - **ga:** google analytics tracking code. If present, GA code is added to the 97 | bottom of the page. 98 | 99 | ### Themes 100 | 101 | There are two configuration options associated with theming, `colors` and 102 | `fontPair`. Look at the [themes page](themes.html) for examples and more 103 | information. 104 | 105 | ## Special Headings 106 | 107 | There are a few suffixes you can put onto headings that will give them extra 108 | properties (See the [markdown source](https://github.com/jaredly/demobox/blob/master/Readme.md) of this page for an example). 109 | 110 | - `(<<)` marks the section as collapsed 111 | - `(>>)` marks the section as collapsible but expanded 112 | - `(||)` makes the section part of a column group. Adjascent sections (of the 113 | same heading level) that have this marking will be rendered side-by-side. 114 | An example of this is the `demo.md` and "`demo.html` rendered page" 115 | sections above. 116 | 117 | 118 | # The `demobox.js` drop-in script 119 | 120 | ### Included in the `` 121 | 122 | ```html 123 | 124 | 125 | 126 | ``` 127 | 128 | ### Markup in the page 129 | 130 | ```html 131 | 137 | ``` 138 | 139 | ### Rendered as a demobox 140 | 141 | The `demobox.js` script finds all `textarea`s with the `data-demobox` 142 | attribute and converts them into demoboxes that look like this: 143 | 144 | ```jsx 145 | // @demobox 146 | // some great code here 147 | var x = element; 148 | 149 | End with a react {x} 150 | ``` 151 | 152 | Configuration options can be given as `data-*` attributes on the textarea. 153 | Look at the html source of this page (and [the demo page](demo.html)) for example usage. 154 | 155 | # As a react component 156 | 157 | If you install the `demobox` library from npm (`npm install -S demobox`) then 158 | you can use the `DemoBox` react component in your project. 159 | 160 | In this demobox, you can play with the `DemoBox` component :). 161 | 162 | ```jsx 163 | // @demobox 164 | var value = `\ 165 | 166 | It's demoboxes all the way down... 167 | 168 | `; 169 | 170 | // Try changing position to left, right 171 | // or top, and codeMirror to false 172 | 178 | ``` 179 | 180 | # FAQ 181 | 182 | ### Why not use jekyll / some other static site generator? 183 | Demobox is for quick and simple jobs, and requires very little configuration 184 | to get something usable and beautiful. You don't have time to set up a 185 | full-blown website for each little component you create, but you want a way to 186 | **show the world** what you've done in a classy way. 187 | 188 | ### Why not use jsfiddle / plunkr / codepen for embedding editable code snippets? 189 | Demobox works with vesion control; jsfiddle etc. does not. Demobox also has 190 | first-class support for JSX and React components (and support for HTML/CSS coming soon!). 191 | 192 | -------------------------------------------------------------------------------- /bin/bottom.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | {ga} 7 | 8 | 9 | -------------------------------------------------------------------------------- /bin/demobox: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var marked = require('marked') 4 | , fs = require('fs') 5 | , path = require('path') 6 | , exec = require('child_process').exec 7 | 8 | , renderFile = require('./render-file') 9 | 10 | var program = require('commander') 11 | 12 | var cmds = { 13 | gen: function (options) { 14 | var inFile = options.infile || 'demo.md' 15 | , outFile = options.outfile || inFile.slice(0, -3) + '.html' 16 | 17 | if (!fs.existsSync(inFile)) { 18 | console.log(inFile + ' does not exist') 19 | console.log() 20 | program.help() 21 | } 22 | 23 | if (!fs.existsSync(path.dirname(outFile))) { 24 | console.log('Destination directory ' + path.dirname(outFile) + ' does not exist') 25 | console.log() 26 | program.help() 27 | } 28 | 29 | renderFile(inFile, outFile, !options.cdn) 30 | }, 31 | }; 32 | 33 | program 34 | .usage('demobox [options]') 35 | .option('-i, --infile [infile]', "The path of the markdown file (default: demo.md)") 36 | .option('-o, --outfile [outfile]', "The path of the markdown file (default: demo.html)") 37 | .option('--no-cdn', "Don't use github pages to serve demobox assets") 38 | .version('1.0.0') 39 | 40 | program.parse(process.argv) 41 | 42 | /* 43 | var cmd = program.args.length ? program.args[0] : 'gen' 44 | 45 | if (!cmds[cmd]) { 46 | program.help() 47 | } 48 | */ 49 | 50 | cmds.gen(program) 51 | 52 | -------------------------------------------------------------------------------- /bin/demobox.js: -------------------------------------------------------------------------------- 1 | // #!/usr/bin/env node 2 | 3 | var marked = require('marked') 4 | , rend = new marked.Renderer() 5 | 6 | rend.code = require('./render-code') 7 | 8 | var program = require('commander') 9 | 10 | program 11 | .version('1.0.0') 12 | .usage('[options] ') 13 | .parse(process.argv) 14 | 15 | if (program.args.length != 2) { 16 | program.help() 17 | } 18 | 19 | var fileName = program.args[0] 20 | , outName = program.args[1] 21 | 22 | if (!fs.existsSync(fileName)) { 23 | console.log(fileName + ' does not exist') 24 | console.log() 25 | program.help() 26 | } 27 | 28 | if (!fs.existsSync(path.dirname(outName))) { 29 | console.log('Destination directory ' + path.dirname(outName) + ' does not exist') 30 | console.log() 31 | program.help() 32 | } 33 | 34 | var body 35 | try { 36 | body = marked(fs.readFileSync(fileName).toString('utf8'), {renderer: renderer}) 37 | } catch (e) { 38 | console.log('Failed to render markdown!') 39 | console.log(e.message + '\n' + e.stack) 40 | process.exit(1) 41 | } 42 | 43 | var top = fs.readFileSync(__dirname + '/top.html', 'utf8') 44 | , bottom = fs.readFileSync(__dirname + '/bottom.html', 'utf8') 45 | 46 | fs.writeFileSync(outName, top + body + bottom) 47 | 48 | -------------------------------------------------------------------------------- /bin/get-config.js: -------------------------------------------------------------------------------- 1 | 2 | var yamlish = require('yamlish') 3 | , deepcopy = require('deepcopy') 4 | 5 | module.exports = getConfig 6 | 7 | module.exports.DEFAULTS = { 8 | title: 'Demo Page', 9 | subtitle: '', 10 | links: {}, 11 | cdn: true, 12 | ga: false, 13 | fontPair: '', 14 | colors: '', 15 | styles: [], 16 | scripts: [], 17 | source: '', 18 | css: '', 19 | js: '', 20 | extraHead: [], 21 | bodyTop: [], 22 | demobox: { 23 | height: 150, 24 | output: true, 25 | position: "right", 26 | }, 27 | } 28 | 29 | function getConfig(text) { 30 | var config = deepcopy(module.exports.DEFAULTS) 31 | var parsed 32 | try { 33 | parsed = yamlish.decode(text) 34 | } catch (e) { 35 | console.log('Failed to parse config at top of file.') 36 | process.exit(2) 37 | } 38 | for (var name in config) { 39 | if (undefined === parsed[name]) continue 40 | if (Array.isArray(config[name])) { 41 | if ('string' === typeof parsed[name]) { 42 | config[name].push(parsed[name]) 43 | } else if (Array.isArray(parsed[name])) { 44 | config[name] = config[name].concat(parsed[name]) 45 | } else { 46 | console.log('Unknown value for ' + name + ' in config') 47 | } 48 | } else if ('object' === typeof config[name]) { 49 | for (var sub in parsed[name]) { 50 | config[name][sub] = parsed[name][sub] 51 | } 52 | } else { 53 | config[name] = parsed[name] 54 | } 55 | } 56 | return config 57 | } 58 | 59 | -------------------------------------------------------------------------------- /bin/render-code.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var marked = require("marked") 4 | , deepcopy = require('deepcopy') 5 | 6 | module.exports = renderCode; 7 | 8 | var conflinex = { 9 | javascript: /^\/\/ @demobox([^\n]*)/, 10 | html: /^/, 11 | css: /^\/\*+ @demobox (.*?)\*+\// 12 | }; 13 | conflinex.jsx = conflinex.javascript; 14 | conflinex.less = conflinex.css; 15 | 16 | function renderCode(rend, defaults, text, language) { 17 | var config = getConfig(defaults, text, language); 18 | if (!config) return marked.defaults.renderer.code.call(rend, text.trim(), language); 19 | // remove config line 20 | text = text.slice(text.indexOf("\n") + 1); 21 | 22 | // will be prepended to a later box 23 | if (config.withNext) { 24 | if (rend._demobox_next) { 25 | rend._demobox_next.push([text, config, language]); 26 | } else { 27 | rend._demobox_next = [[text, config, language]]; 28 | } 29 | return ""; 30 | } 31 | 32 | var attrs = configAttrs(config); 33 | if (!rend._demobox_next) { 34 | return ""; 35 | } 36 | var langs = rend._demobox_next || []; 37 | delete rend._demobox_next; 38 | langs.push([text, config, language]); 39 | return "
\n" + langs.map(function (lang) { 40 | return " "; 41 | }).join("\n") + "
"; 42 | } 43 | 44 | function configValue(value) { 45 | if (value === "true") return false; 46 | if (value === "false") return false; 47 | if (value.match(/^\d+(\.\d+)?$/)) { 48 | return +value; 49 | } 50 | if (value[0] === "\"" && value[value.length - 1] === "\"") { 51 | return value.slice(1, -1); 52 | } 53 | return value; 54 | } 55 | 56 | function configAttrs(config) { 57 | return Object.keys(config).map(function (key) { 58 | return key + "=\"" + ('' + config[key]).replace(/"/g, """).replace(/') { 20 | parts[1] = parts[1].trim().slice(3) 21 | } 22 | } 23 | if (!parts[0].trim()) { 24 | parts.shift() 25 | } 26 | config = getConfig(parts[0]) 27 | raw = parts.slice(1).join('\n---\n') 28 | } 29 | 30 | if (noCDN) { 31 | config.cdn = false 32 | } 33 | 34 | for (var name in extraConfig) { 35 | config[name] = extraConfig[name] 36 | } 37 | 38 | var theme = utils.getTheme(config) 39 | 40 | var top = fs.readFileSync(__dirname + '/top.html', 'utf8') 41 | , bottom = fs.readFileSync(__dirname + '/bottom.html', 'utf8') 42 | , body = require('./render')(raw, config) 43 | , cdn = config.cdn ? 'https://jaredly.github.io/demobox/' : '' 44 | 45 | top = utils.format(top, { 46 | title: config.title, 47 | subtitle: config.subtitle, 48 | links: utils.makeLinks(config.links), 49 | 50 | cdn: cdn, 51 | 52 | 'font-imports': theme.fonts.imports, 53 | 'font:head': theme.fonts.head.name, 54 | 'font:body': theme.fonts.body.name, 55 | 56 | css: config.css, 57 | js: config.js, 58 | 59 | 'color:main': theme.colors.main, 60 | 'color:lightest': theme.colors.lightest, 61 | 'color:accent': theme.colors.accent, 62 | 'color:accent-light': theme.colors.accentLight, 63 | 64 | scripts: config.scripts.map(function (name) { 65 | return '' 66 | }).join('\n'), 67 | styles: config.styles.map(function (name) { 68 | return '' 69 | }).join('\n'), 70 | extraHead: config.extraHead.map(function (name) { 71 | return fs.readFileSync(name).toString('utf8') 72 | }).join('\n'), 73 | bodyTop: config.bodyTop.map(function (name) { 74 | return fs.readFileSync(name).toString('utf8') 75 | }).join('\n') 76 | }) 77 | 78 | bottom = utils.format(bottom, { 79 | ga: utils.makeGoogleAnalytics(config.ga), 80 | 'source-line': config.source && '

view the markdown source for this page

', 81 | }) 82 | 83 | fs.writeFileSync(outName, top + body + bottom) 84 | } 85 | 86 | -------------------------------------------------------------------------------- /bin/render-utils.js: -------------------------------------------------------------------------------- 1 | 2 | var themes = require('./themes') 3 | 4 | module.exports = { 5 | format: format, 6 | getTheme: getTheme, 7 | makeLinks: makeLinks, 8 | makeGoogleAnalytics: makeGoogleAnalytics, 9 | stripHidden: stripHidden, 10 | } 11 | 12 | function stripHidden(text) { 13 | return text.replace(/^\s*\s*\n(.|\n)*?\n\s*\s*$/gim, '') 14 | } 15 | 16 | function format(str, dct) { 17 | return str.replace(/{([^}\n]+)}/g, function (full, name) { 18 | if (undefined === dct[name]) return '' 19 | return dct[name] 20 | }) 21 | } 22 | 23 | function chooseByTitle(title, object) { 24 | var total = [].map.call(title, function (a) {return a.charCodeAt(0)}).reduce(function (a, b) {return a+b}, 0) 25 | , keys = Object.keys(object) 26 | return object[keys[total % keys.length]] 27 | } 28 | 29 | function fontImports(font) { 30 | return '' 32 | } 33 | 34 | function getTheme(config) { 35 | var theme = {} 36 | if (config.colors) { 37 | theme.colors = themes.colors[config.colors.toLowerCase()] || themes.colors.red 38 | } else { 39 | theme.colors = chooseByTitle(config.title, themes.colors) 40 | } 41 | 42 | if (config.fontPair) { 43 | theme.fonts = themes.fonts[config.fontPair.toLowerCase()] || themes.fonts['open sans'] 44 | } else { 45 | theme.fonts = chooseByTitle(config.title, themes.fonts) 46 | } 47 | theme.fonts.imports = fontImports(theme.fonts) 48 | 49 | return theme 50 | } 51 | 52 | function makeLinks(links) { 53 | return Object.keys(links).map(function (name) { 54 | return '' + name + '' 55 | }).join('\n') 56 | } 57 | 58 | function makeGoogleAnalytics(ga) { 59 | if (!ga) return '' 60 | return "" 68 | } 69 | 70 | 71 | -------------------------------------------------------------------------------- /bin/render.js: -------------------------------------------------------------------------------- 1 | 2 | var marked = require('marked') 3 | , hljs = require('highlight.js') 4 | 5 | , utils = require('./render-utils') 6 | 7 | // Synchronous highlighting with highlight.js 8 | marked.setOptions({ 9 | langPrefix: '', 10 | highlight: function (code, language) { 11 | if (!language) return hljs.highlightAuto(code).value 12 | return hljs.highlight(language, code).value; 13 | } 14 | }); 15 | 16 | function linkTarget(title) { 17 | return title.replace(/<[^>]+>/g, '').replace(/\./g, '').replace(/[^\w]/g, '-').toLowerCase() 18 | } 19 | 20 | function link(href, title, text) { 21 | var target = '' 22 | if (href.indexOf('//') === -1) { 23 | if (href.slice(-3) === '.md') { 24 | href = href.slice(0, -3) + '.html' 25 | } 26 | } else { 27 | // external link 28 | target = ' target="_blank"' 29 | } 30 | if (title) { 31 | title = ' title="' + title + '"' 32 | } else { 33 | title = '' 34 | } 35 | return '' + text + '' 36 | } 37 | 38 | module.exports = function (raw, config) { 39 | raw = utils.stripHidden(raw) 40 | 41 | var rend = new marked.Renderer() 42 | rend._demobox_headlevel = [] 43 | rend._demobox_column = false 44 | rend.code = require('./render-code').bind(null, rend, config.demobox) 45 | rend.link = link 46 | 47 | rend.heading = function (title, level) { 48 | var l = rend._demobox_headlevel 49 | , text = '' 50 | while (l.length && l[l.length-1] >= level) { 51 | text += '\n' 52 | l.pop() 53 | } 54 | l.push(level) 55 | var collapsed = title.match(/\((<<|>>)\)\s*$/) 56 | if (collapsed) { 57 | if (rend._demobox_column == level) { 58 | rend._demobox_column = false 59 | text += '\n' 60 | } 61 | title = title.slice(0, -collapsed[0].length).trim() 62 | text += '\n' 63 | text += '\n
\n' 65 | } else { 66 | var column = title.match(/\(\|\|\)\s*$/) 67 | if (column) { 68 | if (!rend._demobox_column) { 69 | rend._demobox_column = level 70 | text += '\n
' 71 | } 72 | title = title.slice(0, -column[0].length).trim() 73 | text += '\n' 74 | text += '\n
\n' 75 | } else { 76 | if (rend._demobox_column == level) { 77 | rend._demobox_column = false 78 | text += '\n
' 79 | } 80 | text += '\n' 81 | text += '\n
\n' 82 | } 83 | } 84 | text += '' + 85 | '' + 86 | title + 87 | '\n' 88 | return text 89 | } 90 | 91 | var body 92 | try { 93 | body = marked(raw, {renderer: rend, langPrefix: ''}) 94 | } catch (e) { 95 | console.log('Failed to render markdown!') 96 | console.log(e.message + '\n' + e.stack) 97 | process.exit(1) 98 | } 99 | 100 | return body 101 | } 102 | 103 | -------------------------------------------------------------------------------- /bin/themes.js: -------------------------------------------------------------------------------- 1 | var names = ["Red", "Pink", "Purple", "Deep Purple", "Indigo", "Blue", 2 | "Light Blue", "Cyan", "Teal", "Green", "Light Green", "Lime", "Yellow", 3 | "Amber", "Orange", "Deep Orange", "Brown", "Grey", "Blue Grey"] 4 | var main = ["#f44336", "#e91e63", "#9c27b0", "#673ab7", "#3f51b5", "#2196f3", 5 | "#03a9f4", "#00bcd4", "#009688", "#4caf50", "#8bc34a", "#cddc39", "#ffeb3b", 6 | "#ffc107", "#ff9800", "#ff5722", "#795548", "#9e9e9e", "#607d8b"] 7 | var lightest = ["#ffebee", "#fce4ec", "#f3e5f5", "#ede7f6", "#e8eaf6", 8 | "#e3f2fd", "#e1f5fe", "#e0f7fa", "#e0f2f1", "#e8f5e9", "#f1f8e9", "#f9fbe7", 9 | "#fffde7", "#fff8e1", "#fff3e0", "#fbe9e7", "#efebe9", "#fafafa", "#eceff1", 10 | "#ffffff"] 11 | var accent = ["#b71c1c", "#880e4f", "#4a148c", "#311b92", "#1a237e", 12 | "#0d47a1", "#01579b", "#006064", "#004d40", "#1b5e20", "#33691e", "#827717", 13 | "#f57f17", "#ff6f00", "#e65100", "#bf360c", "#3e2723", "#212121", "#263238"] 14 | var accentLight = ["#d32f2f", "#c2185b", "#7b1fa2", "#512da8", "#303f9f", 15 | "#1976d2", "#0288d1", "#0097a7", "#00796b", "#388e3c", "#689f38", "#afb42b", 16 | "#fbc02d", "#ffa000", "#f57c00", "#e64a19", "#5d4037", "#616161", "#455a64"] 17 | var colors = {} 18 | 19 | for (var i=0; i 2 | 3 | 4 | 5 | {title} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | {font-imports} 20 | 56 | 57 | {styles} 58 | {scripts} 59 | {extraHead} 60 | 61 | 62 | 63 | 64 | 65 | {bodyTop} 66 | 67 | 77 | 78 |
79 | -------------------------------------------------------------------------------- /demo.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Demobox Demos 3 | subtitle: Getting rather meta 4 | fontPair: Open Sans 5 | colors: light-green 6 | ga: UA-7002862-5 7 | links: 8 | Home: index.html 9 | Demos: 10 | Themes: themes.html 11 | Github: https://github.com/jaredly/demobox 12 | --- 13 | 14 | Check out [the source for this page](https://github.com/jaredly/demobox/blob/master/demo.md) 15 | and [the source for the home page](https://github.com/jaredly/demobox/blob/master/Readme.md) for extended examples of using `demobox` page generator. 16 | 17 | This page demonstrates some of the configuration for the `DemoBox` component. 18 | 19 | # First example 20 | 21 | ```javascript 22 | // @demobox height=150px 23 | var first = 'javascript code' 24 | , second = `You can evaluate ${first} with es6 goodness.`; 25 | // the last line must be an expression that results in a react 26 | // element. 27 |

28 | {second}
29 | JSX is just fine 30 |

31 | ``` 32 | 33 | ## The source for this demo (<<) 34 | 35 | ### Using `demobox` cli (||) 36 | 37 | ````markdown 38 | ```javascript 39 | // @demobox 40 | var first = 'javascript code' 41 | , second = `You can evaluate ${first} with es6 goodness.`; 42 | // the last line must be an expression that results in a react 43 | // element. 44 |

45 | {second} 46 | JSX is just fine 47 |

48 | ``` 49 | ```` 50 | 51 | ### Using `demobox.js` drop-in script (||) 52 | 53 | ```html 54 | 64 | ``` 65 | 66 | # Slightly larger example 67 | 68 | The rest of these examples will show demobox-in-demobox, to make things 69 | easier. 70 | 71 | ```jsx 72 | // @demobox 73 | var initialValue=`\ 74 | var name = 'Sara' 75 | var NameComponent = React.createClass({ 76 | render: function () { 77 | return 78 | Hello {this.props.name} 79 | 80 | } 81 | }); 82 | `; 83 | 84 | 87 | ``` 88 | 89 | # Other Positions 90 | 91 | The "position" argument allows you to specify where the output should 92 | be displayed. Default is "right". 93 | 94 | ```jsx 95 | // @demobox width=300 96 |
97 | 98 |
99 | 100 |
101 | 102 |
103 | 104 |
105 | ``` 106 | 107 | # Without CodeMirror (just uses a text box) 108 | 109 | ```jsx 110 | // @demobox 111 | 112 | ``` 113 | 114 | # Advanced Usage: External Output Node 115 | 116 | 117 | 118 | 133 | 145 | 146 | 147 | 148 | 151 | 152 |
119 | 132 | 134 |

Source HTML

135 |
<textarea data-demobox data-target="#id-of-target">
136 | // code here
137 | <span>
138 |   <strong>[render target] </strong>
139 |   This demo box one has a render target
140 |   that is external, in a different
141 |   cell of this table
142 | </span>
143 | </textarea>
144 |
149 |
150 |
153 | 154 | -------------------------------------------------------------------------------- /demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredly/demobox/0623506aced7a8e31298079353448eb0da2139c3/demo.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = require('./lib') 3 | 4 | -------------------------------------------------------------------------------- /less/colors.js: -------------------------------------------------------------------------------- 1 | 2 | var names = ["Red", "Pink", "Purple", "Deep Purple", "Indigo", "Blue", "Light Blue", "Cyan", "Teal", "Green", "Light Green", "Lime", "Yellow", "Amber", "Orange", "Deep Orange", "Brown", "Grey", "Blue Grey"] 3 | var main = ["#f44336", "#e91e63", "#9c27b0", "#673ab7", "#3f51b5", "#2196f3", "#03a9f4", "#00bcd4", "#009688", "#4caf50", "#8bc34a", "#cddc39", "#ffeb3b", "#ffc107", "#ff9800", "#ff5722", "#795548", "#9e9e9e", "#607d8b"] 4 | var accent = ["#b71c1c", "#880e4f", "#4a148c", "#311b92", "#1a237e", "#0d47a1", "#01579b", "#006064", "#004d40", "#1b5e20", "#33691e", "#827717", "#f57f17", "#ff6f00", "#e65100", "#bf360c", "#3e2723", "#212121", "#263238"] 5 | var accentLight = ["#d32f2f", "#c2185b", "#7b1fa2", "#512da8", "#303f9f", "#1976d2", "#0288d1", "#0097a7", "#00796b", "#388e3c", "#689f38", "#afb42b", "#fbc02d", "#ffa000", "#f57c00", "#e64a19", "#5d4037", "#616161", "#455a64"] 6 | 7 | var themes = [] 8 | 9 | var fs = require('fs') 10 | var tpl = fs.readFileSync('./tpl.less', 'utf8') 11 | var less = require('less') 12 | 13 | function save(filename, err, text) { 14 | if (err) return console.error('Failed to compile ' + filename, err) 15 | fs.writeFileSync(filename, text.css) 16 | } 17 | 18 | for (var i=0; i :first-child { 4 | cursor: pointer; 5 | padding: 5px; 6 | 7 | &:hover { 8 | cursor: pointer; 9 | outline: 1px solid #ccc; 10 | } 11 | 12 | &::after { 13 | content: '-'; 14 | display: inline-block; 15 | padding-left: 5px; 16 | } 17 | } 18 | 19 | &.collapsed { 20 | > :not(:first-child) { 21 | display: none; 22 | } 23 | > :first-child { 24 | font-style: italic; 25 | color: #666; 26 | 27 | &::after { 28 | content: '+'; 29 | } 30 | } 31 | } 32 | } 33 | 34 | 35 | 36 | .DemoBox_head { 37 | font-size: 14px; 38 | font-family: monospace; 39 | padding: 4px 6px; 40 | background-color: #BDBDBD; 41 | border-bottom: 1px solid #ccc; 42 | color: #616161; 43 | 44 | a { 45 | text-decoration: none; 46 | color: inherit; 47 | display: block; 48 | } 49 | } 50 | 51 | .DemoBox_text { 52 | border: 1px solid #cca; 53 | background-color: #f0f0f0; 54 | 55 | .CodeMirrorRx { 56 | margin: 5px; 57 | } 58 | 59 | .CodeMirror { 60 | background: transparent; 61 | height: auto; 62 | } 63 | 64 | textarea { 65 | border: none; 66 | width: 200px; 67 | height: 200px; 68 | background: transparent; 69 | 70 | &:focus { 71 | outline: none; 72 | } 73 | } 74 | } 75 | 76 | .DemoBox { 77 | display: flex; 78 | 79 | &-top { 80 | flex-direction: column; 81 | } 82 | &-bottom { 83 | flex-direction: column-reverse; 84 | } 85 | &-left { 86 | flex-direction: row; 87 | } 88 | &-right { 89 | flex-direction: row-reverse; 90 | } 91 | &-top, &-left { 92 | justify-content: flex-start; 93 | } 94 | &-bottom, &-right { 95 | justify-content: flex-end; 96 | } 97 | } 98 | 99 | 100 | .DemoBox_output { 101 | border: 1px solid #ccc; 102 | padding: 10px; 103 | display: flex; 104 | align-items: center; 105 | justify-content: center; 106 | 107 | .DemoBox-top > & { 108 | border-bottom: none; 109 | } 110 | .DemoBox-bottom > & { 111 | border-top: none; 112 | } 113 | .DemoBox-left > & { 114 | border-right: none; 115 | } 116 | .DemoBox-right > & { 117 | border-left: none; 118 | } 119 | } 120 | 121 | .DemoBox_error { 122 | padding: 10px 20px; 123 | color: #700; 124 | font-weight: bold; 125 | background-color: #fdd; 126 | border: 1px solid #caa; 127 | margin: 5px; 128 | } 129 | 130 | -------------------------------------------------------------------------------- /less/theme.less: -------------------------------------------------------------------------------- 1 | 2 | @import url(https://fonts.googleapis.com/css?family=Open+Sans:800); 3 | @import url(https://fonts.googleapis.com/css?family=Gentium+Basic); 4 | 5 | #header { 6 | font-family: 'Open Sans', sans-serif; 7 | } 8 | 9 | footer { 10 | text-align: center; 11 | font-size: .8em; 12 | margin-top: 30px; 13 | border-top: 1px solid #ccc; 14 | padding-top: 10px; 15 | padding-bottom: 20px; 16 | 17 | p { 18 | margin: 0; 19 | } 20 | } 21 | 22 | #main { 23 | font-family: "Gentium Basic", serif; 24 | } 25 | 26 | body { 27 | margin: 0; 28 | } 29 | 30 | #header { 31 | background-color: #f44336; 32 | padding: 50px 20px 30px; 33 | text-align: center; 34 | 35 | .title { 36 | font-size: 80px; 37 | color: white; 38 | } 39 | 40 | .subtitle { 41 | color: white; 42 | font-size: 25px; 43 | margin: -5px 0 10px; 44 | 45 | &:empty { 46 | display: none; 47 | } 48 | } 49 | } 50 | 51 | .link { 52 | padding: 5px 10px; 53 | margin: 0 5px; 54 | display: inline-block; 55 | background-color: #b71c1c; 56 | transition: background-color .1s ease; 57 | color: white; 58 | border-radius: 5px; 59 | text-decoration: none; 60 | 61 | &:hover { 62 | background-color: #d32f2f; 63 | } 64 | } 65 | 66 | h1 { 67 | position: relative; 68 | &::before { 69 | position: absolute; 70 | top: 0; 71 | bottom: 5px; 72 | right: 100%; 73 | width: 10px; 74 | background-color: #ccc; 75 | content: ' '; 76 | margin-right: 20px; 77 | } 78 | } 79 | 80 | img { 81 | max-width: 100%; 82 | } 83 | 84 | #main { 85 | max-width: 1000px; 86 | margin: 10px auto; 87 | font-size: 22px; 88 | } 89 | 90 | pre, .CodeMirror { 91 | font-size: 16px; 92 | } 93 | 94 | code { 95 | background-color: #eee; 96 | padding: 2px 5px; 97 | border-radius: 5px; 98 | } 99 | 100 | pre > code { 101 | display: block; 102 | overflow-x: auto; 103 | padding: 0.5em; 104 | background: #f0f0f0; 105 | } 106 | 107 | h1 + p, 108 | h2 + p, 109 | h3 + p, 110 | h4 + p { 111 | margin-top: 0; 112 | } 113 | 114 | p { 115 | line-height: 1.7; 116 | } 117 | 118 | h1 { 119 | font-size: 1.8em; 120 | } 121 | h2 { 122 | font-size: 1.3em; 123 | color: #333; 124 | } 125 | h3 { 126 | font-size: 1.2em; 127 | } 128 | h4 { 129 | font-size: 1.1em; 130 | color: #333; 131 | } 132 | h5, h6 { 133 | font-size: 1em; 134 | } 135 | 136 | h1, h2, h3, h4, h5 { 137 | margin-top: 20px; 138 | margin-bottom: 10px; 139 | } 140 | 141 | h1 a, h2 a, h3 a, h4 a { 142 | text-decoration: none; 143 | color: inherit; 144 | position: relative; 145 | 146 | &:visited { 147 | color: inherit; 148 | } 149 | 150 | &:hover:before { 151 | content: '\1f517'; 152 | position: absolute; 153 | right: 100%; 154 | padding-right: 10px; 155 | font-weight: bold; 156 | color: #333; 157 | font-size: 16px; 158 | top: 50%; 159 | margin-top: -10px; 160 | } 161 | } 162 | 163 | #main { 164 | padding: 10px 20px 20px; 165 | } 166 | 167 | table td { 168 | border: 1px solid #acc; 169 | padding: 20px; 170 | } 171 | 172 | h3 { 173 | margin-top: 10px; 174 | margin-bottom: 10px; 175 | } 176 | 177 | section.columns { 178 | display: flex; 179 | 180 | > section { 181 | margin-left: 20px; 182 | flex: 1; 183 | 184 | &:nth-child(2) { 185 | margin-left: 0; 186 | } 187 | } 188 | } 189 | 190 | section p:first-child { 191 | margin-top: 0; 192 | } 193 | 194 | /* 195 | section > div > .DemoBox > .DemoBox_output { 196 | border-left: 10px double #aaa; 197 | padding-left: 10px; 198 | margin: 0 10px; 199 | } 200 | */ 201 | 202 | 203 | .head-left { 204 | width: 500px; 205 | } 206 | 207 | .head { 208 | border-bottom: 1px solid #555; 209 | margin-bottom: 10px; 210 | } 211 | 212 | .head span { 213 | display: inline-block; 214 | padding: 7px 10px; 215 | font-weight: bold; 216 | } 217 | 218 | -------------------------------------------------------------------------------- /lib/codemirror-rx.js: -------------------------------------------------------------------------------- 1 | 2 | var React = require('react') 3 | , PT = React.PropTypes 4 | 5 | function px(val) { 6 | if ('number' === typeof val) return val + 'px' 7 | return val 8 | } 9 | 10 | function reactStyle(node, style) { 11 | var nopx = 'opacity,z-index,zIndex'.split(',') 12 | for (var name in style) { 13 | if (nopx.indexOf(name) !== -1) { 14 | node.style[name] = style[name] 15 | } else { 16 | node.style[name] = px(style[name]) 17 | } 18 | } 19 | } 20 | 21 | var CodeMirrorRx = React.createClass({ 22 | getDefaultProps: function () { 23 | return { 24 | mode: 'javascript', 25 | } 26 | }, 27 | componentDidMount: function () { 28 | this._cm = new CodeMirror(this.getDOMNode(), this.props) 29 | if (this.props.onChange) { 30 | this._cm.on('change', doc => this.props.onChange(doc.getValue())) 31 | } 32 | var node = this._cm.getWrapperElement() 33 | if (this.props.style) { 34 | reactStyle(node, this.props.style) 35 | this._cm.refresh() 36 | } 37 | setTimeout(() => this._cm.refresh(), 1000) 38 | }, 39 | componentDidUpdate: function (prevProps) { 40 | var same = true 41 | for (var name in this.props) { 42 | if (this.props[name] !== prevProps[name]) { 43 | if (name === 'value' && this._cm.getValue() === this.props[name]) continue 44 | this._cm.setOption(name, this.props[name]) 45 | } 46 | } 47 | var node = this._cm.getWrapperElement() 48 | if (this.props.style) { 49 | reactStyle(node, this.props.style) 50 | this._cm.refresh() 51 | } 52 | }, 53 | render: function () { 54 | return
55 | } 56 | }) 57 | 58 | module.exports = CodeMirrorRx 59 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | // React Demobox 2 | 3 | var React = require('react') 4 | , PT = React.PropTypes 5 | 6 | , jsx = require('./jsx') 7 | , CodeMirrorRx = require('./codemirror-rx') 8 | 9 | var defaultCode = `\ 10 | var name = 'Lisa'; 11 | 12 |
13 | Hello {name}! 14 |
` 15 | 16 | var DemoBox = React.createClass({ 17 | propTypes: { 18 | initialValue: PT.string, 19 | codeMirror: PT.bool, 20 | position: PT.oneOf(['top', 'bottom', 'left', 'right']), 21 | outputNode: PT.object, 22 | }, 23 | 24 | getDefaultProps: function () { 25 | return { 26 | initialValue: defaultCode, 27 | header: true, 28 | codeMirror: true, 29 | position: 'right', 30 | } 31 | }, 32 | 33 | getInitialState: function () { 34 | return { 35 | error: null, 36 | value: this.props.initialValue, 37 | } 38 | }, 39 | 40 | componentDidMount: function () { 41 | this._renderDemo() 42 | }, 43 | 44 | componentDidUpdate: function (prevProps, prevState) { 45 | if (prevProps.initialValue !== this.props.initialValue) { 46 | this.setState({value: this.props.initialValue}) 47 | } 48 | if (prevState.value === this.state.value) return; 49 | this._renderDemo() 50 | }, 51 | 52 | _renderDemo: function () { 53 | var comp; 54 | try { 55 | comp = eval(jsx(this.state.value)) 56 | } catch (e) { 57 | return this.setState({error: 'Failed to evaluate: ' + e.message}) 58 | } 59 | if (!comp || !React.isValidElement(comp)) { 60 | return this.setState({ 61 | error: "It looks like your code doesn't end with a valid react element." 62 | }) 63 | } 64 | var node = this.props.outputNode || this.refs.output.getDOMNode(); 65 | try { 66 | React.render(comp, node) 67 | } catch (e) { 68 | return this.setState({error: 'Render failed:' + e.message}) 69 | } 70 | this.setState({error: null}) 71 | }, 72 | 73 | _onChange: function (value) { 74 | this.setState({value: value}) 75 | }, 76 | 77 | render: function () { 78 | var text 79 | , horizontal = !this.props.outputNode && ['left', 'right'].indexOf(this.props.position) !== -1 80 | , style = { 81 | maxWidth: horizontal ? '500px' : '100%', 82 | } 83 | for (var name in this.props.style) { 84 | style[name] = this.props.style[name] 85 | } 86 | 87 | if (this.props.codeMirror && window.CodeMirror) { 88 | text = 95 | } else { 96 | text = 127 |

The Source code for this example:

128 |
<!-- <head> -->
129 | 
130 | <!-- these aren't needed if you want just a plain textarea instead of a codemirror editor -->
131 | <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/4.11.0/codemirror.min.css">
132 | <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/4.11.0/codemirror.min.js"></script>
133 | <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/4.11.0/mode/javascript/javascript.min.js"></script>
134 | <!-- this is the only requirement -->
135 | <script src="react-demobox.js"></script>
136 | 
137 | 
138 | <!-- <body> -->
139 | 
140 | <!-- all you need is this attribute on a textarea -->
141 | <textarea data-demobox>
142 |     var first = 'javascript code'
143 |         , second = `You can evaluate ${first} with es6 goodness.`;
144 |     // the last line must be an expression that results in a react
145 |     //  element.
146 |     <p>
147 |         <span>{second} </span>
148 |         <strong>JSX is just fine</strong>
149 |     </p>
150 | </textarea>
151 |
152 | 153 |

Slightly larger example

154 |
155 |

156 | The rest of these examples will show demobox-in-demobox, to make things 157 | easier. 158 |

159 |
160 | This is editable 161 | This is the output 162 |
163 | 177 |
178 | 179 |

Other Positions

180 |
181 |

182 | The "position" argument allows you to specify where the output should 183 | be displayed. Default is "right". 184 |

185 |
186 | This is editable 187 | This is the output 188 |
189 | 200 |
201 | 202 | 203 |

Without CodeMirror (just uses a textbox)

204 |
205 | 208 |
209 | 210 |

Advanced Usage: External Output Node

211 |
212 | 213 | 214 | 226 | 227 |
215 |
216 |
217 | 225 |
228 |
229 | 230 | 231 | 232 | --------------------------------------------------------------------------------