1
1
mirror of https://github.com/jxnblk/mdx-deck.git synced 2024-11-29 13:58:02 +03:00
This commit is contained in:
Brent Jackson 2018-07-28 14:21:36 -04:00
commit c9157a37d9
7 changed files with 386 additions and 0 deletions

43
README.md Normal file
View File

@ -0,0 +1,43 @@
# mdx-deck
Create presentation decks with [MDX][]
```sh
npm i mdx-deck
```
````md
---
imports:
- import Demo from './components/Demo'
---
# This is the title of my deck
---
# About Me
---
```jsx
<CodeSnippet />
```
---
<Demo />
---
# The end
````
```sh
mdx-deck deck.mdx
```
[MDX]: https://github.com/mdx-js/mdx

17
cli.js Executable file
View File

@ -0,0 +1,17 @@
#!/usr/bin/env node
const open = require('react-dev-utils/openBrowser')
const dev = require('./lib/dev')
const opts = {
}
dev(opts)
.then(res => {
const url = 'http://localhost:' + res.port
open(url)
console.log(url)
})
.catch(err => {
console.error(err)
})

23
docs/index.mdx Normal file
View File

@ -0,0 +1,23 @@
---
imports:
- import Box from 'superbox'
---
# Hello
---
# This is a presentation deck!
---
# Built with [MDX][]
[MDX]: https://github.com/mdx-js/mdx
---
<Box p={3} bg='magenta'>
And you can import React components!
</Box>

140
lib/dev.js Normal file
View File

@ -0,0 +1,140 @@
const path = require('path')
const webpack = require('webpack')
const Koa = require('koa')
const getPort = require('get-port')
const koaWebpack = require('koa-webpack')
const HTMLPlugin = require('mini-html-webpack-plugin')
const ProgressBarPlugin = require('progress-bar-webpack-plugin')
const chalk = require('chalk')
const devMiddleware = {
publicPath: '/',
clientLogLevel: 'error',
stats: 'errors-only',
logLevel: 'error',
}
const babel = {
presets: [
'babel-preset-env',
'babel-preset-stage-0',
'babel-preset-react',
].map(require.resolve)
}
const rules = [
{
test: /\.js$/,
exclude: /node_modules/,
loader: require.resolve('babel-loader'),
options: babel
},
{
test: /\.js$/,
exclude: path.resolve(__dirname, '../node_modules'),
include: [
path.resolve(__dirname, '..'),
],
loader: require.resolve('babel-loader'),
options: babel
},
{
test: /\.mdx?$/,
exclude: /node_modules/,
use: [
{
loader: require.resolve('babel-loader'),
options: babel
},
require.resolve('./loader.js'),
]
}
]
const template = ({
js,
publicPath
}) => `<!DOCTYPE html>
<meta name='viewport' content='width=device-width,initial-scale=1'>
<style>*{box-sizing:border-box}body{font-family:system-ui,sans-serif;margin:0}</style>
<div id=root></div>
${HTMLPlugin.generateJSReferences(js, publicPath)}
`
const config = {
stats: 'errors-only',
mode: 'development',
module: {
rules
},
resolve: {
modules: [
path.relative(process.cwd(), path.join(__dirname, '../node_modules')),
'node_modules'
]
},
plugins: [
new HTMLPlugin({
template
}),
new ProgressBarPlugin({
width: '24',
complete: '█',
incomplete: chalk.gray('░'),
format: [
chalk.magenta('[ok] :bar'),
chalk.magenta(':percent'),
chalk.gray(':elapseds :msg'),
].join(' '),
// summaryContent: chalk.magenta('[ok] done '),
summary: false,
})
]
}
const start = async (opts = {}) => {
const app = new Koa()
// const dirname = path.dirname(opts.entry)
const hotPort = await getPort()
const hotClient = {
port: hotPort,
logLevel: 'error'
}
// config.context = dirname
config.resolve.modules.push(
// dirname,
// path.join(dirname, 'node_modules')
)
config.entry = [
// path.join(__dirname, './overlay.js'),
path.join(__dirname, './entry.js')
]
config.plugins.push(
new webpack.DefinePlugin({
OPTIONS: JSON.stringify(opts),
// APP_FILENAME: JSON.stringify(opts.entry),
HOT_PORT: JSON.stringify(hotPort)
})
)
const middleware = await koaWebpack({
config,
devMiddleware,
hotClient
})
const port = opts.port || await getPort()
app.use(middleware)
const server = app.listen(port)
return new Promise((resolve) => {
middleware.devMiddleware.waitUntilValid(() => {
resolve({ server, app, middleware, port })
})
})
}
module.exports = start

102
lib/entry.js Normal file
View File

@ -0,0 +1,102 @@
import React from 'react'
import { render } from 'react-dom'
import PropTypes from 'prop-types'
import { MDXProvider } from '@mdx-js/tag'
import styled from 'styled-components'
import Box from 'superbox'
import throttle from 'lodash.throttle'
import debounce from 'lodash.debounce'
// todo: dynamic import
import slides from '../docs/index.mdx'
const Carousel = styled.div([], {
display: 'flex',
overflowX: 'auto',
width: '100vw',
})
const Slide = styled.div([], {
outline: '2px solid tomato',
flex: 'none',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
overflow: 'hidden',
width: '100vw',
height: '90vh'
})
const inc = state => ({ index: (state.index + 1) % state.length })
const dec = state => state.index > 0
? ({ index: (state.index - 1) % state.length })
: null
class App extends React.Component {
static propTypes = {
slides: PropTypes.array.isRequired,
}
state = {
length: this.props.slides.length,
index: 0
}
root = React.createRef()
update = fn => this.setState(fn)
handleScroll = debounce(e => {
if (this.isProgrammaticScroll) return
const { scrollLeft } = e.target
const rect = e.target.getBoundingClientRect()
const n = Math.round(scrollLeft / rect.width)
console.log('scroll', e, n)
this.setState({ index: n })
}, 1000)
componentDidMount () {
this.root.current.addEventListener('scroll', this.handleScroll)
}
componentWillUnmount () {
this.root.current.removeEventListener('scroll', this.handleScroll)
}
componentDidUpdate () {
if (!this.root.current) return
const { index } = this.state
const el = this.root.current.querySelector('#slide-' + index)
if (!el) return
this.isProgrammaticScroll = true
el.scrollIntoView({
behavior: 'smooth'
})
setTimeout(() => {
console.log(this.isProgrammaticScroll)
this.isProgrammaticScroll = false
}, 1000)
}
render () {
const { slides } = this.props
return (
<div>
<Carousel innerRef={this.root}>
{slides.map((Component, i) => (
<Slide key={i} id={'slide-' + i}>
<Component />
</Slide>
))}
</Carousel>
<samp>{this.state.index + 1}/{this.state.length}</samp>
<button onClick={e => this.update(dec)}>previous</button>
<button onClick={e => this.update(inc)}>next</button>
</div>
)
}
}
render(<App slides={slides} />, window.root)
if (module.hot) module.hot.accept()

32
lib/loader.js Normal file
View File

@ -0,0 +1,32 @@
const mdx = require('@mdx-js/mdx')
const matter = require('gray-matter')
const EXREG = /export\sdefault\s/g
module.exports = async function (src) {
const callback = this.async()
const { data, content } = matter(src)
// todo: hoist imports
const slides = content.split('---\n')
.map(str => mdx.sync(str))
.map(str => str.trim())
.map(str => str.replace(EXREG, ''))
console.log(data)
const {
imports = []
} = data
const code = `import React from 'react'
import { MDXTag } from '@mdx-js/tag'
${imports.join('\n')}
export default [
${slides.join(',\n\n')}
]`
// console.log(code)
return callback(null, code)
}

29
package.json Normal file
View File

@ -0,0 +1,29 @@
{
"name": "mdx-deck",
"version": "1.0.0-0",
"description": "",
"main": "index.js",
"scripts": {
"start": "./cli.js"
},
"keywords": [],
"author": "Brent Jackson",
"license": "MIT",
"dependencies": {
"@mdx-js/tag": "^0.14.1",
"chalk": "^2.4.1",
"debounce": "^1.1.0",
"get-port": "^4.0.0",
"gray-matter": "^4.0.1",
"koa": "^2.5.2",
"koa-webpack": "^5.1.0",
"lodash.throttle": "^4.1.1",
"mini-html-webpack-plugin": "^0.2.3",
"ok-cli": "^2.0.9",
"progress-bar-webpack-plugin": "^1.11.0",
"prop-types": "^15.6.2",
"react-dev-utils": "^5.0.1",
"styled-components": "^3.3.3",
"superbox": "^2.1.0"
}
}