├── .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 | {
39 | alert('Hello')
40 | }}
41 | children='Beep'
42 | />
43 |
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 | {
62 | alert('Beep')
63 | }}
64 | children='Beep'
65 | />
66 |
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 |
--------------------------------------------------------------------------------