mirror of
https://github.com/jxnblk/mdx-deck.git
synced 2024-11-29 13:58:02 +03:00
commit
fff120b38e
23
.babelrc
23
.babelrc
@ -1,23 +0,0 @@
|
||||
{
|
||||
presets: [
|
||||
'@babel/env',
|
||||
'@babel/react'
|
||||
],
|
||||
plugins: [
|
||||
'@babel/plugin-proposal-class-properties',
|
||||
'@babel/plugin-proposal-do-expressions',
|
||||
'@babel/plugin-proposal-export-default-from',
|
||||
'@babel/plugin-proposal-export-namespace-from',
|
||||
'@babel/plugin-proposal-function-bind',
|
||||
'@babel/plugin-proposal-function-sent',
|
||||
'@babel/plugin-proposal-json-strings',
|
||||
'@babel/plugin-proposal-logical-assignment-operators',
|
||||
'@babel/plugin-proposal-nullish-coalescing-operator',
|
||||
'@babel/plugin-proposal-numeric-separator',
|
||||
'@babel/plugin-proposal-optional-chaining',
|
||||
'@babel/plugin-proposal-throw-expressions',
|
||||
'@babel/plugin-syntax-dynamic-import',
|
||||
'@babel/plugin-syntax-import-meta',
|
||||
'babel-plugin-styled-components',
|
||||
]
|
||||
}
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,5 +1,5 @@
|
||||
dist
|
||||
site
|
||||
public
|
||||
coverage
|
||||
node_modules
|
||||
yarn.lock
|
||||
package-lock.json
|
||||
|
11
.npmignore
11
.npmignore
@ -1,11 +0,0 @@
|
||||
src
|
||||
site
|
||||
docs
|
||||
coverage
|
||||
test
|
||||
.babelrc
|
||||
.travis.yml
|
||||
CHANGELOG.md
|
||||
CONTRIBUTING.md
|
||||
templates
|
||||
create-deck
|
5
.prettierrc
Normal file
5
.prettierrc
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5"
|
||||
}
|
@ -1,14 +1,12 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- 10
|
||||
before_deploy:
|
||||
- npm install
|
||||
- npm run build
|
||||
- cp docs/card.png site
|
||||
beforeDeploy:
|
||||
- yarn build
|
||||
deploy:
|
||||
provider: pages
|
||||
skip_cleanup: true
|
||||
github_token: $GH_TOKEN
|
||||
local_dir: site
|
||||
local_dir: docs/dist
|
||||
on:
|
||||
branch: master
|
||||
|
12
CHANGELOG.md
12
CHANGELOG.md
@ -1,8 +1,18 @@
|
||||
|
||||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Simplified custom mdx loader, removing unused front-matter support
|
||||
- Simplified theming and default styles
|
||||
- Removes default Provider component with dot indicator
|
||||
- Removes timers from presentation mode (maybe)
|
||||
- Uses Reach Router
|
||||
- Fix for focus trap
|
||||
- Removed PDF export and screenshots from core CLI - now available with the `@mdx-deck/export` package
|
||||
- Removed built-in syntax highlighting
|
||||
- Removed `notes` language attribute for fenced code blocks
|
||||
- Refactored dev server
|
||||
|
||||
## v1.10.2 2019-03-10
|
||||
|
||||
- Fix bad release
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
# Contributing
|
||||
|
||||
Thanks for contributing!
|
||||
@ -27,7 +26,6 @@ Run `npm test`
|
||||
- Watch Mode: `npm test -- --watch`
|
||||
- Coverage: `npm test -- --coverage`
|
||||
|
||||
|
||||
---
|
||||
|
||||
# Contributor Covenant Code of Conduct
|
||||
@ -87,7 +85,7 @@ further defined and clarified by project maintainers.
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at [INSERT EMAIL ADDRESS]. All
|
||||
reported by contacting the project team at jxnblk@gmail.com. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
@ -103,5 +101,3 @@ This Code of Conduct is adapted from the [Contributor Covenant][homepage], versi
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
|
||||
|
134
README.md
134
README.md
@ -1,4 +1,4 @@
|
||||
# mdx-deck
|
||||
# MDX Deck
|
||||
|
||||
![](https://s3.amazonaws.com/jxnblk/mdx-deck.gif)
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
[npm]: https://npmjs.com/package/mdx-deck
|
||||
|
||||
```sh
|
||||
npm i -D mdx-deck
|
||||
npm i -D mdx-deck@next
|
||||
```
|
||||
|
||||
- :memo: Write presentations in markdown
|
||||
@ -33,21 +33,27 @@ Create an [MDX][] file and separate each slide with `---`.
|
||||
|
||||
````mdx
|
||||
# This is the title of my deck
|
||||
|
||||
---
|
||||
|
||||
# About Me
|
||||
|
||||
---
|
||||
|
||||
```jsx
|
||||
<CodeSnippet />
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
import Demo from './components/Demo'
|
||||
|
||||
<Demo />
|
||||
---
|
||||
## <Demo />
|
||||
|
||||
# The end
|
||||
````
|
||||
|
||||
Add a run script to your `package.json` with the mdx-deck CLI
|
||||
Add a run script to your `package.json` with the MDX Deck CLI
|
||||
pointing to the `.mdx` file to start the dev server:
|
||||
|
||||
```json
|
||||
@ -71,14 +77,14 @@ npm start
|
||||
- [Build a Custom Provider Component for MDX-Deck](ks-egghead) by [Kyle Shevlin][]
|
||||
|
||||
[egghead]: https://egghead.io/lessons/react-build-a-slide-deck-with-mdx-deck-using-markdown-react
|
||||
[Kent C. Dodds]: https://mobile.twitter.com/kentcdodds
|
||||
[kent c. dodds]: https://mobile.twitter.com/kentcdodds
|
||||
[kcd-video]: http://youtu.be/d2sQiI5NFAM?a
|
||||
[kcd-medium]: https://blog.kentcdodds.com/mdx-deck-slide-decks-powered-by-markdown-and-react-bfc6d6af20da
|
||||
[hw-video]: https://www.youtube.com/watch?v=LvP2EqCiQMg&feature=youtu.be
|
||||
[hw-demo]: https://github.com/hswolff/mdx-deck-demo
|
||||
[Harry Wolff]: https://mobile.twitter.com/hswolff
|
||||
[harry wolff]: https://mobile.twitter.com/hswolff
|
||||
[ks-egghead]: https://egghead.io/lessons/javascript-build-a-custom-provider-component-for-mdx-deck
|
||||
[Kyle Shevlin]: https://twitter.com/kyleshevlin
|
||||
[kyle shevlin]: https://twitter.com/kyleshevlin
|
||||
|
||||
## Quick Start
|
||||
|
||||
@ -94,21 +100,19 @@ MDX can use Markdown syntax and render React components with JSX.
|
||||
|
||||
### Imports
|
||||
|
||||
To import components, use ES import syntax separated with empty lines from any markdown or JSX syntax.
|
||||
To import components, use ES import syntax separated with empty lines between any markdown or JSX syntax.
|
||||
|
||||
```mdx
|
||||
import { Box } from 'grid-styled'
|
||||
|
||||
<Box color='tomato'>
|
||||
Hello
|
||||
</Box>
|
||||
<Box color="tomato">Hello</Box>
|
||||
```
|
||||
|
||||
Read more about MDX syntax in the [MDX Docs][MDX].
|
||||
Read more about MDX syntax in the [MDX Docs][mdx].
|
||||
|
||||
## Theming
|
||||
|
||||
mdx-deck uses [styled-components][] for styling, making practically any part of the presentation themeable.
|
||||
MDX Deck uses [emotion][] for styling, making practically any part of the presentation themeable.
|
||||
|
||||
### Built-in Themes
|
||||
|
||||
@ -118,7 +122,7 @@ mdx-deck uses [styled-components][] for styling, making practically any part of
|
||||
<img src='docs/images/yellow.png' width='256' />
|
||||
</div>
|
||||
|
||||
mdx-deck includes several built-in themes to change the look and feel of the presentation.
|
||||
MDX Deck includes several built-in themes to change the look and feel of the presentation.
|
||||
Export `theme` from your MDX file to enable a theme.
|
||||
|
||||
```mdx
|
||||
@ -141,15 +145,10 @@ export { default as theme } from './theme'
|
||||
```
|
||||
|
||||
The theme should be an object with fields for fonts, colors, and CSS for individual components.
|
||||
It's recommended that all custom themes extend the default theme as a base.
|
||||
|
||||
```js
|
||||
// example theme.js
|
||||
import theme from 'mdx-deck/themes'
|
||||
|
||||
export default {
|
||||
// extends the default theme
|
||||
...theme,
|
||||
// add a custom font
|
||||
font: 'Roboto, sans-serif',
|
||||
// custom colors
|
||||
@ -157,7 +156,7 @@ export default {
|
||||
text: '#f0f',
|
||||
background: 'black',
|
||||
link: '#0ff',
|
||||
}
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@ -165,18 +164,20 @@ Read more about theming in the [Theming docs](docs/theming.md)
|
||||
|
||||
### Components
|
||||
|
||||
mdx-deck includes built-in components to help with creating presentations, including a full screen Image component, the Appear component that allows stepping through parts of a single slide, and the Notes component for adding speaker notes.
|
||||
MDX Deck includes built-in components to help with creating presentations, including a full screen Image component, the Appear component that allows stepping through parts of a single slide, and the Notes component for adding speaker notes.
|
||||
|
||||
Read more in the [components docs](docs/components.md).
|
||||
|
||||
### Libraries
|
||||
|
||||
These third-party libraries are great for use with mdx-deck.
|
||||
These third-party libraries are great for use with MDX Deck.
|
||||
|
||||
- [CodeSurfer][]: React component for scrolling, zooming and highlighting code.
|
||||
- [mdx-code][]: Runnable code playgrounds for MDX Deck.
|
||||
- [mdx-deck-live-code][]: Live React and JS coding in slides.
|
||||
|
||||
_Note: please check with version compatibility when using these libraries._
|
||||
|
||||
[codesurfer]: https://github.com/pomber/code-surfer
|
||||
[mdx-code]: https://github.com/pranaygp/mdx-code
|
||||
[mdx-deck-live-code]: https://github.com/JReinhold/mdx-deck-live-code
|
||||
@ -195,8 +196,9 @@ export default ({ children }) => (
|
||||
style={{
|
||||
width: '100vw',
|
||||
height: '100vw',
|
||||
backgroundColor: 'tomato'
|
||||
}}>
|
||||
backgroundColor: 'tomato',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
@ -208,6 +210,7 @@ import Layout from './Layout'
|
||||
# No Layout
|
||||
|
||||
---
|
||||
|
||||
export default Layout
|
||||
|
||||
# Custom Layout
|
||||
@ -218,69 +221,58 @@ which means you can use a nested ThemeProvider or target elements with CSS-in-JS
|
||||
|
||||
### Built-in Layouts
|
||||
|
||||
mdx-deck includes some built-in layouts for inverting theme colors and changing the layout of a slide. Read more about [built-in layouts](docs/components.md#layouts).
|
||||
MDX Deck includes some built-in layouts for inverting theme colors and changing the layout of a slide.
|
||||
Read more about [built-in layouts](docs/components.md#layouts).
|
||||
|
||||
## Presenter Mode
|
||||
|
||||
mdx-deck includes a built-in presenter mode, with a preview of the next slide and a timer.
|
||||
MDX Deck includes a built-in _Presenter Mode_, with a preview of the next slide and a timer.
|
||||
|
||||
![presenter mode screenshot](docs/images/presenter-mode.png)
|
||||
|
||||
To use presenter mode:
|
||||
|
||||
- Open two windows in the same browser, with the same URL on two different screens. (this should work in both development and exported presentations)
|
||||
- In your window, press the `Option/Alt + P` keys (or add `?mode=presenter` to the URL) to enter presenter mode.
|
||||
- Display the other window on the screen for the audience to see.
|
||||
- Control the presentation from your window by using the left and right arrow keys; the other window should stay in sync
|
||||
1. Open your presentation and enter _Presenter Mode_
|
||||
2. Click on the link in the bottom to open the presentation in another tab
|
||||
3. Move the other window to the screen for the audience to see
|
||||
4. Control the presentation from your window using the left and right arrow keys – the other window should stay in sync
|
||||
5. Be sure to move your cursor so that it doesn't drive anyone in the audience crazy
|
||||
|
||||
### Speaker Notes
|
||||
|
||||
Notes that only show in presenter mode can be added to any slide.
|
||||
Speaker notes can be added in one of the following two ways:
|
||||
|
||||
**Markdown:** Use the `notes` language attribute in a fenced code block to add speaker notes.
|
||||
|
||||
````mdx
|
||||
# Slide Content
|
||||
|
||||
```notes
|
||||
These are only visible in presenter mode
|
||||
```
|
||||
````
|
||||
|
||||
**Notes Component:** Use the `Notes` component to create more complex speaker notes.
|
||||
Speaker notes can be added using the `<Notes />` component.
|
||||
|
||||
```mdx
|
||||
import { Notes } from 'mdx-deck'
|
||||
|
||||
# Slide Content
|
||||
|
||||
<Notes>
|
||||
Only visible in presenter mode
|
||||
</Notes>
|
||||
<Notes>Only visible in presenter mode</Notes>
|
||||
```
|
||||
|
||||
## Overview Mode
|
||||
|
||||
![Overview Mode](docs/images/overview-mode.png)
|
||||
|
||||
When editing a slide deck, toggle overview mode with `Option + O` or add `?mode=overview` to the URL.
|
||||
When editing a slide deck, toggle overview mode with `Option + O`.
|
||||
This shows a list of all slides on the left and a preview of the current slide on the right.
|
||||
|
||||
## Keyboard Shortcuts
|
||||
|
||||
Key | Description
|
||||
----------- | -----------
|
||||
Left Arrow | Go to previous slide (or step in [Appear][])
|
||||
Right Arrow | Go to next slide (or step in [Appear][])
|
||||
Space | Go to next slide (or step in [Appear][])
|
||||
Up Arrow | Hide current step in [Appear][] component without navigating slides
|
||||
Down Arrow | Show next step in [Appear][] component without navigating slides
|
||||
Option + P | Toggle [Presenter Mode](#presenter-mode)
|
||||
Option + O | Toggle [Overview Mode](#overview-mode)
|
||||
Option + G | Toggle grid view mode
|
||||
| Key | Description |
|
||||
| ----------- | -------------------------------------------- |
|
||||
| Left Arrow | Go to previous slide (or step in [Appear][]) |
|
||||
| Right Arrow | Go to next slide (or step in [Appear][]) |
|
||||
| Space | Go to next slide (or step in [Appear][]) |
|
||||
| Option + P | Toggle [Presenter Mode](#presenter-mode) |
|
||||
| Option + O | Toggle [Overview Mode](#overview-mode) |
|
||||
|
||||
[Appear]: docs/components.md#appear
|
||||
### URL Query String
|
||||
|
||||
The alternative modes in MDX Deck can also be set with query strings: `?mode=presenter` or `?mode=overview`
|
||||
|
||||
[appear]: docs/components.md#appear
|
||||
|
||||
## Exporting
|
||||
|
||||
@ -298,15 +290,10 @@ See more exporting options in the [Exporting Documentation](docs/exporting.md)
|
||||
|
||||
```
|
||||
-p --port Dev server port
|
||||
--hot-port Dev server hot reload port
|
||||
-h --host Host the dev server listens to
|
||||
--no-open Prevent from opening in default browser
|
||||
-d --out-dir Output directory for exporting
|
||||
--no-html Disable static HTML rendering
|
||||
--out-file Filename for screenshot or PDF export
|
||||
--width Width in pixels
|
||||
--height Height in pixels
|
||||
--webpack Path to webpack config file
|
||||
--webpack Path to custom webpack config file
|
||||
```
|
||||
|
||||
## Docs
|
||||
@ -317,7 +304,6 @@ See more exporting options in the [Exporting Documentation](docs/exporting.md)
|
||||
- [Components](docs/components.md)
|
||||
- [Exporting](docs/exporting.md)
|
||||
- [Advanced Usage](docs/advanced.md)
|
||||
- [React API](docs/react.md)
|
||||
|
||||
## Examples
|
||||
|
||||
@ -332,22 +318,14 @@ See more exporting options in the [Exporting Documentation](docs/exporting.md)
|
||||
### Related
|
||||
|
||||
- [MDX][]
|
||||
- [mdx-go][]
|
||||
- [ok-mdx][]
|
||||
- [Compositor x0][]
|
||||
- [styled-components][]
|
||||
- [styled-system][]
|
||||
- [emotion][]
|
||||
- [Spectacle][]
|
||||
|
||||
[MIT License](LICENSE.md)
|
||||
|
||||
[MDX]: https://github.com/mdx-js/mdx
|
||||
[ok-mdx]: https://github.com/jxnblk/ok-mdx
|
||||
[Compositor x0]: https://github.com/c8r/x0
|
||||
[styled-system]: https://github.com/jxnblk/styled-system
|
||||
[styled-components]: https://github.com/styled-components/styled-components
|
||||
[Spectacle]: https://github.com/FormidableLabs/spectacle
|
||||
[mdx-go]: https://github.com/jxnblk/mdx-go
|
||||
[mdx]: https://mdxjs.com/
|
||||
[spectacle]: https://github.com/FormidableLabs/spectacle
|
||||
[emotion]: https://emotion.sh
|
||||
|
||||
<!-- examples -->
|
||||
|
||||
|
1
babel.config.js
Normal file
1
babel.config.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require('./packages/mdx-deck/babel.config')
|
202
cli.js
202
cli.js
@ -1,202 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
const path = require('path')
|
||||
const meow = require('meow')
|
||||
const findup = require('find-up')
|
||||
const open = require('react-dev-utils/openBrowser')
|
||||
const chalk = require('chalk')
|
||||
const remark = {
|
||||
emoji: require('remark-emoji'),
|
||||
unwrapImages: require('remark-unwrap-images'),
|
||||
}
|
||||
const pkg = require('./package.json')
|
||||
|
||||
const config = require('pkg-conf').sync('mdx-deck')
|
||||
const log = require('./lib/log')
|
||||
|
||||
const cli = meow(
|
||||
`
|
||||
${chalk.gray('Usage')}
|
||||
|
||||
$ ${chalk.magenta('mdx-deck deck.mdx')}
|
||||
|
||||
$ ${chalk.magenta('mdx-deck build deck.mdx')}
|
||||
|
||||
$ ${chalk.magenta('mdx-deck pdf deck.mdx')}
|
||||
|
||||
$ ${chalk.magenta('mdx-deck screenshot deck.mdx')}
|
||||
|
||||
${chalk.gray('Options')}
|
||||
|
||||
--webpack Path to webpack config file
|
||||
--version Print version number
|
||||
|
||||
${chalk.gray('Dev server options')}
|
||||
|
||||
-h --host Dev server host
|
||||
-p --port Dev server port
|
||||
--hot-port Dev server hot reload port
|
||||
--no-open Prevent from opening in default browser
|
||||
|
||||
${chalk.gray('Build options')}
|
||||
|
||||
-d --out-dir Output directory for exporting
|
||||
--no-html Disable static HTML rendering
|
||||
|
||||
${chalk.gray('Export options')}
|
||||
|
||||
--out-file Filename for screenshot or PDF export
|
||||
--width Width in pixels
|
||||
--height Height in pixels
|
||||
|
||||
${chalk.gray('PDF options')}
|
||||
|
||||
--no-sandbox Disable Puppeteer sandbox
|
||||
|
||||
`,
|
||||
{
|
||||
description: chalk.magenta('[mdx-deck] ') + chalk.gray(pkg.description),
|
||||
flags: {
|
||||
port: {
|
||||
type: 'string',
|
||||
alias: 'p',
|
||||
},
|
||||
host: {
|
||||
type: 'string',
|
||||
alias: 'h',
|
||||
},
|
||||
hotPort: {
|
||||
type: 'string',
|
||||
},
|
||||
open: {
|
||||
type: 'boolean',
|
||||
alias: 'o',
|
||||
default: true,
|
||||
},
|
||||
outDir: {
|
||||
type: 'string',
|
||||
alias: 'd',
|
||||
},
|
||||
outFile: {
|
||||
type: 'string',
|
||||
},
|
||||
html: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
webpack: {
|
||||
type: 'string',
|
||||
},
|
||||
sandbox: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const [cmd, file] = cli.input
|
||||
const doc = file || cmd
|
||||
|
||||
if (!doc) cli.showHelp(0)
|
||||
|
||||
const opts = Object.assign(
|
||||
{
|
||||
dirname: path.dirname(path.resolve(doc)),
|
||||
globals: {
|
||||
FILENAME: JSON.stringify(path.resolve(doc)),
|
||||
},
|
||||
host: 'localhost',
|
||||
port: 8080,
|
||||
outDir: 'dist',
|
||||
},
|
||||
config,
|
||||
cli.flags
|
||||
)
|
||||
|
||||
opts.outDir = path.resolve(opts.outDir)
|
||||
if (opts.webpack) {
|
||||
opts.webpack = require(path.resolve(opts.webpack))
|
||||
} else {
|
||||
const webpackConfig = findup.sync('webpack.config.js', { cwd: opts.dirname })
|
||||
if (webpackConfig) opts.webpack = require(webpackConfig)
|
||||
}
|
||||
|
||||
let dev
|
||||
|
||||
switch (cmd) {
|
||||
case 'build':
|
||||
log('building')
|
||||
const build = require('./lib/build')
|
||||
build(opts)
|
||||
.then(res => {
|
||||
log('done')
|
||||
process.exit(0)
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err)
|
||||
process.exit(1)
|
||||
})
|
||||
break
|
||||
case 'pdf':
|
||||
log('exporting to PDF')
|
||||
const pdf = require('./lib/pdf')
|
||||
dev = require('./lib/dev')
|
||||
dev(opts)
|
||||
.then(({ server }) => {
|
||||
log('rendering PDF')
|
||||
pdf(opts)
|
||||
.then(filename => {
|
||||
server.close()
|
||||
log('done', filename)
|
||||
process.exit(0)
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err)
|
||||
process.exit(1)
|
||||
})
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err)
|
||||
process.exit(1)
|
||||
})
|
||||
break
|
||||
case 'screenshot':
|
||||
log('exporting to PNG')
|
||||
const screenshot = require('./lib/screenshot')
|
||||
dev = require('./lib/dev')
|
||||
dev(opts)
|
||||
.then(({ server }) => {
|
||||
log('rendering screenshot')
|
||||
screenshot(opts)
|
||||
.then(filename => {
|
||||
server.close()
|
||||
log('done', filename)
|
||||
process.exit(0)
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err)
|
||||
process.exit(1)
|
||||
})
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err)
|
||||
process.exit(1)
|
||||
})
|
||||
break
|
||||
case 'dev':
|
||||
default:
|
||||
log('starting dev server')
|
||||
dev = require('./lib/dev')
|
||||
dev(opts)
|
||||
.then(res => {
|
||||
const { address, port } = res.server.address()
|
||||
const url = 'http://' + address + ':' + res.port
|
||||
if (opts.open) open(url)
|
||||
log('listening on', chalk.magenta(url))
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err)
|
||||
process.exit(1)
|
||||
})
|
||||
break
|
||||
}
|
@ -1,56 +1,62 @@
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import styled from '@emotion/styled'
|
||||
import { space, color } from 'styled-system'
|
||||
|
||||
const Root = styled.div([], {
|
||||
const Root = styled.div({
|
||||
display: 'flex',
|
||||
alignItems: 'center'
|
||||
alignItems: 'center',
|
||||
})
|
||||
|
||||
const Button = styled.button([], {
|
||||
appearance: 'none',
|
||||
fontFamily: 'inherit',
|
||||
fontSize: 'inherit',
|
||||
fontWeight: 'bold',
|
||||
borderRadius: '4px',
|
||||
border: 'none',
|
||||
width: '2em',
|
||||
'&:focus': {
|
||||
outline: 'none',
|
||||
boxShadow: '0 0 0 2px magenta'
|
||||
}
|
||||
}, space, color)
|
||||
const Button = styled.button(
|
||||
{
|
||||
appearance: 'none',
|
||||
fontFamily: 'inherit',
|
||||
fontSize: 'inherit',
|
||||
fontWeight: 'bold',
|
||||
borderRadius: '4px',
|
||||
border: 'none',
|
||||
width: '2em',
|
||||
'&:focus': {
|
||||
outline: 'none',
|
||||
boxShadow: '0 0 0 2px magenta',
|
||||
},
|
||||
},
|
||||
space,
|
||||
color
|
||||
)
|
||||
Button.defaultProps = {
|
||||
m: 0,
|
||||
px: 3,
|
||||
py: 2,
|
||||
color: 'background',
|
||||
bg: 'text'
|
||||
bg: 'text',
|
||||
}
|
||||
|
||||
const Samp = styled.samp([], {
|
||||
}, space)
|
||||
|
||||
const Samp = styled.samp(space)
|
||||
|
||||
export default class Counter extends React.Component {
|
||||
state = {
|
||||
count: 0
|
||||
count: 0,
|
||||
}
|
||||
|
||||
inc = () => {
|
||||
this.setState(state => ({count: state.count + 1}))
|
||||
this.setState(state => ({ count: state.count + 1 }))
|
||||
}
|
||||
|
||||
dec = () => {
|
||||
this.setState(state => ({count: state.count - 1}))
|
||||
this.setState(state => ({ count: state.count - 1 }))
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Root>
|
||||
<Button ml='auto' onClick={this.dec}>-</Button>
|
||||
<Button ml="auto" onClick={this.dec}>
|
||||
-
|
||||
</Button>
|
||||
<Samp mx={3}>{this.state.count}</Samp>
|
||||
<Button mr='auto' onClick={this.inc}>+</Button>
|
||||
<Button mr="auto" onClick={this.inc}>
|
||||
+
|
||||
</Button>
|
||||
</Root>
|
||||
)
|
||||
}
|
||||
|
@ -1,40 +1,35 @@
|
||||
|
||||
# Advanced Usage
|
||||
|
||||
## Custom MDX components
|
||||
|
||||
mdx-deck includes default components for MDX, but to provide custom components to the [MDXProvider][], add a `components` object to the `theme`.
|
||||
MDX Deck includes default components for MDX, but to provide custom components to the [MDXProvider][], add a `components` object to the `theme`.
|
||||
|
||||
```js
|
||||
// example theme
|
||||
import { theme } from 'mdx-deck/themes'
|
||||
import Heading from './Heading'
|
||||
|
||||
export default {
|
||||
...theme,
|
||||
components: {
|
||||
h1: Heading
|
||||
}
|
||||
h1: Heading,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
See the [MDX][] docs for more or take a look
|
||||
at the [default set of components](../src/components.js) as a reference.
|
||||
at the [default set of components](../packages/components/src/mdx-components.js) as a reference.
|
||||
|
||||
## Custom Provider component
|
||||
|
||||
A custom Provider component can be added to the theme to wrap the entire application.
|
||||
This is useful for adding custom context providers in React.
|
||||
This is useful for adding custom context providers in React or adding persistent UI elements to the entire deck.
|
||||
|
||||
```js
|
||||
// example theme.js
|
||||
import { theme } from 'mdx-deck/themes'
|
||||
import Provider from './Provider'
|
||||
|
||||
export default {
|
||||
...theme,
|
||||
font: 'Georgia, serif',
|
||||
Provider
|
||||
Provider,
|
||||
}
|
||||
```
|
||||
|
||||
@ -43,20 +38,19 @@ which can be used to show custom page numbers or add other elements to the UI.
|
||||
|
||||
#### Props
|
||||
|
||||
- `slides`: (array) the components for each slide
|
||||
- `index`: (number) the current slide index
|
||||
- `length`: (number) the length of the slides array
|
||||
- `mode`: (string) the current mode (one of `'NORMAL'`, `'PRESENTER'`, or `'OVERVIEW'`)
|
||||
- `notes`: (object) custom [speaker notes](#speaker-notes) for all slides
|
||||
- `step`: (number) the current visible step in an Appear component
|
||||
|
||||
- `mode`: (string) the current mode (one of `'normal'`, `'presenter'`, or `'overview'`)
|
||||
- `step`: (number) the current visible step in an Appear or Step component
|
||||
- Each slide includes a `meta` object with a `notes` field when the Notes component is used within a slide
|
||||
|
||||
## Combining multiple mdx files
|
||||
|
||||
Unlike the official `@mdx-js/loader`,
|
||||
the `mdx-deck/loader` exports an array of components instead of just one.
|
||||
the `@mdx-deck/loader` exports an additional `slides` array of components instead of just the entire document.
|
||||
Multiple MDX files can be combined into a single presentation if the filesize is getting difficult to manage.
|
||||
|
||||
First create a couple `.mdx` files like any other mdx-deck file, with `---` to separate the different slides.
|
||||
First create a couple `.mdx` files like any other MDX Deck file, with `---` to separate the different slides.
|
||||
|
||||
```mdx
|
||||
# one.mdx
|
||||
@ -78,16 +72,13 @@ Next, create a `.js` file to import and combine the two `.mdx` files.
|
||||
|
||||
```js
|
||||
// deck.js
|
||||
import one from './one.mdx'
|
||||
import two from './two.mdx'
|
||||
import { slides as one } from './one.mdx'
|
||||
import { slides as two } from './two.mdx'
|
||||
|
||||
export default [
|
||||
...one,
|
||||
...two
|
||||
]
|
||||
export const slides = [...one, ...two]
|
||||
```
|
||||
|
||||
Then, point the mdx-deck CLI comment in your `package.json` to the `deck.js` file.
|
||||
Then, point the MDX Deck CLI comment in your `package.json` to the `deck.js` file.
|
||||
|
||||
```json
|
||||
"scripts": {
|
||||
@ -97,7 +88,9 @@ Then, point the mdx-deck CLI comment in your `package.json` to the `deck.js` fil
|
||||
|
||||
## Custom webpack config
|
||||
|
||||
Webpack configuration files named `webpack.config.js` will automatically be merged with the built-in configuration, using [webpack-merge](https://github.com/survivejs/webpack-merge). To use a custom filename, pass the file path to the `--webpack` flag.
|
||||
Webpack configuration files named `webpack.config.js` will automatically be merged with the built-in configuration,
|
||||
using [webpack-merge](https://github.com/survivejs/webpack-merge).
|
||||
To use a custom filename, pass the file path to the `--webpack` flag.
|
||||
|
||||
```js
|
||||
// webpack.config.js example
|
||||
@ -106,26 +99,23 @@ module.exports = {
|
||||
rules: [
|
||||
{
|
||||
test: /\.svg$/,
|
||||
use: [
|
||||
{ loader: 'babel-loader' },
|
||||
{ loader: 'react-svg-loader' }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
use: [{ loader: 'babel-loader' }, { loader: 'react-svg-loader' }],
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
**Careful**: When overwriting the loader for `mdx` files, make sure to include the default loader from `mdx-deck/loader`.
|
||||
**Careful**: When overwriting the loader for `mdx` files, make sure to include the default loader from `@mdx-deck/loader`.
|
||||
|
||||
## Custom build setups
|
||||
|
||||
The core mdx-deck components can also be used in any React application,
|
||||
The core MDX Deck components can also be used in any React application,
|
||||
such as [create-react-app][] or [next.js][].
|
||||
|
||||
See the [React API](react.md) docs for more.
|
||||
See the [`@mdx-deck/components`](../packages/components) package for more.
|
||||
|
||||
[MDX]: https://github.com/mdx-js/mdx
|
||||
[MDXProvider]: https://github.com/mdx-js/mdx/blob/master/docs/getting-started/index.md#mdxprovider
|
||||
[mdx]: https://mdxjs.com
|
||||
[mdxprovider]: https://github.com/mdx-js/mdx/blob/master/docs/getting-started/index.md#mdxprovider
|
||||
[create-react-app]: https://github.com/facebook/create-react-app
|
||||
[next.js]: https://github.com/zeit/next.js/
|
||||
|
30
docs/code.js
30
docs/code.js
@ -1,30 +0,0 @@
|
||||
export const a = `import React from 'react'
|
||||
|
||||
const Foo = props =>
|
||||
<h1>Bar</h1>
|
||||
|
||||
export default Foo`
|
||||
|
||||
export const b = `import styled from 'styled-components'
|
||||
import { space, color } from 'styled-system'
|
||||
|
||||
const Box = styled.div([], space, color)
|
||||
|
||||
export default Box`
|
||||
|
||||
export const surfer = `import { CodeSurfer } from 'mdx-deck-code-surfer'
|
||||
import codeExample from './code-example'
|
||||
|
||||
<CodeSurfer
|
||||
title='Check out my code'
|
||||
code={codeExample}
|
||||
steps={[
|
||||
{
|
||||
lines: [ 4, 5 ],
|
||||
notes: 'This is lines 4 & 5 highlighted'
|
||||
},
|
||||
]}
|
||||
/>
|
||||
`
|
||||
|
||||
export default { a, b, surfer }
|
@ -1,7 +1,6 @@
|
||||
|
||||
# Components
|
||||
|
||||
mdx-deck includes a few built-in components to help with creating presentations.
|
||||
MDX Deck includes a few built-in components to help with creating presentations.
|
||||
|
||||
## Head
|
||||
|
||||
@ -13,11 +12,11 @@ import { Head } from 'mdx-deck'
|
||||
|
||||
<Head>
|
||||
<title>My Presentation</title>
|
||||
<meta name='twitter:card' content='summary_large_image' />
|
||||
<meta name='twitter:site' content='@jxnblk' />
|
||||
<meta name='twitter:title' content='My Presentation' />
|
||||
<meta name='twitter:description' content='A really great presentation' />
|
||||
<meta name='twitter:image' content='https://example.com/card.png' />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:site" content="@jxnblk" />
|
||||
<meta name="twitter:title" content="My Presentation" />
|
||||
<meta name="twitter:description" content="A really great presentation" />
|
||||
<meta name="twitter:image" content="https://example.com/card.png" />
|
||||
</Head>
|
||||
```
|
||||
|
||||
@ -28,10 +27,11 @@ Use the `<Image />` component to render a fullscreen image (using the CSS `backg
|
||||
```mdx
|
||||
import { Image } from 'mdx-deck'
|
||||
|
||||
<Image src='kitten.png' />
|
||||
<Image src="kitten.png" />
|
||||
```
|
||||
|
||||
### Props
|
||||
|
||||
- `src` (string) image URL
|
||||
- `size` (string) CSS background-size
|
||||
|
||||
@ -52,31 +52,23 @@ import { Appear } from 'mdx-deck'
|
||||
</ul>
|
||||
```
|
||||
|
||||
Internally, the `<Appear />` component uses the `<Step />` component, which can be used to build custom components with similar behavior.
|
||||
|
||||
## Speaker Notes
|
||||
|
||||
Speaker notes that only show in presenter mode can be added to any slide with either markdown syntax or with the Notes component.
|
||||
|
||||
````mdx
|
||||
# Markdown speaker notes
|
||||
|
||||
```notes
|
||||
These are only visible in presenter mode
|
||||
```
|
||||
````
|
||||
Speaker notes that only show in presenter mode can be added to any slide with the Notes component.
|
||||
|
||||
```mdx
|
||||
import { Notes } from 'mdx-deck'
|
||||
|
||||
# Slide Content
|
||||
|
||||
<Notes>
|
||||
Only visible in presenter mode
|
||||
</Notes>
|
||||
<Notes>Only visible in presenter mode</Notes>
|
||||
```
|
||||
|
||||
## Layouts
|
||||
|
||||
mdx-deck includes a few built-in layouts for common slide variations.
|
||||
MDX Deck includes a few built-in layouts for common slide variations.
|
||||
Export a layout as the `default` within a slide to wrap the contents.
|
||||
|
||||
### Invert
|
||||
@ -123,6 +115,10 @@ export default SplitRight
|
||||
## Meow
|
||||
```
|
||||
|
||||
### Horizontal
|
||||
|
||||
Similar to the Split components, but renders all children side-by-side
|
||||
|
||||
### FullScreenCode
|
||||
|
||||
Render fenced code blocks fullscreen.
|
||||
@ -136,4 +132,3 @@ export default FullScreenCode
|
||||
<Button>Beep</Button>
|
||||
```
|
||||
````
|
||||
|
||||
|
@ -1,8 +1,7 @@
|
||||
export { future as theme } from '../themes'
|
||||
import { Head, Image, Notes, Appear } from '../dist'
|
||||
import { Invert, Split, FullScreenCode, Horizontal} from '../layouts'
|
||||
export { future as theme } from '@mdx-deck/themes'
|
||||
import { Head, Image, Notes, Appear } from '@mdx-deck/components'
|
||||
import { Invert, Split, SplitRight, FullScreenCode, Horizontal} from '@mdx-deck/layouts'
|
||||
import Counter from './Counter'
|
||||
import code from './code'
|
||||
|
||||
<Head>
|
||||
<title>mdx-deck</title>
|
||||
@ -13,23 +12,30 @@ import code from './code'
|
||||
<meta name='twitter:image' content='https://jxnblk.com/mdx-deck/card.png' />
|
||||
</Head>
|
||||
|
||||
# mdx-deck
|
||||
# MDX Deck
|
||||
|
||||
MDX-based presention decks
|
||||
|
||||
---
|
||||
|
||||
# Presentation decks
|
||||
|
||||
---
|
||||
|
||||
# Built with [MDX][]
|
||||
[MDX]: https://github.com/mdx-js/mdx
|
||||
|
||||
---
|
||||
import Box from 'superbox'
|
||||
import { Box } from '@rebass/emotion'
|
||||
|
||||
<Box
|
||||
fontSize={3}
|
||||
fontSize={[ 6, 7 ]}
|
||||
p={4}
|
||||
color='navy'
|
||||
bg='magenta'>
|
||||
Import React components
|
||||
</Box>
|
||||
|
||||
---
|
||||
|
||||
- Make bulleted lists
|
||||
@ -49,10 +55,11 @@ import Box from 'superbox'
|
||||
<button>code example</button>
|
||||
```
|
||||
|
||||
```notes
|
||||
- These are speaker notes
|
||||
<Notes>
|
||||
These are speaker notes
|
||||
- And they won't be rendered in your slide
|
||||
```
|
||||
</Notes>
|
||||
|
||||
---
|
||||
|
||||
```jsx
|
||||
@ -66,7 +73,9 @@ class extends React.Component {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> “Blockquotes are essential to any good presentation”
|
||||
|
||||
– Anonymous
|
||||
@ -79,6 +88,7 @@ class extends React.Component {
|
||||
</Notes>
|
||||
|
||||
---
|
||||
|
||||
### Appear
|
||||
|
||||
<ul>
|
||||
@ -89,6 +99,7 @@ class extends React.Component {
|
||||
<li>Four</li>
|
||||
</Appear>
|
||||
</ul>
|
||||
|
||||
---
|
||||
|
||||
<Image
|
||||
@ -96,9 +107,10 @@ class extends React.Component {
|
||||
size='contain'
|
||||
/>
|
||||
|
||||
```notes
|
||||
<Notes>
|
||||
Testing object fit
|
||||
```
|
||||
</Notes>
|
||||
|
||||
---
|
||||
|
||||
### Real React Components
|
||||
@ -110,6 +122,7 @@ Testing object fit
|
||||
<Image src='https://images.unsplash.com/photo-1462331940025-496dfbfc7564?w=2048&q=20' />
|
||||
|
||||
---
|
||||
|
||||
export default Split
|
||||
|
||||
![](https://images.unsplash.com/photo-1462331940025-496dfbfc7564?w=2048&q=20)
|
||||
@ -117,6 +130,17 @@ export default Split
|
||||
## Split Layout
|
||||
|
||||
---
|
||||
|
||||
export default SplitRight
|
||||
|
||||
![](https://images.unsplash.com/photo-1462331940025-496dfbfc7564?w=2048&q=20)
|
||||
|
||||
## Split Layout
|
||||
|
||||
(To the right)
|
||||
|
||||
---
|
||||
|
||||
export default Horizontal
|
||||
|
||||
![](https://source.unsplash.com/random/1024x768')
|
||||
@ -157,4 +181,6 @@ Prop | Type | Description
|
||||
export default Invert
|
||||
|
||||
# Get started :sunglasses:
|
||||
|
||||
[GitHub](https://github.com/jxnblk/mdx-deck)
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
# Exporting
|
||||
|
||||
## Static Bundle
|
||||
@ -12,26 +11,24 @@ add a `build` script to your `package.json` file.
|
||||
}
|
||||
```
|
||||
|
||||
## PDF Export
|
||||
### PDF & Screenshots
|
||||
|
||||
Presentations can be exported as PDF using the CLI.
|
||||
This works well as a backup option for any unforeseen technical difficulties.
|
||||
To export a deck as PDF or create a PNG screenshot, install the export CLI package:
|
||||
|
||||
```json
|
||||
"script": {
|
||||
"pdf": "mdx-deck pdf deck.mdx"
|
||||
}
|
||||
```sh
|
||||
npm i @mdx-deck/export
|
||||
```
|
||||
|
||||
## Screenshots
|
||||
Then run the following command to create a PDF:
|
||||
|
||||
A PNG image of the first slide can be exported with the `screenshot` command.
|
||||
This is useful for creating open graph images for Twitter, Facebook, or Slack.
|
||||
```sh
|
||||
mdx-deck-export pdf deck.mdx
|
||||
```
|
||||
|
||||
```json
|
||||
"script": {
|
||||
"screenshot": "mdx-deck screenshot deck.mdx"
|
||||
}
|
||||
Or export the first slide as a PNG:
|
||||
|
||||
```sh
|
||||
mdx-deck-export png deck.mdx
|
||||
```
|
||||
|
||||
### OG Image
|
||||
@ -43,7 +40,6 @@ Note that the meta tag should point to a full URL, including schema and domain n
|
||||
import { Head } from 'mdx-deck'
|
||||
|
||||
<Head>
|
||||
<meta name='og:image' content='https://example.com/card.png' />
|
||||
<meta name="og:image" content="https://example.com/card.png" />
|
||||
</Head>
|
||||
```
|
||||
|
||||
|
22
docs/package.json
Normal file
22
docs/package.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "@mdx-deck/docs",
|
||||
"version": "2.0.0-8",
|
||||
"main": "index.js",
|
||||
"author": "Brent Jackson <jxnblk@gmail.com>",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"start": "mdx-deck demo.mdx",
|
||||
"build": "mdx-deck build demo.mdx"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/core": "^10.0.7",
|
||||
"@emotion/styled": "^10.0.7",
|
||||
"@mdx-deck/components": "^2.0.0-8",
|
||||
"@mdx-deck/layouts": "^2.0.0-7",
|
||||
"@mdx-deck/themes": "^2.0.0-7",
|
||||
"@rebass/emotion": "^3.0.0",
|
||||
"mdx-deck": "^2.0.0-8",
|
||||
"rebass": "^3.0.1"
|
||||
}
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
export { yellow as theme } from '../src/themes'
|
||||
|
||||
# Say hello to...
|
||||
|
||||
---
|
||||
|
||||
# Presenter mode
|
||||
|
||||
(press *Option + P*)
|
||||
|
||||
---
|
||||
|
||||
## You can see the next slide
|
||||
|
||||
---
|
||||
|
||||
## And the *next* one
|
||||
|
||||
---
|
||||
|
||||
## And a timer
|
||||
|
||||
---
|
||||
|
||||
## And [speaker notes][] too!
|
||||
|
||||
[speaker notes]: https://github.com/jxnblk/mdx-deck#speaker-notes
|
||||
|
||||
```notes
|
||||
No one will see this 🙈
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# mdx-deck
|
||||
|
@ -1,48 +0,0 @@
|
||||
|
||||
# React API
|
||||
|
||||
The core mdx-deck components can also be used in any React application, such as [create-react-app][] or [next.js][].
|
||||
|
||||
### Webpack Loader
|
||||
|
||||
mdx-deck uses a custom webpack loader to split MDX files into an array of slides. Use this loader to import mdx files in a webpack application.
|
||||
|
||||
```js
|
||||
// example webpack.config.js
|
||||
module.exports = {
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.mdx$/,
|
||||
ignore: /node_modules/,
|
||||
use: [
|
||||
'babel-loader',
|
||||
'mdx-deck/loader'
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### SlideDeck Component
|
||||
|
||||
```js
|
||||
import React from 'react'
|
||||
import { SlideDeck } from 'mdx-deck'
|
||||
import slides from './deck.mdx'
|
||||
import theme from './theme'
|
||||
|
||||
export default () =>
|
||||
<SlideDeck
|
||||
slides={slides}
|
||||
theme={theme}
|
||||
width='100vw'
|
||||
height='100vh'
|
||||
/>
|
||||
```
|
||||
|
||||
View the source for other components available for use.
|
||||
|
||||
[create-react-app]: https://github.com/facebook/create-react-app
|
||||
[next.js]: https://github.com/zeit/next.js/
|
@ -1,11 +1,11 @@
|
||||
|
||||
# Themes
|
||||
|
||||
![](images/default.png)
|
||||
Default
|
||||
|
||||
```mdx
|
||||
export { default as theme } from 'mdx-deck/themes'
|
||||
export { default as theme } from 'mdx-deck/themes
|
||||
'
|
||||
```
|
||||
|
||||
---
|
||||
@ -14,7 +14,8 @@ export { default as theme } from 'mdx-deck/themes'
|
||||
Big
|
||||
|
||||
```mdx
|
||||
export { big as theme } from 'mdx-deck/themes'
|
||||
export { big as theme } from 'mdx-deck/themes
|
||||
'
|
||||
```
|
||||
|
||||
---
|
||||
@ -23,7 +24,8 @@ export { big as theme } from 'mdx-deck/themes'
|
||||
Book
|
||||
|
||||
```mdx
|
||||
export { book as theme } from 'mdx-deck/themes'
|
||||
export { book as theme } from 'mdx-deck/themes
|
||||
'
|
||||
```
|
||||
|
||||
---
|
||||
@ -32,7 +34,8 @@ export { book as theme } from 'mdx-deck/themes'
|
||||
Code
|
||||
|
||||
```mdx
|
||||
export { code as theme } from 'mdx-deck/themes'
|
||||
export { code as theme } from 'mdx-deck/themes
|
||||
'
|
||||
```
|
||||
|
||||
---
|
||||
@ -41,7 +44,8 @@ export { code as theme } from 'mdx-deck/themes'
|
||||
Comic
|
||||
|
||||
```mdx
|
||||
export { comic as theme } from 'mdx-deck/themes'
|
||||
export { comic as theme } from 'mdx-deck/themes
|
||||
'
|
||||
```
|
||||
|
||||
---
|
||||
@ -50,7 +54,8 @@ export { comic as theme } from 'mdx-deck/themes'
|
||||
Condensed
|
||||
|
||||
```mdx
|
||||
export { condensed as theme } from 'mdx-deck/themes'
|
||||
export { condensed as theme } from 'mdx-deck/themes
|
||||
'
|
||||
```
|
||||
|
||||
---
|
||||
@ -59,7 +64,8 @@ export { condensed as theme } from 'mdx-deck/themes'
|
||||
Dark
|
||||
|
||||
```mdx
|
||||
export { dark as theme } from 'mdx-deck/themes'
|
||||
export { dark as theme } from 'mdx-deck/themes
|
||||
'
|
||||
```
|
||||
|
||||
---
|
||||
@ -68,7 +74,8 @@ export { dark as theme } from 'mdx-deck/themes'
|
||||
Future
|
||||
|
||||
```mdx
|
||||
export { future as theme } from 'mdx-deck/themes'
|
||||
export { future as theme } from 'mdx-deck/themes
|
||||
'
|
||||
```
|
||||
|
||||
---
|
||||
@ -77,7 +84,8 @@ export { future as theme } from 'mdx-deck/themes'
|
||||
Hack
|
||||
|
||||
```mdx
|
||||
export { hack as theme } from 'mdx-deck/themes'
|
||||
export { hack as theme } from 'mdx-deck/themes
|
||||
'
|
||||
```
|
||||
|
||||
---
|
||||
@ -91,7 +99,8 @@ Lobster
|
||||
Notes
|
||||
|
||||
```mdx
|
||||
export { notes as theme } from 'mdx-deck/themes'
|
||||
export { notes as theme } from 'mdx-deck/themes
|
||||
'
|
||||
```
|
||||
|
||||
---
|
||||
@ -105,7 +114,8 @@ Rye
|
||||
Script
|
||||
|
||||
```mdx
|
||||
export { script as theme } from 'mdx-deck/themes'
|
||||
export { script as theme } from 'mdx-deck/themes
|
||||
'
|
||||
```
|
||||
|
||||
---
|
||||
@ -114,7 +124,8 @@ export { script as theme } from 'mdx-deck/themes'
|
||||
Swiss
|
||||
|
||||
```mdx
|
||||
export { swiss as theme } from 'mdx-deck/themes'
|
||||
export { swiss as theme } from 'mdx-deck/themes
|
||||
'
|
||||
```
|
||||
|
||||
---
|
||||
@ -123,6 +134,33 @@ export { swiss as theme } from 'mdx-deck/themes'
|
||||
Yellow
|
||||
|
||||
```mdx
|
||||
export { yellow as theme } from 'mdx-deck/themes'
|
||||
export { yellow as theme } from 'mdx-deck/themes
|
||||
'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Poppins
|
||||
|
||||
```mdx
|
||||
export { poppins as theme } from 'mdx-deck/themes
|
||||
'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Syntax Highlighter
|
||||
|
||||
```mdx
|
||||
export { syntaxHighlighter as theme } from 'mdx-deck/themes
|
||||
'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Syntax Highlighter Prism
|
||||
|
||||
```mdx
|
||||
export { syntaxHighlighterPrism as theme } from 'mdx-deck/themes
|
||||
'
|
||||
```
|
||||
|
141
docs/theming.md
141
docs/theming.md
@ -1,7 +1,6 @@
|
||||
|
||||
# Theming
|
||||
|
||||
mdx-deck uses [styled-components][] for styling, making practically any part of the presentation themeable.
|
||||
mdx-deck uses [emotion][] for styling, making practically any part of the presentation themeable.
|
||||
|
||||
## Built-in Themes
|
||||
|
||||
@ -27,145 +26,84 @@ export { default as theme } from './theme'
|
||||
```
|
||||
|
||||
The theme should be an object with fields for fonts, colors, and CSS for individual components.
|
||||
It's recommended that all custom themes extend the default theme as a base.
|
||||
|
||||
<small>Note: you need to include [all keys](#reference) in the `colors` object for the theming to work correctly. You may want to use a deep [merge](https://www.npmjs.com/search?q=merge) strategy for extending existing themes more easily.</small>
|
||||
|
||||
```js
|
||||
// Example theme.js
|
||||
import theme from 'mdx-deck/themes'
|
||||
|
||||
export default {
|
||||
// extends the default theme
|
||||
...theme,
|
||||
// add a custom font
|
||||
font: 'Roboto, sans-serif',
|
||||
// custom colors
|
||||
colors: {
|
||||
...theme.colors, // include existing theme colors
|
||||
text: '#f0f',
|
||||
background: 'black',
|
||||
link: '#0ff',
|
||||
}
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Composing Themes
|
||||
|
||||
Multiple themes can be used together.
|
||||
For example, this allows the use of a syntax highlighting theme,
|
||||
along with a color theme, and a separate typography theme.
|
||||
|
||||
To compose themes together export a `themes` array instead of a single theme.
|
||||
|
||||
```mdx
|
||||
import { syntaxHighlighter } from 'mdx-deck/themes'
|
||||
import customTheme from './theme'
|
||||
|
||||
export const themes = [syntaxHighlighter, customTheme]
|
||||
|
||||
# Cool. :sunglasses:
|
||||
```
|
||||
|
||||
Please note that themes are deep merged together and the last theme specified will override fields from themes before it.
|
||||
|
||||
### Google Fonts
|
||||
|
||||
To use webfonts, mdx-deck will attempt to add `<link>` tags for any font from Google Fonts.
|
||||
To load webfonts from other sources, use a custom [Provider component](advanced.md#custom-provider-component) to add custom `<link>` tags.
|
||||
Themes can specify a `googleFont` field to automatically add a `<link>` tag to the document head.
|
||||
Alternatively, use the `<Head />` component to add a custom `<link>` tag.
|
||||
|
||||
### Syntax Highlighting
|
||||
|
||||
By default fenced code blocks do not include any syntax highlighting.
|
||||
Syntax highlighting in fenced code blocks can be enabled by providing a `prism` style object in a theme.
|
||||
The syntax highlighting is built with [react-syntax-highlighter][] and [PrismJS][].
|
||||
Create a `theme.js` file and export it via the `MDX` file.
|
||||
Themes can provide a set of custom MDX components, including a replacement for the default `code` component that can add syntax highlighting with libraries like [react-syntax-highlighter][].
|
||||
|
||||
```js
|
||||
export { default as theme } from './theme'
|
||||
//...
|
||||
```
|
||||
MDX Deck includes two themes for adding syntax highlighting with [react-syntax-highlighter][]: `syntaxHighlighter` and `syntaxHighlighterPrism`.
|
||||
|
||||
```js
|
||||
// example theme.js
|
||||
import { future } from 'mdx-deck/themes'
|
||||
import okaidia from 'react-syntax-highlighter/styles/prism/okaidia'
|
||||
|
||||
export default {
|
||||
...future,
|
||||
prism: {
|
||||
style: okaidia
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
By default, only JavaScript and JSX are enabled for syntax highlighting to keep bundle sizes to a minimum.
|
||||
To enable other languages, add a `languages` object to the `prism` object in the theme.
|
||||
|
||||
```js
|
||||
// example theme.js
|
||||
import { future } from 'mdx-deck/themes'
|
||||
import okaidia from 'react-syntax-highlighter/styles/prism/okaidia'
|
||||
import prismRuby from 'react-syntax-highlighter/languages/prism/ruby'
|
||||
|
||||
export default {
|
||||
...future,
|
||||
prism: {
|
||||
style: okaidia,
|
||||
languages: {
|
||||
ruby: prismRuby
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For lists of available syntax styles and languages, see:
|
||||
|
||||
- [Prism languages](https://github.com/conorhastings/react-syntax-highlighter/blob/master/AVAILABLE_LANGUAGES_PRISM.MD)
|
||||
- [Prism styles](https://github.com/conorhastings/react-syntax-highlighter/blob/master/AVAILABLE_STYLES_PRISM.MD)
|
||||
|
||||
[PrismJS]: https://github.com/PrismJS/prism
|
||||
[react-syntax-highlighter]: https://github.com/conorhastings/react-syntax-highlighter
|
||||
|
||||
### Slide Transitions
|
||||
|
||||
The slide transition timing function and duration can be customized with a custom theme.
|
||||
|
||||
```js
|
||||
// example theme
|
||||
import theme from 'mdx-deck/themes'
|
||||
|
||||
export default {
|
||||
...theme,
|
||||
transitionTimingFunction: 'linear',
|
||||
transitionDuration: '.1s'
|
||||
}
|
||||
```
|
||||
Since MDX supports using React components inline, you can also import a syntax highlighting component directly, if you prefer.
|
||||
|
||||
### Styling Elements
|
||||
|
||||
Each element can be styled with a theme. Add a style object (or string) to the theme to target specific elements.
|
||||
Each element can be styled with a theme.
|
||||
Add a style object (or string) to the theme to target specific elements.
|
||||
|
||||
```js
|
||||
// example theme
|
||||
import theme from 'mdx-deck/themes'
|
||||
|
||||
export default {
|
||||
...theme,
|
||||
h1: {
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.1em'
|
||||
letterSpacing: '0.1em',
|
||||
},
|
||||
blockquote: {
|
||||
fontStyle: 'italic'
|
||||
}
|
||||
fontStyle: 'italic',
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
See the [reference](#reference) below for a full list of element keys.
|
||||
|
||||
|
||||
## Reference
|
||||
|
||||
The following keys are available for theming:
|
||||
|
||||
- `font`: base font family
|
||||
- `weights`: array of font weights for the main font
|
||||
- `monospace`: font family for `<pre>` and `<code>`
|
||||
- `fontSizes`: array of font sizes from smallest to largest
|
||||
- `colors`: object of colors used for MDX components
|
||||
- `text`: root foreground color
|
||||
- `background`: root background color
|
||||
- `link`
|
||||
- `heading`
|
||||
- `blockquote`
|
||||
- `pre`
|
||||
- `preBackground`
|
||||
- `code`
|
||||
- `codeBackground`
|
||||
- `transitionTimingFunction`: timing function value for slide transitions
|
||||
- `transitionDuration`: duration value for slide transitions. set to `0` to disable transitions
|
||||
- `code`: text color for `<pre>` and `<code>`
|
||||
- `codeBackground`: background color for `<pre>` and `<code>`
|
||||
- `css`: root CSS object
|
||||
- `heading`: CSS for all headings
|
||||
- `h1`: CSS for `<h1>`
|
||||
@ -174,20 +112,25 @@ The following keys are available for theming:
|
||||
- `h4`: CSS for `<h4>`
|
||||
- `h5`: CSS for `<h5>`
|
||||
- `h6`: CSS for `<h6>`
|
||||
- `paragraph`: CSS for `<p>`
|
||||
- `link`: CSS for `<a>`
|
||||
- `p`: CSS for `<p>`
|
||||
- `a`: CSS for `<a>`
|
||||
- `ul`: CSS for `<ul>`
|
||||
- `ol`: CSS for `<ol>`
|
||||
- `li`: CSS for `<li>`
|
||||
- `img`: CSS for `<img>`
|
||||
- `blockquote`: CSS for `<blockquote>`
|
||||
- `table`: CSS for `<table>`
|
||||
- `pre`: CSS for `<pre>`
|
||||
- `code`: CSS for `<code>`
|
||||
- `Slides`: CSS to apply to the wrapping Slide component
|
||||
- `components`: object of MDX components to render markdown
|
||||
- `Provider`: component for wrapping the entire app
|
||||
- `googleFont`: CSS HREF for adding a Google Font `<link>` tag
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
For more advanced customizations see the [Advanced Usage](advanced.md) docs.
|
||||
|
||||
[styled-components]: https://github.com/styled-components/styled-components
|
||||
[MDX]: https://github.com/mdx-js/mdx
|
||||
[emotion]: https://emotion.sh
|
||||
[mdx]: https://github.com/mdx-js/mdx
|
||||
[react-syntax-highlighter]: https://github.com/conorhastings/react-syntax-highlighter
|
||||
|
@ -1 +0,0 @@
|
||||
module.exports = require('./dist/layouts')
|
6
lerna.json
Normal file
6
lerna.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"packages": ["packages/*"],
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"version": "2.0.0-8"
|
||||
}
|
44
lib/build.js
44
lib/build.js
@ -1,44 +0,0 @@
|
||||
const webpack = require('webpack')
|
||||
const createConfig = require('./config')
|
||||
const renderHTML = require('./html')
|
||||
const log = require('./log')
|
||||
|
||||
const build = async (opts = {}) => {
|
||||
if (opts.html) {
|
||||
log('rendering static html')
|
||||
const { body, head, css } = await renderHTML(opts)
|
||||
Object.assign(opts, {
|
||||
body,
|
||||
head,
|
||||
css,
|
||||
})
|
||||
}
|
||||
|
||||
log('bundling js')
|
||||
const config = createConfig(opts)
|
||||
|
||||
config.mode = 'production'
|
||||
config.output = {
|
||||
path: opts.outDir
|
||||
}
|
||||
|
||||
const compiler = webpack(config)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
compiler.run((err, stats) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
return
|
||||
}
|
||||
|
||||
if (stats.compilation.errors && stats.compilation.errors.length) {
|
||||
reject(stats.compilation.errors);
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(stats)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = build
|
153
lib/config.js
153
lib/config.js
@ -1,153 +0,0 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const webpack = require('webpack')
|
||||
const HTMLPlugin = require('mini-html-webpack-plugin')
|
||||
const ProgressBarPlugin = require('progress-bar-webpack-plugin')
|
||||
const merge = require('webpack-merge')
|
||||
const chalk = require('chalk')
|
||||
const remark = {
|
||||
emoji: require('remark-emoji'),
|
||||
unwrapImages: require('remark-unwrap-images'),
|
||||
}
|
||||
|
||||
const babel = {
|
||||
presets: ['@babel/preset-env', '@babel/preset-react'].map(require.resolve),
|
||||
plugins: [
|
||||
'@babel/plugin-proposal-class-properties',
|
||||
'@babel/plugin-proposal-do-expressions',
|
||||
'@babel/plugin-proposal-export-default-from',
|
||||
'@babel/plugin-proposal-export-namespace-from',
|
||||
'@babel/plugin-proposal-function-bind',
|
||||
'@babel/plugin-proposal-function-sent',
|
||||
'@babel/plugin-proposal-json-strings',
|
||||
'@babel/plugin-proposal-logical-assignment-operators',
|
||||
'@babel/plugin-proposal-nullish-coalescing-operator',
|
||||
'@babel/plugin-proposal-numeric-separator',
|
||||
'@babel/plugin-proposal-optional-chaining',
|
||||
'@babel/plugin-proposal-throw-expressions',
|
||||
'@babel/plugin-syntax-dynamic-import',
|
||||
'@babel/plugin-syntax-import-meta',
|
||||
'babel-plugin-styled-components',
|
||||
].map(require.resolve),
|
||||
}
|
||||
|
||||
const rules = [
|
||||
{
|
||||
test: /\.jsx?$/,
|
||||
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,
|
||||
},
|
||||
{
|
||||
loader: require.resolve('./loader.js'),
|
||||
options: {
|
||||
mdPlugins: [remark.emoji, remark.unwrapImages],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const template = ({
|
||||
head = '<title>mdx-deck</title>',
|
||||
css = '',
|
||||
body = '',
|
||||
js,
|
||||
publicPath,
|
||||
}) => `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
<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>
|
||||
<meta name='generator' content='mdx-deck'>
|
||||
${head}${css}
|
||||
</head>
|
||||
<body>
|
||||
<div id=root>${body}</div>
|
||||
${HTMLPlugin.generateJSReferences(js, publicPath)}
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
|
||||
const baseConfig = {
|
||||
stats: 'errors-only',
|
||||
mode: 'development',
|
||||
module: {
|
||||
rules,
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.js', '.jsx'],
|
||||
alias: {
|
||||
'mdx-deck': path.resolve(__dirname, '..'),
|
||||
},
|
||||
modules: [
|
||||
path.relative(process.cwd(), path.join(__dirname, '../node_modules')),
|
||||
'node_modules',
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new ProgressBarPlugin({
|
||||
width: '24',
|
||||
complete: '█',
|
||||
incomplete: chalk.gray('░'),
|
||||
format: [
|
||||
chalk.magenta('[mdx-deck] :bar'),
|
||||
chalk.magenta(':percent'),
|
||||
chalk.gray(':elapseds :msg'),
|
||||
].join(' '),
|
||||
summary: false,
|
||||
customSummary: () => {},
|
||||
}),
|
||||
],
|
||||
}
|
||||
|
||||
const createConfig = (opts = {}) => {
|
||||
const config = merge(baseConfig, opts.webpack)
|
||||
config.context = opts.dirname
|
||||
|
||||
config.resolve.modules.push(
|
||||
opts.dirname,
|
||||
path.join(opts.dirname, 'node_modules')
|
||||
)
|
||||
|
||||
config.entry = [path.join(__dirname, '../dist/entry.js')]
|
||||
|
||||
const defs = Object.assign({}, opts.globals, {
|
||||
OPTIONS: JSON.stringify(opts),
|
||||
HOT_PORT: JSON.stringify(opts.hotPort),
|
||||
HOT_HOST: JSON.stringify(opts.host),
|
||||
})
|
||||
|
||||
config.plugins.push(
|
||||
new webpack.DefinePlugin(defs),
|
||||
new HTMLPlugin({ template, context: opts })
|
||||
)
|
||||
|
||||
if (config.resolve.alias) {
|
||||
const hotAlias = config.resolve.alias['webpack-hot-client/client']
|
||||
if (!fs.existsSync(hotAlias)) {
|
||||
const hotPath = path.dirname(require.resolve('webpack-hot-client/client'))
|
||||
config.resolve.alias['webpack-hot-client/client'] = hotPath
|
||||
}
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
module.exports = createConfig
|
47
lib/dev.js
47
lib/dev.js
@ -1,47 +0,0 @@
|
||||
const path = require('path')
|
||||
const Koa = require('koa')
|
||||
const getPort = require('get-port')
|
||||
const koaWebpack = require('koa-webpack')
|
||||
const koaStatic = require('koa-static')
|
||||
const createConfig = require('./config')
|
||||
|
||||
const devMiddleware = {
|
||||
publicPath: '/',
|
||||
clientLogLevel: 'error',
|
||||
stats: 'errors-only',
|
||||
logLevel: 'error',
|
||||
}
|
||||
|
||||
const start = async (opts = {}) => {
|
||||
const app = new Koa()
|
||||
opts.host = opts.host || 'localhost'
|
||||
opts.hotPort = opts.hotPort || await getPort()
|
||||
const hotClient = {
|
||||
port: opts.hotPort,
|
||||
host: opts.host,
|
||||
logLevel: 'error'
|
||||
}
|
||||
opts.dirname = opts.dirname || path.dirname(opts.entry)
|
||||
const config = createConfig(opts)
|
||||
config.entry.push(
|
||||
path.join(__dirname, './overlay.js')
|
||||
)
|
||||
|
||||
const middleware = await koaWebpack({
|
||||
config,
|
||||
devMiddleware,
|
||||
hotClient
|
||||
})
|
||||
const port = opts.port || await getPort()
|
||||
app.use(middleware)
|
||||
app.use(koaStatic(opts.dirname))
|
||||
|
||||
const server = app.listen(port, opts.host)
|
||||
return new Promise((resolve) => {
|
||||
middleware.devMiddleware.waitUntilValid(() => {
|
||||
resolve({ server, app, middleware, port })
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = start
|
64
lib/html.js
64
lib/html.js
@ -1,64 +0,0 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const React = require('react')
|
||||
const { renderToString, renderToStaticMarkup } = require('react-dom/server')
|
||||
const { ServerStyleSheet } = require('styled-components')
|
||||
const webpack = require('webpack')
|
||||
const nodeExternals = require('webpack-node-externals')
|
||||
const rimraf = require('rimraf')
|
||||
const mkdirp = require('mkdirp')
|
||||
const createConfig = require('./config')
|
||||
|
||||
const getApp = async opts => {
|
||||
opts.tempdir = path.join(opts.outDir, 'TEMP')
|
||||
|
||||
if (!fs.existsSync(opts.outDir)) mkdirp.sync(opts.outDir)
|
||||
if (!fs.existsSync(opts.tempdir)) mkdirp.sync(opts.tempdir)
|
||||
|
||||
const config = createConfig(opts)
|
||||
|
||||
config.output = {
|
||||
path: opts.tempdir,
|
||||
filename: '[name].js',
|
||||
libraryTarget: 'umd',
|
||||
}
|
||||
config.entry = {
|
||||
App: path.join(__dirname, '../dist/entry.js'),
|
||||
}
|
||||
config.target = 'node'
|
||||
config.externals = [
|
||||
nodeExternals({
|
||||
whitelist: ['mdx-deck', 'mdx-deck/themes', 'mdx-deck/layouts'],
|
||||
}),
|
||||
]
|
||||
|
||||
const compiler = webpack(config)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
compiler.run((err, stats) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
return
|
||||
}
|
||||
const App = require(path.resolve(opts.tempdir, './App.js')).default
|
||||
rimraf(opts.tempdir, err => {
|
||||
if (err) console.error(err)
|
||||
})
|
||||
resolve(App)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const renderHTML = async opts => {
|
||||
const App = await getApp(opts)
|
||||
const headTags = []
|
||||
const sheet = new ServerStyleSheet()
|
||||
const body = renderToString(
|
||||
sheet.collectStyles(React.createElement(App, { headTags }))
|
||||
)
|
||||
const head = renderToStaticMarkup(headTags)
|
||||
const css = sheet.getStyleTags()
|
||||
return { body, head, css }
|
||||
}
|
||||
|
||||
module.exports = renderHTML
|
@ -1,43 +0,0 @@
|
||||
const { getOptions } = require('loader-utils')
|
||||
const mdx = require('@mdx-js/mdx')
|
||||
const matter = require('gray-matter')
|
||||
const stringifyObject = require('stringify-object')
|
||||
const normalizeNewline = require('normalize-newline')
|
||||
|
||||
const SLIDEREG = /\n---\n/
|
||||
|
||||
module.exports = async function (src) {
|
||||
const callback = this.async()
|
||||
const options = getOptions(this) || {}
|
||||
options.skipExport = true
|
||||
|
||||
const { data, content } = matter(src)
|
||||
|
||||
const modules = []
|
||||
const slides = normalizeNewline(content)
|
||||
.split(SLIDEREG)
|
||||
.map(str => {
|
||||
const code = mdx.sync(str, options)
|
||||
const lines = code.split('\n')
|
||||
const tagIndex = lines.findIndex(str => /^</.test(str))
|
||||
modules.push(
|
||||
...lines.slice(0, tagIndex)
|
||||
.filter(Boolean)
|
||||
)
|
||||
const jsx = lines.slice(tagIndex).join('\n')
|
||||
|
||||
return `({ components, ...props }) => ${jsx}`
|
||||
})
|
||||
.map(str => str.trim())
|
||||
|
||||
const code = `import React from 'react'
|
||||
import { MDXTag } from '@mdx-js/tag'
|
||||
${modules.join('\n')}
|
||||
export const meta = ${stringifyObject(data)}
|
||||
|
||||
export default [
|
||||
${slides.join(',\n\n')}
|
||||
]`
|
||||
|
||||
return callback(null, code)
|
||||
}
|
16
lib/log.js
16
lib/log.js
@ -1,16 +0,0 @@
|
||||
const chalk = require('chalk')
|
||||
|
||||
const log = (...args) => {
|
||||
console.log(
|
||||
chalk.magenta('[mdx-deck]'),
|
||||
...args
|
||||
)
|
||||
}
|
||||
log.error = (...args) => {
|
||||
console.log(
|
||||
chalk.red('[err]'),
|
||||
...args
|
||||
)
|
||||
}
|
||||
|
||||
module.exports = log
|
@ -1,81 +0,0 @@
|
||||
const ansiHTML = require('ansi-html')
|
||||
const Entities = require('html-entities').AllHtmlEntities
|
||||
const entities = new Entities()
|
||||
|
||||
const colors = {
|
||||
reset: ['transparent', 'transparent'],
|
||||
black: '000000',
|
||||
red: 'FF0000',
|
||||
green: '00FF00',
|
||||
yellow: 'FFFF00',
|
||||
blue: '0000FF',
|
||||
magenta: 'FF00FF',
|
||||
cyan: '00FFFF',
|
||||
lightgrey: 'EEEEEE',
|
||||
darkgrey: '666666'
|
||||
};
|
||||
ansiHTML.setColors(colors)
|
||||
|
||||
let overlay
|
||||
|
||||
const style = (el, styles) => {
|
||||
for (const key in styles) {
|
||||
el.style[key] = styles[key]
|
||||
}
|
||||
return el
|
||||
}
|
||||
|
||||
const show = ({
|
||||
title = '',
|
||||
text = ''
|
||||
}) => {
|
||||
overlay = document.body.appendChild(
|
||||
document.createElement('pre')
|
||||
)
|
||||
style(overlay, {
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
boxSizing: 'border-box',
|
||||
fontFamily: 'Menlo, monospace',
|
||||
fontSize: '12px',
|
||||
overflow: 'auto',
|
||||
lineHeight: 1.5,
|
||||
padding: '8px',
|
||||
margin: 0,
|
||||
color: 'magenta',
|
||||
backgroundColor: 'black'
|
||||
})
|
||||
const code = ansiHTML(entities.encode(text))
|
||||
overlay.innerHTML = `<span>${title}</span>
|
||||
<br />
|
||||
<br />${code}
|
||||
`
|
||||
}
|
||||
|
||||
const destroy = () => {
|
||||
if (!overlay) return
|
||||
document.body.removeChild(overlay)
|
||||
}
|
||||
|
||||
const ws = new WebSocket('ws://' + HOT_HOST + ':' + HOT_PORT)
|
||||
|
||||
ws.addEventListener('message', msg => {
|
||||
const data = JSON.parse(msg.data)
|
||||
switch (data.type) {
|
||||
case 'errors':
|
||||
const [ text ] = data.data.errors
|
||||
console.error(data.data.errors)
|
||||
show({ title: 'failed to compile', text })
|
||||
break
|
||||
case 'ok':
|
||||
destroy()
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
ws.addEventListener('close', () => {
|
||||
show({ title: 'disconnected' })
|
||||
})
|
39
lib/pdf.js
39
lib/pdf.js
@ -1,39 +0,0 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const mkdirp = require('mkdirp')
|
||||
const puppeteer = require('puppeteer')
|
||||
|
||||
module.exports = async (opts = {}) => {
|
||||
// This can be used to control some UI components's visibility like: Appear,...
|
||||
|
||||
const {
|
||||
outDir,
|
||||
outFile = 'presentation.pdf',
|
||||
port,
|
||||
width = 1280,
|
||||
height = 960,
|
||||
sandbox = true,
|
||||
} = opts
|
||||
const args = sandbox ? [] : ['--no-sandbox', '--disable-setuid-sandbox']
|
||||
const browser = await puppeteer.launch({ args })
|
||||
const page = await browser.newPage()
|
||||
page.setUserAgent('MDX-Deck/1.7.7 Print/PDF')
|
||||
const filename = path.join(outDir, outFile)
|
||||
if (!fs.existsSync(outDir)) mkdirp.sync(outDir)
|
||||
|
||||
await page.goto('http://localhost:' + port, {
|
||||
waitUntil: 'networkidle2',
|
||||
})
|
||||
|
||||
await page.pdf({
|
||||
width,
|
||||
height,
|
||||
path: filename,
|
||||
scale: 1,
|
||||
printBackground: true,
|
||||
})
|
||||
|
||||
await browser.close()
|
||||
|
||||
return filename
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const mkdirp = require('mkdirp')
|
||||
const puppeteer = require('puppeteer')
|
||||
|
||||
module.exports = async (opts) => {
|
||||
const {
|
||||
width = 1280,
|
||||
height = 720,
|
||||
outFile = 'card.png',
|
||||
sandbox = true
|
||||
} = opts
|
||||
const file = path.join(opts.outDir, outFile)
|
||||
|
||||
if (!fs.existsSync(opts.outDir)) mkdirp.sync(opts.outDir)
|
||||
|
||||
const browser = await puppeteer.launch({
|
||||
args: [
|
||||
!sandbox && '--no-sandbox'
|
||||
].filter(Boolean)
|
||||
})
|
||||
const page = await browser.newPage()
|
||||
|
||||
await page.setViewport({ width, height })
|
||||
|
||||
await page.goto('http://localhost:' + opts.port, {
|
||||
waitUntil: 'networkidle2'
|
||||
})
|
||||
|
||||
await page.screenshot({
|
||||
path: file,
|
||||
type: 'png',
|
||||
clip: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width,
|
||||
height,
|
||||
}
|
||||
})
|
||||
|
||||
await browser.close()
|
||||
|
||||
return outFile
|
||||
}
|
11282
package-lock.json
generated
11282
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
123
package.json
123
package.json
@ -1,110 +1,29 @@
|
||||
{
|
||||
"name": "mdx-deck",
|
||||
"version": "1.10.2",
|
||||
"description": "MDX-based presentation decks",
|
||||
"main": "dist/index.js",
|
||||
"bin": {
|
||||
"mdx-deck": "./cli.js"
|
||||
},
|
||||
"private": true,
|
||||
"version": "2.0.0-0",
|
||||
"workspaces": [
|
||||
"packages/*",
|
||||
"templates/*",
|
||||
"docs"
|
||||
],
|
||||
"scripts": {
|
||||
"prepare": "npm run babel",
|
||||
"babel": "babel src -d dist",
|
||||
"watch": "babel src -d dist --watch",
|
||||
"start": "./cli.js docs/index.mdx -p 8080",
|
||||
"build": "./cli.js build docs/index.mdx -d site",
|
||||
"pdf": "./cli.js pdf docs/index.mdx -d site",
|
||||
"screenshot": "./cli.js screenshot docs/index.mdx -d docs",
|
||||
"help": "./cli.js",
|
||||
"start": "yarn workspace @mdx-deck/docs start",
|
||||
"build": "yarn workspace @mdx-deck/docs build",
|
||||
"test": "jest"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "Brent Jackson",
|
||||
"license": "MIT",
|
||||
"repository": "github:jxnblk/mdx-deck",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.0.0",
|
||||
"@babel/plugin-proposal-class-properties": "^7.0.0",
|
||||
"@babel/plugin-proposal-do-expressions": "^7.0.0",
|
||||
"@babel/plugin-proposal-export-default-from": "^7.0.0",
|
||||
"@babel/plugin-proposal-export-namespace-from": "^7.0.0",
|
||||
"@babel/plugin-proposal-function-bind": "^7.0.0",
|
||||
"@babel/plugin-proposal-function-sent": "^7.0.0",
|
||||
"@babel/plugin-proposal-json-strings": "^7.0.0",
|
||||
"@babel/plugin-proposal-logical-assignment-operators": "^7.0.0",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0",
|
||||
"@babel/plugin-proposal-numeric-separator": "^7.0.0",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.0.0",
|
||||
"@babel/plugin-proposal-throw-expressions": "^7.0.0",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
|
||||
"@babel/plugin-syntax-import-meta": "^7.0.0",
|
||||
"@babel/preset-env": "^7.0.0",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"@compositor/webfont": "^1.0.39",
|
||||
"@mdx-js/mdx": "^0.15.7",
|
||||
"@mdx-js/tag": "^0.15.6",
|
||||
"ansi-html": "0.0.7",
|
||||
"babel-loader": "^8.0.0",
|
||||
"babel-plugin-styled-components": "^1.10.0",
|
||||
"chalk": "^2.4.2",
|
||||
"clipboardy": "^1.2.3",
|
||||
"find-up": "^3.0.0",
|
||||
"get-port": "^4.1.0",
|
||||
"gray-matter": "^4.0.2",
|
||||
"hhmmss": "^1.0.0",
|
||||
"html-entities": "^1.2.1",
|
||||
"koa": "^2.7.0",
|
||||
"koa-static": "^5.0.0",
|
||||
"koa-webpack": "^5.2.1",
|
||||
"loader-utils": "^1.2.3",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.get": "^4.4.2",
|
||||
"meow": "^5.0.0",
|
||||
"mini-html-webpack-plugin": "^0.2.3",
|
||||
"mkdirp": "^0.5.1",
|
||||
"normalize-newline": "^3.0.0",
|
||||
"pkg-conf": "^2.1.0",
|
||||
"progress-bar-webpack-plugin": "^1.12.1",
|
||||
"prop-types": "^15.7.2",
|
||||
"puppeteer": "^1.12.2",
|
||||
"querystring": "^0.2.0",
|
||||
"react": "^16.8.2",
|
||||
"react-dev-utils": "^7.0.3",
|
||||
"react-dom": "^16.8.2",
|
||||
"react-swipeable": "^4.3.2",
|
||||
"react-syntax-highlighter": "^8.1.0",
|
||||
"remark-emoji": "^2.0.2",
|
||||
"remark-unwrap-images": "^0.1.0",
|
||||
"rimraf": "^2.6.3",
|
||||
"stringify-object": "^3.3.0",
|
||||
"styled-components": "^4.1.3",
|
||||
"styled-system": "^3.2.1",
|
||||
"superbox": "^2.1.0",
|
||||
"webpack": "^4.29.5",
|
||||
"webpack-hot-client": "^4.1.1",
|
||||
"webpack-merge": "^4.2.1",
|
||||
"webpack-node-externals": "^1.7.2"
|
||||
},
|
||||
"peerDependencies": {},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.0.0",
|
||||
"babel-jest": "^24.1.0",
|
||||
"@babel/core": "^7.3.4",
|
||||
"@babel/preset-env": "^7.3.4",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"husky": "^1.3.1",
|
||||
"jest": "^24.1.0",
|
||||
"jest-styled-components": "^6.3.1",
|
||||
"lint-staged": "^7.3.0",
|
||||
"mdx-deck-code-surfer": "^0.5.5",
|
||||
"prettier": "^1.16.4",
|
||||
"react-test-renderer": "^16.8.2"
|
||||
"jest": "^24.3.1",
|
||||
"lerna": "^3.13.1",
|
||||
"lint-staged": "^8.1.5",
|
||||
"prettier": "^1.16.4"
|
||||
},
|
||||
"jest": {
|
||||
"roots": [
|
||||
"<rootDir>/test/"
|
||||
],
|
||||
"testMatch": [
|
||||
"**/test/**/*.js"
|
||||
],
|
||||
"testURL": "http://localhost/",
|
||||
"coverageReporters": [
|
||||
"lcov",
|
||||
"html"
|
||||
]
|
||||
},
|
||||
@ -114,15 +33,9 @@
|
||||
}
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,json}": [
|
||||
"*.{md,js,json}": [
|
||||
"prettier --write",
|
||||
"git add"
|
||||
]
|
||||
},
|
||||
"prettier": {
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5",
|
||||
"jsxBracketSameLine": true
|
||||
}
|
||||
}
|
||||
|
5
packages/components/README.md
Normal file
5
packages/components/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# @mdx-deck/components
|
||||
|
||||
React components use in MDX Deck
|
||||
|
||||
https://github.com/jxnblk/mdx-deck
|
26
packages/components/package.json
Normal file
26
packages/components/package.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "@mdx-deck/components",
|
||||
"version": "2.0.0-8",
|
||||
"main": "src/index.js",
|
||||
"author": "Brent Jackson <jxnblk@gmail.com>",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@emotion/core": "^10.0.7",
|
||||
"@emotion/styled": "^10.0.7",
|
||||
"@mdx-js/tag": "^1.0.0-alpha.5",
|
||||
"@reach/router": "^1.2.1",
|
||||
"emotion-theming": "^10.0.7",
|
||||
"hhmmss": "^1.0.0",
|
||||
"lodash.merge": "^4.6.1",
|
||||
"react-swipeable": "^5.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@mdx-deck/thems": "^2.0.0-0",
|
||||
"@mdx-js/tag": "^0.18.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"react": "^16.8.3",
|
||||
"react-dom": "^16.8.3",
|
||||
"react-test-renderer": "^16.8.4"
|
||||
}
|
||||
}
|
21
packages/components/src/Appear.js
Normal file
21
packages/components/src/Appear.js
Normal file
@ -0,0 +1,21 @@
|
||||
import React from 'react'
|
||||
import useSteps from './useSteps'
|
||||
|
||||
export const Appear = props => {
|
||||
const arr = React.Children.toArray(props.children)
|
||||
const step = useSteps(arr.length)
|
||||
const children = arr.map((child, i) =>
|
||||
i < step
|
||||
? child
|
||||
: React.cloneElement(child, {
|
||||
style: {
|
||||
...child.props.style,
|
||||
visibility: 'hidden',
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
return <>{children}</>
|
||||
}
|
||||
|
||||
export default Appear
|
25
packages/components/src/Catch.js
Normal file
25
packages/components/src/Catch.js
Normal file
@ -0,0 +1,25 @@
|
||||
import React from 'react'
|
||||
|
||||
export class Catch extends React.Component {
|
||||
state = {
|
||||
err: null,
|
||||
}
|
||||
|
||||
componentDidCatch(err) {
|
||||
this.setState({ err })
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if (!this.state.err) return
|
||||
this.setState({ err: null })
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.err) {
|
||||
return <pre children={this.state.err.toString()} />
|
||||
}
|
||||
return <>{this.props.children}</>
|
||||
}
|
||||
}
|
||||
|
||||
export default Catch
|
57
packages/components/src/Clock.js
Normal file
57
packages/components/src/Clock.js
Normal file
@ -0,0 +1,57 @@
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import hhmmss from 'hhmmss'
|
||||
import Pre from './Pre'
|
||||
|
||||
const spacer = <div style={{ margin: 4 }} />
|
||||
|
||||
let timer
|
||||
|
||||
const Clock = props => {
|
||||
const [time, setTime] = useState(new Date().toLocaleTimeString())
|
||||
const [seconds, setSeconds] = useState(0)
|
||||
const [on, setTimer] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const tick = () => {
|
||||
const now = new Date()
|
||||
setTime(now.toLocaleTimeString())
|
||||
if (on) setSeconds(seconds + 1)
|
||||
}
|
||||
timer = setInterval(tick, 1000)
|
||||
return () => {
|
||||
clearInterval(timer)
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<button
|
||||
disabled={!seconds || on}
|
||||
onClick={e => {
|
||||
setSeconds(0)
|
||||
}}
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
{spacer}
|
||||
<button
|
||||
onClick={e => {
|
||||
setTimer(!on)
|
||||
}}
|
||||
>
|
||||
{on ? 'Pause' : 'Start'}
|
||||
</button>
|
||||
{spacer}
|
||||
<Pre>
|
||||
{hhmmss(seconds)} | {time}
|
||||
</Pre>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Clock
|
13
packages/components/src/GoogleFonts.js
Normal file
13
packages/components/src/GoogleFonts.js
Normal file
@ -0,0 +1,13 @@
|
||||
import React from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import { withTheme } from 'emotion-theming'
|
||||
|
||||
const GoogleFonts = withTheme(({ theme }) => {
|
||||
if (!theme.googleFont) return false
|
||||
return createPortal(
|
||||
<link rel="stylesheet" href={theme.googleFont} />,
|
||||
document.head
|
||||
)
|
||||
})
|
||||
|
||||
export default GoogleFonts
|
@ -1,49 +1,28 @@
|
||||
import React from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
|
||||
const noop = () => {
|
||||
console.warn('Missing HeadProvider')
|
||||
}
|
||||
|
||||
export const Context = React.createContext({
|
||||
export const HeadContext = React.createContext({
|
||||
tags: [],
|
||||
push: noop
|
||||
push: () => {
|
||||
console.warn('Missing HeadProvider')
|
||||
},
|
||||
})
|
||||
|
||||
export class HeadProvider extends React.Component {
|
||||
static defaultProps = {
|
||||
tags: []
|
||||
}
|
||||
|
||||
push = (elements) => {
|
||||
this.props.tags.push(...elements)
|
||||
}
|
||||
|
||||
render () {
|
||||
const context = {
|
||||
...this.props,
|
||||
push: this.push
|
||||
}
|
||||
|
||||
return (
|
||||
<Context.Provider value={context}>
|
||||
{this.props.children}
|
||||
</Context.Provider>
|
||||
)
|
||||
export const HeadProvider = ({ tags = [], children }) => {
|
||||
const push = elements => {
|
||||
tags.push(...elements)
|
||||
}
|
||||
const context = { push }
|
||||
return <HeadContext.Provider value={context}>{children}</HeadContext.Provider>
|
||||
}
|
||||
|
||||
export class Head extends React.Component {
|
||||
state = {
|
||||
didMount: false
|
||||
didMount: false,
|
||||
}
|
||||
|
||||
rehydrate = () => {
|
||||
const children = React.Children.toArray(this.props.children)
|
||||
const nodes = [
|
||||
...document.head.querySelectorAll('[data-head]')
|
||||
]
|
||||
|
||||
const nodes = [...document.head.querySelectorAll('[data-head]')]
|
||||
nodes.forEach(node => {
|
||||
node.remove()
|
||||
})
|
||||
@ -59,27 +38,22 @@ export class Head extends React.Component {
|
||||
if (meta) meta.remove()
|
||||
}
|
||||
})
|
||||
|
||||
this.setState({
|
||||
didMount: true
|
||||
})
|
||||
this.setState({ didMount: true })
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
componentDidMount() {
|
||||
this.rehydrate()
|
||||
}
|
||||
|
||||
render () {
|
||||
const children = React.Children.toArray(this.props.children)
|
||||
.map(child => React.cloneElement(child, {
|
||||
'data-head': true
|
||||
}))
|
||||
|
||||
const { didMount } = this.state
|
||||
|
||||
if (!didMount) {
|
||||
render() {
|
||||
const children = React.Children.toArray(this.props.children).map(child =>
|
||||
React.cloneElement(child, {
|
||||
'data-head': true,
|
||||
})
|
||||
)
|
||||
if (!this.state.didMount) {
|
||||
return (
|
||||
<Context.Consumer
|
||||
<HeadContext.Consumer
|
||||
children={({ push }) => {
|
||||
push(children)
|
||||
return false
|
||||
@ -87,10 +61,8 @@ export class Head extends React.Component {
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return createPortal(
|
||||
children,
|
||||
document.head
|
||||
)
|
||||
return createPortal(children, document.head)
|
||||
}
|
||||
}
|
||||
|
||||
export default Head
|
22
packages/components/src/Image.js
Normal file
22
packages/components/src/Image.js
Normal file
@ -0,0 +1,22 @@
|
||||
import styled from '@emotion/styled'
|
||||
|
||||
export const Image = styled.div(
|
||||
{
|
||||
backgroundPosition: 'center',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
},
|
||||
props => ({
|
||||
backgroundSize: props.size,
|
||||
width: props.width,
|
||||
height: props.height,
|
||||
backgroundImage: `url(${props.src})`,
|
||||
})
|
||||
)
|
||||
|
||||
Image.defaultProps = {
|
||||
size: 'cover',
|
||||
width: '100vw',
|
||||
height: '100vh',
|
||||
}
|
||||
|
||||
export default Image
|
257
packages/components/src/MDXDeck.js
Normal file
257
packages/components/src/MDXDeck.js
Normal file
@ -0,0 +1,257 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Router, globalHistory, navigate, Link } from '@reach/router'
|
||||
import { Swipeable } from 'react-swipeable'
|
||||
import querystring from 'querystring'
|
||||
import Provider from './Provider'
|
||||
import Slide from './Slide'
|
||||
import Presenter from './Presenter'
|
||||
import Overview from './Overview'
|
||||
import Print from './Print'
|
||||
import GoogleFonts from './GoogleFonts'
|
||||
import Catch from './Catch'
|
||||
|
||||
const NORMAL = 'normal'
|
||||
const PRESENTER = 'presenter'
|
||||
const OVERVIEW = 'overview'
|
||||
const PRINT = 'print'
|
||||
|
||||
const STORAGE_INDEX = 'mdx-slide'
|
||||
const STORAGE_STEP = 'mdx-step'
|
||||
|
||||
const keys = {
|
||||
right: 39,
|
||||
left: 37,
|
||||
space: 32,
|
||||
p: 80,
|
||||
o: 79,
|
||||
}
|
||||
|
||||
const toggleMode = key => state => ({
|
||||
mode: state.mode === key ? NORMAL : key,
|
||||
})
|
||||
|
||||
const BaseWrapper = props => <>{props.children}</>
|
||||
|
||||
const inputElements = ['INPUT', 'TEXTAREA', 'A', 'BUTTON']
|
||||
|
||||
export class MDXDeck extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
slides: props.slides,
|
||||
step: 0,
|
||||
mode: NORMAL,
|
||||
}
|
||||
}
|
||||
|
||||
handleKeyDown = e => {
|
||||
const { key, keyCode, metaKey, ctrlKey, altKey, shiftKey } = e
|
||||
const { activeElement } = document
|
||||
if (inputElements.includes(activeElement.tagName)) {
|
||||
return
|
||||
}
|
||||
if (metaKey || ctrlKey) return
|
||||
const alt = altKey && !shiftKey
|
||||
const shift = shiftKey && !altKey
|
||||
|
||||
const { pathname } = globalHistory.location
|
||||
if (keyCode === keys.p && shiftKey && altKey) {
|
||||
navigate('/print')
|
||||
this.setState({ mode: 'print' })
|
||||
}
|
||||
if (pathname === '/print') return
|
||||
|
||||
if (alt) {
|
||||
switch (keyCode) {
|
||||
case keys.p:
|
||||
this.setState(toggleMode(PRESENTER))
|
||||
break
|
||||
case keys.o:
|
||||
this.setState(toggleMode(OVERVIEW))
|
||||
break
|
||||
}
|
||||
} else {
|
||||
switch (keyCode) {
|
||||
case keys.left:
|
||||
e.preventDefault()
|
||||
this.previous()
|
||||
break
|
||||
case keys.right:
|
||||
case keys.space:
|
||||
e.preventDefault()
|
||||
this.next()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getIndex = () => {
|
||||
const { pathname } = globalHistory.location
|
||||
return Number(pathname.split('/')[1] || 0)
|
||||
}
|
||||
|
||||
getMeta = i => {
|
||||
const { slides } = this.state
|
||||
const { meta = {} } = slides[i] || {}
|
||||
return meta
|
||||
}
|
||||
|
||||
goto = i => {
|
||||
const current = this.getIndex()
|
||||
const reverse = i < current
|
||||
navigate('/' + i)
|
||||
const meta = this.getMeta(i)
|
||||
this.setState({
|
||||
step: reverse ? meta.steps || 0 : 0,
|
||||
})
|
||||
}
|
||||
|
||||
previous = () => {
|
||||
const { slides, step } = this.state
|
||||
const index = this.getIndex()
|
||||
const meta = this.getMeta(index)
|
||||
if (meta.steps && step > 0) {
|
||||
this.setState(state => ({
|
||||
step: state.step - 1,
|
||||
}))
|
||||
} else {
|
||||
const previous = index - 1
|
||||
if (previous < 0) return
|
||||
this.goto(previous)
|
||||
}
|
||||
}
|
||||
|
||||
next = () => {
|
||||
const { slides, step } = this.state
|
||||
const index = this.getIndex()
|
||||
const meta = this.getMeta(index)
|
||||
if (meta.steps && step < meta.steps) {
|
||||
this.setState(state => ({
|
||||
step: state.step + 1,
|
||||
}))
|
||||
} else {
|
||||
const next = index + 1
|
||||
if (next > slides.length - 1) return
|
||||
this.goto(next)
|
||||
}
|
||||
}
|
||||
|
||||
register = (index, meta) => {
|
||||
const { slides } = this.state
|
||||
const initialMeta = slides[index].meta || {}
|
||||
slides[index].meta = { ...initialMeta, ...meta }
|
||||
this.setState({ slides })
|
||||
}
|
||||
|
||||
handleStorageChange = e => {
|
||||
const { key } = e
|
||||
switch (key) {
|
||||
case STORAGE_INDEX:
|
||||
const index = parseInt(e.newValue, 10)
|
||||
this.goto(index)
|
||||
break
|
||||
case STORAGE_STEP:
|
||||
const step = parseInt(e.newValue, 10)
|
||||
this.setState({ step })
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
getMode = () => {
|
||||
const query = querystring.parse(
|
||||
globalHistory.location.search.replace(/^\?/, '')
|
||||
)
|
||||
this.setState(query)
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.body.addEventListener('keydown', this.handleKeyDown)
|
||||
window.addEventListener('storage', this.handleStorageChange)
|
||||
this.getMode()
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.body.removeEventListener('keydown', this.handleKeyDown)
|
||||
window.removeEventListener('storage', this.handleStorageChange)
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
const index = this.getIndex()
|
||||
const { step, mode } = this.state
|
||||
localStorage.setItem(STORAGE_INDEX, index)
|
||||
localStorage.setItem(STORAGE_STEP, step)
|
||||
if (mode !== NORMAL && mode !== PRINT) {
|
||||
const query = '?' + querystring.stringify({ mode })
|
||||
navigate(query)
|
||||
} else {
|
||||
const { pathname } = globalHistory.location
|
||||
navigate(pathname)
|
||||
}
|
||||
}
|
||||
|
||||
componentDidCatch(err) {
|
||||
console.error('componentDidCatch')
|
||||
console.error(err)
|
||||
}
|
||||
|
||||
render() {
|
||||
const { pathname } = globalHistory.location
|
||||
const { slides } = this.state
|
||||
const mode = pathname === '/print' ? PRINT : this.state.mode
|
||||
const index = this.getIndex()
|
||||
const meta = this.getMeta(index)
|
||||
const context = {
|
||||
...this.state,
|
||||
register: this.register,
|
||||
}
|
||||
|
||||
const [FirstSlide] = slides
|
||||
|
||||
let Wrapper = BaseWrapper
|
||||
switch (mode) {
|
||||
case PRESENTER:
|
||||
Wrapper = Presenter
|
||||
break
|
||||
case OVERVIEW:
|
||||
Wrapper = Overview
|
||||
break
|
||||
}
|
||||
|
||||
return (
|
||||
<Provider {...this.props} {...this.state} mode={mode} index={index}>
|
||||
<Catch>
|
||||
<GoogleFonts />
|
||||
<Wrapper {...this.state} index={index}>
|
||||
<Swipeable onSwipedRight={this.previous} onSwipedLeft={this.next}>
|
||||
<Router>
|
||||
<Slide path="/" index={0} {...context}>
|
||||
<FirstSlide path="/" />
|
||||
</Slide>
|
||||
{slides.map((Component, i) => (
|
||||
<Slide key={i} path={i + '/*'} index={i} {...context}>
|
||||
<Component path={i + '/*'} />
|
||||
</Slide>
|
||||
))}
|
||||
<Print path="/print" {...this.props} />
|
||||
</Router>
|
||||
</Swipeable>
|
||||
</Wrapper>
|
||||
</Catch>
|
||||
</Provider>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
MDXDeck.propTypes = {
|
||||
slides: PropTypes.array.isRequired,
|
||||
headTags: PropTypes.array.isRequired,
|
||||
}
|
||||
|
||||
MDXDeck.defaultProps = {
|
||||
slides: [],
|
||||
headTags: [],
|
||||
}
|
||||
|
||||
export default MDXDeck
|
25
packages/components/src/Notes.js
Normal file
25
packages/components/src/Notes.js
Normal file
@ -0,0 +1,25 @@
|
||||
import React from 'react'
|
||||
import { withContext } from './context'
|
||||
|
||||
export const Notes = withContext(
|
||||
class extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
const { context, children } = props
|
||||
if (
|
||||
!context ||
|
||||
typeof context.index === 'undefined' ||
|
||||
typeof context.register !== 'function'
|
||||
) {
|
||||
return
|
||||
}
|
||||
context.register(context.index, { notes: children })
|
||||
}
|
||||
|
||||
render() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
export default Notes
|
92
packages/components/src/Overview.js
Normal file
92
packages/components/src/Overview.js
Normal file
@ -0,0 +1,92 @@
|
||||
import React, { useEffect } from 'react'
|
||||
import { Location, navigate } from '@reach/router'
|
||||
import Zoom from './Zoom'
|
||||
import Slide from './Slide'
|
||||
import Pre from './Pre'
|
||||
|
||||
const getIndex = ({ pathname }) => {
|
||||
return Number(pathname.split('/')[1] || 0)
|
||||
}
|
||||
|
||||
const withLocation = Component => props => (
|
||||
<Location
|
||||
children={({ location }) => (
|
||||
<Component {...props} location={location} index={getIndex(location)} />
|
||||
)}
|
||||
/>
|
||||
)
|
||||
|
||||
export const Overview = withLocation(props => {
|
||||
const { index, slides } = props
|
||||
const activeThumb = React.createRef()
|
||||
|
||||
useEffect(() => {
|
||||
const el = activeThumb.current
|
||||
if (!el) return
|
||||
if (typeof el.scrollIntoViewIfNeeded === 'function') {
|
||||
el.scrollIntoViewIfNeeded()
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'flex-start',
|
||||
height: '100vh',
|
||||
color: 'white',
|
||||
backgroundColor: 'black',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
flex: 'none',
|
||||
height: '100vh',
|
||||
paddingLeft: 4,
|
||||
paddingRight: 4,
|
||||
overflowY: 'auto',
|
||||
marginRight: 'auto',
|
||||
}}
|
||||
>
|
||||
{slides.map((Component, i) => (
|
||||
<div
|
||||
ref={i === index ? activeThumb : null}
|
||||
key={i}
|
||||
onClick={e => {
|
||||
navigate('/' + i)
|
||||
}}
|
||||
style={{
|
||||
display: 'block',
|
||||
color: 'inherit',
|
||||
textDecoration: 'none',
|
||||
padding: 0,
|
||||
marginTop: 4,
|
||||
marginBottom: 4,
|
||||
cursor: 'pointer',
|
||||
outline: i === index ? '4px solid #0cf' : null,
|
||||
}}
|
||||
>
|
||||
<Zoom zoom={1 / 6}>
|
||||
<Slide>
|
||||
<Component />
|
||||
</Slide>
|
||||
</Zoom>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
width: 200 / 3 + '%',
|
||||
margin: 'auto',
|
||||
}}
|
||||
>
|
||||
<Zoom zoom={2 / 3}>{props.children}</Zoom>
|
||||
<Pre>
|
||||
{index} of {slides.length - 1}
|
||||
</Pre>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
export default Overview
|
13
packages/components/src/Pre.js
Normal file
13
packages/components/src/Pre.js
Normal file
@ -0,0 +1,13 @@
|
||||
import React from 'react'
|
||||
|
||||
export default props => (
|
||||
<pre
|
||||
{...props}
|
||||
style={{
|
||||
fontFamily: 'Menlo, monospace',
|
||||
fontSize: 18,
|
||||
whiteSpace: 'pre-wrap',
|
||||
...props.style,
|
||||
}}
|
||||
/>
|
||||
)
|
87
packages/components/src/Presenter.js
Normal file
87
packages/components/src/Presenter.js
Normal file
@ -0,0 +1,87 @@
|
||||
import React from 'react'
|
||||
import { globalHistory } from '@reach/router'
|
||||
import Zoom from './Zoom'
|
||||
import Slide from './Slide'
|
||||
import Pre from './Pre'
|
||||
import Clock from './Clock'
|
||||
|
||||
export const Presenter = props => {
|
||||
const { slides, index } = props
|
||||
const Current = slides[index]
|
||||
const Next = slides[index + 1]
|
||||
const { notes } = Current.meta || {}
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
color: 'white',
|
||||
backgroundColor: 'black',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100vh',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
marginTop: 'auto',
|
||||
marginBottom: 'auto',
|
||||
display: 'flex',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: 500 / 8 + '%',
|
||||
minWidth: 0,
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
}}
|
||||
>
|
||||
<Zoom zoom={5 / 8}>{props.children}</Zoom>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
width: 100 / 4 + '%',
|
||||
minWidth: 0,
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
}}
|
||||
>
|
||||
<Zoom zoom={1 / 4}>
|
||||
{Next && (
|
||||
<Slide>
|
||||
<Next />
|
||||
</Slide>
|
||||
)}
|
||||
</Zoom>
|
||||
<Pre>{notes}</Pre>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: 16,
|
||||
}}
|
||||
>
|
||||
<Pre>
|
||||
{index} of {slides.length - 1}
|
||||
</Pre>
|
||||
<div style={{ margin: 'auto' }} />
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href={globalHistory.location.origin + globalHistory.location.pathname}
|
||||
style={{
|
||||
color: 'inherit',
|
||||
}}
|
||||
>
|
||||
Open New Window
|
||||
</a>
|
||||
<div style={{ margin: 'auto' }} />
|
||||
<Clock />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Presenter
|
14
packages/components/src/Print.js
Normal file
14
packages/components/src/Print.js
Normal file
@ -0,0 +1,14 @@
|
||||
import React from 'react'
|
||||
import Slide from './Slide'
|
||||
|
||||
export const Print = props => (
|
||||
<>
|
||||
{props.slides.map((Component, i) => (
|
||||
<Slide key={i} index={i}>
|
||||
<Component />
|
||||
</Slide>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
|
||||
export default Print
|
55
packages/components/src/Provider.js
Normal file
55
packages/components/src/Provider.js
Normal file
@ -0,0 +1,55 @@
|
||||
import React from 'react'
|
||||
import { globalHistory } from '@reach/router'
|
||||
import { Global } from '@emotion/core'
|
||||
import { ThemeProvider } from 'emotion-theming'
|
||||
import merge from 'lodash.merge'
|
||||
import { HeadProvider } from './Head'
|
||||
import { MDXProvider } from '@mdx-js/tag'
|
||||
import defaultTheme from '@mdx-deck/themes'
|
||||
import mdxComponents from './mdx-components'
|
||||
|
||||
const DefaultProvider = props => <>{props.children}</>
|
||||
|
||||
const mergeThemes = themes =>
|
||||
themes.reduce(
|
||||
(acc, theme) =>
|
||||
typeof theme === 'function' ? theme(acc) : merge(acc, theme),
|
||||
{}
|
||||
)
|
||||
|
||||
export const Provider = props => {
|
||||
const { headTags, theme: baseTheme, themes = [] } = props
|
||||
const theme = mergeThemes([defaultTheme, baseTheme, ...themes])
|
||||
const {
|
||||
Provider: UserProvider = DefaultProvider,
|
||||
components: themeComponents = {},
|
||||
} = theme
|
||||
|
||||
const allComponents = {
|
||||
...mdxComponents,
|
||||
...themeComponents,
|
||||
}
|
||||
const style =
|
||||
props.mode !== 'print' ? (
|
||||
<Global
|
||||
styles={{
|
||||
body: {
|
||||
overflow: 'hidden',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
) : null
|
||||
|
||||
return (
|
||||
<HeadProvider tags={headTags}>
|
||||
{style}
|
||||
<ThemeProvider theme={theme}>
|
||||
<MDXProvider components={allComponents}>
|
||||
<UserProvider {...props} />
|
||||
</MDXProvider>
|
||||
</ThemeProvider>
|
||||
</HeadProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export default Provider
|
60
packages/components/src/Root.js
Normal file
60
packages/components/src/Root.js
Normal file
@ -0,0 +1,60 @@
|
||||
import styled from '@emotion/styled'
|
||||
|
||||
const themed = (...tags) => props =>
|
||||
tags.map(tag => props.theme[tag] && { ['& ' + tag]: props.theme[tag] })
|
||||
|
||||
const themedHeadings = props => ({
|
||||
'& h1, & h2, & h3, & h4, & h5, & h6': props.theme.heading,
|
||||
})
|
||||
|
||||
const themedLinks = props => ({
|
||||
'& a': {
|
||||
color: props.theme.colors.link,
|
||||
},
|
||||
})
|
||||
|
||||
// backwards compatibility
|
||||
const themedQuote = props => ({
|
||||
'& blockquote': props.theme.quote,
|
||||
})
|
||||
|
||||
const themedCode = props => ({
|
||||
'& code, & pre': {
|
||||
fontFamily: props.theme.monospace,
|
||||
color: props.theme.colors.code,
|
||||
background: props.theme.colors.codeBackground,
|
||||
},
|
||||
})
|
||||
|
||||
export const Root = styled.div(
|
||||
props => ({
|
||||
fontFamily: props.theme.font,
|
||||
color: props.theme.colors.text,
|
||||
backgroundColor: props.theme.colors.background,
|
||||
}),
|
||||
props => props.theme.css,
|
||||
themedLinks,
|
||||
themedHeadings,
|
||||
themedCode,
|
||||
themedQuote,
|
||||
themed(
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'h5',
|
||||
'h6',
|
||||
'a',
|
||||
'ul',
|
||||
'ol',
|
||||
'li',
|
||||
'p',
|
||||
'blockquote',
|
||||
'img',
|
||||
'table',
|
||||
'pre',
|
||||
'code'
|
||||
)
|
||||
)
|
||||
|
||||
export default Root
|
27
packages/components/src/Slide.js
Normal file
27
packages/components/src/Slide.js
Normal file
@ -0,0 +1,27 @@
|
||||
import React from 'react'
|
||||
import styled from '@emotion/styled'
|
||||
import Root from './Root'
|
||||
import { Context } from './context'
|
||||
|
||||
const SlideRoot = styled.div(
|
||||
{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: '100vw',
|
||||
height: '100vh',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
props => props.theme.Slide
|
||||
)
|
||||
|
||||
export const Slide = ({ children, ...props }) => (
|
||||
<Context.Provider value={props}>
|
||||
<Root>
|
||||
<SlideRoot>{children}</SlideRoot>
|
||||
</Root>
|
||||
</Context.Provider>
|
||||
)
|
||||
|
||||
export default Slide
|
21
packages/components/src/Steps.js
Normal file
21
packages/components/src/Steps.js
Normal file
@ -0,0 +1,21 @@
|
||||
import React from 'react'
|
||||
import { withContext } from './context'
|
||||
|
||||
export const Steps = withContext(
|
||||
class extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
const { register, index } = props.context
|
||||
const { length } = props
|
||||
if (typeof register !== 'function') return
|
||||
register(index, { steps: length })
|
||||
}
|
||||
render() {
|
||||
const { context, render } = this.props
|
||||
const { step } = context
|
||||
return render({ step })
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
export default Steps
|
25
packages/components/src/Zoom.js
Normal file
25
packages/components/src/Zoom.js
Normal file
@ -0,0 +1,25 @@
|
||||
import React from 'react'
|
||||
import styled from '@emotion/styled'
|
||||
|
||||
const ZoomRoot = styled.div(props => ({
|
||||
backgroundColor: props.theme.colors.background,
|
||||
width: 100 * props.zoom + 'vw',
|
||||
height: 100 * props.zoom + 'vh',
|
||||
}))
|
||||
|
||||
const ZoomInner = styled.div([], props => ({
|
||||
transformOrigin: '0 0',
|
||||
transform: `scale(${props.zoom})`,
|
||||
}))
|
||||
|
||||
export const Zoom = ({ zoom, ...props }) => (
|
||||
<ZoomRoot zoom={zoom}>
|
||||
<ZoomInner zoom={zoom} {...props} />
|
||||
</ZoomRoot>
|
||||
)
|
||||
|
||||
Zoom.defaultProps = {
|
||||
zoom: 1,
|
||||
}
|
||||
|
||||
export default Zoom
|
12
packages/components/src/__tests__/Appear.js
Normal file
12
packages/components/src/__tests__/Appear.js
Normal file
@ -0,0 +1,12 @@
|
||||
import React from 'react'
|
||||
import TestRenderer from 'react-test-renderer'
|
||||
import Appear from '../Appear'
|
||||
|
||||
test('Appear renders', () => {
|
||||
const json = TestRenderer.create(
|
||||
<Appear>
|
||||
<h1>Hello</h1>
|
||||
</Appear>
|
||||
).toJSON()
|
||||
expect(json).toMatchSnapshot()
|
||||
})
|
5
packages/components/src/__tests__/Clock.js
Normal file
5
packages/components/src/__tests__/Clock.js
Normal file
@ -0,0 +1,5 @@
|
||||
import React from 'react'
|
||||
import TestRenderer from 'react-test-renderer'
|
||||
import Clock from '../Clock'
|
||||
|
||||
test.todo('Clock renders')
|
5
packages/components/src/__tests__/GoogleFonts.js
Normal file
5
packages/components/src/__tests__/GoogleFonts.js
Normal file
@ -0,0 +1,5 @@
|
||||
import React from 'react'
|
||||
import TestRenderer from 'react-test-renderer'
|
||||
import GoogleFonts from '../GoogleFonts'
|
||||
|
||||
test.todo('GoogleFonts renders')
|
15
packages/components/src/__tests__/Head.js
Normal file
15
packages/components/src/__tests__/Head.js
Normal file
@ -0,0 +1,15 @@
|
||||
import React from 'react'
|
||||
import TestRenderer from 'react-test-renderer'
|
||||
import { Head, HeadProvider } from '../Head'
|
||||
|
||||
test.skip('Head populates HeadProvider‘s tag prop', () => {
|
||||
const tags = []
|
||||
TestRenderer.create(
|
||||
<HeadProvider tags={tags}>
|
||||
<Head>
|
||||
<title>Hello</title>
|
||||
</Head>
|
||||
</HeadProvider>
|
||||
)
|
||||
expect(tags.length).toBe(1)
|
||||
})
|
8
packages/components/src/__tests__/Pre.js
Normal file
8
packages/components/src/__tests__/Pre.js
Normal file
@ -0,0 +1,8 @@
|
||||
import React from 'react'
|
||||
import TestRenderer from 'react-test-renderer'
|
||||
import Pre from '../Pre'
|
||||
|
||||
test('Pre renders', () => {
|
||||
const json = TestRenderer.create(<Pre children="hi" />).toJSON()
|
||||
expect(json).toMatchSnapshot()
|
||||
})
|
8
packages/components/src/__tests__/Steps.js
Normal file
8
packages/components/src/__tests__/Steps.js
Normal file
@ -0,0 +1,8 @@
|
||||
import React from 'react'
|
||||
import TestRenderer from 'react-test-renderer'
|
||||
import Steps from '../Steps'
|
||||
|
||||
test('Steps renders', () => {
|
||||
const json = TestRenderer.create(<Steps render={() => 'hi'} />).toJSON()
|
||||
expect(json).toMatchSnapshot()
|
||||
})
|
@ -0,0 +1,13 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Appear renders 1`] = `
|
||||
<h1
|
||||
style={
|
||||
Object {
|
||||
"visibility": "hidden",
|
||||
}
|
||||
}
|
||||
>
|
||||
Hello
|
||||
</h1>
|
||||
`;
|
15
packages/components/src/__tests__/__snapshots__/Pre.js.snap
Normal file
15
packages/components/src/__tests__/__snapshots__/Pre.js.snap
Normal file
@ -0,0 +1,15 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Pre renders 1`] = `
|
||||
<pre
|
||||
style={
|
||||
Object {
|
||||
"fontFamily": "Menlo, monospace",
|
||||
"fontSize": 18,
|
||||
"whiteSpace": "pre-wrap",
|
||||
}
|
||||
}
|
||||
>
|
||||
hi
|
||||
</pre>
|
||||
`;
|
@ -0,0 +1,3 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Steps renders 1`] = `"hi"`;
|
11
packages/components/src/context.js
Normal file
11
packages/components/src/context.js
Normal file
@ -0,0 +1,11 @@
|
||||
import React, { useContext } from 'react'
|
||||
|
||||
export const Context = React.createContext({})
|
||||
|
||||
export const withContext = Component => props => (
|
||||
<Context.Consumer
|
||||
children={context => <Component {...props} context={context} />}
|
||||
/>
|
||||
)
|
||||
|
||||
export const useDeck = () => useContext(Context)
|
8
packages/components/src/index.js
Normal file
8
packages/components/src/index.js
Normal file
@ -0,0 +1,8 @@
|
||||
export { MDXDeck } from './MDXDeck'
|
||||
export { Head } from './Head'
|
||||
export { Image } from './Image'
|
||||
export { Notes } from './Notes'
|
||||
export { Steps } from './Steps'
|
||||
export { Appear } from './Appear'
|
||||
export { withContext, useDeck } from './context'
|
||||
export useSteps from './useSteps'
|
56
packages/components/src/mdx-components.js
Normal file
56
packages/components/src/mdx-components.js
Normal file
@ -0,0 +1,56 @@
|
||||
import React from 'react'
|
||||
import styled from '@emotion/styled'
|
||||
|
||||
export const inlineCode = styled.code(
|
||||
props => ({
|
||||
fontFamily: props.theme.monospace,
|
||||
}),
|
||||
props => props.theme.code
|
||||
)
|
||||
|
||||
export const code = styled.pre(
|
||||
props => ({
|
||||
fontFamily: props.theme.monospace,
|
||||
fontSize: '.75em',
|
||||
}),
|
||||
props => props.theme.pre
|
||||
)
|
||||
|
||||
export const img = styled.img({
|
||||
maxWidth: '100%',
|
||||
height: 'auto',
|
||||
objectFit: 'cover',
|
||||
})
|
||||
|
||||
export const TableWrap = styled.div({
|
||||
overflowX: 'auto',
|
||||
})
|
||||
export const Table = styled.table({
|
||||
width: '100%',
|
||||
borderCollapse: 'separate',
|
||||
borderSpacing: 0,
|
||||
'& td, & th': {
|
||||
textAlign: 'left',
|
||||
paddingRight: '.5em',
|
||||
paddingTop: '.25em',
|
||||
paddingBottom: '.25em',
|
||||
borderBottom: '1px solid',
|
||||
verticalAlign: 'top',
|
||||
},
|
||||
})
|
||||
|
||||
export const table = props => (
|
||||
<TableWrap>
|
||||
<Table {...props} />
|
||||
</TableWrap>
|
||||
)
|
||||
|
||||
export const components = {
|
||||
pre: props => props.children,
|
||||
code,
|
||||
inlineCode,
|
||||
img,
|
||||
table,
|
||||
}
|
||||
|
||||
export default components
|
13
packages/components/src/useSteps.js
Normal file
13
packages/components/src/useSteps.js
Normal file
@ -0,0 +1,13 @@
|
||||
import { useContext, useMemo } from 'react'
|
||||
import { Context } from './context'
|
||||
|
||||
export default length => {
|
||||
const context = useContext(Context)
|
||||
const { register, index, step } = context
|
||||
useMemo(() => {
|
||||
if (typeof register !== 'function') return
|
||||
register(index, { steps: length })
|
||||
}, [length])
|
||||
|
||||
return step
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
|
||||
# npm init deck
|
||||
|
||||
Create mdx-deck presentations
|
@ -15,28 +15,31 @@ log.error = (...args) => {
|
||||
|
||||
const template = 'jxnblk/mdx-deck/templates/basic'
|
||||
|
||||
const cli = meow(`
|
||||
const cli = meow(
|
||||
`
|
||||
Usage
|
||||
|
||||
$ npm init deck my-presentation
|
||||
|
||||
$ npx create-deck my-presentation
|
||||
|
||||
`, {
|
||||
booleanDefault: undefined,
|
||||
flags: {
|
||||
help: {
|
||||
type: 'boolean',
|
||||
alias: 'h'
|
||||
`,
|
||||
{
|
||||
booleanDefault: undefined,
|
||||
flags: {
|
||||
help: {
|
||||
type: 'boolean',
|
||||
alias: 'h',
|
||||
},
|
||||
version: {
|
||||
type: 'boolean',
|
||||
alias: 'v',
|
||||
},
|
||||
},
|
||||
version: {
|
||||
type: 'boolean',
|
||||
alias: 'v'
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
const [ name ] = cli.input
|
||||
const [name] = cli.input
|
||||
|
||||
if (!name) {
|
||||
cli.showHelp(0)
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-deck",
|
||||
"version": "1.0.0",
|
||||
"version": "2.0.0-1",
|
||||
"description": "Create mdx-deck presentations",
|
||||
"bin": {
|
||||
"create-deck": "cli.js"
|
1
packages/export/.gitignore
vendored
Normal file
1
packages/export/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
dist
|
28
packages/export/README.md
Normal file
28
packages/export/README.md
Normal file
@ -0,0 +1,28 @@
|
||||
# MDX Deck Export CLI
|
||||
|
||||
```sh
|
||||
npm i -D @mdx-deck/export
|
||||
```
|
||||
|
||||
Export as PDF
|
||||
|
||||
```sh
|
||||
mdx-deck-export pdf deck.mdx
|
||||
```
|
||||
|
||||
Export as PNG
|
||||
|
||||
```sh
|
||||
mdx-deck-export png deck.mdx
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
```
|
||||
-d --out-dir Output directory
|
||||
-f --out-file Output filename
|
||||
-p --port Server port
|
||||
-w --width Width in pixels
|
||||
-h --height Height in pixels
|
||||
--no-sandbox Disable puppeteer sandbox
|
||||
```
|
82
packages/export/cli.js
Executable file
82
packages/export/cli.js
Executable file
@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env node
|
||||
const path = require('path')
|
||||
const meow = require('meow')
|
||||
|
||||
const cli = meow(
|
||||
`
|
||||
|
||||
Usage:
|
||||
|
||||
$ mdx-deck-export pdf deck.mdx
|
||||
$ mdx-deck-export png deck.mdx
|
||||
|
||||
Options:
|
||||
|
||||
-d --out-dir Output directory
|
||||
-f --out-file Output filename
|
||||
-p --port Server port
|
||||
-w --width Width in pixels
|
||||
-h --height Height in pixels
|
||||
--no-sandbox Disable puppeteer sandbox
|
||||
|
||||
`,
|
||||
{
|
||||
flags: {
|
||||
outDir: {
|
||||
type: 'string',
|
||||
alias: 'd',
|
||||
default: 'dist',
|
||||
},
|
||||
outFile: {
|
||||
type: 'string',
|
||||
alias: 'f',
|
||||
default: 'presentation.pdf',
|
||||
},
|
||||
port: {
|
||||
type: 'string',
|
||||
alias: 'p',
|
||||
default: 8080,
|
||||
},
|
||||
width: {
|
||||
type: 'string',
|
||||
alias: 'w',
|
||||
default: 1280,
|
||||
},
|
||||
height: {
|
||||
type: 'string',
|
||||
alias: 'h',
|
||||
default: 960,
|
||||
},
|
||||
sandbox: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const [cmd, input] = cli.input
|
||||
|
||||
if (!input || !cmd) {
|
||||
cli.showHelp(0)
|
||||
}
|
||||
|
||||
const opts = Object.assign({}, cli.flags, {
|
||||
input,
|
||||
dirname: path.dirname(path.resolve(input)),
|
||||
globals: {
|
||||
FILENAME: JSON.stringify(path.resolve(input)),
|
||||
},
|
||||
host: 'localhost',
|
||||
type: cmd,
|
||||
})
|
||||
|
||||
require('./index')(opts)
|
||||
.then(filename => {
|
||||
console.log(`saved ${cmd} to`, filename)
|
||||
process.exit(0)
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err)
|
||||
process.exit(1)
|
||||
})
|
59
packages/export/index.js
Normal file
59
packages/export/index.js
Normal file
@ -0,0 +1,59 @@
|
||||
const path = require('path')
|
||||
const puppeteer = require('puppeteer')
|
||||
const mkdirp = require('mkdirp')
|
||||
const dev = require('mdx-deck/lib/dev')
|
||||
|
||||
module.exports = async opts => {
|
||||
const { type, outDir, outFile, port, width, height, sandbox } = opts
|
||||
|
||||
const args = []
|
||||
if (!sandbox) {
|
||||
args.push('--no-sandbox', '--disable-setuid-sandbox')
|
||||
}
|
||||
|
||||
const server = await dev(opts)
|
||||
|
||||
const browser = await puppeteer.launch({ args })
|
||||
const page = await browser.newPage()
|
||||
const filename = path.join(
|
||||
outDir,
|
||||
path.basename(outFile, path.extname(outFile)) + '.' + type
|
||||
)
|
||||
mkdirp.sync(outDir)
|
||||
|
||||
switch (type) {
|
||||
case 'pdf':
|
||||
await page.goto(`http://localhost:${port}/print`, {
|
||||
waitUntil: 'networkidle2',
|
||||
})
|
||||
await page.pdf({
|
||||
width,
|
||||
height,
|
||||
path: filename,
|
||||
scale: 1,
|
||||
printBackground: true,
|
||||
})
|
||||
break
|
||||
case 'png':
|
||||
await page.setViewport({ width, height })
|
||||
await page.goto('http://localhost:' + port, {
|
||||
waitUntil: 'networkidle2',
|
||||
})
|
||||
await page.screenshot({
|
||||
path: filename,
|
||||
type: 'png',
|
||||
clip: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width,
|
||||
height,
|
||||
},
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
await browser.close()
|
||||
await server.close()
|
||||
|
||||
return filename
|
||||
}
|
20
packages/export/package.json
Normal file
20
packages/export/package.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "@mdx-deck/export",
|
||||
"version": "2.0.0-8",
|
||||
"main": "index.js",
|
||||
"author": "Brent Jackson <jxnblk@gmail.com>",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"mdx-deck-export": "./cli.js"
|
||||
},
|
||||
"scripts": {
|
||||
"pdf": "./cli.js pdf ../../docs/demo.mdx",
|
||||
"png": "./cli.js png ../../docs/demo.mdx"
|
||||
},
|
||||
"dependencies": {
|
||||
"mdx-deck": "^2.0.0-8",
|
||||
"meow": "^5.0.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"puppeteer": "^1.13.0"
|
||||
}
|
||||
}
|
16
packages/html-plugin/README.md
Normal file
16
packages/html-plugin/README.md
Normal file
@ -0,0 +1,16 @@
|
||||
# @mdx-deck/webpack-html-plugin
|
||||
|
||||
Webpack plugin for generating HTML
|
||||
|
||||
```sh
|
||||
npm i @mdx-deck/webpack-html-plugin
|
||||
```
|
||||
|
||||
```js
|
||||
// webpack.config.js
|
||||
const HTMLPlugin = require('@mdx-deck/webpack-html-plugin')
|
||||
|
||||
module.exports = {
|
||||
plugins: [new HTMLPlugin()],
|
||||
}
|
||||
```
|
93
packages/html-plugin/index.js
Normal file
93
packages/html-plugin/index.js
Normal file
@ -0,0 +1,93 @@
|
||||
// based on mini-html-webpack-plugin
|
||||
const path = require('path')
|
||||
const { RawSource } = require('webpack-sources')
|
||||
|
||||
class HTMLPlugin {
|
||||
constructor(options = {}) {
|
||||
this.options = options
|
||||
this.plugin = this.plugin.bind(this)
|
||||
}
|
||||
|
||||
plugin(compilation, callback) {
|
||||
const { publicPath } = compilation.options.output
|
||||
const {
|
||||
filename = 'index.html',
|
||||
template = defaultTemplate,
|
||||
context,
|
||||
} = this.options
|
||||
|
||||
const files = getFiles(compilation.entrypoints)
|
||||
const links = generateCSSReferences(files.css, publicPath)
|
||||
const scripts = generateJSReferences(files.js, publicPath)
|
||||
const ctx = Object.assign(
|
||||
{},
|
||||
files,
|
||||
{
|
||||
publicPath,
|
||||
links,
|
||||
scripts,
|
||||
},
|
||||
context
|
||||
)
|
||||
|
||||
compilation.assets[filename] = new RawSource(template(ctx))
|
||||
|
||||
callback()
|
||||
}
|
||||
|
||||
apply(compiler) {
|
||||
compiler.hooks.emit.tapAsync('MDXDeckHTMLPlugin', this.plugin)
|
||||
}
|
||||
}
|
||||
|
||||
const getFiles = entrypoints => {
|
||||
const files = {}
|
||||
|
||||
entrypoints.forEach(entry => {
|
||||
entry.getFiles().forEach(file => {
|
||||
const extension = path.extname(file).replace(/\./, '')
|
||||
|
||||
if (!files[extension]) {
|
||||
files[extension] = []
|
||||
}
|
||||
|
||||
files[extension].push(file)
|
||||
})
|
||||
})
|
||||
|
||||
return files
|
||||
}
|
||||
|
||||
const defaultTemplate = ({
|
||||
links,
|
||||
scripts,
|
||||
title = '',
|
||||
body = '',
|
||||
head = '',
|
||||
css = '',
|
||||
publicPath,
|
||||
}) => `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
<meta name='viewport' content='width=device-width,initial-scale=1'>
|
||||
<style>*{box-sizing:border-box}body{margin:0;font-family:system-ui,sans-serif}</style>
|
||||
<meta name='generator' content='mdx-deck'>
|
||||
${head}${css}${links}
|
||||
</head>
|
||||
<body>
|
||||
<div id=root>${body}</div>
|
||||
${scripts}
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
const generateCSSReferences = (files = [], publicPath = '') =>
|
||||
files
|
||||
.map(file => `<link href='${publicPath + file}' rel='stylesheet'>`)
|
||||
.join('')
|
||||
|
||||
const generateJSReferences = (files = [], publicPath = '') =>
|
||||
files.map(file => `<script src='${publicPath + file}'></script>`).join('')
|
||||
|
||||
module.exports = HTMLPlugin
|
||||
module.exports.template = defaultTemplate
|
10
packages/html-plugin/package.json
Normal file
10
packages/html-plugin/package.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "@mdx-deck/webpack-html-plugin",
|
||||
"version": "2.0.0-1",
|
||||
"author": "Brent Jackson <jxnblk@gmail.com>",
|
||||
"license": "MIT",
|
||||
"repository": "github:jxnblk/mdx-deck",
|
||||
"dependencies": {
|
||||
"webpack-sources": "^1.3.0"
|
||||
}
|
||||
}
|
5
packages/layouts/README.md
Normal file
5
packages/layouts/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# @mdx-deck/layouts
|
||||
|
||||
Layout components used in MDX Deck
|
||||
|
||||
https://github.com/jxnblk/mdx-deck
|
12
packages/layouts/package.json
Normal file
12
packages/layouts/package.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "@mdx-deck/layouts",
|
||||
"version": "2.0.0-7",
|
||||
"main": "src/index.js",
|
||||
"author": "Brent Jackson <jxnblk@gmail.com>",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@emotion/core": "^10.0.7",
|
||||
"@emotion/styled": "^10.0.7",
|
||||
"styled-system": "^4.0.1"
|
||||
}
|
||||
}
|
12
packages/layouts/src/Box.js
Normal file
12
packages/layouts/src/Box.js
Normal file
@ -0,0 +1,12 @@
|
||||
import styled from '@emotion/styled'
|
||||
import { width, space, color } from 'styled-system'
|
||||
|
||||
export default styled.div(
|
||||
{
|
||||
flex: 'none',
|
||||
minWidth: 0,
|
||||
},
|
||||
width,
|
||||
space,
|
||||
color
|
||||
)
|
25
packages/layouts/src/Flex.js
Normal file
25
packages/layouts/src/Flex.js
Normal file
@ -0,0 +1,25 @@
|
||||
import styled from '@emotion/styled'
|
||||
import {
|
||||
alignItems,
|
||||
justifyContent,
|
||||
flexWrap,
|
||||
flexDirection,
|
||||
} from 'styled-system'
|
||||
import Box from './Box'
|
||||
|
||||
export const Flex = styled(Box)(
|
||||
{
|
||||
display: 'flex',
|
||||
},
|
||||
alignItems,
|
||||
justifyContent,
|
||||
flexWrap,
|
||||
flexDirection
|
||||
)
|
||||
|
||||
Flex.defaultProps = {
|
||||
justifyContent: 'center',
|
||||
flexDirection: 'row',
|
||||
}
|
||||
|
||||
export default Flex
|
@ -1,9 +1,7 @@
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import styled from '@emotion/styled'
|
||||
|
||||
const FullCode = styled.div({
|
||||
width: '100vw',
|
||||
height: '100vh',
|
||||
textAlign: 'left',
|
||||
'& pre': {
|
||||
// needed to override inline styles from syntax highlighting
|
24
packages/layouts/src/Horizontal.js
Normal file
24
packages/layouts/src/Horizontal.js
Normal file
@ -0,0 +1,24 @@
|
||||
import React from 'react'
|
||||
import Flex from './Flex'
|
||||
import Box from './Box'
|
||||
|
||||
const Horizontal = ({ children }) => {
|
||||
const kids = React.Children.toArray(children)
|
||||
|
||||
return (
|
||||
<Flex
|
||||
css={{
|
||||
alignItems: 'center',
|
||||
height: '100%',
|
||||
}}
|
||||
>
|
||||
{kids.map(child => (
|
||||
<Box key={child.key} width={1 / kids.length}>
|
||||
{child}
|
||||
</Box>
|
||||
))}
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
export default Horizontal
|
@ -1,5 +1,4 @@
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import styled from '@emotion/styled'
|
||||
import { color } from 'styled-system'
|
||||
|
||||
const Invert = styled.div(
|
||||
@ -7,6 +6,7 @@ const Invert = styled.div(
|
||||
width: '100vw',
|
||||
height: '100vh',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
'& a': {
|
||||
@ -21,4 +21,4 @@ Invert.defaultProps = {
|
||||
bg: 'text',
|
||||
}
|
||||
|
||||
export default props => <Invert {...props} />
|
||||
export default Invert
|
20
packages/layouts/src/Split.js
Normal file
20
packages/layouts/src/Split.js
Normal file
@ -0,0 +1,20 @@
|
||||
import React from 'react'
|
||||
import Flex from './Flex'
|
||||
import Box from './Box'
|
||||
|
||||
const Split = ({ children }) => {
|
||||
const [a, ...rest] = React.Children.toArray(children)
|
||||
return (
|
||||
<Flex
|
||||
css={{
|
||||
alignItems: 'center',
|
||||
height: '100%',
|
||||
}}
|
||||
>
|
||||
<Box width={1 / 2}>{a}</Box>
|
||||
<Box width={1 / 2}>{rest}</Box>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
export default Split
|
21
packages/layouts/src/SplitRight.js
Normal file
21
packages/layouts/src/SplitRight.js
Normal file
@ -0,0 +1,21 @@
|
||||
import React from 'react'
|
||||
import Flex from './Flex'
|
||||
import Box from './Box'
|
||||
|
||||
const SplitRight = ({ children }) => {
|
||||
const [a, ...rest] = React.Children.toArray(children)
|
||||
|
||||
return (
|
||||
<Flex
|
||||
css={{
|
||||
alignItems: 'center',
|
||||
height: '100%',
|
||||
}}
|
||||
>
|
||||
<Box width={1 / 2}>{rest}</Box>
|
||||
<Box width={1 / 2}>{a}</Box>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
export default SplitRight
|
5
packages/loader/README.md
Normal file
5
packages/loader/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# @mdx-deck/loader
|
||||
|
||||
Webpack loader used in MDX Deck
|
||||
|
||||
https://github.com/jxnblk/mdx-deck
|
18
packages/loader/index.js
Normal file
18
packages/loader/index.js
Normal file
@ -0,0 +1,18 @@
|
||||
const { getOptions } = require('loader-utils')
|
||||
const mdx = require('@mdx-js/mdx')
|
||||
const mdxPlugin = require('@mdx-deck/mdx-plugin')
|
||||
|
||||
module.exports = async function(src) {
|
||||
const callback = this.async()
|
||||
const options = getOptions(this) || {}
|
||||
options.mdPlugins = options.mdPlugins || []
|
||||
options.mdPlugins.push(mdxPlugin)
|
||||
|
||||
const result = mdx.sync(src, options)
|
||||
|
||||
const code = `/** @jsx mdx */
|
||||
import mdx from '@mdx-js/mdx/create-element'
|
||||
${result}`
|
||||
|
||||
return callback(null, code)
|
||||
}
|
12
packages/loader/package.json
Normal file
12
packages/loader/package.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "@mdx-deck/loader",
|
||||
"version": "2.0.0-7",
|
||||
"main": "index.js",
|
||||
"author": "Brent Jackson <jxnblk@gmail.com>",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@mdx-deck/mdx-plugin": "^2.0.0-7",
|
||||
"@mdx-js/mdx": "^1.0.0-alpha.5",
|
||||
"loader-utils": "^1.2.3"
|
||||
}
|
||||
}
|
4
packages/mdx-deck/README.md
Normal file
4
packages/mdx-deck/README.md
Normal file
@ -0,0 +1,4 @@
|
||||
|
||||
# MDX Deck
|
||||
|
||||
https://github.com/jxnblk/mdx-deck
|
13
packages/mdx-deck/babel.config.js
Normal file
13
packages/mdx-deck/babel.config.js
Normal file
@ -0,0 +1,13 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@babel/preset-env',
|
||||
'@babel/preset-react',
|
||||
'@emotion/babel-preset-css-prop',
|
||||
].map(require.resolve),
|
||||
plugins: [
|
||||
'@babel/plugin-proposal-class-properties',
|
||||
'@babel/plugin-proposal-export-default-from',
|
||||
'@babel/plugin-proposal-export-namespace-from',
|
||||
'@babel/plugin-syntax-dynamic-import',
|
||||
].map(require.resolve),
|
||||
}
|
121
packages/mdx-deck/cli.js
Executable file
121
packages/mdx-deck/cli.js
Executable file
@ -0,0 +1,121 @@
|
||||
#!/usr/bin/env node
|
||||
const path = require('path')
|
||||
const meow = require('meow')
|
||||
const findup = require('find-up')
|
||||
const open = require('react-dev-utils/openBrowser')
|
||||
const chalk = require('chalk')
|
||||
const pkg = require('./package.json')
|
||||
|
||||
const config = require('pkg-conf').sync('mdx-deck')
|
||||
|
||||
const log = (...args) => {
|
||||
console.log(chalk.green('[mdx-deck]'), ...args)
|
||||
}
|
||||
log.error = (...args) => {
|
||||
console.log(chalk.red('[err]'), ...args)
|
||||
}
|
||||
|
||||
const cli = meow(
|
||||
`
|
||||
${chalk.gray('Usage')}
|
||||
|
||||
$ ${chalk.green('mdx-deck deck.mdx')}
|
||||
|
||||
$ ${chalk.green('mdx-deck build deck.mdx')}
|
||||
|
||||
${chalk.gray('Options')}
|
||||
|
||||
-h --host Dev server host
|
||||
-p --port Dev server port
|
||||
--no-open Prevent from opening in default browser
|
||||
--webpack Path to webpack config file
|
||||
-d --out-dir Output directory for exporting
|
||||
|
||||
`,
|
||||
{
|
||||
description: chalk.green('[mdx-deck] ') + chalk.gray(pkg.description),
|
||||
flags: {
|
||||
port: {
|
||||
type: 'string',
|
||||
alias: 'p',
|
||||
},
|
||||
host: {
|
||||
type: 'string',
|
||||
alias: 'h',
|
||||
},
|
||||
open: {
|
||||
type: 'boolean',
|
||||
alias: 'o',
|
||||
default: true,
|
||||
},
|
||||
outDir: {
|
||||
type: 'string',
|
||||
alias: 'd',
|
||||
},
|
||||
webpack: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const [cmd, file] = cli.input
|
||||
const doc = file || cmd
|
||||
|
||||
if (!doc) cli.showHelp(0)
|
||||
|
||||
const opts = Object.assign(
|
||||
{
|
||||
dirname: path.dirname(path.resolve(doc)),
|
||||
globals: {
|
||||
FILENAME: JSON.stringify(path.resolve(doc)),
|
||||
},
|
||||
host: 'localhost',
|
||||
port: 8080,
|
||||
outDir: 'dist',
|
||||
},
|
||||
config,
|
||||
cli.flags
|
||||
)
|
||||
|
||||
opts.outDir = path.resolve(opts.outDir)
|
||||
if (opts.webpack) {
|
||||
opts.webpack = require(path.resolve(opts.webpack))
|
||||
} else {
|
||||
const webpackConfig = findup.sync('webpack.config.js', { cwd: opts.dirname })
|
||||
if (webpackConfig) opts.webpack = require(webpackConfig)
|
||||
}
|
||||
|
||||
let dev
|
||||
|
||||
switch (cmd) {
|
||||
case 'build':
|
||||
log('building')
|
||||
const build = require('./lib/build')
|
||||
build(opts)
|
||||
.then(res => {
|
||||
log('done')
|
||||
process.exit(0)
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err)
|
||||
process.exit(1)
|
||||
})
|
||||
break
|
||||
case 'dev':
|
||||
default:
|
||||
log('starting dev server')
|
||||
dev = require('./lib/dev')
|
||||
dev(opts)
|
||||
.then(server => {
|
||||
const { address, port } = server.address()
|
||||
const url = `http://localhost:${port}`
|
||||
if (opts.open) open(url)
|
||||
log('listening on', chalk.green(url))
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err)
|
||||
process.exit(1)
|
||||
})
|
||||
break
|
||||
}
|
111
packages/mdx-deck/demo.mdx
Normal file
111
packages/mdx-deck/demo.mdx
Normal file
@ -0,0 +1,111 @@
|
||||
import { Head, Image, Notes, Appear, Steps } from '@mdx-deck/components'
|
||||
|
||||
export const themes = []
|
||||
|
||||
<Head>
|
||||
<title>MDX Deck Demo</title>
|
||||
</Head>
|
||||
|
||||
# Hello MDX Deck
|
||||
|
||||
---
|
||||
|
||||
```jsx
|
||||
import React from 'react'
|
||||
|
||||
export default props =>
|
||||
<div style={{ color: 'red' }}>
|
||||
{props.children}
|
||||
</div>
|
||||
|
||||
export const Beep = props =>
|
||||
<Hello>
|
||||
{props.children}
|
||||
</Hello>
|
||||
|
||||
```
|
||||
|
||||
## This is v2
|
||||
|
||||
---
|
||||
|
||||
## What's New
|
||||
|
||||
<ul>
|
||||
<Appear>
|
||||
<li>Reach Router</li>
|
||||
<li>Less opinionated styles</li>
|
||||
<li>more stuff</li>
|
||||
</Appear>
|
||||
</ul>
|
||||
|
||||
---
|
||||
|
||||
<Image
|
||||
src='https://source.unsplash.com/random/1024x768'
|
||||
size='contain'
|
||||
/>
|
||||
|
||||
---
|
||||
|
||||
## This slide has notes
|
||||
|
||||
<Notes>
|
||||
Hello, secret speaker notes
|
||||
</Notes>
|
||||
|
||||
---
|
||||
|
||||
```js
|
||||
const codeExample = require('./code-example')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## More Appear
|
||||
|
||||
<Appear>
|
||||
<div>One</div>
|
||||
<div>Two</div>
|
||||
<div>Three</div>
|
||||
<div>Four</div>
|
||||
<div>Five</div>
|
||||
<div>Six</div>
|
||||
</Appear>
|
||||
|
||||
---
|
||||
|
||||
## Steps Components
|
||||
|
||||
<Steps
|
||||
length={3}
|
||||
render={({ step }) => (
|
||||
<pre>
|
||||
Step: {step}
|
||||
</pre>
|
||||
)}
|
||||
/>
|
||||
|
||||
---
|
||||
|
||||
export default props =>
|
||||
<div
|
||||
style={{
|
||||
width: '100vw',
|
||||
height: '100vh',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: 'tomato'
|
||||
}}>
|
||||
{props.children}
|
||||
</div>
|
||||
|
||||
## With a (tomato) layout
|
||||
|
||||
---
|
||||
|
||||
## Last Slide
|
||||
|
||||
|
3
packages/mdx-deck/index.js
Normal file
3
packages/mdx-deck/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
export * from '@mdx-deck/components'
|
||||
export * as themes from '@mdx-deck/themes'
|
||||
export * as Layouts from '@mdx-deck/layouts'
|
1
packages/mdx-deck/layouts.js
Normal file
1
packages/mdx-deck/layouts.js
Normal file
@ -0,0 +1 @@
|
||||
export * from '@mdx-deck/layouts'
|
89
packages/mdx-deck/lib/build.js
Normal file
89
packages/mdx-deck/lib/build.js
Normal file
@ -0,0 +1,89 @@
|
||||
const path = require('path')
|
||||
const webpack = require('webpack')
|
||||
const React = require('react')
|
||||
const { renderToString, renderToStaticMarkup } = require('react-dom/server')
|
||||
const HTMLPlugin = require('@mdx-deck/webpack-html-plugin')
|
||||
const createConfig = require('./config')
|
||||
|
||||
const getApp = async (config, opts) => {
|
||||
const serverConfig = Object.assign({}, config, {
|
||||
target: 'node',
|
||||
output: {
|
||||
path: opts.outDir,
|
||||
filename: '__app.js',
|
||||
libraryTarget: 'umd',
|
||||
},
|
||||
externals: ['react', 'react-dom'],
|
||||
})
|
||||
const compiler = webpack(serverConfig)
|
||||
return new Promise((resolve, reject) => {
|
||||
compiler.run((err, stats) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
return
|
||||
}
|
||||
if (stats.compilation.errors && stats.compilation.errors.length) {
|
||||
reject(stats.compilation.errors)
|
||||
return
|
||||
}
|
||||
|
||||
const filename = path.resolve(opts.outDir, './__app.js')
|
||||
const App = require(filename).default
|
||||
resolve(App)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const renderHTML = async App => {
|
||||
const headTags = []
|
||||
const body = renderToString(
|
||||
React.createElement(App, {
|
||||
headTags,
|
||||
})
|
||||
)
|
||||
const head = renderToStaticMarkup(
|
||||
React.createElement(React.Fragment, null, headTags)
|
||||
)
|
||||
return { body, head }
|
||||
}
|
||||
|
||||
const build = async (opts = {}) => {
|
||||
const config = createConfig(opts)
|
||||
|
||||
const App = await getApp(config, opts)
|
||||
const { body, head } = await renderHTML(App)
|
||||
|
||||
config.mode = 'production'
|
||||
config.output = {
|
||||
path: opts.outDir,
|
||||
}
|
||||
|
||||
config.plugins.push(
|
||||
new HTMLPlugin({
|
||||
context: {
|
||||
head,
|
||||
body,
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
const compiler = webpack(config)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
compiler.run((err, stats) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
return
|
||||
}
|
||||
|
||||
if (stats.compilation.errors && stats.compilation.errors.length) {
|
||||
reject(stats.compilation.errors)
|
||||
return
|
||||
}
|
||||
|
||||
resolve(stats)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = build
|
101
packages/mdx-deck/lib/config.js
Normal file
101
packages/mdx-deck/lib/config.js
Normal file
@ -0,0 +1,101 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const webpack = require('webpack')
|
||||
const WebpackBar = require('webpackbar')
|
||||
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
|
||||
const merge = require('webpack-merge')
|
||||
const chalk = require('chalk')
|
||||
const remark = {
|
||||
emoji: require('remark-emoji'),
|
||||
unwrapImages: require('remark-unwrap-images'),
|
||||
}
|
||||
const HTMLPlugin = require('@mdx-deck/webpack-html-plugin')
|
||||
const babel = require('../babel.config')
|
||||
|
||||
const rules = [
|
||||
{
|
||||
test: /\.jsx?$/,
|
||||
exclude: /node_modules/,
|
||||
loader: require.resolve('babel-loader'),
|
||||
options: babel,
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: path.resolve(__dirname, '../node_modules'),
|
||||
include: [path.resolve(__dirname, '..'), /@mdx\-deck/],
|
||||
loader: require.resolve('babel-loader'),
|
||||
options: babel,
|
||||
},
|
||||
{
|
||||
test: /\.mdx?$/,
|
||||
exclude: /node_modules/,
|
||||
use: [
|
||||
{
|
||||
loader: require.resolve('babel-loader'),
|
||||
options: babel,
|
||||
},
|
||||
{
|
||||
loader: require.resolve('@mdx-deck/loader'),
|
||||
options: {
|
||||
mdPlugins: [remark.emoji, remark.unwrapImages],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: ['style-loader', 'css-loader'].map(require.resolve),
|
||||
},
|
||||
]
|
||||
|
||||
const baseConfig = {
|
||||
stats: 'errors-only',
|
||||
mode: 'development',
|
||||
module: {
|
||||
rules,
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.js', '.jsx'],
|
||||
alias: {
|
||||
// 'mdx-deck': path.resolve(__dirname, '..'),
|
||||
'webpack-hot-middleware/client': path.resolve(
|
||||
require.resolve('webpack-hot-middleware/client')
|
||||
),
|
||||
},
|
||||
modules: [
|
||||
path.relative(process.cwd(), path.join(__dirname, '../node_modules')),
|
||||
'node_modules',
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new WebpackBar({
|
||||
name: '[mdx-deck]',
|
||||
}),
|
||||
new FriendlyErrorsPlugin(),
|
||||
],
|
||||
}
|
||||
|
||||
const createConfig = (opts = {}) => {
|
||||
const config = merge(baseConfig, opts.webpack)
|
||||
config.context = opts.dirname
|
||||
|
||||
config.resolve.modules.push(
|
||||
opts.dirname,
|
||||
path.join(opts.dirname, 'node_modules')
|
||||
)
|
||||
|
||||
config.entry = [path.join(__dirname, './entry.js')]
|
||||
|
||||
const defs = Object.assign({}, opts.globals, {
|
||||
OPTIONS: JSON.stringify(opts),
|
||||
})
|
||||
|
||||
config.plugins.push(
|
||||
new webpack.DefinePlugin(defs),
|
||||
new HTMLPlugin({ context: opts })
|
||||
)
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
module.exports = createConfig
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user