├── .npmignore ├── .gitignore ├── temp.md ├── index.js ├── lib ├── theme.js ├── entry.js ├── colors.js ├── html.js ├── config.js ├── index.js ├── App.js └── Live.js ├── .babelrc ├── test.js ├── docs └── config.js ├── package.json ├── bin └── live-doc.js └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | docs 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | -------------------------------------------------------------------------------- /temp.md: -------------------------------------------------------------------------------- 1 | 2 | live-doc 3 | 4 | 5 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib') 2 | -------------------------------------------------------------------------------- /lib/theme.js: -------------------------------------------------------------------------------- 1 | const theme = { 2 | maxWidth: 960 3 | } 4 | 5 | export default theme 6 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "env", 4 | "stage-0", 5 | "react" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /lib/entry.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render } from 'react-dom' 3 | import App from './App' 4 | 5 | const config = CONFIG ? require(CONFIG) : null 6 | 7 | render(, app) 8 | -------------------------------------------------------------------------------- /lib/colors.js: -------------------------------------------------------------------------------- 1 | export const colors = { 2 | black: '#24292e', 3 | gray: '#f6f8fa', 4 | gray2: '#eaecef', 5 | midgray: '#6a737d', 6 | red: '#d73a49', 7 | green: '#22863a', 8 | purple: '#6f42c1', 9 | blue: '#005cc5', 10 | } 11 | 12 | export default colors 13 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import live from './lib' 3 | 4 | test('exports a function', t => { 5 | t.is(typeof live, 'function') 6 | }) 7 | 8 | test('returns an html string', async t => { 9 | const a = await live('# Hello') 10 | t.is(typeof a, 'string') 11 | }) 12 | -------------------------------------------------------------------------------- /lib/html.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ 2 | bundle, 3 | title = 'live-doc', 4 | css = '', 5 | meta = [], 6 | script 7 | }) => { 8 | return (` 9 | 10 | ${title} 11 | 12 | 13 | 14 | ${metaTags(meta)}
15 | ${scriptTag(script)}`) 16 | } 17 | 18 | const metaTags = meta => meta.map(({ name, content }) => ( 19 | `` 20 | )).join('\n') 21 | 22 | const scriptTag = script => script 23 | ? `\n` 24 | : '' 25 | -------------------------------------------------------------------------------- /docs/config.js: -------------------------------------------------------------------------------- 1 | const { Heading } = require('rebass') 2 | 3 | module.exports = { 4 | title: 'Live Doc', 5 | scope: { 6 | Heading 7 | }, 8 | meta: [ 9 | { name: 'twitter:card', content: 'summary' }, 10 | { name: 'twitter:site', content: '@jxnblk' }, 11 | { name: 'twitter:title', content: 'Live Doc' }, 12 | { name: 'twitter:description', content: 'Convert markdown to live React demos' }, 13 | { name: 'twitter:image', content: 'http://i.imgur.com/vqKCbnh.jpg' }, 14 | // { name: 'twitter:image', content: 'http://clipart-library.com/images/6cy5Gbazi.jpg' }, 15 | ], 16 | script: `window.twttr = (function(d, s, id) { 17 | var js, fjs = d.getElementsByTagName(s)[0], 18 | t = window.twttr || {}; 19 | if (d.getElementById(id)) return t; 20 | js = d.createElement(s); 21 | js.id = id; 22 | js.src = "https://platform.twitter.com/widgets.js"; 23 | fjs.parentNode.insertBefore(js, fjs); 24 | 25 | t._e = []; 26 | t.ready = function(f) { 27 | t._e.push(f); 28 | }; 29 | 30 | return t; 31 | }(document, "script", "twitter-wjs"));` 32 | } 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "live-doc", 3 | "version": "1.0.0-9", 4 | "description": "Convert markdown to live React demos", 5 | "main": "index.js", 6 | "bin": { 7 | "doc": "./bin/live-doc.js" 8 | }, 9 | "scripts": { 10 | "start": "./bin/live-doc.js README.md -d docs -c docs/config.js", 11 | "test": "ava" 12 | }, 13 | "keywords": [], 14 | "author": "Brent Jackson", 15 | "license": "MIT", 16 | "dependencies": { 17 | "babel-core": "^6.25.0", 18 | "babel-loader": "^7.1.1", 19 | "babel-preset-env": "^1.6.0", 20 | "babel-preset-react": "^6.24.1", 21 | "babel-preset-stage-0": "^6.24.1", 22 | "memory-fs": "^0.4.1", 23 | "meow": "^3.7.0", 24 | "ora": "^1.3.0", 25 | "react": "^15.6.1", 26 | "react-dom": "^15.6.1", 27 | "react-live": "^1.7.0", 28 | "react-markdown": "^2.5.0", 29 | "rebass": "^1.0.0", 30 | "styled-components": "^2.1.1", 31 | "update-notifier": "^2.2.0", 32 | "webpack": "^3.3.0", 33 | "webpack-dev-server": "^2.5.1" 34 | }, 35 | "devDependencies": { 36 | "ava": "^0.21.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | 4 | const babel = { 5 | loader: require.resolve('babel-loader'), 6 | options: { 7 | presets: [ 8 | 'babel-preset-env', 9 | 'babel-preset-stage-0', 10 | 'babel-preset-react' 11 | ].map(require.resolve) 12 | } 13 | } 14 | 15 | module.exports = { 16 | devtool: 'cheap-source-map', 17 | entry: [ 18 | path.join(__dirname, './entry.js') 19 | ], 20 | output: { 21 | path: '/', 22 | filename: 'bundle.js' 23 | }, 24 | module: { 25 | rules: [ 26 | { 27 | test: /\.js$/, 28 | exclude: /node_modules/, 29 | use: babel 30 | }, 31 | { 32 | test: /\.js$/, 33 | include: /live\-doc/, 34 | exclude: /live\-doc\/node_modules/, 35 | use: babel 36 | } 37 | ], 38 | }, 39 | plugins: [ 40 | new webpack.optimize.UglifyJsPlugin({ 41 | beautify: false, 42 | mangle: { 43 | screw_ie8: true, 44 | keep_fnames: true 45 | }, 46 | compress: { 47 | screw_ie8: true 48 | }, 49 | comments: false 50 | }) 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const MemoryFS = require('memory-fs') 3 | const config = require('./config') 4 | const createHTML = require('./html') 5 | 6 | const mfs = new MemoryFS() 7 | 8 | const compile = (source, options = {}) => { 9 | const CONFIG = options.config ? JSON.stringify(options.config) : null 10 | const definePlugin = new webpack.DefinePlugin({ 11 | 'process.env.NODE_ENV': JSON.stringify('production'), 12 | MARKDOWN: JSON.stringify(source), 13 | CONFIG, 14 | }) 15 | 16 | config.plugins.push(definePlugin) 17 | 18 | const compiler = webpack(config) 19 | 20 | compiler.outputFileSystem = mfs 21 | 22 | return new Promise((resolve, reject) => { 23 | compiler.run((err, stats) => { 24 | if (err) { 25 | throw new Error(err) 26 | reject(err) 27 | } 28 | 29 | const output = mfs.readFileSync('/bundle.js', 'utf8') 30 | resolve(output) 31 | }) 32 | }) 33 | } 34 | 35 | module.exports = async (source, options = {}) => { 36 | const bundle = await compile(source, options) 37 | 38 | const opts = options.config ? require(options.config) : null 39 | 40 | const html = createHTML(Object.assign({ 41 | bundle 42 | }, opts)) 43 | 44 | return html 45 | } 46 | -------------------------------------------------------------------------------- /bin/live-doc.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs') 4 | const path = require('path') 5 | const meow = require('meow') 6 | const ora = require('ora') 7 | const notifier = require('update-notifier') 8 | const render = require('..') 9 | const pkg = require('../package.json') 10 | 11 | notifier({ pkg }).notify() 12 | 13 | const spinner = ora('converting markdown').start() 14 | 15 | const cli = meow(` 16 | Usage 17 | $ doc 18 | 19 | Options 20 | -d --out-dir Output directory 21 | 22 | -c --config Path to config file 23 | 24 | `, { 25 | alias: { 26 | d: 'outDir', 27 | c: 'config' 28 | } 29 | }) 30 | 31 | const [ file ] = cli.input 32 | const opts = cli.flags 33 | const src = fs.readFileSync(file, 'utf8') 34 | 35 | const { 36 | outDir = '' 37 | } = opts 38 | 39 | const dirname = path.join(process.cwd(), outDir) 40 | 41 | opts.config = opts.config 42 | ? path.join(process.cwd(), opts.config) 43 | : null 44 | 45 | if (opts.config) { 46 | spinner.text = 'using custom config from ' + opts.config 47 | } 48 | 49 | const getHTML = async () => { 50 | const html = await render(src, opts) 51 | const filename = path.join(dirname, 'index.html') 52 | fs.writeFileSync(filename, html) 53 | spinner.succeed('file saved') 54 | } 55 | 56 | getHTML() 57 | 58 | -------------------------------------------------------------------------------- /lib/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Markdown from 'react-markdown' 3 | import R, { 4 | Provider, 5 | Container, 6 | Box, 7 | BlockLink, 8 | Link, 9 | Image, 10 | Divider, 11 | Pre, 12 | } from 'rebass' 13 | import styled from 'styled-components' 14 | import Live from './Live' 15 | import theme from './theme' 16 | import colors from './colors' 17 | 18 | const CodeBlock = ({ 19 | language, 20 | literal, 21 | scope 22 | }) => { 23 | if (/^\.\.?jsx/.test(language)) { 24 | const noInline = /^\.\./.test(language) 25 | return ( 26 | 27 | 32 | 33 | ) 34 | } 35 | 36 | return ( 37 |
 43 |   )
 44 | }
 45 | 
 46 | const PageTitle = props => (
 47 |   
 54 | )
 55 | 
 56 | const Heading = ({
 57 |   level,
 58 |   children
 59 | }) => level === 1
 60 |   ? 
 61 |   : (
 62 |     
 63 |       
 69 |     
 70 |   )
 71 | 
 72 | const Code = ({
 73 |   literal,
 74 |   inline
 75 | }) => (
 76 |   
 82 | )
 83 | 
 84 | const List = ({ type }) => {}
 85 | 
 86 | const ThematicBreak = props => (
 87 |   
 92 | )
 93 | 
 94 | const renderers = config => ({
 95 |   CodeBlock: props => CodeBlock({ ...props, ...config }),
 96 |   Heading,
 97 |   Code,
 98 |   Link,
 99 |   Image,
100 |   ThematicBreak,
101 | })
102 | 
103 | const Root = styled(Container)`
104 |   line-height: 1.5;
105 | `
106 | 
107 | export default ({ config }) => (
108 |   
109 |     
110 |       
115 |     
116 |   
117 | )
118 | 


--------------------------------------------------------------------------------
/lib/Live.js:
--------------------------------------------------------------------------------
  1 | import React from 'react'
  2 | import styled from 'styled-components'
  3 | import {
  4 |   LiveProvider,
  5 |   LivePreview,
  6 |   LiveEditor,
  7 |   LiveError
  8 | } from 'react-live'
  9 | import {
 10 |   hoc,
 11 |   Flex,
 12 |   Box,
 13 |   monospace
 14 | } from 'rebass'
 15 | import colors from './colors'
 16 | 
 17 | const Provider = styled(LiveProvider)`
 18 |   position: relative;
 19 | `
 20 | 
 21 | const Preview = styled(hoc()(LivePreview))`
 22 |   position: relative;
 23 |   padding: 16px;
 24 | `
 25 | 
 26 | const Editor = styled(hoc()(LiveEditor))`
 27 |   box-sizing: border-box;
 28 |   font-family: ${monospace};
 29 |   font-size: 13px;
 30 |   margin: 0;
 31 |   padding: 16px;
 32 |   overflow: auto;
 33 |   outline: none;
 34 |   tab-size: 2;
 35 |   color: ${colors.black};
 36 |   background-color: ${colors.gray};
 37 | 
 38 |   .token.comment,
 39 |   .token.prolog,
 40 |   .token.doctype,
 41 |   .token.cdata {
 42 |     color: ${colors.midgray};
 43 |   }
 44 |   .token.punctuation {
 45 |     color: ${colors.black};
 46 |   }
 47 |   .token.property,
 48 |   .token.tag,
 49 |   .token.boolean,
 50 |   .token.number,
 51 |   .token.constant,
 52 |   .token.symbol {
 53 |     // color: hsl(350, 40%, 70%);
 54 |     color: ${colors.green};
 55 |     // color: ${colors.black};
 56 |   }
 57 |   .token.selector,
 58 |   .token.attr-name,
 59 |   .token.string,
 60 |   .token.char,
 61 |   .token.builtin,
 62 |   .token.inserted {
 63 |     color: ${colors.purple};
 64 |   }
 65 |   // .token.operator,
 66 |   // .token.entity,
 67 |   // .token.url,
 68 |   // .language-css .token.string,
 69 |   // .style .token.string,
 70 |   // .token.variable {
 71 |   //   color: hsl(40, 90%, 60%);
 72 |   // }
 73 |   .token.atrule,
 74 |   .token.attr-value,
 75 |   .token.keyword {
 76 |     color: ${colors.red};
 77 |   }
 78 |   .token.regex,
 79 |   .token.important {
 80 |     color: ${colors.red};
 81 |   }
 82 |   .token.important,
 83 |   .token.bold {
 84 |     font-weight: bold;
 85 |   }
 86 |   .token.italic {
 87 |     font-style: italic;
 88 |   }
 89 |   .token.entity {
 90 |     cursor: help;
 91 |   }
 92 |   .token.deleted {
 93 |     color: red;
 94 |   }
 95 | `
 96 | 
 97 | const Error = styled(LiveError)`
 98 |   position: absolute;
 99 |   top: 100%;
100 |   left: 0;
101 |   right: 0;
102 |   font-family: ${monospace};
103 |   font-size: 13px;
104 |   padding: 8px;
105 |   color: white;
106 |   background-color: red;
107 | `
108 | 
109 | const Row = styled(Flex)`
110 |   border-width: 1px;
111 |   border-style: solid;
112 |   border-color: ${colors.gray2};
113 | `
114 | 
115 | export default props => (
116 |   
119 |     
120 |       
121 |       
122 |     
123 |     
124 |   
125 | )
126 | 


--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
  1 | 
  2 | **No longer maintained** see https://mdxjs.com for a better alternative
  3 | 
  4 | # live-doc
  5 | 
  6 | Convert markdown to live React demos 
  7 | 
  8 | Built with: [react-markdown][0], [React Live][1], [Rebass][2], and [styled-components][3]
  9 | 
 10 | 
 13 | 
 14 | 
 15 | ## Getting Started
 16 | 
 17 | ```sh
 18 | npm i -g live-doc
 19 | ```
 20 | 
 21 | Convert a markdown file to a React app and save as `index.html`:
 22 | 
 23 | ```sh
 24 | doc README.md
 25 | ```
 26 | 
 27 | ## Live Code Example
 28 | 
 29 | By using the `.jsx` language attribute at the beginning of a code block,
 30 | live-doc will convert the example into a live-editable example using [React Live][1].
 31 | 
 32 | ```.jsx
 33 | 
34 | 35 | Hello! Edit me 36 | 37 |
44 | ``` 45 | 46 | In this example, the [Rebass][2] `Heading` component has been added to the scope in the `docs/config.js` file, 47 | making it available to the [React Live][1] preview. 48 | 49 | ### React Live noInline Mode 50 | 51 | To enable the [`noInline` mode](https://github.com/FormidableLabs/react-live#liveprovider-) 52 | in React Live, use the `..jsx` language attribute at the beginning of a code block. 53 | 54 | ```..jsx 55 | const Hello = () =>

Hello

56 | 57 | const App = () => ( 58 |
59 | 60 |
67 | ) 68 | 69 | render() 70 | ``` 71 | 72 | 73 | ## CLI Options 74 | 75 | ``` 76 | -d --out-dir Output directory 77 | -c --config Path to config file 78 | ``` 79 | 80 | 81 | ## Configuration 82 | 83 | To customize the output React app, create a config file that exports an object. 84 | 85 | ```js 86 | // config.js 87 | module.exports = { 88 | // Scope for react-live previews 89 | scope: { 90 | foo: 'Hello' 91 | }, 92 | title: 'Page Title', 93 | css: 'body { color: tomato }', 94 | script: 'console.log("Hello");', 95 | // Meta tags 96 | meta: [ 97 | { 98 | name: 'og:image', 99 | content: 'kitten.jpg' 100 | } 101 | ] 102 | } 103 | ``` 104 | 105 | Then pass the file with the `--config` flag to the CLI. 106 | 107 | ```sh 108 | doc README.md --config config.js 109 | ``` 110 | 111 | --- 112 | 113 | [GitHub](https://github.com/jxnblk/live-doc) 114 | [Made by Jxnblk](http://jxnblk.com) 115 | 116 | MIT License 117 | 118 | [0]: https://github.com/rexxars/react-markdown 119 | [1]: https://github.com/FormidableLabs/react-live 120 | [2]: https://github.com/jxnblk/rebass 121 | [3]: https://github.com/styled-components/styled-components 122 | --------------------------------------------------------------------------------