mirror of
https://github.com/jxnblk/mdx-deck.git
synced 2024-11-29 13:58:02 +03:00
Init
This commit is contained in:
commit
c9157a37d9
43
README.md
Normal file
43
README.md
Normal 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
17
cli.js
Executable 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
23
docs/index.mdx
Normal 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
140
lib/dev.js
Normal 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
102
lib/entry.js
Normal 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
32
lib/loader.js
Normal 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
29
package.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user